嵌入式操作系统
基于 STM32F103 · 零基础讲解嵌入式操作系统概念、FreeRTOS 任务管理、任务间通信、时间管理和内存管理。
🎯学习目标
- 了解嵌入式操作系统的基本概念、分类和特点;
- 理解实时操作系统的概念,掌握硬实时和软实时的区别;
- 了解 FreeRTOS 的特点、内核功能和应用场景;
- 掌握 FreeRTOS 的任务管理,包括任务状态、创建、调度策略;
- 掌握任务间通信机制,包括队列、信号量、互斥锁、事件标志组;
- 掌握时间管理功能,包括延时函数和软件定时器;
- 了解 FreeRTOS 的内存管理策略;
- 熟悉 FreeRTOS 标准库函数和 HAL 库函数的使用;
- 能够使用 FreeRTOS 开发多任务应用程序。
1操作系统概述
嵌入式操作系统(Embedded Operating System,Embedded OS)是管理嵌入式系统硬件资源、为应用程序提供公共服务的系统软件。它负责管理 CPU、内存、外设等硬件资源,并提供任务调度、同步通信、内存管理等基本服务。
没有操作系统:你必须自己管理所有硬件,处理多个任务时代码复杂,容易出错。就像一个人要同时做饭、洗衣服、接电话,手忙脚乱。
有操作系统:操作系统帮你管理硬件,你只需要写应用代码,多个任务可以"同时"运行。就像有一个管家帮你安排好一切,你只需要告诉管家做什么。
好处:① 简化开发——不用直接操作硬件;② 多任务管理——多个任务"同时"运行;③ 资源管理——合理分配 CPU、内存;④ 实时性保证——保证重要任务及时响应;⑤ 可移植性——代码可在不同平台运行。
嵌入式操作系统的分类
| 分类方式 | 类型 | 特点 | 代表 |
|---|---|---|---|
| 是否实时 | 实时操作系统(RTOS) | 响应时间确定,可预测 | FreeRTOS、RT-Thread、μC/OS |
| 非实时操作系统 | 响应时间不确定 | Linux、Windows CE | |
| 是否可抢占 | 可抢占式 | 高优先级任务可抢占低优先级 | FreeRTOS、RT-Thread |
| 非抢占式 | 任务主动让出 CPU | 简单协作式系统 | |
| 应用场景 | 通用嵌入式 OS | 功能丰富,适合复杂应用 | 嵌入式 Linux |
| 专用 RTOS | 轻量、高效,适合资源受限 | FreeRTOS、μC/OS |
嵌入式操作系统的核心功能
任务调度
Task Scheduling任务通信
IPC内存管理
Memory时间管理
Timer裸机编程:就像自己盖房子,从打地基到装修全自己干。代码直接操作硬件,while(1) 循环里一个一个处理任务。缺点是任务多了就忙不过来,响应慢。
操作系统:就像请了施工队,你只需要画图纸(写应用代码)。操作系统帮你安排工人(CPU)、分配材料(内存)、协调进度(调度)。多个任务可以"同时"进行,重要任务优先处理。
2实时操作系统(RTOS)⭐
实时操作系统(Real-Time Operating System,RTOS)是能在确定时间内处理事件的操作系统。"实时"不是"快",而是"准时"——能在规定的时间内完成任务。
实时 ≠ 快。实时是指"在规定时间内完成"。
比如:硬实时——汽车安全气囊必须在碰撞后 50ms 内弹出,晚了人就受伤了,这是"必须准时"。软实时——视频播放要流畅,偶尔卡一下(延迟 100ms)可以接受,这是"尽量准时"。
关键:实时操作系统保证"可预测性"——你可以说"这个任务一定能在 10ms 内完成",而普通操作系统只能说"一般能在 10ms 内完成,但偶尔可能要 100ms"。
硬实时 vs 软实时(必考)
硬实时
Hard Real-Time软实时
Soft Real-Time| 对比项 | 硬实时 | 软实时 |
|---|---|---|
| 截止时间 | 必须满足,不可违反 | 尽量满足,偶尔可违反 |
| 超时后果 | 系统失败、灾难性后果 | 性能下降、用户体验变差 |
| 典型延迟 | 微秒~毫秒级 | 毫秒~秒级 |
| 应用场景 | 航空航天、医疗、汽车 | 消费电子、多媒体、网络 |
| 设计难度 | 高,需要严格保证 | 中,尽力而为 |
RTOS 的核心特点
- 确定性(Determinism):响应时间可预测,最坏情况下的响应时间是确定的;
- 可抢占性(Preemptive):高优先级任务可以立即抢占低优先级任务;
- 快速响应(Fast Response):中断响应时间短,通常在微秒级;
- 可靠性(Reliability):系统稳定,不易崩溃,适合关键任务;
- 低抖动(Low Jitter):任务执行时间波动小,行为一致。
常见嵌入式 RTOS 对比
| RTOS | 开源 | 特点 | 适用场景 |
|---|---|---|---|
| FreeRTOS | ✅ MIT | 轻量、生态好、AWS支持 | IoT、消费电子、工业控制 |
| RT-Thread | ✅ Apache | 国产、组件丰富、中文文档好 | IoT、智能家居、工业 |
| μC/OS | 部分开源 | 教科书经典、认证完善 | 工业、医疗、航空 |
| Zephyr | ✅ Apache | Linux基金会、蓝牙/WiFi支持好 | IoT、可穿戴设备 |
| embOS | 商业 | 高性能、认证完善 | 工业、医疗 |
3FreeRTOS 简介
FreeRTOS 是一个开源、轻量级的实时操作系统内核,由 Richard Barry 于 2003 年创建,现由 Amazon Web Services(AWS)维护。它是世界上使用最广泛的嵌入式 RTOS 之一。
① 开源免费:MIT 许可证,商业产品免费使用;② 轻量级:内核只有几千行代码,ROM 占用 6~10KB,RAM 占用几百字节;③ 生态好:AWS IoT 支持,大量教程和例程;④ 社区活跃:全球开发者贡献,bug 修复快。
FreeRTOS 主要特点
- 开源免费:MIT 许可证,商业友好,无授权费用;
- 轻量级:内核小(6~10KB ROM),适合资源受限的 MCU;
- 可裁剪:通过宏定义选择需要的功能,减少代码体积;
- 可移植:支持 ARM Cortex-M、RISC-V、ESP32 等 40+ 处理器架构;
- 丰富功能:任务管理、队列、信号量、互斥锁、事件标志组、软件定时器、内存管理等。
FreeRTOS 内核功能全景
| 功能模块 | 说明 | 关键函数 |
|---|---|---|
| 任务管理 | 创建、删除、挂起、恢复任务 | xTaskCreate, vTaskDelete, vTaskSuspend |
| 调度器 | 优先级抢占式调度 + 时间片轮转 | vTaskStartScheduler |
| 队列 | 任务间传递数据 | xQueueCreate, xQueueSend, xQueueReceive |
| 信号量 | 任务同步和资源计数 | xSemaphoreCreateBinary, xSemaphoreTake |
| 互斥锁 | 保护共享资源,支持优先级继承 | xSemaphoreCreateMutex |
| 事件标志组 | 多事件组合同步 | xEventGroupCreate, xEventGroupWaitBits |
| 软件定时器 | 定时回调函数 | xTimerCreate, xTimerStart |
| 内存管理 | 动态内存分配(5种策略) | pvPortMalloc, vPortFree |
| 任务通知 | 轻量级任务间通信 | xTaskNotify, xTaskNotifyWait |
4任务管理 ⭐(核心考点)
任务(Task)是 FreeRTOS 中的基本执行单元,类似于线程。每个任务都是一个独立的函数,有自己的堆栈空间,看起来像是"独占"一个 CPU。
想象一个餐厅有多个厨师(任务)共享一个灶台(CPU):
• 任务就像一个厨师,有自己的菜谱(代码)和工作台(堆栈)。
• 调度器就像厨师长,决定谁现在用灶台。优先级高的厨师(紧急订单)可以插队。
• 任务切换就像厨师换人:A 厨师暂停,记住做到哪一步(保存现场),B 厨师上灶(恢复现场)继续做。
• 时间片就像给每个厨师限时 1 分钟,超时就换人,大家轮流用灶台。
任务状态(必考)
- 运行态(Running):任务正在使用 CPU,正在执行代码;
- 就绪态(Ready):任务准备好了,等待 CPU 分配;
- 阻塞态(Blocked):任务在等待某个事件(如延时到期、队列有数据、信号量可用);
- 挂起态(Suspended):任务被挂起,不参与调度,需要手动恢复。
创建任务
使用 xTaskCreate() 函数创建任务,任务函数必须是无限循环:
// 任务函数原型(必须是无限循环)
void vTaskFunction(void *pvParameters)
{
// 初始化代码(只执行一次)
for(;;)
{
// 任务主循环代码
LED_Toggle();
vTaskDelay(500); // 延时 500ms,让出 CPU
}
}
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, // 任务函数指针
const char * const pcName, // 任务名称(调试用)
configSTACK_DEPTH_TYPE usStackDepth, // 堆栈大小(字)
void *pvParameters, // 传给任务的参数
UBaseType_t uxPriority, // 任务优先级
TaskHandle_t *pxCreatedTask // 任务句柄(可为NULL)
);
// 任务 1:LED 闪烁
void vTaskLED(void *pvParameters)
{
for(;;)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
vTaskDelay(500); // 延时 500ms
}
}
// 任务 2:串口打印
void vTaskUART(void *pvParameters)
{
for(;;)
{
printf("Hello FreeRTOS!\r\n");
vTaskDelay(1000); // 延时 1000ms
}
}
// 主函数
int main(void)
{
HAL_Init();
SystemClock_Config();
LED_Init();
UART_Init();
// 创建任务 1(优先级 2)
xTaskCreate(vTaskLED, "LED", 128, NULL, 2, NULL);
// 创建任务 2(优先级 1)
xTaskCreate(vTaskUART, "UART", 128, NULL, 1, NULL);
// 启动调度器
vTaskStartScheduler();
// 不应该运行到这里
for(;;);
}
任务优先级与调度策略
- 优先级数值越大,优先级越高(0 是最低优先级);
- 高优先级任务可以抢占低优先级任务(抢占式调度);
- 相同优先级的任务轮流执行(时间片轮转,需要开启 configUSE_TIME_SLICING);
- 阻塞/挂起的任务不占用 CPU,让出给其他任务;
- 调度器始终选择最高优先级的就绪任务运行。
就像医院急诊:普通门诊(低优先级)正在看病,突然来了急诊病人(高优先级),医生立刻放下普通病人去处理急诊。
在 FreeRTOS 中:低优先级任务正在运行,高优先级任务从阻塞态变为就绪态(如延时到期),调度器立刻切换到高优先级任务。
任务控制函数
| 函数 | 功能 | 说明 |
|---|---|---|
vTaskDelete() | 删除任务 | 传入 NULL 删除自身 |
vTaskSuspend() | 挂起任务 | 任务进入挂起态,不参与调度 |
vTaskResume() | 恢复任务 | 任务从挂起态恢复到就绪态 |
vTaskPrioritySet() | 设置优先级 | 运行时动态修改任务优先级 |
uxTaskPriorityGet() | 获取优先级 | 查询任务当前优先级 |
vTaskDelay() | 相对延时 | 任务进入阻塞态指定时间 |
vTaskDelayUntil() | 绝对延时 | 周期性任务使用,保证周期准确 |
堆栈大小单位是"字"(4 字节),不是字节。128 表示 512 字节。
• 简单任务(LED 闪烁):128 字(512B)
• 中等任务(串口通信):256 字(1KB)
• 复杂任务(浮点运算):512 字(2KB)
• 可用 uxTaskGetStackHighWaterMark() 查询堆栈使用情况
5任务间通信 ⭐(核心考点)
多个任务需要协同工作时,就需要任务间通信(Inter-Process Communication,IPC)。FreeRTOS 提供多种通信机制:
队列
Queue信号量
Semaphore互斥锁
Mutex事件标志组
Event Group5.1 队列(Queue)
队列是 FreeRTOS 中最常用的任务间通信方式。它是一个 FIFO(先进先出)缓冲区,一个任务往里写数据,另一个任务从里面读数据。
队列就像一条传送带:生产者(任务A)把物品放上传送带,消费者(任务B)从传送带另一端取走物品。
• 传送带满了,生产者等待(阻塞);传送带空了,消费者等待(阻塞)。
• 可以设置等待时间:等多久、不等直接返回、一直等到有数据。
// 创建队列
QueueHandle_t xQueueCreate(
UBaseType_t uxQueueLength, // 队列长度(能存几个元素)
UBaseType_t uxItemSize // 每个元素大小(字节)
);
// 发送到队列(任务中使用)
BaseType_t xQueueSend(
QueueHandle_t xQueue, // 队列句柄
const void *pvItemToQueue, // 要发送的数据指针
TickType_t xTicksToWait // 等待时间(portMAX_DELAY=永久等待)
);
// 从队列接收(任务中使用)
BaseType_t xQueueReceive(
QueueHandle_t xQueue, // 队列句柄
void *pvBuffer, // 接收缓冲区
TickType_t xTicksToWait // 等待时间
);
// 从中断发送到队列
BaseType_t xQueueSendFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
// 定义队列句柄
QueueHandle_t xQueue;
// 任务 1:发送数据
void vTaskSender(void *pvParameters)
{
uint32_t count = 0;
for(;;)
{
count++;
xQueueSend(xQueue, &count, portMAX_DELAY);
printf("发送: %d\r\n", count);
vTaskDelay(1000);
}
}
// 任务 2:接收数据
void vTaskReceiver(void *pvParameters)
{
uint32_t received;
for(;;)
{
if(xQueueReceive(xQueue, &received, portMAX_DELAY) == pdPASS)
{
printf("接收: %d\r\n", received);
}
}
}
// 主函数
int main(void)
{
// 创建队列(能存 10 个 uint32_t 数据)
xQueue = xQueueCreate(10, sizeof(uint32_t));
xTaskCreate(vTaskSender, "Sender", 128, NULL, 2, NULL);
xTaskCreate(vTaskReceiver, "Receiver", 128, NULL, 1, NULL);
vTaskStartScheduler();
}
5.2 信号量(Semaphore)
信号量用于任务同步和资源计数。分为二值信号量(0/1)和计数信号量(0~N)。
二值信号量像一扇门:门关着(0),任务等待;门开了(1),任务通过。用于"事件通知"——中断来了开门,任务通过后关门。
计数信号量像停车场计数器:每进一辆车计数+1,每出一辆车计数-1。计数为 0 时,新车等待。用于"资源管理"——管理多个相同资源。
// 创建二值信号量
SemaphoreHandle_t xSemaphoreCreateBinary(void);
// 创建计数信号量
SemaphoreHandle_t xSemaphoreCreateCounting(
UBaseType_t uxMaxCount, // 最大计数值
UBaseType_t uxInitialCount // 初始计数值
);
// 获取信号量(等待/减 1)
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait
);
// 释放信号量(通知/加 1)
BaseType_t xSemaphoreGive(
SemaphoreHandle_t xSemaphore
);
// 从中断释放信号量
BaseType_t xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
SemaphoreHandle_t xSemaphore;
// 中断服务程序
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// 任务:等待中断通知
void vTaskButton(void *pvParameters)
{
for(;;)
{
// 等待信号量(中断给的)
if(xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdPASS)
{
printf("按键按下!\r\n");
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
}
}
}
int main(void)
{
xSemaphore = xSemaphoreCreateBinary(); // 创建二值信号量
xTaskCreate(vTaskButton, "Btn", 128, NULL, 2, NULL);
vTaskStartScheduler();
}
5.3 互斥锁(Mutex)
互斥锁用于保护共享资源,确保同一时刻只有一个任务访问资源。它和二值信号量类似,但有优先级继承机制,能防止优先级反转问题。
互斥锁就像卫生间门锁:一个人进去后锁上门(获取锁),其他人只能在门口等。里面的人出来后开锁(释放锁),下一个人才能进去。
优先级继承:如果低优先级任务持有锁,高优先级任务在等锁,系统临时提升低优先级任务的优先级,让它快点做完释放锁。防止中等优先级任务插队(优先级反转)。
SemaphoreHandle_t xMutex;
// 任务 A:使用串口
void vTaskA(void *pvParameters)
{
for(;;)
{
xSemaphoreTake(xMutex, portMAX_DELAY); // 获取锁
printf("TaskA: 正在使用串口...\r\n");
vTaskDelay(100);
xSemaphoreGive(xMutex); // 释放锁
vTaskDelay(500);
}
}
// 任务 B:使用串口
void vTaskB(void *pvParameters)
{
for(;;)
{
xSemaphoreTake(xMutex, portMAX_DELAY); // 获取锁
printf("TaskB: 正在使用串口...\r\n");
vTaskDelay(100);
xSemaphoreGive(xMutex); // 释放锁
vTaskDelay(500);
}
}
int main(void)
{
xMutex = xSemaphoreCreateMutex(); // 创建互斥锁
xTaskCreate(vTaskA, "A", 128, NULL, 2, NULL);
xTaskCreate(vTaskB, "B", 128, NULL, 1, NULL);
vTaskStartScheduler();
}
5.4 事件标志组(Event Group)
事件标志组可以同时等待多个事件的组合,支持"与"(所有事件都发生)和"或"(任一事件发生)条件。
就像一个任务清单:有多个 checkbox,每个 checkbox 代表一个事件。
• "与"等待:所有 checkbox 都勾上才能继续(如:WiFi 连接 AND 蓝牙配对 AND 传感器就绪)。
• "或"等待:任意一个 checkbox 勾上就能继续(如:按键按下 OR 定时器到期 OR 收到数据)。
// 创建事件标志组
EventGroupHandle_t xEventGroupCreate(void);
// 设置事件标志
EventBits_t xEventGroupSetBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet
);
// 等待事件标志
EventBits_t xEventGroupWaitBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor, // 要等待的位
const BaseType_t xClearOnExit, // 退出时清除
const BaseType_t xWaitForAllBits, // TRUE=与, FALSE=或
TickType_t xTicksToWait
);
5.5 任务通知(Task Notification)⭐ 轻量级
任务通知是 FreeRTOS v10 新增的轻量级通信方式,比队列和信号量更快,但只能用于一对一直接通信。
// 发送通知(可以携带数据)
BaseType_t xTaskNotify(
TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction
);
// 等待通知
BaseType_t xTaskNotifyWait(
uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait
);
// 从中断发送通知
BaseType_t xTaskNotifyFromISR(
TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken
);
通信机制对比
| 机制 | 数据传递 | 同步 | 速度 | 适用场景 |
|---|---|---|---|---|
| 队列 | ✅ 复制数据 | ✅ 阻塞等待 | 中等 | 传递结构化数据 |
| 二值信号量 | ❌ 无数据 | ✅ 事件通知 | 快 | 中断通知任务 |
| 计数信号量 | ❌ 无数据 | ✅ 资源计数 | 快 | 管理多个资源 |
| 互斥锁 | ❌ 无数据 | ✅ 资源保护 | 快 | 保护共享资源 |
| 事件标志组 | ❌ 无数据 | ✅ 多事件组合 | 快 | 复杂同步 |
| 任务通知 | ✅ 32位值 | ✅ 直接通知 | 最快 | 简单一对一通信 |
6时间管理
FreeRTOS 提供完善的时间管理功能,包括延时函数和软件定时器。
6.1 延时函数
| 函数 | 功能 | 说明 |
|---|---|---|
vTaskDelay() | 相对延时 | 从调用开始延时指定时间(会受任务执行时间影响) |
vTaskDelayUntil() | 绝对延时 | 周期性执行任务,周期更准确 |
xTaskGetTickCount() | 获取时钟节拍数 | 返回系统启动以来的节拍数 |
xTaskGetTickCountFromISR() | 中断中获取节拍数 | 中断安全版本 |
pdMS_TO_TICKS() | 毫秒转节拍 | 将毫秒转换为时钟节拍数 |
vTaskDelay(100):从"调用这一刻"开始等 100 个节拍。如果任务执行花了 20 个节拍,实际周期是 120 个节拍。
vTaskDelayUntil(&时间, 100):从"上一次唤醒时间"开始等 100 个节拍。即使任务执行花了 20 个节拍,周期仍然是 100 个节拍。
结论:周期性任务用 vTaskDelayUntil,普通延时用 vTaskDelay。
// 方式 1:vTaskDelay(周期不准确)
void vTask1(void *pvParameters)
{
for(;;)
{
LED_Toggle();
vTaskDelay(pdMS_TO_TICKS(100)); // 延时 100ms
// 实际周期 = 100ms + 任务执行时间
}
}
// 方式 2:vTaskDelayUntil(周期准确)
void vTask2(void *pvParameters)
{
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xPeriod = pdMS_TO_TICKS(100); // 100ms 周期
for(;;)
{
LED_Toggle();
vTaskDelayUntil(&xLastWakeTime, xPeriod);
// 实际周期 = 100ms(准确)
}
}
6.2 软件定时器(Software Timer)
软件定时器在定时器服务任务中执行回调函数,适合周期性操作。注意:回调函数中不能使用阻塞 API!
// 创建定时器
TimerHandle_t xTimerCreate(
const char * const pcTimerName, // 定时器名称
const TickType_t xTimerPeriod, // 周期(节拍数)
const UBaseType_t uxAutoReload, // TRUE=自动重载, FALSE=单次
void * const pvTimerID, // 定时器 ID
TimerCallbackFunction_t pxCallbackFunction // 回调函数
);
// 启动定时器
BaseType_t xTimerStart(TimerHandle_t xTimer, TickType_t xTicksToWait);
// 停止定时器
BaseType_t xTimerStop(TimerHandle_t xTimer, TickType_t xTicksToWait);
// 修改周期
BaseType_t xTimerChangePeriod(TimerHandle_t xTimer, TickType_t xNewPeriod, TickType_t xTicksToWait);
// 定时器回调函数(不能阻塞!)
void vTimerCallback(TimerHandle_t xTimer)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0); // 翻转 LED
}
int main(void)
{
// 创建定时器,500ms 周期,自动重载
TimerHandle_t xTimer = xTimerCreate(
"LED_Timer",
pdMS_TO_TICKS(500),
pdTRUE, // 自动重载
NULL,
vTimerCallback
);
// 启动定时器
xTimerStart(xTimer, 0);
vTaskStartScheduler();
}
7内存管理
FreeRTOS 提供 5 种内存分配策略(heap_1 ~ heap_5),根据应用需求选择:
| 策略 | 特点 | 适用场景 |
|---|---|---|
| heap_1 | 只分配不释放,最简单 | 任务创建后永不删除 |
| heap_2 | 支持分配和释放,不合并碎片 | 分配大小固定 |
| heap_3 | 封装标准 malloc/free | 使用编译器提供的内存管理 |
| heap_4 | 支持分配和释放,合并相邻空闲块 | 通用场景(推荐) |
| heap_5 | 支持非连续内存区域 | 内存不连续的系统 |
大多数情况下使用 heap_4(推荐):支持碎片合并,适合动态创建/删除任务的场景。
如果任务创建后永不删除,使用 heap_1(最简单、最小)。
// 动态分配内存
void *pvPortMalloc(size_t xSize);
// 释放内存
void vPortFree(void *pv);
// 获取剩余空闲内存
size_t xPortGetFreeHeapSize(void);
// 获取历史最小空闲内存(水位线)
size_t xPortGetMinimumEverFreeHeapSize(void);
8标准库函数
FreeRTOS 标准 API 提供丰富的任务管理和通信函数。以下是常用函数分类汇总:
8.1 任务管理函数
| 函数 | 原型 | 功能 |
|---|---|---|
| 创建任务 | xTaskCreate(pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask) | 动态创建任务 |
| 删除任务 | vTaskDelete(xTask) | 删除任务(传 NULL 删除自身) |
| 挂起任务 | vTaskSuspend(xTaskToSuspend) | 挂起任务 |
| 恢复任务 | vTaskResume(xTaskToResume) | 恢复被挂起的任务 |
| 启动调度器 | vTaskStartScheduler() | 启动 FreeRTOS 调度器 |
| 停止调度器 | vTaskEndScheduler() | 停止调度器 |
| 任务延时 | vTaskDelay(xTicksToDelay) | 相对延时 |
| 绝对延时 | vTaskDelayUntil(pxPreviousWakeTime, xTimeIncrement) | 周期性延时 |
8.2 队列函数
| 函数 | 原型 | 功能 |
|---|---|---|
| 创建队列 | xQueueCreate(uxQueueLength, uxItemSize) | 创建队列 |
| 发送数据 | xQueueSend(xQueue, pvItemToQueue, xTicksToWait) | 发送到队列尾部 |
| 接收数据 | xQueueReceive(xQueue, pvBuffer, xTicksToWait) | 从队列头部接收 |
| 查看数据 | xQueuePeek(xQueue, pvBuffer, xTicksToWait) | 查看但不取出 |
| 队列长度 | uxQueueMessagesWaiting(xQueue) | 获取队列中数据个数 |
8.3 信号量/互斥锁函数
| 函数 | 原型 | 功能 |
|---|---|---|
| 创建二值信号量 | xSemaphoreCreateBinary() | 创建二值信号量 |
| 创建计数信号量 | xSemaphoreCreateCounting(uxMaxCount, uxInitialCount) | 创建计数信号量 |
| 创建互斥锁 | xSemaphoreCreateMutex() | 创建互斥锁 |
| 获取信号量 | xSemaphoreTake(xSemaphore, xTicksToWait) | 等待/获取信号量 |
| 释放信号量 | xSemaphoreGive(xSemaphore) | 释放/给出信号量 |
8.4 事件标志组函数
| 函数 | 原型 | 功能 |
|---|---|---|
| 创建事件组 | xEventGroupCreate() | 创建事件标志组 |
| 设置标志 | xEventGroupSetBits(xEventGroup, uxBitsToSet) | 设置事件标志 |
| 等待标志 | xEventGroupWaitBits(xEventGroup, uxBitsToWaitFor, xClearOnExit, xWaitForAllBits, xTicksToWait) | 等待事件标志 |
| 清除标志 | xEventGroupClearBits(xEventGroup, uxBitsToClear) | 清除事件标志 |
9HAL 库与 FreeRTOS
STM32 HAL 库与 FreeRTOS 配合使用时,需要注意一些关键点:
9.1 HAL 库中的 FreeRTOS 配置
// FreeRTOSConfig.h 关键配置
#define configUSE_PREEMPTION 1 // 1=抢占式调度
#define configCPU_CLOCK_HZ 72000000 // CPU 主频
#define configTICK_RATE_HZ 1000 // 时钟节拍 1kHz(1ms)
#define configMAX_PRIORITIES 5 // 最大优先级数
#define configMINIMAL_STACK_SIZE 128 // 最小堆栈大小
#define configTOTAL_HEAP_SIZE 10240 // 堆大小(10KB)
#define configUSE_MUTEXES 1 // 启用互斥锁
#define configUSE_COUNTING_SEMAPHORES 1 // 启用计数信号量
#define configUSE_TIMERS 1 // 启用软件定时器
#define configUSE_TASK_NOTIFICATIONS 1 // 启用任务通知
9.2 HAL 库函数与 FreeRTOS 配合
| HAL 函数 | 功能 | FreeRTOS 中的使用 |
|---|---|---|
HAL_Delay() | 毫秒延时 | 会阻塞整个任务,建议用 vTaskDelay() |
HAL_GPIO_TogglePin() | 翻转 GPIO | 可在任务中直接使用 |
HAL_UART_Transmit() | 串口发送 | 阻塞调用,需注意任务堆栈 |
HAL_UART_Receive_IT() | 串口中断接收 | 配合信号量通知任务 |
HAL_Delay():基于 SysTick 的忙等待,会阻塞整个 CPU,其他任务无法运行。
vTaskDelay():任务进入阻塞态,CPU 可以运行其他任务。在 FreeRTOS 中应该使用 vTaskDelay()。
9.3 标准库 vs HAL 库对比
| 对比 | 标准外设库 (StdPeriph) | HAL 库 |
|---|---|---|
| 抽象程度 | 贴近寄存器,效率高 | 高度抽象,跨系列可移植 |
| FreeRTOS 集成 | 手动配置 | CubeMX 可图形化配置 |
| 中断处理 | 手动编写 | HAL 有统一的中断处理框架 |
| 延时函数 | 自己实现 delay_ms | HAL_Delay() 或 vTaskDelay() |
| 推荐 | 学习理解底层 | 实际项目开发 |
⭐重点例题
例题 1:多任务 LED 闪烁
// 任务 1
void vTaskLED1(void *pvParameters)
{
for(;;)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
// 任务 2
void vTaskLED2(void *pvParameters)
{
for(;;)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 主函数
int main(void)
{
HAL_Init();
SystemClock_Config();
LED_Init();
xTaskCreate(vTaskLED1, "LED1", 128, NULL, 2, NULL);
xTaskCreate(vTaskLED2, "LED2", 128, NULL, 1, NULL);
vTaskStartScheduler();
for(;;);
}
关键点:① 任务函数必须是无限循环;② 必须有延时或阻塞,让出 CPU;③ 优先级高的任务先执行。
例题 2:队列通信
QueueHandle_t xQueue;
// 按键任务
void vTaskButton(void *pvParameters)
{
uint8_t msg = 1;
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
{
vTaskDelay(20); // 消抖
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET)
{
xQueueSend(xQueue, &msg, portMAX_DELAY);
while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET);
}
}
vTaskDelay(10);
}
}
// LED 任务
void vTaskLED(void *pvParameters)
{
uint8_t received;
for(;;)
{
if(xQueueReceive(xQueue, &received, portMAX_DELAY) == pdPASS)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
}
}
}
int main(void)
{
xQueue = xQueueCreate(5, sizeof(uint8_t));
xTaskCreate(vTaskButton, "Btn", 128, NULL, 2, NULL);
xTaskCreate(vTaskLED, "LED", 128, NULL, 1, NULL);
vTaskStartScheduler();
}
例题 3:信号量同步
SemaphoreHandle_t xSemaphore;
// 中断服务程序
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// 处理任务
void vTaskHandler(void *pvParameters)
{
for(;;)
{
if(xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdPASS)
{
printf("中断触发,处理事件!\r\n");
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
}
}
}
int main(void)
{
xSemaphore = xSemaphoreCreateBinary();
xTaskCreate(vTaskHandler, "Handler", 256, NULL, 2, NULL);
vTaskStartScheduler();
}
例题 4:互斥锁保护共享资源
SemaphoreHandle_t xMutex;
void vTaskA(void *pvParameters)
{
for(;;)
{
xSemaphoreTake(xMutex, portMAX_DELAY);
printf("[TaskA] Hello from A\r\n");
vTaskDelay(100);
xSemaphoreGive(xMutex);
vTaskDelay(500);
}
}
void vTaskB(void *pvParameters)
{
for(;;)
{
xSemaphoreTake(xMutex, portMAX_DELAY);
printf("[TaskB] Hello from B\r\n");
vTaskDelay(100);
xSemaphoreGive(xMutex);
vTaskDelay(700);
}
}
int main(void)
{
xMutex = xSemaphoreCreateMutex();
xTaskCreate(vTaskA, "A", 128, NULL, 2, NULL);
xTaskCreate(vTaskB, "B", 128, NULL, 1, NULL);
vTaskStartScheduler();
}
🎯自测(点击展开)
FreeRTOS 的任务有哪几种状态?
硬实时和软实时有什么区别?
任务间通信有哪些方式?
如何创建一个 FreeRTOS 任务?
实时操作系统的特点是什么?
vTaskDelay 和 vTaskDelayUntil 有什么区别?
互斥锁和信号量有什么区别?
FreeRTOS 的调度方式是什么?
软件定时器的回调函数有什么限制?
FreeRTOS 提供几种内存分配策略?推荐使用哪种?
📝强化题库
选择题点选即时判分;填空题输入后"检查"或"显示答案"。