3、FreeRTOS入门:FreeRTOS简介、源码结构、移植方法、任务创建与删除、任务优先级
各位好,欢迎来到《运动控制多任务调度设计》的第三讲。今天咱们聊聊FreeRTOS。说实话,在运动控制领域,裸机跑循环的时代已经过去了。你想想看,一个电机要转,一个编码器要读,一个通信要处理,还要响应急停——单靠一个while(1)大循环,迟早要出问题。
FreeRTOS是什么?说白了,就是一个轻量级的实时操作系统内核。它帮你管理CPU的时间,让多个任务看起来像是“同时”在运行。我在做多轴联动控制时,就靠它来分配每个轴的计算时间片。嗯,这里要注意,FreeRTOS不是万能的,但在资源受限的MCU上,它绝对是最靠谱的选择之一。
FreeRTOS简介
FreeRTOS的全称是“Free Real-Time Operating System”,免费、开源、小巧。它的核心功能就三样:任务调度、任务间通信、内存管理。你不需要关心底层怎么切换上下文,只需要把精力放在业务逻辑上。
我个人习惯把FreeRTOS看作一个“任务管家”。你创建几个任务,告诉它哪个任务优先级高,哪个任务需要多少栈空间,剩下的它帮你搞定。比如在运动控制中,我通常会创建三个任务:一个负责轨迹规划(高优先级),一个负责IO监控(中优先级),一个负责日志输出(低优先级)。
核心特点:
- 抢占式调度:高优先级任务随时可以打断低优先级任务
- 协作式调度:任务主动让出CPU
- 时间片轮转:同优先级任务轮流执行
- 支持任务、队列、信号量、互斥量、软件定时器
我曾经在一个项目中,因为不了解抢占式调度的特性,导致一个低优先级的电机控制任务被高优先级的通信任务频繁打断,结果电机出现了抖动。后来我调整了优先级,才解决了问题。所以,优先级设计真的不是随便填个数字就完事的。
源码结构
FreeRTOS的源码结构非常清晰。你下载下来后,主要关注这几个目录:
| 目录/文件 | 说明 |
|---|---|
| Source/ | 核心源码,包括tasks.c、queue.c、timers.c等 |
| Source/include/ | 头文件,FreeRTOS.h、task.h、queue.h等 |
| Source/portable/ | 移植层,针对不同MCU的适配代码 |
| Demo/ | 官方示例工程,可以直接参考 |
你可能会问:“这么多文件,我该看哪个?”我的建议是,先看tasks.c和queue.c。这两个文件是FreeRTOS的灵魂。tasks.c负责任务创建、删除、调度;queue.c负责任务间通信。其他的比如timers.c、event_groups.c,用到的时候再看。
我记得刚开始学FreeRTOS时,总想一口气看完所有源码。结果越看越晕。后来我师傅告诉我:“别贪多,先跑起来一个任务,再慢慢加。”嗯,这个建议我一直用到现在。
移植方法
移植FreeRTOS到你的板子上,其实就三步:
- 选择移植层:在Source/portable/目录下找到你的MCU对应的文件夹。比如STM32就用GCC/ARM_CM4F。
- 配置FreeRTOSConfig.h:这个文件是FreeRTOS的“开关”。你需要配置CPU频率、堆栈大小、最大优先级数等。
- 实现滴答定时器:FreeRTOS需要一个1ms的定时器中断来驱动调度。通常用SysTick来实现。
小技巧: 移植时,先不要急着改配置。直接用官方Demo里的FreeRTOSConfig.h,然后根据你的MCU调整一下CPU频率和堆栈大小就行。我刚开始移植时,因为把configMINIMAL_STACK_SIZE设得太小,结果任务一运行就崩溃。后来改成128个字(512字节),才稳定下来。
下面是一个典型的FreeRTOSConfig.h片段:
#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 )
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES ( 5 )
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 20 * 1024 ) )
这里要注意,configTICK_RATE_HZ设为1000,表示每个tick是1ms。如果你需要更精细的时间控制,可以设成10000(0.1ms),但这样会增加CPU开销。我在运动控制中通常用1000Hz,因为电机控制的周期一般是1ms,刚好匹配。
任务创建与删除
任务创建是FreeRTOS最基础的操作。用xTaskCreate()函数就行。它的原型是这样的:
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, // 任务函数指针
const char * const pcName, // 任务名称(调试用)
configSTACK_DEPTH_TYPE usStackDepth, // 栈深度(单位:字)
void *pvParameters, // 任务参数
UBaseType_t uxPriority, // 任务优先级
TaskHandle_t *pxCreatedTask // 任务句柄(输出)
);
举个例子,创建一个电机控制任务:
void vMotorControlTask(void *pvParameters)
{
while(1)
{
// 读取编码器
// 计算PID
// 输出PWM
vTaskDelay(pdMS_TO_TICKS(1)); // 1ms周期
}
}
void main(void)
{
TaskHandle_t xMotorHandle = NULL;
xTaskCreate(vMotorControlTask, "Motor", 256, NULL, 3, &xMotorHandle);
vTaskStartScheduler(); // 启动调度器
}
删除任务用vTaskDelete()。如果你在任务内部删除自己,可以传NULL:
void vSomeTask(void *pvParameters)
{
// 做一些初始化工作
// 如果初始化失败,删除自己
vTaskDelete(NULL);
}
警告: 千万不要在中断服务函数中调用vTaskDelete()!我曾经犯过这个错误,结果系统直接卡死。正确的做法是在中断中发送一个信号量,然后在任务中处理删除操作。
任务优先级
优先级是FreeRTOS调度的心脏。数值越大,优先级越高。比如优先级3的任务,会打断优先级2的任务。同优先级的任务,则按时间片轮转。
在运动控制中,我一般这样分配优先级:
- 最高优先级(4-5):紧急停止、故障处理。这些任务必须立即响应,不能有任何延迟。
- 高优先级(3):电机控制、轨迹插补。这些任务对实时性要求高,周期固定。
- 中优先级(2):通信处理(CAN、EtherCAT)、IO监控。
- 低优先级(1):日志输出、状态显示、参数配置。
你可能会问:“为什么紧急停止要单独一个任务?”嗯,这是因为在运动控制中,急停信号可能来自硬件中断,但处理逻辑需要在一个任务中完成。如果把这个任务放在低优先级,万一CPU正忙着做轨迹计算,急停信号就被忽略了。这可不是闹着玩的。
我曾经在一个项目中,把通信任务设成了最高优先级。结果通信数据量一大,电机控制任务就被饿死了。后来我调整了优先级,让电机控制任务比通信任务高一级,问题就解决了。所以,优先级设计一定要结合你的实际场景来定。
知识体系结构图
下面我用一张SVG图来总结本章的核心内容。这张图展示了FreeRTOS在运动控制中的角色,以及任务创建、优先级、移植之间的关系。
这张图把FreeRTOS的核心知识点串起来了。你从内核出发,左边是任务创建与删除,右边是优先级,下面是移植方法和源码结构。最底部是运动控制中的典型应用场景。嗯,这样一看,是不是清晰多了?
本章小结:
- FreeRTOS是一个轻量级实时内核,适合资源受限的MCU
- 源码结构清晰,重点看tasks.c和queue.c
- 移植只需三步:选移植层、配Config、实现滴答定时器
- 任务创建用xTaskCreate,删除用vTaskDelete
- 优先级设计要结合实际场景,避免任务饿死
好了,这一讲就到这里。FreeRTOS入门其实不难,关键是动手跑起来。下一讲我们会深入任务调度机制,看看FreeRTOS到底是怎么切换任务的。到时候见。
公众号:蓝海资料掘金营,微信deep3321