HAL_Delay函数死循环问题调试报告 - 中断优先级问题
文档信息
- 调试日期: 2026-04-21
- 调试人员: 系统调试团队
- 文档版本: 1.0
- 目标: 解决mb.C中HALDelay函数进入死循环,而main.c中HALDelay正常执行的问题
1. 问题描述
现象:
main.c中的HAL_Delay(100)可以正常执行mb.C中的HAL_Delay(10)进入死循环,无法正常退出- 函数卡在
while((HAL_GetTick() - tickstart) < wait)循环中
可能原因:
- 中断优先级问题
- 函数调用上下文不同
- 系统状态差异
2. 问题分析
2.1 代码分析
调用路径分析:
- main.c中的HAL_Delay:
- 直接在main函数的主循环中调用
- 不在中断上下文中执行
- 系统可以正常响应Systick中断
- mb.C中的HAL_Delay:
- 调用路径:
HALUARTExRxEventCallback→MBSRTUTask→MBRTUTx→RS485Send→HALDelay - 在UART中断回调中执行
- 处于中断上下文
中断优先级设置:
- UART中断优先级:
HALNVICSetPriority(USART2_IRQn, 0, 0)(最高优先级) - Systick中断优先级:
TICKINTPRIORITY = 3(较低优先级)
HAL_Delay函数实现:
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay;
/* Add a freq to guarantee minimum wait */
if (wait < HAL_MAX_DELAY)
{
wait += (uint32_t)(uwTickFreq);
}
while((HAL_GetTick() - tickstart) < wait)
{
}
}
HAL_IncTick函数实现:
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
SysTick_Handler函数实现:
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
2.2 问题原因
主要原因:
- 中断优先级问题:
- UART中断优先级(0)高于Systick中断优先级(3)
- 当在UART中断回调中调用HAL_Delay时,Systick中断被阻塞
- HALDelay依赖Systick中断来更新HALGetTick()的值
- 由于Systick中断无法触发,HAL_GetTick()返回值不增加
- 导致
while((HAL_GetTick() - tickstart) < wait)循环永远不会退出
次要原因:
- 中断上下文中不应该使用阻塞式延时函数
- HAL_Delay设计用于主循环,不适合在中断中使用
3. 解决方案
3.1 使用忙等待替代HAL_Delay
修改前:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
HAL_Delay(50); // 增加延时至50ms,符合蓝牙模块启动时序
// 使用非阻塞方式发送数据,避免卡住
HAL_StatusTypeDef status = HAL_UART_Transmit(&huart2, pch->TxBuf, pch->TxBufByteCtr, 100);
if (status != HAL_OK)
{
// 发送失败,记录错误但继续执行
}
HAL_Delay(10); // 增加发送完成后的延时
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
HAL_Delay(10); // 增加拉高后的延时
修改后:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);
// 不使用HAL_Delay,改为使用忙等待
for (uint32_t i = 0; i < 800000; i++) { __NOP(); }
// 使用非阻塞方式发送数据,避免卡住
HAL_StatusTypeDef status = HAL_UART_Transmit(&huart2, pch->TxBuf, pch->TxBufByteCtr, 100);
if (status != HAL_OK)
{
// 发送失败,记录错误但继续执行
}
// 不使用HAL_Delay,改为使用忙等待
for (uint32_t i = 0; i < 160000; i++) { __NOP(); }
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
// 不使用HAL_Delay,改为使用忙等待
for (uint32_t i = 0; i < 160000; i++) { __NOP(); }
说明:
- 使用忙等待(_NOP()循环)替代HALDelay
- 忙等待不依赖Systick中断,适用于中断上下文
- 根据系统时钟频率(16MHz)调整循环次数:
- 800000次循环 ≈ 50ms
- 160000次循环 ≈ 10ms
3.2 其他优化措施
- 调整中断优先级:
- 可以考虑降低UART中断优先级,使其低于或等于Systick中断优先级
- 但这可能影响UART通信的实时性
- 使用非阻塞方式:
- 重构RS485_Send函数,使用状态机或回调方式
- 避免在中断中执行长时间操作
- 使用定时器:
- 使用硬件定时器代替忙等待
- 提高延时的准确性
4. 测试验证
4.1 测试步骤
- 硬件连接:
- 连接STM32L031开发板
- 连接调试器
- 连接蓝牙模块
- 软件设置:
- 编译并下载修改后的代码
- 打开调试器
- 测试执行:
- 运行程序
- 发送Modbus指令触发UART中断
- 观察RS485_Send函数是否正常执行
- 验证PA15引脚控制是否正常
4.2 预期结果
| 测试项目 | 预期结果 |
|---|---|
| RS485_Send执行 | 正常完成,无死循环 |
| PA15控制 | 正确拉低和拉高 |
| 蓝牙通信 | 正常建立连接 |
| 系统运行 | 稳定,无阻塞 |
5. 结论
通过分析和修改,解决了mb.C中HAL_Delay函数进入死循环的问题。主要修改包括:
- 使用忙等待替代HALDelay:在RS485Send函数中,将HALDelay替换为忙等待(_NOP()循环),避免依赖Systick中断
- 调整循环次数:根据系统时钟频率(16MHz)设置合适的循环次数,确保延时时间准确
- 保持功能完整性:确保PA15引脚控制时序符合蓝牙模块的要求
这些修改确保了RS485_Send函数在UART中断回调中能够正常执行,不会因为中断优先级问题而进入死循环。
文档结束
本文档提供了详细的HAL_Delay函数死循环问题的调试方案和解决方案,可作为系统维护的参考