三、定时器的 3 大核心应用场景解析
场景 1:替代 Delay 的精准计时(非阻塞延时)
你是否遇到过用Delay_ms()
时,程序卡住无法响应按键的情况?这是因为Delay_ms()
是阻塞式延时 ,期间 CPU 啥也不能干。而定时器中断能实现非阻塞延时,让 CPU 在等待时去处理其他任务(比如扫描按键)。
实现原理:
- 让定时器每 1ms 触发一次中断,用全局变量
tick
记录时间(类似秒表)。 - 需要延时 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; // 未捕获到有效信号
}
应用场景:
- 测量红外遥控器信号频率。
- 检测电机编码器脉冲,计算转速。
四、定时器常见问题与避坑指南
- 为什么 PWM 没波形?
- 检查引脚是否复用正确(如 PA6 对应 TIM3_CH1)。
- 确认定时器时钟已使能(
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
)。
- 输入捕获数值乱跳?
- 增加滤波参数(
TIM_ICFilter
),过滤高频噪声。 - 确保信号幅度足够(接近 3.3V,避免电平不稳)。
- 增加滤波参数(
- 延时不准?
- 检查预分频值(PSC)和自动重装值(ARR)计算是否正确。
- 避免在中断函数中执行复杂操作,减少中断耗时。
五、从定时器到复杂系统的进阶思路
学会定时器后,可尝试以下组合玩法:
- 多定时器协同:用 TIM2 做延时,TIM3 输出 PWM,实现 "按键调节 LED 亮度 + 定时熄灭"。
- 定时器 + DMA:ADC 采集数据时,用定时器触发 DMA 传输,实现无 CPU 参与的高频采样。
- 高级功能探索:用 TIM1 的死区控制驱动电机,防止短路(需配合 H 桥电路)。
下一篇预告:《STM32 通信实战:USART 串口与蓝牙模块的无缝对接》,教你用手机 APP 控制 STM32 设备
互动思考:你想用定时器做一个什么有趣的项目?比如定时浇花、智能闹钟?留言分享你的创意! ⏰