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秒延时精度。

定时器中断延时验证时序 t (ms) TIM4中断 tick=1 2 3 4 5 6 7 8 9 10 0 10 20 30 40 50 60 70 80 90 100 LED输出 高电平 500ms 低电平 500ms delay_ms(500) 调用 delay_ms(500) 调用 判定标准:LED周期 = 1000ms ± 10ms(50个tick)

验证判定标准

将上述代码编译后烧录至开发板,用示波器探头连接PA6引脚。若观察到方波周期为1000ms(高电平500ms、低电平500ms),且误差在±10ms以内,则判定案例通过。若LED闪烁明显偏快或偏慢,应检查TIM4时钟配置是否正确(确认APB1定时器时钟是否为84MHz),以及中断优先级是否被其他中断阻塞。