--- tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第6章 中断管理" author: "尚硅谷研究院" date: 2026-07-17 created: 2026-07-17 --- # 中断管理与临界段 > **用生活理解**:中断就像领导突然敲门安排紧急任务——你得先把手头工作保存好(保存上下文),处理完紧急任务再回来继续。临界区则像"开会时手机静音"——某些关键时刻不允许被打断。 ## 概念介绍 ### 基本概念 FreeRTOS 需要管理中断以确保系统的实时性和确定性。其核心思路是: - **临界段(Critical Section)**:完全关闭中断,任何中断都不能打断当前代码 - **BASEPRI 优先级屏蔽**:只屏蔽优先级低于阈值的中断,高优先级中断仍可响应 - **中断安全 API(FromISR 后缀)**:可在中断服务函数中安全调用的 API 版本 ### 工作原理 FreeRTOS 提供两个层次的中断控制: **1. 临界段保护(完整中断屏蔽)** | API | 描述 | | --------------------------------- | -------------------------------------- | | `taskENTER_CRITICAL()` | 进入临界区,关闭所有中断(有嵌套计数) | | `taskEXIT_CRITICAL()` | 退出临界区,恢复之前的中断状态 | | `taskENTER_CRITICAL_FROM_ISR()` | 从中断中进入临界区 | | `taskEXIT_CRITICAL_FROM_ISR()` | 从中断中退出临界区 | 任务级临界区内部维护一个嵌套计数器(uxCriticalNesting): - 第一次进入时关闭中断 - 嵌套进入时仅增加计数 - 每次退出递减计数,减到 0 时恢复中断 **2. 中断开关(直接操作)** | API | 描述 | | ---------------------------------- | ---------------- | | `portDISABLE_INTERRUPTS()` | 直接关闭所有中断 | | `portENABLE_INTERRUPTS()` | 直接打开所有中断 | | 注意:无嵌套保护,不推荐用于临界段 | | **3. BASEPRI 优先级屏蔽** FreeRTOS 使用 Cortex-M3 的 BASEPRI 寄存器实现优先级屏蔽: ``` configKERNEL_INTERRUPT_PRIORITY = 15 << 4 // 内核中断优先级(最低) configMAX_SYSCALL_INTERRUPT_PRIORITY = 5 << 4 // API可调用的最高中断优先级 ``` - 中断优先级 >= 5(数值 <= 5<<4)→ 可调用 FreeRTOS API - 中断优先级 < 5(数值 > 5<<4)→ 不允许调用 FreeRTOS API **4. 延后中断处理(Deferred Interrupt Processing)** 在 ISR 中只做最少的处理(记录事件、发送信号量/消息到队列),将耗时逻辑推迟到任务中执行。这是 RTOS 中推荐的中断处理模式。 ## 实验 ### 需求描述 - task1 检测按键: - KEY1 → `portDISABLE_INTERRUPTS()` 关闭中断(LED1 停止响应) - KEY2 → `portENABLE_INTERRUPTS()` 打开中断(LED1 恢复) - 对比 `vTaskDelay()` 和 `HAL_Delay()` 的行为差异 ### 代码实现 代码来源:04_中断管理/Core/Src/freertos_demo.c ```c #include "freertos_demo.h" #include "FreeRTOS.h" #include "task.h" #include "LED.h" #include "Key.h" /* 起始任务配置 */ #define START_TASK_STACK 128 #define START_TASK_PRIORITY 1 TaskHandle_t start_task_handle; void start_task(void *pvParameters); /* 任务1配置 */ #define TASK1_STACK 128 #define TASK1_PRIORITY 2 TaskHandle_t task1_handle; void task1(void *pvParameters); void freertos_start(void) { /* 动态创建起始任务 */ xTaskCreate((TaskFunction_t)start_task, (char *)"start_task", (configSTACK_DEPTH_TYPE)START_TASK_STACK, (void *)NULL, (UBaseType_t)START_TASK_PRIORITY, (TaskHandle_t *)&start_task_handle); /* 启动调度器 */ vTaskStartScheduler(); } void start_task(void *pvParameters) { /* 进入临界区,防止创建过程被打断 */ taskENTER_CRITICAL(); /* 创建1个任务 */ xTaskCreate((TaskFunction_t)task1, (char *)"task1", (configSTACK_DEPTH_TYPE)TASK1_STACK, (void *)NULL, (UBaseType_t)TASK1_PRIORITY, (TaskHandle_t *)&task1_handle); /* 删除起始任务自身 */ vTaskDelete(NULL); taskEXIT_CRITICAL(); } /** * @description: 任务1——按键控制中断开关 * KEY1 → portDISABLE_INTERRUPTS() 关闭中断 * KEY2 → portENABLE_INTERRUPTS() 打开中断 * 使用HAL_Delay模拟工作负载(非vTaskDelay) */ void task1(void *pvParameters) { uint8_t key = 0; while (1) { key = Key_Detect(); if (key == KEY1_PRESS) { /* KEY1按下:关闭所有中断——系统将无法响应任何中断 */ printf(">>>>关闭中断.....\r\n"); portDISABLE_INTERRUPTS(); } else if (key == KEY2_PRESS) { /* KEY2按下:打开所有中断 */ printf(">>>>打开中断.....\r\n"); portENABLE_INTERRUPTS(); } /* 注意:这里使用HAL_Delay而非vTaskDelay HAL_Delay依赖SysTick中断,如果中断被关闭,HAL_Delay将永远卡死! vTaskDelay不依赖中断,在中断关闭时仍能正常工作 */ HAL_Delay(500); } } ``` ### 关键配置 **FreeRTOSConfig.h**(中断优先级配置): ```c /* Cortex-M3 中断优先级配置 */ /* 内核中断优先级:最低优先级 15<<4 = 240(数值最大,优先级最低) */ #define configKERNEL_INTERRUPT_PRIORITY (15 << 4) /* 系统调用中断优先级:优先级 5 以上的中断可以调用 FreeRTOS API 优先级为 0-4 的中断被屏蔽,不能调用 FreeRTOS API 数值越小优先级越高:0 > 5 > 15 */ #define configMAX_SYSCALL_INTERRUPT_PRIORITY (5 << 4) /* 别名配置 */ #define configMAX_API_CALL_INTERRUPT_PRIORITY configMAX_SYSCALL_INTERRUPT_PRIORITY ``` ## 核心函数速查表 | 函数 | 参数 | 返回值 | 功能简述 | | -------------------------------------- | ----------------- | ----------- | ---------------------------------- | | `taskENTER_CRITICAL()` | 无 | void | 进入临界区(支持嵌套) | | `taskEXIT_CRITICAL()` | 无 | void | 退出临界区(嵌套计数减一) | | `taskENTER_CRITICAL_FROM_ISR()` | 无 | uint32_t | 从中断进入临界区,返回中断屏蔽状态 | | `taskEXIT_CRITICAL_FROM_ISR(ux)` | uxSavedMaskStatus | void | 从中断退出临界区,恢复中断状态 | | `portDISABLE_INTERRUPTS()` | 无 | void | 直接关闭所有中断 | | `portENABLE_INTERRUPTS()` | 无 | void | 直接打开所有中断 | | `portDISABLE_INTERRUPTS_FROM_ISR()` | 无 | UBaseType_t | ISR中关闭中断 | | `portENABLE_INTERRUPTS_FROM_ISR(ux)` | uxSavedStatus | void | ISR中恢复中断 | ## 常见问题与避坑 1. **HAL_Delay 在中断关闭后卡死**:`HAL_Delay()` 依赖 SysTick 中断更新 uwTick 变量。如果通过 `portDISABLE_INTERRUPTS()` 关闭了中断,SysTick 无法触发,`HAL_Delay` 将永远死等。 2. **临界区内不要执行耗时操作**:临界区内所有中断被屏蔽,包括 SysTick。长时间停留在临界区内会破坏系统的实时性。 3. **嵌套临界区必须配对**:每个 `taskENTER_CRITICAL()` 必须有对应的 `taskEXIT_CRITICAL()`,否则中断永久关闭。 4. **portDISABLE_INTERRUPTS 无嵌套保护**:与 `taskENTER_CRITICAL()` 不同,`portDISABLE_INTERRUPTS()` 不维护嵌套计数,在嵌套使用时可能会导致提前恢复中断。 5. **BASEPRI 与 NVIC 优先级的关系**:STM32 使用 4 位优先级(0~15),在 FreeRTOS 中需左移 4 位变为 CM3 的 8 位格式。例如优先级 5 → `5 << 4 = 0x50`。 6. **ISR 中必须使用 FromISR 版本**:在中断服务函数中调用队列、信号量等 API 时,必须使用带 `FromISR` 后缀的版本,否则可能导致内核数据结构损坏。