4. 定时器中断实现精确延时
任务说明
本案例要求在ARM嵌入式平台上,利用硬件定时器产生周期性中断,在中断服务程序中维护一个软件计数器,并基于该计数器实现毫秒级延时函数。与软件空循环延时不同,定时器中断延时不受CPU主频波动和编译器优化影响,精度由定时器时钟源决定。
硬件定时器配置
以STM32F407为例,使用TIM4(挂载在APB1总线,定时器时钟84MHz)。配置TIM4工作在向上计数模式,自动重装载值ARR=8399,预分频器PSC=99,则定时器溢出频率为:84MHz / (99+1) / (8399+1) = 100Hz,即每10ms产生一次更新中断。在中断服务函数中递增一个全局变量timer_tick,延时函数通过查询该变量的变化量实现毫秒级延时。
代码实现
#include "stm32f4xx.h"
/* 全局软件计数器,在中断中递增 */
volatile uint32_t timer_tick = 0;
/* TIM4初始化:PSC=99, ARR=8399 → 10ms中断一次 */
void TIM4_Init(void) {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
TIM_TimeBaseInitTypeDef TIM_InitStruct;
TIM_InitStruct.TIM_Prescaler = 99; /* 84MHz/(99+1) = 840kHz */
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_InitStruct.TIM_Period = 8399; /* 840kHz/(8399+1) = 100Hz */
TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM4, &TIM_InitStruct);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM4, ENABLE);
}
/* TIM4中断服务函数:每10ms进入一次 */
void TIM4_IRQHandler(void) {
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) {
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
timer_tick++;
}
}
/* 毫秒级延时函数:基于timer_tick计数差值 */
void delay_ms(uint32_t ms) {
uint32_t start = timer_tick;
/* 每10ms一个tick,计算需要等待的tick数 */
uint32_t ticks_needed = (ms + 9) / 10; /* 向上取整 */
while ((timer_tick - start) < ticks_needed) {
/* 等待中断递增timer_tick */
}
}
/* 简单测试:LED闪烁周期500ms */
int main(void) {
/* 初始化LED引脚(PA6) */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
TIM4_Init();
while (1) {
GPIO_SetBits(GPIOA, GPIO_Pin_6);
delay_ms(500);
GPIO_ResetBits(GPIOA, GPIO_Pin_6);
delay_ms(500);
}
}
代码说明
中断服务函数TIM4_IRQHandler每10ms将timer_tick加1。delay_ms函数记录进入时的timer_tick值,然后循环等待差值达到所需tick数。由于timer_tick为无符号32位变量,即使发生回绕,差值运算仍能正确计算经过的tick数。延时精度为±10ms(一个中断周期),若要更高精度可调整PSC和ARR使中断频率更高(如1kHz),但会增加中断负载。
验证方法
将代码编译烧录至开发板,观察LED闪烁周期。使用秒表或示波器测量LED高电平持续时间,若实测周期为500ms±10ms,则说明定时器中断延时功能正常。也可在delay_ms(1000)前后翻转另一个GPIO,用示波器测量两次翻转间隔来验证1秒延时精度。
验证判定标准
将上述代码编译后烧录至开发板,用示波器探头连接PA6引脚。若观察到方波周期为1000ms(高电平500ms、低电平500ms),且误差在±10ms以内,则判定案例通过。若LED闪烁明显偏快或偏慢,应检查TIM4时钟配置是否正确(确认APB1定时器时钟是否为84MHz),以及中断优先级是否被其他中断阻塞。