FreeRTOS:中断(ISR)与 RTOS 安全 API

FreeRTOS:中断(ISR)与 RTOS 安全 API

前言

在嵌入式系统中,中断是处理实时事件的核心机制。然而,当我们引入 RTOS 后,中断服务程序(ISR)与操作系统的交互就成了一个需要格外小心的领域。使用不当的 API 可能导致系统崩溃、任务调度失败,甚至数据损坏。

本节课程将深入探讨如何在 FreeRTOS 中正确处理中断,包括专用的 FromISR 后缀 API、上下文切换机制、中断优先级配置,以及一些常见的陷阱和解决方案。

一、核心概念

1.1 为什么需要 FromISR API?

FreeRTOS 的常规 API(如 xQueueSendxSemaphoreGive)是为任务上下文设计的,它们可能会引发任务切换、进入阻塞状态。但在 ISR 中:

  • 不能阻塞

    :ISR 必须快速执行并返回

  • 上下文切换时机特殊

    :需要延迟到 ISR 结束后才能切换任务

  • 临界区处理不同

    :ISR 通过禁用中断实现互斥,而非调度器锁

因此,FreeRTOS 提供了一套专门的 FromISR API,它们:

  • 永不阻塞

  • 通过 pxHigherPriorityTaskWoken 参数指示是否需要上下文切换

  • 使用中断安全的临界区保护

1.2 Cortex-M 中断优先级与 FreeRTOS

在 ARM Cortex-M 架构中,中断优先级数字越小优先级越高 (0 是最高优先级)。FreeRTOS 通过 configMAX_SYSCALL_INTERRUPT_PRIORITY 定义了一个临界值:

cpp 复制代码
优先级 0-4:不受 FreeRTOS 管理(不能调用任何 FreeRTOS API)
优先级 5-15:受 FreeRTOS 管理(可以调用 FromISR API)

BASEPRI 寄存器 :FreeRTOS 使用 BASEPRI 寄存器来实现临界区。设置 BASEPRI 后,所有优先级数值大于等于该值的中断会被屏蔽。

PRIMASK 寄存器:全局中断使能位,设置后屏蔽所有可屏蔽中断(不推荐在 RTOS 中使用)。

二、核心 FromISR API 详解

2.1 队列操作

xQueueSendFromISR / xQueueSendToBackFromISR
cpp 复制代码
BaseType_t xQueueSendFromISR(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,
    BaseType_t *pxHigherPriorityTaskWoken
);

参数说明

  • xQueue

    :队列句柄

  • pvItemToQueue

    :要发送的数据指针

  • pxHigherPriorityTaskWoken

    :输出参数,指示是否有更高优先级任务被唤醒

返回值

  • pdTRUE

    :成功发送

  • errQUEUE_FULL

    :队列已满

使用示例

cpp 复制代码
void UART_IRQHandler(void) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    char receivedChar;
    
    if (UART_GetITStatus(UART1, UART_IT_RXNE)) {
        receivedChar = UART_ReceiveData(UART1);
        
        // 发送到队列
        xQueueSendFromISR(uartQueue, &receivedChar, &xHigherPriorityTaskWoken);
        
        UART_ClearITPendingBit(UART1, UART_IT_RXNE);
    }
    
    // ISR 结束时检查是否需要上下文切换
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
xQueueReceiveFromISR
cpp 复制代码
BaseType_t xQueueReceiveFromISR(
    QueueHandle_t xQueue,
    void *pvBuffer,
    BaseType_t *pxHigherPriorityTaskWoken
);

注意:这个 API 较少使用,因为通常是 ISR 向队列发送数据,任务从队列接收。

2.2 信号量操作

xSemaphoreGiveFromISR
cpp 复制代码
BaseType_t xSemaphoreGiveFromISR(
    SemaphoreHandle_t xSemaphore,
    BaseType_t *pxHigherPriorityTaskWoken
);

典型用例:中断触发后,通知任务进行处理

cpp 复制代码
void TIM2_IRQHandler(void) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    if (TIM_GetITStatus(TIM2, TIM_IT_Update)) {
        // 释放二值信号量,唤醒处理任务
        xSemaphoreGiveFromISR(timeSemaphore, &xHigherPriorityTaskWoken);
        
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
    
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

2.3 任务通知

vTaskNotifyGiveFromISR
cpp 复制代码
void vTaskNotifyGiveFromISR(
    TaskHandle_t xTaskToNotify,
    BaseType_t *pxHigherPriorityTaskWoken
);

优势:比信号量更快,开销更小

cpp 复制代码
TaskHandle_t dataProcessTask;

void ADC_IRQHandler(void) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    if (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)) {
        // 直接通知任务
        vTaskNotifyGiveFromISR(dataProcessTask, &xHigherPriorityTaskWoken);
        
        ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
    }
    
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
xTaskNotifyFromISR
cpp 复制代码
BaseType_t xTaskNotifyFromISR(
    TaskHandle_t xTaskToNotify,
    uint32_t ulValue,
    eNotifyAction eAction,
    BaseType_t *pxHigherPriorityTaskWoken
);

eAction 参数

  • eNoAction

    :仅设置通知状态

  • eSetBits

    :将 ulValue 与任务通知值按位或

  • eIncrement

    :递增通知值(忽略 ulValue)

  • eSetValueWithOverwrite

    :强制设置为 ulValue

  • eSetValueWithoutOverwrite

    :仅在没有待处理通知时设置

2.4 上下文切换宏

portYIELD_FROM_ISR
cpp 复制代码
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

功能:在 ISR 退出前,检查是否需要立即进行上下文切换

等价形式(Cortex-M):

cpp 复制代码
if (xHigherPriorityTaskWoken) {
    portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);
}

三、实验:中断驱动的串口接收

3.1 实验目标

实现一个完整的中断驱动串口接收系统:

  1. UART 中断接收字符

  2. 通过队列传递数据到任务

  3. 任务处理接收到的数据(回显 + 统计)

3.2 完整代码

cpp 复制代码
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "stm32f4xx.h"
#include <stdio.h>

/* 队列句柄 */
QueueHandle_t xUartRxQueue;

/* 统计信息 */
typedef struct {
    uint32_t totalReceived;
    uint32_t overrunErrors;
    uint32_t queueFullCount;
} UartStats_t;

UartStats_t uartStats = {0};

/* UART 初始化 */
void UART_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 使能时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    
    // GPIO 配置:PA9(TX), PA10(RX)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
    
    // UART 配置
    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);
    
    // 使能接收中断
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    
    // 配置 NVIC(重要:优先级必须高于 configMAX_SYSCALL_INTERRUPT_PRIORITY)
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;  // 假设 configMAX_SYSCALL = 5
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    USART_Cmd(USART1, ENABLE);
}

/* UART 中断服务函数 */
void USART1_IRQHandler(void) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    char receivedChar;
    
    // 检查接收中断标志
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        receivedChar = (char)USART_ReceiveData(USART1);
        
        // 尝试发送到队列
        if (xQueueSendFromISR(xUartRxQueue, &receivedChar, &xHigherPriorityTaskWoken) != pdTRUE) {
            // 队列满,记录错误
            uartStats.queueFullCount++;
        } else {
            uartStats.totalReceived++;
        }
        
        // 清除中断标志(STM32 读取数据后自动清除)
    }
    
    // 检查溢出错误
    if (USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET) {
        USART_ReceiveData(USART1);  // 读取数据寄存器清除 ORE
        uartStats.overrunErrors++;
    }
    
    // 执行上下文切换(如果需要)
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

/* UART 发送函数(阻塞式,供任务使用) */
void UART_SendChar(char ch) {
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    USART_SendData(USART1, ch);
}

void UART_SendString(const char* str) {
    while (*str) {
        UART_SendChar(*str++);
    }
}

/* 数据处理任务 */
void vUartProcessTask(void *pvParameters) {
    char receivedChar;
    char buffer[64];
    
    UART_SendString("UART Processing Task Started\r\n");
    UART_SendString("Type characters to echo back...\r\n");
    
    while (1) {
        // 从队列接收数据(阻塞等待)
        if (xQueueReceive(xUartRxQueue, &receivedChar, portMAX_DELAY) == pdTRUE) {
            // 回显字符
            UART_SendChar(receivedChar);
            
            // 特殊命令处理
            if (receivedChar == '\r') {
                UART_SendChar('\n');  // 换行
            } else if (receivedChar == 's') {
                // 显示统计信息
                snprintf(buffer, sizeof(buffer), 
                    "\r\n[Stats] Total: %lu, Queue Full: %lu, Overrun: %lu\r\n",
                    uartStats.totalReceived, 
                    uartStats.queueFullCount,
                    uartStats.overrunErrors);
                UART_SendString(buffer);
            }
        }
    }
}

/* 监控任务(可选) */
void vMonitorTask(void *pvParameters) {
    char buffer[64];
    uint32_t lastTotal = 0;
    
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(5000));  // 每 5 秒报告一次
        
        uint32_t newChars = uartStats.totalReceived - lastTotal;
        lastTotal = uartStats.totalReceived;
        
        snprintf(buffer, sizeof(buffer), 
            "[Monitor] New: %lu chars, Queue waiting: %lu\r\n",
            newChars, uxQueueMessagesWaiting(xUartRxQueue));
        UART_SendString(buffer);
    }
}

/* 主函数 */
int main(void) {
    // 初始化硬件
    UART_Init();
    
    // 创建队列(32 个字符缓冲)
    xUartRxQueue = xQueueCreate(32, sizeof(char));
    if (xUartRxQueue == NULL) {
        // 队列创建失败
        while (1);
    }
    
    // 创建数据处理任务(高优先级)
    xTaskCreate(vUartProcessTask, "UartProcess", 
                configMINIMAL_STACK_SIZE * 2, NULL, 3, NULL);
    
    // 创建监控任务(低优先级)
    xTaskCreate(vMonitorTask, "Monitor", 
                configMINIMAL_STACK_SIZE, NULL, 1, NULL);
    
    // 启动调度器
    vTaskStartScheduler();
    
    // 不应该运行到这里
    while (1);
}

3.3 实验运行效果

  1. 正常接收

    :输入字符会立即回显

  2. 统计命令

    :输入 s 显示接收统计

  3. 监控输出

    :每 5 秒自动报告接收速率

  4. 错误处理

    :队列满时记录丢失计数

3.4 关键设计点

  1. 最小化 ISR 工作量

    :ISR 只负责读取数据、发送队列,不做复杂处理

  2. 队列缓冲

    :32 字符缓冲足以应对突发流量

  3. 错误统计

    :记录队列满和溢出错误,便于调试

  4. 优先级配置

    :UART 中断优先级设置为 6(高于 configMAX_SYSCALL)

四、作业:中断优先级错误问题分析

4.1 作业题目

场景描述: 某嵌入式系统使用 FreeRTOS,配置如下:

  • configMAX_SYSCALL_INTERRUPT_PRIORITY = 5

    (在 STM32 上,实际值为 5 << 4 = 0x50)

  • UART 中断优先级设置为 3

  • 定时器中断优先级设置为 7

系统运行后出现以下问题:

  1. UART 中断处理后偶尔系统崩溃

  2. 定时器中断调用 xSemaphoreGiveFromISR 正常工作

  3. 调试发现 HardFault 发生在任务切换代码中

问题

  1. 分析导致系统崩溃的根本原因

  2. 给出详细的解决方案

  3. 编写验证代码,展示正确和错误的配置

4.2 问题分析

根本原因

UART 中断优先级配置错误

cpp 复制代码
UART 优先级 = 3 < configMAX_SYSCALL (5)
                 ↑
            不受 FreeRTOS 管理

当 UART 中断优先级低于 (数值小于)configMAX_SYSCALL_INTERRUPT_PRIORITY 时,该中断处于 FreeRTOS 无法控制的范围。此时:

  1. 临界区失效

    :FreeRTOS 的临界区通过设置 BASEPRI = configMAX_SYSCALL 来禁用受管理的中断。但 UART 中断(优先级 3)仍然可以抢占,导致临界区被破坏。

  2. 数据结构损坏

    :如果 UART 中断在任务切换过程中触发,可能会修改正在更新的内核数据结构(如就绪列表、延迟列表)。

  3. 调度器状态不一致

    :中断可能在调度器挂起期间运行,导致调度器状态机混乱。

为什么定时器中断正常?

定时器中断优先级 = 7 > configMAX_SYSCALL (5),处于受管理范围,FreeRTOS 可以正确保护临界区。

4.3 解决方案

方案 1:调整 UART 中断优先级(推荐)

将 UART 中断优先级设置为 5 或更高

cpp 复制代码
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;  // 正确:6 > 5
方案 2:调整 configMAX_SYSCALL_INTERRUPT_PRIORITY

如果 UART 中断确实需要极高优先级,可以降低 configMAX_SYSCALL:

cpp 复制代码
// FreeRTOSConfig.h
#define configMAX_SYSCALL_INTERRUPT_PRIORITY  (2 << 4)  // 降低到 2

但这会导致更多中断不受 FreeRTOS 管理,需谨慎评估。

方案 3:高优先级中断不调用 FreeRTOS API

如果必须使用高优先级(< configMAX_SYSCALL),则完全不能调用任何 FreeRTOS API

cpp 复制代码
void USART1_IRQHandler(void) {
    char data = USART_ReceiveData(USART1);
    
    // 直接写入全局缓冲区(使用硬件同步或原子操作)
    if (rxBufferHead != rxBufferTail) {
        rxBuffer[rxBufferHead++] = data;
    }
    
    // 不调用任何 FreeRTOS API!
}

4.4 验证代码

cpp 复制代码
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "stm32f4xx.h"

SemaphoreHandle_t xTestSemaphore;
volatile uint32_t errorCount = 0;
volatile uint32_t successCount = 0;

/* 错误配置示例 */
void ConfigureUART_Wrong(void) {
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 错误:优先级 3 < configMAX_SYSCALL (5)
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;  // ❌ 错误
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    printf("UART Priority: 3 (WRONG - below configMAX_SYSCALL)\r\n");
}

/* 正确配置示例 */
void ConfigureUART_Correct(void) {
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 正确:优先级 6 > configMAX_SYSCALL (5)
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 6;  // ✓ 正确
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    printf("UART Priority: 6 (CORRECT - above configMAX_SYSCALL)\r\n");
}

/* UART 中断处理 */
void USART1_IRQHandler(void) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        USART_ReceiveData(USART1);  // 读取数据
        
        // 尝试释放信号量
        if (xSemaphoreGiveFromISR(xTestSemaphore, &xHigherPriorityTaskWoken) == pdTRUE) {
            successCount++;
        } else {
            errorCount++;
        }
        
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

/* 测试任务:触发大量中断 */
void vStressTestTask(void *pvParameters) {
    uint32_t count = 0;
    
    while (1) {
        // 模拟发送数据触发 UART 中断
        USART_SendData(USART1, 'A');
        
        // 获取信号量
        if (xSemaphoreTake(xTestSemaphore, pdMS_TO_TICKS(100)) == pdTRUE) {
            count++;
            
            if (count % 1000 == 0) {
                printf("Test cycles: %lu, Success: %lu, Errors: %lu\r\n",
                       count, successCount, errorCount);
            }
        }
        
        vTaskDelay(pdMS_TO_TICKS(1));
    }
}

/* 优先级检查任务 */
void vPriorityCheckTask(void *pvParameters) {
    uint32_t priority = NVIC_GetPriority(USART1_IRQn);
    uint32_t maxSyscall = configMAX_SYSCALL_INTERRUPT_PRIORITY >> 4;
    
    printf("\r\n=== Interrupt Priority Analysis ===\r\n");
    printf("configMAX_SYSCALL_INTERRUPT_PRIORITY: %lu\r\n", maxSyscall);
    printf("UART IRQ Priority: %lu\r\n", priority);
    
    if (priority < maxSyscall) {
        printf("❌ ERROR: UART priority too high (not managed by FreeRTOS)\r\n");
        printf("   This will cause system instability!\r\n");
        printf("   Solution: Set priority >= %lu\r\n", maxSyscall);
    } else {
        printf("✓ CORRECT: UART priority properly configured\r\n");
    }
    printf("===================================\r\n\r\n");
    
    vTaskDelete(NULL);
}

/* 主函数 */
int main(void) {
    // 创建二值信号量
    xTestSemaphore = xSemaphoreCreateBinary();
    
    // 选择配置方式
    #ifdef USE_WRONG_CONFIG
        ConfigureUART_Wrong();   // 测试错误配置
    #else
        ConfigureUART_Correct(); // 使用正确配置
    #endif
    
    // 创建测试任务
    xTaskCreate(vPriorityCheckTask, "PriorityCheck", 
                configMINIMAL_STACK_SIZE * 2, NULL, 1, NULL);
    xTaskCreate(vStressTestTask, "StressTest", 
                configMINIMAL_STACK_SIZE * 2, NULL, 2, NULL);
    
    // 启动调度器
    vTaskStartScheduler();
    
    while (1);
}

4.5 调试技巧

1. 使用 configASSERT 检查

在 FreeRTOSConfig.h 中启用断言:

cpp 复制代码
#define configASSERT(x) \
    if ((x) == 0) { \
        taskDISABLE_INTERRUPTS(); \
        printf("ASSERT FAILED: %s:%d\r\n", __FILE__, __LINE__); \
        for(;;); \
    }
2. 编译时检查宏
cpp 复制代码
// 在 FromISR API 前添加
#define CHECK_ISR_PRIORITY() \
    do { \
        if (__get_IPSR() != 0) { \
            uint32_t irqn = __get_IPSR() - 16; \
            uint32_t priority = NVIC_GetPriority(irqn); \
            configASSERT(priority >= (configMAX_SYSCALL_INTERRUPT_PRIORITY >> 4)); \
        } \
    } while(0)
3. HardFault 分析

当发生 HardFault 时,检查:

  • HFSR

    寄存器:确定是否为 FORCED 标志

  • CFSR

    寄存器:查看具体错误类型(UFSR/BFSR/MMFSR)

  • 栈回溯:查看崩溃时的调用栈

cpp 复制代码
void HardFault_Handler(void) {
    __asm volatile (
        "TST lr, #4 \n"
        "ITE EQ \n"
        "MRSEQ r0, MSP \n"
        "MRSNE r0, PSP \n"
        "B HardFault_Handler_C \n"
    );
}

void HardFault_Handler_C(uint32_t *hardfault_args) {
    printf("HardFault!\r\n");
    printf("R0 = 0x%08X\r\n", hardfault_args[0]);
    printf("R1 = 0x%08X\r\n", hardfault_args[1]);
    printf("PC = 0x%08X\r\n", hardfault_args[6]);
    printf("HFSR = 0x%08X\r\n", SCB->HFSR);
    printf("CFSR = 0x%08X\r\n", SCB->CFSR);
    
    while(1);
}

五、ISR 使用最佳实践

5.1 黄金法则

  1. 快速进出

    :ISR 执行时间应 < 几十微秒

  2. 延迟处理

    :复杂逻辑交给任务完成

  3. 正确优先级

    :受管理中断 ≥ configMAX_SYSCALL

  4. 使用 FromISR API

    :永远不在 ISR 中调用常规 API

  5. 检查返回值

    :处理队列满等错误情况

5.2 禁止在 ISR 中使用的 API

禁止使用(会阻塞) 使用替代 API
xQueueSend xQueueSendFromISR
xSemaphoreTake 不应在 ISR 获取信号量
vTaskDelay 不应在 ISR 延时
xTaskCreate 在任务中创建任务
vTaskSuspend vTaskNotifyGiveFromISR 配合使用

5.3 性能优化

示例:高频中断优化

cpp 复制代码
#define BUFFER_SIZE 128
static char dmaBuffer[BUFFER_SIZE];
static volatile uint16_t dmaIndex = 0;

void DMA_IRQHandler(void) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    if (DMA_GetITStatus(DMA1_Stream5, DMA_IT_TCIF5)) {
        // 传输完成,通知任务处理整个缓冲区
        xTaskNotifyFromISR(dmaProcessTask, 
                          dmaIndex, 
                          eSetValueWithOverwrite,
                          &xHigherPriorityTaskWoken);
        
        // 切换缓冲区
        dmaIndex = (dmaIndex == 0) ? BUFFER_SIZE : 0;
        
        DMA_ClearITPendingBit(DMA1_Stream5, DMA_IT_TCIF5);
    }
    
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
相关推荐
Zeku2 天前
20260110 - Linux 驱动开发学习笔记:上下文、中断与休眠
stm32·freertos·linux驱动开发·linux应用开发
Zeku2 天前
20260110 - Linux驱动学习笔记:环形缓冲区与按键防丢失
stm32·freertos·linux驱动开发·linux应用开发
Zeku2 天前
20260111 - Linux驱动学习笔记:异步通知
笔记·stm32·freertos·linux驱动开发·linux应用开发
张世争3 天前
windows clion MingW cmake 编译运行 FreeRTOS
windows·freertos·mingw·cmake·clion
TangDuoduo00054 天前
【FreeRTOS推迟中断】
freertos
TangDuoduo00056 天前
【FreeRTOS空闲钩子函数、优先级函数、删除函数及调度器算法】
freertos
小曹要微笑6 天前
队列集详解
freertos·队列·队列集
wzfj123457 天前
FreeRTOS 学习方法
freertos
Zeku9 天前
20260103 - Linux平台总线LED驱动架构深度解析
stm32·freertos·linux驱动开发·linux应用开发