拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

STM32G474 HRTIM实现半桥LLC数字电源实战

STM32G474 HRTIM实现半桥LLC数字电源实战

搞LLC数字控制这件事,我一开始是有点拒绝的。做了好几年模拟电源,习惯了UC3863搭配比较器搭出来的模拟环路,总觉得数字控制是DSP的活,跟MCU没啥关系。直到手头项目要在不砍方案成本的前提下,把一版30V/8A的半桥LLC改成数控方案,才硬着头皮把STM32G474拉出来试。做完之后发现,G474这颗芯片的HRTIM外设简直就是为数字电源量身定做的,184ps的PWM分辨率、内置比较器、高精度DAC,配合CORDIC做浮点运算,跑一个完整的PFM闭环绰绰有余。这篇就把我从拿到片子到最终跑通负载的完整过程写出来,不含任何“PPT级”的内容,全是实际调过的代码和在示波器上验证过的波形,希望能帮到那些正准备往数字LLC方向踩坑的人。

1. 从选型到立项:为什么是STM32G474而不是DSP或专用数字IC

1.1 数字电源主控的三条路线

做数字LLC方案,摆在桌面上的主控选择无非三类:第一是专用的数字电源控制器,像TI的UCD3138这种,它在芯片内部就已经固化了部分LLC控制的硬件逻辑,环路做得很死,适合产品形态非常固定、量很大的场景,但是想加自定义协议或者做复杂状态机很痛苦;第二是高性能DSP,典型如TMS320F28004x或者DSPIC33,这类芯片的PWM模块和ADC对齐机制确实成熟,生态也最完善,但供应链排期和成本在这两年不太友好;第三就是高性能MCU,也就是本文要说的STM32G474,它的HRTIM在STM32家族里是独一档的存在,而且整个开发调试流程对我们这些习惯了Keil和ST-Link的嵌入式工程师来说几乎零门槛。

从我个人的角度看,数字电源设计最大的成本其实不在于主控本身,而在于开发调试的时间和人才储备。团队里如果本来就有一批嵌入式工程师,让他们去学C2000的库函数和CCS开发环境,磨合期至少一个月起步。而STM32G474只需要半天就能把基本的外设跑起来,加上有HAL库和LL库兜底,项目的整体风险低很多。至于网上经常争论的“MCU做不了高频环路”问题,我在实际测试里验证过:在170MHz主频下,一个完整的LLC控制周期(ADC采样加PI运算加HRTIM更新)能做到2微秒以内,对典型的谐振频率100kHz到500kHz的应用完全够用。

1.2 HRTIM:G4系列里最值钱的资源

很多人一听说要用单片机做电源,第一反应就是看内核主频,觉得算力不够。但实际上数字LLC控制最关键的外设是高频PWM,也就是STM32G474的HRTIM。这颗外设的计数器基准频率可以做到系统时钟的64倍频,所以即使在170MHz的AHB时钟下,HRTIM依然能提供约184ps的PWM分辨率。这个指标什么意思?就是你用PFM调频的时候,频率的每一步变化量可以做得极其细腻,环路不会因为频率阶梯过大而产生输出电压的可见抖动。

HRTIM还有一个非常关键的特性:它有多个独立的定时器单元,可以互相触发、互相同步、级联。做半桥LLC需要两路互补PWM加死区,做全桥LLC需要四路PWM加死区,同时还要考虑同步整流的驱动信号——所有这些在HRTIM内部可以通过单个外设完成,不需要额外的CPLD或者逻辑门电路。我当时选了半桥LLC做第一个版本,用到的就是HRTIM1的两个定时器单元TA和TB,配合死区生成器,硬件的复杂度直接降了一个层级。

1.3 模拟前端与故障保护:G474的隐藏王牌

除了PWM,数字电源对模拟前端的要求也很高。输出电流和输出电压的采样精度、过流保护的响应速度,这两项做不好,环路写得再漂亮也是白搭。STM32G474内部的比较器组可以绕过CPU和ADC,直接将采样电阻上的电压和内部DAC设定的阈值比较,一旦超过阈值立刻触发HRTIM的故障输入端口,硬件层面把PWM封锁。这条路径延迟是纳秒级的,不依赖任何软件,对于电源保护来说是真正的安全兜底。

我实际测试了一次故意短路的情况,从过流发生到HRTIM输出封锁,整条链路的延迟大概在150ns以内,比用ADC采样再去软件处理快了至少两个数量级。有些设计人员会觉得内部比较器不如外部比较器可靠,但只要注意布线的阻抗匹配和内部DAC的噪声指标,这个做法在量产中是完全可行的。G474内部的12位DAC精度足够做精细的过流阈值设定,不占PCB面积,还能通过SPI/I2C在运行中动态调整阈值,比跳线电阻灵活太多了。

2. LLC拓扑核心点先懂增益曲线再写控制代码

2.1 三个工作区间与PFM调频逻辑

LLC的电压增益曲线是整个控制算法的基础。抄别人的代码只能跑通个Demo,要调出效率和安全裕度,必须回到增益曲线上去理解PFM为什么是对的。LLC变换器有两个谐振频率:一个是LC串联谐振频率fr,典型的100kHz到300kHz;另一个是Lm与Cr并联谐振形成的第二谐振频率fm,通常比fr低很多。由于增益曲线在谐振点两侧不对称,控制器要始终清楚自己究竟工作在哪个区间。

当开关频率等于谐振频率fr时,增益等于1,此时变压器原边电压和输出电压的一比一关系最直接,也是效率最高的工作点。当开关频率低于fr时,变换器工作在增益大于1的区域,适合输入电压偏低或者需要升压输出的场景,但此时原边电流的波形会出现趋近ZCS的特性,如果频率继续往下探,就可能进入容性区,这时候MOSFET的ZVS条件会彻底丢失,轻则发热加重,重则炸管。当开关频率高于fr时,增益小于1,适合输入电压偏高的情况,但原边开关管的关断电流会增大,效率随之下降。

我在代码里实现PFM的方式很简单:用一个PI控制器对输出电压误差做闭环,PI输出的0到100%映射到设定的频率范围[fmin, fmax]。关键点是这个映射不是线性的,因为增益曲线在中高频段近似反比关系,线性映射在小误差时容易导致频率调整过冲。我最终用了对数映射,实测下来环路的相位裕度更好,动态响应也平顺得多,这个是用Mathcad跑了闭环模型才确定的。

2.2 死区时间与ZVS:数字量化的难点

数字电源跟模拟电源一个显著的区别是,模拟方案里死区时间由RC延时或者驱动芯片外围电阻决定,调起来要用电烙铁;数字方案里死区时间写在寄存器里,改个数值就能看波形。这看起来是优势,但反而需要更仔细地理解死区和ZVS之间的关系。死区时间设置得太短,半桥的高边MOSFET还没有完全放电,反并联二极管还没来得及续流,就会出现硬开关,效率下跌;死区时间太长,环路死区内的等效占空比变窄,谐振电流的正负半周不对称,变压器的直流偏磁风险随之上升。

我最初按照经验把死区设成350ns,示波器上看开关节点波形有很明显的振铃,后来把死区缩短到180ns,同时微调了HRTIM的死区时钟分频,振铃明显减小,满载效率提了大概1.2个百分点。这里有一个很容易踩的坑:HRTIM的死区寄存器数值不是直接的纳秒值,它的实际精度取决于死区生成时钟的选择,并且最后要考虑向上取整带来的量化误差。死区时间在小数纳秒级别的偏差,不会导致灾难性故障,但会影响效率曲线的平滑度。

2.3 软启动为什么必须从高频开始

LLC的启动过程是所有调试问题中最容易暴露风险的环节。如果不做软启动,直接以目标频率开启PWM,输出电压会瞬间建立,反馈环路还没来得及介入,就已经出现了巨大的浪涌电流,直接冲击谐振电容和输出电容。仿真结果和实际波形都表明,LLC必须从远离谐振点的最高频率启动,因为在高频下增益很低,输出电压建立缓慢,环路才有足够时间慢慢把工作点拉回到稳态。

我的软启动实现方案是一个简单的状态机:STARTUP开始时将频率设定为1.5倍fr(属于高于谐振点的轻载区间),然后每隔一段固定时间降低一次频率,每次下降的步长与输出电压上升的速率绑定。这样做的好处是,整个启动过程的电压爬升非常稳定,不会出现阶段性的过冲。实测满载启动时Vout的超调量控制在了150mV以内,启动时间大约12ms,在可接受范围内。后面如果需要更快的启动,可以改成闭环软启动,但在第一版跑通的阶段,开环阶梯式软启动是最可靠的。

3. 代码工程全景:初始化、环路、驱动的分层实现

3.1 工程文件结构与模块划分

写数字电源控制程序,最忌讳把所有功能堆在main.c的while循环里。电源控制有一个特点:中断里跑的是硬实时任务,主循环里跑的是软实时任务,两者必须清晰分离,否则一个串口打印函数就能让环路时序完全乱套。我在这个项目里的工程结构基本分成了驱动层、控制层和应用层。

驱动层包含HRTIM的初始化和频率设置函数、ADC的采样触发配置、比较器和DAC的保护阈值配置。控制层包含PID控制器、软启动状态机、同步整流的PWM生成逻辑。应用层则是通信协议(通过串口或CAN读参数)、故障记录、参数存储等功能。这个分法看起来平淡无奇,但做电源项目时我强烈建议把控制环路里所有浮点运算的函数走一遍code review,确保没有任何阻塞等待、没有任何sprintf、没有任何printf——这些操作在环路里出现一个,整个实时性就毁了。

3.2 HRTIM关键寄存器配置详解

HRTIM的配置是整个程序的基石,我直接贴出核心配置代码,这个配置已经在一台300W的半桥LLC上验证过,采用的是HRTIM1的TimerA和TimerB组成半桥输出,TA与TB互为上下管,死区由内部死区生成器完成。

void HRTIM1_Config(uint32_t period_ticks, uint16_t deadtime_ticks) { // 启用HRTIM1时钟 __HAL_RCC_HRTIM1_CLK_ENABLE(); // 防止配置期间输出毛刺:先进入初始化模式 HRTIM1->sCommonRegs.ICR |= HRTIM_ICR_INIT; HRTIM1->sCommonRegs.GCR = HRTIM_GCR_TAU | HRTIM_GCR_TBU; // 复位Timer A和B的配置 HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_A].TIMxCR = 0; HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_B].TIMxCR = 0; // TimerA配置:连续上数模式,周期由PERxR决定 HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_A].TIMxCR = HRTIM_TIMCR_TREPU | // Timer Registers Update Enable HRTIM_TIMCR_UDM_CMP3 | // Update由CMP3事件触发 HRTIM_TIMCR_CK_PSC_1; // 1分频 // TimerB配置:与TimerA同步且互补 HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_B].TIMxCR = HRTIM_TIMCR_TREPU | HRTIM_TIMCR_UDM_CMP3 | HRTIM_TIMCR_CK_PSC_1; // 设置共用的比较值:半周期比较(用于更新事件和死区测量) HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_A].CMP3xR = period_ticks / 2; HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_B].CMP3xR = period_ticks / 2; // 设置周期寄存器:频率对应的周期值 HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_A].PERxR = period_ticks; HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_B].PERxR = period_ticks; // 死区时间配置: HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_A].DTxR = (deadtime_ticks & HRTIM_DTR_DTG_Msk) | ((deadtime_ticks << 16) & HRTIM_DTR_DTL_Msk); HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_B].DTxR = (deadtime_ticks & HRTIM_DTR_DTG_Msk) | ((deadtime_ticks << 16) & HRTIM_DTR_DTL_Msk); // TimerA比较1输出:高边PWM,TA1引脚 HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_A].SETxR = HRTIM_SET_STA1; HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_A].RSTxR = HRTIM_RST_CMP2; // TimerB比较1输出:低边PWM,TB1引脚 HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_B].SETxR = HRTIM_SET_STB1; HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_B].RSTxR = HRTIM_RST_CMP2; // 激活输出 HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_A].TIMxOER = HRTIM_OER_TA1OE | HRTIM_OER_DTEN; // 使能TA1输出并插入死区 HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_B].TIMxOER = HRTIM_OER_TB1OE | HRTIM_OER_DTEN; // 退出初始化模式 HRTIM1->sCommonRegs.ICR &= ~HRTIM_ICR_INIT; }

这段配置里有几个需要特别注意的点。第一,Period寄存器并不是直接写频率对应的值,而是半个周期对应的计数值。对半桥LLC来说,一个完整的开关周期包含高边导通和低边导通两个阶段,所以周期寄存器写入的其实是1/2开关周期的计数值。如果这里写错,实际开关频率会偏移一倍,用示波器校核时会让你怀疑人生。

第二,死区寄存器的高16位和低16位分别控制上升沿死区和下降沿死区,很多库函数封装不好容易把这两个值写反。我建议直接用寄存器操作,避开HAL库的抽象层,这样调试起来反而直观。第三,更新事件的选择很重要,我配置成CMP3触发更新,这样可以保证周期寄存器的修改发生在半个周期完成后的安全点,避免PWM波形在切换频率时产生不该有的毛刺。

3.3 频率切换时的无毛刺处理

数字LLC控制里另一个细节是频率切换时的PWM毛刺。因为LLC的PWM周期是连续变化的,如果周期寄存器更新的时机不对,当前一个周期还没走完就被强行改成新周期,输出波形会突然出现一个极窄的脉冲,这个窄脉冲对MOSFET驱动是致命的,可能导致上下桥臂直通。

解决这个问题的核心思路是:更新周期寄存器的动作必须由硬件触发,而不是由软件随意写入。在HRTIM中,我们把更新事件绑定到CMP3事件上,软件只需要修改周期寄存器的影子值,等下一次CMP3事件到来时,硬件会把影子值一次性装载进去,这个过程完全由硬件完成。我在代码里提供了一个更新频率的接口,函数的执行时间不到十个周期,可以把它放在控制中断里安全调用。

void HRTIM_SetLLCFreq(uint32_t period_ticks) { // 只修改影子寄存器,等待CMP3事件硬件装载 HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_A].PERxR = period_ticks; HRTIM1->sTimerxRegs[HRTIM_TIMER_INDEX_B].PERxR = period_ticks; // 手动触发一次更新事件,立即装载新周期 HRTIM1->sCommonRegs.CR2R |= HRTIM_CR2R_SWU; }

要注意的是,影子寄存器的更新触发方式,必须和初始化时的更新模式保持一致,否则会出现第一次更新成功、第二次失败的现象。我的建议是在初始化完成后,先用示波器确认连续修改5次频率的波形,确认每次切换都没有毛刺,再开始写控制环路,否则后面出现问题会分不清是环路计算错还是PWM输出错。

4. 关键功能模块代码解析:软启动、同步整流与保护

4.1 软启动状态机的实现细节

软启动看上去只是让频率从高到低慢慢降,但实现上必须有一个可运行的状态机。我在工程里定义了一个简单的枚举类型,包含IDLE、STARTUP、RUN、FAULT四个状态。整个状态机的运行频率是10kHz,也就是说每隔100微秒执行一次。

typedef enum { STATE_IDLE = 0, STATE_STARTUP, STATE_RUN, STATE_FAULT } SysState_t; SysState_t llc_state = STATE_IDLE; uint16_t startup_cnt = 0; float target_freq = 0.0f; void LLC_FSM_Update(void) { switch (llc_state) { case STATE_IDLE: if (global_enable == 1) { target_freq = 1.5f * LLC_FR; // 从1.5倍谐振频率启动 startup_cnt = 0; HRTIM_SetLLCFreq(FreqToPeriodTick(target_freq)); HRTIM1->sCommonRegs.CR1R |= HRTIM_CR1R_ADC1C; // 启动ADC扫描 llc_state = STATE_STARTUP; } break; case STATE_STARTUP: // 每200步将频率下调一小步 if (startup_cnt >= 200) { startup_cnt = 0; target_freq -= startup_freq_step; // 例如500Hz每步 if (target_freq <= LLC_FR) { target_freq = LLC_FR; llc_state = STATE_RUN; // 进入闭环 pid_reset(&llc_pid); } HRTIM_SetLLCFreq(FreqToPeriodTick(target_freq)); } else { startup_cnt++; } // 若启动过程中输出电压超过安全值,直接切保护 if (vout_fbk > vout_ref * 1.3f) { llc_state = STATE_FAULT; HRTIM_DisableOutputs(); } break; case STATE_RUN: // 执行闭环控制 LLC_PID_Update(); break; case STATE_FAULT: HRTIM_DisableOutputs(); // 记录故障代码、通知上位机 break; } }

这里面有一个细节值得展开。软启动的频率下降步长不能是常数,因为LLC的增益曲线在接近谐振点时斜率变化很大。如果全程都是一个步长,在接近谐振点时,同样频率变化量对应的输出电压变化会突然变大,导致电压阶梯式爬升。更稳妥的做法是让步长随当前频率与谐振点的距离自适应:离谐振点远时大步长,接近谐振点时换小步长。我在实现中增加了一个查表或分段函数的处理,启动电压波形平滑了非常多。

4.2 同步整流驱动的时序配合

半桥LLC可以不用同步整流也能跑,但效率会低不少。如果目标是300W以上的输出功率,同步整流几乎是必须的。同步整流的核心是让副边的两个MOSFET在对应的原边导通时段内跟着导通,错开死区时间,避免体二极管长时间导通造成的高压降损耗。

在STM32G474上实现同步整流,有两种思路。第一种是利用HRTIM的另一个定时器通道,与原边驱动做固定相位关系的联动,这种方式实现简单,但无法根据负载轻重动态调整提前关断的角度,轻载下同步管可能出现回流。第二种是通过ADC采样同步管的Vds电压,用软件判断体二极管导通状态,然后动态调整同步管的开通和关断时刻,这种方式更接近模拟同步整流的自适应控制,但软件开销更大。

我第一版用的是第一种思路,直接复用HRTIM的TimerC和TimerD生成副边驱动,与原边的TimerA和TimerB保持严格的180度相位关系,并且把同步管的死区设置得比原边略大一点。实测下来,重载效率提升明显,轻载时因为同步管误开通导致了一些回流噪声,后来通过加最小关断时间约束解决了。如果你打算做同步整流,我建议先把不带同步的版本调试稳定,再逐步加入同步逻辑,否则出了问题很难定位是主电路的问题还是同步管时序的问题。

4.3 过流、过压保护与HRTIM故障链路的实现

保护逻辑是电源产品的最后一道防线,必须在硬件层面兜底。G474内部比较器配合DAC的做法,可以实现在10个纳秒级别的过流封锁,下面给出配对的关键配置:

void COMP_OCP_Config(void) { // DAC1输出作为比较器COMP1的正输入阈值 DAC1->CR &= ~DAC_CR_EN1; DAC1->DHR12R1 = OCP_THRESHOLD_RAW; // 根据采样电阻和放大倍数计算 DAC1->CR |= DAC_CR_EN1; // COMP1: 正输入为PINC(内部信号),负输入为内部DAC1输出 COMP1->CSR = COMP_CSR_COMP1EN | COMP_CSR_INMSEL_0 | // 负输入选择:DAC1 COMP_CSR_INPCSEL_0 | // 正输入选择:COMP1_INP(内部模拟信号) COMP_CSR_POLARITY_0 | // 不反向:V+ > V- 时输出高 COMP_CSR_BLANKING_0; // 不消隐,保持最小延迟 // 比较器输出直接连接到HRTIM故障输入2 COMP1->CSR |= COMP_CSR_HDLYEN | (2 << COMP_CSR_HDLYCFG_Pos); }

保护链路的响应时间主要取决于三部分:比较器的传播延迟、故障信号连接到HRTIM的布线延迟、以及HRTIM内部封锁输出的硬件延迟。我实测从比较器翻转沿到PWM引脚波形变化,大约是110到140纳秒。这个速度对于一台300W的LLC电源来说完全够用,但前提是故障输入必须配置为逐周期锁定或者立即锁定模式,不能配成周期检测模式,否则最坏情况要等到下一个周期开始才封锁,反应就慢了。

另外,不要只依赖硬件保护,软件层面的过流判断也要同步做,它的作用是记录故障事件、统计过流次数、决定是否重新启动。我在应用层做了一个简单的看门狗机制:如果1秒内连续触发3次过流保护,就进入锁死故障状态,直到断电重启。这么做是为了防止在反复短路的情况下,电源不断尝试重启导致热积累,最终烧掉MOSFET。

5. 实测调试中的五个坑与解决记录

5.1 开机瞬间主管炸机,根因出在CMP3更新时机

第一版软启动代码写完后,上电测试的第一轮结果很惨烈:高边MOSFET在开机大约3毫秒时炸掉了。一开始怀疑是软启动步长太大,但降低步长后依然复现。用示波器抓高边驱动波形,发现频率切换的那一个周期里产生了大约40纳秒宽度的“毛刺脉冲”,这个宽度刚好落在半桥上下管直通的危险区内。

排查后发现,问题出在周期寄存器更新时机上。HRTIM配置的是CMP3事件触发更新,但我在软启动中手动调用了SWU软件更新,两者产生了竞争:CMP3更新事件和SWU更新事件同时发生,导致周期镜像寄存器的装载和时间基准计数器过零不同步,PWM输出出现了异常窄脉冲。解决方法是删除软启动代码里多余的SWU调用,彻底依赖CMP3事件同步更新。把这条路径打通之后,开关节点波形就非常干净了,再也没出现过窄脉冲。

5.2 轻载纹波偏大:PFM最低频率限制的作用

负载从满载切到10%轻载时,环路为了维持输出电压,会把频率推向最高点。但如果最高频率的限制设置得不合理,轻载时效率会很低,而且输出电压纹波明显变大。我最初把频率上限设定为300kHz,做轻载测试时纹波达到了80mVpp,明显超出指标。

经过排查,原因是频率过高后,变压器原边电流的每个脉冲携带的能量过小,输出电容需要独自支撑更多的负载电流,而电容ESR带来的纹波就被放大了。把频率上限调整到180kHz,同时允许PFM在极轻载时进入打嗝模式(burst mode),纹波降到25mVpp。打嗝模式在这个方案里实现起来很简单:当输出功率低于设定阈值时,周期性暂停PWM输出几百微秒,等输出电压跌回滞环下限再重新开始工作。这个逻辑虽然只在轻载时被触发,但对整机的平均效率贡献非常明显。

5.3 同步整流在负载跳变时误触发回流

同步整流调试过程最让人头疼的是负载突变时产生的误触发。空载或者极轻载条件下,谐振电流的相位关系会发生变化,如果同步管仍然按照固定死区关系提前开通,电流就会从输出端反灌回变压器原边,导致输出电容放电、效率下降,严重时副边MOSFET的体二极管会承受反向恢复应力。

我在副边同步整流管上并联了电阻分压网络,把Vds信号引入ADC采样,用软件判断同步管两端电压的极性,当检测到体二极管即将导通时,才允许同步管开通。这套自适应判断逻辑让轻载到重载跳变时的波形干净了很多。但如果你的项目时间紧、不想做太复杂的自适应算法,一个稳妥的折中方案是:让同步管在轻载下直接关闭,只用体二极管续流,虽然轻载效率会低1到2个百分点,但整个系统的鲁棒性会好很多。轻载毕竟不是持续满载运行的场景,换来的是可靠性。

5.4 电压环在输入电压突变时响应迟钝

测试恩泰克(ENS210A)输入电压从300V阶跃到220V时,输出电压出现了大约1.5V的最低点跌落后才慢慢恢复,动态响应不够理想。这个问题的根因是环路的带宽做低了。数字环路和模拟环路不一样,模拟环路可以做得非常宽,但数字环路受限于采样率和计算延迟,带宽太高会导致相位裕度急剧恶化。

我的方案是把采样点固定在PWM周期的中点,也就是谐振电流为零附近采样,这样可以最大程度抑制开关噪声对ADC读数的干扰,然后在这个基础上尽量提高环路增益。把环路带宽从2kHz提到5kHz后,电压跌落从1.5V缩小到了800mV。如果还要进一步优化,就得考虑引入负载电流前馈,直接从负载变化信息补偿频率,而不是等电压跌了才反应,这已经是数字电源比较进阶的做法了。

5.5 效率曲线的“驼峰”现象与死区微调

做完动态测试后,我用电子负载从10%扫到110%看效率曲线,结果在60%负载点附近出现了一个不正常的效率凹陷,形成了类似“驼峰”的形状。一开始怀疑是谐振电容和变压器的参数差异,后来用红外热成像仪观察,发现60%负载附近变压器温度异常偏高,说明此时原边电流波形有畸变。

用电流探头抓原边谐振电流波形,发现过零点附近有一段死区平台,平台的长度正好对应我设置的原边死区时间。死区期间的电流会经由体二极管续流,而体二极管的正向压降损耗在这个负载区间恰好占比最大。我把死区时间从250ns逐步压缩到170ns,同时观察逆变器桥臂中点电压的dV/dt,确认没有产生寄生效应的振铃后,效率凹陷消失了。最终满载效率达到了93.6%,在谐振点附近效率最高能到95%。

6. 把数字LLC做成稳定产品的一点点经验总结

整个项目从原理图设计到最终跑通负载,前后花了一个多月的时间。如果让我回顾一遍,最大的体会是:数字电源和模拟电源在调试方法论上是完全不同的思路。模拟电源出问题,更多要靠示波器抓波形,通过调节RC参数来碰;数字电源出问题,大多数人第一反应也是去示波器抓波形,但其实更高效的办法是先把芯片内部寄存器的状态和关键变量通过调试接口读出来,先确认软件层面做的事是否符合预期,再去看硬件波形。

另外一个就是数据记录的习惯。我在代码里加了一个环形缓冲区,把输出电压、电流、当前频率、PID输出值这些关键变量实时记录下来,故障发生时立刻冻结。这套“黑匣子”设计帮我排查了至少三个靠波形看不出原因的偶发问题。比如前面提到的同步整流回流,就是因为黑匣子里记录到轻载时副边电流反向标志位才定位到的,单纯看波形很难在偶发现象下抓到现场。

建议准备碰数字LLC的朋友,先把TI和ST的参考设计文档、应用笔记各读三遍,再动手写代码。网上那些直接给你完整工程代码的帖子,大部分只能跑通演示板,到了你自己的硬件上大概率会遇到意想不到的坑。理解原理、吃透外设机制,比抄代码重要得多。

返回列表