如果中断使用delay函数:
中断里的
HAL_Delay()要等系统时基 tick 增加;而这个 tick 往往依赖 SysTick 中断。当前中断没退出时,SysTick 可能进不来,于是HAL_Delay()一直等,程序就卡住了。
这更像是:
中断里阻塞等待,结果把给它计时的人也堵住了。
所以应该怎么办?
中断里只做短、小、快的事
不要在中断里做:
HAL_Delay()- 长时间循环
- 复杂计算
- 串口长打印
- 大量业务逻辑
那业务逻辑写在哪里?
一般分两层:
1)中断里:只做"事件登记"
比如:
- 置一个标志位
- 记录当前时间
- 记录哪个按键来了
- 极简单地翻一个状态
中断里适合做的是这种"快进快出"的事。
2)主循环 while(1) 里:做真正业务处理
比如:
- 消抖
- 判断长按短按
- 切换 LED
- 刷界面
- 发串口
- 状态机处理
所以大部分情况下,真正业务逻辑更推荐写在 main 的 while(1) 里。
最推荐的模式
这种按键外部中断,标准思路是:
中断里
只告诉主循环:
"有一个按键事件来了。"
比如:
c
volatile uint8_t key6_event = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_5)
{
key6_event = 1;
}
}
主循环里
再真正处理:
c
while (1)
{
if (key6_event == 1)
{
key6_event = 0;
HAL_Delay(20); // 消抖放这里,不放中断里
if (HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_5) == GPIO_PIN_RESET)
{
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);
}
}
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_9);
HAL_Delay(500);
}
那中断里能不能直接写业务?
可以,但要满足一个条件:
这个业务必须非常短、非常快、不阻塞。
比如这种就可以:
c
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_5)
{
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);
}
}
因为它只是翻转一个引脚,动作非常快。
所以到底怎么选?
情况1:逻辑特别简单
比如:
- 按一下翻转一个 LED
可以直接写在中断里。
情况2:逻辑稍微复杂
比如:
- 要消抖
- 要判断长按短按
- 要做延时
- 要串口输出
- 要修改多个状态
就不要全塞中断里,应该:
- 中断里置标志
- 主循环里处理业务
可以记一个经验法则
中断里做:
"通知"
while(1) 里做:
"处理"
为什么主循环更适合做业务?
因为主循环里:
- 可以安全用
HAL_Delay - 可以慢慢判断状态
- 不会长时间占住中断
- 不容易影响别的外设响应
而中断里如果写太多,会带来这些问题:
- 卡住别的中断
- 响应变差
- 调试困难
- 出现这次这种"看起来死锁"的现象
例子1
c
volatile uint8_t key6_pressed = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_5)
{
key6_pressed = 1;
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while (1)
{
if (key6_pressed == 1)
{
key6_pressed = 0;
HAL_Delay(20); // 消抖放主循环
if (HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_5) == GPIO_PIN_RESET)
{
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);
}
}
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_9);
HAL_Delay(500);
}
}
小结
中断里不做阻塞延时,不做重业务;中断里只置标志,主循环里处理业务。