6路交错PWM实现:LAT1593中心对齐模式详解 6路交错PWM这个需求我是在做一台48V转12V/100A输出的大功率DC-DC时遇到的。输入电流纹波和输出电容的发热都压在面前单路PWM怎么调都过不了温升最后只能上交错方案。而在LAT1593上把Up Down计数模式玩明白之后6路相位差60度发波这件事其实比想象中要顺。这篇文章就把我整个从原理到代码再到实测的过程完整写出来给同样要做多相交错电源或者电机控制的同行一个参考。1. 为什么非要6路交错单路PWM哪里不够用1.1 单路PWM的纹波困境单路Buck变换器在重载时的纹波问题是客观存在的。假设12V输出、100A负载开关频率60kHz电感选1μH那么单相的电流纹波峰峰值大约在十几安培这个脉动电流几乎全部要由输出电容来吸收。电容ESR只要有几毫欧温升就会很难看而且纹波电压也压不下来。实际项目里我最初用单路方案输出用了6个470μF的固态电容并联ESR已经压到很低了但满载时输入侧电流纹波依然大得离谱。输入线缆上的共模干扰还会影响到旁边一块控制板最后查来查去根源就是单路结构本身——所有能量都在一个开关周期内脉冲式地从输入端抽取输入纹波电流等于输出电感纹波电流的一半加上负载电流的开关分量。1.2 交错后的等效效果纹波互消交错PWM的思路很简单把原来一路的大脉动拆成6路小脉动每路承担约1/6的输出功率而且在时间上让6路错开。6路均分一个360°载波周期相邻两路之间相差60°也就是1/6个开关周期。这样做最大的收益在总电流上。6个电感的纹波电流不再是同相叠加而是等相位差地相互错开。以60kHz、周期16.67μs为例6路交错后输出端总电流的纹波频率是360kHz也就是开关频率的6倍而纹波峰峰值在占空比不是极端值时通常会降到单路的1/5到1/10。有一个理想的极限情况特别能说明问题当占空比D恰好等于1/6时各路电感的脉动电流在时间轴上首尾相接理论上总纹波电流为零。虽然实际中由于电感量差异和死区影响不可能完全为零但那个数量级的差距已经足够让人满意了。1.3 为什么载波要选Up Down计数模式做交错PWM时载波模式的选择直接影响纹波抵消效果。用递增计数模式也能做多路PWM把各路的比较值依次延迟但这里有个问题边沿对齐的PWM脉冲是靠一边对齐的各路脉冲的上升沿错开了可是脉冲的中心位置并不均匀分布。在多相交错电源里各路电流叠加后的总纹波会因为脉冲中心不齐而产生额外的低频分量纹波抵消效果明显变差。而Up Down计数模式也就是中心对齐模式计数器从0走到ARR再走回0比较匹配产生的PWM脉冲天然关于计数周期中心对称。六路载波相位均匀错开后各路脉冲的中心点也跟着均匀错开电流叠加后纹波抵消的效果最干净。中心对齐还有一个隐藏优势对做数字电源的人尤其有用可以在计数器下溢或上溢的时刻触发ADC采样此时流过电感的电流正好等于平均电流采样值不需要再做相位补偿。这在后面第6节我会详细展开。2. Up Down计数模式下PWM到底怎么生成2.1 计数器运行轨迹LAT1593的高级定时器在Up Down计数模式下计数器从0开始向上计数到达自动重装载值ARR后翻转方向再向下计数到0然后再翻转为向上如此往复。一个完整的PWM周期算下来计数器要走过 2 × ARR 个计数步。这里有两个关键时间点上溢事件计数器到达ARR的那一刻。下溢事件计数器到达0的那一刻。在中心对齐模式下通常上下溢事件都可以作为更新事件Update Event的来源具体用哪一种可以在寄存器里配置。更新事件非常重要它是PWM周期切换的标志也是占空比影子寄存器同步装载的时刻后面调试时遇到首脉冲异常的问题就和它有关。2.2 比较匹配与输出翻转逻辑PWM输出电平的翻转发生在计数器值与比较寄存器CCR匹配的时刻。以PWM模式1为例向上计数过程中计数器值等于CCR时输出变为有效电平默认高电平。继续向上到达ARR后翻转方向向下计数过程中再次等于CCR时输出变为无效电平默认低电平。一直保持低电平到计数器回到0。所以一个完整周期内输出先是低电平到达CCR后变高随后又在下行段CCR处变低。高电平的中心正好落在计数器周期的中点也就是ARR时刻这就是中心对齐PWM的由来。高电平持续时间由CCR与ARR的关系决定占空比D CCR / ARR在CCR不超过ARR的前提下0到100%均可实现CCR大于ARR时恒为有效等于0时恒为无效。2.3 实际频率计算中心对齐模式下PWM频率的计算公式是f_PWM f_TCLK / ((PSC 1) × 2 × ARR)这个2倍是必须注意的因为在Up Down计数模式下计数器一个完整往返才是1个PWM周期很多新手第一次配置时按递增模式算频率结果实际输出频率少了一半。我项目里的实际参数LAT1593的定时器时钟为96MHz预分频PSC取0目标开关频率60kHz代入公式60kHz 96MHz / (2 × ARR)解得ARR 800。这里有个经验之谈做6路交错时ARR的取值最好能被6整除或者接近整除这样各路相位差对应的计数步长才容易取整。800虽然不能被6整除但相位差是ARR × 2 / 6 266.67个计数步取266或者267引入的误差在60kHz下只有约11纳秒完全可接受。3. 6路60°相位差初值错相与同步启动3.1 思路多时基加相位初值做6路交错PWM很多人第一反应是一个定时器有6个比较通道用6个CCR不就行了吗实际工程中我不推荐这么做。用单一比较通道偏置去模拟不同载波相位在中心对齐模式下很难做均匀因为每个通道只有一个CCR你很难同时表达“相位偏移后比较值超出ARR范围”的情况。就算用取模映射把比较值折回有效区间PWM输出逻辑的正确性验证起来也相当麻烦。我推荐的方案是用LAT1593的多个独立时基Timer单元每个时基负责一路PWM。所有时基配置完全相同的PSC和ARR也就是完全相同的载波频率和载波形状但每个时基的计数器初始值不同这样一路路在启动瞬间就处于载波周期的不同相位上之后再以相同的速率增减计数相位差自然保持不变。3.2 相位初值表ARR800时的6路配置在Up Down计数模式下任意相位对应的瞬时计数值并不是线性递增的而是一个三角波。以ARR800为例一个完整周期相位从0°到360°计数器走过的路径是0°时计数器值为0方向向上。90°时计数器值为400方向向上。180°时计数器值为800方向翻转。270°时计数器值为400方向向下。360°时计数器值为0方向再翻转。如果6路相位分别是0°、60°、120°、180°、240°、300°那么各路的初始配置如下表通道相位偏移计数器初值初始计数方向CH00°0向上CH160°266向上CH2120°533向上CH3180°800向下CH4240°533向下CH5300°266向下这个方法对初值计算有两点要求。第一60°对应的计数步长不是ARR/6而是2×ARR/6 ARR/3因为一个完整360°周期对应的是计数器的往返全程。第二对180°以后相位瞬时值要按三角波的下降段映射不能简单按ARR减去偏移量来算。我在第一次做时就直接套了个线性值结果示波器上看CH3、CH4、CH5全挤在一起相位关系完全不对。3.3 同步启动让6个计数器同时起步初值写入正确只是第一步更关键的是让6个时基在同一个时钟沿同时开始计数。如果不用同步机制而是初始化完一个启动一个第一个时基已经开始跑了第六个才刚启动相位差就完全不是预设的值了。LAT1593的定时器同步方案有两种可选。第一种是硬件主从模式把TIM0设为主机产生触发输出其余5个时基设为从机接收到触发信号后启动。第二种是用软件触发初始化完所有寄存器后通过一条写操作同时启动多个时基。我习惯用硬件主从模式因为它有一个额外的好处——以后的启停都可以通过一个主定时器来控制如果遇到故障需要同时封锁所有PWM输出主从同步的逻辑会清晰很多。有个初始化顺序的细节要提醒必须先写入各时基的计数器初值然后使能对应的自动重装载和预装载功能最后才发送同步触发。如果顺序反了先启动后写初值计数器可能已经错过了初值装载窗口首周期波形就会多出一个不正常的窄脉冲。4. 初始化流程与代码骨架4.1 核心初始化步骤下面这段代码是通用化的C伪代码结构具体寄存器名称以LAT1593的参考手册为准但流程顺序是经过验证的。// 第1步使能6个时基的时钟 CLK_EnableTimerClock(TIM0); CLK_EnableTimerClock(TIM1); CLK_EnableTimerClock(TIM2); CLK_EnableTimerClock(TIM3); CLK_EnableTimerClock(TIM4); CLK_EnableTimerClock(TIM5); // 第2步配置时基全部使用Up Down计数模式 TIM_TimeBaseInitTypeDef tb; tb.Period 800; // ARR tb.Prescaler 0; // PSC tb.CountingMode TIM_COUNTING_MODE_UPDOWN; tb.RepetitionCounter 0; for (int i 0; i 6; i) { TIM_TimeBaseInit(timBase[i], tb); } // 第3步配置输出比较统一PWM模式1 TIM_OCInitTypeDef oc; oc.OCMode TIM_OCMODE_PWM1; oc.Pulse 400; // 50%占空比 oc.OCPolarity TIM_OCPOLARITY_HIGH; for (int i 0; i 6; i) { TIM_OCInit(timBase[i], TIM_CHANNEL_1, oc); } // 第4步写相位初值和方向关键 uint16_t phaseInit[6] {0, 266, 533, 800, 533, 266}; bool phaseDirUp[6] {true, true, true, false, false, false}; for (int i 0; i 6; i) { TIM_SetCounter(timBase[i], phaseInit[i]); TIM_SetCountingDirection(timBase[i], phaseDirUp[i] ? TIM_DIRECTION_UP : TIM_DIRECTION_DOWN); } // 第5步配置主从同步 TIM_SelectMasterSlaveMode(TIM0, TIM_MASTER_TRIGGER_ENABLE); for (int i 1; i 6; i) { TIM_SelectInputTrigger(timBase[i], TIM_TRIGGER_SOURCE_TIM0); TIM_SelectSlaveMode(timBase[i], TIM_SLAVE_MODE_TRIGGER); } // 第6步发送同步触发6路同时启动 TIM_GenerateTrigger(TIM0, TIM_TRIGGER_SOFTWARE);这段代码里第4步的相位初值表是整个方案的灵魂必须放在启动触发之前执行完。我调试验证过初值表和方向表只要错一位最终的相位关系就会乱套。4.2 占空比更新预装载机制必须打开运行中更新占空比时要使用比较寄存器的预装载能力。比较寄存器CCR有两种工作方式立即生效和预装载生效。预装载模式下CCR的值先写入预装载寄存器等到下一个更新事件上溢或下溢时才统一装载到实际比较寄存器。这个机制在6路交错场景里极其重要。如果没有预装载你在中断里依次更新6个通道的CCR从第0路写到第5路有几十个时钟周期的时间差这几路在同一个开关周期内的实际占空比就不一致相电流就会瞬间不平衡。而开了预装载6路会在同一个更新事件瞬间完成切换相位关系和占空比关系始终保持一致。4.3 三相全桥场景3个时基加互补输出也能得到6路交错如果你的6路PWM不是6相Buck而是三相电机控制里的三相全桥驱动那方案还可以简化使用3个时基每个时基输出2路互补PWMPWMH和PWML定时器内部自动插入死区。三个时基的初值分别设为0、266、533方向都向上。这时候看6个功率管的开关时刻三个上管的相位分别是0°、120°、240°三个下管因为与对应上管互补相位分别是180°、300°、60°。汇总到所有6个开关器件上刚好也是均匀交错60°。所以“6路交错”的语义在三相桥场景下依然成立而且实现代码更少、死区更安全。5. 实测相位精度与调试中踩过的坑5.1 示波器验证相位的方法代码下载之后第一件事是用示波器验证相位关系。我用手上的四通道示波器加逻辑分析仪组合一次看4路、分两批看完6路。测量方法很简单触发源选CH0的上升沿分别量CH1到CH5相对CH0上升沿的延迟时间。在60kHz、占空比50%的条件下各路上升沿的理论延迟为CH00μsCH12.78μsCH25.56μsCH38.33μsCH411.11μsCH513.89μs实际测出来的延迟和理论值相差在40纳秒以内对于60kHz的载波来说相位误差不到0.3°这个精度来自计数器时基本身足够满足电源交错控制的需求。5.2 我踩过的三个坑第一个坑是首周期窄脉冲。现象是启动后第一个开关周期内某些通道会出现一个极窄的低电平毛刺。排查之后发现是计数器初值方向配置的问题CH3初值是800如果方向配成了向上启动瞬间它会先尝试继续向上但已经到顶了会立刻触发上溢翻转这个“立刻翻转”的动作在输出上就表现为一个异常窄的脉冲。把方向改成向下后问题消失。第二个坑是更新占空比后相位漂移。现象比较隐蔽稳态下相位差正常但一旦负载变化、程序更新占空比示波器上就能看到某些通道的相位发生了几十微秒的偏移然后又慢慢回到正常。原因是第一版代码没开比较预装载各通道CCR生效时刻不同导致各路在一个“瞬间”处于不同相位。开了预装载后彻底解决。第三个坑是修改开关频率时没有重算初值表。调试时我把ARR从800改成600结果各路相位差不再是60°而是乱七八糟的。因为相位初值表是严格按照ARR800算出来的修改ARR后必须重新计算。建议把初值表做成一个根据ARR运算的函数而不是写死常量这样后期调频率就方便了。5.3 死区对相位测量的影响在三相全桥场景中插入死区后会有一点需要注意互补通道PWML的上升沿会相对理论位置偏移一个死区时间。比如死区200ns那么PWML相对于PWMH的下降沿会延迟200ns。如果你的应用对6路均分精度要求极高测量时要把死区时间算进去。对于多相Buck的非互补输出场景不存在这个问题通道间只有正常的相位关系。6. 扩展从6路到更多相以及与ADC联动6.1 扩展到12路或更多路多相交错的相数不是只能做6路。基于前面初值表的计算逻辑我可以给出一个通用公式。假设N路交错ARR已知相位偏移k×360°/N其中k0,1,…,N-1。对应的瞬时计数初值相位P k × 2 × ARR / N将P对2×ARR做三角波映射而不是简单的取余uint16_t P (k * 2 * ARR) / N; uint16_t phaseInit, direction; uint16_t triPeriod 2 * ARR; P P % triPeriod; if (P ARR) { phaseInit P; direction TIM_DIRECTION_UP; } else { phaseInit triPeriod - P; direction TIM_DIRECTION_DOWN; }用这个公式12路交错只需要把N改成12初值和方向会自动算出来。我在后续实验里试过12路相邻相位差30°总纹波频率等效到720kHz输出端的电容压力进一步降低。6.2 触发ADC采样中心对齐模式的隐藏优势中心对齐模式对数字电源最大的价值在于ADC同步采样。因为计数器在ARR或0位置时电感电流恰好处于平均值附近开关噪声也相对小这是天然的采样点。实现方法是在主定时器上配置下溢事件触发ADC触发源每个PWM周期采样一次总输出电流。由于6路相位交错各路电感电流的脉动并不整齐但总输出电流在交错之后的纹波频率很高、幅值很小在载波谷底采样得到的总电流值基本等于负载电流的直流分量滤波计算量大大减小。如果你希望采样到每一路的相电流可以把ADC的触发源分别配置到对应时基的下溢事件上这样每个时基都能在自己载波的谷底采样自己的电流得到的就是该相电感的平均电流。这个配合在数字均流算法里非常实用。6.3 最后分享一个小习惯这个方案定型之后我在实际项目里还养成了一个调试习惯写上位机脚本把6路PWM的当前占空比、相位初值、ARR值一次性打印出来检查配置和计算是否一致。做过多相交错的同行都知道这种问题出在代码里往往不是算法难而是初始化顺序、寄存器影子装载这类“小地方”最容易卡人。把状态可视化之后排错速度快很多。目前这套6路交错PWM已经在我的电源项目里跑了大半年满载温升、输入纹波、输出电容发热都符合设计预期。如果你也在折腾多相交错、或者被MCU定时器的中心对齐模式绕晕了希望这篇文章能帮你少走几步弯路。