深入解析STM32定时器PWM模式:从原理到实战应用

发布时间:2026/8/8 5:14:24
深入解析STM32定时器PWM模式:从原理到实战应用 1. 从“定时”到“调光”PWM模式的核心价值如果你玩过STM32或者任何一款单片机定时器Timer这个外设肯定不陌生。它最基本的功能就是“数数”数到某个值就触发一个中断或者事件实现精准的延时或周期性任务。但定时器的能力远不止于此它的“PWM模式”才是将数字世界的0和1转化为模拟世界连续控制信号的魔法钥匙。简单来说PWMPulse Width Modulation脉冲宽度调制模式让定时器不再只是简单地“定时到点就跳变”而是能持续输出一个周期固定、但高电平或低电平占空比可调的方波信号。这个“占空比可调”的特性就是其核心价值所在。它让一个只能输出0V或3.3V的GPIO引脚具备了输出“等效电压”的能力。比如一个周期为10ms的PWM波如果高电平占5ms低电平占5ms那么它的占空比就是50%。对于一个额定电压为3.3V的LED来说这个50%占空比的PWM信号在人眼看来由于视觉暂留效应其亮度就等效于一直施加了1.65V电压的效果。这就是调光的原理。在实际项目中PWM的应用无处不在驱动LED实现呼吸灯、调节直流电机转速、控制舵机角度、生成简单的DAC数模转换信号、甚至用于开关电源的调控。理解并熟练运用STM32定时器的PWM模式是从单片机“点灯学徒”迈向“系统控制工程师”的关键一步。这篇文章我将结合我过去在电机控制、LED屏驱动等项目中的实际经验抛开标准库和HAL库的抽象层深入到寄存器层面把STM32定时器PWM模式的几种配置方式、背后的工作原理、以及那些容易让人栽跟头的细节坑点一次给你讲透。无论你是刚开始接触STM32还是想深化对定时器的理解相信都能找到你需要的东西。2. 定时器PWM的硬件架构不仅仅是比较寄存器在深入配置之前我们必须先搞清楚STM32定时器这里主要指通用定时器TIMx和高级定时器TIM1/TIM8为PWM模式准备了哪些“硬件舞台”。很多教程只告诉你怎么配置库函数但如果不明白硬件在做什么一旦遇到异常比如输出不对、没有输出、互补输出异常排查起来就会非常困难。2.1 核心参与者CNT, ARR, CCRx任何一个定时器PWM的产生都离不开三个核心寄存器CNT计数器寄存器这是定时器的“心脏”一个向上、向下或向上/向下计数的寄存器。它的计数频率由预分频器PSC对系统时钟分频后得到。ARR自动重装载寄存器这定义了PWM波的“周期”。当CNT计数到ARR的值向上计数模式或从ARR计数到0向下计数模式时会发生“更新事件”CNT被重置开始新一轮计数。因此PWM周期 (ARR 1) * (PSC 1) / 定时器时钟频率。CCRx捕获/比较寄存器x1,2,3,4这才是PWM“脉宽”或“占空比”的直接控制者。每个定时器通常有多个通道CH1-CH4每个通道对应一个CCRx寄存器。在PWM模式下硬件会持续比较CNT的值和CCRx的值。2.2 输出比较单元硬件自动化的关键定时器内部有一个“输出比较单元”。当定时器被配置为PWM模式时该单元会监控CNT与CCRx的比较结果并依据你设定的“PWM模式”自动控制对应输出通道OCx的电平状态。这才是“模式”二字的精髓——硬件自动完成电平切换无需软件干预。STM32主要支持两种PWM模式以向上计数为例PWM模式1在向上计数时当CNT CCRx通道x输出有效电平可配置为高或低当CNT ≥ CCRx则输出无效电平。在向下计数时逻辑相反。PWM模式2逻辑与模式1完全相反。这里的“有效电平”和“无效电平”是通过另一个寄存器位CCxP来配置的。例如你可以设置“有效电平高无效电平低”那么PWM模式1 有效电平高CNT CCRx时输出高CNT ≥ CCRx时输出低。这就是最常见的“高电平有效”PWM波。PWM模式1 有效电平低CNT CCRx时输出低CNT ≥ CCRx时输出高。这就是“低电平有效”PWM波。注意这个“有效电平”的概念非常重要特别是在驱动一些外部器件时。比如很多电机驱动芯片是“低电平使能”如果你的PWM配置成了高电平有效可能就无法工作。我第一次驱动一个MOS管栅极时就栽在这个坑里死活不转最后用逻辑分析仪抓信号才发现极性设反了。2.3 预装载与影子寄存器让变化更平滑这是一个容易被忽略但至关重要的高级特性。ARR和CCRx寄存器都有所谓的“预装载”功能。当启用预装载时你写入ARR或CCRx的值并不会立即生效而是先存入一个“影子寄存器”。直到下一个“更新事件”CNT重载发生时影子寄存器的值才会被真正加载到工作寄存器中。这样做的好处是避免毛刺在PWM周期中间修改占空比CCRx或频率ARR如果直接生效可能导致当前周期输出一个畸变的脉冲。通过预装载可以确保所有修改在周期边界同步生效输出波形始终干净整齐。简化软件你可以随时安全地修改这些寄存器而不用担心破坏当前输出。在实现平滑调光或调速时这个功能几乎是必需的。在标准库中通过TIM_OCxPreloadConfig(TIMx, TIM_OCPreload_Enable)和TIM_ARRPreloadConfig(TIMx, ENABLE)来启用CCRx和ARR的预装载。在HAL库中这些通常包含在初始化结构体里。3. 四种配置路径详解从寄存器到HAL理解了硬件原理我们来看看如何让它工作起来。STM32的PWM配置有多种“路径”从最底层的寄存器操作到最便捷的CubeMXHAL各有优劣。我会逐一分析并给出我个人的选型建议。3.1 寄存器直接操作终极控制与理解这是最底层、最直接的方式适合对时序有极致要求或想彻底理解原理的开发者。我们以TIM3的通道1PA6输出PWM为例假设使用72MHz系统时钟目标产生1kHz频率、50%占空比的PWM。// 1. 开启时钟 RCC-APB1ENR | RCC_APB1ENR_TIM3EN; // 使能TIM3时钟 RCC-APB2ENR | RCC_APB2ENR_IOPAEN; // 使能GPIOA时钟 // 2. 配置GPIO PA6为复用推挽输出50MHz GPIOA-CRL ~(GPIO_CRL_MODE6 | GPIO_CRL_CNF6); // 清零 GPIOA-CRL | (0b11 GPIO_CRL_MODE6_Pos); // 输出模式最大速度50MHz GPIOA-CRL | (0b10 GPIO_CRL_CNF6_Pos); // 复用功能输出 // 3. 配置定时器基础向上计数预分频周期 TIM3-PSC 71; // 72MHz / (711) 1MHz 的计数频率 TIM3-ARR 999; // 周期 (9991) / 1MHz 1ms - 1kHz TIM3-CR1 ~TIM_CR1_DIR; // 向上计数 TIM3-CR1 | TIM_CR1_ARPE; // ARR预装载使能 // 4. 配置通道1为PWM模式1 TIM3-CCMR1 ~TIM_CCMR1_OC1M; // 清零OC1M位 TIM3-CCMR1 | (0b110 TIM_CCMR1_OC1M_Pos); // PWM模式1 TIM3-CCMR1 | TIM_CCMR1_OC1PE; // CCR1预装载使能 TIM3-CCER | TIM_CCER_CC1E; // 输出使能 TIM3-CCER ~TIM_CCER_CC1P; // 有效电平为高 (CC1P0) // 5. 设置占空比 (50%) TIM3-CCR1 500; // 占空比 CCR1 / (ARR1) 500/1000 50% // 6. 启动定时器 TIM3-CR1 | TIM_CR1_CEN;为什么这样配置步骤3中PSC71是为了将72MHz降到1MHz这样每个计数周期是1微秒方便计算。ARR999则决定了计数从0到999共1000个 tick周期1ms。步骤4是关键OC1M110就是PWM模式1的编码。步骤5的CCR1500意味着在1000个tick中前500个tick输出高电平因为配置了有效电平高。实操心得直接操作寄存器时务必查阅《参考手册》中对应定时器的“寄存器描述”章节每个位的含义要清清楚楚。特别是CCMRx寄存器它同时控制输入捕获和输出比较模式别搞混了。我建议新手至少用寄存器方式成功配置一次PWM这对理解后续的库函数有巨大帮助。3.2 标准外设库SPL平衡与经典标准库在寄存器之上做了一层适度的封装代码更易读且保持了较好的灵活性。以下是同样功能的配置TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; TIM_OCInitTypeDef TIM_OCInitStruct; GPIO_InitTypeDef GPIO_InitStruct; // 1. 时钟和GPIO初始化略与寄存器类似调用RCC_APBxPeriphClockCmd和GPIO_Init // 2. 时基单元初始化 TIM_TimeBaseInitStruct.TIM_Prescaler 71; TIM_TimeBaseInitStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInitStruct.TIM_Period 999; TIM_TimeBaseInitStruct.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInitStruct.TIM_RepetitionCounter 0; // 高级定时器用 TIM_TimeBaseInit(TIM3, TIM_TimeBaseInitStruct); // 3. 输出比较PWM初始化 TIM_OCInitStruct.TIM_OCMode TIM_OCMode_PWM1; // PWM模式1 TIM_OCInitStruct.TIM_OutputState TIM_OutputState_Enable; // 输出使能 TIM_OCInitStruct.TIM_Pulse 500; // 这就是CCR1的初始值 TIM_OCInitStruct.TIM_OCPolarity TIM_OCPolarity_High; // 有效电平高 TIM_OCInitStruct.TIM_OCIdleState TIM_OCIdleState_Reset; // 高级定时器用 TIM_OC1Init(TIM3, TIM_OCInitStruct); // 初始化通道1 TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); // 使能CCR1预装载 TIM_ARRPreloadConfig(TIM3, ENABLE); // 使能ARR预装载 // 4. 启动定时器 TIM_Cmd(TIM3, ENABLE);可以看到标准库通过结构体将相关参数分组逻辑更清晰。TIM_Pulse这个成员名起得非常好直指“脉冲宽度”。这里有一个大坑标准库的初始化函数TIM_OCxInit在默认情况下并不会自动将TIM_CCMRx寄存器中的OCxPE位预装载使能置1你必须额外调用TIM_OCxPreloadConfig。很多人在动态调整CCRx时发现波形有毛刺问题就出在这里。3.3 硬件抽象层库HAL快速开发与代价HAL库的哲学是“最大程度抽象”追求跨STM32系列的兼容性。代码量更少但执行效率和代码透明度有所下降。TIM_HandleTypeDef htim3; TIM_OC_InitTypeDef sConfigOC; // 1. 时钟和GPIO初始化通常在CubeMX生成的MX_GPIO_Init中完成。 // 2. 时基初始化 htim3.Instance TIM3; htim3.Init.Prescaler 71; htim3.Init.CounterMode TIM_COUNTERMODE_UP; htim3.Init.Period 999; htim3.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim3.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_ENABLE; // ARR预装载 HAL_TIM_PWM_Init(htim3); // 注意这里是PWM初始化函数 // 3. PWM通道配置 sConfigOC.OCMode TIM_OCMODE_PWM1; sConfigOC.Pulse 500; sConfigOC.OCPolarity TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(htim3, sConfigOC, TIM_CHANNEL_1); // 4. 启动PWM输出 HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1);HAL库将时基和PWM模式初始化合并在HAL_TIM_PWM_Init中并且默认开启了CCRx的预装载具体行为取决于HAL_TIM_PWM_ConfigChannel的实现但通常已处理。它的优势是简单但当你需要精确控制时序或排查复杂问题时层层封装的回调函数和状态机可能会让你感到隔靴搔痒。3.4 CubeMX图形化配置效率至上对于快速原型开发STM32CubeMX是无敌的。你只需要在图形界面勾选TIMx选择通道为“PWM Generation CHx”设置Prescaler、Counter PeriodARR、Pulse初始CCRx以及极性生成代码即可。它底层调用的是HAL库。个人选型建议学习和深度调试从寄存器开始然后用标准库巩固。这是打下坚实根基的最佳路径。快速产品原型和简单应用使用CubeMX HAL库。效率最高能避免大量低级错误。对性能和代码大小有严格要求的量产项目推荐使用标准库或甚至回归寄存器。HAL库的通用性是以牺牲部分效率和增加代码体积为代价的。绝对不要只停留在“CubeMX生成代码就能用”的层面否则遇到问题你连调试的方向都没有。4. 高级应用与实战坑点排查掌握了基础输出我们可以玩些更花的。同时这里也是坑点密集区。4.1 互补输出与死区插入电机与电源的守护神这是高级定时器TIM1/TIM8的专属技能常用于驱动H桥电路来控制直流电机或逆变器。你需要一对互补的PWM信号CHx和CHxN来控制桥臂的上、下管。但为了防止上下管同时导通直通会短路烧毁必须在它们切换状态时插入一个“死区时间”Dead Time确保一个管子完全关断后另一个管子才开启。配置互补输出和死区需要操作BDTR刹车和死区寄存器和CCER寄存器。在标准库中有专门的函数和结构体。// 标准库配置互补输出和死区示例 (TIM1_CH1, CH1N) TIM_BDTRInitTypeDef TIM_BDTRInitStruct; TIM_OCInitTypeDef TIM_OCInitStruct; // ... 时基初始化同上 ... // 1. 配置PWM通道1主输出 TIM_OCInitStruct.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStruct.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStruct.TIM_OutputNState TIM_OutputState_Enable; // 互补输出使能 TIM_OCInitStruct.TIM_Pulse 500; TIM_OCInitStruct.TIM_OCPolarity TIM_OCPolarity_High; TIM_OCInitStruct.TIM_OCNPolarity TIM_OCNPolarity_High; // 互补通道极性 TIM_OCInitStruct.TIM_OCIdleState TIM_OCIdleState_Reset; TIM_OCInitStruct.TIM_OCNIdleState TIM_OCNIdleState_Set; TIM_OC1Init(TIM1, TIM_OCInitStruct); // 2. 配置死区时间 TIM_BDTRInitStruct.TIM_OSSRState TIM_OSSRState_Enable; TIM_BDTRInitStruct.TIM_OSSIState TIM_OSSIState_Enable; TIM_BDTRInitStruct.TIM_LOCKLevel TIM_LOCKLevel_OFF; TIM_BDTRInitStruct.TIM_DeadTime 0x54; // 死区时间设置需要根据时钟计算 TIM_BDTRInitStruct.TIM_Break TIM_Break_Disable; TIM_BDTRInitStruct.TIM_BreakPolarity TIM_BreakPolarity_Low; TIM_BDTRInitStruct.TIM_AutomaticOutput TIM_AutomaticOutput_Enable; TIM_BDTRConfig(TIM1, TIM_BDTRInitStruct); // 3. 必须使能主输出MOE TIM_CtrlPWMOutputs(TIM1, ENABLE); // 4. 启动定时器 TIM_Cmd(TIM1, ENABLE);死区时间计算TIM_DeadTime是一个8位值DTG[7:0]其具体时间由公式决定取决于定时器时钟周期。例如当时钟为72MHzPSC0则一个时钟周期约13.9ns。DTG值的不同位域对应不同的计算公式线性或带倍乘需要仔细查阅数据手册。设置太小可能无法防止直通设置太大会降低有效电压利用率。务必用示波器测量确认踩坑实录在一次BLDC电机驱动项目中我设置了死区但电机依然发热严重偶尔冒烟。用示波器同时抓取CH1和CH1N发现死区时间确实存在但波形上升/下降沿不够陡峭。原因是GPIO输出速度配置为了2MHz开关速度太慢导致在死区时间内管子并未完全关断与另一个管子的开启仍有重叠。将GPIO速度改为50MHz后问题解决。教训死区时间是数字逻辑上的保护必须结合功率器件的实际开关特性导通/关断时间和驱动电路的性能来综合设定。4.2 多通道同步与中心对齐模式有时我们需要多个通道的PWM完全同步启动或者输出中心对称的PWM常用于某些变频控制。这就需要用到“主/从模式”和“中心对齐计数模式”。同步可以将一个定时器主的更新事件UEV通过TRGO输出连接到另一个定时器从的ITRx输入从而让从定时器与主定时器同步启动和复位。这在控制多个并联的LED灯带或需要严格相位关系的多路DAC时非常有用。中心对齐模式将计数模式设置为TIM_CounterMode_CenterAligned1/2/3。此时计数器先向上计数到ARR然后向下计数到0。PWM的比较点CCRx在向上和向下计数过程中各发生一次匹配。这种模式产生的PWM波形关于中心对称其开关频率是计数频率的一半但能减少某些应用中的谐波分量。4.3 动态调整频率与占空比平滑之道动态调整PWM是常见需求比如实现呼吸灯效果。这里的关键就是利用好预装载寄存器。调整占空比CCRx这是最安全的。只要启用了CCRx预装载你可以随时写入新的TIMx-CCRy值它会在下一个更新事件生效不会产生毛刺。// 标准库方式 TIM_SetCompare1(TIM3, new_pulse_value); // 安全平滑 // 寄存器方式假设预装载已使能 TIM3-CCR1 new_pulse_value; // 安全平滑调整频率ARR需要格外小心。改变ARR会立即改变周期。如果未启用ARR预装载直接修改ARR当前计数周期就可能被截断或拉长导致一个异常周期。如果启用了ARR预装载新ARR值在下一个更新事件生效。但这里还有一个隐藏坑当你同时需要改变频率和占空比时必须确保新的CCRx值不超过新的ARR值否则在新的周期里PWM可能永远为有效电平或无效电平取决于模式。安全的做法是先计算并设置新的CCRx确保其小于等于新ARR然后再设置新ARR。// 调整频率和占空比的推荐顺序 new_period 1999; // 新周期值 new_pulse 1500; // 新脉宽值 // 1. 首先确保新脉宽不超过新周期软件保护 if(new_pulse new_period) new_pulse new_period; // 2. 更新CCRx预装载使其暂不生效 TIM_SetCompare1(TIM3, new_pulse); // 3. 更新ARR预装载使其暂不生效 __HAL_TIM_SET_AUTORELOAD(htim3, new_period); // HAL库方式 // 或 TIM3-ARR new_period; (寄存器需预装载) // 4. 当下一个更新事件到来时两者同时生效波形平滑过渡。4.4 无输出、波形异常问题排查指南当你按照教程配置好了但引脚上没有波形或者波形不对可以按照以下链路排查时钟树检查这是第一步也是最容易忽略的一步。定时器所在的APB总线时钟开了吗RCC_APBxPeriphClockCmd。GPIO端口的时钟开了吗用CubeMX检查是最直观的。GPIO复用映射STM32的引脚复用功能很灵活。TIM3_CH1可能映射到PA6、PB4或PC6。你配置的GPIO和AFIO复用功能映射对吗参考芯片数据手册的“Alternate function mapping”表格。定时器是否真的启动了TIMx-CR1寄存器的CEN位1吗对于高级定时器的互补输出MOE主输出使能在BDTR寄存器中置1了吗输出使能位TIMx-CCER寄存器的CCxE位和CCxNE如果是互补输出置1了吗这个位控制着输出通道与物理引脚的实际连接。极性配置CCxP位配置对吗如果你配置了高电平有效但用万用表量一直是低电平可能是极性设反了或者你的测量设备如某些逻辑分析仪默认高电平阈值较高。预分频与周期值是否溢出PSC和ARR都是16位寄存器对于通用定时器最大值65535。如果你的计算值超过这个范围需要调整分频比。同时确保CCRx的值小于等于ARR。使用调试器或指示灯在调试时可以在更新中断或CCx匹配中断里翻转一个测试用的GPIO用示波器看这个测试引脚就能知道定时器是否在正常运行比较是否触发。终极武器示波器/逻辑分析仪硬件问题最终必须用硬件工具解决。抓取波形看频率、占空比、电平是否符合预期有无毛刺。这是定位问题的金标准。我自己就曾卡在“GPIO复用映射”这个坑里半天。当时用的芯片是STM32F103C8T6我想当然地以为TIM3_CH1只能在PA6结果原理图设计用的是PB4。代码里GPIO初始化成了PA6自然没有输出。后来查数据手册才发现PB4也有TIM3_CH1的复用功能。所以引脚功能一定要以你使用的具体芯片型号的数据手册为准。5. 超越基础PWM应用场景扩展PWM不仅仅是为了让LED呼吸。理解其本质后你可以用它做很多有趣的事情。5.1 实现一个简易DAC虽然STM32有真正的DAC外设但用PWM加一个简单的RC低通滤波器就能实现数模转换。PWM波经过低通滤波后其平均值直流分量与占空比成正比。占空比越大平均电压越高。设计要点PWM频率选择频率越高滤波后纹波越小波形越平滑。但频率受限于定时器和GPIO的最高速度。通常选择几kHz到几十kHz。滤波器设计一阶RC滤波器的截止频率f_c 1/(2πRC)。为了有效滤除PWM基波及其谐波应使f_c远小于PWM频率比如1/10或更小。例如PWM频率为10kHz可以设计f_c 1kHz选择R1kΩ则C ≈ 0.16uF取标准值0.1uF或0.22uF。软件实现通过改变CCRx来改变输出“模拟电压”。分辨率取决于ARR的值。如果ARR255那么你就有256级0-255的“模拟”输出精度。// 设置PWM DAC输出电压 (假设VCC3.3V, ARR255) void PWM_DAC_SetVoltage(float voltage) { if(voltage 0) voltage 0; if(voltage 3.3) voltage 3.3; uint16_t dac_value (uint16_t)((voltage / 3.3) * 255); TIM_SetCompare1(TIM3, dac_value); }局限性输出阻抗高由R决定带载能力弱响应速度慢受限于RC时间常数。适用于对精度和速度要求不高的场景如生成一个可调的参考电压。5.2 精确控制舵机角度舵机控制是PWM的经典应用。舵机接收一个周期为20ms50Hz脉宽在0.5ms到2.5ms之间的PWM信号脉宽对应着输出轴的角度通常0.5ms对应-90°2.5ms对应90°1.5ms对应0°。配置计算假设系统时钟72MHz使用TIM2。目标周期20ms 0.02s定时器计数频率为了便于计算脉宽我们希望一个 tick 代表 1us。所以设置PSC 71使得计数频率 72MHz / (711) 1MHz (1 tick 1us)。ARR值周期 (ARR1) * 1us 20000us ARR 19999。脉宽对应CCRx值0.5ms - CCRx 5001.5ms - CCRx 15002.5ms - CCRx 2500// 设置舵机角度-90° ~ 90° void Servo_SetAngle(TIM_TypeDef* TIMx, uint32_t Channel, float angle) { float pulse_width_us 1500.0 (angle / 90.0) * 1000.0; // 将角度转换为脉宽us if(pulse_width_us 500.0) pulse_width_us 500.0; if(pulse_width_us 2500.0) pulse_width_us 2500.0; uint16_t ccr_value (uint16_t)pulse_width_us; // 因为1 tick 1us switch(Channel){ case TIM_CHANNEL_1: TIMx-CCR1 ccr_value; break; case TIM_CHANNEL_2: TIMx-CCR2 ccr_value; break; // ... 其他通道 } }实操心得舵机对PWM的周期要求并不严格20ms ± 几毫秒都可以但对脉宽非常敏感。0.5ms的差异就能引起明显的角度变化。务必确保你的定时器配置计算准确并且避免在运行中频繁大幅度修改ARR周期专注于修改CCRx脉宽即可。另外一些廉价舵机可能存在“抖舵”现象可以在到达目标角度后稍微增加一点死区比如将目标脉宽±10个tick内都视为稳定减少不必要的调整。5.3 测量PWM输入频率与占空比定时器除了输出PWM还能输入捕获PWM。利用输入捕获模式可以测量外部PWM信号的频率和占空比。通常需要用到两个输入捕获通道如CH1和CH2一个捕获上升沿一个捕获下降沿通过计算两次捕获的时间差得到高电平脉宽和整个周期。这部分涉及定时器的“输入捕获”和“从模式”功能是另一个广阔的话题。其核心思想是将定时器配置为从模式“复位”模式用PWM信号的上升沿复位计数器一个通道捕获上升沿得到周期另一个通道捕获下降沿得到高电平时间。通过计算两者的比值得到占空比。STM32的某些定时器甚至支持“PWM输入模式”硬件自动完成上述测量只需读取两个CCR寄存器的值即可。从简单的LED调光到复杂的电机驱动、电源管理STM32的定时器PWM模式提供了一个强大而灵活的硬件基础。关键在于理解其工作原理CNT、ARR、CCRx三个寄存器的舞蹈在输出比较单元的指挥下奏出占空比可调的方波乐章。无论是直接操作寄存器感受硬件的脉搏还是借助HAL库快速搭建应用亦或是用CubeMX可视化配置工具只是手段对原理的把握才是应对一切问题的底气。