定时器中断功能主要是两点:
1.怎么配置的定时器中断时间间隔;
2.中断里长什么样
一、定时器中断配置函数
直接在bsp_basic_timer.c里找到下面函数:
void basic_timer_config(uint16_t pre,uint16_t per)
{/* Ò»¸öÖÜÆÚµÄʱ¼äT = 1/f, ¶¨Ê±Ê±¼ätime = T * ÖÜÆÚÉèÔ¤·ÖƵֵλpre,ÖÜÆÚλpertime = (pre + 1) * (per + 1) / psc_clk*/timer_parameter_struct timere_initpara; // ¶¨Ò嶨ʱÆ÷½á¹¹Ìå/* ¿ªÆôʱÖÓ */rcu_periph_clock_enable(BSP_TIMER_RCU); // ¿ªÆô¶¨Ê±Æ÷ʱÖÓ/* CK_TIMERx = 4 x CK_APB1 = 4x50M = 200MHZ */rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4); // ÅäÖö¨Ê±Æ÷ʱÖÓtimer_deinit(BSP_TIMER); // ¸´Î»¶¨Ê±Æ÷/* ÅäÖö¨Ê±Æ÷²ÎÊý */timere_initpara.prescaler = pre-1; // ʱÖÓÔ¤·ÖƵֵ 0-65535 psc_clk = CK_TIMER / pretimere_initpara.alignedmode = TIMER_COUNTER_EDGE; // ±ßÔµ¶ÔÆë timere_initpara.counterdirection = TIMER_COUNTER_UP; // ÏòÉϼÆÊý timere_initpara.period = per-1; // ÖÜÆÚ /* ÔÚÊäÈë²¶»ñµÄʱºòʹÓà Êý×ÖÂ˲¨Æ÷ʹÓõIJÉÑùƵÂÊÖ®¼äµÄ·ÖƵ±ÈÀý */timere_initpara.clockdivision = TIMER_CKDIV_DIV1; // ·ÖƵÒò×Ó /* Ö»Óи߼¶¶¨Ê±Æ÷²ÅÓÐ ÅäÖÃΪx£¬¾ÍÖØ¸´x+1´Î½øÈëÖÐ¶Ï */ timere_initpara.repetitioncounter = 0; // ÖØ¸´¼ÆÊýÆ÷ 0-255 timer_init(BSP_TIMER,&timere_initpara); // ³õʼ»¯¶¨Ê±Æ÷/* ÅäÖÃÖжÏÓÅÏȼ¶ */nvic_irq_enable(BSP_TIMER_IRQ,3,2); // ÉèÖÃÖжÏÓÅÏȼ¶Îª 3,2/* ʹÄÜÖÐ¶Ï */timer_interrupt_enable(BSP_TIMER,TIMER_INT_UP); // ʹÄܸüÐÂʼþÖÐ¶Ï /* ʹÄܶ¨Ê±Æ÷ */timer_enable(BSP_TIMER);
}
定时器中断初始化流程:
启用定时器时钟
- 代码:
rcu_periph_clock_enable(BSP_TIMER_RCU);
- 功能:开启定时器模块的时钟(
BSP_TIMER_RCU
定义了具体定时器外设,如 TIMER0)。 - 说明:通过 RCU(Reset and Clock Unit)模块为定时器提供时钟信号。
- 代码:
配置定时器时钟分频
- 代码:
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
- 功能:设置定时器时钟为 APB1 时钟的 4 倍(4 × 50MHz = 200MHz)。
- 说明:定时器时钟频率(CK_TIMER)决定计数速度,影响定时精度。
- 代码:
复位定时器
- 代码:
timer_deinit(BSP_TIMER);
- 功能:将定时器寄存器复位到默认状态,确保无残留配置。
- 说明:清除之前的配置,为新设置做准备。
- 代码:
配置定时器参数
- 代码:
timer_parameter_struct timere_initpara; timere_initpara.prescaler = pre-1; // 预分频值
timere_initpara.alignedmode = TIMER_COUNTER_EDGE; // 边沿对齐 timere_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数 timere_initpara.period = per-1; // 周期 timere_initpara.clockdivision = TIMER_CKDIV_DIV1; // 时钟分频因子 timere_initpara.repetitioncounter = 0; // 重复计数器 timer_init(BSP_TIMER, &timere_initpara);
- 功能:
- 定义并初始化定时器参数结构体
timer_parameter_struct
。 - 设置预分频值 (
pre-1
),定时器计数频率 = CK_TIMER / (pre)。 - 配置为边沿对齐模式(
TIMER_COUNTER_EDGE
)和向上计数(TIMER_COUNTER_UP
)。 - 设置周期值 (
per-1
),决定定时器溢出周期,定时时间 = (pre × per) / CK_TIMER。 - 时钟分频因子为 1(
TIMER_CKDIV_DIV1
),不额外分频。 - 重复计数器为 0(
repetitioncounter = 0
),无重复计数,适用于基本定时器。 - 调用
timer_init
初始化定时器寄存器。
- 定义并初始化定时器参数结构体
- 说明:定时时间计算公式为
time = (pre × per) / CK_TIMER
,例如,若pre=20000
,per=10000
, CK_TIMER=200MHz,则time = (20000 × 10000) / 200,000,000 = 1秒
。
- 代码:
配置中断优先级
- 代码:
nvic_irq_enable(BSP_TIMER_IRQ, 3, 2);
- 功能:启用定时器中断(
BSP_TIMER_IRQ
),设置 NVIC 中断优先级为抢占优先级 3,子优先级 2。 - 说明:确保定时器中断能够被 CPU 响应,优先级决定中断处理顺序。
- 代码:
使能定时器更新中断
- 代码:
timer_interrupt_enable(BSP_TIMER, TIMER_INT_UP);
- 功能:启用定时器更新事件中断(
TIMER_INT_UP
),当计数器溢出时触发中断。 - 说明:更新事件发生在计数器达到
per-1
并溢出时,触发中断处理程序。
- 代码:
启动定时器
- 代码:
timer_enable(BSP_TIMER);
- 功能:使能定时器,开始计数。
- 说明:定时器按照配置的频率和周期运行,定期触发中断。
- 代码:
二、中断函数
同样在bsp_basic_timer.c中的中断函数如下:
void BSP_TIMER_IRQHANDLER(void)
{/* ÕâÀïÊǶ¨Ê±Æ÷ÖÐ¶Ï */if(timer_interrupt_flag_get(BSP_TIMER,TIMER_INT_FLAG_UP) == SET){timer_interrupt_flag_clear(BSP_TIMER,TIMER_INT_FLAG_UP); // Çå³ýÖжϱê־λ /* Ö´Ðй¦ÄÜ */gpio_bit_toggle(PORT_LED2,PIN_LED2); // ·×ªledprintf("BSP_TIMER_IRQHANDLER!\r\n"); // ´®¿Ú´òÓ¡BSP_TIMER_IRQHANDLER!}
}
定时器中断函数在执行功能前主要进行了两个操作:
检查中断标志
- 代码:
if(timer_interrupt_flag_get(BSP_TIMER, TIMER_INT_FLAG_UP) == SET)
- 功能:检查定时器
BSP_TIMER
的更新中断标志位(TIMER_INT_FLAG_UP
)是否置位(SET
)。 - 说明:
- 更新中断标志在定时器计数器溢出时自动置位,表示一次定时周期完成。
- 条件判断确保只处理更新中断,避免误处理其他中断类型。
BSP_TIMER
是预定义的定时器,此处对应的是TIMER5,由历史会话中的basic_timer_config
配置。
- 代码:
清除中断标志
- 代码:
timer_interrupt_flag_clear(BSP_TIMER, TIMER_INT_FLAG_UP);
- 功能:清除定时器的更新中断标志位。
- 说明:
- 清除标志位以确保下一次中断能够正确触发。
- 如果不清除,中断可能持续触发,导致程序异常或死锁。
- 这是中断服务函数的标准操作,防止重复进入中断。
- 代码:
三、如何调用
在main.c里面只需要配置一下 basic_timer_config函数就行,调用代码如下:
basic_timer_config(20000,10000); // ¶¨Ê±Æ÷³õʼ»¯
- 定时时间公式:
time = (pre × per) / CK_TIMER
- 代入参数
pre = 20000 (实际预分频系数 19999 + 1)
per = 10000 (实际周期 9999 + 1)
CK_TIMER = 200,000,000 Hz
time = (20000 × 10000) / 200,000,000 = 200,000,000 / 200,000,000 = 1 秒
运行后可以看到串口一直在输出:
并且开发板上对应的LED2在闪烁
四、思考
为什么常常使用ADC时不直接放在 while(1)
主循环中使用 delay
控制采样间隔?
不是不行,也可以,但是:
延时不精确,受主循环影响
- 在主循环中使用
delay_ms(500)
等软件延时函数时,延时基于 CPU 空闲循环(如空指令循环)实现,但实际时间会因编译优化、系统时钟偏差或中断干扰而抖动(例如,串口中断或 GPIO 操作可能打断延时,导致采样间隔不均匀)。 - 问题:采样间隔(如每 500ms 采样一次)无法严格控制,可能导致数据采集不均匀,影响信号处理精度(如历史会话中提到的 ADC 采样)。
- 示例:若主循环中插入其他任务(如 LED 控制),延时函数会阻塞整个 CPU,造成采样时机漂移。
- 在主循环中使用
阻塞主循环,降低系统响应性
delay
是“忙等待”(busy-waiting),CPU 在延时期间完全闲置,无法处理其他任务(如用户输入、通信或外设事件)。- 问题:在多任务环境中(如实时控制系统),主循环被阻塞会导致系统响应迟钝,甚至错过关键事件。例如,定时器中断每 1 秒触发 LED 翻转,若也需要ADC 采样用
delay
阻塞 500ms,主循环就无法及时响应其他中断。 - 效率低:CPU 利用率低下,浪费资源,尤其在低功耗应用中不友好。
不适合实时性和周期性要求
- ADC 采样往往需要固定间隔(如传感器数据采集),主循环的软件延时无法保证硬件级精度,受温度、电压等因素影响。
- 问题:在高速采样场景(如音频或电机控制),间隔抖动可能导致数据失真或系统不稳定。若 ADC 采样直接用
delay
,与定时器中断(1 秒间隔)冲突,可能干扰整体流程。
为什么ADC采样常放到定时器中断中?
精确的硬件定时
- 定时器使用硬件计数器(如
basic_timer_config(20000, 10000)
配置 1 秒间隔),基于系统时钟(CK_TIMER = 200MHz)精确控制中断触发时机,间隔误差极小(纳秒级)。 - 优势:不受主循环影响,确保 ADC 采样严格周期性(如每 500ms 触发一次),提高数据一致性和精度。定时器可自动重载,无需软件干预。
- 定时器使用硬件计数器(如
非阻塞执行,提高系统效率
- 中断服务函数(如
BSP_TIMER_IRQHANDLER
)在定时器溢出时自动调用,执行 ADC 采样后快速返回,主循环继续运行其他任务。 - 优势:避免阻塞,支持多任务并行。例如,历史会话中定时器中断可用于 ADC 触发采样,同时主循环处理串口输出或 LED 控制,CPU 利用率高。结合 DMA(直接内存访问)可进一步自动化数据传输,减少 CPU 负担。
- 中断服务函数(如
增强实时性和可扩展性
- 中断优先级机制(如 NVIC 设置抢占优先级 3)确保 ADC 采样及时响应,适用于实时系统(如工业控制)。
- 优势:易于调整间隔(修改
pre
和per
参数),并支持多通道采样(如定时器触发 ADC 多通道)。1 秒中断可扩展为 500ms ADC 采样,结合timer_interrupt_enable(BSP_TIMER, TIMER_INT_UP)
实现周期触发。