
1. 从一次调光翻车说起PWM模式选错到底有多坑前阵子帮朋友调一块STM32F103的板子功能很简单——用定时器输出PWM驱动一颗LED做呼吸灯顺带控制一个小风扇的转速。硬件没问题代码也是照着例程抄的TIM3通道1预分频和自动重装载值都算好了频率1kHz占空比从0慢慢加到100。结果烧进去一看灯是亮了但亮度变化完全不对劲占空比设成10%的时候灯几乎不亮设成90%的时候反而暗得跟10%差不多中间50%最亮。朋友一脸懵地问我是不是LED接反了。我让他把PWM模式从模式1改成模式2重新编译下载呼吸灯立马正常了。问题就出在PWM模式1和模式2的极性定义上——这两个模式在STM32的参考手册里各占半页纸看起来只是有效电平和无效电平的措辞差别但落到实际电路上直接决定了你的波形是高电平有效还是低电平有效进而影响驱动电路的逻辑、死区时间的配合、甚至电机转向。这件事让我意识到很多做STM32项目的人包括不少工作两三年的对PWM模式1和模式2的理解停留在抄例程时改一下能跑就行的层面真到了要算死区、要配互补输出、要接反相驱动电路的时候就开始抓瞎。所以这篇就把这两个模式从寄存器层面到实际应用场景彻底讲清楚包括我自己踩过的坑和总结出来的判断方法。2. 先把定时器输出通道的底层逻辑捋顺2.1 从计数器到引脚一个PWM波是怎么长出来的STM32的通用定时器TIM2~TIM5和高级定时器TIM1、TIM8每个都有4个独立通道每个通道可以配置为输入捕获、输出比较、PWM输出、强制输出等模式。PWM输出本质上是输出比较的一种特殊用法核心逻辑就三样东西计数器CNT、比较寄存器CCR、自动重装载寄存器ARR。计数器从0开始往上数数到ARR就归零重新开始这是向上计数模式也是最常用的。在数数的过程中硬件会实时比较CNT和CCR的大小关系。当CNT CCR时通道输出一种电平当CNT CCR时输出另一种电平。这个一种和另一种到底谁高谁低就是PWM模式1和模式2要回答的问题。用生活化的类比CNT就像一个不断上涨的水位CCR是你画的一条刻度线。水位在刻度线以下时水闸开水位涨过刻度线水闸关。PWM模式1和模式2的区别就是水闸开对应的是放水还是堵水。2.2 模式1与模式2的官方定义拆解翻STM32F10x参考手册RM0008第14章输出比较模式那一节写得很清楚PWM模式1向上计数时CNT CCR通道为有效电平CNT CCR通道为无效电平。PWM模式2向上计数时CNT CCR通道为无效电平CNT CCR通道为有效电平。这里的关键词是有效电平和无效电平而不是直接说高电平和低电平。为什么因为STM32的每个输出通道都有一个极性控制位CCxP在CCER寄存器里。CCxP 0时有效电平 高电平无效电平 低电平CCxP 1时有效电平 低电平无效电平 高电平。所以完整的逻辑链是PWM模式决定什么时候输出有效电平极性位决定有效电平是高还是低。这两个配置组合起来才能确定最终引脚上的波形。我见过太多人把这两个概念混在一起以为模式1就是高电平有效结果在配置互补输出的时候极性位一改整个波形全反了。记住模式和极性是正交的两个维度不要绑在一起记。2.3 向上计数、向下计数与中心对齐的差异上面说的是向上计数模式。如果配置成向下计数模式1和模式2的行为会反过来向下计数时CNT CCR为有效电平模式1CNT CCR为无效电平。而中心对齐模式也叫中央对齐更特殊计数器先向上再向下一个周期内CNT会两次经过CCR所以输出波形的频率是向上计数模式的一半且模式1和模式2在上下半周期的行为需要仔细推导。实际项目中绝大多数PWM应用用向上计数就够了比如LED调光、电机调速、舵机控制。中心对齐模式主要用在需要对称波形的场合比如某些电机FOC控制、数字电源的SPWM生成。向下计数用得最少一般只在特定同步需求下才用。3. 模式1和模式2到底怎么选三个判断维度3.1 看驱动电路的逻辑极性这是最直接的判断依据。你的PWM信号最终驱动的是什么LED直接接在引脚和GND之间引脚拉高点亮需要高电平有效选模式1 极性0。LED接在VCC和引脚之间引脚拉低点亮即灌电流需要低电平有效选模式2 极性0或者模式1 极性1。驱动N沟道MOSFET低边开关MOSFET栅极高电平导通需要高电平有效模式1 极性0。驱动P沟道MOSFET高边开关栅极低电平导通需要低电平有效模式2 极性0。接反相器后再驱动负载逻辑要再翻一次模式1 极性1等效于模式2 极性0。我个人的习惯是先确定负载需要什么极性的有效电平再决定用模式1还是模式2。如果负载是高电平有效我优先用模式1 极性0因为这是最直观的配置调试时示波器上看到的波形和代码里的占空比数值完全对应不容易搞混。3.2 看死区时间与互补输出的配合高级定时器TIM1和TIM8支持互补输出即一个通道同时输出CHx和CHxN两路信号中间可以插入死区时间防止上下桥臂直通。这种场景下模式1和模式2的选择会直接影响死区插入的位置。以驱动半桥为例上管需要高电平导通下管需要高电平导通假设用两个N沟道MOSFET加自举电路。CHx驱动上管CHxN驱动下管。如果CHx配置为模式1 极性0那么CHx在CNT CCR时输出高电平上管导通CHxN是CHx的互补信号在CNT CCR时输出高电平下管导通。死区时间就插入在CHx下降沿和CHxN上升沿之间以及CHxN下降沿和CHx上升沿之间。如果改成模式2CHx的有效电平区间会反过来CHxN的相位也跟着变死区的插入位置就完全不同了。在电机驱动和数字电源项目里模式选错会导致死区加在了错误的位置轻则效率下降重则上下管直通烧管子。我调试BLDC驱动的时候就在这上面栽过当时用模式2配互补输出结果死区加在了导通区间中间MOSFET发热严重后来改成模式1才正常。3.3 看占空比的计算公式模式1和模式2的占空比计算公式也不一样。假设向上计数ARR 999CCR 300模式1 极性0CNT 300时输出高电平高电平持续300个计数周期占空比 300 / (999 1) 30%。模式2 极性0CNT 300时输出低电平CNT 300时输出高电平高电平持续700个计数周期占空比 700 / 1000 70%。也就是说同样的CCR值模式2的占空比是模式1的补数。如果你在代码里用__HAL_TIM_SET_COMPARE(htim, TIM_CHANNEL_1, duty)设置占空比duty 300模式1得到30%模式2得到70%。这个差异在写调光、调速代码时必须心里有数否则会出现调大数值反而变暗/变慢的诡异现象。4. 手把手配置从寄存器到HAL库的完整实现4.1 寄存器级配置看清每一位的作用先看最底层的寄存器操作理解了寄存器HAL库的封装就一目了然了。以TIM3通道1为例配置PWM模式1、极性高、频率1kHz、占空比50%// 1. 使能TIM3时钟 RCC-APB1ENR | RCC_APB1ENR_TIM3EN; // 2. 配置时基72MHz / 72 1MHzARR 999频率 1MHz / 1000 1kHz TIM3-PSC 71; // 预分频72分频 TIM3-ARR 999; // 自动重装载 TIM3-CCR1 500; // 比较值50%占空比 // 3. 配置通道1为PWM模式1使能预装载 TIM3-CCMR1 ~TIM_CCMR1_OC1M; TIM3-CCMR1 | (0x6 4); // OC1M 110PWM模式1 TIM3-CCMR1 | TIM_CCMR1_OC1PE; // 使能CCR1预装载 // 4. 配置极性为高电平有效使能输出 TIM3-CCER ~TIM_CCER_CC1P; // CC1P 0高电平有效 TIM3-CCER | TIM_CCER_CC1E; // 使能通道1输出 // 5. 使能自动重装载预装载启动计数器 TIM3-CR1 | TIM_CR1_ARPE; TIM3-CR1 | TIM_CR1_CEN;如果要改成PWM模式2只需要把OC1M改成0x7111。如果要改成低电平有效把CC1P置1。就这么简单但组合起来有四种情况每种对应的波形都不一样。4.2 HAL库配置CubeMX生成代码的解读用CubeMX配置的话界面里有个PWM Generation CH1模式选中后会出现Pulse即CCR值和CH Polarity极性两个参数。注意CubeMX里没有直接让你选模式1还是模式2的选项它默认生成的是PWM模式1。如果你想用模式2需要在生成的代码里手动改或者直接操作寄存器。HAL库启动PWM的函数是HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1);修改占空比的函数是__HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 500);这里有个坑__HAL_TIM_SET_COMPARE修改的是CCR值不改变模式。如果你初始化时用的是模式1运行时改CCR占空比按模式1的公式算如果初始化时用的是模式2改CCR后占空比按模式2的公式算。所以模式必须在初始化时定好运行时不要随意切换。4.3 频率与占空比的精确计算STM32F103的TIM2~TIM5挂在APB1上时钟频率72MHzAPB1预分频系数不为1时定时器时钟是APB1时钟的2倍。TIM1和TIM8挂在APB2上也是72MHz。PWM频率的计算公式f_pwm f_tim / ((PSC 1) * (ARR 1))其中f_tim是定时器时钟频率。比如要生成1kHz的PWMf_tim 72MHz先定PSC 71则计数时钟 72MHz / 72 1MHz。再定ARR 999则PWM频率 1MHz / 1000 1kHz。占空比的计算模式1 极性0duty CCR / (ARR 1)模式2 极性0duty (ARR 1 - CCR) / (ARR 1)我一般习惯把ARR设成固定值比如999或9999然后只改CCR来调占空比。ARR越大占空比的分辨率越高。比如ARR 999时占空比最小步进是0.1%ARR 9999时步进是0.01%。但ARR太大会限制最高频率因为f_pwm f_tim / ((PSC1)*(ARR1))ARR大了要维持同样的频率就得减小PSC而PSC最小是0即1分频。所以频率和分辨率是一对矛盾需要根据实际需求折中。5. 四个真实场景模式1和模式2的实战选择5.1 场景一LED呼吸灯调光这是最基础的应用。假设LED阳极接VCC阴极通过限流电阻接PA6TIM3_CH1即灌电流驱动引脚拉低时LED亮。需要低电平有效。方案APWM模式1 极性1CC1P 1。CNT CCR时输出有效电平低LED亮。方案BPWM模式2 极性0CC1P 0。CNT CCR时输出有效电平高但高电平LED不亮所以实际是CNT CCR时LED亮。两种方案效果一样但方案A的占空比公式更直观duty CCR / (ARR1)CCR越大LED越亮。方案B的占空比是反的CCR越大LED越暗。所以我推荐用方案A代码可读性好后期维护不容易搞错。5.2 场景二直流电机调速用TIM1_CH1驱动一个N沟道MOSFET低边开关MOSFET栅极高电平导通电机接在VCC和MOSFET漏极之间。需要高电平有效。方案PWM模式1 极性0。CNT CCR时输出高电平MOSFET导通电机通电。占空比 CCR / (ARR1)CCR越大转速越快。这里要注意MOSFET的开关频率。一般有刷直流电机的PWM频率选在10kHz~20kHz之间太低会有可听噪声太高会增加开关损耗。我通常用15kHz左右ARR 4799PSC 072MHz / 4800 15kHz。5.3 场景三舵机角度控制舵机需要50Hz的PWM信号脉宽0.5ms~2.5ms对应0°~180°。用TIM2_CH172MHz时钟。计算50Hz意味着周期20ms。设PSC 71计数时钟1MHz周期20ms需要ARR 19999。0.5ms脉宽对应CCR 5002.5ms对应CCR 2500。舵机信号是高电平有效用PWM模式1 极性0。占空比范围500/20000 2.5% 到 2500/20000 12.5%。舵机对模式不敏感因为它的信号是标准的高电平脉宽调制用模式1最自然。但如果你用模式2CCR值就要反过来算容易出错。5.4 场景四互补输出驱动半桥这是模式选择最关键的场景。以TIM1_CH1和CH1N驱动一个半桥上管和下管都是N沟道MOSFET需要死区时间。上管由CH1驱动高电平导通。下管由CH1N驱动高电平导通。CH1配置为PWM模式1 极性0CH1N自动互补。死区时间通过BDTR寄存器的DTG位配置比如DTG 0x18对应约1us死区。如果误把CH1配成模式2CH1的有效电平区间会反过来CH1N的相位也跟着反死区就加错了位置。判断方法很简单用示波器同时看CH1和CH1N正常应该是两路互补中间有明显的死区平台如果发现两路有重叠或者死区位置不对就是模式配错了。6. 踩坑实录那些年我在PWM模式上栽的跟头6.1 坑一模式改了但忘了改极性有一次调一个反相驱动电路负载需要低电平有效。我把模式从1改成2以为就完事了结果波形还是不对。后来才发现模式2 极性0的情况下CNT CCR时输出高电平有效电平但我的负载需要低电平有效所以还得把极性改成1。模式和极性是两个独立的配置改了一个不一定能达到预期效果必须两个一起看。6.2 坑二CCR预装载没使能导致波形抖动在运行中动态修改CCR值的时候如果没使能CCR预装载OCxPE位新的CCR值会立即生效可能在计数器周期的中间时刻改变比较点导致输出波形出现毛刺或抖动。正确做法是使能CCR预装载这样新的CCR值会在下一个更新事件UEV时才生效波形切换干净利落。TIM3-CCMR1 | TIM_CCMR1_OC1PE; // 使能CCR1预装载6.3 坑三高级定时器的MOE位没使能TIM1和TIM8是高级定时器输出PWM之前必须先使能主输出MOE位在BDTR寄存器里。我见过不少人用TIM1调PWM寄存器都配对了引脚就是没波形最后发现是MOE没开。TIM1-BDTR | TIM_BDTR_MOE; // 使能主输出用HAL库的话HAL_TIM_PWM_Start内部会自动处理MOE但如果你直接操作寄存器这一步不能漏。6.4 坑四中心对齐模式下模式1和模式2的行为差异中心对齐模式下计数器先向上再向下。向上半周期模式1在CNT CCR时输出有效电平向下半周期模式1在CNT CCR时输出有效电平。所以一个完整周期内有效电平是对称的。但模式2的行为正好相反。如果你从向上计数模式切换到中心对齐模式模式1和模式2的波形都会变需要重新用示波器确认。7. 常见问题速查表问题现象可能原因排查方法解决方案引脚无波形MOE未使能高级定时器检查BDTR寄存器MOE位置位MOE或使用HAL_TIM_PWM_Start波形极性反了模式或极性配错示波器看有效电平区间调整OCxM或CCxP占空比调大反而变暗模式2的占空比是补数核对CCR与占空比公式改用模式1或反转CCR值波形有毛刺CCR预装载未使能检查CCMR寄存器的OCxPE位使能预装载频率不对PSC或ARR算错重新计算f_tim/((PSC1)*(ARR1))修正PSC或ARR互补输出死区位置不对模式配错示波器同时看CHx和CHxN改用模式1配互补输出中心对齐波形不对称模式与计数方向不匹配推导上下半周期的CNT与CCR关系根据需求选模式1或28. 几个提高调试效率的实操技巧8.1 用示波器触发功能快速判断模式把示波器的一个通道接PWM输出另一个通道接一个GPIO在更新中断里翻转这样可以看到PWM周期和中断的对应关系。如果PWM的上升沿和中断翻转对齐说明有效电平从周期开始就生效是模式1 极性0的典型特征。这个方法比盯着代码推导快得多。8.2 用固定CCR值做静态测试调试阶段先把CCR设成ARR的一半这样占空比是50%模式1和模式2的波形差异最明显一个前高后低一个前低后高。用示波器一看就知道是哪个模式。等确认模式对了再改成动态调占空比的代码。8.3 记录每次修改的配置组合我习惯在代码注释里写清楚当前通道的配置// TIM3_CH1: PWM模式1, 极性高, 1kHz, 初始占空比50%。这样下次改代码或者换人维护的时候不用重新推导。PWM模式这种东西隔一个月不看就容易忘注释是最好的备忘录。8.4 用STM32CubeMX的引脚图辅助验证CubeMX配置完PWM后引脚图上会显示当前配置的通道和模式。虽然它不直接显示模式1还是模式2但可以通过生成的代码反推。我一般会对照CubeMX的配置和手动计算的ARR、PSC值确认频率和占空比符合预期后再下载。9. 模式选择的核心判断流程最后把整个判断流程串一遍遇到新项目的时候按这个顺序走确定负载的有效电平高电平有效还是低电平有效看驱动电路MOSFET是N沟道还是P沟道LED是拉电流还是灌电流。确定是否需要互补输出如果需要驱动半桥或全桥用高级定时器TIM1/TIM8模式选1极性根据上下管类型定。确定占空比的计算方向希望CCR越大占空比越大就用模式1 极性0高有效或模式1 极性1低有效。希望CCR越大占空比越小才用模式2。确定频率和分辨率先算ARR和PSC确保频率正确再检查分辨率是否满足调光/调速的平滑度要求。使能预装载和MOECCR预装载必开高级定时器MOE必开。示波器验证静态测试50%占空比确认波形极性和模式符合预期再上动态代码。这套流程我在多个电机控制和电源项目里用过基本不会出错。模式1和模式2本身没有优劣之分关键是和你的硬件电路匹配。匹配对了代码怎么写都顺匹配错了怎么调都是反的。我个人在实际操作中的体会是90%的PWM应用用模式1 极性0就能搞定剩下的10%里大部分是低电平有效的负载需要模式1 极性1真正需要模式2的场景其实很少。所以如果你拿不准先用模式1 极性0试波形不对再调极性最后才考虑换模式2。这样排查路径最短也最不容易把自己绕进去。