STM32 8路PWM输出加4路反馈:定时器资源与同步配置详解

发布时间:2026/8/30 12:37:16
STM32 8路PWM输出加4路反馈:定时器资源与同步配置详解 这条问题我在嵌入式论坛上见过很多次做多轴电机控制、多路数字电源和机器人驱动的人迟早都会遇到手头要出8路同步PWM同时还要读回4路PWM反馈信号那么STM32到底要占用几个定时器、几个通道先给结论如果按STM32标准的PWM输入模式来读占空比比较稳妥的方案是4个定时器、16个通道其中2个定时器出PWM2个定时器读反馈如果反馈信号只关心频率不关心占空比可以压到3个定时器、12个通道。至于为什么是4个、什么时候能压到3个、什么时候6个都不一定够用下面把账给你算明白。1. 先把需求拆开8路输出和4路反馈分别占什么资源很多人在这一步就乱了是因为没有把“输出”和“输入”拆开算。8路PWM输出和4路PWM反馈对定时器资源的消耗完全不同混在一起想当然会得出各种奇怪的结论。1.1 8路同步PWM为什么两个4通道定时器是起步配置STM32的通用定时器和高级定时器无论TIM1、TIM2还是TIM8单看PWM输出能力最多就是4个独立通道。有些高级定时器还带互补输出比如TIM1_CH1和TIM1_CH1N但那是“互补对”不是“独立8路”。如果你要的是8路可以各自调占空比、各自决定极性的PWM那么至少需要2个定时器每个出4路。这里还要扣一下“同步”两个字。同步PWM不是把占空比配成一样就完事它要求两个定时器的计数相位一致或者说每个PWM周期的起始点得对齐。如果两个定时器各跑各的哪怕ARR和PSC配得一模一样上电瞬间的计数起点不同输出就不可能有确定性的相位关系。要让两个定时器同步STM32提供了一套现成的机制主从模式。主定时器通过TRGO引脚或者内部触发线发出更新事件从定时器收到这个事件后把自己的计数器清零于是两个定时器的计数周期被强行“焊”在一起。这套机制不需要外部连线也不占用额外引脚纯粹是芯片内部的事。所以8路同步PWM的起步配置就是2个定时器、8个通道一个做主一个做从。至于选择TIM1和TIM8还是TIM1和TIM4取决于你的型号和引脚后面专门说。1.2 4路PWM反馈频率加占空比决定了每路得占两个捕获通道这是最容易算错的地方。很多人以为4路PWM反馈就是4个通道一个定时器就够。但如果你要测量的是完整的PWM信号也就是同时知道频率和占空比那STM32的PWM输入模式规定得非常死一路PWM信号必须同时接到同一个定时器的两个通道上一个通道捕获周期另一个通道捕获占空比。为什么这么设计因为一个输入捕获通道在同一时刻只能配置一种捕获边沿。要测上升沿到上升沿的时间差才能得到周期要测上升沿到下降沿的时间差才能得到占空比两个边沿都要抓所以必须两个通道配合。一个通道负责记“周期”一个通道负责记“高电平时间”硬件上自动完成软件只需要读两个CCR寄存器。按这个逻辑4路完整PWM反馈就是4路乘以每路2个通道等于8个捕获通道。STM32一个定时器最多4个捕获通道所以最少需要2个定时器来测反馈。这里有个变通空间如果你的“PWM反馈”实际只是速度脉冲信号周期对应转速占空比无关紧要那完全可以省掉通道配对用一个定时器的4个通道分别做上升沿捕获测4路频率。这种情况下一个定时器就够了。但题目明确说的是“PWM feedback inputs”我下面还是以完整PWM输入模式为主来展开。1.3 算总账推荐方案和最少资源方案把输出和输入加起来账就清楚了。方案PWM输出资源PWM反馈资源合计定时器合计通道完整PWM输入模式推荐2个定时器8通道2个定时器8通道416只测频率/周期的紧凑方案2个定时器8通道1个定时器4通道312每个定时器只测一路的保守方案2个定时器8通道4个定时器8通道6166个定时器的保守方案最容易理解但代价太高。用两个定时器分别测2路PWMCH1/CH2测一路CH3/CH4测另一路是性价比最高的选择既保证了完整PWM输入又只多占一个定时器。所以下面的实现细节我都按4个定时器来写。2. 8路同步PWM输出主从定时器配置输出这部分是重头戏多数人第一次做多路同步PWM时都会在从定时器配置这里卡住。其实STM32参考手册里讲得很清楚只是不少教程只给你看CubeMX的截图不讲背后的寄存器关系。2.1 用TIM1加TIM8输出8路PWM的引脚与通道划分先选定时器和引脚。以资源丰富的STM32F103VCT6这类大容量型号为例TIM1_CH1到CH4通常在PA8、PA9、PA10、PA11TIM8_CH1到CH4通常在PC6、PC7、PC8、PC9。这两组定时器都在APB2总线上时钟来源一致做同步时少了一层总线时钟差异的麻烦所以我个人优先推荐TIM1加TIM8。如果用的是STM32F103C8T6这种“麻雀”型号没有TIM8那就用TIM1加TIM4。TIM1出4路TIM4出4路也能凑够8路。TIM4在APB1上做同步时要确认APB1和APB2的定时器时钟最终算下来一致具体坑在2.4里说。通道划分上没那么多讲究只要别把输出引脚和别的外设冲突就行。真正常见的失误是引脚Remap没配对比如TIM1默认引脚被JTAG占用或者TIM4需要重映射到PC6到PC9硬件上不匹配示波器怎么抓都只有一路有波形。另外如果做电机驱动我还建议用高级定时器TIM1/TIM8来出8路PWM不是因为普通定时器不行而是高级定时器自带死区、刹车、故障保护这些电机控制刚需功能。你看看“半桥驱动PWM占空比100%”这种需求就知道高端驱动必须防直通死区时间不是软件空转能代替的。2.2 主定时器TRGO加从定时器Reset Mode实现硬件同步同步的核心是两行配置。先看主定时器TIM1要让它把更新事件作为触发输出TIM_MasterConfigTypeDef sMasterConfig {0}; sMasterConfig.MasterOutputTrigger TIM_TRGO_UPDATE; HAL_TIMEx_MasterConfigSynchronization(htim1, sMasterConfig);再从定时器TIM8这边把触发源指向TIM1并且让从定时器工作在主定时器更新时复位的模式TIM_SlaveConfigTypeDef sSlaveConfig {0}; sSlaveConfig.SlaveMode TIM_SLAVEMODE_RESET; sSlaveConfig.InputTrigger TIM_TS_ITR0; // 这里要查参考手册确保ITR0对应TIM1 HAL_TIM_SlaveConfigSynchro(htim8, sSlaveConfig);如果你用CubeMX操作更直观在TIM8的Slave Mode Configuration里把Slave Mode选成Reset ModeTrigger Source选成“Internal Trigger 0”或者下拉框里对应TIM1的那一项生成代码后它自动帮你填好SMCR寄存器。为什么用Reset Mode而不是External Clock Mode1我的理解是Reset Mode下主定时器和从定时器各自用自己的时钟源独立计数主定时器更新事件只是负责把从定时器的计数器清零。也就是说从定时器内部有自己完整的PSC和ARR周期长度还是由自己决定主定时器只负责锁相位。这样只要把两个定时器的PSC和ARR设成一样的值它们就能保持同频、同相。举个具体例子假如STM32主频72MHzTIM1和TIM8的时钟也是72MHz想做20kHz PWMhtim1.Init.Prescaler 71; // 72MHz / 72 1MHz htim1.Init.Period 49; // 1MHz / 50 20kHz htim8.Init.Prescaler 71; htim8.Init.Period 49;两个定时器的PSC和ARR完全一致。TIM1每计数到49产生一次更新事件TRGO发一个脉冲TIM8收到后清零CNT。由于TIM8的周期也是49这个清零动作就发生在TIM8即将自然溢出时两边的PWM上升沿对齐同步完成。2.3 占空比同步刷新预装载和更新事件配合8路PWM同步还有一个容易忽略的层面占空比的更新时机。如果两个定时器实现了计数同步但你在运行中修改CCR时TIM1先更新了、TIM8后更新那么这8路PWM的占空比会短暂地不匹配电机上可能表现为一次抖动。解决方法是使用预装载功能。PWM模式1下CCR寄存器本身有预装载影子寄存器只要使能了预装载CCR的写入不会立即生效而是要等到更新事件才全部灌入影子寄存器。同理ARR也有预装载。所以标准做法是8个通道都用HAL_TIM_OC_ConfigChannel配置PWM然后统一开启预装载。运行中修改占空比时先一次性把8个CCR都写进去再等下一个更新事件这时候8路会同时切到新的占空比__HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, duty1); __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_2, duty2); // ... 其他通道 ... __HAL_TIM_SET_COMPARE(htim8, TIM_CHANNEL_1, duty5); // ... 其他通道 ...不要一边写一边等也别相信写CCR的顺序能保证原子性。更新事件一旦到来所有影子寄存器一起刷新顺序最终不影响结果。2.4 同步配置中最容易踩的时钟域坑主从同步最大的坑就是把两个时钟域不一样的定时器配在一起。比如F103里TIM1/TIM8挂在APB2上TIM2/TIM3/TIM4/TIM5挂在APB1上。如果主定时器TIM1在APB2、从定时器TIM4在APB1先查APB1和APB2的定时器时钟是不是一致。有些配置下APB1是36MHz但定时器时钟被倍频到72MHz看起来能和APB2一致可一旦你改过低速外设时钟分频两边就可能差出2倍。我实际踩过这个坑TIM1做20kHzTIM4做从PSC和ARR都按72MHz算好结果TIM4输出的是10kHz。查了半天问题出在RCC配置里APB1预分频不是1导致TIM4的定时器时钟只有36MHz。用Reset Mode的时候主定时器更新事件照样能把TIM4清零但TIM4自己数到ARR的时间变长一倍周期自然就变成主定时器的两倍相位虽然锁住了频率不对也没用。因此我的铁律是能用同一个总线域的两个定时器就绝不跨域。TIM1TIM8是首选如果型号必须要用TIM1TIM4先确认两个定时器时钟完全相等再写死PSC和ARR。3. 4路PWM反馈输入两种主流实现反馈输入的方案直接决定了你要不要多买两个定时器也决定了后续计算的复杂度。这里把完整PWM输入模式和只测频率的输入捕获模式都拆开讲。3.1 PWM输入模式一路PWM占用CH1和CH2两个捕获通道PWM输入模式是STM32输入捕获里的一个特殊玩法。它利用定时器通道的硬件连接关系让一个定时器的两个通道同时测量同一个外部信号CH1捕获上升沿由CH1得到周期的计数值CH2配置成间接映射捕获同一个信号的下降沿得到高电平时间。在CubeMX里配置时选择TIM2的Input Capture Indirect Mode然后CH1使用DirectCH2使用Indirect。对应的HAL代码大概是TIM_IC_InitTypeDef sConfigIC {0}; sConfigIC.ICPolarity TIM_ICPOLARITY_RISING; sConfigIC.ICSelection TIM_ICSELECTION_DIRECTTI; // CH1直接映射到TI1FP1 sConfigIC.ICPrescaler TIM_ICPSC_DIV1; sConfigIC.ICFilter 0; HAL_TIM_IC_ConfigChannel(htim2, sConfigIC, TIM_CHANNEL_1); sConfigIC.ICPolarity TIM_ICPOLARITY_FALLING; sConfigIC.ICSelection TIM_ICSELECTION_INDIRECTTI; // CH2间接映射到TI1FP2 HAL_TIM_IC_ConfigChannel(htim2, sConfigIC, TIM_CHANNEL_2);关键点就在这个INDIRECTTI。很多新手会顺手把CH2也配置成DIRECTTI结果占空比完全读不出来。为什么因为DIRECTTI会让CH2去测TI2FP2对应的引脚而外部信号只接在TI1引脚上CH2对应的引脚空着自然捕获不到有效边沿。配置完成后启动捕获HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_2);中断回调里分别读CCR1和CCR2void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { if (htim-Channel HAL_TIM_ACTIVE_CHANNEL_1) { period HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); } else if (htim-Channel HAL_TIM_ACTIVE_CHANNEL_2) { duty HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); } } }频率和占空比的计算公式也很直白频率 定时器时钟 / ((PSC 1) * period) 占空比 duty / period * 100%一个定时器最多4个通道所以TIM2的CH1/CH2测PWM1CH3/CH4测PWM2TIM3的CH1/CH2测PWM3CH3/CH4测PWM4。这样正好两个定时