STM32中主循环与中断共用SPI时HAL_SPI_TransmitReceive的中断安全问题
一、中断回调中长SPI操作的替代方案
中断处理函数必须尽可能简短,避免阻塞其他中断或主循环,但NRF24L01+需要及时读取消息以腾出接收缓冲区,可采用以下两种方案:
1. 半中断模式:中断触发+主循环处理
中断回调中只完成最小必要操作:设置一个全局标志位(必须用volatile修饰防止编译器优化),可选仅执行清除中断状态的极简SPI操作,随后立即退出中断。主循环中轮询该标志位,检测到后再执行完整的消息读取与缓存逻辑。
注意:若主循环存在HAL_Delay()这类阻塞延时,需替换为非阻塞延时(比如用定时器计数实现),确保标志位能被及时检测到。
示例代码:
volatile uint8_t nrf_new_data_flag = 0; uint8_t nrf_status = 0; #define NRF_CMD_STATUS 0x07 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == NRF24L01_IRQ_Pin) { nrf_new_data_flag = 1; // 仅读取状态寄存器清除中断标志,避免中断内长操作 CSN_LOW(); HAL_SPI_TransmitReceive(&hspi1, &NRF_CMD_STATUS, &nrf_status, 1, SPI_TIMEOUT); CSN_HIGH(); } } // 非阻塞延时实现示例(基于定时器) uint32_t delay_start = 0; uint8_t non_blocking_delay(uint32_t ms) { if (HAL_GetTick() - delay_start >= ms) { delay_start = HAL_GetTick(); return 1; } return 0; } // 主循环 while (1) { if (nrf_new_data_flag) { nrf_new_data_flag = 0; // 这里执行完整的消息读取与缓存操作 read_nrf24l01_data_and_cache(); } // 原主循环的SPI操作与业务逻辑 if (non_blocking_delay(10)) { // 延时到期后执行的操作 } }
2. DMA+双缓冲区方案
配置SPI的DMA接收模式,让NRF24L01+收到的数据直接通过DMA写入硬件缓冲区,中断回调仅负责切换缓冲区标记,无需手动调用SPI读写函数。这种方案完全剥离了中断内的SPI操作,效率最高,但需要额外配置DMA与缓冲区逻辑。
二、主循环与中断SPI冲突的解决方法
SPI总线是共享资源,主循环与中断同时操作会导致句柄状态混乱,触发HardFault,核心解决思路是保证SPI访问的互斥性:
1. 临界区保护(裸机场景)
在主循环访问SPI前后,临时关闭NRF24L01+对应的EXTI中断,操作完成后重新开启,避免中断打断SPI传输。
示例代码:
while (1) { // 进入临界区:关闭NRF中断,防止打断SPI操作 HAL_NVIC_DisableIRQ(NRF24L01_IRQ_IRQn); CSN_LOW(); HAL_SPI_TransmitReceive(&hspi1, &command, &status, 1, SPI_TIMEOUT); CSN_HIGH(); // 退出临界区:重新开启NRF中断 HAL_NVIC_EnableIRQ(NRF24L01_IRQ_IRQn); // 处理缓存消息与发送响应的SPI操作,同样加临界区保护 HAL_NVIC_DisableIRQ(NRF24L01_IRQ_IRQn); send_response_via_spi(); // 自定义的SPI发送响应函数 HAL_NVIC_EnableIRQ(NRF24L01_IRQ_IRQn); // 非阻塞延时 non_blocking_delay(10); }
注意:该方案会短暂屏蔽中断,若主循环SPI操作耗时过长,可能错过中断事件,但NRF24L01+的中断状态会保持到读取状态寄存器,只要操作时间控制在合理范围(比如几微秒级别),不会影响功能。
2. RTOS互斥锁(带操作系统场景)
若项目使用FreeRTOS等RTOS,可为SPI总线创建一个互斥锁。主循环任务在访问SPI前获取锁,操作完成后释放;中断回调中不直接操作SPI,而是设置任务通知或标志位,唤醒专门的SPI处理任务,由该任务获取锁后执行SPI操作(避免中断内调用阻塞式锁API)。
3. 全主循环处理(最稳妥方案)
将所有SPI操作统一放到主循环中,中断仅负责触发标志位,从根本上避免中断与主循环的SPI冲突。只要主循环采用非阻塞逻辑,响应速度足以满足NRF24L01+的缓冲区需求。
内容的提问来源于stack exchange,提问作者An Hoa

