
1. 从“升魂浩荡”说起这套FOC电流环到底在折腾什么“升魂浩荡”这个词放在电机控制上其实挺传神。做永磁同步电机PMSM的磁场定向控制FOC最让人头疼、也最能体现功力的地方就是电流环。速度环调不好顶多是响应慢一点、有点超调电流环要是没整明白电机直接啸叫、发热、抖动严重的时候功率管炸给你看。所以圈子里有句话FOC的魂在电流环电流环的魂在解耦和采样时刻。这篇内容面向的是正在做PMSM无感FOC或者有感FOC的嵌入式工程师、电控方向的学生以及那些已经能把电机转起来、但总觉得“转得不够漂亮”的开发者。我会把电流环从采样时刻、坐标变换、PI整定、内模解耦到实际代码落地这一整条链路拆开讲重点放在那些文档里不写、但实际调试中一定会遇到的问题上。关键词里的“foc d轴电机”“永磁同步电机电流环内膜解耦”“foc adc采样应该在什么时刻”“电流环pi参数整定”这些都会在对应章节里展开。先给一个整体认知电流环的本质是让定子电流的d轴分量和q轴分量分别跟踪给定值。d轴一般给0表贴式电机追求最大转矩电流比时另说q轴给转矩指令。听起来简单但难点在于——你采到的电流到底是不是此刻真实的电流你算出的电压到底有没有准确加在电机上这两个问题不解决PI参数怎么调都是白搭。我见过太多人一上来就调PI调了半天发现是ADC采样时刻偏了或者坐标变换的角度用错了。所以这篇的顺序是先讲采样和变换这些“地基”再讲解耦和PI这些“上层建筑”最后给一套可以照着写的代码框架和整定流程。2. 电流环的地基ADC采样时刻与坐标变换的时序陷阱2.1 为什么采样时刻选错PI怎么调都白费先问一个问题你用的三电阻采样还是单电阻采样这个选择直接决定了ADC触发时刻。三电阻采样最常用也最容易理解我们就以它为主线。在SVPWM调制下三相桥臂的开关状态每个PWM周期都在变。如果在下桥臂导通的时候去采相电流采样电阻上才有压降才能采到电流。但问题是下桥臂导通的时间窗口是有限的而且随着占空比变化这个窗口会移动。最常见的错误做法在PWM周期中间随便触发一次ADC觉得“差不多就行”。结果就是轻载时电流采不准重载时反而好一点因为重载时下桥臂导通时间长。这种“轻载不准”的现象十有八九是采样时刻没对准。正确的做法是在PWM计数器的谷底或峰值触发ADC并且要保证此时至少有两相下桥臂是导通的。对于中心对齐的PWM通常选择在计数器等于0的时候触发因为这时候三相占空比刚刚更新下桥臂导通状态最确定。但这里有个细节从ADC触发到采样保持电路真正采到值中间有采样延迟。这个延迟包括ADC的采样保持时间、触发到转换的延迟。如果你的PWM频率是20kHz周期50微秒采样延迟哪怕只有1微秒对应的电角度误差在高速时也不可忽略。高速电机电频率可能到1kHz1微秒对应0.36度电角度看起来不大但如果你用的是单电阻采样重构算法对时序更敏感。提示采样时刻的验证方法——让电机堵转给定一个固定的d轴电流用示波器同时看下桥臂驱动信号和ADC触发信号确认触发点落在下桥臂导通窗口的正中间。这个动作花十分钟能省你后面十小时的调参时间。2.2 Clarke和Park变换里那些容易写反的符号采样拿到的是三相电流ia、ib、ic。Clarke变换把它变成αβ坐标系下的iα、iβ。这里第一个坑系数是2/3还是sqrt(2/3)这取决于你用的是等幅值变换还是等功率变换。FOC控制里通常用等幅值变换系数是2/3。如果你用等功率变换后面PI参数和电压限幅都要跟着变混用就会导致转矩输出不对。Clarke变换的公式看起来简单iα iaiβ (ia 2ib) / sqrt(3)但注意这个公式的前提是ia ib ic 0。实际采样中三相电流之和不会严格为零因为有采样误差和偏置。所以很多人会做一步“零漂校准”在电机不转、桥臂关断的时候采几百次ADC求平均作为偏置后面每次采样都减掉这个偏置。这一步不做低速时电流波形会歪转矩脉动大。Park变换需要电角度θ。这个θ从哪来有感FOC用编码器或霍尔无感FOC用观测器估算。这里最大的坑是角度对齐编码器读到的机械角度要乘以极对数变成电角度还要加上一个偏移量使得d轴电流为零时编码器角度对应电角度的零点。这个偏移量如果标定不准d轴和q轴就耦合了你给q轴电流结果d轴也有电流电机效率低、发热大。标定方法给d轴一个固定电压q轴给零让电机堵转或者低速转微调角度偏移量直到q轴电流采样值接近零。这个过程叫“角度自学习”很多驱动器都有这个功能但自己写代码的时候必须实现。2.3 电流采样中的噪声与滤波别把有用信号也滤掉了ADC采到的电流值一定有噪声。PWM开关动作会在采样电阻上产生尖峰虽然采样时刻避开了开关瞬间但寄生电容和电感还是会让信号有振铃。常见的做法是加一个一阶低通滤波器i_filtered i_filtered alpha * (i_raw - i_filtered);alpha越小滤波越强但相位滞后越大。电流环的带宽通常设在一千到两千赫兹如果滤波器截止频率低于这个值相位裕度就不够了系统会振荡。我的经验是滤波器截止频率至少设为电流环带宽的5到10倍。如果电流环带宽1.5kHz滤波器截止频率至少7.5kHz。对于20kHz的PWM频率这个截止频率对应的alpha大约是0.3到0.5。具体值可以用示波器看电流波形来调目标是波形干净但不过分圆滑。还有一种做法是“采样两次取平均”或者“在PWM周期内对称位置采两次”利用PWM的对称性抵消部分噪声。这个方法在TI的InstaSPIN里有用到效果不错但实现起来复杂一些。3. 内模解耦让d轴和q轴不再互相打架3.1 为什么PI控制器天然存在耦合PMSM在dq坐标系下的电压方程是这样的ud R * id Ld * did/dt - ωe * Lq * iquq R * iq Lq * diq/dt ωe * Ld * id ωe * ψf注意看ud方程里有-ωe * Lq * iq这一项uq方程里有ωe * Ld * id这一项。这就是交叉耦合你想控制id但iq的变化会通过ωe * Lq影响ud你想控制iqid的变化也会影响uq。电机转得越快ωe越大耦合越严重。如果只用两个独立的PI控制器每个轴各管各的那么在高速时d轴和q轴的相互干扰会导致动态响应变差严重时甚至振荡。这就是为什么很多人发现低速时电流环好好的一上高速就抖。3.2 前馈解耦与内模解耦的区别最直接的解耦方法是前馈解耦在PI输出上直接加上耦合项的补偿。也就是说ud的输出等于PI_id的输出减去ωe * Lq * iquq的输出等于PI_iq的输出加上ωe * Ld * id加上ωe * ψf。这样就把耦合项抵消掉了。前馈解耦的问题是它依赖电机参数Ld、Lq、ψf的准确性。如果参数不准补偿就不完全还是会有残余耦合。而且前馈解耦是“开环补偿”对参数误差没有鲁棒性。内模解耦Internal Model Decoupling则不同。它的思路是在控制器里内置一个电机模型的“副本”让控制器去模拟电机的行为从而实现对耦合的自然抵消。具体来说内模控制把电机模型和控制器结合起来设计一个补偿器使得闭环传递函数变成一阶惯性环节没有交叉项。内模解耦的优点是对参数误差有一定的鲁棒性而且设计好的话d轴和q轴的响应可以做到完全独立。缺点是实现起来比前馈解耦复杂需要设计补偿器的参数。在实际工程中我通常这样取舍如果电机参数比较准比如自己测过电感前馈解耦就够了简单可靠如果参数不准或者电机参数随温度变化大可以考虑内模解耦但调试周期会长一些。3.3 解耦项的计算时机与数值精度不管用哪种解耦计算耦合项的时候要注意时机。耦合项里的id、iq应该是当前周期的采样值而不是上一周期的值。如果用上一周期的值在高速时会有相位滞后解耦效果打折扣。另外ωe的计算精度也很重要。ωe是电角速度等于电角度θ的微分。如果θ有噪声微分后噪声会被放大。所以通常会对ωe做低通滤波或者用观测器直接输出ωe而不是对θ微分。还有一个细节解耦项里的Ld、Lq应该是饱和后的电感值。电机在大电流时电感会饱和Ld、Lq会下降。如果解耦项里用的是小信号电感大电流时解耦就不准了。所以如果有条件最好做一个电感-电流查表根据当前电流查表得到电感值。4. 电流环PI参数整定从理论计算到实际微调4.1 用零极点对消法算出初始值电流环的被控对象可以近似为一阶惯性环节加一个延迟。对于d轴被控对象是1/(Lds R)对于q轴是1/(Lqs R)。PI控制器的传递函数是Kp Ki/s。用零极点对消法让PI的零点对消被控对象的极点也就是令Ki/Kp R/L。这样闭环就变成一个一阶系统带宽由Kp决定。具体公式Kp L * ωcKi R * ωc其中ωc是期望的电流环带宽单位是rad/s。如果希望带宽是1kHzωc 2π * 1000 ≈ 6283 rad/s。举个例子电机相电阻R 0.5Ωd轴电感Ld 2mH期望带宽1.5kHz。那么Kp 0.002 * 2π * 1500 ≈ 18.85Ki 0.5 * 2π * 1500 ≈ 4712这个Ki看起来很大但注意它是积分增益实际代码里通常写成Ki * Ts的形式Ts是控制周期。如果控制周期是50微秒那么每个周期累加的积分量是4712 * 0.00005 ≈ 0.236。这个值就合理了。4.2 实际调试中PI参数怎么微调理论算出来的值只是起点。实际调试时我通常按这个顺序来先把Ki设为0只调Kp。给q轴一个阶跃电流指令看电流响应。如果响应太慢增大Kp如果超调大或者振荡减小Kp。目标是响应快但不过冲。然后加Ki。从小往大加观察稳态误差是否消除。如果加了Ki之后出现低频振荡说明Ki太大了减小。最后看d轴。d轴通常给0但可以给一个小阶跃看d轴电流是否被q轴干扰。如果干扰大检查解耦项。这里有个经验q轴的Kp通常比d轴稍大一点因为q轴电感通常比d轴小对于凸极电机同样的带宽需要更大的Kp。但差别不会太大一般不超过20%。还有一个坑积分饱和。如果电流指令突变PI输出会饱和到电压限幅值积分项会一直累加导致退饱和时响应迟钝。解决办法是加抗饱和逻辑当输出饱和时停止积分累加或者把积分项回退到限幅值。// 简单的抗饱和实现 if (output max_voltage) { output max_voltage; integral - Ki * error; // 回退积分 }4.3 带宽到底设多少合适电流环带宽不是越高越好。带宽越高响应越快但对采样噪声越敏感对PWM频率要求也越高。一般来说PWM频率10kHz电流环带宽不超过1kHzPWM频率20kHz电流环带宽可以到2kHzPWM频率40kHz以上带宽可以到3kHz甚至更高经验公式电流环带宽 ≤ PWM频率 / 10。这是一个保守但安全的准则。如果你追求极致响应可以到PWM频率/5但要做好噪声处理。另外带宽还受限于速度环。速度环带宽通常是电流环带宽的1/5到1/10。如果电流环带宽1.5kHz速度环带宽150到300Hz比较合适。5. 无感FOC下的电流环特殊考量5.1 观测器角度误差对电流环的影响无感FOC没有编码器角度来自观测器滑模观测器、龙伯格观测器等。观测器估算的角度θ_est和真实角度θ_real之间一定有误差。这个误差对电流环的影响是Park变换和反Park变换用的角度不准导致d轴和q轴的实际电流与指令值有偏差。具体来说如果角度误差是Δθ那么实际d轴电流id_real id_cmd * cos(Δθ) iq_cmd * sin(Δθ)q轴类似。也就是说角度误差会导致d轴和q轴之间“串扰”。如果Δθ是10度sin(10°) ≈ 0.17那么q轴指令的17%会跑到d轴去造成额外的铜耗和转矩脉动。所以无感FOC下电流环的PI参数通常要比有感FOC保守一些因为角度误差本身就会引入扰动。而且观测器的带宽和电流环带宽要匹配观测器太快会引入噪声太慢会跟不上。5.2 低速时的电流环信噪比问题无感FOC在低速时反电动势很小观测器很难估算准确角度。这时候电流环采到的电流里有用信号也小信噪比差。常见的做法是低速时用高频注入法估算角度但高频注入会带来额外的电流谐波电流环要能抑制这些谐波。或者低速时切换到开环启动电流环只做电流控制角度由斜坡给定。不管哪种方法低速时电流环的带宽通常要降低因为噪声相对更大。我通常会在低速时把电流环带宽降到高速时的60%到70%等速度上来再恢复。5.3 弱磁与MTPA对电流环的额外要求关键词里提到了“foc mpta控制”也就是最大转矩电流比控制。MTPA的意思是对于给定的转矩指令分配id和iq使得定子电流最小。对于凸极电机Ld ≠ LqMTPA意味着id不为零通常是负值弱磁方向。MTPA下电流环的d轴和q轴都有指令而且两个轴的指令是耦合的由转矩指令和转速共同决定。这时候电流环的动态响应要求更高因为id和iq要同时跟踪。如果电流环带宽不够MTPA的轨迹就会偏离效率下降。另外弱磁控制时d轴电流是负的电机端电压接近极限。这时候电流环的输出电压很容易饱和抗饱和逻辑就特别重要。如果积分饱和处理不好弱磁时电流会失控。6. 一套可落地的电流环代码框架6.1 中断服务程序里的执行顺序电流环通常放在PWM中断里执行每个PWM周期执行一次。执行顺序很重要读ADC电流从ADC数据寄存器读ia、ib计算ic -ia - ib。零漂补偿减去预先标定的偏置。Clarke变换ia、ib、ic → iα、iβ。Park变换iα、iβ、θ → id、iq。电流PIid、iq与指令比较PI输出ud、uq。解耦补偿ud、uq加上耦合项。电压限幅ud、uq的幅值不能超过母线电压对应的最大电压。反Park变换ud、uq、θ → uα、uβ。SVPWMuα、uβ → 三相占空比。更新PWM寄存器。这个顺序不能乱。比如Park变换必须在读ADC之后SVPWM必须在反Park之后。解耦补偿可以在PI之后、限幅之前做。6.2 关键代码片段与注释// 电流环中断服务程序简化版 void FOC_CurrentLoop_ISR(void) { // 1. 读ADC int16_t ia_raw ADC_Read(IA_CHANNEL); int16_t ib_raw ADC_Read(IB_CHANNEL); // 2. 零漂补偿 float ia (ia_raw - ia_offset) * current_scale; float ib (ib_raw - ib_offset) * current_scale; float ic -ia - ib; // 3. Clarke变换 float i_alpha ia; float i_beta (ia 2.0f * ib) * ONE_BY_SQRT3; // 4. Park变换 float sin_theta sinf(theta_elec); float cos_theta cosf(theta_elec); float id i_alpha * cos_theta i_beta * sin_theta; float iq -i_alpha * sin_theta i_beta * cos_theta; // 5. 电流PI float id_error id_ref - id; float iq_error iq_ref - iq; ud_pi Kp_d * id_error Ki_d * id_integral; uq_pi Kp_q * iq_error Ki_q * iq_integral; // 积分累加与抗饱和 id_integral id_error; iq_integral iq_error; // ... 抗饱和逻辑 // 6. 解耦补偿 float ud ud_pi - omega_e * Lq * iq; float uq uq_pi omega_e * Ld * id omega_e * psi_f; // 7. 电压限幅 float u_mag sqrtf(ud * ud uq * uq); if (u_mag u_max) { ud ud * u_max / u_mag; uq uq * u_max / u_mag; } // 8. 反Park变换 float u_alpha ud * cos_theta - uq * sin_theta; float 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); }这段代码里每一步都有讲究。比如sinf和cosf很耗时实际工程中通常用查表法或者CORDIC算法。查表法要注意表的大小和插值表太小会有量化误差导致电流谐波。6.3 调试接口与数据记录调电流环的时候一定要有数据记录手段。最简单的方法是用DAC输出关键变量比如id、iq、ud、uq用示波器看波形。如果没有DAC可以用PWM输出加RC滤波或者用串口发送数据到上位机。我习惯在代码里留一个“调试模式”可以通过串口命令切换要观察的变量。比如// 调试变量选择 typedef enum { DEBUG_ID, DEBUG_IQ, DEBUG_UD, DEBUG_UQ, DEBUG_THETA, DEBUG_SPEED } DebugVar_t; DebugVar_t debug_var DEBUG_ID; // 在中断里 if (debug_counter debug_div) { debug_counter 0; DAC_Output(GetDebugValue(debug_var)); }这样调参的时候想看哪个变量就看哪个不用改代码重新编译。7. 那些年我踩过的电流环坑7.1 电流采样偏置随温度漂移有一次调一个电机常温下电流环好好的跑半小时后开始抖。查了半天发现是采样电阻的偏置随温度漂移了。采样电阻本身温漂不大但运放的偏置温漂大。后来换了低温漂运放并且在代码里加了“上电校准运行中缓慢校准”的逻辑电机不转时校准一次运行中如果发现d轴电流长期不为零而指令是零就缓慢修正偏置。这个坑的教训是零漂校准不能只做一次要考虑温漂。7.2 解耦项符号写反导致高速振荡前馈解耦的符号很容易写反。ud方程里是-ωe * Lq * iquq方程里是ωe * Ld * id。如果符号写反解耦就变成了“正反馈”高速时振荡会越来越厉害。我见过一个项目低速没问题一到3000转就啸叫查了两天才发现是解耦符号反了。验证方法让电机在高速下运行给q轴一个小阶跃看d轴电流的响应。如果d轴电流先往一个方向跳然后慢慢回来说明解耦方向是对的如果d轴电流越跳越大那就是符号反了。7.3 PI参数在空载和负载下表现不一致空载时调好的PI参数带载后可能就振荡了。原因是带载后电流大了电感饱和被控对象的极点变了。零极点对消法用的电感是小信号电感大电流下电感下降极点右移原来的PI参数就不匹配了。解决办法要么用电流查表得到实时电感要么把PI参数设得保守一些牺牲一点动态响应换取稳定性。我通常会在代码里做一个简单的“增益调度”根据电流大小微调Kp和Ki电流大时适当减小Kp。7.4 中断执行时间超限电流环中断里要做Clarke、Park、PI、解耦、反Park、SVPWM计算量不小。如果主频不够高或者用了浮点运算但没有FPU中断执行时间可能超过PWM周期导致控制周期不稳定。我遇到过用STM32G070Cortex-M0无FPU做FOC的浮点运算全靠软件模拟中断执行时间接近40微秒而PWM周期是50微秒几乎没余量。后来改成定点运算时间降到15微秒。所以选型的时候要注意做FOC最好选带FPU的MCU比如Cortex-M4F或M7。如果非要用M0那就用定点运算Q格式要选好避免溢出。8. 从能转到转得好电流环的进阶优化8.1 电流环带宽的在线辨识如果不想手动调PI可以做电流环带宽的在线辨识。思路是给电流环一个特定频率的小信号测量响应幅值和相位然后自动调整PI参数使得带宽达到目标值。这个方法在高端驱动器里有用到但实现复杂一般项目用不上。8.2 预测电流控制预测电流控制Predictive Current Control是另一种思路不用PI而是根据电机模型直接计算出下一个周期需要的电压使得电流在下一个周期达到指令值。理论上响应更快但依赖模型准确性参数不准时性能下降明显。8.3 电流环与速度环的带宽匹配最后强调一点电流环和速度环的带宽要匹配。如果速度环带宽接近电流环带宽两个环会相互干扰系统振荡。经验法则是速度环带宽 ≤ 电流环带宽 / 5。如果电流环带宽1.5kHz速度环带宽不要超过300Hz。调速度环的时候先把电流环调好确认电流环阶跃响应干净利落再调速度环。如果电流环没调好就调速度环那就是在沙子上盖楼。我在实际项目中的体会是电流环调好了整个FOC就成功了八成。剩下的速度环、位置环、弱磁、MTPA都是在电流环的基础上叠加。所以花时间把电流环的采样、变换、解耦、PI整定这些基础打牢后面会省很多事。每次遇到电机控制的问题先回头看电流环十有八九能找到根源。