You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32中主循环与中断共用SPI时HAL_SPI_TransmitReceive的中断安全问题

STM32 SPI驱动NRF24L01+的中断与总线冲突问题解决

一、中断回调中长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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 03:15:00