4、Tick中断服务编程:SysTick_Handler编写、中断优先级设置、临界区保护
好,咱们接着聊。上一节我们把SysTick定时器初始化好了,让它开始滴答滴答地跑。但光跑还不够,你得告诉CPU,每次滴答一下,该干点啥。这就是Tick中断服务函数——SysTick_Handler的活儿。
4.1 SysTick_Handler:心脏的每一次跳动
说白了,这个函数就是系统的心跳。每次SysTick计数到0,触发中断,CPU就跳进来执行这里面的代码。我个人习惯,这个函数里只放最核心、最紧急的事。
void SysTick_Handler(void)
{
// 1. 系统节拍计数器加1
uwTick++;
// 2. 检查并执行软件定时器回调
// 这里通常由HAL库或RTOS接管
}
你看,代码就这么简单。但简单背后有门道。uwTick这个变量,是整个系统的时间基准。我在项目中遇到过,有人在这个函数里塞了串口打印、LED闪烁,甚至做了复杂运算。结果呢?系统响应变慢,任务调度乱套。
4.2 中断优先级设置:别让心跳被堵住
为什么会强调优先级?你想想看,如果系统正在处理一个紧急的外部中断,比如电机堵转保护,这时候SysTick中断来了,你是先处理心跳,还是先保护电机?
嗯,这里要注意。SysTick中断的优先级,通常设置为中等或较低。因为它的工作是维持系统节拍,偶尔延迟一两个节拍,系统还能撑住。但电机保护延迟了,可能就烧了。
设置方法很简单,以Cortex-M3/M4为例:
// 设置SysTick中断优先级为最低(数值越大优先级越低)
// 注意:NVIC优先级分组要先配置好
NVIC_SetPriority(SysTick_IRQn, 0x0F); // 假设分组为4位抢占优先级
我曾经在一个项目中,把SysTick优先级设得比所有中断都高。结果一个高频率的外部中断频繁触发,SysTick每次都抢着执行,导致外部中断响应延迟,数据采集丢包。调试了整整两天才找到原因。
4.3 临界区保护:别让数据乱掉
好,现在有个新问题。uwTick这个变量,在中断里被修改。如果主循环也在读它,会发生什么?
举个例子:主循环里判断if(uwTick > 1000),刚读到高8位,中断来了,uwTick从0x00FF变成了0x0100。中断返回后,继续读低8位。结果读到的值是0x0000?还是0x01FF?
这就是典型的“数据不一致”问题。解决它,就需要临界区保护。
临界区,说白了就是一段“不允许被打断”的代码。在单核MCU上,最简单的办法就是——关中断。
// 进入临界区
__disable_irq();
// 读取或修改共享变量
uint32_t local_tick = uwTick;
// 退出临界区
__enable_irq();
但这里有个坑。如果你在中断里也调用了这个保护,那__disable_irq()会把所有中断都关了,包括它自己。这就死锁了。
正确的做法是:
- 在中断服务函数中:不需要临界区保护。因为中断本身已经禁止了同级或低优先级中断的嵌套。
- 在主循环或任务中:访问共享变量时,必须进入临界区。
更专业的做法,是用RTOS提供的临界区API,或者用Cortex-M的LDREX/STREX指令实现无锁访问。但对于简单的节拍计数器,关中断就够了。
4.4 实战:一个完整的Tick中断示例
来,我把前面讲的串起来,给你一个完整的示例。这个例子在STM32上验证过,你可以直接移植。
/* 系统节拍计数器 */
static volatile uint32_t uwTick = 0;
/* SysTick中断服务函数 */
void SysTick_Handler(void)
{
uwTick++;
}
/* 获取当前节拍值(带临界区保护) */
uint32_t GetTick(void)
{
uint32_t tick;
__disable_irq(); // 进入临界区
tick = uwTick; // 原子读取
__enable_irq(); // 退出临界区
return tick;
}
/* 毫秒延时函数(基于SysTick) */
void DelayMs(uint32_t delay)
{
uint32_t start = GetTick();
while((GetTick() - start) < delay)
{
// 等待,可在此处加入低功耗模式
}
}
你看,GetTick()函数加了临界区保护,保证了读取的原子性。DelayMs()里每次循环都调用GetTick(),虽然效率不高,但胜在安全可靠。
vTaskDelay()和xTaskGetTickCount()已经帮你处理好了临界区。直接用就行,别自己再造轮子。
4.5 总结一下
嗯,这一节内容不少,但核心就三点:
- SysTick_Handler要精简——只做节拍计数和必要的系统维护。
- 中断优先级要合理——SysTick优先级别太高,给更紧急的中断让路。
- 临界区保护要到位——访问共享变量时,记得关中断,但别在中断里关中断。
这些经验,都是我一个个坑踩出来的。你照着做,能少走很多弯路。下一节,咱们聊聊如何用SysTick实现软件定时器,那才是真正实用的东西。