STM32 定时器应用:从精准延时到智能控制的实战指南

三、定时器的 3 大核心应用场景解析

场景 1:替代 Delay 的精准计时(非阻塞延时)

你是否遇到过用Delay_ms()时,程序卡住无法响应按键的情况?这是因为Delay_ms()阻塞式延时 ,期间 CPU 啥也不能干。而定时器中断能实现非阻塞延时,让 CPU 在等待时去处理其他任务(比如扫描按键)。

实现原理

  1. 让定时器每 1ms 触发一次中断,用全局变量tick记录时间(类似秒表)。
  2. 需要延时 100ms 时,只需判断tick的差值是否达到 100,无需死等。

代码示例(TIM2 中断实现 1ms 计时):

cpp 复制代码
volatile uint32_t tick = 0; // 全局时间戳(volatile防止被编译器优化)

// 定时器初始化(1ms中断)
void TIM2_Init() {
  // 配置时钟分频:72MHz ÷ (71+1) = 1MHz(1μs/计数)
  TIM_TimeBaseInitTypeDef timer;
  timer.TIM_Prescaler = 71; 
  timer.TIM_Period = 999; // 计满1000次=1ms
  TIM_TimeBaseInit(TIM2, &timer);
  
  // 开启中断
  TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
  NVIC_EnableIRQ(TIM2_IRQn);
  TIM_Cmd(TIM2, ENABLE); // 启动定时器
}

// 中断函数:每1ms触发一次,tick自增
void TIM2_IRQHandler() {
  if (TIM_GetITStatus(TIM2, TIM_IT_Update)) {
    tick++;
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除中断标志
  }
}

// 自定义非阻塞延时函数
void Delay_MS(uint32_t ms) {
  uint32_t start = tick; // 记录开始时间
  while ((tick - start) < ms); // 循环直到时间差达到ms
}

应用场景

  • 按键消抖:在延时期间检测按键是否持续按下,避免误触发。
  • 多任务调度:配合 RTOS,实现任务按优先级分时执行。

场景 2:PWM 调光与电机调速(呼吸灯 / 风扇控制)

PWM(脉冲宽度调制)是定时器的 "招牌功能",能通过调节波形的 "占空比" 实现模拟量控制。比如:

  • 呼吸灯:通过改变 LED 亮灭的时间比例,让人眼感知亮度渐变。
  • 电机调速:占空比越大,电机转速越快(需配合驱动电路,如 L298N)。

关键参数

  • 周期:决定波形频率(如 20ms 周期适用于舵机控制)。
  • 占空比占空比 = 高电平时间 / 周期,范围 0%~100%。

代码示例(TIM3 生成 PWM 控制 LED 亮度):

cpp 复制代码
// 初始化TIM3(PWM模式,周期1ms)
void PWM_Init() {
  // PA6复用为TIM3_CH1(LED接PA6)
  GPIO_InitTypeDef gpio;
  gpio.GPIO_Pin = GPIO_Pin_6;
  gpio.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
  GPIO_Init(GPIOA, &gpio);
  
  // 配置定时器:72MHz ÷ (71+1) = 1MHz,周期1ms(ARR=999)
  TIM_TimeBaseInitTypeDef timer;
  timer.TIM_Prescaler = 71;
  timer.TIM_Period = 999;
  TIM_TimeBaseInit(TIM3, &timer);
  
  // 配置PWM模式1,初始占空比0%(CCR=0)
  TIM_OCInitTypeDef oc;
  oc.TIM_OCMode = TIM_OCMode_PWM1;
  oc.TIM_OutputState = TIM_OutputState_Enable;
  oc.TIM_Pulse = 0; // 高电平时间0μs(全灭)
  TIM_OC1Init(TIM3, &oc);
  
  TIM_Cmd(TIM3, ENABLE); // 启动定时器
}

// 主函数:呼吸灯效果(占空比0%→100%→0%循环)
int main() {
  PWM_Init();
  uint16_t duty = 0; // 占空比(0~999)
  uint8_t dir = 1; // 1=递增,0=递减
  
  while (1) {
    if (dir) duty++; else duty--; // 改变占空比
    if (duty == 999 || duty == 0) dir = !dir; // 到达边界反转方向
    
    // 更新占空比(CCR决定高电平时间)
    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, duty);
    HAL_Delay(5); // 延时5ms,控制渐变速度
  }
}

效果演示

  • 占空比 0% 时 LED 全灭,100% 时全亮,中间值对应不同亮度。
  • 可通过修改HAL_Delay(5)调整呼吸快慢。

场景 3:测量信号频率(输入捕获功能)

想知道传感器输出的方波频率?用定时器的 "输入捕获" 功能!它能记录信号上升沿 / 下降沿的时间差,计算频率。

硬件连接

  • 信号源接 STM32 的定时器输入引脚(如 PA0 对应 TIM2_CH1)。
  • 配置引脚为 "浮空输入" 模式,捕获信号跳变。

代码示例(TIM2 测量频率):

cpp 复制代码
volatile uint32_t capture1 = 0, capture2 = 0; // 两次捕获值
volatile uint8_t capture_ok = 0; // 捕获完成标志

// 初始化TIM2输入捕获(上升沿触发)
void TIM2_Capture_Init() {
  // PA0浮空输入
  GPIO_InitTypeDef gpio;
  gpio.GPIO_Pin = GPIO_Pin_0;
  gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(GPIOA, &gpio);
  
  // 配置输入捕获:捕获TIM2_CH1(PA0)的上升沿
  TIM_ICInitTypeDef ic;
  ic.TIM_Channel = TIM_Channel_1;
  ic.TIM_ICPolarity = TIM_ICPolarity_Rising; // 上升沿触发
  ic.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 不分频,直接计数
  TIM_ICInit(TIM2, &ic);
  
  // 开启捕获中断
  TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);
  NVIC_EnableIRQ(TIM2_IRQn);
  TIM_Cmd(TIM2, ENABLE); // 启动定时器
}

// 中断函数:处理两次捕获事件
void TIM2_IRQHandler() {
  if (TIM_GetITStatus(TIM2, TIM_IT_CC1)) {
    if (!capture_ok) {
      capture1 = TIM_GetCapture1(TIM2); // 第一次捕获时间
      capture_ok = 1;
    } else {
      capture2 = TIM_GetCapture1(TIM2); // 第二次捕获时间
      capture_ok = 0; // 重置标志
    }
    TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); // 清除中断标志
  }
}

// 计算频率函数
uint32_t Get_Frequency() {
  if (capture1 && capture2 && (capture2 > capture1)) {
    uint32_t period = capture2 - capture1; // 周期(计数次数)
    return 72000000 / period; // 频率=时钟频率/周期
  }
  return 0; // 未捕获到有效信号
}

应用场景

  • 测量红外遥控器信号频率。
  • 检测电机编码器脉冲,计算转速。

四、定时器常见问题与避坑指南

  1. 为什么 PWM 没波形?
    • 检查引脚是否复用正确(如 PA6 对应 TIM3_CH1)。
    • 确认定时器时钟已使能(RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);)。
  2. 输入捕获数值乱跳?
    • 增加滤波参数(TIM_ICFilter),过滤高频噪声。
    • 确保信号幅度足够(接近 3.3V,避免电平不稳)。
  3. 延时不准?
    • 检查预分频值(PSC)和自动重装值(ARR)计算是否正确。
    • 避免在中断函数中执行复杂操作,减少中断耗时。

五、从定时器到复杂系统的进阶思路

学会定时器后,可尝试以下组合玩法:

  • 多定时器协同:用 TIM2 做延时,TIM3 输出 PWM,实现 "按键调节 LED 亮度 + 定时熄灭"。
  • 定时器 + DMA:ADC 采集数据时,用定时器触发 DMA 传输,实现无 CPU 参与的高频采样。
  • 高级功能探索:用 TIM1 的死区控制驱动电机,防止短路(需配合 H 桥电路)。

下一篇预告:《STM32 通信实战:USART 串口与蓝牙模块的无缝对接》,教你用手机 APP 控制 STM32 设备

互动思考:你想用定时器做一个什么有趣的项目?比如定时浇花、智能闹钟?留言分享你的创意! ⏰

相关推荐
码小文13 分钟前
MCU、MPU、GPU、Soc、DSP、FPGA、CPLD……它们到底是什么?
笔记·单片机·嵌入式硬件·学习·ic常识
我命由我123451 小时前
STM32 开发 - 中断案例(中断概述、STM32 的中断、NVIC 嵌套向量中断控制器、外部中断配置寄存器组、EXTI 外部中断控制器、实例实操)
c语言·开发语言·c++·stm32·单片机·嵌入式硬件·嵌入式
宋一平工作室2 小时前
单片机队列功能模块的实战和应用
c语言·开发语言·stm32·单片机·嵌入式硬件
SY师弟2 小时前
台湾TEMI协会竞赛——2、足球机器人组装教学
c语言·单片机·嵌入式硬件·机器人·嵌入式·台湾temi协会
挨踢玩家2 小时前
stm32---dma串口发送+fifo队列框架
stm32·单片机·嵌入式硬件
youcans_2 小时前
【EdgeAI实战】(3)边缘AI开发套件 STM32N6570X0 用户手册
stm32·单片机·嵌入式硬件·边缘计算·边缘ai
JXNL@6 小时前
STM32外设学习之串口
stm32·单片机·学习
森旺电子6 小时前
stm32温湿度-超声波-LCD1602结合项目(Proteus仿真程序)
stm32·嵌入式硬件·proteus
woshihonghonga7 小时前
STM32通用定时器TRC含义解析
stm32·单片机·嵌入式硬件
电院工程师9 小时前
ChipWhisperer教程(三)
笔记·python·嵌入式硬件·安全·fpga开发·安全架构