一、为什么学FreeRTOS/RT-Thread?嵌入式开发者必看
嵌入式开发从"裸机"到"多任务"的跨越,核心是掌握实时操作系统(RTOS)。FreeRTOS和RT-Thread是目前国内嵌入式领域最主流的两款RTOS:FreeRTOS以"轻量、稳定、资料多"成为入门首选,RT-Thread以"国产、中文生态、组件丰富"适配国内工业场景。本文从零基础讲起,对比两款RTOS的核心用法,帮你快速落地项目。
1.1 两款RTOS核心差异(新手选型必看)
| 维度 | FreeRTOS | RT-Thread |
|---|---|---|
| 开源协议 | MIT开源(完全免费,商用无限制) | Apache 2.0(国产开源,合规性高) |
| 核心特点 | 内核精简(核心代码仅数万行),专注实时调度 | 模块化设计,内置文件系统、网络、GUI等组件 |
| 生态支持 | 全球社区,英文文档为主,STM32适配最佳 | 中文社区,官方教程完善,适配国产MCU(如华大、复旦微) |
| 适用场景 | 消费电子、IoT低功耗设备、小型工业控制 | 国内工业控制、智能家居、车载设备 |
| 学习成本 | 低(入门案例多,易上手) | 中(组件多,需掌握模块化使用) |
1.2 学习前提(避免走弯路)
- 具备基础C语言和STM32裸机开发能力(会配置GPIO、串口、定时器);
- 准备硬件:STM32F103/STM32F407开发板(两款RTOS均完美适配);
- 开发环境:Keil MDK 5.x 或 STM32CubeIDE(本文以Keil为例)。
二、FreeRTOS入门:从环境搭建到多任务点灯
2.1 FreeRTOS工程搭建(STM32F103为例)
步骤1:下载FreeRTOS源码
从FreeRTOS官网(www.freertos.org/)下载最新版源码,解压...
- 内核文件:
tasks.c(任务调度)、queue.c(消息队列)、semphr.c(信号量/互斥锁); - 端口文件:
portable/ARM/Cortex-M3/port.c(适配Cortex-M3内核); - 内存管理:
portable/MemMang/heap_4.c(推荐使用,解决内存碎片)。
步骤2:移植到STM32工程
- 新建STM32F103裸机工程(配置时钟、GPIO、串口);
- 将FreeRTOS核心文件添加到工程,新建
FreeRTOS分组,加入上述文件; - 添加头文件路径:
FreeRTOS/Source/include、FreeRTOS/Source/portable/ARM/Cortex-M3; - 复制
FreeRTOSConfig.h到工程(关键配置文件,文末附模板)。
步骤3:核心配置(FreeRTOSConfig.h关键宏)
c
#define configUSE_PREEMPTION 1 // 开启抢占式调度(必开)
#define configUSE_TIME_SLICING 1 // 开启时间片轮转
#define configTICK_RATE_HZ 100 // 系统节拍100Hz(时间片10ms)
#define configMAX_PRIORITIES 16 // 任务最大优先级(0-15,15最高)
#define configMINIMAL_STACK_SIZE 128 // 最小任务栈大小
#define configTOTAL_HEAP_SIZE ((size_t)10*1024) // 堆大小10KB
2.2 FreeRTOS第一个例程:多任务点灯
核心目标:创建两个任务,LED1每500ms闪烁,LED2每1000ms闪烁,体现多任务并发。
步骤1:硬件初始化(LED引脚配置)
c
// LED1接PA0,LED2接PA1
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_SetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1); // 初始灭灯
}
步骤2:创建任务函数
c
// 任务1:LED1闪烁(优先级1)
void LED1_Task(void *pvParameters)
{
while(1)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 亮灯
vTaskDelay(500); // 延时500ms(释放CPU)
GPIO_SetBits(GPIOA, GPIO_Pin_0); // 灭灯
vTaskDelay(500);
}
}
// 任务2:LED2闪烁(优先级1)
void LED2_Task(void *pvParameters)
{
while(1)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
vTaskDelay(1000);
GPIO_SetBits(GPIOA, GPIO_Pin_1);
vTaskDelay(1000);
}
}
步骤3:在main函数中启动调度器
c
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); // 中断分组(必配)
LED_Init();
USART1_Init(115200); // 串口初始化(可选,用于调试)
// 创建任务
xTaskCreate(LED1_Task, "LED1_Task", 128, NULL, 1, NULL);
xTaskCreate(LED2_Task, "LED2_Task", 128, NULL, 1, NULL);
vTaskStartScheduler(); // 启动调度器(从此任务接管CPU)
// 调度器启动后,以下代码不会执行
while(1);
}
关键说明
vTaskDelay()是FreeRTOS延时函数,会释放CPU,让其他任务执行(区别于裸机for循环延时);- 任务优先级数值越大,优先级越高;同优先级任务按时间片轮转执行;
- 任务栈大小设为128(单位:字,STM32中1字=4字节),避免栈溢出。
三、RT-Thread入门:从移植到FinSH控制台
RT-Thread的核心优势是"组件化"和"中文生态",入门重点是掌握其内核用法和FinSH控制台(调试神器)。
3.1 RT-Thread工程搭建(STM32F103为例)
步骤1:下载RT-Thread源码
从RT-Thread官网(www.rt-thread.org/)下载`RT-Thre... Standard`版本,核心目录:
kernel:内核文件(任务、调度、IPC通信);components:组件(finsh、dfs文件系统、lwip网络);bsp/stm32/stm32f103-template:STM32F103模板工程(直接复用)。
步骤2:快速移植(复用官方模板)
- 复制
bsp/stm32/stm32f103-template到本地,用Keil打开工程; - 配置芯片型号为
STM32F103C8T6,修改时钟配置为72MHz; - 开启FinSH控制台:在
rtconfig.h中开启#define RT_USING_FINSH,并配置串口1为FinSH端口。
3.2 RT-Thread第一个例程:多任务点灯+FinSH调试
步骤1:创建点灯线程(RT-Thread中"任务"称为"线程")
c
#include <rtthread.h>
#include "stm32f10x.h"
#define LED1_PIN GET_PIN(A, 0)
#define LED2_PIN GET_PIN(A, 1)
// 线程1:LED1闪烁
void led1_thread_entry(void *parameter)
{
rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT);
while (1)
{
rt_pin_write(LED1_PIN, PIN_LOW);
rt_thread_mdelay(500); // RT-Thread延时函数
rt_pin_write(LED1_PIN, PIN_HIGH);
rt_thread_mdelay(500);
}
}
// 线程2:LED2闪烁
void led2_thread_entry(void *parameter)
{
rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT);
while (1)
{
rt_pin_write(LED2_PIN, PIN_LOW);
rt_thread_mdelay(1000);
rt_pin_write(LED2_PIN, PIN_HIGH);
rt_thread_mdelay(1000);
}
}
步骤2:初始化线程并启动
c
int main(void)
{
// 创建线程1(优先级10,栈大小1024)
rt_thread_t led1_thread = rt_thread_create("led1",
led1_thread_entry,
RT_NULL,
1024,
10,
10);
// 创建线程2
rt_thread_t led2_thread = rt_thread_create("led2",
led2_thread_entry,
RT_NULL,
1024,
10,
10);
// 启动线程
if (led1_thread != RT_NULL)
rt_thread_startup(led1_thread);
if (led2_thread != RT_NULL)
rt_thread_startup(led2_thread);
return 0;
}
步骤3:FinSH控制台调试(RT-Thread核心优势)
编译下载程序后,通过串口助手连接开发板(波特率115200),输入list_thread可查看所有线程状态:
arduino
msh > list_thread
thread pri status sp stack size max used left tick error
---------- --- ------- ---------- ---------- ------ ---------- ---
led2 10 suspend 0x00000070 0x00000400 23% 999 000
led1 10 suspend 0x00000070 0x00000400 23% 499 000
tshell 20 running 0x00000100 0x00001000 10% 999 000
idle 31 ready 0x00000020 0x00000100 0% 999 000
通过FinSH可实时查看线程、修改参数,大幅降低调试难度。
四、FreeRTOS/RT-Thread核心功能对比(实战必学)
4.1 任务/线程管理(核心用法)
| 功能 | FreeRTOS API | RT-Thread API |
|---|---|---|
| 创建任务 | xTaskCreate() | rt_thread_create()/rt_thread_init() |
| 启动任务 | vTaskStartScheduler() | rt_thread_startup() |
| 延时 | vTaskDelay() | rt_thread_mdelay() |
| 挂起/恢复 | vTaskSuspend()/vTaskResume() | rt_thread_suspend()/rt_thread_resume() |
4.2 任务同步与通信(解决多任务协作)
1. 消息队列(任务间传数据)
- FreeRTOS示例:
c
// 创建队列(长度5,每个元素4字节)
QueueHandle_t xQueue = xQueueCreate(5, sizeof(uint32_t));
// 发送数据
uint32_t data = 100;
xQueueSend(xQueue, &data, portMAX_DELAY);
// 接收数据
uint32_t recv_data;
xQueueReceive(xQueue, &recv_data, portMAX_DELAY);
- RT-Thread示例:
c
// 创建队列
rt_mq_t mq = rt_mq_create("mq", 4, 5, RT_IPC_FLAG_FIFO);
// 发送数据
uint32_t data = 100;
rt_mq_send(mq, &data, sizeof(data));
// 接收数据
uint32_t recv_data;
rt_mq_recv(mq, &recv_data, sizeof(recv_data), RT_WAITING_FOREVER);
2. 信号量(解决资源竞争)
- FreeRTOS示例:
c
// 创建二值信号量
SemaphoreHandle_t xSemaphore = xSemaphoreCreateBinary();
// 获取信号量
xSemaphoreTake(xSemaphore, portMAX_DELAY);
// 释放信号量
xSemaphoreGive(xSemaphore);
- RT-Thread示例:
c
// 创建信号量
rt_sem_t sem = rt_sem_create("sem", 1, RT_IPC_FLAG_FIFO);
// 获取信号量
rt_sem_take(sem, RT_WAITING_FOREVER);
// 释放信号量
rt_sem_release(sem);
五、实战项目:FreeRTOS/RT-Thread实现串口数据采集+处理
5.1 需求说明
- 任务1(串口接收):接收上位机发送的数据,放入消息队列;
- 任务2(数据处理):从队列中取数据,解析并打印;
- 要求:避免串口资源竞争,保证数据不丢失。
5.2 FreeRTOS实现核心代码
c
// 定义队列句柄
QueueHandle_t xUartQueue;
// 串口中断服务函数(接收数据)
void USART1_IRQHandler(void)
{
uint8_t ch;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
ch = USART_ReceiveData(USART1);
// 中断中发送数据到队列(用FromISR版本)
xQueueSendFromISR(xUartQueue, &ch, NULL);
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
// 任务1:串口初始化(略)
// 任务2:数据处理任务
void DataProcess_Task(void *pvParameters)
{
uint8_t recv_ch;
while(1)
{
// 从队列取数据
if(xQueueReceive(xUartQueue, &recv_ch, portMAX_DELAY))
{
printf("接收数据:%c\n", recv_ch);
}
}
}
// main函数中创建队列和任务
xUartQueue = xQueueCreate(32, sizeof(uint8_t));
xTaskCreate(DataProcess_Task, "DataProcess", 128, NULL, 2, NULL);
5.3 RT-Thread实现核心差异
- 串口中断中发送数据到消息队列,使用
rt_mq_send_from_isr(); - 线程优先级配置:数据处理线程优先级高于串口接收线程,保证数据及时处理。
六、学习误区与进阶方向
6.1 新手常见误区
- 死记API不理解原理:比如分不清
vTaskDelay()和裸机延时的区别,核心是前者释放CPU,后者占用CPU; - 任务栈设置过小:FreeRTOS/RT-Thread任务栈建议至少128字(FreeRTOS)/1024字节(RT-Thread);
- 滥用全局变量:多任务间传数据必须用队列/信号量,避免全局变量冲突;
- 忽略中断优先级:FreeRTOS/RT-Thread中断优先级需低于
configMAX_SYSCALL_INTERRUPT_PRIORITY。
6.2 进阶学习方向
- FreeRTOS:深入内存管理(heap_4/heap_5)、优先级继承、低功耗模式;
- RT-Thread:学习设备驱动框架、LWIP网络协议栈、DFS文件系统;
- 项目落地:基于FreeRTOS做IoT传感器节点,基于RT-Thread做工业控制终端。
七、附:FreeRTOSConfig.h极简模板(STM32F103)
c
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
#define configUSE_PREEMPTION 1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
#define configUSE_TICKLESS_IDLE 0
#define configCPU_CLOCK_HZ (72000000)
#define configTICK_RATE_HZ (100)
#define configMAX_PRIORITIES (16)
#define configMINIMAL_STACK_SIZE (128)
#define configTOTAL_HEAP_SIZE ((size_t)(10*1024))
#define configMAX_TASK_NAME_LEN (16)
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_TASK_NOTIFICATIONS 1
#define configUSE_MUTEXES 1
#define configUSE_RECURSIVE_MUTEXES 1
#define configUSE_COUNTING_SEMAPHORES 1
#define configUSE_ALTERNATIVE_API 0
#define configQUEUE_REGISTRY_SIZE 8
#define configUSE_QUEUE_SETS 0
#define configUSE_TIME_SLICING 1
#define configUSE_NEWLIB_REENTRANT 0
#define configENABLE_BACKWARD_COMPATIBILITY 1
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5
// 中断优先级配置
#define configPRIO_BITS 4
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
#define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
// 钩子函数
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCHECK_FOR_STACK_OVERFLOW 0
#define configUSE_MALLOC_FAILED_HOOK 0
// 调试配置
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
// 包含头文件
#include "stm32f10x.h"
#endif /* FREERTOS_CONFIG_H */
总结
FreeRTOS和RT-Thread的核心都是"多任务调度",入门先掌握任务创建、队列/信号量用法,再结合实战项目深化。新手建议先学FreeRTOS打基础,再学RT-Thread掌握组件化开发;国内项目优先选RT-Thread(适配国产芯片+中文支持),海外项目优先选FreeRTOS(生态更通用)。学习过程中多动手调试,比如用FinSH查看RT-Thread线程状态,用vTaskList()查看FreeRTOS任务状态,能快速定位问题。