上一篇搞定了串口,这篇讲定时器。
定时器是STM32最复杂也最强大的外设之一——延时、PWM、测频、编码器全靠它。
这篇用大白话把定时器的核心用法讲透,看完就能上手。
一、定时器到底能干什么?
很多新手觉得定时器就是"定时用的",其实它能干的事远比你想的多:
| 功能 | 应用场景 |
|---|---|
| 定时中断 | 1秒闪一次灯、周期性采样 |
| 延时 | 替代不精确的while(count--) |
| PWM输出 | 控制LED亮度、舵机角度、电机调速 |
| 输入捕获 | 测量脉冲宽度、频率(测速、测距) |
| 输出比较 | 精确时间点触发动作(计数器等于比较值时输出电平变化,PWM就是用输出比较实现的) |
| 编码器接口 | 读电机编码器(编码器=安装在电机轴上的传感器,输出脉冲信号反映转速和转向),测速测位置 |
一句话:定时器是STM32的"瑞士军刀",搞不定它,很多项目做不了。
二、STM32定时器的分类
STM32F1系列有8个定时器,分三类:
┌─────────────────────────────────────┐ │ 高级定时器(2个) │ │ TIM1、TIM8 │ │ 功能最全:6路PWM互补输出、死区(死区=互补PWM两路输出之间的"都关闭"的短暂间隔,防止电机驱动的上下桥臂同时导通导致短路,新手暂时不用深究) │ │ 适用:电机控制、电源 │ ├─────────────────────────────────────┤ │ 通用定时器(4个) │ │ TIM2、TIM3、TIM4、TIM5 │ │ 功能中等:4路PWM、输入捕获 │ │ 适用:PWM调光、测频、舵机 │ ├─────────────────────────────────────┤ │ 基本定时器(2个) │ │ TIM6、TIM7 │ │ 功能最简:只能定时中断 │ │ 适用:纯延时、DAC触发 │ └─────────────────────────────────────┘新手建议:先学通用定时器(TIM2-TIM5),它覆盖了90%的常用场景。
三、定时器的核心概念:预分频和重装载
这是理解定时器最关键的两个参数,先搞懂它们,后面的都好办。
3.1 时钟源
定时器的计数时钟来自哪里?以STM32F103(72MHz主频)为例:
系统时钟 72MHz ↓ APB1分频(通常/2 = 36MHz) ↓ 定时器自动×2倍频 = 72MHz ↓ 预分频器(PSC) ↓ 计数器(CNT) ↓ 重装载值(ARR) ↓ 触发中断/事件坑点:TIM2-TIM5挂在APB1(最大36MHz),但STM32硬件会自动×2倍频,所以通用定时器的时钟也是72MHz。别以为APB1是36MHz,定时器就只能36MHz。
为什么APB1分频了又要倍频?STM32设计上,APB1总线最大只能跑36MHz(为了保证总线上的外设稳定工作),所以系统时钟72MHz要先÷2再给APB1。但定时器需要更高频率才能实现精细计时,所以硬件在APB1分频系数≠1时,自动把定时器时钟×2补偿回来,让定时器能跑到72MHz。这不是多此一举,而是总线速度和定时器精度的折中设计。
3.2 预分频器(PSC)
作用:把72MHz分频成更慢的计数时钟。
计数频率 = 72MHz / (PSC + 1)为什么+1?因为PSC=0表示不分频(÷1),PSC=1表示÷2,所以分频系数 = PSC+1。
例:要1MHz计数频率(1μs计一个数):
PSC = 72MHz / 1MHz - 1 = 713.3 重装载值(ARR)
作用:计数器计到这个值就清零,并触发更新事件(中断)。
中断周期 = (ARR + 1) / 计数频率同样+1,因为计数器从0计到ARR(含ARR),共ARR+1个数。
3.4 计算公式
定时中断频率:
中断频率 = 72MHz / [(PSC + 1) × (ARR + 1)]例:要1kHz中断(1ms一次):
方案1:PSC=71(1MHz计数),ARR=999(1ms触发) 方案2:PSC=7199(10kHz计数),ARR=9(1ms触发)建议:PSC取小、ARR取大,定时器精度更高。比如1ms中断用PSC=71, ARR=999比PSC=7199, ARR=9精度好。
四、定时中断实现(让LED每秒翻转)
以TIM2为例,实现1秒定时中断。
4.1 HAL库版(CubeMX配置)
CubeMX配置:
Timers → TIM2 → 勾选 "Internal Clock" Prescaler (PSC): 7199 → 10kHz计数频率 Counter Period (ARR): 9999 → 1秒触发一次 NVIC Settings → TIM2 global interrupt → Enable代码:
/* main.c */MX_TIM2_Init();/* 初始化定时器 */HAL_TIM_Base_Start_IT(&htim2);/* 启动定时中断 *//* 重写回调函数(名字固定) */voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if(htim->Instance==TIM2){HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);/* 翻转LED */}}4.2 寄存器版
voidtim2_init(void){RCC->APB1ENR|=(1<<0);/* 开TIM2时钟 */TIM2->PSC=7199;/* 预分频:72MHz/7200 = 10kHz */TIM2->ARR=9999;/* 重装载:10kHz/10000 = 1Hz */TIM2->DIER|=(1<<0);/* 允许更新中断 */TIM2->CR1|=(1<<0);/* 启动定时器 */NVIC_EnableIRQ(TIM2_IRQn);/* NVIC使能(NVIC=嵌套向量中断控制器,STM32内部管理中断的硬件模块,这里告诉它"允许TIM2中断") */}/* 中断服务函数(名字固定,启动文件里定义) */voidTIM2_IRQHandler(void){if(TIM2->SR&(1<<0)){/* 检查更新中断标志 */TIM2->SR&=~(1<<0);/* 清标志(必须!) */GPIOC->ODR^=(1<<13);/* 翻转PC13 */}}关键:中断函数里必须清除中断标志,否则会无限重入中断,主程序根本跑不了。
五、用定时器实现精确延时
上一篇串口文章里,延时用的是HAL_Delay()或while(count--)。前者精度毫秒级,后者更不准。用定时器可以实现微秒级精确延时。
5.1 微秒延时(阻塞式)
利用CNT计数器,1MHz计数频率下,CNT每加1就是1μs:
/* 前提:TIM配置为PSC=71(1MHz计数),ARR=65535 */voiddelay_us(uint16_tus){__HAL_TIM_SET_COUNTER(&htim2,0);/* 计数器清零 */while(__HAL_TIM_GET_COUNTER(&htim2)<us);/* 等到计数到us */}5.2 毫秒延时
voiddelay_ms(uint16_tms){while(ms--){delay_us(1000);}}适用场景:传感器初始化序列、时序敏感的通信协议。
HAL_Delay()最小1ms,做不了μs级延时。
六、PWM输出:调LED亮度/控制舵机
PWM(脉宽调制)是定时器最常用的功能之一。
6.1 PWM原理
PWM通过改变"高电平占整个周期的比例"(占空比),来控制平均电压:
占空比 50%: ┌──┐ ┌──┐ ┌──┐ │ │ │ │ │ │ ──┘ └──┘ └──┘ └── ←周期→ 占空比 20%(LED暗): ┌─┐ ┌─┐ ┌─┐ │ │ │ │ │ │ ──┘ └────┘ └────┘ └─── 占空比 80%(LED亮): ┌───┐ ┌───┐ ┌───┐ │ │ │ │ │ │ ──┘ └──┘ └──┘ └─平均电压 = 供电电压 × 占空比。占空比越大,LED越亮。
6.2 三个关键参数
| 参数 | 作用 | 公式 |
|---|---|---|
| ARR | 决定PWM周期(频率) | 频率 = 72MHz / [(PSC+1)×(ARR+1)] |
| CCR | 决定占空比 | 占空比 = CCR / (ARR+1) |
| PSC | 预分频 | 和ARR一起决定频率 |
6.3 PWM调光实现(HAL库)
CubeMX配置:
TIM3 → Channel1 → PWM Generation CH1 Prescaler: 71 → 1MHz计数 Counter Period (ARR): 999 → 1kHz PWM频率 Pulse (CCR): 500 → 初始50%占空比代码:
/* 启动PWM */MX_TIM3_Init();HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);/* 调节占空比(0-1000对应0%-100%) */__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,500);/* 50%亮度 *//* 呼吸灯效果 */uint16_tbrightness=0;int8_tstep=10;while(1){__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,brightness);brightness+=step;if(brightness>=1000||brightness==0)step=-step;HAL_Delay(20);}呼吸灯原理:占空比从0渐变到100%再变回0,循环往复,LED看起来像在"呼吸"。
6.4 PWM控制舵机
舵机(SG90)控制信号是50Hz PWM,占空比2.5%-12.5%对应0-180度。
这些参数怎么来的?舵机内部的控制电路期望接收50Hz(周期20ms)的PWM信号,其中高电平持续时间0.5ms-2.5ms对应0-180度。换算成占空比:0.5ms/20ms=2.5%,2.5ms/20ms=12.5%。这些参数来自SG90的Datasheet(数据手册),不同舵机可能不同。
/* 舵机配置:50Hz PWM *//* PSC=71(1MHz), ARR=19999 → 1MHz/20000 = 50Hz *//* CCR 500-2500 对应 0-180度 */voidservo_set_angle(floatangle){uint16_tccr=500+(uint16_t)(angle*2000.0/180.0);__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,ccr);}/* 使用 */servo_set_angle(0);/* 0度 */HAL_Delay(1000);servo_set_angle(90);/* 90度 */HAL_Delay(1000);servo_set_angle(180);/* 180度 */七、输入捕获:测量频率
输入捕获可以测脉冲宽度、频率,常用于转速测量、超声波测距。
7.1 原理
┌─────────┐ ┌─────────┐ │ │ │ │ ───────┘ └─────────┘ └── ↑ ↑ 捕获点1 捕获点2 (上升沿) (下降沿) 两次捕获的CNT差值 = 高电平时间定时器在引脚电平变化时(上升沿/下降沿)自动把当前CNT值存入CCR,通过两次CCR差值就能算出脉冲宽度。
7.2 测量频率代码(HAL库)
uint32_tcapture_val1=0,capture_val2=0;uint8_tcapture_flag=0;floatfrequency=0;/* CubeMX: TIM4 CH1, 配置为上升沿捕获 */voidHAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef*htim){if(htim->Instance==TIM4){if(capture_flag==0){capture_val1=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);capture_flag=1;}else{capture_val2=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);/* 计算周期(假设计数频率1MHz) */uint32_tperiod=capture_val2-capture_val1;if(period>0){frequency=1000000.0/period;/* 1MHz / 周期 */}capture_flag=0;}}}/* 启动 */HAL_TIM_IC_Start_IT(&htim4,TIM_CHANNEL_1);应用:测电机转速(霍尔传感器输出脉冲)、超声波HC-SR04回波时间、红外遥控解码。
八、常见踩坑
坑1:定时中断不触发
排查顺序:
- 开了时钟吗?
RCC->APB1ENR对应位 - 开了中断吗?
TIMx->DIER的UIE位 - NVIC使能了吗?
NVIC_EnableIRQ() - 启动定时器了吗?
TIMx->CR1的CEN位 - 清标志了吗?中断函数里必须清
SR的UIF位
五步缺一不可,少一步中断就不工作。
坑2:中断频率不对
现象:算的是1秒中断,实际2秒或0.5秒。
原因:
- 忘了+1:PSC和ARR都要+1,公式里是
(PSC+1)×(ARR+1) - 时钟频率搞错:APB1定时器倍频忘了,以为36MHz实际72MHz
- CubeMX里的PSC值:CubeMX里Prescaler栏填的就是PSC寄存器值(比如填71,寄存器就是71),硬件会自动按PSC+1=72来分频。你不需要手动加1,填71就行。
验证方法:用示波器看LED翻转频率,或串口打印计数验证。
坑3:PWM无输出
排查:
- 引脚配置对了吗?PWM引脚必须是"复用功能"(AF),不能是普通GPIO
- 启动PWM了吗?
HAL_TIM_PWM_Start()必须调用 - CCR值合理吗?CCR=0时占空比0%(常灭),CCR>ARR时占空比100%(常亮)
- 通道选对了吗?TIM3_CH1对应PA6,别接到PA7(CH2)上
坑4:输入捕获值跳动
现象:测出的频率忽大忽小。
原因:
- 信号有毛刺,触发误捕获 → 加硬件滤波
- 计数器溢出了 → 增大ARR或用预分频降低计数频率
- 两次捕获间隔过长,CNT回绕 → 检测溢出并补偿
九、总结
| 要点 | 内容 |
|---|---|
| 定时器分类 | 基本(TIM6/7) / 通用(TIM2-5) / 高级(TIM1/8) |
| 核心参数 | PSC预分频 + ARR重装载,公式:频率=72MHz/[(PSC+1)(ARR+1)] |
| 定时中断 | 配PSC/ARR → 开中断 → 启动 → 中断里清标志 |
| 精确延时 | 1MHz计数频率下,CNT每+1=1μs |
| PWM输出 | CCR决定占空比,ARR决定频率 |
| 输入捕获 | 两次捕获CCR差值 = 脉冲宽度 |
一句话总结:定时器的核心就是PSC+ARR算频率,CCR定占空比,把这两个公式记牢,定时器就拿下了一半。
下一篇预告:《STM32 ADC采样详解:从单次转换到DMA连续采集》
如果这篇文章对你有帮助,点赞 + 收藏 + 关注,这是我持续更新的动力!
有问题欢迎评论区交流,我会逐条回复。作者:嵌入式阿蔡