3、FreeRTOS移植与配置:移植步骤、FreeRTOSConfig.h详解、堆栈设置
好,咱们进入正题。这一章讲的是FreeRTOS的移植和配置。说白了,就是把一个跑在PC上的操作系统,硬生生塞进你的单片机里。我刚开始做这个的时候,也觉得挺玄乎的,后来发现,只要摸清几个关键点,这事儿其实没那么复杂。
3.1 移植步骤:别急着复制粘贴
很多人移植FreeRTOS,第一反应就是去官网下载源码,然后一股脑全加进工程。嗯,我当年也这么干过,结果编译报错一大堆,找bug找了三天。后来我学乖了,移植其实就三步:
- 拿核心源码:只需要三个文件夹——Source(内核源码)、Portable(与硬件相关的移植层)、Demo(参考例程)。别把整个仓库都拖进来,没必要。
- 选对移植层文件:这是最关键的。你用的什么芯片?ARM Cortex-M3?M4?还是RISC-V?去Portable文件夹里找到对应的编译器目录。比如我用的是STM32F4,Keil编译器,那就选
Portable/GCC/ARM_CM4F或者Portable/Keil/ARM_CM4F。选错了,系统根本跑不起来。 - 配置中断和时钟:FreeRTOS需要一个心跳中断,通常是SysTick。你得确保这个中断的优先级设置正确。我见过有人把SysTick优先级设得比所有中断都低,结果任务调度直接被其他中断卡死。
3.2 FreeRTOSConfig.h详解:这个文件是灵魂
FreeRTOSConfig.h,说白了就是操作系统的配置文件。你想想看,一个操作系统要跑多快、支持多少任务、用不用互斥量,全在这里定义。我每次做新项目,第一件事就是打开这个文件,逐行检查。
这里有几个关键配置项,我挑重要的说:
| 配置项 | 作用 | 我的建议值 |
|---|---|---|
configUSE_PREEMPTION |
是否启用抢占式调度 | 1(抢占式,实时性更好) |
configCPU_CLOCK_HZ |
CPU主频,单位Hz | 根据实际芯片填写,比如168000000 |
configTICK_RATE_HZ |
系统心跳频率,单位Hz | 1000(1ms一个tick,够用) |
configMAX_PRIORITIES |
最大任务优先级数量 | 5~32,别设太大,浪费RAM |
configMINIMAL_STACK_SIZE |
空闲任务的最小堆栈大小 | 128(单位是字,不是字节) |
configTOTAL_HEAP_SIZE |
FreeRTOS管理的总堆大小 | 根据RAM大小,比如4096或8192 |
configCPU_CLOCK_HZ 和 configTICK_RATE_HZ 这两个值必须匹配你的硬件实际时钟。我曾经在一个项目里把CPU主频写错了,结果系统时间全乱套,任务调度像抽风一样。后来查了三天才发现是这里的问题。
还有一个容易被忽略的配置——configUSE_IDLE_HOOK 和 configUSE_TICK_HOOK。这两个钩子函数,说白了就是让你在空闲任务或者每个tick中断里插入自己的代码。我个人建议,非必要不开启。因为钩子函数是在中断上下文或者特殊上下文里执行的,稍不注意就会出问题。
3.3 堆栈设置:别让任务饿死
堆栈设置,是新手最容易踩坑的地方。你想想看,每个任务都有自己的栈空间,用来保存局部变量、函数调用信息。栈设小了,任务跑着跑着就崩了;栈设大了,RAM不够用。
那到底设多大?我一般这么估算:
- 简单任务(只点灯、读IO):128字(512字节)就够了。
- 中等任务(有少量局部数组、调用几个函数):256字(1024字节)。
- 复杂任务(有递归、大数组、浮点运算):512字(2048字节)起步。
但说实话,估算总是不准的。我有个更实用的方法:
实战技巧:先用 uxTaskGetStackHighWaterMark() 这个API,在任务运行一段时间后,查看栈的剩余空间。如果剩余空间小于总栈大小的20%,那就说明栈设小了,得加。如果剩余空间超过80%,那就说明设大了,可以减一减,省点RAM。
我曾经在一个血氧仪项目里,有个任务负责处理PPG信号,算法里用了几个大数组。一开始我设了256字的栈,结果跑着跑着就HardFault。后来用高水位标记一查,发现栈只剩不到10个字了。赶紧改成512字,问题解决。嗯,从那以后,我每个任务都会留个高水位标记的调试接口。
另外,关于堆栈的单位,FreeRTOS里默认是字(word)。在32位处理器上,1个字 = 4字节。所以 configMINIMAL_STACK_SIZE 128 实际占用的RAM是 128 × 4 = 512字节。这个换算关系一定要记清楚,不然你算出来的RAM用量全是错的。
3.4 堆内存管理:选对策略
FreeRTOS提供了5种堆内存管理方案,从heap_1到heap_5。我简单说一下:
- heap_1:最简单,只能分配不能释放。适合那些任务创建后永不删除的场景。血氧仪这种嵌入式设备,任务基本是固定的,用heap_1就挺好。
- heap_2:可以分配和释放,但可能产生碎片。我一般不用,因为碎片问题很难排查。
- heap_3:直接调用标准库的malloc/free,但需要你自己保证线程安全。不推荐在裸机移植里用。
- heap_4:可以分配和释放,而且能合并相邻的空闲块,减少碎片。这是我最常用的方案。
- heap_5:支持多个不连续的内存区域。如果你的芯片有多个RAM块,比如CCM RAM和普通SRAM,可以用这个。
好了,这一章的内容就这些。移植和配置是FreeRTOS的基础,基础打牢了,后面写任务调度才能得心应手。下一章咱们聊聊任务创建与删除,到时候我会分享一个我踩过的坑——任务句柄没保存好,导致系统资源泄漏。嗯,到时候细说。