TencentOS tiny / RT-Thread / FreeRTOS 怎么选:一次表计项目的选型对比实录
前阵子接了个三相电能表的项目,客户在技术协议里加了一条我很少见的要求:关键软件组件须采用国产开源实时操作系统,并提供选型评估报告——理由是要过他们内部的供应链与自主可控评审。我们团队三个人,这几年的活儿全是在 FreeRTOS 上跑的,从 8 位机到 M4 都是它。这条要求一下把我们按在了桌前:换不换?换谁?
我没敢凭印象拍脑袋。花了三周,在同一块核心板(国产 Cortex-M4,512KB Flash / 64KB RAM)上把三家都搭了一遍最小可跑工程,把资源占用、API 手感、组件、许可协议、现场可维护性逐项过了一遍,最后交了一张带权重的打分表。
结论不是”谁最强”,而是那句老话:选型不是选最牛的技术,是选在你这堆约束里最不别扭的那一个。
一句话结论:三家都能跑业务,差异全在约束边界上——要内核极简 + 生态最大 + 不押注单一厂商,选 FreeRTOS;要组件齐 + 现场命令行可调 + 本土支持跟得上,选 RT-Thread;产品就是 NB-IoT / LoRa 模组接云、要从最小资源起步,选 TencentOS tiny。真正的坑不在选谁,而在迁移时那三个”同名不同义”的 API 语义。
第一步:先把约束写成清单,否则对比就是口水
我犯的第一个错误是:一上来就开始比较三家内核的调度器实现。比了两天,全是”都挺好”。
后来被主管一句话点醒:你比的不是操作系统,是约束。 于是我把项目约束逐条落纸,这才发现真正能一票否决的只有三四个:
| 约束项 | 本项目取值 | 对选型的硬影响 |
|---|---|---|
| Flash / RAM 预算 | 512KB / 64KB,业务代码已占 220KB | 内核省个 3KB 毫无意义,组件才是大头 |
| 国产化要求 | 关键软件组件需国产开源 | FreeRTOS 需额外说明,本土两家中选 |
| 许可协议 | 商业闭源产品,不接受任何 copyleft 传染风险 | 排除 GPL 系列,逐条核对条款 |
| 现场可维护性 | 表计装在配电间,运维只能插串口 | 必须有量产可用的命令行调试 |
| 通信协议栈 | DL/T 645 + DL/T 698.45(另有上行 MQTT) | 需要完整可裁的 TCP/IP + TLS |
| 低功耗 | 电池辅助供电,异常掉电上报 | 需要 tickless / 低功耗框架 |
| 团队熟悉度 | 3 人中 2 人只有 FreeRTOS 经验 | 迁移成本要算进总账,别只算技术分 |
| 长期维护 | 产品生命周期 8 年 | 维护方要能撑 8 年,不看热度看节奏 |
这张表一列出来,问题的形状立刻变了:我们不是在选内核,是在选”组件栈 + 调试手段 + 运维能力”的打包方案。
三家定位速查:先认清它们各自为什么存在
| 维度 | FreeRTOS | RT-Thread | TencentOS tiny |
|---|---|---|---|
| 出身 / 维护方 | 2003 年 Richard Barry 创建,现由 Amazon 维护 | 国内团队开发,2006 年起积累,全自主知识产权 | 腾讯 2018 年立项,2019 年 9 月开源,2020 年 10 月捐赠给开放原子开源基金会 |
| 许可协议 | MIT | Apache-2.0(3.1.0 及以前是 GPL V2+,老项目务必核基线版本) | BSD-3-Clause(第三方组件另注:spiffs/CmBacktrace 走 MIT,mbedTLS/CMSIS 走 Apache-2.0) |
| 内核定位 | 极简内核,其余全交给你组合 | 内核 + 组件化中间层(IoT OS 思路) | 轻量内核 + IoT 协议栈 + 端云一体 |
| 生态强项 | 几乎每种芯片的原厂 SDK 都带它 | 软件包中心 + 设备框架 + FinSH | AT/SAL 模组适配 + 云平台对接 + LoRaWAN/NB-IoT |
| 官方宣称的最小内核 | ROM 约 5–10KB,调度器 RAM 236 字节起 | Nano 版 3KB Flash / 1.2KB RAM | 内核 ROM 1.8KB / RAM 0.6KB;典型传感应用 ROM 约 12KB / RAM 约 3.3KB |
| 典型场景 | 通用 MCU 项目、原厂例程、认证需求(有商业认证版) | 中资源 MCU 上的联网设备、需要现场调试的产品 | 模组类 / 传感终端 / 需要快速接腾讯云的设备 |
这里有一条必须提前说清楚:许可协议不是”都是开源的所以随便用”。 三个协议的差别在商业闭源产品上会实打实地影响法务评审:
- MIT / BSD-3-Clause:保留版权声明即可,无专利条款,条款最短、风险最低;
- Apache-2.0:同样允许商业闭源,但多了显式的专利授权 + 专利诉讼终止条款,并要求保留 NOTICE 文件。对我们的价值是正向的(专利被明确授予了),但要把声明文件纳入发布清单;
- RT-Thread 的 GPL V2+ 是历史区间:如果项目基线是 3.0.x 甚至更早,评估报告里必须写清楚,否则一个协议问题能拖掉整个立项。
顺带一句:开源许可不背书功能安全。 如果产品要走 IEC 61508 / ISO 26262 这类功能安全认证,开源内核本身通常不能直接拿来用,得走认证版本或自证流程(FreeRTOS 有商业认证版本 SAFERTOS,本土 RTOS 也有对应的商业支持与认证渠道,具体以官方公告为准)。这一步别靠猜,早期就去问,否则后期返工代价极大。
资源占用:别信任何官方数字,自己编一遍
三家官网给的数字口径完全不同:有的报”最小内核”,有的报”典型应用”,有的把组件算进去了有的没算。拿它们直接横向比是自欺欺人。我的做法是:同一块板、同一个 arm-none-eabi-gcc 版本、同一优化级别(-Os)、同一个最小应用,自己量。
统一的最小应用:3 个任务 + 1 个队列 + 1 个二值信号量 + 1 个互斥量,任务里只做翻转 GPIO 和收发队列,不引任何组件。
# 统一量测口径:只统计真正落到 Flash 的部分
arm-none-eabi-size -A build/app.elf | grep -E "\.text|\.rodata|\.data"
# 更细的账要看 map,找出到底是谁在吃空间
arm-none-eabi-nm --size-sort -S build/app.elf | tail -30
量完的结论非常反直觉:
| 统计项 | 现象 |
|---|---|
| 三家空内核的 Flash 差异 | 只有几 KB 量级,占总预算 1% 上下 |
| 加上 lwIP + TLS + 文件系统后 | 三家的差异被彻底淹没,都是几十 KB 量级 |
| FreeRTOS 的 RAM 账 | 调度器约 236 字节 + 每个任务约 64 字节控制块 + 各自的栈 |
| 真正的大头 | 不是内核,是协议栈和 TLS;其次是你没注意的 printf 和断言 |
所以那次评估里最值钱的一句话是:别为了省 3KB 内核去换 RTOS。 如果资源真的紧到 3KB 都要计较,你要做的不是换内核,是砍组件、关 printf、把断言从 assert 改成计数上报。我见过一个项目为了省 Flash 把 RTOS 从 A 换到 B,结果因为少了现成的组件,自己糊了两个月,代码量涨了 40KB——这是典型的”省了芝麻,赔了西瓜”。
真正的成本在迁移:三个”同名不同义”的坑
这才是这次评估的核心产出。三家的 API 名字看着都差不多,但有三处语义完全不同,照搬会整体失效,而且大多不报错。
坑一(最致命):优先级方向,两家和一家是反的
| RTOS | 优先级语义 | 空闲任务优先级 |
|---|---|---|
| FreeRTOS | 数值越大,优先级越高(0 最低) | 0(最低) |
| RT-Thread | 数值越小,优先级越高(0 最高) | RT_THREAD_PRIORITY_MAX − 1 |
| TencentOS tiny | 数值越小,优先级越高 | TOS_CFG_TASK_PRIO_MAX − 1 |
TencentOS tiny 这里还有两个细节:TOS_CFG_TASK_PRIO_MAX 默认是 10、且配置值必须 ≥ 8,而空闲任务长期占着 MAX − 1 这一档,所以用户能用的优先级范围实际是 [0, TOS_CFG_TASK_PRIO_MAX − 2]。
这意味着什么?如果你的代码里有一张硬编码的优先级表(很多项目都有,通信任务 5、业务任务 3、LED 任务 1),直接搬过去,业务和通信的相对优先级会整体反转,而且编译零告警、启动正常、空载测试也正常。它只在负载高的时候表现出来——比如下午总线帧多的时候偶发丢帧、按键响应变慢。这类问题查起来极其痛苦,因为你会先去怀疑驱动、再怀疑硬件。
我的建议是:迁移第一步就是写个 5 分钟的证伪实验,别跳。
/* FreeRTOS:数值大 = 优先高 */
xTaskCreate(task_a, "A", 128, NULL, 3, NULL);
xTaskCreate(task_b, "B", 128, NULL, 5, NULL);
/* 现象:B 先跑;B 不主动阻塞时 A 拿不到 CPU */
/* RT-Thread:同样两个数字,结果完全反过来 */
rt_thread_create("A", task_a, NULL, 512, 3, 10);
rt_thread_create("B", task_b, NULL, 512, 5, 10);
/* 现象:A 先跑 —— 数值小者优先 */
/* TencentOS tiny:与 RT-Thread 同向,与 FreeRTOS 反向 */
tos_task_create(&task_a, "A", task_a_entry, NULL, 3, stack_a, STK_A_ELEMS, 0);
tos_task_create(&task_b, "B", task_b_entry, NULL, 5, stack_b, STK_B_ELEMS, 0);
/* 同样是 3 比 5 优先 */
两个任务都写成”打印 + 延时”,看谁先打印,一目了然。这一步花 5 分钟,能省你后面 5 天。
坑二:栈大小的单位,三家有三种意思
这是第二个必炸的地方:
| RTOS | 创建任务时传入的栈参数 | 实际占用字节 |
|---|---|---|
| FreeRTOS | usStackDepth,单位是 StackType_t(word) |
depth × sizeof(StackType_t),M4 上 ×4 |
| RT-Thread | stack_size,单位就是 字节 |
原值 |
| TencentOS tiny | stk_size,单位是 k_stack_t 元素个数 |
个数 × sizeof(k_stack_t),M4 上 ×4 |
所以 128 这个数字:在 FreeRTOS 里是 512 字节,在 RT-Thread 里就是 128 字节,在 TencentOS tiny 里(作为元素个数)又是 512 字节。
RT-Thread 下给一个稍复杂的任务分 128 字节栈,代码稍微深一层调用就栈溢出;而反过来,把 RT-Thread 的 512 当成 FreeRTOS 的 depth 传进去,你会莫名其妙多吃 2KB RAM。统一按字节算,然后用编译期断言把不一致掐死在编译阶段:
/* 栈单位归一:三家统一按"字节"对外,内部各自换算 */
#define OS_STACK_BYTES_FREERTOS(depth) ((depth) * sizeof(StackType_t))
#define OS_STACK_BYTES_RTTHREAD(size) (size)
#define OS_STACK_BYTES_TENCENTOS(cnt) ((cnt) * sizeof(k_stack_t))
/* 编译期兜底:栈小于 256 字节直接编译不过,别等运行时炸 */
#define OS_STATIC_ASSERT(cond, msg) typedef char os_sa_##msg[(cond) ? 1 : -1]
OS_STATIC_ASSERT(APP_STACK_LED_BYTES >= 256, led_stack_too_small);
坑三:中断里的 API,形态全不一样
| RTOS | 中断中通知/投递的写法 |
|---|---|
| FreeRTOS | 必须用 ...FromISR() 版本 + 传 pxHigherPriorityTaskWoken + 结尾 portYIELD_FROM_ISR() |
| RT-Thread | 没有 FromISR 概念,移植层通过 rt_interrupt_enter()/leave() 自动识别中断上下文,API 同名 |
| TencentOS tiny | 中断上下文有独立的 ISR 版本 API(命名与任务版本区分,具体名字以你手上版本的头文件为准) |
RT-Thread 这一条是”看起来很爽但容易松警惕”的设计:因为 API 同名,你在中断里写 rt_sem_release() 不会编译报错,行为也对(会自动记录是否需要切换)。但它同时意味着——你在中断里用了带阻塞的 API 也不会被编译器拦住,比如在 ISR 里调了个会等资源的接口,问题只会在某个高负载时刻冒出来。RT-Thread 的应对是开 RT_USING_ASSERT / RT_DEBUG,让内核自己在运行时抓住这类误用。
TencentOS tiny 这条相对明确:设计上就把任务上下文和中断上下文的接口分开,好处是错觉少,代价是迁移时每一处中断投递都得手动改名字。别靠记忆改,grep 头文件把所有 _isr 后缀的接口列出来做成对照表,一次性替换。
附送一个小坑:超时和延时的单位换算
pdMS_TO_TICKS(5) 在 tick 频率 1000Hz 时是 5 个 tick;但如果哪个项目把 configTICK_RATE_HZ 配成 100,它就变成 0——于是”延时 5ms”退化成”让出一次 CPU”,时序相关的业务逻辑会以一种很隐蔽的方式坏掉。三家都有各自的换算宏(FreeRTOS 的 pdMS_TO_TICKS、RT-Thread 的 rt_tick_from_millisecond()、TencentOS tiny 的 tick/ms 互转接口),共同点是:换算存在取整,短延时在高分频下会被抹成 0。 需要毫秒级精度的业务别用 tick 延时,用硬件定时器。
组件与中间件:这才是决定工作量的东西
内核那点差异两周就能抹平,真正影响人力投入的是组件栈。同样是”我要一个 MQTT + TLS + 掉电存储”,三家的落地路径差别很大:
| 能力 | FreeRTOS | RT-Thread | TencentOS tiny |
|---|---|---|---|
| 文件系统 | 需外挂 FatFS / LittleFS | 内置 DFS 虚拟文件系统,挂载不同后端 | 提供 spiffs 等组件 |
| 网络 | 需外挂 lwIP 或 FreeRTOS+TCP | 内置 SAL 套接字抽象层,可换不同协议栈 | 内置 SAL + AT 框架,模组适配最省事 |
| 命令行调试 | 无,自建或引第三方 CLI | 内置 FinSH,ps/free/list_device 开箱可用 |
提供 shell 组件 + 云端”最后一屏”故障现场 |
| OTA | 自己实现(我上几期写过整套) | 有 OTA 组件 | 内置 OTA 组件 |
| 设备驱动模型 | 无统一模型,各 SDK 各自封装 | 统一 device 框架,驱动与业务解耦 | 内核对模组/外设做适配层 |
| 端云对接 | 走 AWS 生态或自研 | 走社区软件包 | 直连腾讯云,端云 API 一条龙 |
我对组件这一栏的实操标准只有一句话:不看”有没有”,看”能不能被替换”。
组件齐全不等于好事。如果某家的协议栈和内核耦合太深(比如内部直接调内核私有接口),你后期想换一个更省内存的协议栈,就得连内核一起动。所以评估的时候要专门问一句:这套组件的对外接口是标准 POSIX/BSD socket,还是自家私有 API? 有 SAL 这层抽象的方案,替换成本低一个数量级。
现场可维护性:FinSH 这一条,就够决定选型了
这条在我们的打分表里权重给到 15%,实际影响比 15% 还大。
我们这批表计装在配电间,运维人员拿不到网络,只能拉开柜门插串口。有命令行和没命令行,现场处理方式完全不同:
/* FinSH 自定义命令:现场一条命令把关键运行状态全捞出来 */
#include <rtthread.h>
#include <finsh.h>
static void meter_info(int argc, char **argv)
{
rt_kprintf("uptime : %u s\n", rt_tick_get() / RT_TICK_PER_SECOND);
rt_kprintf("heap : total %u, used %u, max %u\n",
rt_memory_info_total(), rt_memory_info_used(),
rt_memory_info_max_used());
rt_kprintf("dl645 : rx %u, tx %u, crc_err %u\n",
meter_stat.rx, meter_stat.tx, meter_stat.crc_err);
rt_kprintf("last err: %s @ %u\n", meter_stat.last_err, meter_stat.err_tick);
}
MSH_CMD_EXPORT(meter_info, dump meter runtime state);
现场一句 meter_info,不用重启、不用接调试器,五分钟定位是通信问题还是内存问题。FreeRTOS 要达到同样效果,得引 CLI 组件、自己注册命令表、自己写内存统计,工作量不小——这是隐性成本,而且是每次现场都要付的成本。
TencentOS tiny 在这里走的是另一条路:官方提供”最后一屏”调试能力,设备异常时自动保存故障现场、重启后上传云端。对于无人值守、拆机成本极高的场景(水表、燃气表、埋地设备),这个能力比串口命令行更值钱;但对于我们这种”能插串口但是要派人爬梯子”的场景,还是 FinSH 更直接。选型就是这样:同一个功能,场景不同,价值差十倍。
落地:我们怎么把 FreeRTOS 代码迁到 RT-Thread
评估完是 9 月中旬,签完这份报告我就开始动手了。核心思路是——别让业务代码认识任何一个 RTOS。
先加一层薄封装,把所有差异(尤其是优先级方向和栈单位)在这一层归一;业务代码只认 os_task_create / os_delay_ms 这种名字。
/* app_os.h —— 把 RTOS 关在门外的一层薄封装 */
#ifndef APP_OS_H
#define APP_OS_H
#include "os_config.h" /* 决定本次构建用哪家 RTOS */
#if defined(USE_FREERTOS)
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
typedef TaskHandle_t os_task_t;
typedef QueueHandle_t os_queue_t;
typedef SemaphoreHandle_t os_sem_t;
/* 以"优先级档位"对外,内部映射到各家语义,业务代码永不直接写数字 */
typedef enum { OS_PRIO_LOW = 0, OS_PRIO_MID, OS_PRIO_HIGH, OS_PRIO_MAX } os_prio_t;
#define OS_DP(prio) ((BaseType_t)((prio) + 1)) /* FreeRTOS:数值大者优先 */
#define os_task_create(h, name, entry, arg, prio, stack_bytes) \
do { \
xTaskCreate((entry), (name), (stack_bytes) / sizeof(StackType_t), \
(arg), OS_DP(prio), (h)); \
} while (0)
#define os_delay_ms(ms) vTaskDelay(pdMS_TO_TICKS(ms))
#elif defined(USE_RTTHREAD)
#include <rtthread.h>
typedef rt_thread_t os_task_t;
typedef rt_mq_t os_queue_t;
typedef rt_sem_t os_sem_t;
typedef enum { OS_PRIO_LOW = 0, OS_PRIO_MID, OS_PRIO_HIGH, OS_PRIO_MAX } os_prio_t;
/* RT-Thread:数值小者优先,且 0 号常被系统占用,这里留出余量 */
#define OS_DP(prio) ((rt_uint8_t)(RT_THREAD_PRIORITY_MAX - 3 - (prio)))
#define os_task_create(h, name, entry, arg, prio, stack_bytes) \
do { \
*(h) = rt_thread_create((name), (entry), (arg), (stack_bytes), \
OS_DP(prio), 10); \
if (*(h) != RT_NULL) rt_thread_startup(*(h)); \
} while (0)
#define os_delay_ms(ms) rt_thread_mdelay(ms)
#elif defined(USE_TENCENTOS)
#include "tos_k.h"
typedef k_task_t *os_task_t;
typedef k_mutex_t *os_sem_t;
typedef enum { OS_PRIO_LOW = 0, OS_PRIO_MID, OS_PRIO_HIGH, OS_PRIO_MAX } os_prio_t;
/* TencentOS tiny:同样是数值小者优先,且 MAX-1 被 idle 独占 */
#define OS_DP(prio) ((k_prio_t)(TOS_CFG_TASK_PRIO_MAX - 2 - (prio)))
#define os_task_create(h, name, entry, arg, prio, stack_bytes, kcb, stk_buf) \
tos_task_create((h), (name), (entry), (arg), OS_DP(prio), (stk_buf), \
(stack_bytes) / sizeof(k_stack_t), 0)
#define os_delay_ms(ms) tos_task_delay(tos_ms2tick(ms))
#endif
#endif /* APP_OS_H */
这层封装看着土,但它的价值在”评估报告”这一环就兑现了:万一明年国产化要求变了,或者又冒出一家更合适的 RTOS,我们改的是这 60 行,不是 3 万行业务代码。
除封装之外,迁移里真正费时间的其实是这几件不起眼的事:
- 启动流程不同:FreeRTOS 是”建完任务调
vTaskStartScheduler()“;RT-Thread 是rt_thread_startup()逐个启动,而且有INIT_BOARD_EXPORT/INIT_DEVICE_EXPORT这种段初始化机制——驱动和组件按等级自动初始化,不需要你在 main 里排队调用。用熟了很省事,但第一次看到”我没调用它怎么就跑了”会懵,注意初始化等级顺序; - 堆管理要显式打开:RT-Thread 的动态对象依赖
RT_USING_HEAP,关了就只能用rt_thread_init()这类静态接口。我们的规矩是业务层一律静态创建,和我们 MCU 项目”零 malloc”的纪律一致(这点我前几期专门写过); - 空闲钩子和 tickless 要重新接:低功耗那套(前几期写过的 Tickless)在新平台上是另一套配置项,别以为迁完内核就完事了;
- 中断优先级分组:RT-Thread 的临界区保护依赖中断优先级设置,
rt_hw_interrupt_*这条链路必须和你的 NVIC 分组对上,否则临界区挡不住高优先级中断。
迁移完的体感:内核层大概两周,组件与驱动层一个半月。 这也是为什么我在打分表里给”团队熟悉度”留了 10% 权重——它不决定技术优劣,但决定你能不能按时交货。
决策矩阵:我们最后是怎么打分的
| 维度 | 权重 | FreeRTOS | RT-Thread | TencentOS tiny |
|---|---|---|---|---|
| 资源占用 / 可裁剪性 | 20% | 5 | 4 | 5 |
| 组件与中间件完备度 | 20% | 3 | 5 | 4 |
| 现场可维护性 / 调试手段 | 15% | 3 | 5 | 4 |
| 本土支持与中文文档 | 15% | 3 | 5 | 4 |
| 许可协议与合规友好 | 10% | 5 | 4 | 5 |
| 团队熟悉度与迁移成本 | 10% | 5 | 3 | 2 |
| 长期维护与生态活力 | 10% | 5 | 4 | 3 |
| 加权总分 | 100% | 4.00 | 4.40 | 4.00 |
(5 分制,分数来自我们三个人的独立打分取均值,不是客观真理,换一批约束,结果会变。)
结论:主产品线选 RT-Thread。 决定性理由是组件完备度 + FinSH 现场调试 + 本土支持,这三项加起来值 0.4 分。
同时我们没有把鸡蛋放一个篮子里:
- 低端老产品线继续用 FreeRTOS:那批产品是 8 位/小容量 MCU,资源紧、功能简单、稳定出货多年,迁移的成本远大于收益。能不动就不动,这也是工程判断;
- 另一条 NB-IoT 传感终端线选 TencentOS tiny:它的资源起步最低,而且我们的模组本来就接腾讯云,端云对接省下的开发量最实在。
什么时候”不该选”
这部分比”该选谁”更重要:
- 资源真的在 20KB Flash / 4KB RAM 以下:别碰组件化方案,FreeRTOS 的极简内核或 RT-Thread Nano 才合适,Full 版加任何组件都会爆;
- 产品要做功能安全认证:开源内核不能直接顶上,先谈认证版本和商业支持,再谈技术;
- 团队没有一个人有维护能力:选了个组件丰富但没人懂的 RTOS,出事时你只能等社区回帖。选型的下限是你的团队,不是你的架构图。
三条避免翻车的方法论
- 别把业务代码绑死在 RTOS 上:那 60 行封装就是保险,成本低到你没有理由不做;
- 动态与静态创建都要能跑:MCU 项目里静态创建是纪律(避免堆碎片、避免 %f 那类隐性依赖),但评估阶段必须验一遍动态路径,否则你没法判断资源上限;
- 把 RTOS 当成可替换件,而不是地基:TR 换 EFM、换 GD32、甚至换 RTOS,在我们的代码结构里都只是改配置和适配层。能替换的东西才是资产,绑死的东西是负债。
RTOS 选型自查清单
- ☐ 约束清单已书面化(资源预算 / 许可 / 国产化 / 认证 / 现场维护 / 团队能力 / 生命周期)
- ☐ 三家内核资源在同一块板、同一编译器、同一优化级别下实测过,而非引用官网数字
- ☐ 已核算组件(协议栈 / TLS / 文件系统)占用,确认内核差异不构成决策依据
- ☐ 许可协议逐条核对,确认基线版本(尤其 RT-Thread 3.1.0 之前的 GPL 区间)
- ☐ 优先级语义方向已用”双任务打印实验”当场证伪,不存在整体反转
- ☐ 栈大小单位已在封装层归一,并有编译期断言兜底
- ☐ 中断上下文 API 已形成对照表(FromISR / ISR 后缀 / 自动识别),逐处替换
- ☐ 超时与延时统一走换算宏,短延时未因 tick 分频被抹成 0
- ☐ 现场可维护性已验证:命令行/日志/故障现场保存,至少有一种能在量产设备上打开
- ☐ 动态创建与静态创建两条路径都跑过,堆管理策略已明确(能不用堆就不用)
- ☐ 启动流程与初始化等级顺序已理清,驱动初始化顺序有据可查
- ☐ 业务代码与 RTOS 之间已有薄封装层,替换 RTOS 的改动量可评估
—
下一期预告:既然选型落到了 RT-Thread,紧接着的问题就是驱动怎么迁——《从裸机寄存器到 rt_device:把一套 SPI 驱动在 RT-Thread 上重写三遍的血泪记》,还是第一人称踩坑视角,把设备框架、驱动注册与 FinSH 联调那套完整走一遍。