
简介面向电力电子技术学习者和嵌入式C语言开发者这是一份关于PFC功率因数校正的PID控制仿真与离散化实现学习包。资源基于Simulink搭建PFC控制模型通过PID控制器将功率因数校正到接近1并把控制器离散化后以C语言代码实现帮助阅读者理解从仿真验证到嵌入式移植的完整闭环。压缩包共5个文件包含Simulink模型.slx、两个C语言源文件、一个TLC代码生成配置文件及参考说明文档整体仅35KB结构精简便于快速上手。目前已有2348人查看学习适合需要掌握PFC控制原理、PID参数调节与离散化C编程的初学者或工程师。学习时可结合Simulink模型和C源码具体观察离散PID的采样周期、系数处理与输出限幅等细节从模型搭建、PID整定到C生成封装形成一条可跟练的实践路径为后续电源控制开发提供参考。 做电源的同行应该都见过这种场景PFC电感发出刺耳的“吱吱”声母线电压在重载切轻载的瞬间冲过头或者MOSFET的电流波形在过零点扭成一团。你怀疑是环路参数不对可反复调了几次Kp和Ki问题依然原地踏步。后来你会发现问题往往不在“PID怎么调”而在“PID在PFC里到底怎么落地的”。这里的落地指的不是Matlab里的连续域仿真模型而是主控芯片里跑的C语言离散化实现。这也是我想写这篇文章的原因。PFC的PID控制特别是基于C语言的离散化实现是数字电源绕不开的一步。无论是单项三脚升压PFC、图腾柱PFC还是三相六开关PFC只要主控用的是MCU或者DSP你就必须把连续域的控制律写成采样周期驱动的离散代码。适合正在做数字电源、PFC控制、或者刚把控制理论往工程代码上迁移的工程师参考。1. 先把PFC的控制目标和PID的“控制对象”对齐很多刚接触PFC的同事会上来就问“PID三个参数怎么设”但PFC的控制和普通DC-DC有个本质区别它的电流参考不是恒定的而是跟随输入电压波形同步变化的馒头波。这决定了PID不是对着一个固定目标在追而是让电感电流高频率地去跟踪一个动态正弦包络。如果没想通这一层后面调参都会跑偏。1.1 电压外环和电流内环各自干什么PFC的控制链路是典型双闭环结构。电压外环的作用很直接维持输出母线电压恒定。负载发生变化时输出电容的电压会先跌落电压外环感知到误差后调整电流环的参考幅度。这个幅度是电流参考包络的峰值也就是正弦参考的幅值系数。电流内环的作用则是让输入电流的瞬态值紧紧咬住这个正弦参考保证输入电压与输入电流同相位、同波形从而实现功率因数校正。两者的带宽要求完全不同。电压环的带宽通常只做到10~20Hz因为它的任务是对抗负载的大幅波动不是对抗每个开关周期内的纹波。如果电压环带宽太高不仅会让母线动态出现振荡还会把输出电容的低频纹波传到电流参考里导致输入电流失真。电流环的带宽则要高得多一般要做到开关频率的1/10到1/5因为只有足够高的带宽电流才能在一个交流周期内跟踪上正弦参考的斜率。1.2 为什么普通增量式PID直接套用会出问题网上能搜到大量“增量式PID的C语言实现”那些代码没有错但如果直接复制进PFC的中断服务函数里大概率会失望。原因在于PFC的电流环面对的误差信号不是缓慢变化的而是双工频频率的交流量。一个正弦周期只有10ms或20ms电流内环必须在这么短的时间内完成上百次跟踪修正对控制律的相位滞后非常敏感。我测过一组数据相同参数下用前向欧拉离散化得到的电流环带宽大约比双线性变换低10%~15%而相位裕度可能少8~10度。在普通电机转速环里可能无所谓但在PFC电流环里相位裕度不足会直接表现为过零点处的电流畸变或者轻载时电感电流断续引发的振荡。这些都是后期很难靠调Kp补回来的缺口。所以离散化方法的选择不是一个“用哪个都行”的问题而是控制带宽需求决定的必须项。2. 离散化这一步到底在数学上做了什么模拟PID控制器的传递函数是连续的可MCU只能按固定周期采样和计算。离散化就是把连续积分和微分操作换成数值近似。这一换不是简单替换符号而是把时间因子明确地放进了算法里。2.1 位置式与增量式的选择逻辑位置式PID输出的是控制量的绝对值例如PFC电流环输出的是占空比本身。它的优点是直观缺点是积分项需要保存历史累加值一旦输出饱和必须做复杂的抗积分饱和处理。增量式PID输出的是控制量的相对变化量也就是每次中断里计算Δu再和上一次输出相加得到本次占空比。它天然不需要累计历史误差抗饱和逻辑也简单很多因为只要限制输出幅值即可积分累加过程隐含在输出值里。我的建议是PFC控制里尽量用增量式尤其电流环。占空比的每一次变化不能太剧烈增量式天然的“平滑”特性在这里很有用。加上PFC的电流环运行频率通常是50kHz~200kHz每拍只改变一小步天然起到一定限幅作用不会出现极端跳变。2.2 双线性变换、前向欧拉、后向欧拉怎么选离散化的三个常用方法背后对应不同的近似精度和稳定域前向欧拉最简单用当前误差和上一次误差的差替代微分。缺点是有可能把稳定系统映射到离散域的稳定域外采样频率不够高时容易震荡。后向欧拉稳定域比前向欧拉好但会让控制滞后多一点在高带宽环路中会产生附加相位延迟。双线性变换Tustin精度最高把连续域的频率曲线映射到离散域时保持稳定的同时频率畸变在Nyquist频率附近可控。代价是表达式稍复杂但在C语言里也就是多个加减乘除。对PFC这种高带宽功率变换器我会优先选双线性变换。它的相位特性最接近连续原型在电流环带宽接近采样频率1/10时依然能保持较好的相位裕度。不过双线性变换的微分项有个需要注意的地方它会引入一个位于z-1的零点对高频噪声更敏感所以实际实现时通常需要在微分项前面加一阶低通滤波或者干脆把微分系数调得很小。2.3 采样周期的物理含义和代码约束PFC的采样周期通常是开关周期的一半或等于开关周期。这里有一个隐蔽的坑PID计算的执行时间不能超过采样周期否则下一次中断到来时上次计算还没跑完系统时序全乱。所以我个人习惯先把整个中断里的代码执行周期用示波器实测一遍而不是只靠估算。实测结果通常会让你吃惊一个带浮点乘法的完整PID计算加ADC读取和PWM更新在72MHz的MCU上需要几个微秒在低端MCU上可能翻倍。如果执行时间太长可以考虑把PID计算拆到两个中断里或者用定点数替代浮点数。3. C语言实现的核心抗积分饱和与输出限幅代码骨架本身并不复杂一个PID_Update函数几十行就能写完。真正拉开水平差距的是异常工况下的处理逻辑。PFC上电瞬间母线电压从零开始建立误差大到离谱如果积分项不处理占空比会瞬间打满然后电流过冲保险丝或者整流桥可能因此受累。3.1 增量式PID的C代码骨架下面这段代码是我在项目里实际用过的结构基于双线性变换的离散化结果整理成适合PFC电流环的形式typedef struct { float kp; float ki; float kd; float dt; float prev_err; float prev_delta_out; float out_limit_max; float out_limit_min; } PidInc_t; float PidInc_Update(PidInc_t *pid, float ref, float fb) { float err ref - fb; float delta_out; delta_out pid-kp * (err - pid-prev_err) pid-ki * err pid-kd * (err - 2.0f * pid-prev_err pid-prev_err2); // 输出增量限幅防止单拍占空比跳变过大 if (delta_out pid-delta_limit) delta_out pid-delta_limit; if (delta_out -pid-delta_limit) delta_out -pid-delta_limit; pid-prev_err2 pid-prev_err; pid-prev_err err; pid-out delta_out; // 输出值限幅 if (pid-out pid-out_limit_max) pid-out pid-out_limit_max; if (pid-out pid-out_limit_min) pid-out pid-out_limit_min; return pid-out; }里面有个容易被忽略的字段prev_err2。这是为了保存前两拍的误差因为双线性变换后的微分项需要用到e(k)、e(k-1)、e(k-2)三拍的数据。这和教科书里最常见的“增量式PID只需保存两个误差”的版本略有差异但因为双线性变换的微分处理方式不同必须多存一拍。如果你用的是前向欧拉那就不需要这个变量。这正说明了“拿到代码就抄”的风险离散化方式不一样代码结构就不一样。3.2 积分的隐性处理和抗饱和策略增量式PID看似不需要积分累加项但实际上积分作用隐含在输出值out里。它的抗积分饱和策略和位置式有所不同位置式必须在积分项饱和时冻结积分累加增量式则天然不会让积分无限制增长因为每拍输出变化量受限out值也会被输出限幅钳住。但这个天然抗饱和是有前提的输出限幅的上下限必须和PWM实际可用的占空比范围匹配。比如数字PWM的周期寄存器是6000那么占空比范围就是0~6000你把out_limit_max设为5500、min设为500就会比直接设成6000和0更安全。多留出的裕量给电感电流的瞬态尖峰提供一个缓冲避免占空比在饱和边界处来回撞击。3.3 电压外环的建议必须加前馈PFC的电压环还有一个特殊问题输出母线电压含有100Hz或120Hz的纹波。如果电压环PID直接对这个纹波起反应电流参考就会带上纹波分量导致输入电流THD变差。针对这个情况工程上常用的做法不是单纯靠调低电压环带宽而是在电压环输出后叠加一个输入电压有效值的前馈项。这样电流内环的参考幅度 电压环输出量 × 输入电压波形函数前馈提供一个“基底”电压环只在误差基础上做微调。我在代码里的实现方式是把电压环PID的输出作为母线有效值的修正系数然后乘以归一化的输入电压正弦波形采样值得到电流环的实时参考current_ref (voltage_loop_out feed_forward_gain) * normalized_input_voltage;feed_forward_gain完全可以通过输入电压RMS和输出功率计算出来作为基础值。比如输出功率2000W输入220VAC效率95%那么电流峰值的基值大约为sqrt(2)2000/(2200.95)约13.5A。这个前馈值能在负载突加时让电流环秒级响应而电压环只负责消除稳态误差和低频扰动。4. 参数整定的顺序与实测中的意外状况很多教程告诉你“先调Kp再调Ki最后调Kd”这个顺序没错但对于PFC这种双闭环系统更关键的是先调内环再调外环。内环没稳定外环参数怎么调都是空的。4.1 电流环的整定方法和安全启动策略电流环的Kp可以从理论计算给出一个初始值。以升压PFC的平均电流模式为例电流环被控对象近似为电感L上的电流对占空比的传递函数G(s)Vout/(s·L)。如果你用的是DSP或者高端MCU可以直接算如果是普通MCU就用试凑法设一个小的KpKd0Ki设为零然后逐渐增大Kp直到电流波形出现轻微的抖动再把Kp回调到抖动的70%左右。注意在整定过程中要让PFC带着负载跑空载和满载的系统特性差很多。空载时电感电流是断续的模型不连续调出来的参数在满载时大概率不能用。反过来也一样。真正的控制器参数应该在1/3负载和满载这两个工况下分别测一遍然后取折中。如果两端差异太大就得考虑是否要加分段PID或者自适应参数不过那是后话了。4.2 轻载振荡的根因和解决思路我遇到过一种情况满载电流波形很漂亮THD不到5%但负载降到10%时母线电压开始以几Hz的频率小幅振荡同时电感啸叫。初步怀疑是电压环增益过高但把Kp减小后满载动态又变差了。后来用示波器抓了电流环参考和反馈的波形发现轻载时电流参考很小而电流环在断续模式下的增益明显下降导致相位裕度变低整个环路在边界上摇摆。最终的解决办法是加了轻载阈值切换当输出功率低于一定值时强制系统进入间断模式Burst ModePID输出不做连续调节而是以固定脉宽周期性开启和关闭。这个方案比单纯降增益更有效因为它在轻载时绕开了电流环连续控制的频率特性限制。如果你不想做Burst Mode那么至少要在轻载时降低电压环的Kp并把电流环的Ki调小一点减小低频段增益消除振荡趋势。4.3 过零点畸变的调试案例另一个常见问题是电流波形在输入市电过零点附近出现削平或凹陷。这通常不是PID参数问题而是电感电流在过零点附近小电流时容易进入断续模式或者电流环更新率和占空比变化方向跟不上正弦参考的极性切换。有一个很隐蔽的代码坑ADC采样电流使用了均值滤波而均值窗口跨越了PWM的开关周期导致采样值不是该拍的真实值。特别在过零点附近参考电压很低电流斜率变化快滤波后的反馈值和真实值之间产生较大相位差控制器就会在错误的方向上调节。解决方法是把ADC采样时刻对齐到PWM计数器的谷底或峰值时刻确切地说要在开关管关断的中点采样电感电流这样采到的值接近电感电流的平均值。这一点在代码里体现为触发ADC的时机设置而不是简单地在定时器中断里随便启动一次采样。4.4 表格PFC调试常见现象与排查顺序现象优先排查方向对应的处理手段满载电流波形正常轻载母线振荡电压环增益、电流环在DCM下的相位裕度降低电压环Kp或轻载使能Burst Mode过零点电流畸变ADC采样时序、电流环带宽对齐PWM中点采样提高采样频率接近开关频率突加负载母线电压跌落过大电压环带宽、前馈设计增加电压环Ki加入功率前馈补偿电感啸叫从100Hz~几kHz不等环路稳定性、占空比跳变过大加入输出增量限幅减小Kp或者增加微分滤波THD整体偏高电流参考质量、输入电压采样检查电压环纹波是否污染电流参考考虑加入陷波器5. 把代码放进工程前的最后几个检查项写完PID代码不代表可以合入项目我在实际开发中吃过几次亏后来总结出一个固定检查清单每次都会过一遍。5.1 变量类型与数据位宽要明确PFC环路PID如果使用浮点类型在带FPU的MCU上问题不大但在普通Cortex-M0这种不带FPU的芯片上浮点运算会消耗大量CPU周期。我试过在 48MHz 的 Cortex-M0 上跑三环PID电压、电流、以及一个额外的相位补偿环三个全用浮点中断执行时间一下子到30微秒以上加上ADC和通信PWM频率只能降到40kHz以下完全不够用。这种情况下需要把PID的核心计算转成Q格式定点数。常见做法是把系数统一乘上2的N次方比如Q15格式然后整型乘加再右移N位还原。整数运算在M0上比浮点快一个数量级。但注意每乘一次之后数值范围会变大中间变量必须用32位甚至64位整数承接否则溢出后整个控制就乱了。代码里每处都加上注释标清楚定点格式和范围别指望复盘的时候能想起来。5.2 中断优先级和PWM事件对齐PID计算建议放在PWM周期匹配中断里执行保证每个开关周期正好更新一次占空比。不同厂商的PWM外设触发中断的时机不一样有些是周期溢出触发有些是周期中点触发。务必确认CPU进入中断的时刻和PWM计数器的相位关系这个相位差直接决定了控制延迟。1个开关周期的控制延迟会让电流环相位裕度损失大约360°×f_sw/f_crossover 的部分实际数值可能高达几十度根本不可能靠参数调回来。另外一个值得做的事是把ADC采样完成中断和PID计算中断设为同一优先级并且确保PID中断不会被其他非关键中断打断。我在项目里吃过亏通信中断优先级设得比PID高每隔几百微秒PID计算就被通信打断一次执行时间抖动叠加到占空比更新上输出电流波形出现了明显的周期性毛刺。5.3 从仿真到实机的落差要提前预期不少同事习惯先在Matlab/Simulink把PFC双闭环模型调到非常好的指标然后拿着仿真参数写C代码。实机一测波形不对就以为是代码写错了。其实这种落差大部分来自两个地方一是仿真里控制延迟往往是理想化的而实机代码经过中断处理和PWM更新至少有1~2拍的延迟二是仿真里器件模型是连续平均化的没有体现开关纹波和ADC采样对抗扰性的影响。我自己的做法是在仿真里故意把控制器的计算输出延迟一拍让仿真更接近实机。跑通了以后再用上一节说的整定方法在实机上微调。这样仿真给出的参数在实机上最多差30%的增益不会出现“满盘皆输”的情况。6. 实际调试中的最后一点经验分享关于PFC的PID控制如果让我只说一个最有价值的经验那就是在动手写代码之前先在示波器上把电压和电流两路反馈信号的相位关系弄清楚。市面上任何一本控制教材都会告诉你相位裕度有多重要但真正到了调试现场你会发现最容易搞错的不是数学而是“你以为是反馈的信号其实相位已经转了180度”这种工程事故。比如电流采样用的是霍尔传感器正向定义装反了代码里的减法变成加法PID再怎么调都只能越来越发散。这种错误靠调参是永远调不出来的只有从源头检查信号链。另外一个习惯也建议保留每修改一组PID参数都在代码注释里记下当时的负载条件、输入电压、母线电压以及基本波形表现。我在调试PFC的一周内能记录十几组参数最后回看笔记就会发现系统的变化轨迹很清晰哪一个方向是有效的哪一个方向是无效的一目了然。这比靠感觉反复试错要高效得多。本文还有配套的精品资源点击获取