当前位置: 首页 > news >正文

HAL_Delay 为什么不能在中断里调?

前言

很多 STM32 新手写过这样的代码:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { HAL_Delay(50); // 按键消抖 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); }

下载后发现:按键按下去,程序卡死了

为什么HAL_Delay在 main 循环里用得好好的,进中断就出问题?这篇文章彻底讲清楚。


一、HAL_Delay 是怎么实现的

打开stm32f4xx_hal.c,看看HAL_Delay的代码:

__weak void HAL_Delay(uint32_t Delay) { uint32_t tickstart = HAL_GetTick(); uint32_t wait = Delay; ​ /* 防止 tick 计数器溢出导致 HAL_Delay 判定异常 */ if (wait < HAL_MAX_DELAY) { wait += (uint32_t)(uwTickFreq); } ​ while ((HAL_GetTick() - tickstart) < wait) { } }

核心逻辑就一句话:不断读当前 tick,等时间到了才返回

HAL_GetTick()读取的是uwTick这个全局变量:

__weak void HAL_IncTick(void) { uwTick += uwTickFreq; }

这个uwTick谁来更新?答案是:

// stm32f4xx_it.c 中 void SysTick_Handler(void) { HAL_IncTick(); }

SysTick 中断每 1ms 触发一次,uwTick才加 1。


二、中断优先级导致死锁

问题就在这里。

HAL_Delay是一个死等的函数——它在 while 循环里反复读uwTick,直到值达到目标。

uwTick是由SysTick 中断更新的。

如果发生这种情况:

main 中正常运行 ↓ 按键按下了 ← 外部中断触发(EXTI) ↓ CPU 跳进 EXTI 中断服务函数 ↓ 调用 HAL_Delay(50) ← 开始死等 uwTick ↓ SysTick 中断本该在 1ms 后更新 uwTick ↓ 但是!SysTick 的中断优先级 < EXTI 的中断优先级 ↓ SysTick 中断被 EXTI 挡住了,进不去! ↓ uwTick 永远不增加 ↓ HAL_Delay 永远等下去 ↓ 卡死

关键是中断优先级的关系:

中断默认抢占优先级备注
EXTI较低数字(高优先)默认比 SysTick 高(通常为 0 或 5,取决于 CubeMX 配置)
SysTick较大数字(低优先)默认 15

高优先级中断在执行时,同优先级或更低优先级的中断无法打断它。所以当 EXTI 中断里调HAL_Delay,SysTick 中断无法触发,uwTick卡住,死锁。


三、验证方法

在 EXTI 回调中试着直接读HAL_GetTick()

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { uint32_t t1 = HAL_GetTick(); for (volatile int i = 0; i < 10000; i++); // 磨蹭一会 uint32_t t2 = HAL_GetTick(); ​ printf("t1=%lu, t2=%lu, diff=%lu\r\n", t1, t2, t2 - t1); // t1 和 t2 永远相等——SysTick 中断没进来 }

四、解决办法

4.1 不要在中断里死等

这是最根本的解决思路。中断应该快进快出

// 错误:在中断里等 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { HAL_Delay(50); // ❌ 不要这样做 } ​ // 正确:中断里只置标志位 volatile uint8_t key_flag = 0; ​ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { key_flag = 1; // ✅ 标记事件,主循环处理 } ​ // main 循环中 while (1) { if (key_flag) { key_flag = 0; HAL_Delay(20); // 现在在 main 里等,没问题 // ... 处理按键 } }

中断只干两件事:置标志位接收数据。剩下的主循环做。

4.2 提高 SysTick 优先级(不推荐)

把 SysTick 的中断优先级设得比 EXTI 高,这样即使 EXTI 里调HAL_Delay,SysTick 也能打断它更新uwTick

HAL_InitTick(TICK_INT_PRIORITY); // 在 main 中调用,值越小优先级越高

比如设HAL_InitTick(0),SysTick 就是最高优先级。

为什么不推荐?这只解决了 HAL_Delay 的问题,其他在中断中可能被调用的需要时间等待的函数也会遇到同样的坑。而且改变了系统的中断优先级设计,可能会引入其他 bug。

4.3 自定义非阻塞延时(推荐学习写法)

// 非阻塞延时检查 uint8_t Check_Delay(uint32_t *start_tick, uint32_t delay_ms) { if ((HAL_GetTick() - *start_tick) >= delay_ms) return 1; // 时间到了 return 0; // 还没到 } // 使用方式 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { static uint32_t last_tick = 0; if (Check_Delay(&last_tick, 200)) // 距上次触发超过 200ms 才处理 { last_tick = HAL_GetTick(); HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } }

这段代码不阻塞、不进死等,在任何上下文中都能安全调用


五、还有哪些函数不能在中断里调?

HAL 库中,带有超时参数、内部有 while 死等的函数都不适合在中断里直接调:

函数原因
HAL_Delay()死等 uwTick 更新
HAL_UART_Transmit(..., timeout)等发送完成,中断优先级可能导致死锁
HAL_SPI_Transmit(..., timeout)同上
HAL_I2C_Master_Transmit(..., timeout)同上
所有PollFor...函数本质上都是轮询等待

这些函数在中断优先级低于 SysTick的情况下调用不会死锁,但这不是可靠的设计。

中断里能安全调用的:

  • 置标志位、读写简单变量

  • 往队列/缓冲区写入数据

  • HAL_xxx_Transmit_IT()(中断版——它只启动传输就返回)


六、如果必须要延时怎么办

有些场景确实需要在中断里"等一会儿"(比如跟特定时序的传感器通信)。

方案:状态机 + 定时器

// 定时器每 100μs 触发一次中断 // 状态机在定时器中断中推进 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { static uint8_t step = 0; switch (step) { case 0: // 拉低 CS CS_LOW(); step++; break; case 1: // 等 1ms 后发命令 if (delay_check(1)) { // 自制的简易计时器 SPI_Transmit(cmd); step++; } break; case 2: // 等等... // ... break; } }

不用HAL_Delay,用定时器驱动状态机逐步推进,每个步骤只做一点点事。


七、总结

要点一句话
为什么死锁高优先级中断里等 uwTick,但 SysTick 进不来
最佳实践中断只置标志,主循环再处理耗时操作
不要改 SysTick 优先级来"绕过"治标不治本
非要延时用定时器状态机,不用 HAL_Delay
面试常问这就是考点——中断里的 HAL_Delay 会死锁,原因解释清楚
http://www.cnnetsun.cn/news/3185964.html

相关文章:

  • KMX62与PIC18F45K22在嵌入式运动控制中的优化应用
  • 什么是*Bash* *循环*的正确用法?
  • 模板基础与 SFINAE
  • git的分支介绍
  • MyBatisPlus--简介+入门案例
  • ExpRL: Exploratory RL for LLM Mid-Training——用于LLM中期训练的探索性强化学习
  • EB Garamond 12:让16世纪经典字体在数字时代重获新生
  • 选IP广播时该参考哪些通用标准来判断其专业性?
  • 如何快速转换塞尔达传说:旷野之息存档 - Switch与WiiU存档互通终极指南
  • 2026物业退场监管升级背景下的数智化应对策略
  • 2026河南省高考招生之友+录取统计(物理组+历史组)
  • 抖音动态监控助手:5分钟实现博主实时追踪与直播提醒
  • 扫码点单小程序制作流程详解:附2026阿里开发者社区好评率最高的3款点单小程序制作工具
  • Spring4Shell漏洞深度剖析:从数据绑定到RCE的完整攻击链复现
  • 乌鲁木齐公考师资实力榜:李想、成章等本土名师全面评测
  • 2026大数据工程师转型大模型全攻略|薪资翻倍+零基础可转+落地路径
  • ICM-42605与PIC18F67K40实现高精度运动追踪方案
  • 【Java踩坑笔记】26_toString里打印对象?小心无限递归栈溢出
  • 校园体育器材管理系统
  • Aura:俄罗斯大神打造的免费白噪音神器,把森林搬进你的Windows桌面!
  • AI项目交付完就结束了?不,真正值钱的是后面的事
  • Claude Code Auto memory 的审计与编辑机制,为什么普通 markdown 才是最可靠的工程记忆
  • 手写 MCP Server 控制文件系统:50 行代码让 AI 学会读写文件
  • 【Java转AI实战】第3讲:流式输出——LLM的“分页查询“,附生产级SSE踩坑实录
  • 告别数据分析小白!okbiye AI写作,一键搞定论文数据实证分析
  • 踩坑十多款AIGC检测工具后,我给实验室搭了套全本地的论文内容自查系统
  • Dify_搭建智能销售 Agent 工作流
  • 第38篇:数据组织(一):数据所有者的角色与责任
  • 第40篇:IT部门的重组:从“系统维护中心”到“能力集成中心”
  • G. Summmon (Codeforces 2241)