做嵌入式这几年,经常有人问我一个问题:FreeRTOS到底怎么装到Keil工程里?网上的教程大多数是“下载源码包→手动复制文件→改include path→改FreeRTOSConfig.h”,整套流程没半小时下不来,而且版本一换就踩坑。其实Keil MDK内置了一个很好用的东西——RTE(Run-Time Environment,运行时环境管理器),配合官方软件包,点几下鼠标就能把FreeRTOS装进工程。这个方式特别适合快速验证、学习入门,也适合给现有工程无痛添加RTOS能力。
我最早也是手动移植的,源码文件哪些要加、哪些不用加、编译器宏定义怎么配,全是靠试错试出来的。后来用顺手了RTE才发现,官方其实早就把活干完了。这篇文章就把完整的操作流程、背后原理、常见坑一次性讲清楚,不管你是刚入门的新手,还是用惯了STM32CubeMX想回到Keil生态的老开发,都可以参考。
1. 为什么推荐用RTE装FreeRTOS:省掉的远不止下载这一步
1.1 传统移植方式到底麻烦在哪
先说手动移植。一个标准的FreeRTOS源码包里有几个目录:tasks.c、queue.c、list.c这些是内核核心文件,不管什么芯片都得编译;portable里那个跟芯片架构相关的port.c和portmacro.h,得挑对版本;heap_1到heap_5至少要选一个,内存管理方式不一样,作用也不一样。
这还没完。挑完文件,你得在Keil里手动添加头文件路径。FreeRTOS的include目录、portable目录、编译器相关目录,少加一个就是一片“file not found”。然后还要从demo工程里翻出FreeRTOSConfig.h,改时钟频率、改堆大小、改tick rate,一个不注意就编译过了但运行乱跑。
这套流程里最烦的还不是步骤多,而是版本匹配问题。比如你下载的FreeRTOS版本对应的是CMSIS-RTOS v1封装,而你的芯片支持包默认用v2,编译出来一堆关于osDelay函数的报错,很多人就是卡在这里放弃了。
1.2 RTE方案的原理:把依赖关系交给工具去管
RTE全称是Run-Time Environment,中文叫运行时环境管理器。它是Keil MDK从5.0开始引入的组件管理机制,核心思路是:把芯片支持包、CMSIS、中间件、OS内核这些东西做成一个个可勾选的“组件”,每个组件里显式声明自己依赖什么、需要编译哪些源文件、需要添加哪些头文件路径。
当你勾选FreeRTOS组件时,Keil会自动完成下面这些事:
- 自动把FreeRTOS内核源码文件加进工程,不用手动挑选;
- 自动配置头文件搜索路径,不用一个个手动添加include目录;
- 自动生成FreeRTOSConfig.h,并且放到正确的位置;
- 自动处理好CMSIS-RTOS API的适配层,让osDelay、osThreadNew等标准接口可以直接调用;
- 自动加上启动文件、系统时钟初始化文件(前提是你勾选了Device StartUp组件)。
这就像装软件时你用包管理器而不是去官网手动下载安装包,依赖关系帮你理清楚,卸载升级也干净。
1.3 这种方式的适用范围和注意点
RTE方案最适合的场景是:使用Keil MDK 5.x及以上版本、芯片是ARM Cortex-M系列、官方有对应PACK支持包的工程。对STM32全系列、NXP的部分型号、GD32等国产芯片,基本都适用。
不太适合哪类场景呢?一是非常老的MDK4工程,升级到MDK5后还没做过Pack适配;二是需要对FreeRTOS内核做深度定制的场景,比如改内存分配算法、改上下文切换逻辑,RTE生成的目录结构会约束你的文件组织方式,这种时候直接源码移植更自由。还有一点要注意:RTE方式默认给你的是CMSIS-RTOS v2封装层,如果你看内核源码的学习资料,很多内容都是直接针对原生FreeRTOS API的,中间多了一层cmsis_os2.c适配,初学看代码会有点绕。
不过对大多数人来说,先把系统跑起来、把任务调度和信号量机制用熟,RTE方式就是最快的路径。
2. 准备工作:版本选型与Pack安装
2.1 Keil MDK版本和编译器选择
RTE功能是MDK 5.0之后才完全成型的,所以第一步就是确保你的Keil至少是MDK 5.18以上的版本,建议直接用最新的MDK 5.38或更高版本。从官网下载安装包,一路next装完,这个不多说。
ARM Compiler的版本也值得提一句。MDK里默认的编译器是Arm Compiler 6(AC6),它对C99和C11的支持比老的AC5好,代码优化也更强。但很多老工程和网上的代码库是基于AC5写的,编译会报一些奇怪的语法错误。用RTE装FreeRTOS的话,AC5和AC6都能正常编译,但我推荐新工程直接AC6,性能更好,FreeRTOS官方对AC6的支持也早就成熟了。
如果你用的是MDK社区版,也没有问题。MDK Community Edition对非商业用途免费,包含的基础功能足够跑通RTE和FreeRTOS,只是代码体积限制在32KB以内,学习验证足够用了。
2.2 芯片支持包(Device Pack)的安装
RTE能识别哪些芯片、能提供哪些启动文件和系统初始化代码,全靠Device Pack。以最常用的STM32F103C8T6为例,打开Pack Installer界面,在Search栏输入STM32F1,找到“Keil::STM32F1xx_DFP”这个包,点击Install。
这一步有几个坑需要注意:
- 如果你公司的电脑装了安全软件,Pack安装时最好临时放行,不然容易装到一半卡住;
- 有些版本的Pack Installer在线下载速度很慢,可以到Keil官网手动下载PACK文件(后缀.pack),然后在Pack Installer里用File→Import命令导入;
- Pack版本不是越新越好,你的Keil版本太老时,新PACK可能提示“requires MDK version x.x”,这时候要么升级MDK,要么找兼容的旧版PACK。
安装完成后,在Project窗口里右键工程名,选择“Manage Run-Time Environment”,就能看到RTE界面了。如果这个界面里很多组件都是灰色,多半是Pack没装全,回去检查Device Pack。
2.3 从零新建一个最小的基础工程
在勾选FreeRTOS之前,先把一个空工程建好。操作路径:Project→New μVision Project,选择你要保存的目录,输入工程名,然后在Device选项卡里搜索并选中STM32F103C8。
注意一个细节:Device选择框里搜索出来的型号可能有很多个。如果你用的是常见的蓝色Pill板子,选STM32F103C8即可,内存是64KB Flash、20KB RAM。有些变种是C8T6、CBT6,区别主要是Flash大小和封装,RTE配置方式完全一致。
选中芯片后,Keil会弹出一个“Manage Run-Time Environment”窗口,问你要不要添加组件。这一步先别急着勾FreeRTOS,先勾上两个基础组件:
- CMSIS → CORE:这个是Cortex-M内核的访问层,core_cm3.h等文件就靠这个加进来;
- Device → StartUp:这个会把启动文件startup_stm32f103xb.s和system_stm32f1xx.c加进来。
点OK后,工程里就有最小启动代码了。此时编译一下,应该能通过,但是没写main函数会有个warning,不影响。这个空工程就是后续安装FreeRTOS的底座。
3. 一键安装实操:RTE界面到底怎么点
3.1 进入Manage Run-Time Environment窗口
有两种方式打开RTE窗口:工具栏上有一个绿色小图标,鼠标悬停会显示“Manage Run-Time Environment”;也可以在Project菜单下找到这个选项。选中工程名后点击,RTE窗口就会打开。
这个窗口左侧是一个树状列表,按功能包分门别类地展示所有可用组件。右侧是描述信息和版本号。中间的关键一列是“Sel.”勾选列,所有组件操作都在这一列完成。
初次看到这个界面会有点懵,组件实在太多了。别慌,我们只需要关心三个区域:CMSIS、Device、以及FreeRTOS对应的组件分组。
3.2 勾选组件:CMSIS CORE、RTOS2和FreeRTOS内核
在RTE窗口里按顺序操作:
第一步,确认CMSIS下的“CORE”已经被勾选。如果前面建空工程时勾过,这里就是绿色对勾状态。
第二步,展开CMSIS分支,找到“RTOS2 (API)”,把它勾上。RTOS2是指CMSIS-RTOS v2标准接口,它本身不是操作系统,只是一层适配接口,具体由FreeRTOS内核实现。有了这层接口,你写的代码可以调用统一的osDelay、osThreadNew等函数,完全不关心底层是FreeRTOS还是RTX5。
第三步,展开FreeRTOS组件。不同版本的Pack展示位置可能不一样,有时候在“CMSIS”下面直接能找到“FreeRTOS”,有时候会单独出现“Keil::FreeRTOS”分组。在里面勾选“core”组件——这就是FreeRTOS内核本身。
第四步,展开FreeRTOS组件的“Heap Configuration”,勾选heap_4.c。这是最容易被漏掉的一步。FreeRTOS的内存分配是通过pvPortMalloc实现的,heap_1到heap_5是五种不同的实现策略,分别适合不同的应用场景。heap_4支持内存合并、分配和释放用同一个内存池,实际项目里用得最多,新手就直接选它。
全部勾选后,RTE窗口底部的状态栏会显示依赖关系是否完整。如果某个组件前面出现黄色警告图标,说明它的前置依赖没有满足,鼠标放上去会提示缺什么,按提示补勾就好。
然后点OK,回到主界面。这时候你会发现,Project窗口里多出了一个“RTE”分组,里面自动列出了FreeRTOS的源码文件,比如tasks.c、queue.c、list.c、port.c、heap_4.c,还有cmsis_os2.c这个适配层文件。这些文件已经自动关联到正确的编译路径,不需要你手动添加任何include path。
3.3 RTE自动生成的目录和文件结构
点OK之后,打开工程所在的文件夹,你会发现多了一个叫“RTE”的目录,里面按组件分类存放文件。大致是这个结构:
RTE/ ├── CMSIS/ │ └── cmsis_armcc.h ├── Device/ │ └── STM32F103C8/ │ └── RTE_Components.h └── FreeRTOS/ ├── FreeRTOSConfig.h ├── event_groups.c ├── list.c ├── queue.c ├── tasks.c ├── timers.c ├── portable.c ├── heap_4.c └── ...这个目录结构有几个值得注意的地方:
- FreeRTOSConfig.h是自动生成的默认配置,后面的调优基本都在这个文件里进行;
- RTE_Components.h是MDK内部用的,声明了当前工程启用了哪些RTE组件,别去动它,删了反而会出编译错误;
- 源码文件是“复制”到工程目录下的,而不是引用Pack内部的只读位置,这点对手动修改源码很友好。
3.4 FreeRTOSConfig.h的初始化修改
自动生成的FreeRTOSConfig.h基本能用,但有几个配置项必须根据你的芯片和需求改一遍。用文本编辑器打开这个文件,重点看这几个宏:
- configCPU_CLOCK_HZ:CPU频率。STM32F103C8如果外部晶振8MHz、PLL倍频到72MHz,这里就填72000000。默认模板里可能填的是SystemCoreClock这个变量,在system_stm32f1xx.c里定义,也能用,但我习惯直接填数字,清晰不容易出问题。
- configTOTAL_HEAP_SIZE:FreeRTOS内存池总大小。STM32F103C8的RAM是20KB,RTE模板默认可能给的是4096或8192字节。如果你只跑两三个简单任务,4096可能也够,但一旦用到队列、信号量、软件定时器,建议直接改成8192甚至12288,留足余量。
- configMINIMAL_STACK_SIZE:空闲任务栈大小,单位是字(word),不是字节。100字对应400字节,对大多数单片机来说够用。
- configUSE_PREEMPTION:是否开启抢占式调度。默认1,保持默认。
- configUSE_TIME_SLICING:是否启用时间片轮转调度。默认1,多个同优先级任务可以轮流执行,保持默认。
- configCHECK_FOR_STACK_OVERFLOW:栈溢出检测。建议设为2,运行期一旦检测到任务栈溢出,会调用vApplicationStackOverflowHook函数,方便排查问题。代价是牺牲一点性能,但调试阶段绝对值得。
- configASSERT:断言宏。在调试阶段建议保留定义,比如定义为
configASSERT(x) if((x)==0) { taskDISABLE_INTERRUPTS(); for(;;); },这样参数传错、API调用顺序不对时程序会直接死循环在原地,方便定位。发布代码时再关掉。
改完保存,然后重新编译。如果编译通过且没有报头文件缺失,FreeRTOS的“安装”工作就结束了。
4. 写第一个任务并跑起来
4.1 CMSIS-RTOS v2 API的基本框架
RTE方式默认采用CMSIS-RTOS v2接口,所以写代码时不必直接调用xTaskCreate、vTaskDelay这些原生FreeRTOS函数,而是用osThreadNew、osDelay等标准接口。这样做最大的好处是代码可移植性高,以后想换RTX5,应用层代码几乎不用动。
一个最小程序的骨架是这样的:
#include "cmsis_os2.h" osThreadId_t tid_led1; void led1_task(void *argument) { for (;;) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); osDelay(500); } } int main(void) { // 硬件初始化省略 HAL_Init(); SystemClock_Config(); osKernelInitialize(); tid_led1 = osThreadNew(led1_task, NULL, NULL); osKernelStart(); while (1) { } }注意初始化顺序:先做硬件外设初始化,再调用osKernelInitialize初始化内核,然后创建线程,最后osKernelStart启动调度器。只要osKernelStart一执行,CPU的控制权就交给FreeRTOS了,main函数的while(1)基本不会再执行到,所以别把业务逻辑写在osKernelStart后面。
osThreadNew的第三个参数是osThreadAttr_t类型的线程属性,传NULL会使用默认配置,默认栈大小由configMINIMAL_STACK_SIZE决定。如果你的任务里局部变量比较大、或者调用深度较深的函数,默认栈可能不够,可以显式设置:
osThreadAttr_t attr = {0}; attr.stack_size = 256; // 单位:字节?实际上是字?这里需要注意CMSIS的stack_size单位是字节这里有个单位问题要说清楚:CMSIS-RTOS v2的osThreadAttr_t中的stack_size单位是字节,而FreeRTOS原生API的栈大小单位是字(word)。cmsis_os2.c适配层里会帮我们换算,所以用CMSIS接口时按字节来配。256字节对一个简单任务来说是够用的,但保守一点填512也行。
4.2 两个LED任务的实际例子
单任务还看不出RTOS的价值,写两个LED任务验证任务切换更直观。假设板子上有两个LED,分别接在PA5和PB0:
#include "cmsis_os2.h" void led_a_task(void *argument) { for (;;) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); osDelay(300); } } void led_b_task(void *argument) { for (;;) { HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0); osDelay(700); } } int main(void) { HAL_Init(); SystemClock_Config(); // GPIO初始化,PA5和PB0配成输出 GPIO_Init(); osKernelInitialize(); osThreadNew(led_a_task, NULL, NULL); osThreadNew(led_b_task, NULL, NULL); osKernelStart(); while (1) { } }下载到板子上,现象应该是LED_A以300ms周期翻转、LED_B以700ms周期翻转,两个任务互不阻塞。这背后发生的事是:每1ms,SysTick触发一次tick中断,FreeRTOS在tick中断里检查两个任务的延时是否到期,到期就把它从阻塞状态移到就绪状态,然后按优先级和调度策略决定运行哪一个。
4.3 时钟冲突问题:HAL库和FreeRTOS抢SysTick
很多人跑上面的例程时发现一个怪现象:程序下载后完全没反应,或者在HAL_Delay函数里卡死。这种问题十有八九是SysTick冲突。
STM32的HAL库默认用SysTick作为HAL_InitTick的时基,也就是HAL_Delay、HAL_GetTick都靠SysTick维持。而FreeRTOS的tick也是靠SysTick实现。两边都往SysTick中断里塞自己的处理函数,不冲突才怪。
解决办法有几个,从简单到复杂排一下:
办法一:如果嵌入式应用里不需要用到HAL_Delay,那问题其实不大。FreeRTOS在启动调度器的port层代码里会把SysTick的优先级设置为RTOS要求的值,并且接管SysTick_Handler。HAL_Delay会失去准确度,但单纯从一个任务里翻GPIO不太受影响,只要你自己别再调用HAL_Delay就行。
办法二:在SysTick_Handler里同时调用HAL_IncTick(),让两边的时基共存。具体做法是开启FreeRTOS的configOVERRIDE_DEFAULT_TICK_CONFIGURATION特性,或者在startup文件里手动修改SysTick的向量跳到FreeRTOS的xPortSysTickHandler,同时把HAL_IncTick的调用也带上。这个做法复杂度略高,但对外设初始化还要用HAL_Delay的工程很实用。
办法三(最推荐):让HAL库的时基改用其他定时器,比如STM32F1的TIM6或TIM7。这样HAL_Delay走TIM6的更新中断,FreeRTOS用SysTick,各自井水不犯河水。具体做法是在main函数前重定义HAL_InitTick,或者参考CubeMX生成工程里的stm32f1xx_hal_timebase_tim.c文件。Keil工程里没有这个文件,需要手写一个替换版的HAL_InitTick,把时基时钟切换到TIM6,并实现对应的中断服务函数。
我个人项目的做法是:新工程从一开始就固定用“HAL时基走TIM6 + FreeRTOS走SysTick”的方案,一次配置好后面所有项目复用,从此告别HAL_Delay相关的各种玄学故障。
4.4 用调试器确认任务在跑
如果手边有ST-Link或J-Link,强烈建议把调试器接上看一眼。编译下载后进入Debug模式,暂停程序,打开View→Watch窗口,添加变量pxCurrentTCB或者观察uxCurrentNumberOfTasks,可以看到当前有几个任务在调度器里注册。
另外一个简单粗暴的方式是在任务里加计数变量:
volatile uint32_t led_a_count = 0; volatile uint32_t led_b_count = 0; void led_a_task(void *argument) { for (;;) { led_a_count++; osDelay(300); } }在Watch窗口里加这两个变量,全速运行时如果led_a_count和led_b_count都在持续增加,说明两个任务都在正常调度。如果只有一个在涨,说明另一个任务可能没被创建成功,或者创建后立即卡死了。
5. 高频问题排查:从编译报错到运行崩溃
5.1 编译报错速查表
RTE方式虽然省心,但也不是完全不会出问题。整理一份我在实际项目中踩过的编译错误和解决办法:
| 报错信息 | 常见原因 | 解决办法 |
|---|---|---|
| file not found: FreeRTOSConfig.h | RTE组件没勾选完整 | 重新打开RTE窗口,确认FreeRTOS组件和CMSIS CORE都已勾选 |
| undefined symbol: pvPortMalloc | heap没有被编译进工程 | RTE里勾选Heap Configuration下的某个heap实现,如heap_4.c |
| undefined symbol: vTaskDelay | FreeRTOS内核源码没编译 | 确认FreeRTOS core组件已勾选,检查Project窗口RTE分组里是否有tasks.c |
| Error: L6218E: Undefined symbol osThreadNew | CMSIS-RTOS v2适配层缺失 | 检查cmsis_os2.c是否在工程里,没有就重新勾选RTOS2 (API)组件 |
| .\obj\freertos.hex: error: Q0147E: Failed to create directory | 工程输出目录不可写或路径错误 | Options for Target→Output→Select Folder for Objects里重新指定输出目录 |
| Error: L6915E: Library reports error: Conflicting data types | 某个头文件重复定义 | 检查项目中是否同时包含了旧版的FreeRTOS头文件目录 |
最后那个Q0147E报错比较隐蔽,热词里也出现了这个错误。我第一次遇到时一直怀疑是Keil的bug,后来发现是工程放在了公司的加密盘目录下,输出目录权限有问题。把工程复制到本地磁盘、重建Output路径就好了。
5.2 编译通过但程序不运行
编译没问题、下载成功但现象全无,这是最让人头疼的。按下面的顺序排查:
第一步,确认复位后是否进入了HardFault。在Debug模式下全速运行,然后暂停,看Current Position是否停在HardFault_Handler里。如果是,大概率是启动文件里的Stack_Size不够或者任务栈溢出,把启动文件里的Stack_Size从默认的0x400改成0x1000再试。
第二步,确认调度器是否真的启动了。在osKernelStart调用前后各设置一个GPIO翻转,如果调度器启动后没有执行任何任务,说明内核已经跑起来但任务创建失败。这时代码里检查osThreadNew的返回值,如果为NULL,就是堆内存不够,把configTOTAL_HEAP_SIZE调大。
第三步,检查中断优先级配置。FreeRTOS要求SysTick和PendSV中断使用最低优先级。MDK生成的模板默认把优先级分组设置成4位全部用于抢占优先级,如果外设初始化里改了优先级分组或把某个中断优先级设得比SysTick还低,就可能出现调度异常。在HAL_Init后调用HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4)强制确认一次,能解决很多诡异问题。
5.3 任务栈溢出和内存不足
FreeRTOS的每个任务都有独立栈,栈大小在创建时指定。栈溢出最大的特点就是“不定时崩溃”:有时候跑几分钟才挂,有时候一启动就HardFault,还有时候只在某些特定任务切换时挂。
把configCHECK_FOR_STACK_OVERFLOW设为2之后,一旦检测到溢出,FreeRTOS会调用vApplicationStackOverflowHook函数。在这个函数里放一个断点或者点亮一个LED:
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) { // 停在这里,通过pcTaskName看是哪个任务溢出 for (;;) { } }任务的栈大小设置没有固定的标准,我的经验是:任务里如果只是调调用GPIO翻转、osDelay这类轻量操作,128字节栈就够;如果用到sprintf、printf之类的格式化输出,或者调用层级较深的库函数,512字节起步;如果不确定,先用大栈跑通功能,再用uxTaskGetStackHighWaterMark函数测量实际栈高水位,逐步调整到合理值。
5.4 如何在调试时查看任务状态
Keil的RTE为FreeRTOS提供了Event Recorder组件,在RTE窗口里勾选FreeRTOS → Debug → Event Recorder后,配合调试器的Trace功能,可以在Keil的分析窗口里看到任务切换的事件流。这个功能挺好用,但对调试器型号有要求,ST-Link的SWV速度一般,数据量大了会丢帧。
更可靠、跨平台的方案是使用FreeRTOS自带的vTaskList函数。它能把当前所有任务信息格式化到字符串里:
static char task_info[512]; void task_info_task(void *argument) { for (;;) { vTaskList(task_info); printf("%s\n", task_info); osDelay(2000); } }函数的输出格式是每个任务一行,包括任务名、状态、优先级、剩余栈空间和任务编号。B代表阻塞态,R代表就绪态,X代表挂起态。看到所有任务都周期性地在B和R之间切换,说明系统调度正常。
还有一个个人习惯:我在项目的调试串口上挂一个“心跳任务”,每1秒翻转一次心跳LED并打印一条tick计数,这个心跳正常,基本就能判定系统整体是健康和稳定的。
6. 再深一步:配置调优与升级维护经验
6.1 常用配置项调整方向
跑通基础例程只是第一步,实际产品里对FreeRTOS的应用还有几个方向值得调整。
首先是configTICK_RATE_HZ,也就是系统节拍频率。默认是1000Hz,也就是每1ms进一次tick中断。这个值越高,任务延时精度越高,但同时CPU在中断里花的时间也越多。低功耗产品会把这个值降到100Hz甚至更低,配合tickless模式,功耗能降一个量级。
其次是软件定时器(Software Timer)。如果用到osTimerNew这类API,需要确保FreeRTOSConfig.h里的configUSE_TIMERS为1,并且给定时器服务任务分配足够的栈空间。默认模板通常是开启的,但栈大小不太够,我习惯把configTIMER_TASK_STACK_DEPTH从默认的256改成512。
再就是低功耗相关的tickless模式。把configUSE_TICKLESS_IDLE设为2,系统在空闲任务里会停止tick中断,直到有外部事件唤醒,这对电池供电的设备是刚需。STM32在标准库和HAL库里都有对应的低功耗接口,但要注意FreeRTOS的port层对tickless的支持和芯片的低功耗模式需要配合调,不是单纯改一个宏就能生效。
6.2 手动改源码要注意的事
RTE生成的FreeRTOS源码文件是复制到工程目录下的,所以你可以直接改动里面的代码。但改了之后有个麻烦:一旦你在Pack Installer里升级FreeRTOS组件版本,RTE会重新生成这些文件,所有手动改动都会被覆盖。
所以在RTE方式下,我不建议直接改源码文件,尤其是tasks.c、queue.c这些核心文件。如果确实需要扩展功能,优先考虑在应用层实现,或者通过hook机制(比如空闲任务的vApplicationIdleHook、tick hook等)挂接自定义逻辑。只有到了非改内核不可的地步,再考虑把工程转成源码移植模式。
还有一个升级问题:RTE组件版本升级前一定要备份工程。我有一次点了Pack Installer的更新,IDE直接把所有FreeRTOS源码更新到了新版本,结果工程编译出来各种警告,排查了很久才定位到是新版port层对CM3芯片的配置有变化。从那以后,我的习惯是先把整个工程文件夹复制一份再升级Pack。
6.3 从RTE工程进一步学习的路径建议
如果你是初学者,用RTE方式跑通任务创建、信号量、队列这些机制之后,我建议再做一个事情:对比看一下cmsis_os2.c这个适配文件里osDelay是怎么调用vTaskDelay的,osThreadNew是怎么调用xTaskCreate的。看完你会发现,CMSIS接口其实就是对原生FreeRTOS API的一层薄封装,把参数单位换一换、返回值翻译一下,本质上没有魔改任何内核逻辑。
有了这层认识,再去看FreeRTOS源码里的tasks.c、queue.c,就比直接啃源码容易得多。然后再试着关掉RTE的自动生成,手动从源码包移植一遍,你会明显感觉到当初用RTE积累的概念帮助了理解。两种方式不是对立的,而是学习路径上的两个阶段。
本文内容基于STM32F103C8T6的MDK开发环境,实际操作中如果你用的是GD32、AT32或者瑞萨等其他芯片,Pack名称会不一样,但步骤完全一致。个人项目里我始终保留着一个从RTE自动生成、不做任何手工改动的最小FreeRTOS工程模板,所有新项目都从它开始克隆,省掉了每次重新选择组件的时间。用顺手之后你会发现,真正让工作高效的不是工具本身,而是把工具的标准流程固定成自己的习惯。