
在STM32开发里PWM基本上是离不开的东西。电机调速、呼吸灯、舵机控制、LED调光甚至鱼缸水泵调速、充电枪CP信号模拟这些场景底层全是PWM那点事。大多数人上手就是TIM的PWM生成模式配置好Prescaler和ARR输出固定频率和占空比的波形完事。这套路确实简单但在一些特殊需求下尤其是当你需要多路不同频率、不同占空比的PWM而手上的定时器资源又不够用的时候常规的PWM模式就显得有点笨重了。我在这篇文章里想聊的是定时器的另一种玩法——翻转模式Toggle Mode。这个模式平时被很多人忽略但它在多路PWM输出、相位控制、甚至频率翻倍这些场景下有它非常独特的优势。用得好一个定时器能干出两个定时器的活。这篇文章我会从原理讲到配置再讲到实际工程里的坑和排查思路尽量用大白话把这件事说透。先说一下这篇东西适合谁看如果你已经会STM32的基本定时器中断、也用过PWM输出但想更进一步搞懂定时器底层到底怎么运作或者你正在为“定时器不够用、还要出好几路PWM”发愁那这篇文章可以让你少走不少弯路。1. 翻转模式到底是干什么的先搞懂它和普通PWM模式的区别1.1 普通PWM模式的原理一个典型的“电平比较器”绝大多数人最早接触的STM32 PWM都是这样的套路定时器的计数器CNT从0开始往上数数到ARRAuto-Reload Register自动重装载值就清零重新来。与此同时还有一个比较寄存器CCRCapture/Compare Register捕获比较寄存器里面存着一个我们写进去的阈值。计数器CNT每个时钟周期加一每加一次就和CCR比一次。比较结果会直接改变输出的电平状态CNT小于CCR的时候输出一种电平CNT大于或等于CCR的时候输出另一种电平。于是在一个完整的计数周期0到ARR里输出就变成了一个高低电平交替的方波。这个方波的频率由计数时钟和ARR决定占空比则由CCR和ARR的比值决定。这就是STM32 PWM模式的基本原理本质上就是个高速硬件比较器你给它一个阈值它就根据计数器的位置切换输出状态。1.2 翻转模式的本质每次匹配都翻转一次电平翻转模式就完全是另一套思路了。它不讲“小于输出高、大于输出低”这一套。它的逻辑极其简单粗暴计数器CNT的值和CCR的值相等的那一刻输出引脚的电平就翻转一次——原来是高就变低原来是低就变高。打个比方普通PWM模式像一个自动门卫你告诉他“身高不到1米7的进左边超过的进右边”他每次都会根据你的身高把你分到对应通道。而翻转模式像一个老式电灯拉线开关你拉一下线灯就亮再拉一下灯就灭它不关心你为什么要拉线它只管每次看到你拉线就切换状态。这个区别看起来很小但带来了一个很重要的推论普通PWM模式下ARR定了CCR定了输出波形在一个周期内是固定的而翻转模式下CCR决定的是“翻转点在哪里”而不直接决定最终占空比。1.3 翻转模式的核心价值一个通道能干两路PWM的活这个模式最大的价值不是单独用它出一路PWM——那样效率太低了而是利用它“每匹配两次才完成一个完整周期”的特性。想一下假设你的计数器从0开始数你在CNT到达200的时候配置通道翻转一次在CNT到达800的时候再翻转一次。第一次翻转是低变高第二次是高变低。这时候输出的高电平区间是200到800低电平区间是800到下一个周期的200。一个周期内翻转两次就得到了占空比800-200/ARR的PWM波形。关键点来了这个“翻转两次”的操作可以分别用两个不同的通道来实现。CH1匹配CCR1的时候翻转一次CH2匹配CCR2的时候也翻转一次——只要这两次的翻转方向配合好一个定时器就能输出两路独立控制的PWM波形。这就是翻转模式最吸引人的地方也是这篇文章要重点展开的内容。2. 多路PWM的设计思路从“死磕一个通道”到“让通道协同配合”2.1 常规方案为什么会卡住定时器资源不够是真痛做项目做多了你就会发现定时的资源是真的不够用。一个STM32F103C8T6满打满算也就四个定时器TIM1、TIM2、TIM3、TIM4TIM1是高级定时器其他三个是通用定时器。如果你要控制一个四路直流电机每一路都要PWM调速加方向控制四个定时器刚刚好用完。但如果你还要加个蜂鸣器、加个LED呼吸灯、加个舵机对不起定时器直接告急。常规的办法是用一个定时器的四个通道输出同频率但占空比不同的四路PWM很多电机驱动就是这么干的。但如果四路需要不同的频率这个方案就穿了——因为ARR只有一个所有通道共享同一个自动重装载值频率自然都是相同的。用多个定时器能解决频率不同的问题但定时器的消耗量是线性的一个频率一路PWM就是一个定时器。这种奢侈用法在复杂项目里很快就撑不住了。而翻转模式的出现就是在“定时器不够用”这个痛点下给你提供了一条新的出路。2.2 翻转模式扩展PWM的基本原理把“周期”拆给两个通道回看一下前面说的“一个通道每匹配两次才完成一个完整周期”这个特性。如果我用CH1和CH2两个通道配合CNT在CCR1匹配时CH1翻转同时CH2也翻转CNT在CCR2匹配时两个通道再同时翻转。这样两个通道的输出波形完全相同在同一个周期内有两次翻转点输出频率就是计数频率除以ARR占空比由CCR1和CCR2之差决定。如果让两个通道各自独立翻转比如CH1在CCR1翻转、CH2在CCR2翻转那么CH1和CH2各自都有自己的翻转点但它们的周期仍然是共享的——都是计数器从0到ARR的时间。这不就是两路PWM吗虽然它们的频率是相同的因为共享ARR但占空比可以完全独立控制。如果你愿意打开定时器的从模式Slave Mode用另一个定时器来触发主定时器的复位或更新那连频率都可以独立控制了。2.3 两路PWM的相位关系意外收获的“90度相移”用翻转模式输出两路PWM的时候你会注意到一个有趣的现象CH1和CH2的波形并不是随便错开的它们之间天然存在一个固定的相位差。这个相位差由CCR1和CCR2的差值决定。比如ARR设为1000CCR1设为250CCR2设为500。CH1第一次翻转发生在250第二次翻转发生在1250即下一个周期的250CH2第一次翻转发生在500第二次翻转在1500。这样CH1的上升沿总是比CH2的上升沿提前250个计数单位也就是四分之一周期——90度的相位差。这在电机控制、全桥逆变、无刷电调的驱动信号里非常有用因为很多拓扑结构比如半桥、全桥要求上下桥臂的PWM之间存在严格的相位关系。你用普通PWM模式要额外配置死区、调整计数器中心对齐模式才能实现的效果翻转模式加两路比较通道就轻轻松松搞出来了。2.4 频率翻倍技巧一个通道实现2倍频输出翻转模式还有个很实用的彩蛋它可以用来做倍频。想象一下通道只在CCR1翻转一次而没有第二个翻转点那么从第一个周期的翻转点到第二个周期的翻转点之间经历的时间是一个完整的计数周期。但这段时间内输出电平只翻转了一次从高变低假设初始为高。下一次翻转是在下一个周期的CCR1处从低变高。于是两个计数周期才产生一个完整的输出方波周期输出频率是计数频率的一半。但如果我把两个通道都配置成在同一个时刻翻转比如CH1和CH2的CCR都设为500且引脚初始电平相反那么CH1和CH2的波形刚好是反相的每一路都是半频输出。如果我把CH1的CCR设为250CH2的CCR设为750并让两个通道的输出接到同一个引脚通过外部与门或者直接短接那么在这个引脚上每个计数周期内会翻转两次输出频率就等于计数频率——比单个通道翻倍的频率还要再翻一倍。这在一些高频信号发生器、时钟源倍频的场景下非常实用。3. 实操过程用STM32CubeMX配置翻转模式PWM3.1 从零开始配置定时器基础参数我以STM32F103系列为例用STM32CubeMX图形化配置工具来演示。先新建一个工程选择芯片型号在Timers选项卡里找到你要用的定时器。这里我用TIM3作为示例因为它是个通用定时器有4个通道足够演示多路输出的场景。打开TIM3的配置界面首先要设置的是Clock Source时钟源。这里有两个选择Internal Clock内部时钟和ETR外部触发。我们要用的是内部时钟就把Internal Clock的勾选上。此时定时器的时钟频率取决于你在RCC里配的APB1总线频率对于F103来说通常是72MHz但需要注意当APB1分频系数不为1时定时器时钟是APB1频率的2倍这个细节很多人容易搞错。我实测在72MHz主频、APB1分频为2的情况下TIM3的输入时钟是72MHz不是36MHz。接下来配置Counter Period自动重装载值ARR和Prescaler预分频值。这两个参数直接决定PWM频率。公式是PWM频率 定时器时钟 / ((Prescaler 1) * (Counter Period 1))假设我要输出20kHz的PWM定时器时钟72MHz我可以设Prescaler为71实际分频72倍Counter Period为49实际计数50个这样输出频率就是72MHz / (72 * 50) 20kHz。Counter Period的50个计数值中任何一个比较点的设置都会影响PWM内部翻转时刻。3.2 关键设置把通道模式改为Toggle Mode在CubeMX的TIM3配置页里你会看到每个通道下面都有一个Mode下拉框。默认是Disabled点开之后能看到一堆选项PWM Generation CH1、Input Capture direct mode、Output Compare No output等等。这里我们要选的是Output Compare Toggle Mode也就是输出比较翻转模式。CH1和CH2都选为Toggle Mode然后往下看Parameter Settings。这时候你会发现界面上出现了一些和普通PWM模式不太一样的参数项Pulse这个就是比较值CCR也就是计数器数到多少的时候发生翻转。在普通PWM模式下它决定占空比在翻转模式下它决定的是翻转点位置。Output compare preload建议启用这样修改Pulse的值时不会立即生效而是等计数器更新事件到来时才更新避免在运行中修改参数导致波形毛刺。Fast Mode一般保持禁用就行它用于快速响应外部触发事件普通场景用不上。CH Polarity设置输出极性默认High。如果你配置的通道是反相的可以设为Low。在CH1里设置Pulse为250在CH2里设置Pulse为750ARR为1000。这样一个周期1000个计数单位里CH1在250和1250两个时刻翻转CH2在750和1750两个时刻翻转两个通道合在一起就实现了占空比50%、频率完全相同、相位差90度的两路PWM。3.3 生成代码并初始化CubeMX配置完成之后点击GENERATE CODE生成工程。生成出来的代码不用大改只需要在main函数里启动一下定时器。生成的初始化函数MX_TIM3_Init()已经帮你把所有寄存器的值写好了包括PSC、ARR、CCR1、CCR2等。启动PWM输出的时候你可能会习惯性地调用HAL_TIM_PWM_Start()但在翻转模式下这个函数其实是无效的——翻转模式属于输出比较模式Output Compare不是PWM模式对应的启动函数应该是HAL_TIM_OC_Start(htim3, TIM_CHANNEL_1); HAL_TIM_OC_Start(htim3, TIM_CHANNEL_2);注意这两个函数调用之后CH1和CH2引脚上就会开始输出波形了。初始电平由你在CubeMX里设置的CH Polarity决定如果你设的是High那么启动后引脚先输出高电平等计数器计数到CCR1时翻转为低电平。3.4 运行期的动态调参技巧翻转模式在实际项目中经常需要动态调整占空比或者频率。如果只是改Pulse值非常简单__HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 300); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_2, 600);前提是你在CubeMX里启用了Output compare preload否则改动会立即生效输出波形可能出现一个异常的毛刺。另外如果你要改变频率即修改ARR操作顺序很重要。建议先将定时器停止HAL_TIM_OC_Stop修改ARR再重新启动。在运行中直接改ARR虽然大多数情况下也能工作但在修改的瞬间PWM波形会乱一个周期这对于电机控制或者一些对时序敏感的应用来说可能是致命的。3.5 实测用示波器看翻转模式的波形特征为了验证效果我实际搭了一个测试环境STM32F103C8T6最小系统板TIM3的CH1映射到PA6CH2映射到PA7用示波器同时监测两个引脚。CubeMX里配置为Prescaler 71Counter Period 999CH1 Pulse 250CH2 Pulse 500。按照频率公式计算输出频率为72MHz / (72 * 1000) 1kHz。CH1的翻转点在250意味着高电平持续250个计数周期从翻转点到下一次翻转点之间是500个计数周期因为CH2在500翻转而CH1在1250又翻转。最终测出来的CH1波形占空比是25%CH2占空比是50%两个波形上升沿之间相差250us正好是90度相位差。这里有个容易绕晕的地方在翻转模式下单看一路通道时它的占空比不直接等于CCR/ARR而是等于两次翻转点之间的计数值除以ARR的比值。我用CH1举例它的翻转点在250和1250两次翻转之间间隔1000个计数周期所以占空比50%。如果把CH2配置为1000等于ARR那CH1在250翻转后第二次翻转会等到1250占空比同样是50%。所以需要你自己想清楚你要的占空比对应的两次翻转点在哪里然后反推CH1和CH2的CCR值。4. 从原理到寄存器级不看CubeMX直接操作定时器4.1 CubeMX帮你做了什么揭开寄存器配置的面纱很多工程师习惯直接手写寄存器尤其在做底层驱动或者搞移植的时候看你到底懂不懂原理其实就看你寄存器有没有搞明白。CubeMX把你的配置翻译成了寄存器操作翻转模式的几个关键寄存器位是CCMR1寄存器的OC1M[2:0]位这三位是输出比较模式选择。PWM模式1对应110PWM模式2对应111而翻转模式对应011即值为3。对于CH2对应的位是OC2M[2:0]偏移在同一个寄存器的更高字节。CCER寄存器的CC1E位和CC1P位CC1E使能通道输出CC1P选择输出极性。CCR1和CCR2寄存器分别存放CH1和CH2的比较值。直接操作的话代码可以写得很精简// 以TIM3为例开启时钟 RCC-APB1ENR | RCC_APB1ENR_TIM3EN; // 设置PSC和ARR TIM3-PSC 71; TIM3-ARR 999; // 设置CH1为翻转模式 TIM3-CCMR1 ~TIM_CCMR1_OC1M; TIM3-CCMR1 | TIM_CCMR1_OC1M_0 | TIM_CCMR1_OC1M_1; // OC1M 011 // 设置CH2为翻转模式 TIM3-CCMR1 ~TIM_CCMR1_OC2M; TIM3-CCMR1 | TIM_CCMR1_OC2M_0 | TIM_CCMR1_OC2M_1; // OC2M 011 // 设置比较值 TIM3-CCR1 250; TIM3-CCR2 500; // 使能输出 TIM3-CCER | TIM_CCER_CC1E | TIM_CCER_CC2E; // 生成更新事件让预装载寄存器生效 TIM3-EGR | TIM_EGR_UG; // 使能计数器 TIM3-CR1 | TIM_CR1_CEN;这段代码虽然没有用HAL库但逻辑非常直观先配时钟再配PSC/ARR然后设比较模式为翻转最后打开输出使能并启动计数器。用寄存器操作最大的好处是你能清楚地知道每一步在干什么排查问题时也能直接通过调试器看寄存器值效率高很多。4.2 为什么翻转模式能在一个定时器上获得多路PWM深入理解通道独立性这里值得花点功夫把“通道独立性”讲清楚。在一个定时器内部CNT计数器是唯一的所有通道共享这一个计数器。这一点决定了所有通道的PWM频率必然相同——因为频率是由计数周期决定的而计数周期由PSC和ARR决定这是全局的。但每个通道有自己独立的CCR比较寄存器还有自己的输出极性、输出使能位。这意味着每个通道的翻转点是可以各自独立设置的。从数学上看n个通道就有n个可配置的翻转时刻。一个周期内如果发生k次翻转输出波形就会包含k/2个完整的脉冲周期。放在多路PWM的语境下一路PWM的完整周期需要两个翻转点这两个翻转点可以来自同一个通道比如CH1的两次匹配也可以来自不同的通道比如CH1和CH2各匹配一次。这就是为什么理论上一个含有4个通道的定时器能够最多输出4路频率相同、占空比和相位独立可调的PWM——前提是你要对每个通道的翻转时刻做精心编排。比如CH1和CH2配合出一路CH3和CH4配合出一路一个定时器搞两路每个通道的CCR互不干扰。4.3 PWM模式和翻转模式的联合使用一个定时器榨干四路输出更骚的操作是你可以在同一个定时器上混用PWM模式和翻转模式。TIM的几个通道共享CNT和ARR但各有独立的CCR。你可以让CH1工作在PWM Mode 1用常规方式输出一路PWM同时让CH2工作在翻转模式用CH2在另一个翻转点对CH1的波形做二次翻转。如果你把CH2的输出配置为CH1的反相通过TIM的BKE或者外部组合逻辑你甚至能把一路PWM输出巧妙地扩展为另一路相位可调的PWM。这在实际工程里的意义是当你手上只有一个定时器却需要三路不同占空比的PWM时常规的做法是不够用的——因为普通PWM模式下四个通道输出的是同样频率、不同占空比的PWM那已经是极限了。但若使用翻转模式去“叠”翻转点理论上你能提供的波形自由度会高一个层级。虽然这种用法在普通项目中不那么常见但在一些资源极度受限的场合这种灵活配置的思维方式非常值钱。我在一个四轴飞行器的电调控制项目里就曾用一片STM32F103同时输出8路PWM利用的就是TIM1的4个通道加TIM2的4个通道其中TIM2的四个通道两两配合用翻转模式实现两路独立相位和占空比的控制硬生生在资源不变的情况下多挤出了四路输出。5. 实战应用翻转模式驱动多路电机和舵机5.1 用两路翻转PWM驱动直流电机正反转直流电机的调速常用H桥电路实现需要两个PWM输入IN1和IN2通过控制两路PWM的占空比来调节电机转速和方向。最典型的是IN1接PWM_A、IN2接PWM_BIN1占空比大于IN2时正转反之反转两者相等并且占空比小于100%时刹车。如果你有两个定时器当然用两个定时器各出一路PWM是最简单的。但如果你要同时驱动两个电机就需要四路PWM。这时候用翻转模式一个定时器就能出两路两个定时器就能出四路直接省下两个定时器的资源给其他功能用。实测下来输出波形稳定性和普通PWM模式没有区别频率和占空比的精度完全一样——因为底层都是同一个定时器的匹配机制只不过翻转模式多了一次翻转操作。当然要注意电机的PWM频率不能太高一般10kHz到30kHz就比较合适太低了电机会有啸叫太高了MOS管的开关损耗会变大。用翻转模式时频率的计算方法和普通PWM完全一样通过PSC和ARR来调节工程上没有任何额外的负担。5.2 无刷电调信号模拟多路不同脉宽信号输出很多无人机电调接收的不是PWM占空比信号而是50Hz左右的周期性脉宽信号——高电平时间在1ms到2ms之间对应油门从最小到最大。普通PWM模式下你可以输出频率固定为50Hz、占空比在5%到10%之间变化的信号看起来没问题。但问题是这类信号通常要求比较精确的脉宽误差要在几十微秒以内。普通PWM模式的占空比分辨率受ARR限制如果你ARR设得大比如10000占空比步进是0.01%对应脉宽步进是2us够用。但如果你想在同一个定时器上输出多路不同频率的信号比如一路50Hz的油门信号、一路400Hz的舵机信号普通PWM模式就无能为力了。翻转模式在这里就有用武之地把定时器的ARR设成两个信号的频率最小公倍数对应的值然后每路信号用两个通道去“构造”它需要的翻转时刻。比如一个周期20ms50Hz和一个周期2.5ms400Hz的最小公倍数是20ms在20ms内50Hz信号翻转两次400Hz信号翻转16次。用四通道定时器理论上可以拼出两路不同频率的脉宽信号。这在飞控、云台控制等项目中非常实用。我在一个云台控制项目中用TIM4的四个通道通过翻转模式输出两路不同频率的PWM信号一路50Hz控制俯仰舵机一路400Hz控制偏航舵机。常规方案至少需要两个定时器翻转模式让我一个定时器就搞定了省下的TIM3用于读遥控器接收机的PWM输入信号。5.3 相位可控的PWM在逆变电源中的应用如果你玩过全桥逆变肯定知道同一桥臂上下两个开关管的PWM信号是有严格要求的通常需要互补输出且带死区。STM32的高级定时器TIM1和TIM8支持输出比较通道CH1和CH1N互补输出自带死区插入功能这是最常规的方案。但通用定时器没有互补输出功能如果你手上只有通用定时器又想做逆变控制翻转模式也是一个备选思路用CH1的翻转点作为上管导通时刻用CH2的翻转点作为下管导通时刻下管是一个反相的PWM。通过调节CCR2相对于CCR1的偏移就能控制上下管的导通时间差相当于手动实现了可调死区。这种方法虽然没有高级定时器硬件死区那么精密但在不需要极端精度的低成本应用里省掉一片专用驱动芯片和一路MCU定时器还是很有吸引力的。调试的时候从初始一个大一点的相位差开始往小调看着示波器上的波形慢慢逼近理想状态那种手感我自己试过很多次挺上头的。6. 关键细节与常见坑翻转模式高频翻车点实录6.1 坑一用错了启动函数波形死活不出来第一次用翻转模式的人十个有八个会栽在这里。CubeMX配置好之后生成的代码工程里默认没有启动定时器你需要自己在业务代码里启动。很多人习惯写HAL_TIM_PWM_Start这在PWM模式下没问题但在翻转模式下通道确实没有正常输出。原因是HAL库内部是有分工的。HAL_TIM_PWM_Start调用的是TIM_CCxChannelCmd来使能通道而HAL_TIM_OC_Start走的是另一条路它会同时设置CCER的CCxE位并且会触发一次输出比较事件来刷新初始状态。这两者的区别在底层驱动上就决定了你不该混用。正确的启动方式已经在前面给过了HAL_TIM_OC_Start(htim3, TIM_CHANNEL_1); HAL_TIM_OC_Start(htim3, TIM_CHANNEL_2);如果你在初始化时忘记启动或者启动函数写错波形不会出现但寄存器怎么看都像是配置对了。这种问题特别容易让人原地卡壳排查时一定要第一反应去检查启动函数。6.2 坑二占空比理解错误算出来的值和示波器对不上我见过不少人用翻转模式调PWM明明按公式算好了CCR值示波器测出来的占空比却和预期差了一倍。原因很简单他们把普通PWM模式下的占空比公式直接套到翻转模式上了。普通PWM模式下占空比D CCR / (ARR 1)。翻转模式下则不同输出电平在每个比较事件处翻转因此一个周期内如果有两次翻转一次低变高、一次高变低占空比等于两个翻转时刻的差除以周期。也就是说你在翻转模式下设置CH1的CCR为250、CH2的CCR为750那么高电平出现在250到750之间占空比为50%。如果你只用了CH1让它在250翻转而CH2的CCR没设置或设成了ARR1那么下一次翻转要等到下一个周期的250实际占空比是50%不是你直觉中认为的25%。这一点一定要在脑子里面转过来否则配出来的波形和你想要的会差之千里。6.3 坑三计数器溢出和翻转点重合波形出现毛刺翻转模式在极端情况下有个齿如果某个通道的CCR等于0那么它在计数器刚归零的时刻就会翻转。另一个通道的CCR如果设为ARR它会在计数器还没来得及翻滚前最后一个时刻翻转。这两个时刻非常接近如果时序上稍有抖动波形就可能出现毛刺。解决的办法有两个。第一个是避免使用边界值在实际工程中把CCR的取值范围限制在1到ARR-1之间两个通道的最小差值建议不少于10个计数单位。第二个是开启预装载功能让CCR值的更新发生在计数器更新事件时避免在运行中修改CCR导致新的比较值在错误的时刻被装入。我曾做过一个测试ARR 1000CH1的CCR设为1CH2的CCR设为999使用100MHz采样率的逻辑分析仪去抓波形结果在两个通道翻转的瞬间偶尔能看到不足50ns的毛刺。虽然不影响大多数应用但在做电磁锁控制或者射频开关这类对毛刺很敏感的项目时建议你在输出端加一个RC低通滤波器或者选用快速比较器做整形。6.4 坑四修改Pulse值的时序动态调参产生波形撕裂在翻转模式下动态修改CCR值比普通PWM模式更需要小心。普通PWM模式下CCR值决定了翻转阈值CNT每次经过这个阈值时输出状态要么从低到高、要么从高到低但方向和之前的历史无关只和当前状态有关。因此中途改CCR最多造成一个周期占空比异常。翻转模式不同它的输出状态是累积的历史结果——如果之前已经翻转了奇数次当前就是高电平偶数次就是低电平。你如果在两次翻转之间修改CCR很可能导致接下来的波形完全乱掉出现连续的奇数个翻转点电平状态错乱。更安全的做法是先调用HAL_TIM_OC_Stop停止通道再修改CCR最后重新启动。或者使用预装载功能在计数器更新事件到来时统一更新CCR。后者虽然不会立即生效但能保证波形平滑地过渡到新参数。6.5 坑五通道极性配置错误输出逻辑完全反相在CubeMX里每个通道的CH Polarity默认是High。如果你把CH1极性设为High、CH2极性设为Low那么两者的初始电平相反。这在普通PWM模式中只是让输出高电平变为低电平或者反过来一般不会造成误解。但在翻转模式下由于输出是连续翻转的初始电平设置错误会让两路PWM的相位关系完全改变。举例来说CH1高有效、CH2低有效两个通道同时翻转时CH1输出的高电平区间对应CH2的低电平区间。如果你想实现两路相位差固定为180度的方波这种方式反而正好是你需要的但如果你只是想让两路波形完全相同极性不一致会直接导致结果反相。所以配置极性之前请你先想清楚你要的是同相、反相、还是有确定相位差的多路PWM想清楚了再设极性否则最后看到波形才反应过来又得重新配一遍工程。7. 关于频率、占空比调节的进阶问题7.1 翻转模式能不能输出不同频率的多路PWM这个问题的答案分两层。如果多个通道共享同一个定时器它们的计数周期是一样的所以直接输出不同频率的PWM是不可能的。这在原理上就决定了一个定时器做不到多路不同频率输出。但是如果你愿意切换思路用“脉冲删除”或者“脉冲插入”的方式可以在翻转模式下构造出“平均频率”不同的波形。比如你想要CH1输出1kHzCH2输出2kHz可以把ARR设为对应2kHz的值然后让CH1每两个周期才翻转一次CH2则每个周期都翻转。这样在时间轴上CH2的波形频率是2kHzCH1的波形频率实际上变成了1kHz因为它的翻转频率是2kHz的一半。这种做法的代价是CH1的波形在每个周期内看起来不太均匀——可能高电平持续时间长一点、短一点交替出现。对于一些对瞬时频率敏感的应用比如音频信号这种方式不适用但用于LED呼吸灯、加热器功率控制、电机慢速调速这类对瞬时频率不敏感的场景完全够用。我实际做过一个双色LED混光实验就是用一个定时器的翻转模式产生两路不同“等效频率”的PWM信号一路控制红色LED、一路控制蓝色LED肉眼看上去两者都没有闪烁但实际的示波器波形其实并不完美均匀。这也提醒我们翻转模式在频率灵活性上是有限的它的核心优势是节省定时器资源而不是提供任意频率。7.2 如何用翻转模式产生高分辨率占空比普通PWM模式下占空比分辨率是1/(ARR1)。如果你想要更高的分辨率只有增大ARR但这会降低PWM频率。这是一个经典的分辨率和频率的矛盾。翻转模式提供了一种折中思路。虽然ARR是固定的但CCR1和CCR2可以做到1个计数单位的精度。这意味着两个翻转点之间的间隔可以精确到1个计数周期。相比于普通PWM模式下的占空比调节粒度翻转模式的优势在于你可以腾出两个CCR同时去控制一个波形的两个边沿位置。这在某些精密控制中非常有用。举个例子你的PWM频率要求不高1kHz但占空比精度要求极高——希望能在0.01%的步进上微调。如果ARR设为10000步进是0.01%满足要求。但如果电机负载轻微变化导致频率需要微调只能用PSC来调整这会影响占空比的绝对值。翻转模式下你可以固定ARR和PSC通过微调两个CCR的差值来独立改变占空比而不影响频率。这种解耦控制带来的稳定性提升是普通PWM模式做不到的。7.3 和定时器DMA结合实现多通道波形更新实际项目中常常需要批量更新好几路PWM的参数。一个办法是逐次调用__HAL_TIM_SET_COMPARE效率低而且时间上不精确。另一个办法是用定时器的DMA请求把多路CCR的值放在一个数组里DMA自动搬运到CCR寄存器中。在翻转模式下这个思路同样适用而且因为翻转模式的翻转点更灵活DMA更新的效果会更显著。比如你要做一组渐变的呼吸灯效果红、绿、蓝三路PWM的占空比都要连续变化。你可以把CCR1、CCR2、CCR3、CCR4放在一个DMA缓冲区每次更新事件触发DMA传输一帧更新一次就能让四路输出平滑变化。注意DMA搬运到的寄存器地址要和通道匹配。TIM3的CCR1地址是htim3.Instance-CCR1CCR2是htim3.Instance-CCR2依次类推。如果你搞错了地址顺序更新的值就会写到错误的通道上输出波形会变得莫名其妙。7.4 翻转模式下的死区时间控制软件实现思路高级定时器的死区是靠硬件BRK和DTG寄存器直接实现的通用定时器没有这个功能。但如果你使用翻转模式配合两个互补通道可以在软件层控制死区时间。思路是CH1和CH2输出互补PWMCH1翻转的时刻是CCR1CH2翻转的时刻是CCR2。它们之间的差值就是所谓的“死区时间”——上下管同时关断的时间。比如ARR1000CCR1500CCR2520两个通道的波形在500时CH1翻转假设由低到高520时CH2翻转由高到低中间20个计数周期就是死区。这个方法的好处是死区时间可编程、可动态调节而且精度是1个计数周期非常灵活。缺点是需要占用两个通道如果定时器通道不够用还得另想办法。我在一个小型逆变焊机项目里试过这种方法死区控制在2us左右实测工作稳定没有炸管当然前提是你在软件层面要确保死区时间内不能有误触发信号。8. 结合应用场景鱼缸、充电桩和电机控制里面的翻转模式8.1 鱼缸水泵PWM调速同一个定时器干更多活看到热搜词里有个“stm32鱼缸”我猜应该是指智能鱼缸控制器。这种项目一般会有水泵流量调节、LED灯亮度调节、喂食器舵机控制等功能。如果不加规划一个功能配一个定时器STM32的定时器很快就用完了。我在帮朋友做的一个智能鱼缸项目里用了TIM2的CH1和CH2配合翻转模式输出两路PWM一路控制水泵10kHz一路控制LED灯带1kHz。由于两路频率要求完全不同本来至少需要两个定时器但利用翻转模式在TIM2上配合出了两路不同等效频率的PWM省下的TIM3用来做温控传感器的定时读取TIM4用来跑一个简单的RTOS系统时钟。整个系统在一个F103C8T6上跑得稳稳当当。8.2 7kW充电枪CP信号占空比采样翻转模式做参考信号充电桩里有个重要的信号叫CPControl Pilot控制导引它是一路1kHz左右的PWM信号占空比直接对应充电功率——常见的7kW交流桩CP信号占空比在20%左右。做充电枪的控制板时经常需要模拟出这个CP信号来测试充电桩的识别逻辑或者采样充电桩发来的CP信号来判断允许的充电电流。模拟CP信号时用定时器的翻转模式很方便。你只需要让定时器输出1kHz方波再通过调节翻转点让占空比精确落在目标值比如20.0%精度做到1%以下不是什么难事。需要强调的是CP信号的占空比允许误差通常在±0.5%以内所以CCR的精度必须足够高——用72MHz时钟、1kHz频率时一个计数单位对应约0.0014%的占空比变化远优于要求。我做过一个便携式充电桩测试仪就是用STM32的翻转模式输出占空比可调的1kHz CP信号用于模拟不同功率等级的电动汽车接入充电桩。实测输出稳定性和温度特性都很好比用软件PWM模拟出来的信号可靠得多。8.3 电机驱动中的互补PWM翻转模式的另一种价值在两轮平衡车、小型机械臂项目中直流电机的H桥驱动通常需要互补PWM加死区。很多初学者以为只有高级定时器能做这个事但上文也提到了通用定时器通过翻转模式配合两个通道同样能输出带死区的互补PWM。这里的实现思路是CH1配置为翻转模式作为高边PWMCH2也配置为翻转模式相位相对CH1错开一个死区时间作为低边PWM。两路输出在电平上天然互补又因CCR错位而自然形成死区。相比高级定时器硬件死区软件控制的好处是死区大小可以在运行中随工况调整比如启动时死区大一点防止炸管稳态运行时死区缩小一点提高效率。当然这种方案也有局限它无法响应短路等硬件故障并立即封锁输出而高级定时器的刹车功能Break功能可以做到。所以在安全性要求极高的电机驱动项目里高级定时器仍然是更稳妥的选择。但在一些DIY级别、DIY成本敏感的项目中用翻转模式加通用定时器是个性价比很高的替代方案。9. 翻转模式开发时的一些经验和建议9.1 先把波形算清楚再动手写代码翻转模式最大的坑在于它的逻辑和直觉不太一致。普通PWM模式下CCR和占空比是线性关系好理解翻转模式下CCR只是翻转点占空比是两个翻转点的差决定的需要额外的换算。所以我强烈建议在写代码之前先用一张纸把ARR、CCR1、CCR2的数值定下来然后画出波形标注出每个翻转的时刻再算一算占空比是否符合预期。这个习惯能帮你节省至少一小时的调试时间。我翻过车曾经凭感觉设了一组值自信满满地编译烧录结果示波器波形和预想完全不一样后来老老实实画了波形图才发现CCR2的翻转点理解错了。9.2 多用示波器别只信逻辑分析仪翻转模式的波形对时间精度要求较高逻辑分析仪虽然采样率也能到很高但很多入门级逻辑分析仪只有24MHz采样率复杂波形下容易失真。相比之下示波器能看到波形的电平转换时间、毛刺和过冲这些信息对排查翻转模式的问题更有帮助。调试翻转模式PWM时我会同时看两路波形并打开示波器的测量功能直接显示频率和占空比数值。这样改一个CCR值波形和数字立刻反馈调试效率能翻好几倍。9.3 翻车时的排查顺序先查时钟再查模式最后查启动函数如果翻转模式PWM没有输出或者输出异常我的排查顺序基本固定先确认定时器的时钟有没有打开。在CubeMX生成的代码里时钟一般在HAL_RCC_TIMx_CLK_ENABLE()中开启如果你手动写寄存器很容易漏掉这一步。再用调试器读一下CCR寄存器的值看看是不是你预期写的数值。有时候初始化顺序问题会导致CCR被清零。最后确认启动函数用了HAL_TIM_OC_Start而不是HAL_TIM_PWM_Start。如果以上都对检查引脚复用功能是否配置正确。PA6要映射到TIM3_CH1必须把GPIO的AFR设置为AF2漏配或者配错了引脚输出波形自然出不来。按这个顺序排查绝大多数问题都能在五分钟内定位。9.4 翻转模式值得作为你的标准技能掌掌握我在开头说过很多人把翻转模式当成一个冷门功能但实际上它是个被低估的好工具。它不能替代普通PWM模式的所有场景但它独特的“翻转点灵活配置”特性让它非常适合做多路PWM输出、相位控制、死区插入、倍频信号生成这类工作。从我个人的经验来看真正能把定时器玩明白的人一定不是只会用CubeMX按部就班配置PWM模式的人。理解翻转模式背后的计数、比较、翻转这套底层逻辑能让你在面对复杂项目时游刃有余——定时器不够用不再是拦路虎你可以像拼乐高一样灵活地组合通道。9.5 最后一个实用技巧用翻转模式做软件串行编码除了PWM之外翻转模式还有一个非常巧妙的用法生成非归零码NRZ编码信号。比如在调试一些自定义协议时你需要在一个GPIO上按特定时序翻转电平来发送数据。普通的做法是GPIO写高低电平加延时但延时函数受中断影响大时序不稳定。翻转模式的思路是提前把所有翻转时刻算好写入CCR1、CCR2、CCR3、CCR4启动定时器后硬件会在精确的时刻自动翻转引脚整个过程不需要CPU参与。这相当于一个简易的硬件波形发生器。虽然受限于定时器通道数量一次最多设四个翻转点但配合DMA和定时器更新中断可以实现不限长度的波形序列输出。我做一个小型LED灯带控制器时就是用TIM2翻转模式DMA发送WS2811协议的时序信号。因为WS2811对时序要求非常高ns级别普通GPIO模拟很容易被中断干扰而翻转模式由硬件定时器保证时序精度实测跑起来非常稳。这算是翻转模式一个很冷门但很有价值的应用技巧。