3. 电流环软件架构:PWM触发ADC、电流重构、坐标变换、PI调节器、SVPWM输出的完整链路

这一章,我们来聊聊电流环的完整软件链路。

说白了,就是从一个PWM周期开始,到下一个PWM周期结束,中间那几十微秒里,芯片到底在忙什么。很多初学者拿到FOC例程,看着代码里一堆函数调用,很容易懵——先调哪个?后调哪个?为什么非得这么排?

我刚开始做FOC驱动时也踩过这个坑。当时照着参考代码抄,结果电机一启动就啸叫,电流波形跟锯齿似的。后来才明白,不是PI参数调得不好,而是整个执行时序就错了。ADC采到的电流还没更新,坐标变换就已经算完了——你想想看,这能对吗?

3.1 整体链路概览:一个PWM周期内的“流水线”

先看一张图,把整个链路串起来。

电流环软件链路流程图 电流环单周期执行链路 PWM触发ADC 计数器归零/峰值采样 电流重构 Iu/Iv → Iα/Iβ Clark变换 Iα/Iβ 静止坐标系 Park变换 Id/Iq 旋转坐标系 PI调节器 Id/Iq 误差→Vd/Vq 反Park变换 Vd/Vq → Vα/Vβ SVPWM 扇区判断→占空比 更新PWM比较寄存器 等待下一个周期生效 ⚠ 关键时序约束 ADC采样必须在PWM开关噪声 稳定后进行(死区时间后) 整个链路需在PWM周期内完成

这张图展示的就是一个PWM周期内,电流环从采样到输出的完整路径。注意看,箭头方向是单向的——你不能先算SVPWM再去做坐标变换,那逻辑上就反了。

3.2 PWM触发ADC:时序的起点

电流环的起点,是ADC采样。那ADC什么时候采?谁说了算?

答案是:PWM定时器说了算。

核心原则:ADC采样时刻必须避开功率管的开关动作。通常选择在PWM计数器归零(下溢)或达到峰值时触发,此时三相桥臂处于死区后的稳定状态,电流纹波最小。

我个人习惯使用中心对齐PWM模式。在这种模式下,计数器先递增到峰值,再递减到零。ADC采样触发点设在计数器归零的时刻——也就是PWM周期的正中间。为什么选这个点?

  • 噪声最小:此时所有桥臂都处于导通或关断的稳定状态,没有开关动作,采样值最干净。
  • 电流平均值:中心对齐模式下,归零点对应的电流值接近该周期的平均值,对PI调节器更友好。

小技巧:如果使用单电阻采样,需要额外处理采样窗口。我曾经在一个项目里因为采样窗口没对准,导致电流重构出来的波形全是毛刺,折腾了两天才发现是ADC触发时刻偏了半个死区时间。

3.3 电流重构:从硬件值到物理量

ADC采到的是啥?是电压值,单位是LSB(最低有效位)。我们需要把它换算成真实的电流值,单位是安培。

这一步通常包含两个子步骤:

  1. 偏移校准:减去零点偏移。硬件电路总有偏置,不校准的话,电流环会一直有个静差。
  2. 增益换算:乘以比例系数。这个系数由采样电阻、运放增益和ADC参考电压共同决定。

换算公式很简单:

I_actual = (ADC_raw - offset) * gain;

但这里有个坑——你至少需要两相电流(通常是U相和V相),第三相通过基尔霍夫电流定律算出来:

Iw = -(Iu + Iv);

注意:如果使用单电阻采样,一次只能采到一相电流,需要在一个PWM周期内分两次采样(不同开关状态下),再通过查表重构出三相电流。这种方法对时序要求极高,我曾经在高速电机(20000rpm以上)上试过,采样窗口窄到只有1-2微秒,稍微有点抖动就重构失败。

3.4 坐标变换:从静止到旋转

电流重构完成后,我们有了三相电流Iu、Iv、Iw。但PI调节器不认识三相交流量——它只认识直流量。所以需要坐标变换。

3.4.1 Clark变换(3相→2相静止)

Clark变换把三相电流映射到两相静止坐标系(α-β轴)。公式如下:

Iα = Iu;
Iβ = (Iu + 2*Iv) / sqrt(3);

嗯,这里我简化了。实际工程中常用等幅值变换,系数是2/3。但不管用哪种,核心思想是一样的——把三个变量变成两个变量,减少计算量。

3.4.2 Park变换(静止→旋转)

Park变换把α-β轴上的交流量,变成d-q轴上的直流量。关键输入是转子电角度θ:

Id = Iα * cos(θ) + Iβ * sin(θ);
Iq = -Iα * sin(θ) + Iβ * cos(θ);

到这里,Id和Iq就是直流分量了。Id对应励磁分量,Iq对应转矩分量。PI调节器可以直接对它们进行闭环控制。

经验之谈:Park变换中用到的sin/cos值,我建议提前查表或者用CORDIC算法算好,不要在中断里实时计算。我见过有人直接在中断里调math.h的sin函数,一个周期下来光三角函数就占了十几微秒——带宽能高才怪。

3.5 PI调节器:核心的闭环控制

坐标变换之后,我们得到了Id_ref和Iq_ref(给定值)与Id_fb和Iq_fb(反馈值)的误差。PI调节器的任务就是把这个误差变成电压指令。

离散化后的PI公式(位置式):

error = ref - fb;
integral += error * Ki;
output = error * Kp + integral;

这里有几个关键点:

  • 积分限幅:积分项必须限幅,否则一旦饱和,退饱和会非常慢。我一般把积分上限设为输出电压上限的80%。
  • 输出限幅:PI输出不能超过SVPWM能合成的最大电压(通常是直流母线电压的1/sqrt(3)倍)。
  • 抗积分饱和:当输出达到限幅值时,停止积分累加。这个逻辑一定要加,不然积分项会一直涨,等误差反向时半天回不来。

带宽调试的关键:PI参数(Kp和Ki)直接决定了电流环的带宽。Kp越大,响应越快,但过大会导致震荡;Ki用于消除静差,但太大会引入低频振荡。具体怎么调,我们后面的章节会专门讲。

3.6 反Park变换与SVPWM:从电压指令到开关信号

PI调节器输出的是d-q轴电压指令Vd和Vq。但SVPWM不认识d-q轴,它需要α-β轴上的电压。所以需要反Park变换:

Vα = Vd * cos(θ) - Vq * sin(θ);
Vβ = Vd * sin(θ) + Vq * cos(θ);

得到Vα和Vβ之后,SVPWM开始工作。它的核心步骤是:

  1. 扇区判断:根据Vα和Vβ的符号和比例,判断当前电压矢量落在哪个扇区(共6个)。
  2. 作用时间计算:计算相邻两个基本电压矢量的作用时间T1和T2。
  3. 占空比分配:根据扇区和作用时间,计算出三相桥臂的占空比。
  4. 写入比较寄存器:将占空比写入PWM定时器的比较寄存器,等待下一个周期生效。

SVPWM的占空比计算稍微有点复杂,但核心公式就一个:

Ta = (T - T1 - T2) / 4;
Tb = Ta + T1 / 2;
Tc = Tb + T2 / 2;

其中T是PWM周期。不同扇区下,三相占空比会对应不同的排列组合。

避坑指南:我曾经在SVPWM的扇区判断上翻过车。当时用的芯片是STM32F103,算出来的占空比偶尔会超过PWM周期值。后来发现是Vα和Vβ的标幺化没做好,导致矢量幅值超过了六边形内切圆。解决办法是在SVPWM之前加一个过调制判断,如果矢量幅值超过限制,就按比例缩小。

3.7 完整代码框架(伪代码)

最后,给一个完整的电流环中断服务函数框架。注意看执行顺序:

void CurrentLoop_ISR(void)
{
    // 1. 读取ADC结果
    adc_val_u = ADC1->DR;
    adc_val_v = ADC2->DR;
    
    // 2. 电流重构(偏移校准 + 增益换算)
    Iu = (adc_val_u - offset_u) * gain;
    Iv = (adc_val_v - offset_v) * gain;
    Iw = -(Iu + Iv);
    
    // 3. Clark变换
    Ialpha = Iu;
    Ibeta = (Iu + 2*Iv) * INV_SQRT3;
    
    // 4. Park变换(使用查表得到的sin/cos)
    Id = Ialpha * cos_theta + Ibeta * sin_theta;
    Iq = -Ialpha * sin_theta + Ibeta * cos_theta;
    
    // 5. PI调节器(Id环 + Iq环)
    err_d = Id_ref - Id;
    integral_d += err_d * Ki_d;
    integral_d = LIMIT(integral_d, INTEGRAL_MAX);
    Vd = err_d * Kp_d + integral_d;
    Vd = LIMIT(Vd, VOLTAGE_MAX);
    
    err_q = Iq_ref - Iq;
    integral_q += err_q * Ki_q;
    integral_q = LIMIT(integral_q, INTEGRAL_MAX);
    Vq = err_q * Kp_q + integral_q;
    Vq = LIMIT(Vq, VOLTAGE_MAX);
    
    // 6. 反Park变换
    Valpha = Vd * cos_theta - Vq * sin_theta;
    Vbeta = Vd * sin_theta + Vq * cos_theta;
    
    // 7. SVPWM(扇区判断 + 占空比计算)
    sector = SVPWM_CalcSector(Valpha, Vbeta);
    T1 = SVPWM_CalcT1(Valpha, Vbeta, sector);
    T2 = SVPWM_CalcT2(Valpha, Vbeta, sector);
    duty_u = SVPWM_CalcDuty(sector, T1, T2, PWM_PERIOD);
    duty_v = SVPWM_CalcDuty(sector, T1, T2, PWM_PERIOD);
    duty_w = SVPWM_CalcDuty(sector, T1, T2, PWM_PERIOD);
    
    // 8. 更新PWM比较寄存器
    TIM1->CCR1 = duty_u;
    TIM1->CCR2 = duty_v;
    TIM1->CCR3 = duty_w;
    
    // 9. 清除中断标志
    TIM1->SR = ~TIM_SR_UIF;
}

这个框架看起来简单,但每个函数背后都有很多细节。比如PI调节器的积分限幅值怎么取?SVPWM的过调制怎么处理?这些我们会在后续章节逐一展开。

嗯,这一章的内容就到这里。记住一句话:电流环的软件架构,本质上是一个严格的时序流水线。每个环节的执行时间、执行顺序、数据依赖关系,都必须精确控制。任何一个环节出问题,整个环路的带宽都会受影响。