公司动态
STM32多路同步PWM输出与多通道输入捕获定时器方案详解
有段时间我一直在折腾多轴运动控制的项目屏幕上一排PWM波形叠在一起文档里密密麻麻写着各路占空比和频率。那时候旁边同事经常问一句话我这边要同时输出8路同步PWM还要实时读回4路PWM反馈信号STM32到底够不够用得占几个定时器几个通道这问题看起来很具体但真要答清楚得先把“8路PWM输出”和“4路PWM反馈”各自对硬件资源的要求掰开来看尤其是同步机制和输入捕获的通道消耗这里面的坑比想象中多。这篇文章就把这个问题的完整解法梳理一遍。先算需求账再给出两套可落地的定时器方案然后以STM32F103为例从CubeMX配置到HAL库代码一步步走一遍最后把实际项目中踩过的坑整理成排查清单。无论你是正在画板子选型还是已经焊好板子卡在初始化这篇文章都能直接抄作业。1. 先把需求拆开8路输出和4路反馈分别吃多少资源1.1 8路PWM输出通道数只是表面同步方式才是关键先说8路PWM输出。很多人第一反应是数引脚数完STM32定时器通道一大堆觉得肯定够用。但真正的问题不是“有没有8个通道”而是“这8路PWM到底要不要同步同步到什么程度”。这里的同步有两个层次。第一个层次是频率和计数相位的一致8路PWM如果来自同一个定时器天然就是同步的因为大家共用一个计数器上升沿下降沿从同一个时间基准上切出来。第二个层次是相位关系的可控制性比如要求第2路比第1路滞后30度或者要求某两路之间永远保持固定的时间差这就不能只靠“同一个定时器”这一个条件了要额外设计对齐机制。实际操作中还经常遇到第三种情况8路PWM不是都喜欢用同一组通道比如其中4路要输出带死区的互补PWM另外4路要各自独立控制那么这8路可能分布在两个定时器上。这种情况下两个定时器的计数器如果各自自由运行哪怕寄存器配置一模一样启动瞬间的相位也会有微小偏差。解决的思路就是让其中一个定时器当主另一个当从用主定时器的更新事件去复位从定时器的计数器强行把两边拉到同一个起跑线上。所以8路PWM输出这块先别急着数通道先问自己三个问题各路占空比是不是完全独立需不需要互补输出和死区能不能接受所有输出共用一个计数器。这三个问题直接决定了输出侧需要的定时器数量后面第2章会给出两套对应方案。1.2 4路PWM反馈输入一路PWM反馈要占几个通道反馈输入这侧比输出更容易算错账。你说要测4路PWM反馈那就要先想清楚一路PWM反馈需要测什么参数。常规的PWM反馈至少要拿回两个量周期或者频率和占空比。用STM32的输入捕获来做的话周期需要捕捉上升沿之间的时间差占空比需要捕捉高电平持续的时间这本质上是两个事件上升沿一次下降沿一次。如果用STM32手册里那个标准“PWM输入模式”一路信号要同时喂给同一个定时器的CH1和CH2上升沿捕获的计数锁存到CCR1下降沿捕获的计数锁存到CCR2然后软件拿这两个数一算周期和占空比就都出来了。这里有个容易被忽略的细节标准PWM输入模式下一路完整PWM反馈占2个通道而且一个定时器的触发选择寄存器只能选一个触发源所以一个定时器一次只能测一路完整PWM。4路PWM反馈按这个算法要占4个定时器×2个输入通道。这是让很多人卡住的地方——F103一个芯片上定时器再多也经不起这么造。不过工程上有变通办法。如果你允许自己在中断里做“差值法”运算不完全依赖硬件那套自动清零逻辑那就可以在一个4通道通用定时器上拆两组CH1和CH2测一路PWMCH3和CH4测另一路PWM。这样4路PWM反馈只需要2个定时器就够了代价是代码稍微麻烦一点得自己处理计数器溢出和边沿差值。这个方案在第2章和第3章会详细展开。1.3 盘点定时器资源F103、F407、G0各有多少家底动手配置之前先把手头的家底摸清楚。STM32不同型号的定时器资源差别很大这里列几个常见系列做参考型号系列高级定时器通用定时器基本定时器备注STM32F103C8T6TIM1TIM2、TIM3、TIM4TIM6、TIM7通道紧张84配置需要精打细算STM32F103ZET6TIM1、TIM8TIM2、TIM3、TIM4、TIM5TIM6、TIM7资源相对宽裕84可完整落地STM32F407VGTIM1、TIM8TIM2~TIM5、TIM9、TIM12TIM6、TIM7定时器多选型压力小STM32G0B1TIM1TIM2、TIM3、TIM4、TIM14、TIM15、TIM16、TIM17无小封装型号量大通道分布要仔细看这里的高级定时器指的是TIM1/TIM8这类带互补输出、带死区、带刹车功能的定时器。一个高级定时器有4个输出通道同时每个通道还带一个反向输出加起来就是8路。通用定时器一般只有4个独立通道不带互补输出。基本定时器纯粹用于延时和触发没有输入输出引脚PWM根本用不上。我这篇文章下面的完整例子默认用STM32F103ZET6因为它同时具备TIM1、TIM8两个高级定时器又有TIM2、TIM3、TIM4、TIM5四个通用定时器最能把这个问题的资源分配讲清楚。你要是用F407或者G0思路完全一样资源数量对着表格量一遍就行。2. 两套输出方案互补对方案和独立输出方案2.1 方案A1个高级定时器输出4对互补PWM如果8路PWM实际上可以组织成4对互补信号也就是第1路和第2路频率相同、占空比互补、中间带死区那么最佳选择是用一个高级定时器比如TIM1的4个输出通道加4个互补输出通道。这种情况下8路信号共用一个计数器同步性天然就是最优的不存在跨定时器对齐的问题。这个方案的另一个好处是死区。电机H桥、全桥变换器、逆变器这类应用上下桥臂的PWM如果同时导通就直通炸了必须在互补边沿之间插入死区时间。高级定时器的BDTR寄存器可以硬件配置死区精度能到ns级别比软件延时可靠太多了。而且高级定时器还有刹车输入功能外部故障信号一来输出立即被硬件封锁这对电机驱动来说就是保命功能。但方案A有个前提你得接受输出是成对互补的不能8路完全独立。如果你要的是8路占空比各不相同的独立PWM比如8个舵机角度、8路独立加热棒那方案A就办不到了这时候要看方案B。2.2 方案BTIM1为主、TIM2为从输出8路独立PWM方案B的思路是拆两个定时器每个定时器出4路独立PWM然后用主从同步机制把两个计数器的起点对齐。以F103为例最经典的组合是TIM1当主定时器输出4路TIM2当从定时器输出另外4路。主从同步机制的原理其实不复杂。主定时器TIM1的TRGO引脚内部信号不需要外部接线会在计数器更新事件发生时产生一个脉冲。TIM2把触发源选成来自TIM1的内部触发从模式设为“复位模式”这样TIM2的计数器只要收到TIM1发来的这个更新脉冲就立即清零重新计数。每次TIM1计满一个周期TIM2就跟它对一次表两边相位自然就锁死了。实际操作中有个顺序问题一定要先启动从定时器TIM2再启动主定时器TIM1。原因是TIM2配置成触发复位模式后虽然PWM的PSC和ARR都已经就位但计数器要等主定时器的触发脉冲才开始跑。如果你先把TIM1启动了等看到TIM2也启动的时候两个计数器可能已经差了一段相位。先启动从、后启动主可以保证两者从同一个脉冲出发相位偏差能控制在极小的时钟周期内。方案B占用的定时器数量是2个输出定时器TIM1TIM2再加上反馈侧需要的定时器。如果按第1.2节说的手动双通道捕获方案反馈侧用2个通用定时器TIM3TIM4那总共就是4个定时器这就是标题里那个“8路同步PWM输出4路PWM反馈”的典型完整答案最少4个定时器如果反馈侧用标准PWM输入模式则要5到6个定时器。2.3 反馈侧两种接法标准PWM输入模式与手动双通道捕获反馈侧的接法直接决定定时器预算这里把两种做法放在一起对比。先说标准PWM输入模式。这种模式下一路PWM信号只需要实际接在CH1引脚上CH2不需要外部接线因为芯片内部会把CH1的信号映射给第二个捕获比较器。硬件会自动在上升沿锁存CCR1在下降沿锁存CCR2然后软件读这两个寄存器就能推算出周期和占空比。好处是省事数据干净坏处是一个定时器一次只能服务一路4路就要占4个定时器。再说手动双通道捕获。这种做法的思路是把一个4通道定时器拆成两组CH1负责某一路PWM的周期捕获CH2负责同一路的高电平时间捕获另外CH3和CH4可以同样处理另一路PWM。计算方式不再依赖硬件清零而是在中断里做一个简单的减法每次上升沿到来时用当前捕获值减去上一次的捕获值得到周期每次下降沿到来时用下降沿捕获值减去最近一次上升沿的捕获值得到高电平时间。代码逻辑完全可控4路反馈只占2个定时器。我实际项目里更常用第二种做法因为F103这种芯片上定时器数量有限方案B用了TIM1和TIM2做输出剩下的通用定时器每省一个都是宝贵的。当然手动方式对中断延迟有一定要求中断里不能做耗时操作否则边沿时间戳会漂。后面第3章的代码会把这个写清楚。反馈方案一路PWM占用通道4路占用定时器数优缺点标准PWM输入模式2个通道CH1CH24个定时器配置简单、数据干净但非常耗定时器手动双通道捕获2个通道任意两通道2个定时器省定时器代码稍复杂需注意中断延迟仅测频率简化1个通道1个定时器只要周期不要占空比时最省资源3. 以STM32F103为例的84通道完整配置实战3.1 CubeMX时钟与输出定时器参数配置配置的第一步是时钟树。F103上HSE晶振一般用8MHz经过PLL倍频到72MHz。注意APB1的最大频率是36MHz但定时器时钟不是直接取APB1而是自动乘2所以TIM2、TIM3、TIM4这些挂在APB1总线上的定时器实际时钟还是72MHz。TIM1和TIM8挂在APB2上也是72MHz。这个细节容易在计算分频系数时把人绕晕记住一句话F103的所有定时器时钟统一按72MHz算。然后是输出PWM的频率和分频参数。假设我要做20kHz的PWM占空比分辨率希望做到0.2%左右。计算方法很简单PWM频率等于定时器时钟除以“PSC1”再除以“ARR1”。如果把PSC设为7计数频率就是72MHz/89MHz要让PWM频率等于20kHzARR1就等于9MHz/20kHz450所以ARR设449。这样占空比最小步进是1/450约0.22%对大多数电机控制足够。如果你想分辨率更高可以把PSC调小、ARR调大代价是计数器更容易溢出做输入捕获时要格外注意处理溢出。CubeMX里对应操作是在TIM1的配置页把Channel1到Channel4全选为PWM Generation CHxPrescaler填7Counter Period填449其他保持默认。TIM2的配置和TIM1完全一样。有一点要留意TIM1是高级定时器如果不用互补输出Channel配置那里不要选“PWM Generation CHx CHxN”这种带N的选项否则后面启动时要多调一个HAL_TIMEx_PWMN_Start配置反而绕了。3.2 主从同步配置与启动顺序CubeMX里配置主从同步分两步走。第一步在TIM1的配置页找到“Trigger Output (TRGO)”这一项把它选成“Update Event”。这表示TIM1每次计数器更新都会在内部TRGO信号线上发一个脉冲。TIM1不需要设置从模式它负责当主。第二步在TIM2的配置页把“Clock Source”保持“Internal Clock”然后在“Slave Mode”里选择“Reset Mode”在“Trigger Source”里选择ITR0。F103里TIM2的ITR0正好对应TIM1的TRGO所以这个配置会让TIM2在收到TIM1的更新脉冲时立即把计数器清零。如果你用的型号不是F103Trigger Source的下拉框里通常会直接显示来源定时器的名字按名字选就不会错。代码层面的启动顺序我建议写成下面这样先把从定时器的PWM全启动再启动主定时器// 先启动从定时器 TIM2 的4路PWM HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim2, TIM