3、定时器基础:定时器的工作原理、预分频器、自动重装载、定时器模式(单次、周期、PWM)
各位好,今天我们聊聊定时器。说实话,在运动控制里,定时器就是咱们的节拍器。没有它,电机要么跑飞,要么原地踏步。我刚开始做项目时,总觉得定时器就是计个数,后来踩了坑才发现——这里面的门道,深着呢。
3.1 定时器到底怎么工作的?
说白了,定时器就是一个会自己数数的计数器。它靠时钟源驱动,每来一个时钟脉冲,计数值就加1。当计数值达到我们设定的目标时,就会触发一个事件——比如产生中断、翻转IO口、或者更新PWM波形。
你想想看,这跟咱们用秒表计时是不是一个道理?秒表每1秒跳一个字,定时器每1个时钟周期跳一个字。区别在于,秒表是手动的,定时器是全自动的。
核心公式:定时时间 = (自动重装载值 + 1) × (预分频值 + 1) / 定时器时钟频率
举个例子:时钟频率72MHz,预分频器设为7199,自动重装载值设为999,那么定时时间 = (999+1)×(7199+1)/72MHz = 1000×7200/72,000,000 = 0.1秒 = 100ms
嗯,这里要注意:很多新手会搞混预分频值和自动重装载值的顺序。我个人的习惯是——先定预分频,再算重装载。预分频决定了计数步长,重装载决定了总步数。
3.2 预分频器——把时钟调慢一点
预分频器,英文叫Prescaler。它的作用就是把输入时钟频率分频,降低计数器的计数速度。为什么要这么做?因为MCU的主频通常很高(几十到几百MHz),如果直接拿这个频率去计数,1秒钟就计了几亿次,根本没法做长时间的定时。
我在项目中遇到过这样一个情况:用STM32的定时器做1秒中断,忘了设预分频器,结果中断频率高达72kHz,CPU直接卡死。后来加了7199的预分频,把72MHz降到了10kHz,再配合10000的重装载值,稳稳的1秒。
我的经验:预分频值一般选大一点,让计数频率在1kHz~100kHz之间。这样既保证了定时精度,又不会让中断太频繁。当然,如果你需要微秒级的定时,预分频就得设小一些。
3.3 自动重装载——循环的奥秘
自动重装载寄存器(Auto-Reload Register,ARR)决定了计数器的上限。计数器从0开始,一直数到ARR的值,然后产生一个更新事件(Update Event),接着自动回到0重新开始。
这个机制太重要了。没有自动重装载,每次定时结束你都得手动重置计数器,这在实时性要求高的运动控制里简直是灾难。我曾经见过一个同事,用软件方式重置计数器,结果因为中断响应延迟,定时周期漂了5%,电机转速忽快忽慢。
自动重装载有两种工作方式:
- 向上计数模式:计数器从0数到ARR,然后回到0
- 向下计数模式:计数器从ARR数到0,然后回到ARR
- 中心对齐模式:先向上数到ARR,再向下数到0,如此反复
我个人习惯用向上计数模式,简单直观。中心对齐模式在生成对称PWM时很有用,后面会讲到。
3.4 定时器模式——单次、周期、PWM
定时器模式,说白了就是「数完一次怎么办」的问题。不同的模式决定了定时器的行为,咱们一个一个说。
3.4.1 单次模式(One-Shot)
计数器数到ARR后,产生中断,然后停止。就像按一下秒表,响一声就完事。这种模式常用于延时启动、单次脉冲输出等场景。
应用场景:电机启动前的预充电延时、传感器触发后的单次采样。
3.4.2 周期模式(Periodic)
计数器数到ARR后,自动重装载,继续下一轮计数。这是最常用的模式,用于产生固定周期的中断或定时信号。
应用场景:步进电机的脉冲输出、PID控制器的固定周期采样、LED闪烁。
避坑指南:我曾经在周期模式下犯过一个低级错误——中断服务程序执行时间超过了定时周期。结果中断还没处理完,下一次中断又来了,导致系统卡死。解决办法是:确保ISR执行时间 < 定时周期的70%,留出余量。
3.4.3 PWM模式
PWM模式是定时器的高级玩法。它利用比较寄存器(Capture/Compare Register,CCR)来控制输出电平的翻转时间。简单说:计数器从0开始数,当计数值小于CCR时,输出高电平;当计数值大于等于CCR时,输出低电平。这样就在一个周期内产生了占空比可调的方波。
PWM的两个关键参数:
- 频率:由ARR决定,频率 = 时钟频率 / (预分频+1) / (ARR+1)
- 占空比:由CCR决定,占空比 = CCR / (ARR+1) × 100%
举个例子:ARR=999,CCR=300,那么占空比就是30%。如果CCR=500,占空比就是50%。
运动控制中的PWM应用:
- 直流电机调速:改变占空比即可改变电机转速
- 舵机控制:20ms周期,1ms~2ms的高电平时间对应0°~180°
- 步进电机细分:用PWM控制电流斩波,实现微步驱动
3.5 知识体系总览
下面这张图,是我自己画的定时器知识结构图。你看一遍,基本就清楚定时器是怎么回事了。
3.6 代码示例:STM32定时器配置
光说不练假把式。下面给一个STM32F103的定时器配置代码,实现100ms周期中断和50%占空比的PWM输出。
// 定时器2初始化:100ms周期中断
void TIM2_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 使能定时器时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 配置定时器
TIM_TimeBaseStructure.TIM_Prescaler = 7199; // 预分频:72MHz/7200 = 10kHz
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数
TIM_TimeBaseStructure.TIM_Period = 999; // 自动重装载值:1000个计数
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 使能更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 使能定时器
TIM_Cmd(TIM2, ENABLE);
}
// 定时器3初始化:PWM输出,频率10kHz,占空比50%
void TIM3_PWM_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置PA6为复用推挽输出(TIM3的CH1)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 定时器配置
TIM_TimeBaseStructure.TIM_Prescaler = 71; // 72MHz/72 = 1MHz
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = 99; // 1MHz/100 = 10kHz
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// PWM模式1配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 50; // 占空比50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
// 使能定时器
TIM_Cmd(TIM3, ENABLE);
}
小技巧:调试PWM时,我习惯先用示波器看波形。如果没有示波器,可以用逻辑分析仪,或者干脆用万用表测平均电压——50%占空比时,如果IO口是3.3V,测出来应该是1.65V左右。
3.7 常见问题与避坑
- 定时不准:检查时钟源是否正确。很多MCU默认使用内部RC振荡器,精度只有±1%~±2%。要求高时一定要用外部晶振。
- 中断丢失:中断优先级设置不当,或者ISR执行时间过长。我建议把定时器中断优先级设高一些,ISR里只做标志位操作,具体处理放到主循环。
- PWM抖动:更新CCR值时,最好在更新事件发生后再写。否则可能造成一个周期内占空比异常。很多定时器支持影子寄存器,可以解决这个问题。
- 预分频器更新时机:预分频器的值不是立即生效的,要等到下一个更新事件才会加载。这一点我踩过坑——改了预分频值,结果定时周期没变,排查了半天。
好了,定时器的基础知识就聊到这儿。记住一句话:定时器是运动控制的脉搏,把脉搏摸准了,电机才能听话。下一节咱们聊聊中断,看看怎么让定时器「叫醒」CPU干活。
公众号:蓝海资料掘金营,微信deep3321