文章目录
-
- 每日一句正能量
- 导读
- 一、中断安全的核心原则
-
- [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中不阻塞 | 不可调用 vTaskDelay、xSemaphoreTake 等 |
| 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
欢迎 👍点赞✍评论⭐收藏,欢迎指正