FreeRTOS中断安全:FromISR函数的正确使用场景——临界区、延迟处理

文章目录

    • 每日一句正能量
    • 导读
    • 一、中断安全的核心原则
      • [1.1 为什么ISR中不能使用普通API?](#1.1 为什么ISR中不能使用普通API?)
      • [1.2 中断优先级与MAX_SYSCALL](#1.2 中断优先级与MAX_SYSCALL)
    • 二、FromISR函数深度解析
      • [2.1 pxHigherPriorityTaskWoken机制](#2.1 pxHigherPriorityTaskWoken机制)
      • [2.2 标准ISR模板](#2.2 标准ISR模板)
    • [三、临界区:任务级 vs ISR级](#三、临界区:任务级 vs ISR级)
      • [3.1 临界区的本质](#3.1 临界区的本质)
      • [3.2 ISR级临界区使用示例](#3.2 ISR级临界区使用示例)
      • [3.3 临界区的最佳实践](#3.3 临界区的最佳实践)
    • [四、延迟处理模式(Deferred Processing)](#四、延迟处理模式(Deferred Processing))
      • [4.1 为什么需要延迟处理?](#4.1 为什么需要延迟处理?)
      • [4.2 延迟处理的三种实现方式](#4.2 延迟处理的三种实现方式)
      • [4.3 xTimerPendFunctionCallFromISR------终极延迟处理](#4.3 xTimerPendFunctionCallFromISR——终极延迟处理)
    • 五、常见错误与正确用法对照
      • [5.1 错误1:在ISR中使用阻塞API](#5.1 错误1:在ISR中使用阻塞API)
      • [5.2 错误2:ISR中使用任务级临界区](#5.2 错误2:ISR中使用任务级临界区)
      • [5.3 错误3:忘记portYIELD_FROM_ISR](#5.3 错误3:忘记portYIELD_FROM_ISR)
      • [5.4 错误4:ISR中调用printf/vTaskDelay](#5.4 错误4:ISR中调用printf/vTaskDelay)
      • [5.5 错误5:中断优先级设置错误](#5.5 错误5:中断优先级设置错误)
    • 六、完整工程案例:多传感器中断采集系统
      • [6.1 FreeRTOSConfig.h 配置](#6.1 FreeRTOSConfig.h 配置)
      • [6.2 主应用代码](#6.2 主应用代码)
    • 七、调试与诊断技巧
      • [7.1 检测ISR中非法API调用](#7.1 检测ISR中非法API调用)
      • [7.2 中断延迟测量](#7.2 中断延迟测量)
      • [7.3 队列溢出检测](#7.3 队列溢出检测)
    • 八、总结与最佳实践清单
      • [8.1 黄金法则](#8.1 黄金法则)
      • [8.2 延迟处理决策树](#8.2 延迟处理决策树)
      • [8.3 性能对比](#8.3 性能对比)

每日一句正能量

每一条走上来的路都有其不得不跋涉的理由。

每个人的经历都是必要的,哪怕绕远、跌倒,也有它出现在你生命中的理由。不必羡慕他人的坦途,你走过的每一步都塑造了此刻的你。

导读

在嵌入式实时系统中,中断是硬件与软件交互的核心机制。然而,中断服务程序(ISR)运行在特殊的上下文环境中,与任务调度器共享内核数据结构,稍有不慎就会引发系统崩溃、数据竞争或实时性丧失。FreeRTOS提供了一套以 FromISR 结尾的中断安全API,但许多开发者对其使用场景、临界区管理和延迟处理机制理解不深。本文以STM32F407平台为实验载体,系统剖析FromISR函数的正确使用范式,深入讲解中断级临界区机制,并给出延迟处理模式的完整工程实践。


一、中断安全的核心原则

1.1 为什么ISR中不能使用普通API?

FreeRTOS内核通过调度器管理任务状态、就绪列表、阻塞列表等全局数据结构。当任务调用API(如 xQueueSend())时,调度器可能执行上下文切换,修改这些数据结构。而在ISR中,CPU正处于中断上下文,如果执行类似的调度操作,会导致以下问题:

  • 重入问题:ISR可能打断正在执行内核代码的任务,导致内核数据结构处于不一致状态
  • 调度器状态错误 :ISR中不允许任务切换,但普通API可能触发 vTaskSwitchContext()
  • 阻塞操作无效 :ISR中调用 vTaskDelay() 等阻塞函数会导致未定义行为

因此,FreeRTOS明确规定:在中断服务程序中,只能调用以 FromISR 结尾的API函数

图1 所示,FreeRTOS的FromISR API体系覆盖队列、信号量、任务通知、事件组和定时器五大类。这些函数的核心特点是:不阻塞、不直接触发调度、通过 pxHigherPriorityTaskWoken 参数延迟通知调度需求

1.2 中断优先级与MAX_SYSCALL

在ARM Cortex-M架构中,FreeRTOS使用 configMAX_SYSCALL_INTERRUPT_PRIORITY(或 configMAX_API_CALL_INTERRUPT_PRIORITY)定义了一个关键阈值:

复制代码
┌─────────────────────────────────────┐
│  优先级 0-4 (高)  │  禁止调用FromISR   │
│  (数值越小优先级越高)                │
├─────────────────────────────────────┤
│  优先级 5-15 (低) │  允许调用FromISR   │
│  (数值越大优先级越低)                │
└─────────────────────────────────────┘

关键理解 :Cortex-M使用"数值越小优先级越高"的约定。configMAX_SYSCALL_INTERRUPT_PRIORITY 通常设为5,意味着:

  • 优先级0-4的中断不能调用任何FreeRTOS API(包括FromISR版本)
  • 优先级5-15的中断可以安全调用FromISR API

二、FromISR函数深度解析

2.1 pxHigherPriorityTaskWoken机制

每个FromISR函数都有一个 BaseType_t *pxHigherPriorityTaskWoken 参数,这是理解中断安全的关键。

图4展示了两种场景:

场景一 :ISR释放信号量/发送队列,唤醒的任务优先级低于 当前运行任务。此时 pxHigherPriorityTaskWoken = pdFALSE,ISR返回后继续执行原任务。

场景二 :ISR释放信号量/发送队列,唤醒的任务优先级高于 当前运行任务。此时 pxHigherPriorityTaskWoken = pdTRUE,但不会立即切换 ,而是在ISR末尾通过 portYIELD_FROM_ISR() 触发上下文切换。

为什么不在FromISR内部直接切换? 因为ISR可能被嵌套(高优先级中断打断当前ISR),必须等所有嵌套ISR执行完毕后,在最后一个ISR退出时统一执行切换。

2.2 标准ISR模板

c 复制代码
/* 标准ISR模板 - 适用于所有FromISR场景 */
void TIM2_IRQHandler(void)
{
    /* 1. 必须初始化为pdFALSE */
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    /* 2. 清除中断标志(必须在最前面) */
    if(__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE))
    {
        __HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE);
        
        /* 3. 执行FromISR操作 */
        xQueueSendFromISR(xSensorQueue, &sensorData, &xHigherPriorityTaskWoken);
        
        /* 4. 可多次调用FromISR,xHigherPriorityTaskWoken会自动累积 */
        xEventGroupSetBitsFromISR(xEventGroup, SENSOR_READY_BIT, &xHigherPriorityTaskWoken);
    }
    
    /* 5. 退出时检查是否需要上下文切换 */
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

关键注意点

  • xHigherPriorityTaskWoken 必须初始化为 pdFALSE
  • 多个FromISR调用可以共用同一个变量,内部使用按位或累积
  • portYIELD_FROM_ISR() 必须在ISR最后调用,且只调用一次

三、临界区:任务级 vs ISR级

3.1 临界区的本质

临界区(Critical Section)是通过禁用中断实现的最简单互斥机制。FreeRTOS提供两套API:

图3详细对比了任务级和ISR级临界区的差异:

特性 任务级 taskENTER_CRITICAL() ISR级 taskENTER_CRITICAL_FROM_ISR()
返回值 返回中断状态,必须保存
嵌套支持 是(内部计数器) 是(依赖保存的状态值)
恢复方式 taskEXIT_CRITICAL() taskEXIT_CRITICAL_FROM_ISR(uxSaved)
使用场景 任务上下文 中断上下文
禁止调用 不可在ISR中使用! 不可在任务中使用!

3.2 ISR级临界区使用示例

c 复制代码
/* ISR中访问共享变量的正确方式 */
volatile uint32_t g_ulSharedCounter = 0;
volatile uint32_t g_ulSharedArray[10];

void EXTI_IRQHandler(void)
{
    UBaseType_t uxSavedInterruptStatus;
    
    /* 保存当前中断状态并进入临界区 */
    uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
    
    /* 在临界区内安全访问共享资源 */
    g_ulSharedCounter++;
    g_ulSharedArray[g_ulSharedCounter % 10] = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
    
    /* 退出临界区,恢复之前的中断状态 */
    taskEXIT_CRITICAL_FROM_ISR(uxSavedInterruptStatus);
    
    /* 注意:临界区内不可调用任何FreeRTOS API! */
}

3.3 临界区的最佳实践

c 复制代码
/* 错误:临界区内调用FreeRTOS API */
void BadISR(void)
{
    UBaseType_t uxSaved = taskENTER_CRITICAL_FROM_ISR();
    {
        xQueueSendFromISR(xQueue, &data, &xHPW);  /* ❌ 禁止! */
    }
    taskEXIT_CRITICAL_FROM_ISR(uxSaved);
}

/* 正确:先调用FromISR,再进入临界区访问共享变量 */
void GoodISR(void)
{
    BaseType_t xHPW = pdFALSE;
    uint32_t ulLocalCopy;
    
    /* Step 1: 先使用FromISR API */
    xQueueSendFromISR(xQueue, &data, &xHPW);
    
    /* Step 2: 再进入临界区访问共享变量 */
    UBaseType_t uxSaved = taskENTER_CRITICAL_FROM_ISR();
    {
        ulLocalCopy = g_ulSharedCounter;
        g_ulSharedCounter++;
    }
    taskEXIT_CRITICAL_FROM_ISR(uxSaved);
    
    portYIELD_FROM_ISR(xHPW);
}

四、延迟处理模式(Deferred Processing)

4.1 为什么需要延迟处理?

ISR的设计原则是快进快出。任何耗时操作(协议解析、数据计算、IO操作)都不应在ISR中执行。

图2对比了两种模式:

直接在ISR处理的问题

  • 中断延迟不可控(可能持续数毫秒)
  • 低优先级中断被屏蔽
  • 可能丢失其他中断事件
  • ISR栈空间有限(通常只有几百字节)

延迟到任务处理的优势

  • ISR执行时间 < 10μs
  • 低优先级中断正常响应
  • 不会丢失中断事件
  • 任务栈空间充足(可配置KB级)

4.2 延迟处理的三种实现方式

方式一:二值信号量 + 任务通知
c 复制代码
/* 最轻量的延迟处理 - 适合高频中断 */
SemaphoreHandle_t xDataReadySemaphore = NULL;

void ADC_IRQHandler(void)
{
    BaseType_t xHPW = pdFALSE;
    
    if(ADC1->SR & ADC_SR_EOC)
    {
        /* 仅读取数据并清除标志 */
        g_usADCValue = ADC1->DR;
        
        /* 释放信号量通知任务 */
        xSemaphoreGiveFromISR(xDataReadySemaphore, &xHPW);
    }
    
    portYIELD_FROM_ISR(xHPW);
}

void vADCTask(void *pvParameters)
{
    for(;;)
    {
        /* 阻塞等待中断通知 */
        if(xSemaphoreTake(xDataReadySemaphore, portMAX_DELAY) == pdTRUE)
        {
            /* 在任务上下文中执行耗时操作 */
            uint16_t usFiltered = KalmanFilter(g_usADCValue);
            ProcessADCData(usFiltered);
        }
    }
}
方式二:队列传递数据
c 复制代码
/* 适合需要传递数据的场景 */
QueueHandle_t xUARTQueue = NULL;

void USART_IRQHandler(void)
{
    BaseType_t xHPW = pdFALSE;
    uint8_t ucRxData;
    
    if(USART1->SR & USART_SR_RXNE)
    {
        ucRxData = (uint8_t)(USART1->DR & 0xFF);
        
        /* 将数据发送到队列,ISR中不解析 */
        xQueueSendFromISR(xUARTQueue, &ucRxData, &xHPW);
    }
    
    portYIELD_FROM_ISR(xHPW);
}

void vUARTTask(void *pvParameters)
{
    uint8_t ucData;
    uint8_t ucFrameBuffer[64];
    uint8_t ucIndex = 0;
    
    for(;;)
    {
        if(xQueueReceive(xUARTQueue, &ucData, portMAX_DELAY) == pdTRUE)
        {
            /* 在任务中解析协议帧 */
            ucFrameBuffer[ucIndex++] = ucData;
            
            if(ucIndex >= 64 || ucData == FRAME_END_BYTE)
            {
                ParseFrame(ucFrameBuffer, ucIndex);
                ucIndex = 0;
            }
        }
    }
}
方式三:任务通知(最高效)
c 复制代码
/* 最高效的延迟处理 - 无需创建队列/信号量 */
TaskHandle_t xSensorTaskHandle = NULL;

void TIM_IRQHandler(void)
{
    BaseType_t xHPW = pdFALSE;
    
    if(TIM2->SR & TIM_SR_UIF)
    {
        TIM2->SR &= ~TIM_SR_UIF;
        
        /* 直接通知特定任务 */
        vTaskNotifyGiveFromISR(xSensorTaskHandle, &xHPW);
    }
    
    portYIELD_FROM_ISR(xHPW);
}

void vSensorTask(void *pvParameters)
{
    /* 保存任务句柄,供ISR使用 */
    xSensorTaskHandle = xTaskGetCurrentTaskHandle();
    
    for(;;)
    {
        /* 阻塞等待通知 */
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        
        /* 执行传感器数据采集和处理 */
        ReadAllSensors();
        ProcessSensorFusion();
        UpdateControlLoop();
    }
}

4.3 xTimerPendFunctionCallFromISR------终极延迟处理

对于需要在守护任务上下文中执行复杂逻辑的场景,FreeRTOS提供了 xTimerPendFunctionCallFromISR()

c 复制代码
/* 将函数延迟到守护任务执行 */
void vComplexISRHandler(void)
{
    BaseType_t xHPW = pdFALSE;
    
    /* 将函数vDeferredFunction延迟到守护任务执行 */
    xTimerPendFunctionCallFromISR(
        vDeferredFunction,      /* 要执行的函数 */
        (void *)ulParameter1,   /* 参数1 */
        ulParameter2,           /* 参数2 */
        &xHPW
    );
    
    portYIELD_FROM_ISR(xHPW);
}

/* 此函数在守护任务上下文中执行 */
void vDeferredFunction(void *pvParameter1, uint32_t ulParameter2)
{
    /* 可以调用所有普通API,包括阻塞操作 */
    xSemaphoreTake(xSomeSemaphore, portMAX_DELAY);
    vTaskDelay(pdMS_TO_TICKS(10));
    xQueueSend(xSomeQueue, &data, portMAX_DELAY);
}

五、常见错误与正确用法对照

图5总结了五种最常见的错误及其正确用法。下面深入分析每种错误的根因:

5.1 错误1:在ISR中使用阻塞API

c 复制代码
/* ❌ 错误:ISR中调用阻塞函数 */
void BadISR(void)
{
    xSemaphoreTake(xSem, portMAX_DELAY);  /* 崩溃!ISR中不可阻塞 */
}

/* ✅ 正确:ISR中只释放信号量 */
void GoodISR(void)
{
    BaseType_t xHPW = pdFALSE;
    xSemaphoreGiveFromISR(xSem, &xHPW);  /* 仅释放 */
    portYIELD_FROM_ISR(xHPW);
}

5.2 错误2:ISR中使用任务级临界区

c 复制代码
/* ❌ 错误:ISR中使用任务级临界区 */
void BadISR(void)
{
    taskENTER_CRITICAL();  /* 未定义行为! */
    /* ... */
    taskEXIT_CRITICAL();
}

/* ✅ 正确:使用ISR级临界区 */
void GoodISR(void)
{
    UBaseType_t uxSaved = taskENTER_CRITICAL_FROM_ISR();
    /* ... */
    taskEXIT_CRITICAL_FROM_ISR(uxSaved);
}

5.3 错误3:忘记portYIELD_FROM_ISR

c 复制代码
/* ❌ 错误:忘记触发上下文切换 */
void BadISR(void)
{
    BaseType_t xHPW = pdFALSE;
    xQueueSendFromISR(xQueue, &data, &xHPW);
    /* 缺少 portYIELD_FROM_ISR(xHPW) */
    /* 结果:高优先级任务不会立即执行 */
}

/* ✅ 正确:总是调用portYIELD_FROM_ISR */
void GoodISR(void)
{
    BaseType_t xHPW = pdFALSE;
    xQueueSendFromISR(xQueue, &data, &xHPW);
    portYIELD_FROM_ISR(xHPW);  /* 必须调用 */
}

5.4 错误4:ISR中调用printf/vTaskDelay

c 复制代码
/* ❌ 错误:ISR中执行耗时操作 */
void BadISR(void)
{
    printf("Interrupt!");      /* printf可能使用互斥锁,导致阻塞 */
    vTaskDelay(pdMS_TO_TICKS(10));  /* 绝对禁止! */
}

/* ✅ 正确:ISR中只做通知 */
void GoodISR(void)
{
    BaseType_t xHPW = pdFALSE;
    vTaskNotifyGiveFromISR(xTaskHandle, &xHPW);
    portYIELD_FROM_ISR(xHPW);
}

5.5 错误5:中断优先级设置错误

c 复制代码
/* ❌ 错误:优先级设置过高 */
/* configMAX_SYSCALL_INTERRUPT_PRIORITY = 5 */
HAL_NVIC_SetPriority(TIM2_IRQn, 2, 0);  /* 优先级2 > 5(数值),不能调用FromISR */

/* ✅ 正确:优先级设置低于MAX_SYSCALL */
HAL_NVIC_SetPriority(TIM2_IRQn, 8, 0);  /* 优先级8 < 5(数值),可以调用FromISR */

注意 :STM32 HAL库中 HAL_NVIC_SetPriority() 的优先级参数使用"数值越小优先级越高"的约定,而 configMAX_SYSCALL_INTERRUPT_PRIORITY 也是数值越小优先级越高。因此,要安全调用FromISR,中断优先级数值必须大于等于 configMAX_SYSCALL_INTERRUPT_PRIORITY


六、完整工程案例:多传感器中断采集系统

图6展示了一个典型的多传感器系统架构,采用延迟处理模式。

6.1 FreeRTOSConfig.h 配置

c 复制代码
/* FreeRTOSConfig.h */
#define configUSE_PREEMPTION                    1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
#define configCPU_CLOCK_HZ                      168000000
#define configTICK_RATE_HZ                      1000
#define configMAX_PRIORITIES                    10

/* 中断安全关键配置 */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY    5  /* FreeRTOS可管理的中断优先级 */
#define configKERNEL_INTERRUPT_PRIORITY         15 /* 内核最低优先级 */

/* 软件定时器(用于延迟处理) */
#define configUSE_TIMERS                        1
#define configTIMER_TASK_PRIORITY               (configMAX_PRIORITIES - 1)
#define configTIMER_QUEUE_LENGTH                32
#define configTIMER_TASK_STACK_DEPTH            (configMINIMAL_STACK_SIZE * 4)

/* 断言和调试 */
#define configASSERT(x) if((x)==0) { taskDISABLE_INTERRUPTS(); for(;;); }
#define configCHECK_FOR_STACK_OVERFLOW          2

6.2 主应用代码

c 复制代码
/* main.c - 多传感器中断采集系统 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "timers.h"
#include "event_groups.h"
#include "stm32f4xx_hal.h"

/* ==================== 全局定义 ==================== */
#define SENSOR_QUEUE_LENGTH     16
#define SENSOR_ITEM_SIZE        sizeof(SensorData_t)
#define EVENT_ALL_SENSORS_READY (0x1F)  /* 5个传感器全部就绪 */

/* 传感器数据结构 */
typedef struct {
    uint32_t ulTimestamp;
    uint16_t usSensorID;
    float    fValue;
    uint16_t usRawData;
} SensorData_t;

/* 传感器ID枚举 */
typedef enum {
    SENSOR_ACCEL = 0,
    SENSOR_GYRO,
    SENSOR_TEMP,
    SENSOR_PRESS,
    SENSOR_ENCODER,
    SENSOR_COUNT
} SensorID_t;

/* 全局句柄 */
static QueueHandle_t xSensorQueues[SENSOR_COUNT] = {NULL};
static EventGroupHandle_t xSensorEventGroup = NULL;
static TaskHandle_t xFusionTaskHandle = NULL;

/* 传感器统计 */
static volatile uint32_t g_ulISREntryCount[SENSOR_COUNT] = {0};
static volatile uint32_t g_ulTaskProcessCount[SENSOR_COUNT] = {0};

/* ==================== ISR实现 ==================== */

/* 加速度计中断 - 使用任务通知 */
void EXTI0_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET)
    {
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
        g_ulISREntryCount[SENSOR_ACCEL]++;
        
        /* 轻量通知:仅设置通知 */
        vTaskNotifyGiveFromISR(xTaskAccelHandle, &xHigherPriorityTaskWoken);
    }
    
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

/* 陀螺仪中断 - 使用队列 */
void TIM3_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    if(__HAL_TIM_GET_FLAG(&htim3, TIM_FLAG_UPDATE) != RESET)
    {
        __HAL_TIM_CLEAR_IT(&htim3, TIM_IT_UPDATE);
        g_ulISREntryCount[SENSOR_GYRO]++;
        
        SensorData_t xData = {
            .ulTimestamp = xTaskGetTickCountFromISR(),
            .usSensorID = SENSOR_GYRO,
            .fValue = 0.0f,
            .usRawData = (uint16_t)(MPU6050_ReadGyro() & 0xFFFF)
        };
        
        /* 发送数据到队列,不阻塞 */
        xQueueSendFromISR(xSensorQueues[SENSOR_GYRO], &xData, &xHigherPriorityTaskWoken);
    }
    
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

/* 温度传感器中断 - 使用队列 + 临界区保护共享计数器 */
void TIM4_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    UBaseType_t uxSavedStatus;
    
    if(__HAL_TIM_GET_FLAG(&htim4, TIM_FLAG_UPDATE) != RESET)
    {
        __HAL_TIM_CLEAR_IT(&htim4, TIM_IT_UPDATE);
        
        /* 使用ISR级临界区保护共享计数器 */
        uxSavedStatus = taskENTER_CRITICAL_FROM_ISR();
        {
            g_ulISREntryCount[SENSOR_TEMP]++;
        }
        taskEXIT_CRITICAL_FROM_ISR(uxSavedStatus);
        
        SensorData_t xData = {
            .ulTimestamp = xTaskGetTickCountFromISR(),
            .usSensorID = SENSOR_TEMP,
            .fValue = DS18B20_ReadTemp(),
            .usRawData = 0
        };
        
        xQueueSendFromISR(xSensorQueues[SENSOR_TEMP], &xData, &xHigherPriorityTaskWoken);
    }
    
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

/* 编码器中断 - 使用信号量 */
void EXTI1_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_1) != RESET)
    {
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_1);
        g_ulISREntryCount[SENSOR_ENCODER]++;
        
        /* 使用信号量通知编码器任务 */
        xSemaphoreGiveFromISR(xEncoderSemaphore, &xHigherPriorityTaskWoken);
    }
    
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

/* ==================== 任务实现 ==================== */

/* 加速度任务 - 使用任务通知 */
static TaskHandle_t xTaskAccelHandle = NULL;
void vAccelTask(void *pvParameters)
{
    xTaskAccelHandle = xTaskGetCurrentTaskHandle();
    
    for(;;)
    {
        /* 阻塞等待中断通知 */
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        
        /* 读取传感器数据 */
        int16_t sX, sY, sZ;
        ADXL345_ReadAccel(&sX, &sY, &sZ);
        
        /* 数据滤波 */
        float fAccelX = KalmanFilter_Accel((float)sX);
        float fAccelY = KalmanFilter_Accel((float)sY);
        float fAccelZ = KalmanFilter_Accel((float)sZ);
        
        /* 更新统计 */
        g_ulTaskProcessCount[SENSOR_ACCEL]++;
        
        /* 设置事件组标志 */
        xEventGroupSetBits(xSensorEventGroup, (1 << SENSOR_ACCEL));
    }
}

/* 陀螺仪任务 - 使用队列 */
void vGyroTask(void *pvParameters)
{
    SensorData_t xData;
    
    for(;;)
    {
        /* 阻塞等待队列数据 */
        if(xQueueReceive(xSensorQueues[SENSOR_GYRO], &xData, portMAX_DELAY) == pdTRUE)
        {
            /* 卡尔曼滤波 */
            float fGyroX = KalmanFilter_Gyro((float)(int16_t)xData.usRawData);
            
            /* 更新统计 */
            g_ulTaskProcessCount[SENSOR_GYRO]++;
            
            /* 设置事件组标志 */
            xEventGroupSetBits(xSensorEventGroup, (1 << SENSOR_GYRO));
        }
    }
}

/* 温度任务 - 使用队列 */
void vTempTask(void *pvParameters)
{
    SensorData_t xData;
    
    for(;;)
    {
        if(xQueueReceive(xSensorQueues[SENSOR_TEMP], &xData, portMAX_DELAY) == pdTRUE)
        {
            /* 温度补偿计算 */
            float fCompensated = TemperatureCompensation(xData.fValue);
            
            g_ulTaskProcessCount[SENSOR_TEMP]++;
            xEventGroupSetBits(xSensorEventGroup, (1 << SENSOR_TEMP));
        }
    }
}

/* 编码器任务 - 使用信号量 */
static SemaphoreHandle_t xEncoderSemaphore = NULL;
void vEncoderTask(void *pvParameters)
{
    for(;;)
    {
        /* 阻塞等待信号量 */
        if(xSemaphoreTake(xEncoderSemaphore, portMAX_DELAY) == pdTRUE)
        {
            /* 计算转速 */
            int32_t lPulseCount = Encoder_GetPulse();
            float fRPM = (float)lPulseCount * 60.0f / ENCODER_PPR;
            
            g_ulTaskProcessCount[SENSOR_ENCODER]++;
            xEventGroupSetBits(xSensorEventGroup, (1 << SENSOR_ENCODER));
        }
    }
}

/* 数据融合任务 - 同步所有传感器 */
void vFusionTask(void *pvParameters)
{
    const TickType_t xFusionPeriod = pdMS_TO_TICKS(10);  /* 100Hz融合 */
    TickType_t xLastWakeTime = xTaskGetTickCount();
    
    for(;;)
    {
        /* 等待所有传感器就绪或超时 */
        EventBits_t xBits = xEventGroupWaitBits(
            xSensorEventGroup,
            EVENT_ALL_SENSORS_READY,
            pdTRUE,           /* 清除位 */
            pdFALSE,          /* 不等待所有位 */
            xFusionPeriod
        );
        
        /* 执行数据融合算法 */
        SensorFusion_Update();
        
        /* 输出控制量 */
        ControlLoop_Update();
        
        /* 周期性执行 */
        vTaskDelayUntil(&xLastWakeTime, xFusionPeriod);
    }
}

/* ==================== 初始化和主函数 ==================== */

static void vCreateRTOSObjects(void)
{
    /* 创建传感器队列 */
    for(int i = 0; i < SENSOR_COUNT; i++)
    {
        xSensorQueues[i] = xQueueCreate(SENSOR_QUEUE_LENGTH, SENSOR_ITEM_SIZE);
        configASSERT(xSensorQueues[i]);
    }
    
    /* 创建事件组 */
    xSensorEventGroup = xEventGroupCreate();
    configASSERT(xSensorEventGroup);
    
    /* 创建编码器信号量 */
    xEncoderSemaphore = xSemaphoreCreateBinary();
    configASSERT(xEncoderSemaphore);
    
    /* 创建任务 */
    xTaskCreate(vAccelTask,   "Accel",   256, NULL, 6, NULL);
    xTaskCreate(vGyroTask,    "Gyro",    256, NULL, 6, NULL);
    xTaskCreate(vTempTask,    "Temp",    256, NULL, 5, NULL);
    xTaskCreate(vEncoderTask, "Encoder", 256, NULL, 7, NULL);
    xTaskCreate(vFusionTask,  "Fusion",  512, NULL, 8, &xFusionTaskHandle);
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    
    /* 初始化硬件... */
    ADXL345_Init();
    MPU6050_Init();
    DS18B20_Init();
    Encoder_Init();
    
    /* 创建RTOS对象 */
    vCreateRTOSObjects();
    
    /* 启动调度器 */
    vTaskStartScheduler();
    
    /* 不会到达此处 */
    for(;;);
}

七、调试与诊断技巧

7.1 检测ISR中非法API调用

c 复制代码
/* 自定义断言钩子,捕获ISR中的非法调用 */
void vApplicationAssertHook(const char *pcFile, unsigned long ulLine)
{
    /* 检查是否在ISR中 */
    if(xPortInIsrContext())
    {
        printf("ASSERT FAILED IN ISR: %s:%lu\n", pcFile, ulLine);
        /* 记录到非易失存储器 */
        LogErrorToFlash(pcFile, ulLine);
    }
    
    taskDISABLE_INTERRUPTS();
    for(;;);
}

7.2 中断延迟测量

c 复制代码
/* 使用DWT周期计数器测量ISR执行时间 */
volatile uint32_t g_ulMaxISRCycles = 0;

void TIM2_IRQHandler(void)
{
    uint32_t ulStart = DWT->CYCCNT;  /* 读取周期计数器 */
    BaseType_t xHPW = pdFALSE;
    
    /* ISR逻辑... */
    
    uint32_t ulEnd = DWT->CYCCNT;
    uint32_t ulCycles = ulEnd - ulStart;
    
    if(ulCycles > g_ulMaxISRCycles)
    {
        g_ulMaxISRCycles = ulCycles;
    }
    
    portYIELD_FROM_ISR(xHPW);
}

7.3 队列溢出检测

c 复制代码
/* 检测ISR中队列发送失败(队列满) */
void UART_IRQHandler(void)
{
    BaseType_t xHPW = pdFALSE;
    
    if(USART1->SR & USART_SR_RXNE)
    {
        uint8_t ucData = USART1->DR;
        
        if(xQueueSendFromISR(xUARTQueue, &ucData, &xHPW) != pdPASS)
        {
            /* 队列满,记录溢出 */
            g_ulQueueOverflowCount++;
        }
    }
    
    portYIELD_FROM_ISR(xHPW);
}

八、总结与最佳实践清单

8.1 黄金法则

规则 说明
ISR中只调用FromISR API 普通API会导致系统崩溃
ISR中不阻塞 不可调用 vTaskDelayxSemaphoreTake
ISR中不做耗时操作 只做读取、通知、清除标志
总是调用 portYIELD_FROM_ISR 确保高优先级任务及时响应
正确设置中断优先级 必须低于 configMAX_SYSCALL_INTERRUPT_PRIORITY
使用ISR级临界区 保存/恢复中断状态

8.2 延迟处理决策树

复制代码
中断发生
    │
    ▼
是否需要传递数据? ──是──→ 使用 xQueueSendFromISR
    │否
    ▼
是否需要通知特定任务? ──是──→ 使用 vTaskNotifyGiveFromISR
    │否
    ▼
是否需要同步多个任务? ──是──→ 使用 xSemaphoreGiveFromISR
    │否
    ▼
是否需要设置事件标志? ──是──→ 使用 xEventGroupSetBitsFromISR
    │否
    ▼
是否需要延迟执行复杂逻辑? ──是──→ 使用 xTimerPendFunctionCallFromISR
    │否
    ▼
仅清除中断标志,无需通知

8.3 性能对比

通知方式 执行时间 内存开销 适用场景
任务通知 ~1μs 0字节 单任务通知,最高效
二值信号量 ~2μs 约80字节 简单同步
计数信号量 ~2μs 约80字节 多事件计数
队列 ~3μs 队列长度×数据大小 数据传递
事件组 ~3μs 约100字节 多事件同步
延迟函数 ~5μs 守护任务栈 复杂逻辑延迟

转载自:https://blog.csdn.net/u014727709/article/details/162475988

欢迎 👍点赞✍评论⭐收藏,欢迎指正