当前位置: 首页 > news >正文

STM32定时器详解:从延时到PWM输出

上一篇搞定了串口,这篇讲定时器。
定时器是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 = 71

3.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:定时中断不触发

排查顺序

  1. 开了时钟吗RCC->APB1ENR对应位
  2. 开了中断吗TIMx->DIER的UIE位
  3. NVIC使能了吗NVIC_EnableIRQ()
  4. 启动定时器了吗TIMx->CR1的CEN位
  5. 清标志了吗?中断函数里必须清SR的UIF位

五步缺一不可,少一步中断就不工作。


坑2:中断频率不对

现象:算的是1秒中断,实际2秒或0.5秒。

原因

  1. 忘了+1:PSC和ARR都要+1,公式里是(PSC+1)×(ARR+1)
  2. 时钟频率搞错:APB1定时器倍频忘了,以为36MHz实际72MHz
  3. CubeMX里的PSC值:CubeMX里Prescaler栏填的就是PSC寄存器值(比如填71,寄存器就是71),硬件会自动按PSC+1=72来分频。你不需要手动加1,填71就行。

验证方法:用示波器看LED翻转频率,或串口打印计数验证。


坑3:PWM无输出

排查

  1. 引脚配置对了吗?PWM引脚必须是"复用功能"(AF),不能是普通GPIO
  2. 启动PWM了吗HAL_TIM_PWM_Start()必须调用
  3. CCR值合理吗?CCR=0时占空比0%(常灭),CCR>ARR时占空比100%(常亮)
  4. 通道选对了吗?TIM3_CH1对应PA6,别接到PA7(CH2)上

坑4:输入捕获值跳动

现象:测出的频率忽大忽小。

原因

  1. 信号有毛刺,触发误捕获 → 加硬件滤波
  2. 计数器溢出了 → 增大ARR或用预分频降低计数频率
  3. 两次捕获间隔过长,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连续采集》

如果这篇文章对你有帮助,点赞 + 收藏 + 关注,这是我持续更新的动力!
有问题欢迎评论区交流,我会逐条回复。

作者:嵌入式阿蔡

http://www.jsqmd.com/news/1332518/

相关文章:

  • Unity Sprite Atlas深度解析:从Draw Call优化到实战配置指南
  • 【论文复现】ICLR 2026 北大NVIDIA 提出 MHLA:多头线性注意力,即插即用!附赠 YOLO26 改进
  • Unity游戏开发中的观察者模式:从C#事件到消息总线的实战指南
  • mysqlrouter高可用
  • Matlab实现动态再结晶的元胞自动机模拟
  • STM32-CAN
  • GTA5线上小助手:免费开源工具彻底改变你的洛圣都游戏体验
  • 靠挖漏洞和打比赛赚钱,黑客技术变现的真实路径
  • 金城银行基于 Apache Doris 构建实时数据平台:T+1 到分钟级的金融级实践
  • 别再死记硬背了!用“班级点名册“类比,3分钟搞懂区块链是什么
  • 深度学习在设备寿命预测中的应用:从CNN、LSTM到Transformer的实战解析
  • 高效网页保存解决方案:Chrome滚动截图完全指南
  • 免疫细胞培养基深度评测:从RPMI 1640到无血清配方的选择与优化指南
  • 涿州老王匠实木定制千套实景,还原业主心中理想原木家 - GrowthUME
  • DiskGenius专业版深度解析:分区管理、数据恢复与系统迁移实战指南
  • 抖音批量下载器终极指南:一键保存无水印视频和音乐
  • Windows 10系统迁移后启动失败?UEFI引导与驱动问题深度修复指南
  • 5步掌握BlenderKit:让3D创作效率提升300%的终极指南
  • 基于OpenClaw AI智能体框架构建专业领域论文降重助手
  • LangGraph 之 【工作流模式】(Send)
  • FGO自动化脚本终极指南:告别重复刷本,3分钟解放双手!
  • 基于豆包API与Flask构建智能二维码:AI对话与微信生态整合实战
  • Nginx从入门到实战:安装配置、反向代理与负载均衡详解
  • 数智化转型实战:五大行业应用场景解析
  • Psychopy并口错误:psychopy.parallel has been imported but no parallel port driver found
  • douyin-downloader:抖音内容自动化采集与管理解决方案
  • 不会直播话术需要抖音公会一对一带教孵化 - 甄选测评馆
  • 3分钟定位Windows热键冲突:Hotkey Detective使用全攻略
  • 计算机视觉基础|第1章 走进计算机视觉
  • 移动端HDRP胶片颗粒性能优化:从原理到实践的完整指南