
"你这MCU上还能跑终端?"
之前同事看我对着一个串口工具敲 ps,屏幕上蹦出来线程列表,表情跟见了鬼似的。说实话我第一次用RT-Thread的FinSH也是这反应——一个跑在Cortex-M4上的RTOS,居然自带shell,还能敲命令查内存、看线程栈、操作文件系统?这玩意儿超纲了吧。
从一次死锁排查说起
有回项目里碰到一个诡异的死锁。两个线程同时抢一个互斥量,加了超时重试逻辑但还是随机卡死。按照之前的习惯,只能printf满天飞,或者挂JLINK单步调。但这次是产品原型,JLink的线根本够不着。
FinSH帮了大忙。
直接在串口敲:
msh /> list_thread
thread pri status sp stack size max used left tick error
———– — ——- ———- ———- —— ———- —
sensor 10 suspend 0x00000080 0x00000400 67% 0x00000001 000
wifi 9 suspend 0x00000090 0x00000400 72% 0x0000000a 000
led_task 20 running 0x00000040 0x00000200 32% 0x00000014 000
tx_thread 10 suspend 0x0000011c 0x00000400 89% 0x00000000 000
注意那个 tx_thread,栈用到89%了,而且error列是000但状态一直是suspend。翻了翻代码发现这玩意儿在等一个信号量,但发信号量的线程优先级比它低,而且被它自己占着CPU不放——典型的优先级反转。
要是在裸机或者FreeRTOS上查这个,得花多少功夫。FinSH里敲几行命令就定位到了。
自动初始化,懒人福音
RT-Thread另一个让我觉得"这RTOS设计者有脑子"的地方是自动初始化机制。
以前写FreeRTOS项目,main函数长这样:
int main(void)
{
bsp_init();
gpio_init();
i2c_init();
sensor_init();
wifi_init();
display_init();
/* 这个列表可以写一屏 */
/* 调个顺序都能出bug你敢信 */
create_sensor_task();
create_wifi_task();
create_display_task();
/* 继续写一屏 */
}
RT-Thread怎么搞的?各模块自己注册:
static int bsp_late_init(void)
{
/* 板级初始化 */
return 0;
}
INIT_BOARD_EXPORT(bsp_late_init);
static int sensor_hw_init(void)
{
wdt_para_init(5000); /* 看门狗5秒 */
return 0;
}
/* 优先级120,驱动级初始化 */
INIT_DEVICE_EXPORT(sensor_hw_init);
static int app_component_init(void)
{
/* Wi-Fi连网、MQTT建连 */
return 0;
}
INIT_COMPONENT_EXPORT(app_component_init);
然后main函数就一句话:
int main(void)
{
/* 啥也不用干,初始化已经按优先级跑完了 */
return 0;
}
说实话这设计是真的舒服。每个驱动各管各的初始化,按 BOARD → DEVICE → COMPONENT → APP 的优先级自动排列。想加一个新外设?写个函数加个宏,不用去main里插队,也不会因为插错位置导致某个依赖还没初始化就崩了。
包管理,抄作业的快乐
RT-Thread还有个env + pkgs的包管理。做项目的时候要加个AT指令解析?pkgs –update拉下来。要加个MQTT客户端?menuconfig勾上就行。真就跟npm装包一样。
我记得那时候加了cJSON解析库,cmake都没配,rtconfig.h里定义个宏就能用。
RT-Thread online packages
→ IoT – internet of things
→ [*] cJSON: Ultralightweight JSON parser
点几个选项,代码就进工程了。这体验对于一个习惯了嵌入式到处复制粘贴驱动的人来说,冲击力不小。
当然RT-Thread也不是没有缺点。文档质量参差不齐,有的组件写得挺好但README就一句话。遇到晦涩的问题还是得去翻源码。有些驱动实现比较糙——比如我踩过的坑:SPI DMA传输的buffer没做cache对齐处理,在带Cache的MCU上跑直接数据错乱。
不过总的来说,能用50块钱的STM32F103跑一个带shell、文件系统、网络协议栈的操作系统,这性价比你拿Linux+MPU根本打不了。FinSH这个功能尤其适合调试阶段,有些问题你用printf要编译下载跑三遍才能定位,在FinSH里敲三条命令就解决了。
对了,上面那个list_thread输出的max used列,后来我写了个shell脚本每天自动拉一次,监控各个线程的栈使用变化趋势。比出了bug再去查省心多了。





