1. 从“升魂浩荡”说起:这套FOC电流环到底在折腾什么
“升魂浩荡”这个词放在电机控制里其实挺传神——调电流环调到半夜,看着示波器上那根毛刺乱飞的波形,确实有种灵魂出窍的感觉。我做了七八年电机驱动,从方波六步换相一路做到无感FOC,踩过的坑能填满一个篮球场。今天聊的这套东西,核心就是FOC(磁场定向控制)里的电流环程序实现,说白了就是让电机的定子电流乖乖听话,指哪打哪。
先给不太熟的朋友交个底。FOC的本质是把三相交流电机的定子电流,通过Clarke变换和Park变换,从静止的三相坐标系(ia、ib、ic)转到跟着转子旋转的两相坐标系(id、iq)。转过去之后,id控制励磁分量,iq控制转矩分量,两个量在稳态下都是直流量。直流量好控制啊,一个PI调节器就能做到零稳态误差。这就是为什么FOC比传统的V/F控制、六步换相要“高级”——它把交流问题变成了直流问题。
电流环是整个FOC三环结构(电流环、速度环、位置环)里最内层的一环,也是带宽最高、响应最快的一环。电流环跑不好,外面速度环和位置环全是空中楼阁。我见过太多人速度环参数调得飞起,结果电流环一塌糊涂,电机一转起来就啸叫、发热、甚至炸管。所以这篇文章的重点就一个:把电流环程序从原理到代码到调试,掰开揉碎讲清楚。
适合谁看?如果你正在用STM32、TI C2000或者国产MCU做PMSM/BLDC的FOC驱动,电流环采样有噪声、PI参数整定靠试凑、解耦补偿搞不明白,那这篇东西应该能帮你省下不少烧管子和掉头发的钱。如果你连Clarke/Park变换是什么都还没搞懂,建议先补一下基础,但也不妨碍你往下看,我会尽量用大白话解释。
2. 电流环的整体设计思路与方案选型
2.1 为什么电流环必须跑在最高优先级中断里
电流环的实时性要求极高。以常见的10kHz开关频率为例,每个PWM周期只有100微秒。在这100微秒里,你要完成:ADC采样三相电流、Clarke变换、Park变换、两个PI调节器计算、反Park变换、SVPWM占空比计算、更新PWM寄存器。这一套下来,在168MHz的STM32F4上大概要跑3到5微秒,看起来余量很大,但如果你把电流环放在主循环里轮询,那就完蛋了。
我早期做过一个项目,为了图省事把电流环放在1kHz的定时器中断里,开关频率设了10kHz。结果电机一转起来电流波形像锯齿一样,iq给定和反馈之间差了将近30度相位。原因很简单:电流环的采样和控制频率必须和PWM频率同步,否则采样时刻和PWM更新时刻对不上,等效于引入了一个巨大的延迟。后来改成PWM下溢中断触发ADC,ADC转换完成中断里跑电流环,波形立刻就干净了。
所以第一条铁律:电流环必须由PWM定时器的下溢事件触发,ADC采样时刻要和PWM中心对齐。具体来说,如果你用的是中心对齐模式,在计数器等于零的时候触发ADC采样,这时候三相下桥臂全部导通,采样电阻上的电流就是真实的相电流。这个时刻选不对,采到的就是开关噪声,后面PI调得再好也是白搭。
2.2 电流采样方案:单电阻、双电阻还是三电阻
这是硬件设计阶段就要定下来的事,但软件工程师必须懂,否则程序没法写。
三电阻采样是最省心的方案,每相下桥臂各串一个采样电阻,三个ADC通道同时采样。优点是任何时刻都能采到三相电流,Clarke变换直接算就行。缺点是成本高、PCB面积大,而且三个电阻的温漂要一致,否则引入直流偏置。
双电阻采样省了一个电阻,但代价是必须保证至少有两相下桥臂导通才能采样。在SVPWM的某些扇区里,某一相占空比接近100%,下桥臂导通时间极短,ADC根本来不及采。所以双电阻方案通常要限制最大占空比,或者用“移相采样”的技巧。我一般建议新手从三电阻入手,等玩熟了再考虑降成本。
单电阻采样最便宜,但最麻烦。它只在特定的PWM开关组合下才能采到电流,而且需要根据扇区重构三相电流。程序复杂度直线上升,采样窗口可能只有几百纳秒,对ADC采样保持时间要求极高。除非是量产项目成本卡得很死,否则我不推荐。
| 采样方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 三电阻 | 任意时刻可采,程序简单 | 成本高,占PCB面积 | 工业伺服、高精度场合 |
| 双电阻 | 成本适中,程序较简单 | 需限制占空比,低调制区有盲区 | 家电、风机、水泵 |
| 单电阻 | 成本最低 | 程序复杂,采样窗口窄 | 低成本量产、对体积敏感 |
2.3 电流环PI调节器的结构选择
电流环的PI调节器有两种常见结构:串联式和并联式。串联式就是误差先过PI,输出再乘一个增益;并联式是比例和积分并行。在数字实现里,我更倾向于用增量式PI,因为增量式不需要累加历史误差,抗积分饱和处理起来更自然。
但这里有个坑:很多人直接把连续域的PI公式搬到代码里,结果发现积分项在每次中断里累加,积分增益Ki设大了就振荡,设小了响应慢。问题出在积分时间常数的离散化上。连续域PI是:
u(t) = Kp * e(t) + Ki * ∫e(t)dt
离散化之后,积分项应该写成:
I(k) = I(k-1) + Ki * Ts * e(k)
其中Ts是电流环周期。很多人忘了乘Ts,导致Ki的实际效果和理论差了一个数量级。我一般会在代码里把Ki定义成“积分增益”,然后实际累加的是Ki * Ts * e(k),这样调参的时候心里有数。
另外,抗积分饱和是必须做的。电机启动瞬间,iq给定可能从0直接跳到额定值,误差很大,积分项会迅速累加到饱和。如果不处理,等反馈跟上来了,积分项还在最大值下不来,电流就会过冲甚至振荡。我常用的做法是:当PI输出超过限幅值时,停止积分累加,或者把积分项钳位在限幅值附近。
3. 核心细节解析与实操要点
3.1 Clarke变换和Park变换的代码实现细节
Clarke变换把三相电流变成两相静止坐标系下的iα和iβ。对于三相平衡系统(ia+ib+ic=0),等幅值Clarke变换是:
iα = ia iβ = (ia + 2*ib) / √3
注意这里用的是等幅值变换,不是等功率变换。等幅值变换的好处是电压和电流的幅值在变换前后一致,PI调节器的输出直接对应相电压幅值,物理意义清晰。等功率变换会多一个√(2/3)的系数,在需要计算功率的时候才用。
Park变换把iα、iβ转到旋转坐标系:
id = iα * cosθ + iβ * sinθ iq = -iα * sinθ + iβ * cosθ
θ是转子电角度,来自编码器或者无感观测器。这里有个细节:θ的精度直接影响iq的控制效果。如果θ有偏差,id和iq之间会串扰,本来只想给iq,结果id也冒出来了。我实测过,θ偏差10度,iq的幅值会下降约1.5%,同时id出现一个和iq同量级的直流分量。对于需要精确转矩控制的场合,这个误差不能忍。
所以如果你用的是增量式编码器,一定要做电角度对齐。具体做法是:给d轴通一个固定电压,让转子转到d轴对齐的位置,然后把编码器读数清零或者记录偏移量。这个过程在量产时每个电机都要做一次,因为编码器安装位置有机械公差。
3.2 电流环PI参数整定的工程方法
PI参数整定是电流环最核心也最让人头疼的部分。理论上有零极点对消法:把电机电气模型看成RL串联电路,传递函数是1/(Ls+R),PI调节器的零点设在R/L处,对消掉电机的极点,然后根据想要的带宽算Kp。公式是:
Kp = L * ωc Ki = R * ωc
其中ωc是电流环带宽,一般取开关频率的1/10到1/5。比如10kHz开关频率,带宽取1kHz到2kHz。
但理论归理论,实际调的时候你会发现电机参数L和R随温度、电流饱和变化很大。我一般先用理论值算一个初值,然后在实验中微调。调试步骤是这样的:
- 先把Ki设为0,只调Kp。给iq一个阶跃给定(比如从0跳到额定电流的10%),看反馈波形。Kp太小,响应慢;Kp太大,超调振荡。调到临界振荡再往回退30%左右。
- 然后加Ki。Ki的作用是消除稳态误差。慢慢增大Ki,直到阶跃响应的稳态误差在合理时间内归零,同时不引起低频振荡。
- 最后看id的响应。如果id在iq阶跃时也有明显波动,说明解耦没做好或者θ有偏差。
我踩过的一个坑:PI输出没有做限幅。有一次调一个低压小电机,Kp给大了,PI输出直接冲到PWM占空比的100%,结果上下桥臂直通,炸了两个MOS管。后来学乖了,PI输出一定要钳位在SVPWM能接受的最大电压矢量幅值以内,一般是母线电压的1/√3。
3.3 电流环解耦补偿:什么时候需要,什么时候可以偷懒
在旋转坐标系下,d轴和q轴之间存在交叉耦合项:
ud = Rid + Ldd(id)/dt - ωeLqiq uq = Riq + Lqd(iq)/dt + ωeLdid + ωe*ψf
其中ωe是电角速度,ψf是永磁体磁链。那个-ωeLqiq和+ωeLdid就是耦合项。在低速时,ωe很小,耦合项可以忽略,PI调节器自己就能把误差压下去。但在高速时,耦合项可能占到总电压的30%以上,PI调节器为了补偿这个耦合,会输出一个额外的电压,导致id和iq互相干扰。
前馈解耦的做法是:在PI输出上直接加上耦合项的补偿值。这样PI只需要处理模型误差和扰动,负担大大减轻。我实测过,在3000rpm以上,加了解耦之后iq的跟踪误差从15%降到3%以内。
但解耦需要知道Ld、Lq和ψf。对于表贴式PMSM,Ld≈Lq,解耦简单一些。对于内置式PMSM,Ld和Lq差很多,而且随电流饱和变化,解耦参数不准反而会引入新的扰动。所以我的建议是:低速应用可以不做解耦,高速应用必须做,但参数要在线辨识或者查表。
4. 实操过程与核心环节实现
4.1 硬件平台搭建与ADC校准
我最近做的一个项目用的是STM32G474,内置了高分辨率定时器和运放,很适合做FOC。硬件配置如下:
- PWM频率:20kHz,中心对齐模式
- ADC采样:三相下桥臂电阻采样,运放增益10倍,偏置1.65V
- 编码器:2500线增量式,四倍频后10000脉冲/转
- 母线电压:24V,电机额定电流5A
第一步是ADC偏置校准。上电时电机没转,三相电流应该为零,但ADC读数不一定是零,因为运放有失调电压。我一般会在初始化时连续采样100次,取平均值作为零偏,后续所有采样值都减去这个零偏。这个步骤不做,id和iq上会有直流偏置,导致电机发热。
#define ADC_OFFSET_SAMPLES 100 void ADC_Offset_Calibration(void) { int32_t sum_a = 0, sum_b = 0, sum_c = 0; for (int i = 0; i < ADC_OFFSET_SAMPLES; i++) { sum_a += ADC_GetValue(ADC_CH_A); sum_b += ADC_GetValue(ADC_CH_B); sum_c += ADC_GetValue(ADC_CH_C); Delay_us(10); } offset_a = sum_a / ADC_OFFSET_SAMPLES; offset_b = sum_b / ADC_OFFSET_SAMPLES; offset_c = sum_c / ADC_OFFSET_SAMPLES; }第二步是电流标定。ADC读数要转换成安培,需要知道采样电阻阻值、运放增益和ADC参考电压。比如采样电阻0.01Ω,运放增益10倍,ADC 12位、参考电压3.3V,那么:
电流(A) = (ADC值 - 零偏) * 3.3 / 4096 / 10 / 0.01
算下来每个ADC码对应约0.008A。这个系数要写死在代码里,或者通过上位机标定。
4.2 电流环中断服务程序的完整实现
下面是我在实际项目中用的电流环中断服务程序框架,基于STM32 HAL库,但逻辑是通用的:
void PWM_Underflow_ISR(void) { // 1. 读取三相电流 ia = (ADC_GetValue(ADC_CH_A) - offset_a) * CURRENT_SCALE; ib = (ADC_GetValue(ADC_CH_B) - offset_b) * CURRENT_SCALE; ic = (ADC_GetValue(ADC_CH_C) - offset_c) * CURRENT_SCALE; // 2. Clarke变换 i_alpha = ia; i_beta = (ia + 2.0f * ib) * ONE_BY_SQRT3; // 3. 获取电角度 theta_e = Encoder_GetElecAngle(); sin_theta = arm_sin_f32(theta_e); cos_theta = arm_cos_f32(theta_e); // 4. Park变换 id = i_alpha * cos_theta + i_beta * sin_theta; iq = -i_alpha * sin_theta + i_beta * cos_theta; // 5. 电流环PI调节 id_error = id_ref - id; iq_error = iq_ref - iq; ud_pi = PI_Calc(&pi_d, id_error); uq_pi = PI_Calc(&pi_q, iq_error); // 6. 前馈解耦 ud = ud_pi - omega_e * Lq * iq; uq = uq_pi + omega_e * (Ld * id + psi_f); // 7. 电压限幅 ud = CLAMP(ud, -U_MAX, U_MAX); uq = CLAMP(uq, -U_MAX, U_MAX); // 8. 反Park变换 u_alpha = ud * cos_theta - uq * sin_theta; u_beta = ud * sin_theta + uq * cos_theta; // 9. SVPWM计算 SVPWM_Calc(u_alpha, u_beta, &duty_a, &duty_b, &duty_c); // 10. 更新PWM寄存器 PWM_SetDuty(duty_a, duty_b, duty_c); }这个框架里,第5步的PI_Calc函数需要处理抗积分饱和。我的做法是:
float PI_Calc(PI_Handle *pi, float error) { float p_term = pi->Kp * error; float i_term = pi->integral; float output = p_term + i_term; // 抗积分饱和:输出限幅时停止积分 if (output > pi->out_max) { output = pi->out_max; if (error < 0) pi->integral += pi->Ki * pi->Ts * error; } else if (output < pi->out_min) { output = pi->out_min; if (error > 0) pi->integral += pi->Ki * pi->Ts * error; } else { pi->integral += pi->Ki * pi->Ts * error; } return output; }注意这里的积分项只在输出未饱和或者误差方向有助于退出饱和时才累加,这就是所谓的“条件积分”抗饱和。
4.3 SVPWM的实现与死区补偿
SVPWM(空间矢量脉宽调制)是FOC的标准调制方式,比SPWM的直流电压利用率高15.5%。它的核心是根据uα和uβ判断扇区,然后计算相邻两个基本矢量的作用时间。
我一般用七段式SVPWM,每个PWM周期内开关序列对称,谐波性能好。具体实现网上代码很多,我就不贴了。但有一个坑必须提:死区补偿。
死区是为了防止上下桥臂直通而插入的短暂全关时间,一般几百纳秒。死区会导致实际输出电压和给定电压有偏差,在低速时尤其明显,表现为电流波形过零点畸变。补偿的方法是根据电流方向,在占空比上加减一个死区补偿量。我实测过,不加死区补偿,低速时电流THD能到8%以上;加了之后降到3%左右。
// 死区补偿示例 if (ia > 0) duty_a += deadtime_comp; else duty_a -= deadtime_comp; // 同理处理b相和c相4.4 上位机调试与波形观测
调电流环离不开上位机。我一般用串口或者CAN把id、iq、id_ref、iq_ref、ud、uq这些变量传到上位机,用VOFA+或者自己写的Qt界面画波形。采样率不用太高,1kHz就够了,因为电流环的响应时间在毫秒级。
这里有个技巧:用DAC输出关键变量。STM32的DAC可以实时输出id和iq的模拟量,直接接示波器看,比串口传数据快得多,而且没有传输延迟。我一般把DAC通道1设为id,通道2设为iq,触发模式设为PWM下溢,这样示波器上看到的波形和实际电流环同步,非常直观。
5. 常见问题与排查技巧实录
5.1 电流采样噪声大、波形毛刺多
这是最常见的问题。原因可能有几个:
- 采样时刻不对:如果ADC采样时刻在开关管切换的瞬间,采到的全是开关噪声。解决办法是把ADC触发时刻设在PWM中心,也就是计数器为零的时候。
- 采样电阻走线太长:采样电阻的电压信号很微弱(几十毫伏),走线长了容易引入干扰。要用开尔文连接,差分走线,尽量靠近运放。
- 运放带宽不够:电流信号有高频分量,运放带宽不够会导致信号失真。一般选10MHz以上的运放。
- ADC参考电压不稳:参考电压波动会直接反映在采样值上。要用低噪声LDO给参考电压供电,旁边加去耦电容。
我遇到过一次特别诡异的噪声,排查了半天发现是电机外壳没接地。电机外壳浮空,相当于一个大天线,把开关噪声耦合到了电流采样线上。把外壳接到驱动板的地之后,噪声立刻消失了。
5.2 电流环振荡、电机啸叫
电流环振荡的典型表现是iq反馈在给定值附近高频抖动,电机发出刺耳的啸叫。原因和解决办法:
| 现象 | 可能原因 | 排查方法 | 解决办法 |
|---|---|---|---|
| 高频振荡 | Kp太大 | 减小Kp看是否改善 | 降低Kp,增大带宽余量 |
| 低频振荡 | Ki太大 | 减小Ki看是否改善 | 降低Ki,或加抗饱和 |
| 周期性抖动 | 角度偏差 | 检查θ对齐 | 重新做电角度对齐 |
| 随转速加剧 | 解耦参数不对 | 关闭解耦看是否改善 | 重新辨识Ld、Lq |
| 特定转速振荡 | 机械共振 | 扫频测试 | 加陷波滤波器 |
我印象最深的一次是调一个高速风机,电流环在2000rpm以下都正常,一到2500rpm就开始啸叫。后来发现是速度环和电流环带宽太接近,两个环互相干扰。把电流环带宽从2kHz提到4kHz,速度环降到200Hz,问题解决。所以三环的带宽要拉开,一般电流环带宽是速度环的5到10倍。
5.3 id不为零、有直流分量
id应该控制为零(表贴式PMSM)或者负值(内置式PMSM弱磁),如果id有直流分量,说明:
- 电流采样零偏没校准:重新做ADC偏置校准。
- θ有偏差:id的直流分量和θ偏差成正比。做电角度对齐。
- 三相不平衡:电机绕组不对称或者采样通道增益不一致。检查硬件。
我遇到过一次id直流分量特别大,查了半天发现是C相采样电阻虚焊,导致ic读数始终为零,Clarke变换出来的iα和iβ全错了。所以硬件问题永远要先排除,别一上来就怀疑算法。
5.4 启动时过流保护触发
电机启动瞬间,iq给定从0跳到额定值,如果PI响应太快,电流会过冲,触发过流保护。解决办法:
- 给定斜坡:iq_ref不要直接跳变,加一个斜率限制,比如每毫秒增加0.1A。
- 启动前预定位:先给d轴通一个小电流,把转子拉到已知位置,再给q轴电流。
- PI输出限幅:启动时把PI输出限幅设小一点,等稳定后再放开。
我一般会在启动阶段用电流斜坡,从0慢慢加到目标值,时间大概100到200毫秒。这样既不会过流,也不会因为启动太慢影响响应。
5.5 常见问题速查表
| 问题 | 首要排查方向 | 快速验证方法 |
|---|---|---|
| 电机不转 | PWM输出、使能信号 | 示波器看PWM引脚 |
| 电机抖动不连续 | 角度θ、编码器 | 手动转电机看θ变化 |
| 电流波形畸变 | 死区、采样时刻 | 示波器看相电流 |
| 发热严重 | id不为零、开关损耗 | 测id平均值 |
| 高速失控 | 解耦、电压限幅 | 关闭解耦对比 |
| 噪声大 | 接地、采样电阻 | 短接采样电阻看噪声 |
6. 一些掏心窝子的实操心得
调电流环这件事,说难也难,说简单也简单。我做了这么多年,最大的体会是:硬件是基础,算法是锦上添花。很多人一上来就研究各种高级算法,什么滑模观测器、模型预测控制,结果连最基本的电流采样都没搞干净,调什么都白搭。
我的建议是,先把三电阻采样、中心对齐PWM、电角度对齐这三件事做扎实,然后用一个最朴素的PI调节器,把电流环调到阶跃响应无超调、稳态无误差。这个过程可能需要反复试,但一旦调通了,你对FOC的理解会上一个台阶。
另外,示波器是你的好朋友。不要只盯着上位机的波形看,上位机的数据是经过串口传输、可能还有滤波的,会丢失很多细节。用示波器直接看采样电阻上的电压、看DAC输出的id/iq、看PWM占空比,很多问题一眼就能看出来。
最后说一个我最近才想明白的事:电流环的带宽不是越高越好。理论上带宽越高响应越快,但实际受限于采样频率、计算延迟、PWM更新延迟,带宽太高反而会引入相位滞后,导致振荡。我一般把电流环带宽设在开关频率的1/10左右,比如20kHz开关频率,带宽设2kHz,留足相位裕度。这个值不是绝对的,要根据电机参数和硬件条件调整,但作为一个起点,不会出大错。
电机控制这个领域,理论很重要,但经验更重要。书本上的公式都是理想情况,实际电机有饱和、有温漂、有死区、有各种你想不到的干扰。多动手、多测量、多思考,比看一百篇论文都管用。