深度解析 VESC 参数辨识源码:电阻、电感与磁链

深度解析 VESC 参数辨识源码:电阻、电感与磁链

一、 为什么我们要辨识电机参数?

在 FOC 的控制架构中,这三个参数扮演着决定生死的角色:

  1. 电阻 RRR 和 电感 LLL :决定了电流环 PI 控制器的增益(Kp=L⋅BW,Ki=R⋅BWK_p = L \cdot BW, K_i = R \cdot BWKp=L⋅BW,Ki=R⋅BW),以及 D/Q 轴的解耦前馈补偿。如果没有准确的 RRR 和 LLL,电流环在高速下极易震荡失控。
  2. 磁链 λ\lambdaλ:无感观测器(如 SMO 或 Luenberger)计算转子角度的"绝对基准"。磁链如果测不准,电机在低速带载时必然卡死,高速必失步。

二、 电阻与电感的联合辨识

在 VESC 中,电阻和电感的测量是一起进行的。其核心思想是:先用小电流试探,找到一个合适的直流偏置电流,测出电阻 RRR;然后在这个直流偏置的基础上叠加高频脉冲,测出电感 LLL。

1. 自动试探:寻找完美的测试电流

VESC 不会一上来就给电机通大电流,而是从 2A 开始指数级(乘 1.5 倍)递增试探,直到产生的电压降足够大,保证采样的信噪比。

c 复制代码
// mcpwm_foc.c (精简版源码解析)
int mcpwm_foc_measure_res_ind(float *res, float *ind) {
    // 1. 设置极度保守的 PI 参数,防止测试时电流震荡
    motor->m_conf->foc_current_kp = 0.001;
    motor->m_conf->foc_current_ki = 1.0;

    float i_last = 0.0;
    // 2. 核心试探循环:从 2A 开始,每次乘 1.5 倍
    for (float i = 2.0; i < (motor->m_conf->l_current_max / 2.0); i *= 1.5) {
        float r_tmp = 0.0;
        // 尝试在这个电流下测量电阻
        mcpwm_foc_measure_resistance(i, 20, false, &r_tmp);
        
        // 【功率信噪比判断】:希望 I * R 的压降大于 1V
        if (i > (1.0 / r_tmp)) {
            i_last = i; // 找到了完美的测试电流!
            break;
        }
    }
    // ... 后续正式测量
}

2. 电阻辨识 (RRR):

直流注入法找到合适的电流 i_last 后,VESC 使用极其直白的欧姆定律:在 D 轴(不产生扭矩)闭环注入这个直流电流,等电流稳定后,多次采样电压和电流求平均。

C 复制代码
// 伪代码:直流注入求电阻
for (int i = 0; i < samples; i++) {
    motor->m_motor_state.id_target = current; // D 轴闭环
    chThdSleepMicroseconds(10); // 等待一个周期
    sum_v += motor->m_motor_state.vd; // 累加指令电压
    sum_i += motor->m_motor_state.id_filter; // 累加真实反馈电流
}
// R = 平均电压 / 平均电流
*res = (sum_v / samples) / (sum_i / samples);

避坑指南: 这里的指令电压包含了死区压降。但由于 VESC 用了比较大的电流(找到了能产生 >1V 压降的电流),所以死区造成的误差率被大大降低了。

3. 电感辨识 (LLL):

大脉冲与 IR 补偿这是移植时最容易翻车的重灾区。 很多自编的 FOC 代码在 0A 附近用正负方波测电感,结果因为死区补偿过零点震荡,测出来的电感乱跳。

VESC 的神级操作是:

保持之前的直流偏置大电流(例如 5A)不断,在此基础上突然叠加一个 5V 的电压脉冲。 这使得电流始终为正,彻底避开了过零点死区抽风!

C 复制代码
// 伪代码:VESC 的电感测量核心逻辑
// 1. 先把电流拉到直流偏置点 i_last
mcpwm_foc_set_current(i_last); 
float i_start = motor->m_motor_state.id_filter;
float v_start = motor->m_motor_state.vd;

// 2. 突然在原电压上叠加一个大脉冲 (比如 5V)
float v_pulse = v_start + 5.0; 
motor->m_motor_state.vd = v_pulse;
mcpwm_foc_update_pwm(true); // 强行输出并等待一拍

// 3. 读取脉冲后的电流,计算 di (电流变化量)
float i_end = motor->m_motor_state.id_meas;
float di = i_end - i_start;
float dt = 1.0 / pwm_rate;

// 4. 计算电感公式:L = (V_总 - I_初 * R) * dt / di
float L = (v_pulse - (i_start * r)) * dt / di;

因为加了大脉冲,ΔI\Delta IΔI 很大,所以必须用 (i_start * r) 把刚才直流偏置在这段电阻上吃掉的电压扣除。这就是严谨的物理建模。

三、 磁链辨识

磁链 λ\lambdaλ 必须在电机转动时测量。为了测准这个参数,VESC 祭出了数学上的终极大招:反电动势面积积分法

为了看懂这个大招有多精妙,我们先来看看传统方法是怎么"碰壁"的。

1. 传统稳态方程的困境

在 FOC 旋转坐标系(D-Q 轴)下,Q 轴电压的标准方程为:
Vq=Iq⋅R+LqdIqdt+ωel⋅Ld⋅Id+ωel⋅λV_q = I_q \cdot R + L_q \frac{dI_q}{dt} + \omega_{el} \cdot L_d \cdot I_d + \omega_{el} \cdot \lambdaVq=Iq⋅R+LqdtdIq+ωel⋅Ld⋅Id+ωel⋅λ

传统辨识方法通常是让电机开环匀速旋转,然后让 Id=0I_d = 0Id=0。此时方程简化为:
Vq=Iq⋅R+ωel⋅λ  ⟹  λ=Vq−Iq⋅RωelV_q = I_q \cdot R + \omega_{el} \cdot \lambda \implies \lambda = \frac{V_q - I_q \cdot R}{\omega_{el}}Vq=Iq⋅R+ωel⋅λ⟹λ=ωelVq−Iq⋅R

致命痛点出现了: 如果没有相电压采样电路,你只能靠 MCU 给出的指令电压 VqV_qVq 来算,里面包含了大量的死区非线性误差(Iq⋅RI_q \cdot RIq⋅R 也很容易测不准)。

为了获得极高的信噪比,VESC 采取了 断电飞轮滑行(Freewheeling) 的策略。即瞬间关闭所有 MOSFET,让 Iq=0,Id=0I_q = 0, I_d = 0Iq=0,Id=0。此时方程极其极简:
Vq=ωel⋅λV_q = \omega_{el} \cdot \lambdaVq=ωel⋅λ

但这又引出了一个物理悖论:电机断电后,受摩擦力影响,转速 ωel\omega_{el}ωel 是一直在衰减的! 你如果在这个滑行过程中采样,公式 λ=Vqωel(t)\lambda = \frac{V_q}{\omega_{el}(t)}λ=ωel(t)Vq 里的分母一直在变,你怎么保证算出来的磁链是准确的常量?

2. 数学降维:换元积分法

既然转速 ωel(t)\omega_{el}(t)ωel(t) 这个时间变量这么烦人,VESC 选择在数学层面直接将其"抹杀"。

我们回到电机静止的定子坐标系,观察某一相(比如 A 相)的反电动势。在断电滑行时,A 相产生的真实反电动势波形为:
eA(t)=λ⋅ωel(t)⋅sin⁡(θel)e_A(t) = \lambda \cdot \omega_{el}(t) \cdot \sin(\theta_{el})eA(t)=λ⋅ωel(t)⋅sin(θel)

其中:

  • λ\lambdaλ 是我们要测的未知常数(磁链)
  • ωel(t)\omega_{el}(t)ωel(t) 是不断衰减的瞬时电角速度
  • θel\theta_{el}θel 是当前的电角度

注意微积分里的核心定义:角速度是角度对时间的导数 ,即 ωel(t)=dθeldt\omega_{el}(t) = \frac{d\theta_{el}}{dt}ωel(t)=dtdθel。

我们把这个定义代入反电动势方程:
eA(t)=λ⋅dθeldt⋅sin⁡(θel)e_A(t) = \lambda \cdot \frac{d\theta_{el}}{dt} \cdot \sin(\theta_{el})eA(t)=λ⋅dtdθel⋅sin(θel)

接下来,见证奇迹的时刻!

我们对采集到的半个反电动势正弦波 进行时间上的积分(即从 θel=0\theta_{el} = 0θel=0 过零点开始,积到 θel=π\theta_{el} = \piθel=π 下一个过零点结束):

∫t1t2eA(t)dt=∫t1t2(λ⋅dθeldt⋅sin⁡(θel))dt\int_{t_1}^{t_2} e_A(t) dt = \int_{t_1}^{t_2} \left( \lambda \cdot \frac{d\theta_{el}}{dt} \cdot \sin(\theta_{el}) \right) dt∫t1t2eA(t)dt=∫t1t2(λ⋅dtdθel⋅sin(θel))dt

在积分过程中,时间微元 dtdtdt 被完美地约掉了(换元积分法)!方程彻底脱离了时间轴,变成了纯粹的角度积分:
=∫0πλ⋅sin⁡(θel)dθel= \int_{0}^{\pi} \lambda \cdot \sin(\theta_{el}) d\theta_{el}=∫0πλ⋅sin(θel)dθel

解这个极其简单的定积分:
=λ⋅[−cos⁡(θel)]0π= \lambda \cdot \left[ -\cos(\theta_{el}) \right]_{0}^{\pi}=λ⋅[−cos(θel)]0π
=λ⋅(−cos⁡(π)−(−cos⁡(0)))= \lambda \cdot ( -\cos(\pi) - (-\cos(0)) )=λ⋅(−cos(π)−(−cos(0)))
=λ⋅(1+1)= \lambda \cdot ( 1 + 1 )=λ⋅(1+1)
=2λ= \mathbf{2\lambda}=2λ

3. 结论与代码映射

不论电机转得快还是慢,不论它滑行减速有多么严重,只要你把硬件采集到的半个反电动势波形的面积(积分)算出来,这个面积永远等于 2λ2\lambda2λ!

这就是 VESC 源码里那段积分逻辑的底气所在:

c 复制代码
// 伪代码:VESC 磁链辨识逻辑

// 1. 开环强行把电机拖拽到一个较高的转速
FOC_set_Idq(0.0, spin_up_current, phase_angle);

// 2. 突然关闭所有桥臂,进入断电飞轮滑行 (Iq = 0)
PWMC_TurnOffAllPhases();

// 3. 寻找反电动势从负变正的"过零点"作为积分起点
while(v_bemf < 0.0) { v_bemf = get_v_phase() - (v_bus/2.0); }

// 4. 开始疯狂积分,直到下一个过零点 (正变负)
float flux_integral = 0.0;
while(v_bemf >= 0.0) {
    v_bemf = get_v_phase() - (v_bus/2.0);
    flux_integral += v_bemf * dt; // 累加面积:这行代码的本质就是积分过程
}

// 5. 算出磁链
float flux_linkage = flux_integral / 2.0;

四、 相电压采样电路的设计与避坑指南

很多开发者在移植 VESC 算法时,最容易忽略的就是硬件电路的匹配。如果你没有匹配,那么上文提到的"断电飞轮积分法"将完全失效。

1. 为什么软件补偿(死区补偿)不够用?

在电流过零点附近,MOSFET 的寄生电容(CossC_{oss}Coss)会导致相线电压出现严重的非线性畸变。仅靠软件根据电流方向去加减一个死区时间,在小电流下是极不准确的。直接采样相线上的真实电压,是解决该问题的唯一"物理外挂"。

2. 经典电路拓扑:分压 + 滤波

VESC 6 使用的是一套非常经典且稳健的 RC 分压滤波网络。每一相(A/B/C)都需要一套完全相同的电路:

电路连接:*

  • 输入端: 直接连接在半桥的中点(Phase 线)。
  • 分压电阻 R1R_1R1(高边): 推荐 39kΩ (1% 精度)。
  • 分压电阻 R2R_2R2(低边): 推荐 2.2kΩ (1% 精度)。
  • 滤波电容 CCC: 推荐 2.2nF 贴片电容。

3. 关键参数计算与设计准则

(1) 分压比(决定量程)

ADC 采样点的电压 VadcV_{adc}Vadc 必须在单片机容许范围内(通常 3.3V):
Vadc=Vphase⋅R2R1+R2V_{adc} = V_{phase} \cdot \frac{R_2}{R_1 + R_2}Vadc=Vphase⋅R1+R2R2

按 VESC 6 的参数(39k/2.2k),其最大采样电压约为 3.3⋅39+2.22.2≈61.8V3.3 \cdot \frac{39+2.2}{2.2} \approx 61.8V3.3⋅2.239+2.2≈61.8V。如果你的母线电压更高,需按比例增大 R1R_1R1。

(2) 截止频率(决定相位延迟)

这是一个极容易翻车的陷阱!分压网络本质上是一个低通滤波器(LPF),其截止频率 fcf_cfc 为:
fc=12π⋅(R1//R2)⋅Cf_c = \frac{1}{2\pi \cdot (R_1 // R_2) \cdot C}fc=2π⋅(R1//R2)⋅C1

使用 VESC 6 参数计算得到 fc≈34.8kHzf_c \approx 34.8kHzfc≈34.8kHz。

  • 如果 CCC 过大: 截止频率太低,信号会产生严重的相位滞后,导致高速下观测器角度偏移,电机发热严重。
  • 如果 CCC 过小: 无法滤除 PWM 的高频毛刺,导致采样值全是大刺,积分面积算不准。
(3) 母线电压同步

划重点: VESC 算法中计算反电动势的公式是 v_bemf = v_phase - (v_bus / 2)

为了让这个减法在数学上完美对消,母线电压(Bus Voltage)的采样电路必须使用与相电压完全一致的电阻和电容! 任何阻值偏移都会导致中性点电位计算偏移,进而导致磁链辨识结果随母线电压波动。

4. VESC 源码中的硬件映射

在 VESC 源码的硬件配置文件(如 hw_vesc6.h)中,你会看到这些硬件参数的定义,它们直接参与了反电动势的相位补偿计算:

c 复制代码
// VESC 硬件定义示例
#define HW_ADC_CHANNELS			15
#define VDIV_R1					39000.0  // 高边电阻
#define VDIV_R2					2200.0   // 低边电阻
#define BEMF_SAMPLES_BOTT		1        // ADC 触发点(通常在 PWM 底点)
相关推荐
MaikieMaiky1 小时前
C++STL 系列(三):deque 容器详解与示例
开发语言·c++
崇山峻岭之间1 小时前
单片机时钟配置03
单片机·嵌入式硬件
图码1 小时前
矩阵边界遍历:顺时针与图案打印的两种高效解法
数据结构·python·线性代数·算法·青少年编程·矩阵·深度优先遍历
南境十里·墨染春水1 小时前
线程池学习(三) 实现固定线程池
开发语言·c++·学习
sali-tec1 小时前
C# 基于OpenCv的视觉工作流-章72-点-点距离
图像处理·人工智能·opencv·算法·计算机视觉
nazisami1 小时前
初识AVL树
c++·面向对象·avl树
木燚垚1 小时前
基于STM32的智能灶台控制系统设计与实现
stm32·单片机·嵌入式硬件·智能家居
凌波粒1 小时前
LeetCode--二叉树层序遍历实战指南
算法·leetcode·职场和发展
洛水水1 小时前
【力扣100题】48.乘积最大子数组
算法·leetcode·职场和发展