玩转STM32定时器:从基础原理到实战应用全解析(多任务并发+呼吸灯+高级技巧)
本文全面解析STM32定时器原理与应用,包括定时器分类、多任务并发实现和PWM呼吸灯开发。首先介绍STM32定时器核心原理,重点分析通用定时器的时钟链路与参数配置。随后通过两个实战案例:使用TIM2/TIM3中断实现LED翻转和串口通信的多任务并发,对比轮询法的局限性;以及利用TIM3/TIM4的PWM功能开发呼吸灯效果,详细讲解PWM参数计算和CubeMX配置。文章强调定时器中断在提升系统效率和
玩转STM32定时器:从基础原理到实战应用全解析(多任务并发+呼吸灯+高级技巧)
文章目录
- 玩转STM32定时器:从基础原理到实战应用全解析(多任务并发+呼吸灯+高级技巧)
-
- 一、定时器核心原理与分类
-
- 1. 定时器分类(以STM32F103为例)
- 2. 定时原理与关键参数
- 二、定时器中断实现多任务并发(实战案例)
-
- 1. 功能需求
- 2. 硬件准备
- 3. STM32CubeMX详细配置
-
- (1)基础配置
- (2)定时器配置(TIM2+TIM3)
-
- TIM2(2秒定时):
- TIM3(5秒定时):
- 4. 代码实现与解析
-
- (1)定时器中断回调函数
- (2)主函数初始化
- 5. 实现效果
- 三、非定时器方案对比(轮询法的局限)
-
- 1. 轮询法代码实现
- 2. 轮询法的致命缺陷
- 四、PWM原理与呼吸灯实战
-
- 1. PWM核心概念
- 2. 呼吸灯功能需求
- 3. STM32CubeMX配置(TIM3+TIM4)
-
- (1)TIM3_CH1(PA6)配置
- (2)TIM4_CH2(PB7)配置
- 4. 呼吸灯代码实现
- 5. 实现原理
- 五、定时器高级应用与常见问题排查
-
- 1. 高级应用场景
- 2. 常见问题与解决方案
- 六、总结与拓展
- 七、效果展示
- 八、心得体会
一、定时器核心原理与分类
STM32的定时器是嵌入式开发中实现精准定时、脉冲计数、PWM输出的核心外设,其本质是可编程的计数器,通过预设时钟频率和计数规则实现多样化功能。
1. 定时器分类(以STM32F103为例)
| 类型 | 代表定时器 | 核心特点 | 典型应用场景 |
|---|---|---|---|
| 基本定时器 | TIM6、TIM7 | 仅支持向上计数,无外部引脚,常用于触发DAC或作为时基 | 定时中断、DAC触发 |
| 通用定时器 | TIM2-TIM5 | 支持向上/向下计数,带4个捕获/比较通道,可输出PWM、测量脉冲宽度 | 多任务并发、PWM输出、脉冲计数 |
| 高级定时器 | TIM1、TIM8 | 具备通用定时器全部功能,支持互补PWM输出(带死区控制),适合电机驱动 | 电机控制、三相逆变 |
本实战重点使用通用定时器(TIM2-TIM5),兼顾定时中断和PWM功能。
2. 定时原理与关键参数
定时器的核心是“时钟源→预分频器→计数器→自动重装寄存器”的信号链路,关键参数:
- 时钟源(CK_PSC):定时器的输入时钟,F103通用定时器默认挂载APB1(最大36MHz),若APB1预分频为1,则定时器时钟=APB1时钟;否则=APB1时钟×2(本文配置为72MHz)。
- 预分频器(PSC):将时钟源分频,
分频后频率 = 时钟源 / (PSC+1)(PSC范围0~65535)。 - 自动重装值(ARR):计数器计数到该值后复位(溢出),
溢出周期 = (PSC+1)×(ARR+1) / 时钟源频率。 - 计数模式:向上计数(从0到ARR)、向下计数(从ARR到0)、中央对齐(上下往返)。
二、定时器中断实现多任务并发(实战案例)
传统轮询方式通过HAL_Delay()阻塞CPU,无法同时处理多个定时任务,而定时器中断可实现“无阻塞并发”,大幅提升系统效率。
1. 功能需求
- 任务1:2秒定时翻转LED(PA0引脚外接LED)
- 任务2:5秒定时通过USART1发送“hello windows!”
- 两个任务独立运行,互不干扰
2. 硬件准备
- 主控:STM32F103C8T6最小系统板
- 外设:外接LED(串联220Ω电阻)、USB转TTL模块(用于串口通信)
- 工具:ST-Link下载器、Keil MDK、STM32CubeMX
3. STM32CubeMX详细配置
(1)基础配置
- 芯片选型:STM32F103C8T6
- 时钟配置:HSE(外部晶振)→ PLL倍频至72MHz → APB1=36MHz,APB2=72MHz(定时器时钟=72MHz)
- LED引脚:PA0配置为GPIO_Output(推挽输出,初始高电平)
- 串口配置:USART1(PA9=TX,PA10=RX),波特率115200,无校验位
(2)定时器配置(TIM2+TIM3)
TIM2(2秒定时):
- 模式:Internal Clock(内部时钟)
- 参数计算:
目标定时2秒 = 2000ms,时钟源72MHz
设分频后频率=10kHz(计数精度0.1ms),则PSC=72000000/10000 -1=7199
计数次数=2000ms / 0.1ms=20000 →ARR=20000-1=19999 - 中断配置:NVIC Settings中勾选“TIM2 global interrupt”,抢占优先级1(高于主循环)
TIM3(5秒定时):
- 模式与分频:同TIM2(PSC=7199,分频后10kHz)
- 参数计算:5秒=5000ms → 计数次数=5000/0.1=50000 →
ARR=50000-1=49999 - 中断配置:勾选“TIM3 global interrupt”,抢占优先级1
4. 代码实现与解析
(1)定时器中断回调函数
重写HAL_TIM_PeriodElapsedCallback函数(定时器溢出时自动调用):
/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
// 判断中断来源(哪个定时器)
if (htim->Instance == TIM2) {
// 任务1:2秒翻转LED
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
} else if (htim->Instance == TIM3) {
// 任务2:5秒发送串口数据
uint8_t send_data[] = "hello windows!\r\n";
// 发送数据(超时时间0xFFFF表示无限等待)
HAL_UART_Transmit(&huart1, send_data, sizeof(send_data)-1, 0xFFFF);
}
}
/* USER CODE END 4 */
(2)主函数初始化
int main(void) {
// 初始化HAL库、系统时钟、外设
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();
MX_TIM3_Init();
// 启动定时器中断(关键步骤)
HAL_TIM_Base_Start_IT(&htim2); // 启动TIM2中断
HAL_TIM_Base_Start_IT(&htim3); // 启动TIM3中断
while (1) {
// 主循环可执行其他任务(如按键检测),不被定时任务阻塞
// 示例:每100ms闪烁一次板载LED(PC13)
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
HAL_Delay(100);
}
}
5. 实现效果
- 外接LED(PA0)每2秒翻转一次(亮→灭→亮)
- 串口每5秒输出一次“hello windows!”
- 板载LED(PC13)每100ms快速闪烁,不受前两个任务影响
核心优势:三个任务并发执行,定时器中断优先级高于主循环,确保定时精准。
三、非定时器方案对比(轮询法的局限)
若不使用定时器中断,可通过HAL_GetTick()(系统滴答定时器的时间戳)实现伪并发,但存在明显缺陷。
1. 轮询法代码实现
int main(void) {
uint32_t led2_prev = 0; // 2秒任务的上一次执行时间
uint32_t uart5_prev = 0; // 5秒任务的上一次执行时间
uint32_t current_time; // 当前时间戳
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
while (1) {
current_time = HAL_GetTick(); // 获取当前毫秒级时间戳
// 任务1:2秒翻转LED
if (current_time - led2_prev >= 2000) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
led2_prev = current_time; // 更新时间戳
}
// 任务2:5秒发送串口数据
if (current_time - uart5_prev >= 5000) {
uint8_t send_data[] = "hello windows!\r\n";
HAL_UART_Transmit(&huart1, send_data, sizeof(send_data)-1, 0xFFFF);
uart5_prev = current_time; // 更新时间戳
}
// 主循环其他任务(若耗时过长,会影响定时精度)
// HAL_Delay(500); // 若添加此句,2秒任务会变成约2.5秒
}
}
2. 轮询法的致命缺陷
- 精度依赖主循环速度:若主循环存在耗时操作(如
HAL_Delay(500)),定时误差会累积(2秒任务可能变成2.5秒)。 - CPU利用率低:本质是“循环等待”,CPU在空转中浪费资源。
- 实时性差:高优先级任务(如紧急中断)无法抢占,适合简单场景。
四、PWM原理与呼吸灯实战
PWM(脉冲宽度调制)通过周期性改变“高电平占空比”模拟“模拟电压输出”,是控制LED亮度、电机转速的核心技术。
1. PWM核心概念
- 频率:PWM波形的周期倒数(如1kHz表示每秒1000个周期),LED控制需≥50Hz(避免肉眼闪烁)。
- 占空比:一个周期内高电平的时间占比(0%~100%),占空比越高,LED越亮。
- STM32实现:通用定时器的捕获/比较通道可输出PWM,通过设置“比较值(CCR)”控制占空比:
占空比 = (CCR+1)/(ARR+1)×100%。
2. 呼吸灯功能需求
- 两个LED(PA6外接LED、PB7板载LED)同时实现“呼吸效果”(亮度从暗→亮→暗循环)。
- 呼吸周期2秒(从最暗到最亮1秒,再到最暗1秒)。
3. STM32CubeMX配置(TIM3+TIM4)
(1)TIM3_CH1(PA6)配置
- 定时器模式:PWM Generation CH1(PWM生成通道1)
- 时钟源:Internal Clock,预分频PSC=71(72MHz/(71+1)=1MHz)
- 自动重装值ARR=99(周期=100/1MHz=0.1ms → 频率=10kHz)
- PWM模式:Mode 1(计数器<CCR时输出高电平)
- 初始比较值CCR=0(初始占空比0%,LED熄灭)
(2)TIM4_CH2(PB7)配置
- TIM4默认通道2为PB6,需通过“重映射”将其映射到PB7:
进入TIM4配置→“Channel2”→“Configuration”→“TIM4 Remap”选择“Partial Remap” - 其他参数同TIM3(PSC=71,ARR=99,PWM Mode 1)
4. 呼吸灯代码实现
通过动态修改CCR值(比较值)改变占空比,实现亮度渐变:
// 全局变量
uint8_t pwm_duty = 0; // 占空比(0~99,对应0%~99%)
uint8_t direction = 1; // 变化方向(1:递增,0:递减)
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM3_Init();
MX_TIM4_Init();
// 启动PWM输出
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // TIM3_CH1(PA6)
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_2); // TIM4_CH2(PB7)
while (1) {
// 占空比从0→99→0循环变化
if (direction) {
pwm_duty++;
if (pwm_duty >= 99) direction = 0; // 达到最大占空比,反向
} else {
pwm_duty--;
if (pwm_duty <= 0) direction = 1; // 达到最小占空比,反向
}
// 更新两个通道的PWM占空比
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, pwm_duty);
__HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_2, pwm_duty);
HAL_Delay(10); // 控制变化速度(10ms×100步=1秒完成一次渐变)
}
}
5. 实现原理
- 定时器频率10kHz(周期0.1ms),人眼无法察觉闪烁;
pwm_duty从0递增到99(占空比0%→99%),LED逐渐变亮;- 再从99递减到0,LED逐渐变暗,形成“呼吸”效果;
HAL_Delay(10)控制每步间隔,确保2秒完成一个周期。
五、定时器高级应用与常见问题排查
1. 高级应用场景
- 输入捕获:用TIM2_CH1测量外部脉冲的周期(如超声波传感器的回响信号)。
- 编码器接口:通过TIM3的编码器模式读取电机转速(A、B相脉冲)。
- 互补PWM:用高级定时器TIM1输出带死区的互补PWM,驱动H桥电机。
2. 常见问题与解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 定时时间不准 | 时钟树配置错误(APB1分频错误) | 重新计算APB1时钟,确保定时器时钟频率正确 |
| 中断不触发 | 未启动定时器中断(未调用Start_IT) | 添加HAL_TIM_Base_Start_IT(&htimx) |
| PWM无输出 | 未启动PWM(未调用Start_PWM) | 添加HAL_TIM_PWM_Start(&htimx, TIM_CHANNEL_x) |
| 呼吸灯闪烁(非渐变) | PWM频率过低(<50Hz) | 减小ARR值(如ARR=99,频率10kHz) |
六、总结与拓展
本文系统讲解了STM32定时器的核心应用:
- 定时器中断:通过TIM2和TIM3实现多任务并发,解决了轮询法的阻塞问题,适合需要精准定时的场景;
- PWM输出:利用TIM3和TIM4的PWM功能实现呼吸灯,通过动态调整占空比模拟亮度变化,掌握了
__HAL_TIM_SET_COMPARE函数的用法。
拓展方向:
- 尝试用定时器输入捕获测量按键按下时间;
- 结合FreeRTOS,用定时器作为任务调度器的时基;
- 实现PWM占空比的串口调节(通过上位机发送指令修改亮度)。
七、效果展示
串口
串口plus
实物
小灯plus
八、心得体会
这次 STM32 定时器实战让我深刻体会到 “原理先行” 的重要性。起初配置 2 秒定时时,直接照搬网上的 PSC 和 ARR 参数,结果定时误差高达几百毫秒,直到回头梳理 “时钟源→预分频器→计数器” 的链路,才发现是误将 APB1 时钟当成了定时器时钟,忽略了 “APB1 分频非 1 时定时器时钟翻倍” 的规则。重新计算后(72MHz 时钟源、PSC=7199、ARR=19999),定时精度瞬间达标。这让我明白,嵌入式开发中任何参数配置都不是 “凭经验”,而是基于底层原理的推导,只有把定时器的计数逻辑、时钟链路摸透,才能在多任务并发等场景中实现精准控制,而非盲目试错。
定时器中断与轮询法的对比实践,让我学会了 “根据场景选方案”。轮询法虽然代码简单,但主循环中的耗时操作会直接导致定时不准,比如添加 500ms 延时后,2 秒定时变成了 2.5 秒;而定时器中断完全摆脱了主循环的束缚,即使主循环执行其他任务,2 秒 LED 翻转和 5 秒串口发送依然精准无误,CPU 利用率也大幅提升。后续的 PWM 呼吸灯实战更让我感受到定时器的灵活性 —— 通过动态修改比较值就能实现亮度渐变,无需复杂的模拟电路。这让我意识到,嵌入式开发的核心不是 “实现功能”,而是 “优化实现方式”:简单场景用轮询,精准并发用中断,模拟输出用 PWM,选对方案才能让系统既稳定又高效。
定时器开发的调试过程,让我收获了 “从现象倒推原因” 的排查思维。比如最初配置 PWM 时,PA6 引脚始终无输出,反复检查代码后发现是漏调用了HAL_TIM_PWM_Start函数;而呼吸灯出现闪烁而非渐变,是因为 PWM 频率设置过低(仅 50Hz),调整 ARR 值将频率提升到 1kHz 后问题解决。还有中断不触发的问题,排查后发现是 NVIC 优先级配置冲突,调整抢占优先级后恢复正常。这些经历让我明白,嵌入式开发的细节里藏着很多 “隐形坑”,一个函数的遗漏、一个参数的错误、一次优先级的冲突,都会导致功能失效。调试时不能急于求成,而要按照 “硬件连接→配置参数→代码逻辑→中断优先级” 的顺序逐步排查,凭借耐心和逻辑缩小问题范围,才能最终找到根源。
更多推荐



所有评论(0)