void DEBUG_USART_IRQHandler(void)
{
uint32_t ulReturn;
ulReturn = taskENTER_CRITICAL_FROM_ISR();/* 进入临界段,临界段可以嵌套 */
if(USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE)!=RESET)
{
Uart_DMA_Rx_Data(); /* 释放一个信号量,表示数据已接收 */
USART_ReceiveData(DEBUG_USARTx); /* 清除标志位 */
}
taskEXIT_CRITICAL_FROM_ISR( ulReturn );/* 退出临界段 */
}
1. 中断专用临界段(taskENTER_CRITICAL_FROM_ISR)的本质:不关闭当前中断,仅屏蔽低优先级中断
taskENTER_CRITICAL_FROM_ISR是 FreeRTOS 提供的中断专用临界段入口函数,它的核心作用是:
- 屏蔽优先级低于当前中断的其他中断(防止当前中断处理流程被低优先级中断打断);
- 不关闭当前正在执行的 IDLE 中断,也不会清除当前 IDLE 中断的标志位;
- 临界段是 "嵌套式" 的,不会影响当前中断自身的执行流程。
简单说:当前 IDLE 中断已经触发并进入服务函数,临界段只是 "挡住其他低优先级中断的干扰",对当前已经发生的 IDLE 中断和其标志位毫无影响。
2. IDLE 中断标志位的特性:一旦触发就保持置位,直到软件清除
USART 的 IDLE 中断标志(USART_IT_IDLE)有一个关键特性:
- 当串口接收完一帧数据、无新数据输入时,硬件会自动将 IDLE 标志位置 1(触发中断);
- 该标志位一旦置 1,会持续保持置位状态,不会自动清零,也不会被临界段操作改变;
- 只有通过 "读取 USART 数据寄存器(DR)"(即代码中的
USART_ReceiveData(DEBUG_USARTx))才能手动清除该标志位。
因此,即使进入临界段后再检测标志位,该标志依然是置位状态,USART_GetITStatus能正常检测到。
3. 代码执行逻辑的时序:中断触发在前,临界段进入在后
整个流程的时序是不可逆的,确保了标志位必然能被检测到:
- 硬件触发 IDLE 中断 :串口接收完成→IDLE 标志位置 1→CPU 响应中断,暂停当前任务,跳转到
DEBUG_USART_IRQHandler入口; - 进入中断服务函数:CPU 已经确认 IDLE 中断发生,才会执行中断服务函数内的代码;
- 进入临界段:此时 IDLE 标志位早已置位,临界段操作不会改变这个事实;
- 检测标志位 :
USART_GetITStatus读取到已置位的 IDLE 标志,执行后续数据处理逻辑。
简单理解:"中断触发" 是 "进入中断服务函数" 的前提,而 "进入临界段" 是中断服务函数内的后续操作,不可能出现 "进入临界段后,中断才触发" 的时序颠倒。
三、该代码中临界段的真正作用(不是为了中断检测,而是保护后续操作)
这段代码中,临界段的目的不是为了检测 IDLE 中断,而是为了保护Uart_DMA_Rx_Data()中的关键操作不被干扰:
Uart_DMA_Rx_Data()中包含 DMA 配置修改(关闭 / 使能 DMA、重置接收长度)、信号量释放等原子操作;- 如果没有临界段保护,这些操作可能被其他更高优先级的中断(注:实际是屏蔽低优先级中断,防止当前中断被嵌套打断)打断,导致 DMA 配置出错、信号量释放异常等问题;
- 临界段保证了
Uart_DMA_Rx_Data()和USART_ReceiveData()的执行完整性,避免并发问题。
场景 1:高优先级中断执行过程中,低优先级中断请求产生(无法抢占,但会挂起等待)
假设当前串口 IDLE 中断(抢占优先级 7,较高)正在执行,此时:
- 全局中断是开启的,CPU 会持续检测中断请求线;
- 若有一个低优先级中断(比如定时器中断,抢占优先级 8,更低)产生请求,由于其抢占优先级低于当前串口中断,无法立即打断串口中断的执行;
- 但这个低优先级中断的请求会被硬件挂起(标志位置位),不会消失;
- 当串口中断服务函数执行完毕、退出中断后,CPU 会立即响应这个挂起的低优先级中断,去执行它的服务函数。
这种情况下,低优先级中断虽然没「打断」高优先级中断,但会导致「高优先级中断对应的任务切换被延迟」(比如串口中断释放信号量后,本应立即切换到KEY_Task,但却先执行了低优先级中断,任务切换被延后)。
场景 2:高优先级中断服务函数中,存在「指令间隙」或「耗时操作」,低优先级中断可能嵌套(特殊情况)
更关键的是:高优先级中断的服务函数不是「原子执行」的,它由多条机器指令组成,在指令执行的间隙,若有低优先级中断请求已挂起,可能出现「嵌套」(注意:不是低优先级抢占高优先级,而是高优先级中断执行中,全局中断开启,允许更高优先级中断嵌套,但低优先级中断也可能在特定情况下插入)。
举个通俗例子:串口 IDLE 中断(高优先级)正在执行「关闭 DMA→清除标志→重置 DMA 长度→使能 DMA」这 4 步操作,每一步对应多条 CPU 指令:
- 执行完「关闭 DMA」后,CPU 需要取下一步指令(存在短暂间隙);
- 此时若有低优先级中断请求已挂起,虽然它不能抢占高优先级中断,但由于全局中断是开启的,硬件会标记「有未响应的中断请求」;
- 若高优先级中断的后续指令执行较慢,或存在短暂等待,这个低优先级中断就可能「插入」到高优先级中断的执行流程中(嵌套执行),导致:
- DMA 配置流程被拆分(关闭 DMA 后,还没重置长度,就去执行低优先级中断);
- 等低优先级中断执行完,回到串口中断继续执行时,DMA 状态可能异常(比如被其他操作修改)。
这种「嵌套干扰」不是「低优先级抢占高优先级」,而是「高优先级中断执行中,全局中断开启,允许低优先级中断在指令间隙插入嵌套」,最终导致关键操作(如 DMA 配置、信号量释放)失去原子性。
三、代码中临界段的真正作用:不是防止「被低优先级打断」,而是「屏蔽低优先级中断的干扰 / 嵌套」
taskENTER_CRITICAL_FROM_ISR的核心功能是通过设置 NVIC 的 BASEPRI 寄存器,屏蔽所有「抢占优先级低于某一阈值」的中断 (阈值由 FreeRTOS 的configMAX_SYSCALL_INTERRUPT_PRIORITY配置),它的目的不是防止高优先级中断被低优先级中断「抢占」(本来就不会),而是:
-
保证当前中断服务函数中关键操作的原子性:把「Uart_DMA_Rx_Data ()(DMA 配置 + 信号量释放)+ USART_ReceiveData ()(清除标志)」打包成一个「不可拆分」的原子操作,避免被低优先级中断嵌套插入,防止 DMA 配置出错、信号量释放不完整。
-
避免低优先级中断请求挂起,导致任务切换延迟 :串口中断释放信号量后,需要通过
portYIELD_FROM_ISR触发任务切换(切换到KEY_Task)。如果不屏蔽低优先级中断,串口中断退出后,会先执行挂起的低优先级中断,再进行任务切换,导致数据处理任务被延迟响应。 -
保护 FreeRTOS 内核对象的操作安全 :
xSemaphoreGiveFromISR是 FreeRTOS 内核 API,操作内核的信号量控制块。如果在执行这个 API 时,被低优先级中断嵌套打断,可能导致信号量的链表 / 计数被篡改,引发系统异常。
四、通俗类比
可以把中断执行比作「医院看病」:
- 高优先级中断 = 急诊病人(优先看病,没人能插队);
- 低优先级中断 = 普通门诊病人(正常情况下不能打断急诊);
- 全局中断开启 = 急诊室的门没锁,护士会随时进来提醒「有普通病人在等」;
- 临界段 = 急诊医生锁上门,告诉护士「先别打扰,我正在做关键手术(DMA 配置 + 信号量释放),做完再说」。
急诊医生(高优先级中断)不会被普通病人(低优先级中断)插队(抢占),但如果门没锁(没进入临界段),护士频繁进来提醒(低优先级中断请求挂起),会打断医生的手术节奏(指令间隙被干扰),甚至可能导致手术步骤出错(关键操作拆分)。锁门(临界段)不是为了防止插队,而是为了保证手术能连续、完整地完成。