STM32中如何关闭中断功能(DMA传输为例)_stm32关闭所有中断
一、为什么我们要关中断功能呢?
1.在撰写类似DMA相关串口传输程序时,我们会使用到队列指针的操作,而这个操作绝对不可以被任何行为打断,打断就寄。
2. 因此我们要在进行队列指针的数据处理时,将全局的中断全部关闭。(很快,就一瞬间开关一下)
3.举个例子:
例如在某一串口传输函数中,以下操作需原子性:
ENTER_CRITICAL();if (队列未满) { txHead = next_head; // 更新队列头 if (UART空闲) { 启动DMA发送(); // 更新txTail和isUART3Busy }}EXIT_CRITICAL();
若此处不关闭中断,DMA完成回调可能在更新 txHead 后立即触发,导致 txTail 与 txHead 不一致。
二、那么我们怎么做呢,答:使用以下代码
#define ENTER_CRITICAL() __disable_irq() // 关闭中断(进入临界区)#define EXIT_CRITICAL() __enable_irq() // 打开中断(退出临界区)
1. 底层实现
这两个宏通过操作ARM Cortex-M处理器的 PRIMASK 寄存器 来实现中断开关:
- __disable_irq():将PRIMASK寄存器设置为1,禁止所有可屏蔽中断。
- __enable_irq():将PRIMASK寄存器设置为0,重新允许中断。
代码对应的汇编指令:
// ENTER_CRITICAL() 实际生成的汇编指令CPSID I // 关闭中断(设置 PRIMASK = 1)// EXIT_CRITICAL() 实际生成的汇编指令CPSIE I // 打开中断(清除 PRIMASK = 0)
2. 关闭的中断范围
-
关闭的中断:所有可屏蔽中断(Maskable Interrupts)。
- 包括GPIO中断、定时器中断、UART中断、DMA中断等用户自定义中断。
-
不关闭的中断:不可屏蔽中断(NMI, Non-Maskable Interrupt)。
- 例如:硬件故障、看门狗复位等关键事件触发的中断。
3. 关键特性
(1) 全局开关
- 关闭的是所有可屏蔽中断,而不是某个特定中断。
- 无论中断优先级高低,只要属于可屏蔽中断,都会被禁止。
(2) 嵌套安全
- 如果多次调用 ENTER_CRITICAL(),只有第一次调用会生效(PRIMASK已为1)。
示例:
ENTER_CRITICAL(); // PRIMASK = 1(关闭中断)ENTER_CRITICAL(); // PRIMASK仍为1(无操作)// 操作共享资源...EXIT_CRITICAL(); // PRIMASK = 0(重新允许中断)
4. 适用场景
-
保护共享资源:在操作全局变量(如队列指针txHead、txTail)或外设寄存器时,确保操作原子性。
-
避免数据竞争:防止主程序与中断服务程序(ISR)同时修改同一数据。
示例:
void u1_printf_nonblocking(...){ ENTER_CRITICAL(); // 关闭中断 // 操作队列指针txHead和txTail EXIT_CRITICAL(); // 恢复中断}
5. 注意事项
(1) 临界区应尽量简短
- 关闭中断会导致系统无法响应外部事件,长时间关闭可能影响实时性。
- 避免在临界区内执行复杂操作(如循环、延时)。
(2) 不可屏蔽中断(NMI)仍可能触发
- 如果代码需要完全避免中断打断(例如关键硬件操作),需结合其他机制(如关闭NMI)。
(3) 优先级反转风险
- 若在低优先级中断中调用 ENTER_CRITICAL(),可能导致高优先级中断被延迟。
6. 替代方案(按需选择)
(1) BASEPRI 寄存器
- 仅关闭优先级低于某个阈值的中断,而不是全部。
- 示例:
__set_BASEPRI(0x80) //关闭优先级≥7的中断。
(2) 操作系统提供的锁
- 在使用RTOS(如FreeRTOS)时,优先使用任务调度锁(vTaskSuspendAll())代替直接开关中断。
- 或者使用RTOS提供的互斥锁(如FreeRTOS的 taskENTER_CRITICAL())
总结
- __disable_irq() 和 __enable_irq() 通过设置PRIMASK寄存器关闭所有可屏蔽中断。
- 关闭中断的核心目的:确保对共享资源的操作是原子性的,避免数据竞争。
- 全局中断关闭(PRIMASK):简单粗暴但有效,适合极短时间的临界区保护。
- 不关闭中断的替代方案:
- 使用优先级屏蔽(BASEPRI)仅关闭低优先级中断(适合有严格实时性要求的场景)。
- 使用操作系统提供的锁