FreeRTOS快速入门:从零开始学习实时操作系统¶

门派战报

FreeRTOS快速入门:从零开始学习实时操作系统¶ 学习目标¶ 完成本教程后,你将能够: 理解FreeRTOS的基本概念和架构 搭建FreeRTOS开发环境 创建和……

FreeRTOS快速入门:从零开始学习实时操作系统¶

学习目标¶

完成本教程后,你将能够:

理解FreeRTOS的基本概念和架构

搭建FreeRTOS开发环境

创建和运行第一个FreeRTOS任务

使用STM32CubeMX配置FreeRTOS

掌握基本的调试方法

理解任务调度的基本原理

前置要求¶

在开始本教程之前,你需要:

知识要求:

- 了解C语言基础(指针、结构体、函数)

- 熟悉基本的嵌入式开发概念

- 了解RTOS的基本概念(建议先阅读RTOS概述)

- 掌握STM32的基本开发流程

技能要求:

- 能够使用STM32CubeIDE或Keil MDK

- 会使用基本的调试工具

- 了解如何下载程序到开发板

准备工作¶

硬件准备¶

名称

数量

说明

参考链接

STM32开发板

1

STM32F4 Discovery或类似

-

ST-Link调试器

1

通常开发板自带

-

USB数据线

1

用于连接开发板

-

LED灯

2

用于演示任务(可选)

-

软件准备¶

开发环境:STM32CubeIDE v1.10+ 或 Keil MDK v5.30+

配置工具:STM32CubeMX v6.0+(CubeIDE已集成)

FreeRTOS版本:V10.3.1+(通过CubeMX自动集成)

辅助工具:串口调试助手(用于查看输出)

环境配置¶

安装STM32CubeIDE

从ST官网下载最新版本

按照安装向导完成安装

首次启动时选择工作空间

验证开发板连接

使用USB线连接开发板

打开设备管理器,确认ST-Link驱动正常

在CubeIDE中测试连接

准备串口工具

安装串口调试助手

配置波特率:115200

数据位:8,停止位:1,无校验

FreeRTOS基础概念¶

在开始实践之前,让我们快速了解FreeRTOS的核心概念:

什么是FreeRTOS?¶

FreeRTOS是一个开源的实时操作系统内核,专为嵌入式系统设计。它具有以下特点:

轻量级:内核代码小于10KB

可移植:支持40+种处理器架构

免费开源:MIT许可证

成熟稳定:广泛应用于商业产品

核心概念¶

任务(Task):

- FreeRTOS中的基本执行单元

- 每个任务都有独立的栈空间

- 任务可以有不同的优先级

调度器(Scheduler):

- 负责决定哪个任务运行

- 采用抢占式调度算法

- 高优先级任务优先执行

任务状态:

- 运行态(Running):正在执行

- 就绪态(Ready):等待CPU

- 阻塞态(Blocked):等待事件

- 挂起态(Suspended):被挂起

步骤1:创建FreeRTOS项目¶

1.1 新建STM32项目¶

打开STM32CubeIDE

选择 File → New → STM32 Project

在芯片选择界面:

选择你的目标芯片(如STM32F407VGT6)

或选择开发板型号(如STM32F4DISCOVERY)

点击 Next

输入项目名称:FreeRTOS_FirstProject

选择项目位置

点击 Finish

预期结果:

- 项目创建成功

- 自动打开CubeMX配置界面

1.2 配置系统时钟¶

在左侧导航栏选择 System Core → RCC

配置时钟源:

HSE:Crystal/Ceramic Resonator(外部晶振)

LSE:Disable(暂不使用)

切换到 Clock Configuration 标签页

配置系统时钟为最大频率(如168MHz for STM32F4)

1.3 启用FreeRTOS¶

在左侧导航栏找到 Middleware → FREERTOS

将 Interface 设置为 CMSIS_V1 或 CMSIS_V2

CMSIS_V1:经典API,简单易用

CMSIS_V2:新版API,功能更强

本教程使用CMSIS_V1

在 Configuration 标签页配置:

Tasks and Queues 标签:

默认已有一个 defaultTask

保持默认配置即可

FreeRTOS 标签:

Kernel settings:

USE_PREEMPTION:Enabled(抢占式调度)

CPU_CLOCK_HZ:自动设置

TICK_RATE_HZ:1000(1ms时钟节拍)

MAX_PRIORITIES:5(最大优先级数)

Memory management settings:

Memory Management scheme:heap_4(推荐)

TOTAL_HEAP_SIZE:15360 bytes(根据需要调整)

1.4 配置调试输出(可选)¶

为了方便调试,我们配置串口输出:

在 Connectivity 中选择 USART2

Mode:Asynchronous

配置参数:

Baud Rate:115200

Word Length:8 Bits

Parity:None

Stop Bits:1

在 GPIO Settings 中确认引脚配置:

PA2:USART2_TX

PA3:USART2_RX

1.5 生成代码¶

点击右上角的 GENERATE CODE 按钮

或使用快捷键:Alt + K

等待代码生成完成

选择 Open Project 打开项目

预期结果:

- 代码生成成功

- 项目结构中包含FreeRTOS相关文件

- 可以看到 Middlewares/Third_Party/FreeRTOS 目录

步骤2:理解生成的代码¶

2.1 项目结构¶

生成的项目包含以下关键文件:

FreeRTOS_FirstProject/

├── Core/

│ ├── Src/

│ │ ├── main.c # 主程序文件

│ │ ├── freertos.c # FreeRTOS任务定义

│ │ └── ...

│ └── Inc/

│ ├── main.h

│ ├── FreeRTOSConfig.h # FreeRTOS配置文件

│ └── ...

├── Middlewares/

│ └── Third_Party/

│ └── FreeRTOS/

│ ├── Source/ # FreeRTOS源码

│ └── ...

└── ...

2.2 main.c 文件分析¶

打开 Core/Src/main.c,关键代码如下:

int main(void)

{

/* MCU配置 */

HAL_Init();

SystemClock_Config();

/* 初始化外设 */

MX_GPIO_Init();

MX_USART2_UART_Init();

/* 初始化调度器 */

osKernelInitialize();

/* 创建默认任务 */

MX_FREERTOS_Init();

/* 启动调度器 */

osKernelStart();

/* 永远不会执行到这里 */

while (1)

{

}

}

代码说明:

- osKernelInitialize():初始化FreeRTOS内核

- MX_FREERTOS_Init():创建任务和其他RTOS对象

- osKernelStart():启动调度器,开始任务调度

- 调度器启动后,main()函数不再返回

2.3 freertos.c 文件分析¶

打开 Core/Src/freertos.c,查看默认任务:

/* 默认任务函数 */

void StartDefaultTask(void *argument)

{

/* 用户代码开始 */

for(;;)

{

osDelay(1); // 延时1个时钟节拍(1ms)

}

/* 用户代码结束 */

}

代码说明:

- 任务函数是一个无限循环

- osDelay():任务延时,释放CPU给其他任务

- 任务函数永远不应该返回

2.4 FreeRTOSConfig.h 配置文件¶

这个文件包含FreeRTOS的所有配置选项:

#define configUSE_PREEMPTION 1 // 使用抢占式调度

#define configUSE_IDLE_HOOK 0 // 不使用空闲钩子

#define configUSE_TICK_HOOK 0 // 不使用时钟节拍钩子

#define configCPU_CLOCK_HZ 168000000 // CPU频率

#define configTICK_RATE_HZ 1000 // 时钟节拍频率(1ms)

#define configMAX_PRIORITIES 5 // 最大优先级数

#define configMINIMAL_STACK_SIZE 128 // 最小栈大小(字)

#define configTOTAL_HEAP_SIZE 15360 // 堆大小(字节)

步骤3:创建第一个任务¶

现在让我们创建一个简单的LED闪烁任务。

3.1 配置GPIO¶

首先在CubeMX中配置LED引脚:

重新打开 .ioc 文件

找到LED引脚(如PD12 for STM32F4 Discovery)

设置为 GPIO_Output

在 GPIO Settings 中:

GPIO output level:Low

GPIO mode:Output Push Pull

GPIO Pull-up/Pull-down:No pull-up and no pull-down

Maximum output speed:Low

User Label:LED_GREEN

重新生成代码

3.2 添加LED任务¶

在 freertos.c 中添加新任务。找到 MX_FREERTOS_Init() 函数:

void MX_FREERTOS_Init(void) {

/* 用户代码开始 */

/* 创建LED任务 */

osThreadDef(ledTask, StartLedTask, osPriorityNormal, 0, 128);

ledTaskHandle = osThreadCreate(osThread(ledTask), NULL);

/* 用户代码结束 */

/* 创建默认任务 */

osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);

defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

}

3.3 实现LED任务函数¶

在 freertos.c 中添加任务函数:

/* 任务句柄 */

osThreadId ledTaskHandle;

/* LED任务函数 */

void StartLedTask(void const * argument)

{

/* 用户代码开始 */

for(;;)

{

// 点亮LED

HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_SET);

osDelay(500); // 延时500ms

// 熄灭LED

HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_RESET);

osDelay(500); // 延时500ms

}

/* 用户代码结束 */

}

代码说明:

- osDelay(500):延时500个时钟节拍(500ms)

- 任务在延时期间会进入阻塞态,让出CPU

- 其他任务可以在此期间运行

3.4 添加串口输出任务¶

为了更好地理解任务调度,我们添加一个串口输出任务:

/* 串口任务句柄 */

osThreadId uartTaskHandle;

/* 串口任务函数 */

void StartUartTask(void const * argument)

{

/* 用户代码开始 */

uint32_t counter = 0;

char msg[50];

for(;;)

{

// 格式化消息

sprintf(msg, "Task running: %lu\r\n", counter++);

// 发送到串口

HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);

// 延时1秒

osDelay(1000);

}

/* 用户代码结束 */

}

在 MX_FREERTOS_Init() 中创建任务:

/* 创建串口任务 */

osThreadDef(uartTask, StartUartTask, osPriorityNormal, 0, 128);

uartTaskHandle = osThreadCreate(osThread(uartTask), NULL);

注意:需要在文件开头添加头文件:

#include

#include

步骤4:编译和下载¶

4.1 编译项目¶

点击工具栏的 🔨 Build 按钮

或使用快捷键:Ctrl + B

查看控制台输出

预期输出:

Build Finished. 0 errors, 0 warnings.

常见编译错误:

如果出现 undefined reference to 'sprintf' 错误:

1. 右键项目 → Properties

2. C/C++ Build → Settings

3. MCU GCC Linker → Miscellaneous

4. 在 Other flags 中添加:-u _printf_float

4.2 下载程序¶

连接开发板到电脑

点击 ▶️ Run 按钮

或使用快捷键:F11(调试模式)

等待下载完成

预期结果:

- 程序下载成功

- LED开始闪烁

- 串口输出计数信息

4.3 查看串口输出¶

打开串口调试助手

选择正确的COM口

配置波特率:115200

打开串口

预期输出:

Task running: 0

Task running: 1

Task running: 2

Task running: 3

...

步骤5:调试和验证¶

5.1 使用调试器¶

点击 🐞 Debug 按钮启动调试

程序会在 main() 函数入口处暂停

设置断点:

- 在 StartLedTask() 函数中设置断点

- 在 StartUartTask() 函数中设置断点

观察任务切换:

1. 点击 ▶️ Resume 继续运行

2. 程序会在断点处停止

3. 观察哪个任务正在运行

4. 多次继续运行,观察任务切换

5.2 查看任务状态¶

在调试模式下,可以查看FreeRTOS的运行状态:

打开 Window → Show View → Other

选择 Debug → Variables

展开查看任务句柄和状态

关键变量:

- ledTaskHandle:LED任务句柄

- uartTaskHandle:串口任务句柄

- pxCurrentTCB:当前运行的任务控制块

5.3 性能分析¶

使用 SWV(Serial Wire Viewer) 进行性能分析:

在调试配置中启用SWV

配置ITM端口

查看任务执行时间和CPU占用率

步骤6:理解任务调度¶

6.1 任务优先级实验¶

修改任务优先级,观察调度行为:

/* 创建高优先级LED任务 */

osThreadDef(ledTask, StartLedTask, osPriorityHigh, 0, 128);

ledTaskHandle = osThreadCreate(osThread(ledTask), NULL);

/* 创建低优先级串口任务 */

osThreadDef(uartTask, StartUartTask, osPriorityLow, 0, 128);

uartTaskHandle = osThreadCreate(osThread(uartTask), NULL);

观察结果:

- 高优先级任务优先执行

- 低优先级任务在高优先级任务阻塞时才运行

6.2 任务延时实验¶

修改延时时间,观察任务行为:

void StartLedTask(void const * argument)

{

for(;;)

{

HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);

osDelay(100); // 改为100ms,LED闪烁更快

}

}

6.3 多任务并发实验¶

添加第三个任务,观察多任务并发:

/* 第二个LED任务 */

void StartLed2Task(void const * argument)

{

for(;;)

{

HAL_GPIO_TogglePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin);

osDelay(300); // 不同的闪烁频率

}

}

观察结果:

- 两个LED以不同频率闪烁

- 串口继续输出

- 所有任务并发执行

故障排除¶

问题1:程序无法启动¶

现象:

- 程序下载成功,但LED不闪烁

- 串口无输出

可能原因:

- 堆栈大小不足

- 时钟配置错误

- FreeRTOS配置错误

解决方法:

增加堆栈大小:

// 在FreeRTOSConfig.h中

#define configTOTAL_HEAP_SIZE (20 * 1024) // 增加到20KB

检查时钟配置:

确认 configCPU_CLOCK_HZ 与实际CPU频率一致

检查 SystemClock_Config() 函数

启用断言:

// 在FreeRTOSConfig.h中

#define configASSERT(x) if((x) == 0) { taskDISABLE_INTERRUPTS(); for(;;); }

问题2:任务不执行¶

现象:

- 某个任务从不运行

- 任务创建后无响应

可能原因:

- 任务优先级设置不当

- 任务栈溢出

- 任务被阻塞

解决方法:

检查任务优先级:

// 确保优先级在有效范围内(0 到 configMAX_PRIORITIES-1)

osThreadDef(task, TaskFunc, osPriorityNormal, 0, 128);

启用栈溢出检测:

// 在FreeRTOSConfig.h中

#define configCHECK_FOR_STACK_OVERFLOW 2

// 实现钩子函数

void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)

{

// 栈溢出时会调用此函数

while(1); // 停在这里,方便调试

}

增加任务栈大小:

// 将128改为256或更大

osThreadDef(task, TaskFunc, osPriorityNormal, 0, 256);

问题3:系统运行不稳定¶

现象:

- 系统偶尔死机

- 任务执行异常

- 数据错乱

可能原因:

- 中断优先级配置错误

- 共享资源未保护

- 内存分配失败

解决方法:

配置中断优先级:

// 在FreeRTOSConfig.h中

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15

#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5

// 确保使用FreeRTOS API的中断优先级 >= 5

HAL_NVIC_SetPriority(USART2_IRQn, 5, 0);

保护共享资源:

// 使用互斥量保护共享变量

osMutexId myMutex;

myMutex = osMutexCreate(osMutex(myMutex));

// 访问共享资源前获取互斥量

osMutexWait(myMutex, osWaitForever);

// 访问共享资源

osMutexRelease(myMutex);

问题4:串口输出乱码¶

现象:

- 串口输出乱码或不完整

可能原因:

- 波特率不匹配

- 多任务同时访问串口

- 缓冲区溢出

解决方法:

检查波特率配置

使用互斥量保护串口:

osMutexId uartMutex;

void StartUartTask(void const * argument)

{

uartMutex = osMutexCreate(osMutex(uartMutex));

for(;;)

{

osMutexWait(uartMutex, osWaitForever);

HAL_UART_Transmit(&huart2, msg, len, HAL_MAX_DELAY);

osMutexRelease(uartMutex);

osDelay(1000);

}

}

总结¶

通过本教程,你学习了:

✅ FreeRTOS的基本概念和架构

✅ 使用STM32CubeMX配置FreeRTOS项目

✅ 创建和管理FreeRTOS任务

✅ 理解任务调度和优先级

✅ 使用基本的调试方法

✅ 解决常见问题

关键要点¶

任务是FreeRTOS的基本执行单元

每个任务都有独立的栈空间

任务函数必须是无限循环

使用 osDelay() 让出CPU

调度器负责任务切换

采用抢占式调度

高优先级任务优先执行

相同优先级任务时间片轮转

合理配置是关键

堆栈大小要充足

优先级要合理分配

中断优先级要正确配置

调试技巧很重要

使用断点观察任务切换

启用栈溢出检测

使用断言捕获错误

进阶挑战¶

尝试以下挑战来巩固学习:

挑战1:多任务协作¶

创建3个任务,实现以下功能:

- 任务1:每500ms读取一次按键状态

- 任务2:根据按键状态控制LED闪烁频率

- 任务3:每秒通过串口输出系统状态

提示:需要使用任务间通信机制(下一节课学习)

挑战2:优先级实验¶

创建不同优先级的任务,观察调度行为:

- 高优先级任务(osPriorityHigh)

- 普通优先级任务(osPriorityNormal)

- 低优先级任务(osPriorityLow)

思考:

- 低优先级任务什么时候能运行?

- 如果高优先级任务不延时会怎样?

挑战3:资源监控¶

实现一个监控任务,定期输出:

- 每个任务的栈使用情况

- 系统剩余堆内存

- 任务运行时间统计

提示:使用FreeRTOS的统计API

完整代码示例¶

freertos.c 完整代码¶

/* Includes */

#include "FreeRTOS.h"

#include "task.h"

#include "main.h"

#include "cmsis_os.h"

#include

#include

/* Private variables */

osThreadId defaultTaskHandle;

osThreadId ledTaskHandle;

osThreadId uartTaskHandle;

/* Private function prototypes */

void StartDefaultTask(void const * argument);

void StartLedTask(void const * argument);

void StartUartTask(void const * argument);

/* External variables */

extern UART_HandleTypeDef huart2;

void MX_FREERTOS_Init(void) {

/* Create the thread(s) */

/* LED任务 */

osThreadDef(ledTask, StartLedTask, osPriorityNormal, 0, 128);

ledTaskHandle = osThreadCreate(osThread(ledTask), NULL);

/* 串口任务 */

osThreadDef(uartTask, StartUartTask, osPriorityNormal, 0, 128);

uartTaskHandle = osThreadCreate(osThread(uartTask), NULL);

/* 默认任务 */

osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);

defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

}

/* StartDefaultTask function */

void StartDefaultTask(void const * argument)

{

for(;;)

{

osDelay(1);

}

}

/* StartLedTask function */

void StartLedTask(void const * argument)

{

for(;;)

{

HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);

osDelay(500);

}

}

/* StartUartTask function */

void StartUartTask(void const * argument)

{

uint32_t counter = 0;

char msg[50];

for(;;)

{

sprintf(msg, "Task running: %lu\r\n", counter++);

HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);

osDelay(1000);

}

}

测试环境¶

硬件环境:

- 开发板:STM32F407 Discovery

- 调试器:ST-Link V2

- LED:板载LED(PD12-PD15)

软件环境:

- IDE:STM32CubeIDE v1.10.1

- HAL库版本:v1.27.1

- FreeRTOS版本:V10.3.1

- 编译器:GCC ARM 10.3

下一步学习¶

建议按以下顺序继续学习:

1. 深入任务管理¶

FreeRTOS任务创建与管理

学习任务的创建、删除、挂起、恢复

理解任务优先级和调度策略

2. 任务间通信¶

FreeRTOS队列通信实战

学习使用队列在任务间传递数据

理解阻塞和非阻塞通信

3. 同步机制¶

FreeRTOS信号量应用

学习二值信号量和计数信号量

理解互斥量和临界区保护

4. 高级特性¶

FreeRTOS软件定时器

FreeRTOS事件标志组

FreeRTOS任务通知

参考资料¶

官方文档¶

FreeRTOS官方网站

https://www.freertos.org/

包含完整的API文档和教程

FreeRTOS参考手册

https://www.freertos.org/Documentation/RTOS_book.html

详细的概念和API说明

STM32 FreeRTOS移植指南

ST官方应用笔记

包含移植细节和优化建议

推荐书籍¶

《Mastering the FreeRTOS Real Time Kernel》

FreeRTOS作者编写

免费下载:https://www.freertos.org/Documentation/RTOS_book.html

《嵌入式实时操作系统FreeRTOS原理与实践》

中文书籍,适合入门

包含大量实例

在线资源¶

FreeRTOS论坛

https://forums.freertos.org/

活跃的社区支持

STM32社区

https://community.st.com/

ST官方技术支持

GitHub示例代码

https://github.com/FreeRTOS/FreeRTOS

官方示例和Demo

视频教程¶

FreeRTOS官方YouTube频道

系列教程视频

实时操作系统概念讲解

STM32在线培训

ST官方培训课程

包含FreeRTOS专题

常见问题解答¶

Q1: FreeRTOS和裸机编程有什么区别?¶

A: 主要区别在于:

- 裸机:程序按顺序执行,需要手动管理任务切换

- FreeRTOS:多任务并发执行,调度器自动管理任务切换

优势:

- 代码结构更清晰

- 更容易实现复杂功能

- 更好的实时性保证

Q2: 什么时候应该使用FreeRTOS?¶

A: 以下情况建议使用FreeRTOS:

- 需要同时处理多个独立任务

- 对实时性有要求

- 系统功能复杂,需要模块化设计

- 需要使用中间件(如TCP/IP协议栈)

不适合的场景:

- 简单的单一功能应用

- 资源极度受限(RAM < 8KB)

- 对功耗要求极高的场景

Q3: 如何选择合适的堆内存大小?¶

A: 计算方法:

总堆大小 = 所有任务栈大小之和 + 队列/信号量等对象大小 + 20%余量

示例:

- 3个任务,每个128字(512字节)= 1536字节

- 2个队列,每个100字节 = 200字节

- 余量20% = 347字节

- 总计约2KB,建议设置为3-4KB

Q4: 任务优先级如何分配?¶

A: 优先级分配原则:

1. 实时性要求高的任务:高优先级

2. 周期性任务:中等优先级

3. 后台任务:低优先级

4. 空闲任务:最低优先级(自动)

示例:

- 中断后处理任务:osPriorityHigh

- 数据采集任务:osPriorityAboveNormal

- 显示更新任务:osPriorityNormal

- 日志记录任务:osPriorityBelowNormal

Q5: 如何调试任务栈溢出?¶

A: 步骤:

1. 启用栈溢出检测

2. 实现钩子函数

3. 使用调试器查看栈使用情况

4. 增加栈大小或优化代码

工具:

- uxTaskGetStackHighWaterMark():查看栈剩余空间

- SystemView:可视化任务执行和栈使用

反馈与支持:

如果你在学习过程中遇到问题:

- 💬 在评论区留言讨论

- 📧 发送邮件到:support@embedded-platform.com

- 🐛 报告问题:GitHub Issues

贡献代码:

欢迎提交改进建议和示例代码!

版权声明:本教程采用 CC BY-SA 4.0 许可协议。

最后更新:2024-01-15

文档版本:1.0