4. 定时器与PWM生成:STM32定时器详解、PWM模式配置、占空比与频率控制
电机控制这件事,说白了就是跟「时间」打交道。什么时候该通电,什么时候该断电,电流该给多大——这些全得靠定时器来掐表。我刚开始做电机驱动那会儿,总觉得定时器就是个计数的玩意儿,后来才发现,它才是整个控制系统的「心脏」。
这一章,咱们就把STM32的定时器掰开揉碎了讲。你想想看,一个电机转得快不快、稳不稳,本质上就是PWM的占空比和频率在起作用。搞懂了定时器,你就掌握了电机控制的命门。
核心要点:STM32定时器不仅仅是计时工具,更是PWM生成的硬件基础。理解它的工作原理,是电机控制入门的必修课。
4.1 STM32定时器家族:你该用哪一个?
STM32的定时器分三类:基本定时器、通用定时器、高级定时器。我见过不少新手一上来就开高级定时器,结果被一堆寄存器搞晕了。其实选对型号,事半功倍。
| 定时器类型 | 主要特性 | 电机控制用途 |
|---|---|---|
| 基本定时器(TIM6/TIM7) | 仅基础计时,无PWM输出 | 产生定时中断,做软件计时 |
| 通用定时器(TIM2~TIM5) | 4通道PWM、输入捕获、输出比较 | 普通直流电机、步进电机控制 |
| 高级定时器(TIM1/TIM8) | 互补PWM、死区插入、刹车功能 | BLDC、PMSM矢量控制(必须用) |
我个人习惯是:做简单的有刷电机,用通用定时器就够了;一旦涉及无刷电机或者FOC,直接上高级定时器。别问我为什么——我在项目里吃过亏,通用定时器没有互补输出,做H桥驱动时差点把MOS管烧了。
4.2 定时器核心原理:从计数器到PWM
定时器工作的本质,就是一个计数器在跑。它从0开始往上数,数到某个值就翻回来重新数。这个「某个值」就是自动重装载寄存器(ARR)。
PWM是怎么来的?很简单:
- 计数器从0开始递增
- 当计数值小于比较值(CCR)时,输出高电平
- 当计数值大于等于比较值时,输出低电平
- 计数器到达ARR后归零,重新开始
嗯,这里要注意:频率由ARR决定,占空比由CCR决定。公式是这样的:
PWM频率 = 定时器时钟频率 / (预分频器 + 1) / (ARR + 1)
占空比 = CCR / (ARR + 1) × 100%
举个例子:假如定时器时钟是72MHz,预分频器设为71(即72分频),ARR设为999,那么PWM频率就是72M / 72 / 1000 = 1kHz。如果CCR设为500,占空比就是50%。
我的小技巧:调频率时先固定ARR,再调预分频器。ARR一般设成999或1999,这样占空比调节精度高,而且计算方便。我曾经为了追求高频率把ARR设得很小,结果占空比只能跳变,电机抖得像筛糠。
4.3 PWM模式配置:边沿对齐 vs 中央对齐
STM32的PWM有两种模式:边沿对齐模式和中央对齐模式。说白了,就是计数器计数的方向不一样。
- 边沿对齐模式:计数器从0递增到ARR,然后跳回0。PWM波形的一边是对齐的。
- 中央对齐模式:计数器从0递增到ARR,再递减回0。PWM波形左右对称。
做电机控制时,我建议用边沿对齐模式。为什么?因为中央对齐模式虽然谐波小,但更新频率减半,而且配置起来容易踩坑。除非你做的是高精度伺服,否则边沿对齐完全够用。
配置代码其实不复杂,以通用定时器TIM3为例:
// 开启定时器时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
// 配置预分频器和ARR
TIM_TimeBaseInitTypeDef timBase;
timBase.TIM_Prescaler = 71; // 72分频,得到1MHz计数频率
timBase.TIM_Period = 999; // ARR=999,PWM频率1kHz
timBase.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数(边沿对齐)
TIM_TimeBaseInit(TIM3, &timBase);
// 配置PWM通道
TIM_OCInitTypeDef timOC;
timOC.TIM_OCMode = TIM_OCMode_PWM1; // PWM模式1
timOC.TIM_OutputState = TIM_OutputState_Enable;
timOC.TIM_Pulse = 500; // CCR=500,占空比50%
timOC.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &timOC);
// 使能定时器
TIM_Cmd(TIM3, ENABLE);
注意:高级定时器(TIM1/TIM8)使能后,还需要调用TIM_CtrlPWMOutputs(TIM1, ENABLE)才能输出PWM。我当初第一次用高级定时器时,查了半天才发现少了这行代码。
4.4 占空比与频率的动态控制
电机控制中,占空比和频率经常需要动态调整。比如启动时要软启动,占空比从0慢慢升到目标值;调速时要改变频率。
动态修改占空比很简单:
// 修改CCR值,改变占空比
TIM_SetCompare1(TIM3, newCCRValue);
动态修改频率稍微麻烦点,因为要改ARR。但要注意:修改ARR时,如果计数器正在运行,可能会产生意外的波形。我建议先关闭定时器,改完再打开:
// 安全修改频率
TIM_Cmd(TIM3, DISABLE);
TIM_SetAutoreload(TIM3, newARRValue);
TIM_Cmd(TIM3, ENABLE);
还有一种更优雅的方式:使用预装载寄存器。配置时开启TIM_ARRPreload,这样ARR会在下一个更新事件时才生效,避免波形畸变。
实战经验:做BLDC六步换相时,我需要在每个换相点同时修改三个通道的占空比。如果逐个修改,中间会有短暂的错误波形。解决办法是:先关闭定时器更新中断,批量修改所有CCR,再重新使能。这样波形切换就是「无感」的。
4.5 知识体系总览
下面这张图,是我自己梳理的定时器与PWM的知识脉络。你照着这个思路学,不会乱。
4.6 避坑指南:我踩过的那些坑
做电机控制这些年,我在定时器上栽过不少跟头。挑几个典型的说说:
- 坑一:预分频器改了没生效——我调试时发现频率不对,查了半天才发现预分频器有缓冲寄存器,要等更新事件后才生效。解决办法是手动产生一个更新事件:TIM_GenerateEvent(TIMx, TIM_EventSource_Update);
- 坑二:高级定时器不输出——前面提过,忘了调TIM_CtrlPWMOutputs。这个函数在标准库和HAL库里的名字还不一样,换库时容易忘。
- 坑三:占空比突变导致电机抖动——动态修改CCR时,如果新值和旧值差距太大,电机电流会突变。我后来加了限幅函数,每次只允许占空比变化不超过5%。
我的调试习惯:先用示波器看PWM波形,确认频率和占空比都对,再接到电机驱动板上。别一上来就接电机,万一波形不对,轻则电机乱转,重则烧驱动芯片。我吃过这个亏,烧过三块L298N。
好了,定时器和PWM这部分就讲到这里。你把这些搞明白,电机控制的基础就算打牢了。下一章咱们聊聊怎么用这些知识,让电机真正转起来。
公众号:蓝海资料掘金营,微信deep3321