1. 从“升魂浩荡”说起:这套FOC电流环到底在折腾什么
“升魂浩荡”这个词放在电机控制里其实挺传神——电流环调好了,电机运转那种丝滑跟手的感觉,确实像给一台普通电机“升了魂”。我最早接触FOC是在一台云台电机上,当时用方波驱动,低速抖动大、噪音刺耳,换成FOC之后整个手感完全变了。从那以后,只要涉及永磁同步电机(PMSM)的驱动项目,电流环都是我花时间最多的地方。
这篇内容围绕的是FOC驱动程序中电流环的设计与实现,核心关键词就是FOC、电流环、程序。它解决的是一个非常具体的问题:如何让电机的三相电流精确跟随给定值,从而产生平稳的转矩。适合谁看?如果你正在用STM32或者类似MCU写FOC代码,已经能跑通开环,但电流波形一塌糊涂、电机发热严重、低速转矩脉动大,那这篇就是写给你的。如果你连Clarke、Park变换都还没搞明白,建议先把坐标变换的基础过一遍再回来,不然电流环的很多细节会看得云里雾里。
我见过太多人卡在电流环这一步:明明公式都对着,PI参数也按教程给了,但电流就是跟不上,或者一加载就震荡。问题往往不在公式本身,而在采样时机、解耦处理、参数整定这些“程序之外”的细节上。下面我按自己实际调试的顺序,把电流环从设计思路到代码落地完整拆一遍。
2. 电流环的整体设计思路与方案选型
2.1 为什么电流环是FOC的内核而不是外环
FOC的控制结构是典型的三环嵌套:位置环或速度环在外,电流环在最内。很多人一开始不理解为什么电流环要放在最里面、执行频率最高。道理其实很直白——电机转矩直接正比于q轴电流(对于表贴式PMSM,Te = 1.5 * p * ψf * iq),你要控制转矩,本质上就是控制电流。速度环输出的其实是一个“我想要多大电流”的指令,真正去执行这个指令、把它变成实际电流的,是电流环。
所以电流环的带宽必须远高于速度环,通常电流环执行频率在10kHz到20kHz,速度环在1kHz左右。如果电流环带宽不够,速度环再快也是空中楼阁,因为内环跟不上外环的指令。我一般把电流环带宽设在速度环的5到10倍,这是保证整个系统稳定性的基本前提。
2.2 同步旋转坐标系下的PI控制为什么是主流
电流环的实现方式有好几种:滞环控制、预测控制、PI控制。滞环控制实现简单但开关频率不固定,电流纹波大;预测控制性能好但对参数敏感、计算量大。工程上用得最多的还是同步旋转坐标系(dq坐标系)下的PI控制,原因有三点。
第一,在dq坐标系下,交流量变成了直流量。三相正弦电流经过Clarke和Park变换后,id和iq在稳态下是恒定值,用PI控制器就能实现零稳态误差,不需要谐振控制器去跟踪交流量。第二,d轴和q轴可以独立控制,d轴电流给定通常设为0(表贴式电机),q轴电流给定来自速度环输出,解耦清晰。第三,PI控制器结构简单、参数物理意义明确,整定方法成熟。
我试过用预测控制做过一版,空载时电流跟踪确实漂亮,但一加负载、参数稍微偏一点就发散,最后还是回到PI。对于绝大多数工业应用,dq轴PI加前馈解耦已经足够。
2.3 前馈解耦:不加它电流环永远“慢半拍”
这是很多人忽略的关键点。在dq坐标系下,d轴和q轴电压方程里存在交叉耦合项:ud里有-ωe * Lq * iq,uq里有ωe * Ld * id + ωe * ψf。也就是说,你想改变iq,会通过耦合项影响id,反之亦然。如果只用纯PI,PI需要等误差出现后才去修正,这个修正过程就是耦合项在捣乱,表现为动态响应变慢、电流跟踪有延迟。
解决办法就是前馈解耦:在PI输出上直接叠加耦合项的补偿电压。这样PI只需要负责消除模型误差和扰动,耦合部分由前馈直接抵消。加了前馈解耦之后,电流阶跃响应的上升时间能缩短30%以上,这是我实测对比过的。具体公式后面代码部分会给。
3. 电流环核心细节与实操要点拆解
3.1 ADC采样时刻:采错了后面全白搭
电流环的第一个坑就在ADC采样时机上。FOC需要采三相电流,通常用两个或三个采样电阻,配合运放送到ADC。关键在于:必须在PWM下桥臂导通、电流流过采样电阻的时候采样。
对于中心对齐的PWM,通常在下桥臂导通的中点触发ADC。以STM32为例,可以用定时器的触发事件来启动ADC,比如在计数器等于某个值时触发。如果采样时刻不对,采到的是开关噪声或者续流阶段的电流,波形会非常脏。
我踩过的坑:一开始用软件触发ADC,结果采样点和PWM不同步,电流波形上全是尖刺,PI一调就震荡。后来改成定时器触发,并且把采样点放在PWM周期中间,波形立刻干净了。具体来说,如果PWM是中心对齐模式,计数器从0到ARR再回到0,下桥臂导通区间在计数器接近ARR的时候,这时候触发ADC最稳。
注意:采样电阻上的电流在开关瞬间会有振铃,建议在ADC触发前加一点延时,或者用RC滤波,但延时不能太大,否则采到的不是当前周期的电流。
3.2 Clarke和Park变换的实现细节
Clarke变换把三相电流变成αβ两相。对于三相平衡系统,ia+ib+ic=0,所以只需要采两相就能算出第三相。我一般采ia和ib,ic用-ia-ib算出来。Clarke变换有两种形式:等幅值变换和等功率变换。FOC里通常用等幅值变换,系数是2/3。
Park变换把αβ变成dq,需要转子电角度θe。θe来自编码器或者观测器。这里有个细节:θe的精度直接影响电流环性能。如果角度有偏差,d轴和q轴会串扰,表现为id不为零、转矩输出打折扣。我用编码器的时候会先做一次角度校准,确保θe=0时转子d轴和α轴对齐。
代码上,Clarke和Park都是简单的乘加运算,但要注意定点还是浮点。如果用STM32F4带FPU,直接浮点算,代码清晰。如果用没有FPU的MCU,定点运算要小心溢出和精度。我一般用Q15格式,中间结果用32位暂存。
3.3 PI参数整定的工程方法
PI参数整定是电流环最核心也最玄学的部分。理论上有带宽法、极点配置法,但实际调试中我常用的是试凑加理论估算。
先理论估算:电流环被控对象近似为一阶惯性环节,传递函数是1/(Ls+R)。PI控制器传递函数是Kp + Ki/s。按照典型I型系统整定,让零点抵消被控对象极点,即Ki/Kp = R/L。然后根据想要的带宽ωc确定Kp,Kp = ωc * L。比如电机L=1mH,R=0.5Ω,想要带宽2000rad/s,那Kp=2,Ki=1000。
但理论值只是起点。实际中还有PWM延时、采样延时、数字控制延时,这些都会降低相位裕度。我一般先把Ki设为零,只调Kp,从小到大加,直到电流阶跃响应刚好不震荡,然后再加Ki消除稳态误差。Ki太大会导致低频震荡,太小则稳态误差消不掉。
| 现象 | 可能原因 | 调整方向 |
|---|---|---|
| 电流响应慢,上升时间长 | Kp太小 | 增大Kp |
| 电流超调大,有震荡 | Kp太大或Ki太大 | 减小Kp或Ki |
| 稳态电流有误差 | Ki太小 | 增大Ki |
| 低频抖动 | Ki太大 | 减小Ki |
| 高频噪声大 | Kp太大 | 减小Kp,检查采样 |
3.4 电流环执行频率与PWM频率的关系
电流环执行频率通常等于PWM频率,或者PWM频率的一半。我一般让电流环在PWM中断里执行,每个PWM周期算一次。如果PWM是20kHz,电流环就是20kHz。有些方案为了减轻CPU负担,电流环跑10kHz,PWM跑20kHz,这样每个电流环周期对应两个PWM周期,也可以,但带宽会受限。
关键是电流环周期必须稳定,不能忽快忽慢。我用过一种方案是在主循环里跑电流环,结果主循环里还有其他任务,周期抖动大,电流环性能很差。后来改成定时器中断触发,优先级设最高,问题解决。
4. 电流环程序实现:从代码框架到关键函数
4.1 整体程序框架
我的FOC程序一般分三层:底层驱动(PWM、ADC、编码器)、中间算法层(Clarke、Park、PI、SVPWM)、应用层(速度环、位置环、通信)。电流环属于算法层,被PWM中断调用。
程序流程是这样的:PWM中断触发 -> 读ADC电流 -> Clarke变换 -> Park变换(用当前电角度) -> PI计算 -> 前馈解耦 -> 反Park变换 -> SVPWM生成 -> 更新PWM占空比。整个过程必须在下一个PWM中断到来前完成,否则会丢拍。
以STM32G4为例,PWM频率20kHz,中断周期50us。在170MHz主频下,50us有8500个时钟周期,足够跑完整个电流环。但如果用M0内核,主频低,就要精简运算,尽量用定点。
4.2 关键代码实现
先看PI控制器的结构体定义:
typedef struct { float Kp; float Ki; float integral; float out_max; float out_min; } PI_Controller; float PI_Calc(PI_Controller *pi, float error) { float out; pi->integral += pi->Ki * error; // 积分限幅,防止饱和 if (pi->integral > pi->out_max) pi->integral = pi->out_max; if (pi->integral < pi->out_min) pi->integral = pi->out_min; out = pi->Kp * error + pi->integral; // 输出限幅 if (out > pi->out_max) out = pi->out_max; if (out < pi->out_min) out = pi->out_min; return out; }积分限幅很重要。如果不加,电机堵转或者给定突变时积分项会累积到很大,恢复时严重超调。我一般把积分限幅设得比输出限幅稍小一点。
然后是电流环主函数:
void CurrentLoop_Run(void) { // 1. 读电流 float ia = ADC_GetCurrentA(); float ib = ADC_GetCurrentB(); float ic = -ia - ib; // 2. Clarke变换 float i_alpha = ia; float i_beta = (ia + 2.0f * ib) * ONE_BY_SQRT3; // 3. Park变换 float sin_theta = arm_sin_f32(theta_e); float cos_theta = arm_cos_f32(theta_e); float id = i_alpha * cos_theta + i_beta * sin_theta; float iq = -i_alpha * sin_theta + i_beta * cos_theta; // 4. PI计算 float id_err = id_ref - id; float iq_err = iq_ref - iq; float ud_pi = PI_Calc(&pi_d, id_err); float uq_pi = PI_Calc(&pi_q, iq_err); // 5. 前馈解耦 float ud = ud_pi - omega_e * Lq * iq; float uq = uq_pi + omega_e * Ld * id + omega_e * psi_f; // 6. 反Park变换 float u_alpha = ud * cos_theta - uq * sin_theta; float u_beta = ud * sin_theta + uq * cos_theta; // 7. SVPWM SVPWM_Generate(u_alpha, u_beta, &duty_a, &duty_b, &duty_c); PWM_SetDuty(duty_a, duty_b, duty_c); }这段代码里,theta_e是电角度,omega_e是电角速度,Ld、Lq是dq轴电感,psi_f是永磁体磁链。前馈解耦那两行就是前面说的补偿项。
4.3 参数计算过程
假设电机参数:Ld = Lq = 1mH,R = 0.5Ω,psi_f = 0.01Wb,极对数p = 7。PWM频率20kHz,电流环带宽目标2000rad/s。
Kp = ωc * L = 2000 * 0.001 = 2.0 Ki = Kp * R / L = 2.0 * 0.5 / 0.001 = 1000
但这是连续域的值,数字实现要考虑采样周期。如果电流环周期Ts = 50us,离散化后Ki_digital = Ki * Ts = 1000 * 0.00005 = 0.05。Kp不变。所以实际代码里Kp=2.0,Ki=0.05。
前馈解耦项里,omega_e最大是多少?如果电机最高转速3000rpm,极对数7,电频率 = 3000/60 * 7 = 350Hz,电角速度 = 2π * 350 ≈ 2200rad/s。那么omega_e * Lq * iq,假设iq最大10A,就是2200 * 0.001 * 10 = 22V。这个补偿电压不小,必须加,否则高速时电流根本跟不上。
4.4 SVPWM的实现要点
SVPWM比SPWM电压利用率高15%,是FOC标配。实现上分几步:判断扇区、计算基本矢量作用时间、计算占空比。我一般用七段式SVPWM,每个PWM周期内开关次数固定,谐波特性好。
代码里要注意的是,u_alpha和u_beta要先限幅,防止过调制。限幅值一般是Vdc/√3。如果超出,按比例缩小。我见过有人不限幅,结果给定突变时SVPWM计算出负的占空比,PWM输出异常,电机直接失控。
5. 常见问题与排查技巧实录
5.1 电流波形畸变、有尖刺
这是最常见的现象。原因通常有三个:采样时刻不对、采样电阻布局不好、PWM死区设置不当。排查顺序:先用示波器看采样电阻上的电压波形,确认采样点是否在电流平稳段。如果采样点没问题,再看死区,死区太大会导致电流过零畸变。我一般死区设1us左右,具体看MOS管和驱动芯片。
5.2 电机发热严重、电流偏大
如果id不为零,说明角度有偏差或者解耦没做好。先检查编码器零位校准,确保θe=0时d轴对齐。如果角度没问题,看id_ref是不是设成了0。有些方案为了弱磁会设负的id_ref,但正常工况下应该是0。另外,如果电流环有稳态误差,实际电流会比给定大,也会发热。
5.3 加载后震荡、电流失控
这通常是PI参数太激进或者前馈解耦符号搞反了。先检查解耦项的符号:ud里是减omega_e * Lq * iq,uq里是加omega_e * Ld * id + omega_e * psi_f。符号错了会变成正反馈,必震荡。如果符号对,减小Kp和Ki,降低带宽。
5.4 低速时电流不平稳
低速时电角度变化慢,如果角度分辨率不够,Park变换会有量化误差。用编码器的话问题不大,用观测器的话低速观测精度是难点。我一般低速时用IF启动或者高频注入,等速度起来再切观测器。
| 问题现象 | 排查方向 | 解决方法 |
|---|---|---|
| 电流尖刺 | 采样时刻、死区 | 调整ADC触发点,检查死区 |
| 电机发热 | 角度偏差、id不为零 | 校准编码器,检查id_ref |
| 加载震荡 | PI参数、解耦符号 | 减小Kp/Ki,检查解耦公式 |
| 低速不稳 | 角度分辨率 | 用IF启动或高频注入 |
| 高速电流跟不上 | 前馈解耦、电压限幅 | 加解耦,检查母线电压 |
5.5 实操心得
调电流环的时候,我习惯先用一个很小的电流给定(比如0.5A),让电机堵转或者空载,用示波器看iq的实际波形。如果iq能跟上给定,再逐步加大电流。不要一上来就给额定电流,万一参数不对,电机瞬间过流。
还有一点,PI的积分项在电机启动前要清零,否则上次运行的积分残留会导致启动冲击。我在每次使能电机前都会调用PI_Reset函数,把积分项和输出都清零。
6. 电流环的扩展与优化方向
电流环跑通之后,可以进一步优化。比如加入电流预测控制,利用电机模型提前一拍计算电压,理论上可以消除数字延时的影响。我试过在STM32G4上跑无差拍预测电流控制,电流阶跃响应确实比PI快,但对电感参数敏感,参数偏差20%以上性能就明显下降。
另一个方向是自适应PI,根据转速和电流大小在线调整Kp和Ki。高速时带宽可以适当降低,避免震荡;低速时提高带宽,改善响应。实现上可以用查表法,把不同工况下的PI参数预先算好存起来。
如果MCU资源够,还可以把电流环和观测器放在同一个中断里跑,保证角度和电流的同步性。我用过STM32G4的CORDIC加速器算三角函数,比查表法精度高,比浮点库快,是个不错的选择。
最后再分享一个小技巧:调试电流环时,把id和iq的实际值通过DAC输出到示波器上看,比串口打印直观得多。STM32的DAC可以配置成在中断里更新,实时性很好。我一般用两个DAC通道分别输出id和iq,触发用PWM同步信号,这样能看到每个PWM周期的电流变化,排查问题非常方便。