一、任务创建与删除
1. xTaskCreate() 函数
作用:动态创建一个新任务,并分配所需的堆栈和任务控制块(TCB)内存。
函数原型:
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, // 任务函数指针
const char * const pcName, // 任务名称(字符串标识)
configSTACK_DEPTH_TYPE usStackDepth, // 堆栈大小(以字为单位)
void *pvParameters, // 传递给任务的参数
UBaseType_t uxPriority, // 任务优先级(0为最低,数值越大优先级越高)
TaskHandle_t *pvCreatedTask // 任务句柄(用于后续引用该任务)
);
参数详解:
-
pvTaskCode
任务函数的入口地址。任务必须是一个无限循环或显式调用vTaskDelete()
终止的函数。
示例任务函数形式:void vTaskFunction(void *pvParameters) { while(1) { // 任务逻辑 } }
-
pcName
任务的字符串名称,用于调试和追踪。名称长度由configMAX_TASK_NAME_LEN
定义(默认为16字符)。 -
usStackDepth
任务的堆栈深度,单位为字(word)
。例如,若堆栈需要1024字节,且处理器为32位(4字节/字),则设为256。需根据任务需求合理分配,避免溢出或浪费内存。 -
pvParameters
传递给任务的参数指针,可以是任意类型的数据结构或NULL
。 -
uxPriority
任务优先级,范围从0
(最低)到configMAX_PRIORITIES-1
。优先级高的任务抢占低优先级任务。 -
pvCreatedTask
输出参数,返回任务句柄。可用于后续操作(如删除任务、修改优先级等)。若无需使用,可设为NULL
。
返回值:
pdPASS
:任务创建成功。errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
:内存不足,无法分配堆栈或TCB。
示例:
TaskHandle_t xTaskHandle = NULL;
void vTaskExample(void *pvParameters) {
while(1) {
printf("Task is running.\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
// 创建任务
xTaskCreate(vTaskExample, "ExampleTask", 256, NULL, 1, &xTaskHandle);
2. vTaskDelete() 函数
作用:删除一个任务,释放其占用的内存(TCB和堆栈由空闲任务回收)。
函数原型:
void vTaskDelete(TaskHandle_t xTaskToDelete); // 任务句柄或NULL
参数详解:
- xTaskToDelete
要删除的任务句柄。若传递NULL
,则删除当前正在运行的任务自身。
关键行为:
- 任务被标记为删除,立即退出运行状态。
- 任务的TCB和堆栈由空闲任务(Idle Task)自动回收(需确保空闲任务有执行机会)。
- 若任务持有动态分配的内存、信号量等资源,需在删除前手动释放,否则会导致内存泄漏。
示例:
void vSelfDeletingTask(void *pvParameters) {
printf("Task will delete itself after 5 runs.\n");
for(int i=0; i<5; i++) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
vTaskDelete(NULL); // 删除自身
}
3. 使用注意事项
-
资源管理:
任务删除前需释放其占用的资源(如内存、文件句柄、信号量)。FreeRTOS不会自动处理这些资源。 -
空闲任务的角色:
空闲任务负责清理被删除任务的TCB和堆栈。若使用vTaskDelete()
,需确保空闲任务能运行(即不在其他任务中无限阻塞)。 -
任务自删除:
任务可通过vTaskDelete(NULL)
删除自身,但需注意不要在删除后访问局部变量或执行后续代码。 -
动态分配与静态分配:
xTaskCreate()
动态分配内存,而xTaskCreateStatic()
使用预分配内存。动态分配需启用configSUPPORT_DYNAMIC_ALLOCATION=1
(默认启用)。
4. 完整示例
#include "FreeRTOS.h"
#include "task.h"
TaskHandle_t xTaskHandle = NULL;
void vExampleTask(void *pvParameters) {
char *taskName = (char *)pvParameters;
for(int i=0; i<3; i++) {
printf("%s: Run count %d\n", taskName, i+1);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
vTaskDelete(xTaskHandle); // 删除自身
}
int main() {
xTaskCreate(vExampleTask, "Task1", 256, "Task-A", 1, &xTaskHandle);
vTaskStartScheduler(); // 启动调度器
return 0;
}
5. 常见问题
- 堆栈溢出:堆栈大小不足会导致
usStackDepth
溢出,可使用FreeRTOS的堆栈溢出检测功能(configCHECK_FOR_STACK_OVERFLOW
)。 - 优先级反转:高优先级任务被低优先级任务阻塞,需使用互斥锁的优先级继承机制。
- 内存不足:动态创建任务失败时检查堆大小(
configTOTAL_HEAP_SIZE
)。
通过合理使用xTaskCreate()
和vTaskDelete()
,可以高效管理FreeRTOS中的任务生命周期,确保系统资源的最优利用。
二、任务挂起与恢复
1. vTaskSuspend()
功能
挂起指定任务,使其脱离调度器的管理,任务进入挂起状态(Suspended),不再参与调度。
函数原型
void vTaskSuspend(TaskHandle_t xTaskToSuspend);
- 参数:
xTaskToSuspend
为目标任务的句柄。若为NULL
,则挂起当前任务。 - 返回值:无。
关键点
- 状态变化:任务被挂起后,无论优先级高低,都不会被调度执行。
- 资源释放:挂起时不会自动释放已持有的资源(如信号量、互斥锁),需手动处理。
- 自身挂起:若任务挂起自己,必须由其他任务或中断恢复它,否则会永久阻塞。
- 适用场景:
- 调试时临时冻结任务。
- 暂停非关键任务以释放CPU资源。
- 等待外部事件前的临时挂起。
示例
vTaskSuspend(xTaskHandle); // 挂起指定任务
vTaskSuspend(NULL); // 挂起当前任务(自身)
2. vTaskResume()
功能
恢复被挂起的任务,使其重新进入就绪状态(Ready),参与调度。
函数原型
void vTaskResume(TaskHandle_t xTaskToResume);
- 参数:
xTaskToResume
为目标任务的句柄。 - 返回值:无。
关键点
- 恢复条件:仅对处于挂起状态的任务有效,若任务未被挂起则无操作。
- 调度触发:若恢复的任务优先级高于当前任务,会触发一次上下文切换。
- 调用限制:不可在中断中使用,需通过
xTaskResumeFromISR()
处理中断恢复。 - 无嵌套计数:无论任务被挂起多少次,一次
vTaskResume()
即可恢复。
示例
vTaskResume(xTaskHandle); // 恢复指定任务
3. xTaskResumeFromISR()
功能
在**中断服务程序(ISR)**中恢复被挂起的任务,并返回是否需要手动触发上下文切换。
函数原型
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume);
- 参数:
xTaskToResume
为目标任务的句柄。 - 返回值:
pdTRUE
:恢复的任务优先级高于当前任务,需手动触发上下文切换。pdFALSE
:无需立即切换。
关键点
- 中断安全:专为ISR设计,避免在中断中调用非ISR安全函数。
- 手动切换:若返回
pdTRUE
,应在退出中断前调用portYIELD_FROM_ISR()
。 - 优先级抢占:恢复高优先级任务可能导致当前任务被抢占,需评估实时性影响。
示例
BaseType_t xYieldRequired = xTaskResumeFromISR(xTaskHandle);
if (xYieldRequired == pdTRUE) {
portYIELD_FROM_ISR(); // 手动触发上下文切换
}
4. vTaskSuspendAll()
功能
挂起调度器,阻止任务切换,但不停止当前任务的执行。所有任务保持当前状态,仅暂停调度器的决策。
函数原型
void vTaskSuspendAll(void);
- 参数:无。
- 返回值:无。
关键点
- 调度器挂起:
- 任务切换被禁止,但当前任务继续执行。
- 中断仍可触发,但ISR结束后不会切换到其他任务。
- 嵌套支持:允许多次调用
vTaskSuspendAll()
,需对应次数的xTaskResumeAll()
恢复。 - 适用场景:
- 保护共享资源的原子操作(如读写全局变量)。
- 执行时间敏感的代码段,避免任务切换干扰。
示例
vTaskSuspendAll(); // 挂起调度器
// 执行原子操作(如读写共享数据)
xTaskResumeAll(); // 恢复调度器
5. xTaskResumeAll()
功能
恢复被挂起的调度器,重新允许任务切换,并返回是否有挂起的上下文切换请求。
函数原型
BaseType_t xTaskResumeAll(void);
- 参数:无。
- 返回值:
pdTRUE
:恢复调度器后需要触发上下文切换。pdFALSE
:无需切换。
关键点
- 嵌套处理:需与
vTaskSuspendAll()
成对调用,直到所有挂起请求被解除。 - 上下文切换:恢复后可能立即发生任务抢占(由优先级决定)。
- 资源管理:恢复前需确保共享资源操作已完成。
示例
vTaskSuspendAll();
// 临界区操作
BaseType_t xSwitchRequired = xTaskResumeAll();
if (xSwitchRequired == pdTRUE) {
taskYIELD(); // 主动让出CPU(可选)
}
对比与总结
函数 | 作用对象 | 调用上下文 | 关键特性 | 适用场景 |
---|---|---|---|---|
vTaskSuspend() | 单个任务 | 任务 | 挂起任务,需手动恢复 | 调试、临时暂停任务 |
vTaskResume() | 单个任务 | 任务 | 恢复任务,不可在中断使用 | 任务间协作 |
xTaskResumeFromISR() | 单个任务 | 中断 | 中断中恢复任务,需处理上下文切换 | ISR触发任务恢复 |
vTaskSuspendAll() | 调度器(全部任务) | 任务/中断 | 挂起调度器,允许嵌套调用 | 原子操作、保护共享资源 |
xTaskResumeAll() | 调度器(全部任务) | 任务/中断 | 恢复调度器,返回是否需要切换 | 结束临界区操作 |
使用注意事项
-
优先级与抢占:
- 恢复高优先级任务可能立即抢占当前任务,需确保代码逻辑安全。
- 在临界区中挂起调度器时,需避免长时间阻塞,否则影响系统实时性。
-
中断安全:
xTaskResumeFromISR()
必须在中断中使用,且需处理返回值。- 在中断中避免调用
vTaskSuspendAll()
,可能导致不可预测的行为。
-
资源管理:
- 挂起任务前需释放其占用的资源(如信号量、内存),防止死锁。
- 使用
vTaskSuspendAll()
时,确保临界区操作快速完成,避免中断延迟。
-
调度器挂起与任务挂起:
vTaskSuspendAll()
挂起的是调度器(任务仍可运行),而vTaskSuspend()
挂起的是单个任务。- 调度器挂起期间,ISR仍可运行,但任务切换被禁止。
典型应用场景
-
共享资源保护:
vTaskSuspendAll(); // 禁止任务切换 // 安全读写共享数据 xTaskResumeAll(); // 恢复调度器
-
任务同步:
void vTaskA(void *pvParam) { // 等待事件 vTaskSuspend(NULL); // 挂起自身 } void vTaskB(void *pvParam) { // 触发事件后恢复TaskA vTaskResume(xTaskAHandle); }
-
中断恢复任务:
void vISR_Handler(void) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; xHigherPriorityTaskWoken = xTaskResumeFromISR(xTaskHandle); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); }
三、中断的应用
1. 中断优先级与 FreeRTOS 的交互
FreeRTOS 需要管理中断服务函数的优先级,因为:
- 临界区保护:当 FreeRTOS 进入临界区(如任务切换、资源分配)时,需暂时屏蔽某些中断,防止数据竞争。
- API 安全性:若中断服务函数调用了 FreeRTOS 的 API(如
xQueueSendFromISR
),必须确保这些操作不会被更高优先级的中断破坏。
2. configMAX_SYSCALL_INTERRUPT_PRIORITY
的作用
这是 FreeRTOS 配置文件中定义的阈值,用于划分中断优先级的“可管理范围”:
- 低于等于此阈值的中断:
- 可被 FreeRTOS 屏蔽(通过 BASEPRI 寄存器)。
- 允许调用 FreeRTOS API(如队列操作、信号量等)。
- 称为“受管理的中断”。
- 高于此阈值的中断:
- 不会被 FreeRTOS 屏蔽,具有最高实时性。
- 禁止调用 FreeRTOS API(可能导致系统不一致)。
- 称为“非受管理中断”,通常用于硬件紧急事件(如看门狗)。
3. 配置示例(以 ARM Cortex-M 为例)
假设优先级数值越小优先级越高(ARM Cortex-M 的典型配置):
// FreeRTOSConfig.h
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 5 // 阈值设为优先级 5
- 优先级 0~4:非受管理中断,不可调用 FreeRTOS API,响应最快。
- 优先级 5~15:受管理中断,可调用 FreeRTOS API,但可能被临界区屏蔽。
4. 常见问题与注意事项
- 优先级数值方向:不同处理器可能定义相反(例如数值越大优先级越高),需根据硬件手册调整配置。
- API 调用限制:若高优先级中断误用 FreeRTOS API,会导致未定义行为(如系统崩溃)。
- 实时性权衡:非受管理中断适合实时性要求极高的场景(如电机控制),但需自行处理所有逻辑。
5. 实际应用场景
- 受管理中断:处理传感器数据时,通过队列(
xQueueSendFromISR
)将数据发送给任务。 - 非受管理中断:处理硬件故障(如电源掉电),立即执行关键操作,无需与 FreeRTOS 交互。
四、临界段的应用
1、普通代码调用临界段
**进入临界段 taskENTER_CRITICAL()
**
退出临界段 taskEXIT_CRITICAL()
用途
- 任务上下文:在普通任务代码中使用,保护共享资源的访问。
- 作用:通过提升中断屏蔽级别(如设置BASEPRI寄存器)或禁用中断,确保当前任务独占执行。
工作机制
taskENTER_CRITICAL()
:
保存当前中断状态(如中断屏蔽寄存器值),然后屏蔽特定优先级以下的中断(不一定是所有中断,取决于配置)。支持嵌套调用,内部通过计数器跟踪嵌套层级。taskEXIT_CRITICAL()
:
恢复之前保存的中断状态。仅当嵌套计数器归零时,才会实际恢复中断屏蔽级别。
示例
taskENTER_CRITICAL(); // 进入临界区
// 操作共享资源(如全局变量、外设)
taskEXIT_CRITICAL(); // 退出临界区
注意
- 短时使用:临界区应尽量简短,避免影响实时性。
- 不可在ISR中使用:否则可能导致中断状态错误恢复。
2、中断中调用临界段
**进入临界段 taskENTER_CRITICAL_FROM_ISR()
**
退出临界段 taskEXIT_CRITICAL_FROM_ISR()
用途
- 中断上下文:在中断服务例程(ISR)中使用,保护共享资源。
- 作用:调整中断屏蔽级别,允许在ISR中安全访问共享资源。
工作机制
taskENTER_CRITICAL_FROM_ISR()
:
返回当前中断屏蔽状态(如UBaseType_t
类型),并临时提升中断屏蔽级别。taskEXIT_CRITICAL_FROM_ISR(x)
:
接受之前保存的中断状态(参数x
),恢复中断屏蔽级别。
示例
void vInterruptHandler(void) {
UBaseType_t uxSavedStatus;
uxSavedStatus = taskENTER_CRITICAL_FROM_ISR(); // 进入临界区
// 操作共享资源
taskEXIT_CRITICAL_FROM_ISR(uxSavedStatus); // 退出临界区
}
注意
- 参数传递:必须保存
taskENTER_CRITICAL_FROM_ISR()
的返回值,并在退出时传递。 - 不可在任务中使用:否则可能导致中断状态错误。
关键区别
特性 | 任务版本(无_FROM_ISR ) | ISR版本(带_FROM_ISR ) |
---|---|---|
使用上下文 | 普通任务 | 中断服务例程(ISR) |
状态保存方式 | 自动管理(内部计数器) | 需手动保存返回值 |
嵌套支持 | 支持 | 支持 |
中断恢复机制 | 自动恢复至进入前的状态 | 需显式传递保存的状态 |
最佳实践
-
区分上下文:
- 任务中使用
taskENTER_CRITICAL()
/taskEXIT_CRITICAL()
。 - ISR中使用
_FROM_ISR
版本。
- 任务中使用
-
避免长时间阻塞:
临界区内不要执行耗时操作(如等待信号量)。 -
嵌套处理:
确保ENTER
和EXIT
调用次数匹配,防止状态未恢复。 -
替代方案:
对于复杂资源保护,优先考虑信号量(Semaphore)或互斥量(Mutex)。
底层实现(简析)
- BASEPRI寄存器:
FreeRTOS通过设置ARM Cortex-M的BASEPRI寄存器,屏蔽低于某优先级的中断,而非完全关中断,确保高优先级中断(如系统节拍)仍可响应。 - 可移植性:
具体实现依赖硬件,不同处理器可能通过不同方式屏蔽中断。
五、FreeRTOS 任务管理函数全解析
1. 优先级管理
动态调整任务优先级是实时系统的核心能力,以下函数帮助实现灵活调度。
1.1 获取任务优先级
- 函数:
uxTaskPriorityGet(TaskHandle_t xTask)
- 参数:
xTask
: 目标句柄(NULL
表示当前任务)
- 返回值: 当前优先级数值(0 为最低)
- 典型场景: 在任务协作时保存原优先级以便恢复
UBaseType_t original = uxTaskPriorityGet(xWorkerTask);
1.2 设置任务优先级
- 函数:
vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority)
- 参数:
xTask
: 目标句柄(NULL
为当前任务)uxNewPriority
: 新优先级(必须小于configMAX_PRIORITIES
)
- 注意: 避免在中断中频繁调用
// 紧急任务优先处理
vTaskPrioritySet(xCriticalTask, configMAX_PRIORITIES - 1);
2. 任务信息统计
监控系统任务状态与资源使用情况,用于系统优化与调试。
2.1 获取任务总数
- 函数:
uxTaskGetNumberOfTasks()
- 返回值: 当前存在的任务总数(包含所有状态)
- 用途: 内存分配前的数量检查
UBaseType_t taskCount = uxTaskGetNumberOfTasks();
2.2 系统状态快照
- 函数:
uxTaskGetSystemState(TaskStatus_t *arr, UBaseType_t size, uint32_t *totalTime)
- 关键参数:
arr
: 接收状态的结构体数组size
: 数组容量(需≥实际任务数)totalTime
: 总运行时间指针(需启用时间统计)
- 输出字段: 任务名、状态、优先级、堆栈使用等
TaskStatus_t stats[5];
uint32_t time;
UBaseType_t activeNum = uxTaskGetSystemState(stats, 5, &time);
2.3 单任务详细信息
- 函数:
vTaskGetInfo(TaskHandle_t xTask, TaskStatus_t *info, BaseType_t getStack, eTaskState getState)
- 参数控制:
getStack
:pdTRUE
计算剩余堆栈getState
:eInvalid
跳过状态获取
- 示例:
TaskStatus_t detail;
vTaskGetInfo(xTask, &detail, pdTRUE, eInvalid);
3. 任务句柄操作
通过名称或运行时状态获取任务引用句柄。
3.1 获取当前任务句柄
- 函数:
xTaskGetCurrentTaskHandle()
- 典型应用: 在匿名函数中自引用
TaskHandle_t myself = xTaskGetCurrentTaskHandle();
3.2 按名称查找句柄
- 函数:
xTaskGetHandle(const char *name)
- 注意: 名称需唯一且大小写敏感
TaskHandle_t handle = xTaskGetHandle("CAN_Process");
if(handle == NULL) {/* 错误处理 */}
4. 堆栈空间监控
预防堆栈溢出导致的内存损坏问题。
高水位线检测
- 函数:
uxTaskGetStackHighWaterMark(TaskHandle_t xTask)
- 返回值: 历史最小剩余堆栈(单位:字)
- 安全阈值: 建议保持至少 20% 余量
// 监控自身堆栈
UBaseType_t waterMark = uxTaskGetStackHighWaterMark(NULL);
if(waterMark < 50) vTaskResume(xCleanupTask);
5. 任务状态查询
精确诊断任务所处的调度状态。
状态枚举获取
- 函数:
eTaskGetState(TaskHandle_t xTask)
- 返回状态:
eReady
/eBlocked
/eSuspended
/eDeleted
- 典型应用: 调试死锁时检查任务阻塞状态
if(eTaskGetState(xTask) == eBlocked) {
// 检查等待的资源
}
6. 调试与性能分析
生成可视化报告辅助系统调优。
6.1 文本化任务列表
-
函数:
vTaskList(char *buffer)
-
依赖配置:
#define configUSE_TRACE_FACILITY 1 #define configUSE_STATS_FORMATTING_FUNCTIONS 1
-
输出示例:
Task State Prio Stack Num IDLE R 0 54 1 UART_Tx B 3 120 2
6.2 运行时统计
-
函数:
vTaskGetRunTimeStats(char *buffer)
-
硬件依赖: 需实现高精度计时器
-
配置步骤:
// 在port层配置计时器 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
-
输出示例:
Task Runtime % Logger 12000 15% Sensor 65000 81%
关键实践指南
优先级调整策略
- 紧急响应: 临时提升中断服务任务的优先级
- 优先级反转处理: 使用互斥量的优先级继承机制替代手动调整
堆栈优化技巧
-
通过高水位线计算实际需求:
// 创建时分配的堆栈大小 #define TASK_STACK_SIZE 128 // 实际使用的峰值 size_t used = TASK_STACK_SIZE - uxTaskGetStackHighWaterMark(NULL);
-
为中断嵌套和函数调用留出余量
统计函数最佳实践
-
缓冲区管理: 根据任务数量动态分配内存
UBaseType_t num = uxTaskGetNumberOfTasks(); TaskStatus_t *buf = pvPortMalloc(num * sizeof(TaskStatus_t));
-
定时输出: 在监控任务中周期性打印统计信息
void vStatTask(void *param) { while(1) { vTaskList(debugBuffer); sendToHost(debugBuffer); vTaskDelay(pdMS_TO_TICKS(5000)); } }
典型应用场景
场景 1:紧急任务处理
void vEmergencyHandler() {
// 保存原优先级
UBaseType_t normalPrio = uxTaskPriorityGet(NULL);
// 提升至最高优先级
vTaskPrioritySet(NULL, configMAX_PRIORITIES - 1);
// 执行关键操作
writeCriticalData();
// 恢复原优先级
vTaskPrioritySet(NULL, normalPrio);
}
场景 2:堆栈溢出防护
void vStackMonitorTask(void *param) {
for(;;) {
TaskHandle_t task = xTaskGetHandle("DataProcessor");
UBaseType_t free = uxTaskGetStackHighWaterMark(task);
if(free < MIN_SAFE_STACK) {
xTaskNotify(task, STACK_OVERFLOW_WARNING, eSetValueWithOverwrite);
}
vTaskDelay(3000 / portTICK_PERIOD_MS);
}
}
场景 3:系统健康检查
void vHealthCheck() {
char rtStats[512];
TaskStatus_t allTasks[5];
// 获取运行时统计
vTaskGetRunTimeStats(rtStats);
logToSDCard(rtStats);
// 检查所有任务状态
UBaseType_t num = uxTaskGetSystemState(allTasks, 5, NULL);
for(int i=0; i<num; i++) {
if(allTasks[i].eCurrentState == eSuspended) {
sendAlert("发现挂起任务!", allTasks[i].pcTaskName);
}
}
}
六、延时函数
1. 相对延时函数**vTaskDelay()
**
功能
- 相对延时:从调用该函数的时刻开始,阻塞任务一段指定的时间(以系统时钟节拍
tick
为单位)。 - 适用场景:需要简单延时的场景,不严格要求周期性执行的间隔稳定性。
函数原型
void vTaskDelay(const TickType_t xTicksToDelay);
- 参数:
xTicksToDelay
:延时的节拍数(例如pdMS_TO_TICKS(100)
表示延时 100ms)。
工作机制
- 任务调用
vTaskDelay()
后,进入阻塞状态,经过xTicksToDelay
个节拍后被唤醒。 - 延时起点:从函数调用时开始计算,每次延时的起点可能不同。
- 问题:若任务内部执行时间不固定,会导致循环周期不稳定(总时间 = 任务执行时间 + 延时时间)。
示例
void vTaskExample(void *pvParameters) {
while (1) {
// 执行任务操作
vTaskDelay(pdMS_TO_TICKS(100)); // 每次循环总时间为执行时间 + 100ms
}
}
2. 绝对延时函数 xTaskDelayUntil()
功能
- 绝对延时:基于一个固定的基准时间点,周期性唤醒任务,确保任务执行间隔严格稳定。
- 适用场景:需要精确周期性执行的任务(如传感器采样、控制循环)。
函数原型
BaseType_t xTaskDelayUntil(TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement);
-
参数:
pxPreviousWakeTime
:指向存储上一次唤醒时间的变量指针(需初始化为当前时间)。xTimeIncrement
:任务周期(节拍数,如pdMS_TO_TICKS(100)
表示 100ms 周期)。
-
返回值:
pdTRUE
:成功延时到指定时间。pdFALSE
:因系统节拍计数器溢出导致计算异常(罕见,通常可忽略)。
工作机制
- 根据
pxPreviousWakeTime
记录的基准时间,计算下一次唤醒时间(基准时间 + xTimeIncrement
)。 - 延时起点:始终基于上一次唤醒时间,确保任务周期固定(总时间 = 周期时间)。
- 优势:即使任务执行时间波动,也能保证周期性稳定。
示例
void vPeriodicTask(void *pvParameters) {
TickType_t xLastWakeTime = xTaskGetTickCount(); // 初始化基准时间
while (1) {
// 执行任务操作(执行时间可能不同)
xTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(100)); // 严格 100ms 周期
}
}
关键区别
特性 | vTaskDelay() | xTaskDelayUntil() |
---|---|---|
延时类型 | 相对延时(从调用时刻开始) | 绝对延时(基于基准时间点) |
周期稳定性 | 不保证(总时间=执行时间+延时时间) | 保证(总时间=固定周期) |
适用场景 | 简单延时需求 | 严格周期性任务(如控制循环) |
参数初始化 | 无需初始化基准时间 | 需初始化 pxPreviousWakeTime |
防溢出处理 | 无特殊处理 | 内部自动处理节拍计数器溢出 |
使用注意事项
-
基准时间初始化:
-
使用
xTaskDelayUntil()
前,必须将pxPreviousWakeTime
初始化为当前时间(通过xTaskGetTickCount()
)。 -
示例:
TickType_t xLastWakeTime = xTaskGetTickCount();
-
-
任务执行时间超限:
- 若任务执行时间超过
xTimeIncrement
,xTaskDelayUntil()
会立即唤醒任务(无阻塞),可能导致周期丢失。 - 需优化代码或增大周期值,确保任务在周期内完成。
- 若任务执行时间超过
-
系统节拍配置:
- 延时时间依赖系统节拍频率(如
configTICK_RATE_HZ
),需正确转换时间单位(例如使用pdMS_TO_TICKS()
)。
- 延时时间依赖系统节拍频率(如
场景对比
案例1:简单延时
void vBlinkLEDTask(void *pvParameters) {
while (1) {
toggleLED();
vTaskDelay(pdMS_TO_TICKS(500)); // LED每500ms闪烁一次(相对延时)
}
}
- 即使
toggleLED()
执行时间有微小波动,LED闪烁间隔仍接近 500ms。
案例2:精确周期控制
void vControlLoopTask(void *pvParameters) {
TickType_t xLastWakeTime = xTaskGetTickCount();
while (1) {
readSensor();
updateController();
xTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(10)); // 严格10ms控制周期
}
}
- 即使
readSensor()
和updateController()
的执行时间变化,控制循环始终以 10ms 间隔运行。
总结
vTaskDelay()
:适用于对周期稳定性要求不高的简单延时场景。xTaskDelayUntil()
:专为需要严格周期性的任务设计,能有效避免时间累积误差,是控制循环和实时系统的首选。
七、队列
1. 队列创建与基础操作
1.1 xQueueCreate()
功能
- 动态创建队列,分配内存空间并初始化队列结构。
- 适用场景:需要任务间或任务与中断间通信的任何场景。
函数原型
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
参数
uxQueueLength
:队列最大容量(元素个数)。uxItemSize
:每个队列元素的大小(字节)。
返回值
- 成功:队列句柄(
QueueHandle_t
)。 - 失败:返回
NULL
(内存不足时)。
工作机制
- 内部调用
pvPortMalloc
动态分配内存。 - 队列结构包含头尾指针、元素大小、队列长度等元数据。
示例
// 创建可存储10个int型数据的队列
QueueHandle_t xIntQueue = xQueueCreate(10, sizeof(int));
if(xIntQueue == NULL) {
// 错误处理
}
2. 数据发送函数
2.1 xQueueSend()
/ xQueueSendToBack()
功能
- 将数据插入队列尾部(FIFO操作)。
- 特性:当队列满时,任务可阻塞等待。
函数原型
BaseType_t xQueueSend(QueueHandle_t xQueue,
const void *pvItemToSend,
TickType_t xTicksToWait);
参数
xQueue
:目标队列句柄。pvItemToSend
:待发送数据指针。xTicksToWait
:最大阻塞时间(单位:tick)。
返回值
pdPASS
:数据成功入队。errQUEUE_FULL
:队列满且等待超时。
示例
int sensorData = 123;
xQueueSend(xSensorQueue, &sensorData, pdMS_TO_TICKS(100)); // 最多等待100ms
2.2 xQueueSendToFront()
功能
- 将数据插入队列头部(LIFO操作)。
- 适用场景:需要优先处理最新消息的紧急事件。
函数原型
BaseType_t xQueueSendToFront(QueueHandle_t xQueue,
const void *pvItemToSend,
TickType_t xTicksToWait);
差异点
- 新数据插入位置不同,其他行为与
xQueueSend()
一致。
3. 中断安全发送函数
3.1 xQueueSendFromISR()
功能
- 中断上下文专用的队列尾部插入函数。
- 核心特性:非阻塞、线程安全、支持任务唤醒通知。
函数原型
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,
const void *pvItemToSend,
BaseType_t *pxHigherPriorityTaskWoken);
参数
pxHigherPriorityTaskWoken
:用于通知是否唤醒高优先级任务。
中断使用规范
void ISR_Handler() {
BaseType_t xHPW = pdFALSE;
xQueueSendFromISR(xQueue, &data, &xHPW);
portYIELD_FROM_ISR(xHPW); // 必要时触发上下文切换
}
4. 数据接收函数
4.1 xQueueReceive()
功能
- 从队列头部提取并删除数据(FIFO操作)。
函数原型
BaseType_t xQueueReceive(QueueHandle_t xQueue,
void *pvBuffer,
TickType_t xTicksToWait);
典型应用
int receivedData;
if(xQueueReceive(xQueue, &receivedData, pdMS_TO_TICKS(200)) == pdPASS) {
process_data(receivedData);
}
5. 关键对比
发送函数对比表
特性 | xQueueSend | xQueueSendToFront | xQueueOverwrite | xQueueSendFromISR |
---|---|---|---|---|
数据位置 | 尾部 | 头部 | 覆盖最旧 | 尾部 |
阻塞机制 | ✔️ | ✔️ | ❌ | ❌ |
中断安全 | ❌ | ❌ | ❌ | ✔️ |
适用上下文 | 任务 | 任务 | 任务 | 中断 |
接收函数对比表
特性 | xQueueReceive | xQueuePeek | xQueueReceiveFromISR |
---|---|---|---|
数据移除 | ✔️ | ❌ | ✔️ |
阻塞机制 | ✔️ | ✔️ | ❌ |
中断安全 | ❌ | ❌ | ✔️ |
6. 最佳实践
队列设计原则
-
容量规划:队列长度应至少为
最大突发消息量 + 1
-
元素尺寸:建议使用结构体打包关联数据
typedef struct { uint8_t cmd; uint32_t param; } Message_t; QueueHandle_t xMsgQueue = xQueueCreate(5, sizeof(Message_t));
中断通信模板
// 中断服务程序
void UART_ISR() {
static BaseType_t xHPW;
uint8_t rxByte = UART->DR;
xQueueSendFromISR(xUARTQueue, &rxByte, &xHPW);
portYIELD_FROM_ISR(xHPW);
}
// 任务处理
void vUARTTask(void *pvParam) {
uint8_t data;
while(1) {
if(xQueueReceive(xUARTQueue, &data, portMAX_DELAY)) {
process_uart_data(data);
}
}
}
7. 常见问题
队列溢出处理
-
覆盖策略:使用
xQueueOverwrite()
确保最新数据不丢失float latestVoltage; void ADC_ISR() { latestVoltage = read_ADC(); xQueueOverwrite(xVoltageQueue, &latestVoltage); // 始终保存最新值 }
性能优化
-
内存预分配:对于关键队列使用静态分配
StaticQueue_t xQueueStruct; uint8_t ucQueueStorage[10 * sizeof(Message_t)]; QueueHandle_t xStatQueue = xQueueCreateStatic(10, sizeof(Message_t), ucQueueStorage, &xQueueStruct);
总结
- 队列选择:根据数据优先级选择 FIFO/LIFO,根据场景选择阻塞/非阻塞
- 跨上下文操作:严格区分任务与中断API
- 资源管理:动态队列需配合删除函数
vQueueDelete()
八、二值信号量
1. 创建二值信号量(动态内存分配)
函数原型
SemaphoreHandle_t xSemaphoreCreateBinary(void);
-
功能:
动态分配内存并初始化一个 二值信号量(初始状态为 不可用,即计数值为 0)。 -
返回值:
- 成功:信号量句柄(
SemaphoreHandle_t
类型)。 - 失败:
NULL
(内存不足时)。
- 成功:信号量句柄(
-
示例:
SemaphoreHandle_t xBinarySemaphore = xSemaphoreCreateBinary(); if (xBinarySemaphore == NULL) { // 处理创建失败 }
-
注意:
动态创建的信号量需手动删除(vSemaphoreDelete()
)。
2. 创建二值信号量(静态内存分配)
函数原型
SemaphoreHandle_t xSemaphoreCreateBinaryStatic(StaticSemaphore_t *pxSemaphoreBuffer);
-
功能:
使用预分配的静态内存初始化一个二值信号量(初始状态同样为不可用)。 -
参数:
pxSemaphoreBuffer
:指向StaticSemaphore_t
类型变量的指针(需提前分配内存)。 -
返回值:
信号量句柄(直接使用传入的pxSemaphoreBuffer
地址)。 -
示例:
StaticSemaphore_t xSemaphoreBuffer; SemaphoreHandle_t xSemaphore = xSemaphoreCreateBinaryStatic(&xSemaphoreBuffer);
-
适用场景:
内存受限或需严格避免动态分配的系统。
3. 释放信号量(任务中)
函数原型
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
-
功能:
释放信号量,使其变为 可用状态(计数值 +1)。 -
参数:
xSemaphore
:信号量句柄。 -
返回值:
pdPASS
:释放成功。pdFAIL
:信号量已满(二值信号量最多计数值为 1)。
-
示例:
if (xSemaphoreGive(xSemaphore) == pdPASS) { // 信号量已释放 }
4. 释放信号量(中断中)
函数原型
BaseType_t xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
-
功能:
在中断服务程序(ISR)中释放信号量。 -
参数:
xSemaphore
:信号量句柄。pxHigherPriorityTaskWoken
:
输出参数,若释放操作唤醒了更高优先级的任务,此值会被设为pdTRUE
。
-
返回值:
同xSemaphoreGive()
。 -
注意事项:
- 不可阻塞:中断中必须使用此函数,而非普通
xSemaphoreGive()
。 - 上下文切换:若返回
pdTRUE
,需调用portYIELD_FROM_ISR()
。
- 不可阻塞:中断中必须使用此函数,而非普通
-
示例:
BaseType_t xHigherPriorityTaskWoken = pdFALSE; if (xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken) == pdPASS) { portYIELD_FROM_ISR(xHigherPriorityTaskWoken); // 必要时切换任务 }
5. 获取信号量(任务中)
函数原型
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait
);
-
功能:
尝试获取信号量。若信号量不可用,任务可选择阻塞等待。 -
参数:
xSemaphore
:信号量句柄。xTicksToWait
:
阻塞时间(单位:时钟节拍),portMAX_DELAY
表示无限等待(需启用INCLUDE_vTaskSuspend
)。
-
返回值:
pdTRUE
:成功获取信号量。pdFALSE
:超时或信号量无效。
-
示例:
if (xSemaphoreTake(xSemaphore, pdMS_TO_TICKS(100)) == pdTRUE) { // 成功获取信号量 } else { // 超时处理 }
6. 获取信号量(中断中)
函数原型
BaseType_t xSemaphoreTakeFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
-
功能:
在中断服务程序中尝试获取信号量。 -
参数:
xSemaphore
:信号量句柄。pxHigherPriorityTaskWoken
:输出参数(同xSemaphoreGiveFromISR()
)。
-
返回值:
pdTRUE
:成功获取信号量。pdFALSE
:信号量不可用。
-
注意事项:
- 禁止阻塞:
xTicksToWait
必须为 0(中断中不能等待)。 - 使用频率低:中断中获取信号量的场景较少,通常更推荐用
GiveFromISR
通知任务处理。
- 禁止阻塞:
-
示例:
BaseType_t xHigherPriorityTaskWoken = pdFALSE; if (xSemaphoreTakeFromISR(xSemaphore, &xHigherPriorityTaskWoken) == pdTRUE) { // 成功获取信号量 }
关键对比与使用场景
函数 | 适用环境 | 阻塞能力 | 典型场景 |
---|---|---|---|
xSemaphoreCreateBinary | 任务 | 无 | 动态内存分配的信号量创建 |
xSemaphoreCreateBinaryStatic | 任务 | 无 | 静态内存分配的信号量创建 |
xSemaphoreGive | 任务 | 无 | 任务间同步或资源释放 |
xSemaphoreGiveFromISR | 中断 | 无 | 中断通知任务处理事件 |
xSemaphoreTake | 任务 | 可阻塞 | 任务等待资源或事件 |
xSemaphoreTakeFromISR | 中断 | 不可阻塞 | 中断中快速检查资源(极少使用) |
典型应用示例
任务与中断同步
// 全局信号量句柄
SemaphoreHandle_t xSemaphore;
// 中断服务程序
void vISR() {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// 高优先级任务处理中断事件
void vTask(void *pvParams) {
while (1) {
if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE) {
// 处理中断触发的操作
}
}
}
注意事项
- 初始状态:
动态创建的二进制信号量初始为不可用(计数值 0),需手动释放一次(xSemaphoreGive()
)才能被获取。 - 优先级继承:
二值信号量无优先级继承机制,若需避免优先级反转,应使用互斥量(xSemaphoreCreateMutex()
)。 - 中断安全:
中断中必须使用FromISR
后缀的函数,否则可能导致未定义行为。
九、计数型信号量
1. 创建计数信号量(动态内存分配)
函数原型
SemaphoreHandle_t xSemaphoreCreateCounting(
UBaseType_t uxMaxCount, // 信号量的最大计数值
UBaseType_t uxInitialCount // 信号量的初始计数值
);
-
功能:
动态分配内存并创建一个 计数信号量,用于管理多个资源的访问或事件计数。 -
参数:
uxMaxCount
:信号量能达到的最大值,代表资源池的总容量。uxInitialCount
:初始可用资源数量(必须 ≤uxMaxCount
)。
-
返回值:
- 成功:返回信号量句柄。
- 失败:返回
NULL
(内存不足时)。
-
示例:
// 创建最多允许5个资源,初始有3个可用的计数信号量 SemaphoreHandle_t xCountingSem = xSemaphoreCreateCounting(5, 3); if (xCountingSem == NULL) { // 处理创建失败 }
-
典型场景:
管理共享资源池(如连接池、缓冲区块)。
2. 创建计数信号量(静态内存分配)
函数原型
SemaphoreHandle_t xSemaphoreCreateCountingStatic(
UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount,
StaticSemaphore_t *pxSemaphoreBuffer // 预分配的静态内存块
);
-
功能:
使用静态内存创建计数信号量,避免动态内存分配。 -
参数:
pxSemaphoreBuffer
:指向StaticSemaphore_t
类型变量的指针,需预先分配内存。
-
返回值:
直接使用传入的静态内存地址作为信号量句柄。 -
示例:
StaticSemaphore_t xSemaphoreBuffer; SemaphoreHandle_t xSemaphore = xSemaphoreCreateCountingStatic( 5, 3, &xSemaphoreBuffer);
-
适用场景:
内存受限系统或需避免动态分配的情况。
3. 获取信号量的当前计数值
函数原型
UBaseType_t uxSemaphoreGetCount(
SemaphoreHandle_t xSemaphore // 信号量句柄
);
-
功能:
返回当前信号量的计数值,反映可用资源的数量。 -
返回值:
当前计数值(范围:0 ~uxMaxCount
)。 -
示例:
UBaseType_t count = uxSemaphoreGetCount(xCountingSem); printf("当前可用资源:%d\n", count);
-
用途:
- 监控资源使用情况。
- 调试时检查信号量状态。
- 动态调整任务行为(如根据资源余量切换模式)。
4. 操作计数信号量的核心函数
获取信号量(Take)
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait // 阻塞时间
);
- 行为:
若计数值 > 0,减1并返回成功;否则阻塞等待直到超时或有资源释放。 - 中断安全版本:
xSemaphoreTakeFromISR()
(注意中断中禁止阻塞)。
释放信号量(Give)
BaseType_t xSemaphoreGive(
SemaphoreHandle_t xSemaphore
);
- 行为:
若计数值 <uxMaxCount
,增1并返回成功;否则返回失败。 - 中断安全版本:
xSemaphoreGiveFromISR()
。
5. 典型应用场景与示例
场景:管理缓冲区块
#define BUFFER_POOL_SIZE 5
// 初始化:5个缓冲区全部可用
SemaphoreHandle_t xBufferSem = xSemaphoreCreateCounting(BUFFER_POOL_SIZE, BUFFER_POOL_SIZE);
// 任务获取缓冲区
void vTaskConsumer(void *pvParam) {
while (1) {
if (xSemaphoreTake(xBufferSem, portMAX_DELAY) == pdTRUE) {
// 使用缓冲区
process_buffer();
xSemaphoreGive(xBufferSem); // 释放缓冲区
}
}
}
// 监控资源使用
void vMonitorTask(void *pvParam) {
while (1) {
UBaseType_t free = uxSemaphoreGetCount(xBufferSem);
log("可用缓冲区:%d", free);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
6. 关键注意事项
-
计数值溢出:
释放信号量时,若计数值已达uxMaxCount
,xSemaphoreGive()
返回pdFAIL
,需处理此类情况(如丢弃或记录告警)。 -
优先级反转风险:
计数信号量 不支持优先级继承,若需避免优先级反转,应使用互斥量(xSemaphoreCreateMutex()
)。 -
中断中的操作:
在中断服务程序(ISR)中必须使用FromISR
版本的函数,并处理可能的上下文切换:BaseType_t xHigherPriorityTaskWoken = pdFALSE; xSemaphoreGiveFromISR(xCountingSem, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
-
静态创建的持久性:
静态信号量的生命周期与分配的静态内存一致,无需手动删除。
7. 动态 vs 静态创建对比
特性 | 动态创建 (xSemaphoreCreateCounting ) | 静态创建 (xSemaphoreCreateCountingStatic ) |
---|---|---|
内存管理 | FreeRTOS 内部分配 | 用户预先分配内存 |
灵活性 | 适合不确定数量的信号量 | 适合固定数量的信号量 |
内存泄漏风险 | 需手动调用 vSemaphoreDelete() 释放 | 无动态内存,无需删除 |
适用场景 | 通用场景 | 无动态内存系统或严格内存控制 |
总结
xSemaphoreCreateCounting()
:动态创建计数信号量,灵活但需注意内存管理。xSemaphoreCreateCountingStatic()
:静态创建,适合资源受限环境。uxSemaphoreGetCount()
:监控信号量状态,辅助调试和资源管理。
十、互斥型信号量
1. 互斥量的核心作用
互斥量(Mutex)用于 保护共享资源,确保同一时间只有一个任务可以访问临界区,防止数据竞争和优先级反转。
关键特性:
- 优先级继承:当低优先级任务持有互斥量时,若高优先级任务等待该互斥量,低优先级任务会临时继承高优先级,避免优先级反转。
- 所有权机制:只有获取(Take)互斥量的任务才能释放(Give)它,确保资源访问的严格互斥。
2. 动态创建互斥量:xSemaphoreCreateMutex()
函数原型
SemaphoreHandle_t xSemaphoreCreateMutex(void);
-
功能:
动态分配内存并初始化一个 互斥量,初始状态为 未锁定(可用)。 -
返回值:
- 成功:互斥量句柄(
SemaphoreHandle_t
类型)。 - 失败:
NULL
(内存不足时)。
- 成功:互斥量句柄(
-
示例:
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex(); if (xMutex == NULL) { // 处理创建失败 }
-
内存管理:
动态创建的互斥量需手动删除(vSemaphoreDelete()
),避免内存泄漏。
3. 静态创建互斥量:xSemaphoreCreateMutexStatic()
函数原型
SemaphoreHandle_t xSemaphoreCreateMutexStatic(
StaticSemaphore_t *pxMutexBuffer // 预分配的静态内存块
);
-
功能:
使用用户提供的静态内存初始化互斥量,避免动态内存分配。 -
参数:
pxMutexBuffer
:指向StaticSemaphore_t
类型变量的指针,需预先分配内存。 -
返回值:
直接返回传入的静态内存地址作为互斥量句柄。 -
示例:
StaticSemaphore_t xMutexBuffer; SemaphoreHandle_t xMutex = xSemaphoreCreateMutexStatic(&xMutexBuffer);
-
适用场景:
内存受限系统或需严格避免动态分配的场景。
4. 互斥量的操作函数
获取互斥量(Take)
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xMutex,
TickType_t xTicksToWait // 阻塞时间(portMAX_DELAY 表示无限等待)
);
- 行为:
- 若互斥量可用(未锁定),立即获取并返回
pdTRUE
。 - 若已被其他任务占用,任务将阻塞,直到超时或互斥量释放。
- 若互斥量可用(未锁定),立即获取并返回
- 中断中禁止使用:
互斥量 不能在中断服务程序(ISR) 中获取,必须使用任务上下文。
释放互斥量(Give)
BaseType_t xSemaphoreGive(SemaphoreHandle_t xMutex);
- 行为:
- 只有持有互斥量的任务才能释放它,返回
pdTRUE
。 - 其他任务尝试释放将返回
pdFAIL
。
- 只有持有互斥量的任务才能释放它,返回
5. 优先级继承机制
场景示例
- 低优先级任务(L) 获取互斥量并执行临界区操作。
- 高优先级任务(H) 尝试获取互斥量,因被占用而阻塞。
- FreeRTOS 临时提升 L 的优先级至与 H 相同,使其尽快释放互斥量。
- L 释放互斥量后,优先级恢复原值,H 立即抢占执行。
设计意义
- 避免优先级反转导致的高优先级任务被无限延迟。
- 确保系统实时性,但会增加低优先级任务的执行时间。
6. 使用示例
保护共享资源(如全局变量)
SemaphoreHandle_t xMutex;
// 任务1
void vTask1(void *pvParams) {
while (1) {
if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
// 访问共享资源
modify_shared_data();
xSemaphoreGive(xMutex);
}
}
}
// 任务2
void vTask2(void *pvParams) {
while (1) {
if (xSemaphoreTake(xMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
// 访问共享资源
read_shared_data();
xSemaphoreGive(xMutex);
} else {
// 处理超时(如重试或日志记录)
}
}
}
7. 动态 vs 静态互斥量对比
特性 | 动态互斥量 (xSemaphoreCreateMutex ) | 静态互斥量 (xSemaphoreCreateMutexStatic ) |
---|---|---|
内存分配 | FreeRTOS 从堆中动态分配 | 用户预先提供静态内存块 |
灵活性 | 适合动态创建/销毁的场景 | 适合生命周期固定的互斥量 |
内存泄漏风险 | 需手动调用 vSemaphoreDelete() 释放 | 无需释放,内存由用户管理 |
适用场景 | 通用场景 | 无动态内存系统或严格内存控制 |
8. 注意事项
-
禁止在中断中使用互斥量:
互斥量的Take
/Give
操作只能在任务上下文中进行。若需在中断中同步,使用二值信号量或任务通知。 -
避免嵌套获取:
同一任务重复获取已持有的互斥量会导致死锁(除非使用递归互斥量xSemaphoreCreateRecursiveMutex()
)。 -
最小化临界区时间:
持有互斥量期间应尽快完成操作,避免长时间阻塞高优先级任务。 -
错误处理:
- 检查
xSemaphoreCreateMutex()
的返回值,处理内存不足情况。 - 在
xSemaphoreTake()
中使用超时参数,防止任务永久阻塞。
- 检查
9. 常见问题
Q1:互斥量与二值信号量的区别?
- 互斥量:
- 支持优先级继承,解决优先级反转。
- 必须由获取者释放,严格绑定任务所有权。
- 二值信号量:
- 无优先级继承,任何任务均可释放。
- 适用于任务同步或事件通知。
Q2:为什么动态互斥量初始化为可用状态?
互斥量设计为初始未锁定状态,确保首次获取时可直接访问资源,符合“默认资源可访问”的直观逻辑。
Q3:如何处理互斥量被意外释放?
- 代码审查:确保只有获取者才能释放。
- 使用断言(如
configASSERT()
)检查xSemaphoreGive()
的调用者是否为持有者。
总结
xSemaphoreCreateMutex()
:动态创建互斥量,灵活但需注意内存管理。xSemaphoreCreateMutexStatic()
:静态创建,适合资源受限环境。- 优先级继承:确保高优先级任务实时性,避免优先级反转。
十一、队列集
1. 队列集合的核心作用
队列集合用于 多路复用(Multiplexing) 场景,任务可以阻塞等待多个队列或信号量中的任意一个事件,避免轮询多个队列的开销。
适用场景:
- 任务需要同时监听多个数据源(如多个队列或信号量)。
- 实现高效的资源管理,减少任务频繁切换。
2. 创建队列集合:xQueueCreateSet()
函数原型
QueueSetHandle_t xQueueCreateSet(UBaseType_t uxEventQueueLength);
-
功能:
创建一个队列集合,指定集合中可容纳的 队列/信号量数量(注意:不是队列中元素的数量)。 -
参数:
uxEventQueueLength
:集合中可添加的最大队列或信号量数量。 -
返回值:
- 成功:队列集合句柄(
QueueSetHandle_t
)。 - 失败:
NULL
(内存不足或参数无效)。
- 成功:队列集合句柄(
-
示例:
QueueSetHandle_t xQueueSet = xQueueCreateSet(3); // 最多容纳3个队列/信号量
3. 将队列/信号量加入集合:xQueueAddToSet()
函数原型
BaseType_t xQueueAddToSet(
QueueSetMemberHandle_t xQueueOrSemaphore, // 队列或信号量句柄
QueueSetHandle_t xQueueSet // 队列集合句柄
);
-
功能:
将指定的队列或信号量添加到队列集合中。 -
返回值:
pdPASS
:添加成功。pdFAIL
:添加失败(集合已满或句柄无效)。
-
示例:
QueueHandle_t xQueue1 = xQueueCreate(5, sizeof(int)); QueueHandle_t xQueue2 = xQueueCreate(5, sizeof(int)); xQueueAddToSet(xQueue1, xQueueSet); // 将队列1加入集合 xQueueAddToSet(xQueue2, xQueueSet); // 将队列2加入集合
4. 从集合中移除队列/信号量:xQueueRemoveFromSet()
函数原型
BaseType_t xQueueRemoveFromSet(
QueueSetMemberHandle_t xQueueOrSemaphore, // 队列或信号量句柄
QueueSetHandle_t xQueueSet // 队列集合句柄
);
-
功能:
从队列集合中移除指定的队列或信号量。 -
返回值:
pdPASS
:移除成功。pdFAIL
:移除失败(句柄未在集合中)。
-
示例:
xQueueRemoveFromSet(xQueue1, xQueueSet); // 从集合中移除队列1
5. 等待集合中的事件(任务中):xQueueSelectFromSet()
函数原型
QueueSetMemberHandle_t xQueueSelectFromSet(
QueueSetHandle_t xQueueSet,
TickType_t xTicksToWait // 阻塞时间(portMAX_DELAY 表示无限等待)
);
-
功能:
阻塞任务,直到队列集合中任意一个队列/信号量有数据可用(或超时)。 -
返回值:
- 有效句柄:有数据可读的队列/信号量句柄。
NULL
:超时或集合为空。
-
示例:
QueueSetMemberHandle_t xActivated = xQueueSelectFromSet(xQueueSet, portMAX_DELAY); if (xActivated == xQueue1) { // 处理队列1的数据 int data; xQueueReceive(xQueue1, &data, 0); }
6. 等待集合中的事件(中断中):xQueueSelectFromSetFromISR()
函数原型
QueueSetMemberHandle_t xQueueSelectFromSetFromISR(
QueueSetHandle_t xQueueSet
);
-
功能:
在中断服务程序(ISR)中非阻塞地检查队列集合是否有事件触发。 -
返回值:
- 有效句柄:有数据可读的队列/信号量句柄。
NULL
:无事件触发。
-
注意事项:
- 不可阻塞:中断中必须立即处理。
- 上下文切换:若需要唤醒任务,需手动调用
portYIELD_FROM_ISR()
。
-
示例:
void vISR() { QueueSetMemberHandle_t xActivated = xQueueSelectFromSetFromISR(xQueueSet); if (xActivated != NULL) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; // 处理中断中的数据(如发送任务通知) vTaskNotifyGiveFromISR(xTaskHandle, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } }
7. 队列集合的典型应用场景
场景:多路数据监听
void vDataProcessorTask(void *pvParams) {
QueueSetHandle_t xQueueSet = xQueueCreateSet(3);
QueueHandle_t xSensorQueue = xQueueCreate(5, sizeof(SensorData));
QueueHandle_t xCommandQueue = xQueueCreate(5, sizeof(Command));
xQueueAddToSet(xSensorQueue, xQueueSet);
xQueueAddToSet(xCommandQueue, xQueueSet);
while (1) {
QueueSetMemberHandle_t xActivated = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);
if (xActivated == xSensorQueue) {
SensorData data;
xQueueReceive(xSensorQueue, &data, 0);
process_sensor_data(data);
} else if (xActivated == xCommandQueue) {
Command cmd;
xQueueReceive(xCommandQueue, &cmd, 0);
handle_command(cmd);
}
}
}
8. 关键注意事项
-
集合容量限制:
创建队列集合时指定的uxEventQueueLength
必须 ≥ 实际添加的队列/信号量数量,否则添加失败。 -
队列/信号量的类型:
可以添加普通队列、二值信号量、计数信号量,但 互斥量(Mutex)不能加入队列集合。 -
数据读取:
xQueueSelectFromSet()
仅返回有数据可读的队列句柄,仍需调用xQueueReceive()
或xSemaphoreTake()
读取数据。 -
性能影响:
队列集合适合低频事件监听,高频场景可能因频繁切换上下文导致性能下降。 -
资源释放:
动态创建的队列集合需调用vQueueDelete()
释放内存。
9. 队列集合 vs 事件组
特性 | 队列集合(Queue Set) | 事件组(Event Group) |
---|---|---|
适用对象 | 队列、信号量 | 二进制事件标志 |
同步机制 | 多路复用等待 | 位掩码组合等待 |
数据传递 | 可传递数据(队列) | 仅标志状态,无数据 |
资源开销 | 较高(需管理多个队列) | 较低(单变量存储标志) |
实时性 | 适合异步数据流 | 适合轻量级事件通知 |
总结
xQueueCreateSet()
:创建队列集合,指定最大容量。xQueueAddToSet()
/xQueueRemoveFromSet()
:动态管理集合成员。xQueueSelectFromSet()
:任务中阻塞等待多个事件。xQueueSelectFromSetFromISR()
:中断中非阻塞检查事件。
十二、事件标志组
1. 事件组的核心作用
事件组用于 多任务间的位掩码(Bitwise)同步,通过 24 位(默认)或自定义位数的标志位,实现以下功能:
- 多事件等待:任务可同时等待多个事件中的任意一个或全部触发。
- 轻量级通信:无需传递数据,仅通过标志位传递状态。
- 高效同步:支持原子操作,减少临界区需求。
2. 创建事件组
2.1 动态创建:xEventGroupCreate()
EventGroupHandle_t xEventGroupCreate(void);
-
功能:动态分配内存并初始化一个事件组,所有标志位初始化为 0。
-
返回值:事件组句柄,失败返回
NULL
。 -
示例:
EventGroupHandle_t xEventGroup = xEventGroupCreate();
2.2 静态创建:xEventGroupCreateStatic()
EventGroupHandle_t xEventGroupCreateStatic(StaticEventGroup_t *pxEventGroupBuffer);
-
功能:使用静态内存初始化事件组。
-
参数:
pxEventGroupBuffer
指向预分配的StaticEventGroup_t
类型内存。 -
示例:
StaticEventGroup_t xEventGroupBuffer; EventGroupHandle_t xEventGroup = xEventGroupCreateStatic(&xEventGroupBuffer);
3. 清除事件位
3.1 任务中清除:xEventGroupClearBits()
EventBits_t xEventGroupClearBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear // 需清除的位掩码(如 0x01 | 0x04)
);
-
功能:原子清除指定事件位。
-
返回值:清除前的事件组值。
-
示例:
xEventGroupClearBits(xEventGroup, 0x01); // 清除位0
3.2 中断中清除:xEventGroupClearBitsFromISR()
BaseType_t xEventGroupClearBitsFromISR(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear
);
-
功能:中断安全版本,需配合
portYIELD_FROM_ISR()
。 -
返回值:
pdPASS
表示请求已提交(实际清除由守护任务处理)。 -
示例:
BaseType_t xHigherPriorityTaskWoken = pdFALSE; xEventGroupClearBitsFromISR(xEventGroup, 0x01); portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
4. 设置事件位
4.1 任务中设置:xEventGroupSetBits()
EventBits_t xEventGroupSetBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet // 需设置的位掩码
);
-
功能:原子设置指定事件位,并唤醒等待该位的任务。
-
返回值:设置后的事件组值。
-
示例:
xEventGroupSetBits(xEventGroup, 0x02); // 设置位1
4.2 中断中设置:xEventGroupSetBitsFromISR()
BaseType_t xEventGroupSetBitsFromISR(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken
);
-
功能:中断安全版本,可能触发上下文切换。
-
示例:
BaseType_t xHigherPriorityTaskWoken = pdFALSE; xEventGroupSetBitsFromISR(xEventGroup, 0x02, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
5. 等待事件位:xEventGroupWaitBits()
EventBits_t xEventGroupWaitBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor, // 等待的位掩码
BaseType_t xClearOnExit, // 退出时是否清除这些位
BaseType_t xWaitForAllBits, // 等待所有位还是任一位置位
TickType_t xTicksToWait // 阻塞超时时间
);
-
功能:阻塞任务,直到指定事件位满足条件。
-
返回值:满足条件的事件位值(超时返回当前值)。
-
示例:
// 等待位0或位1被设置,退出时不清除位 EventBits_t bits = xEventGroupWaitBits(xEventGroup, 0x03, pdFALSE, pdFALSE, portMAX_DELAY);
6. 多任务同步:xEventGroupSync()
EventBits_t xEventGroupSync(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet, // 同步成功后设置的位
const EventBits_t uxBitsToWaitFor, // 需等待的位
TickType_t xTicksToWait
);
-
功能:同步多个任务,等待指定位被其他任务设置后,再设置自己的位并唤醒。
-
工作流程:
- 检查
uxBitsToWaitFor
是否已全部置位。 - 若未满足,阻塞等待。
- 当条件满足时,设置
uxBitsToSet
位,并清除uxBitsToWaitFor
位。
- 检查
-
典型场景:多任务协作完成某阶段后触发下一步。
-
示例:
// 任务A:等待位0和位1,成功后设置位2 xEventGroupSync(xEventGroup, 0x04, 0x03, portMAX_DELAY);
7. 事件组的典型应用场景
场景1:多传感器数据就绪通知
// 定义事件位
#define TEMP_READY_BIT (1 << 0)
#define HUMID_READY_BIT (1 << 1)
// 传感器任务设置对应位
void vTempTask(void *pvParam) {
while (1) {
read_temperature();
xEventGroupSetBits(xEventGroup, TEMP_READY_BIT);
}
}
// 处理任务等待任意数据就绪
void vProcessTask(void *pvParam) {
while (1) {
EventBits_t bits = xEventGroupWaitBits(xEventGroup,
TEMP_READY_BIT | HUMID_READY_BIT,
pdTRUE, // 退出时清除位
pdFALSE, // 等待任一位置位
portMAX_DELAY);
if (bits & TEMP_READY_BIT) process_temp();
if (bits & HUMID_READY_BIT) process_humid();
}
}
场景2:多任务阶段同步
// 任务1、2、3完成后同步
void vTask1(void *pvParam) {
xEventGroupSync(xEventGroup, 0x01, 0x07, portMAX_DELAY); // 设置位0,等待位0-2
// 后续操作...
}
8. 关键注意事项
-
位掩码范围:
FreeRTOS 默认支持 24 位(configUSE_16_BIT_TICKS=0
时为 24 位,否则 8 位),可通过configEVENT_GROUP_BITS
自定义。 -
中断安全操作:
- 中断中必须使用
FromISR
函数。 xEventGroupSetBitsFromISR()
可能唤醒高优先级任务,需处理上下文切换。
- 中断中必须使用
-
性能优化:
- 避免高频设置/清除位,尤其是跨任务或中断场景。
- 使用
xClearOnExit
参数减少额外清除操作。
-
与队列/信号量的对比:
特性 事件组 队列/信号量 数据传递 无数据,仅状态位 可传递数据 多事件处理 支持多条件组合 单条件(如数据到达) 资源开销 低(单个变量) 较高(队列缓冲)
总结
- 创建与销毁:动态(
xEventGroupCreate
)与静态(xEventGroupCreateStatic
)初始化。 - 位操作:
Set/Clear
用于修改标志位,Wait
用于阻塞等待条件。 - 同步机制:
Sync
实现多任务阶段同步。 - 中断安全:
FromISR
后缀函数保障中断上下文安全。
十三、任务通知
1. xTaskNotify()
函数原型:
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction );
功能:向指定任务发送通知,并可选更新其通知值。
参数:
xTaskToNotify
:目标任务的句柄(TaskHandle_t
类型)。ulValue
:设置的通知值(根据eAction
决定如何应用)。eAction
:操作类型(见下表)。
返回值:pdPASS
(成功)或pdFAIL
(参数错误)。
eAction 类型 | 说明 |
---|---|
eNoAction | 仅标记通知,不修改通知值。 |
eSetBits | 将ulValue 按位或到原值。 |
eIncrement | 原值递增1(忽略ulValue )。 |
eSetValueWithOverwrite | 直接覆盖原值。 |
eSetValueWithoutOverwrite | 仅当任务未处于通知等待状态时覆盖。 |
2. xTaskNotifyAndQuery()
函数原型:
BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotifyValue );
功能:发送通知并返回修改前的通知值。
参数:
pulPreviousNotifyValue
:指向存储原通知值的变量。
返回值:pdPASS
或pdFAIL
。
3. xTaskNotifyGive()
函数原型:
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
功能:递增目标任务的通知值(通知值 += 1
),等效于信号量Give
操作。
返回值:始终pdPASS
。
4. xTaskNotifyFromISR()
函数原型:
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken );
功能:中断安全版本的xTaskNotify()
。
参数:
pxHigherPriorityTaskWoken
:若发送导致更高优先级任务就绪,此参数会被设为pdTRUE
。
返回值:pdPASS
或pdFAIL
。
注意:可能需要调用portYIELD_FROM_ISR()
。
5. xTaskNotifyAndQueryFromISR()
函数原型:
BaseType_t xTaskNotifyAndQueryFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotifyValue,
BaseType_t *pxHigherPriorityTaskWoken );
功能:中断安全版本的xTaskNotifyAndQuery()
。
参数:同上。
6. vTaskNotifyGiveFromISR()
函数原型:
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify,
BaseType_t *pxHigherPriorityTaskWoken );
功能:中断安全版本的xTaskNotifyGive()
。
示例:
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(xTaskHandle, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); // 必要时切换上下文
7. ulTaskNotifyTake()
函数原型:
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
功能:等待通知并操作通知值(清零或递减)。
参数:
xClearCountOnExit
:pdTRUE
(清零)或pdFALSE
(减1)。xTicksToWait
:最大阻塞时间。
返回值:取到的通知值(uint32_t
类型)。
8. xTaskNotifyWait()
函数原型:
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
功能:等待通知并灵活控制通知值的位域。
参数:
ulBitsToClearOnEntry
:等待前清除的位(如0xFFFF
)。ulBitsToClearOnExit
:退出后清除的位。pulNotificationValue
:存储接收到的通知值。
返回值:pdTRUE
(成功)或pdFALSE
(超时)。
对比总结
函数 | 上下文 | 关键操作 |
---|---|---|
xTaskNotify() | 任务 | 发送通知,可设置值/位/递增 |
xTaskNotifyAndQuery() | 任务 | 发送通知,同时查询原值 |
xTaskNotifyGive() | 任务 | 模拟信号量Give(值递增1) |
xTaskNotifyFromISR() | 中断 | 中断安全发送,支持上下文切换标记 |
vTaskNotifyGiveFromISR() | 中断 | 中断安全Give,无返回值 |
ulTaskNotifyTake() | 任务 | 模拟信号量Take(等待并清零/减1) |
xTaskNotifyWait() | 任务 | 复杂位操作等待 |
示例场景
轻量级信号量(任务间同步)
// 发送端(任务或中断)
xTaskNotifyGive(xReceiverTask); // 或 vTaskNotifyGiveFromISR()
// 接收端
uint32_t ulCount = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
位事件通知
// 发送端(设置第0位)
xTaskNotify(xReceiverTask, 0x01, eSetBits);
// 接收端
uint32_t ulNotifiedValue;
xTaskNotifyWait(0, 0xFFFFFFFF, &ulNotifiedValue, portMAX_DELAY);
if (ulNotifiedValue & 0x01) {
// 处理事件
}
注意事项
- 不可替代队列/信号量:任务通知仅支持单任务接收,多任务通信需用传统IPC。
- ISR限制:中断中必须使用
FromISR
版本,且不得阻塞。 - 性能优势:比队列/信号量更快,适合高频轻量通信。
十四、软件定时器
1. 定时器创建函数
1.1 xTimerCreate()
函数原型:
TimerHandle_t xTimerCreate(
const char * const pcTimerName, // 定时器名称(调试用)
const TickType_t xTimerPeriodInTicks, // 周期(滴答数)
const UBaseType_t uxAutoReload, // 是否自动重载(pdTRUE/pdFALSE)
void * const pvTimerID, // 用户标识ID(用于区分多定时器)
TimerCallbackFunction_t pxCallbackFunction ); // 回调函数
功能:动态创建软件定时器(使用 FreeRTOS 堆内存)。
返回值:成功返回定时器句柄,失败返回 NULL
。
注意:需启用 configSUPPORT_DYNAMIC_ALLOCATION = 1
。
1.2 xTimerCreateStatic()
函数原型:
TimerHandle_t xTimerCreateStatic(
const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction,
StaticTimer_t *pxTimerBuffer ); // 静态分配的内存块
功能:静态创建定时器(需预先分配内存)。
返回值:成功返回句柄,失败返回 NULL
(通常因 pxTimerBuffer
未对齐)。
注意:需启用 configSUPPORT_STATIC_ALLOCATION = 1
。
2. 定时器控制函数
2.1 xTimerStart()
函数原型:
BaseType_t xTimerStart(
TimerHandle_t xTimer, // 定时器句柄
TickType_t xTicksToWait ); // 阻塞时间(发送到定时器队列的等待时间)
功能:启动或重启定时器(若已启动,则重置计数)。
返回值:pdPASS
(成功发送命令)或 pdFAIL
(队列满且超时)。
场景:在任务中启动定时器。
2.2 xTimerStartFromISR()
函数原型:
BaseType_t xTimerStartFromISR(
TimerHandle_t xTimer,
BaseType_t *pxHigherPriorityTaskWoken ); // 是否触发任务切换
功能:中断安全版本,启动或重启定时器。
返回值:pdPASS
或 pdFAIL
。
注意:若返回 pxHigherPriorityTaskWoken = pdTRUE
,需调用 portYIELD_FROM_ISR()
。
2.3 xTimerStop()
函数原型:
BaseType_t xTimerStop(
TimerHandle_t xTimer,
TickType_t xTicksToWait );
功能:停止定时器(若未启动则无操作)。
返回值:同 xTimerStart()
。
2.4 xTimerStopFromISR()
函数原型:
BaseType_t xTimerStopFromISR(
TimerHandle_t xTimer,
BaseType_t *pxHigherPriorityTaskWoken );
功能:中断安全版本,停止定时器。
注意:处理方式同 xTimerStartFromISR()
。
3. 定时器重置与周期修改
3.1 xTimerReset()
函数原型:
BaseType_t xTimerReset(
TimerHandle_t xTimer,
TickType_t xTicksToWait );
功能:重置定时器(等同于 xTimerStart()
,若定时器未运行则启动)。
场景:需要重新开始计时的情况(如响应外部事件)。
3.2 xTimerResetFromISR()
函数原型:
BaseType_t xTimerResetFromISR(
TimerHandle_t xTimer,
BaseType_t *pxHigherPriorityTaskWoken );
功能:中断安全版本的重置操作。
3.3 xTimerChangePeriod()
函数原型:
BaseType_t xTimerChangePeriod(
TimerHandle_t xTimer,
TickType_t xNewPeriod, // 新周期(滴答数)
TickType_t xTicksToWait );
功能:修改定时器周期并自动重置计时。若定时器未运行,则启动。
场景:动态调整定时器频率。
3.4 xTimerChangePeriodFromISR()
函数原型:
BaseType_t xTimerChangePeriodFromISR(
TimerHandle_t xTimer,
TickType_t xNewPeriod,
BaseType_t *pxHigherPriorityTaskWoken );
功能:中断安全版本修改周期。
关键操作对比
函数 | 上下文 | 功能 | 是否启动定时器 |
---|---|---|---|
xTimerStart() | 任务 | 启动/重启 | 是(若已停止) |
xTimerReset() | 任务 | 重置计数器(等同于重启) | 是(若已停止) |
xTimerChangePeriod() | 任务 | 修改周期并重置计数器 | 是(若已停止) |
xTimerStop() | 任务 | 停止 | 否 |
使用注意事项
- 定时器服务任务:所有定时器操作通过队列发送命令,需确保
configUSE_TIMERS = 1
且定时器服务任务优先级合理。 - 阻塞时间:
xTicksToWait
是发送命令到定时器队列的等待时间,非定时器本身阻塞。 - 回调函数限制:定时器回调函数在服务任务中执行,需简短且不可阻塞。
- 中断安全:在 ISR 中必须使用
FromISR
系列函数,并根据返回值处理任务切换。
示例代码
创建并启动自动重载定时器
// 回调函数
void vTimerCallback(TimerHandle_t xTimer) {
uint32_t *pCount = (uint32_t*)pvTimerGetTimerID(xTimer);
(*pCount)++;
}
// 创建定时器
uint32_t timerID = 0;
TimerHandle_t xTimer = xTimerCreate(
"MyTimer",
1000 / portTICK_PERIOD_MS, // 1秒周期
pdTRUE, // 自动重载
&timerID, // 传递计数变量地址
vTimerCallback
);
// 启动定时器
if (xTimerStart(xTimer, 0) == pdPASS) {
// 成功
}
在中断中修改周期
void vInterruptHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xTimerChangePeriodFromISR(xTimer, 2000 / portTICK_PERIOD_MS, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}