ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32G474数字半桥LLC电源控制:从HRTIM配置到PFM调频实战

STM32G474数字半桥LLC电源控制:从HRTIM配置到PFM调频实战 你们做数字电源的应该都经历过这种场面给板子通电之后功率管直接穿了或者输出一带载电压就往下掉稍微再往上调一点频率效率又难看得很。半桥LLC的控制思路跟普通反激完全不是一套玩法——谐振频率、死区时间、采样相位几个点搞不定程序写得再花哨也白搭。这篇文章我把STM32G474上跑通数字LLC电源控制程序的完整过程拆一遍从为什么选这颗芯片到HRTIM怎么配、ADC怎么跟PWM对齐再到软启动和PFM调频环路的代码最后把调试阶段栽过坑也一并列出来。整个工程做的是一个240W左右、24V/10A输出的半桥LLC电源控制软件输入侧前面再接一级PFC拉到400V直流。对于想从零开始接触数字电源或者已经在做模拟LLC、想迁移到数字控制的朋友来说这套程序结构可以作为入门模板。文中所有代码都是我在实际项目里跑过的不是那种“示例代码”你直接抄回去改改参数就能用。1. 数字LLC不是随便调的先把拓扑和控制策略对一遍1.1 半桥LLC为什么适合做大中功率场景在我们常见的开关电源拓扑里反激适合150W以内的应用双管正激和移相全桥也有各自的应用区间但要说到中大功率通信电源、适配器、充电模块这一类场景LLC谐振变换器几乎是绕不开的方案。原因很直接LLC可以在宽输入范围内实现原边开关管ZVS副边二极管ZCS开关损耗被压得非常低很容易做到94%以上的效率。半桥LLC的拓扑结构默认大家都有概念原边两个管子构成半桥输出连接到谐振网络也就是Lr、Cr和变压器励磁电感Lm的串联支路。它跟普通PWM方波变换器最大的区别在于我们不再通过调节占空比来稳压而是通过改变开关频率让谐振网络的阻抗发生变化从而改变输出电压的增益。频率升高谐振网络阻抗变大输出增益会下降频率降低增益上升。这个特性直接决定了数字控制程序的写法。传统反激或正激可以用固定频率PWM直接调节脉宽LLC则要用PFM调频方式而且开关管占空比通常固定为50%上下管互补加入合适的死区时间。程序里要做的事情本质上是“根据输出电压误差实时计算出一个合适的开关频率并刷新到定时器周期寄存器里”。1.2 控制策略PFM调频与增益曲线的对应LLC谐振变换器有一个非常经典的增益曲线族反映的是不同品质因数Q值下电压增益M和归一化频率fn实际开关频率与谐振频率的比值之间的关系。我们不需要把每个公式都背下来但有几个关键结论必须清楚。第一工作频率在谐振频率fr附近时增益约等于1这个区间也是ZVS最容易实现的区域。第二当开关频率大于fr时谐振网络呈感性增益小于1管子在开通前电流方向已经反转会自然续流实现零电压开通。第三当频率低于fr后增益可以大于1但负载越重Q越大增益曲线会变平甚至失去单调性如果控制不好很容易进入容性区域导致ZVS失效严重时直接炸管子。所以数字LLC控制程序里频率范围的上下限设置是安全第一道保障。比如我这个工程谐振频率fr定在300kHz左右实际运行频率范围限制在120kHz到350kHz之间。低于或高于这个范围程序必须做限幅处理防止环路异常引起频率跑飞。增益曲线的非单调性问题也决定了软启动过程的必要性。如果上电直接以额定300kHz运行输出电压基本起不来因为谐振槽路在满载大Q值条件下增益偏低。我习惯把启动频率定在2倍谐振频率也就是600kHz左右然后按设定的速率往下扫频让输出电压缓慢建立等接近目标值之后再切换到闭环PI调节。1.3 为什么选STM32G474而不是普通G431或DSP做数字电源主控选型不是随便拍拍脑袋就定的。STM32G474这颗芯片在数字电源领域已经小有名气核心原因归结为三点。第一它内部集成了一组高分辨率定时器HRTIM1输出PWM的精度和灵活性非常在线。开关电源频率通常是几百kHz周期寄存器值也就几百个tick普通的16位定时器勉强够用但HRTIM的分辨率可以达到几百皮秒级别在做LLC的PFM调频和死区时间微调时优势非常明显。第二G474集成了四个独立的12位ADC支持硬件过采样采样速度最高能到5Msps左右在电源控制环里可以做到“边沿对齐采样”也就是在PWM周期的合适时刻触发转换把开关噪声的影响降到最低。第三它还内置了一组模拟比较器CMP和DAC可以非常快地完成过流保护动作不需要等CPU进中断硬件直接就能把PWM封锁掉。当然Cortex-M4处理器主频170MHz在控制环里也足够用。一个典型电压环控制在2微秒以内就能跑完对240W这个功率级别的LLC来说完全不会成为瓶颈。2. 硬件平台与外设资源分配2.1 HRTIM配置互补PWM与死区时间数字LLC半桥电路需要两路互补的PWM信号分别驱动上下桥臂的功率管。G474的HRTIM1一共有六个从定时器每个从定时器可以输出两路互补信号我取定时器A和定时器B分别作为原边半桥两路当然也可以用单个定时器的输出加互补输出。实际工程里我用了定时器A的输出比较通道TA1和TA2设置为方波模式占空比固定50%。频率由周期寄存器PER决定脉冲宽度由CMP寄存器决定。PFM调频时我们只需要重新写入PER和CMP把占空比保持在50%同时更新死区时间配置就可以平滑改变输出频率。死区时间计算要先确定HRTIM的输入时钟。G474的最大系统时钟是170MHzHRTIM挂载在APB2时钟域如果RCC配置正确可以拿到170MHz的计数频率。以200ns死区时间为例子170MHz下每个tick约5.88ns200ns对应34个tick左右。代码如下LL_HRTIM_TIM_ClearConfig(HRTIM1, LL_HRTIM_TIMER_A); LL_HRTIM_TIM_SetPrescaler(HRTIM1, LL_HRTIM_TIMER_A, LL_HRTIM_PRESCALER_DIV1); LL_HRTIM_TIM_SetCompare(HRTIM1, LL_HRTIM_TIMER_A, LL_HRTIM_COMPARE_REGISTER1, half_period); LL_HRTIM_TIM_SetPeriod(HRTIM1, LL_HRTIM_TIMER_A, period); // 死区时间配置 LL_HRTIM_TIM_SetDeadTime(HRTIM1, LL_HRTIM_TIMER_A, LL_HRTIM_DEADTIME_PRESCALER_DIV1, __LL_HRTIM_CALC_DEADTIME_DELAY_IN_NS(HRTIM_CLOCK, 200000000));注意一个细节死区时间不是越大越好。死区太大虽然安全但是效率明显下降还会在动态响应时引入非线性死区太小半桥上下管可能直通。一般驱动氮化镓管的话死区可以压到50到100ns驱动普通CoolMOS的话取150到250ns比较合适。具体需要在示波器上看原边电流波形和漏源极电压vds的过渡沿来反复调。2.2 电流电压采样通道和ADC触发对齐数字电源控制环路的性能很大程度取决于采样时刻准不准。LLC是谐振变换器原边电流是个正弦状的半周期波形如果用软件随机时刻去采样采出来的电流值没有太多参考意义用作过流判断很容易误报或者漏报。我的做法是用HRTIM的周期事件去触发ADC注入组转换每个开关周期自动采样一次完全不需要CPU干预。ADC采样时序有个细节尽量别在开关管切换的死区时刻采样否则高频振铃会混进来。可以把ADC触发的延迟时间配置成“周期中点附近”也就是半周期位置这时候谐振电流接近峰值附近但避开开关沿信噪比最好。G474内部有四个ADC我把ADC1用来采副边输出电压分压值ADC2用来采原边谐振电流经过电流互感器换算后的信号ADC3留下来做输入电压采样或者其他保护检测。每个通道开启硬件过采样倍率设成4倍虽然转换时间会变长一些但在300kHz开关频率下依然来得及。输出电压分压电阻的选择也要留神24V输出经过电阻分压到ADC可测范围通常用100k加10k的衰减比ADC参考电压用内部VREF满量程3.3V所以24V对应百分比的adc值大概在1.1V多。增益精度不需要太高因为后面环路本身会有稳态误差修正但线性度要保证不要用阻值太大导致输入偏置电流影响分压精度。2.3 保护逻辑比较器、刹车输入和故障锁定数字电源最容易栽跟头的地方是保护逻辑写得不够完整。G474比较器CMP1和CMP2正好派上用场。把原边电流信号同时引到CMP1正输入端DAC1输出配置成过流阈值一旦电流超过阈值比较器输出可以直接联动到HRTIM的刹车输入硬件级别拉死PWM完全不用进中断。这种硬件刹车的好处是响应时间在几十纳秒级别。CPU中断处理即使再快也要几百纳秒甚至一微秒在过流故障时多出来的这一小段时间足以让功率管发热烧毁。软件里还要维护一个故障状态寄存器把过压、欠压、过流、过温这些故障源各占一个bit。任何故障触发后程序进入故障锁定状态PWM保持输出关闭只有故障源被清除并且输入重新上电后才能解除锁定。在实际运行中最容易被忽略的是“软启动完成之前不要解除故障锁定”这个点否则上电瞬间噪声很容易反复触发保护系统会不断重启。3. 核心控制程序实现状态机、软启动和PI环3.1 全局数据结构与运行状态机数字电源程序要想维护起来省心我建议一上来就把状态机写好先不要急着写PID环路。我的工程里定义了这样的运行状态typedef enum { LLC_STATE_POWER_ON, // 上电初始化 LLC_STATE_STARTUP, // 软启动频率扫描 LLC_STATE_NORMAL, // 正常闭环调节 LLC_STATE_BURST, // 轻载打嗝/突发模式 LLC_STATE_FAULT // 故障锁定 } LLC_State_t;每个状态的迁移条件要摆在明面上。比如从STARTUP到NORMAL需要满足输出电压首次进入目标值5%的窗口并且保持至少100个开关周期从NORMAL到BURST需要输出功率低于某个阈值并且持续一段时间避免来回抖动任何状态遇到故障信号都直接进入FAULT。这种状态定义方式好处是后面加功能时不容易把原有逻辑写乱。3.2 软启动流程的核心逻辑LLC上电最危险的是启动瞬间。如果一上电就让环路试图把输出电压稳定到目标值PI调节器很可能会输出一个极低频率的控制量让工作在容性区炸管。所以我采用频率扫描软启动策略。具体流程上电后PWM先输出1kHz左右的低频关断状态等母线电容预充完成、输入电压稳定之后再启动HRTIM并把初始开关频率设置为600kHz大约是谐振频率的两倍。这时候谐振腔阻抗很大输出电压几乎为零电流也很小。随后每500微秒降低一次频率默认步进为1.5kHz频率不断向下扫。随着接近谐振点输出电压开始逐渐爬升当检测到输出达到目标电压的90%左右时停止频率扫描把控制权交给电压PI环之后PI环根据误差自动调整频率到最终工作点。这里有一个很重要的细节软启动期间PI环积分项必须清零并且输出频率下限不要设置得太低。我遇到过几次情况都是因为软启动期间没有处理好PI限幅导致切入闭环瞬间频率突变输出过冲最后把副边电解电容都打鼓了。3.3 离散域PI调节器写法和抗积分饱和当程序进入NORMAL状态核心是一个离散化增量式PI调节器。电压环采样周期取1个开关周期也就是大约3.3微秒一次。虽然主频170MHz完全能把环跑起来但实际环路带宽不用太高我最终把控制频率设置成每4个开关周期执行一次PI更新差不多75kHz左右这个速率已经足够覆盖24V输出负载瞬态的需求。PI写入方式不少我习惯用位置式加积分分离的结构typedef struct { float Kp; float Ki; float integral; float out_max; float out_min; } PI_Controller_t; float PI_Update(PI_Controller_t *pi, float error, bool enable_integral) { float output; float u_integral pi-integral; if (enable_integral) { u_integral pi-Ki * error; if (u_integral pi-out_max) u_integral pi-out_max; if (u_integral pi-out_min) u_integral pi-out_min; pi-integral u_integral; } output pi-Kp * error u_integral ; if (output pi-out_max) output pi-out_max; if (output pi-out_min) output pi-out_min; return output; }积分分离是个容易被忽视的细节。LLC的频率和增益本身不是线性关系如果在启动初期或者动态大幅度调节时还让积分项全速累积很容易造成超调超调之后又反向累积振荡半天才稳定。我采用了一种简单的处理只在误差绝对值小于满量程10%时才让积分累积误差大时只走比例控制动态响应更快也没有明显超调。PI输出量纲也要规划好。这个输出值本身不是频率而是一个0到1的标幺化控制量最后再映射到频率范围fsw LLC_FSW_MIN control_out * (LLC_FSW_MAX - LLC_FSW_MIN);为什么这样映射而不是让PI直接输出频率数值因为标幺化之后PI参数受不同功率等级、不同谐振频率变化的影响会变小换个项目只需要重新计算频率上下限Kp和Ki大概率不用重调太多。实际测试下来这个做法可移植性不错。3.4 把计算出的频率刷进HRTIM周期寄存器算出目标频率后下一步就是把它转成HRTIM的周期值。G474的HRTIM计数时钟我配成170MHz周期寄存器值等于时钟频率除以开关频率例如300kHz对应的period值是170000000/300000约等于566。更新时要注意周期寄存器和比较寄存器在当前周期结束时会自动重载所以不需要先停PWM再写寄存器直接写入即可。但在写的时候建议按“先改CMP后改PER”的顺序并且用影子寄存器功能保证两个值在同一周期边界生效否则中间一个周期可能产生不完整脉冲。void LLC_Set_Frequency(uint32_t fsw_hz) { uint32_t tick_period HRTIM_FCLK / fsw_hz; uint32_t tick_half tick_period / 2u; LL_HRTIM_TIM_SetCompare(HRTIM1, LL_HRTIM_TIMER_A, LL_HRTIM_COMPARE_REGISTER1, tick_half); LL_HRTIM_TIM_SetPeriod(HRTIM1, LL_HRTIM_TIMER_A, tick_period); }注意半周期计算可能会产生取整误差但因为LLC本身有一个电流互感器保护而且占空比只要维持接近50%就能正常工作所以不用做复杂的小数处理。如果想更精确一点可以用CMP值去计算period的一半后再把比较值乘以2倍周期匹配但实际效果差异非常小没必要增加复杂度。4. 代码实战从初始化到主循环完整解析4.1 时钟、GPIO和HRTIM初始化初始化这块我就不贴完整CubeMX生成的代码了那玩意占篇幅又没有增量信息只挑关键部分讲。系统时钟通过PLL倍频到170MHzAPB2定时器时钟同样配置为170MHz。GPIO方面HRTIM1的TIMA引脚映射到PA8和PA9确保复用功能配置正确不要搞成普通输出模式。HRTIM的初始化里面有一个重要参数是PWM输出极性。LLC驱动电路如果用了隔离驱动芯片比如Si8261、UCC21520这类通常高电平有效。如果你的电路是低电平有效需要在初始化里把极性翻转否则一上电就是反逻辑驱动管子直接全开短路。为了让上电过程可控我先初始化HRTIM但是不使能输出等软件状态机确认系统正常后再使能。代码里对应// 先失能输出防止配置过程乱发脉冲 LL_HRTIM_DisableMasterOutput(HRTIM1, LL_HRTIM_MASTER_OUTPUT_TIMER_A); // ... 配置HRTIM、ADC、比较器等 ... // 上电检测完成之后再使能输出 LL_HRTIM_EnableMasterOutput(HRTIM1, LL_HRTIM_MASTER_OUTPUT_TIMER_A);4.2 ADC注入组配置与中断联动ADC触发在HRTIM周期事件中配置好之后完全由硬件自动完成不需要在主循环里等待转换。每次转换完成后会触发注入组转换完成中断在中断服务函数里读取电压电流值再做数字滤波和环路计算。由于注入组转换结果寄存器是共享的中断里要按顺序读取多个通道。为了减少中断延迟我通常直接把ADC中断优先级设成最高抢占优先级并且把PI运算放进这条中断里。测量过连带滤波和限幅在内整个中断处理时间大约1.8微秒在170MHz主频下完全能接受。如果你想提高稳定性可以把“ADC转换结束标志”作为启动下一步计算的信号拿一个互斥变量确保主循环和中断不会同时操作PI参数。不过以我的经验等工程复杂度还没上来的时候把所有控制逻辑全部放进中断里反而是最不容易出错的写法。等后期功能多了再拆模块也不迟。4.3 主循环里只做慢速任务控制频率在中断里做掉了主循环里就只剩慢速任务按键输入、状态显示、通信上报、参数存储、故障状态刷新等。我建议把主循环做成一个固定时间片轮询比如每1ms扫描一次按键、每10ms刷新一次状态LED、每50ms通过UART上报一次运行数据。UART调试信息这块虽然看着不起眼但实际调试速度提升非常明显。我在程序里加了一个简单的printf重定向把setpoint_freq、vout_adc、iout_adc、control_out、state这几个关键变量定时打出来。上位机用串口助手或者Python画个曲线马上就能看出环路是否振荡、是否进入失锁区。5. 调试过程中踩过的坑和排查思路5.1 上电炸管的几种常见原因提到LLC调试第一件事肯定是安全问题。我调试电源时坚持在交流输入端串联一个白炽灯泡限制短路电流。这样可以防止一炸管子就把PCB铜箔都炸飞。如果是首次上电先把频率固定在远高于谐振点的位置用示波器确认驱动波形正常再逐渐往下扫不要一上电就去跑全负载测试。炸管的原因里死区不足是最典型的一个。LLC要实现ZVS需要谐振电流在死区时间内把半桥节点电容充放电完成。如果死区时间太小开关管在开通时vds还没有下降到零硬开关能耗直接砸在管子上。两个管子的结电容不同、pcb寄生参数不同也会让两边的死区需求略有差别尽量分别用上下管独立测量波形。另一个常见原因是启动频率设得太低。如果软启动起始频率低于谐振频率太多系统可能直接工作在容性区初次上电就炸。可以在初始版本把起始频率设高一点确认波形安全之后再把频率逐步降低直到进入目标工作点。这个过程宁可慢不可快。5.2 输出纹波大、间歇性振荡的调试记录程序能跑起来后第一版调试很容易遇到输出电压纹波偏大的问题。我最初以为环路带宽还不够盲目加大Kp结果纹波反而更差还出现了一种间歇振荡——输出电压周期性地在略高和略低之间来回摆大概几十Hz的节拍。后来用示波器同步观察原边电流波形才发现问题出在采样上。ADC触发时刻没有跟HSPWM对齐导致每个周期采到的电压值毛刺很大环路相当于在给噪声做放大。解决办法是把ADC触发延迟放到PWM半周期中点并把这个延迟值做成256级可调在示波器上找到噪声最小的窗口才算把问题解决。另外数字环路延迟本身也有影响。从采样到PWM更新的总延迟如果超过一个开关周期相位裕度会明显下降。所以我后来把PI更新频率从每个开关周期一次改成了每4个周期一次人为限制环路带宽反而提升了稳定性。这个反直觉的操作在不少数字电源书上也有提到。5.3 谐振元件容差导致的工作点偏移LLC谐振频率由Lr和Cr的乘积开根号决定电容电阻一般都有5%到10%的容差。两批板子的谐振频率完全可能相差15kHz如果你把PI下限设置在计算值附近某一批板子可能出现“能启动但带载不稳”的诡异问题。我的处理办法是增加一个开机自检程序上电后在额定负载下用开环方式扫描一个窄频率区间从350kHz往低扫到200kHz记录每个频率下对应的输出电压找到一个增益最低点或者指定增益点把这个点保存为等效谐振频率。后续频率上下限以及软启动起始频率都基于这个实测值来设置。这个方法要占一点上电时间但是对批量生产的一致性非常友好。5.4 轻载打嗝模式参数调整LLC在轻载条件下关断损耗会变得比较突出效率下降得很厉害。为了节能很多电源会在轻载时进入间歇工作模式也就是burst mode让电源在“工作一小段然后休眠一小段”之间循环整体平均输出功率低但单次工作状态又保持在较高效率区间。我的代码里也做了这个功能当检测到输出电流低于额定值10%并持续20ms就进入BURST状态。进burst之后PI环不再运行频率固定在谐振点附近输出一小段脉冲群然后关闭PWM等待输出电压下降到阈值以下再重新开始。这里要格外注意burst开关频率不能被应用系统的音频范围捕捉比如5kHz以下。我实际调试时就遇到过burst频率大概在4kHz左右电感发出明显的吱吱声后来通过调整迟滞阈值差解决了。6. 实测波形与环路参数调整经验6.1 开环扫频测试与死区验证程序基本能跑以后先别急着看闭环效果。我的调参顺序是先开环扫频再闭环加负载最后才做动态响应优化。开环扫频时把PI环完全旁路掉通过UART或者调试器直接设置固定频率。然后从350kHz开始以5kHz步进往下扫用电子负载分别在不同的负载条件下记录输出电压和效率。把数据整理成表格之后你很容易看到这个电源在哪些频段增益较高、哪些频段效率好、哪些频段有异常温升。这个实测增益曲线是后续确定频率上下限和软启动起点最重要的依据。死区时间调整时直接把示波器探头夹在Q1栅极和Q2栅极看看两路PWM之间有没有足够死区。还可以用电流探头看原边电流如果死区太小电流波形上会出现明显的尖峰毛刺而且管子的温升会异常偏高。6.2 电压环Kp和Ki的整定方法PI参数整定有一套相对笨但很实用的方法。先设Ki为0只保留Kp从很小的值开始增加同时用电子负载做20%到100%的负载阶跃观察输出电压恢复情况。有一个“四分之一阻尼”经验过冲到终值的四分之一左右且有过冲回落后大约三四个周期内稳定Kp基本就到位了。之后再把Ki逐步加上去消除稳态误差。需要注意LLC的增益非线性比较强在低压轻载和高压满载两种工况下同样的Kp对应系统的开环增益差异可能很大。所以我最终做了一个简单的增益调度按负载轻重分三档切换Kp和Ki确保在几个典型工况下都有足够的相位裕度。这个调度不用太复杂三个工况点做线性插值就够了实测效果很好。6.3 采样滤波、环路稳定和数据记录技巧控制环里的数字滤波要小心低通滤波器太强会带来额外相位延迟太弱又滤不掉开关毛刺。我实测下来输出电压采样用一阶IIR滤波器系数取0.15到0.2比较合适也就是新采样值占比15%到20%。这类滤波不会对3kHz以下的环路带宽造成太大影响又能把300kHz的开关噪声压掉不少。最后再建议一个调试技巧用高分辨率定时器输出一个“调试标记脚”在PI运算开始时拉高运算结束后拉低。用示波器同时看这个标记脚和PWM波形就能精确测量控制程序引入的延迟。我很多环路稳定性问题最后都是靠这个信号追踪定位的。说到底数字LLC的难点不在于PID本身而是把频率边界、死区、采样相位、软启动时序这些周边细节处理好。先开环摸清增益曲线再闭环调环路这是我做完这个项目之后最想分享的一条经验。只要这几个大方向不出错STM32G474这套方案跑起来会非常顺。等后续有空我再把PFC级联调压、二次侧同步整流、以及氮化镓驱动的具体优化过程整理出来那个环节也是数字电源深水区。
返回列表