1. RT-Thread Nano是什么?为什么选择它?
第一次接触RT-Thread Nano时,我也被它的小巧惊艳到了。这个仅有3KB内存占用的实时操作系统内核,却能完整支持多任务调度、信号量、邮箱等特性。对于资源受限的STM32芯片(比如常见的STM32F103C8T6只有20KB RAM)来说,简直是量身定制的解决方案。
记得去年做一个智能家居网关项目,原本用FreeRTOS发现RAM吃紧,换成Nano后不仅稳定运行,还能省出5KB内存给应用层。它的抢占式调度响应速度实测能达到us级,我在STM32F407上测试线程切换仅需1.7us(72MHz主频下)。最让我惊喜的是它的面向对象设计,设备驱动框架比裸机开发规范得多。
官网提供的软件框图清晰展示了它的可裁剪特性:
- 最小内核仅包含线程调度+时钟管理
- 可选组件包括FinSH命令行、设备框架等
- 支持从Cortex-M0到M7全系列ARM内核
2. 移植前的准备工作
2.1 硬件环境搭建
我习惯用STM32CubeMX快速生成基础工程。以STM32F103C8T6为例:
- 配置时钟树(通常选择外部8MHz晶振→72MHz主频)
- 开启USART1用于调试输出
- 使能一个GPIO控制LED(PC13是开发板常用LED引脚)
// 生成的HAL库初始化代码会自动包含: SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init();2.2 获取RT-Thread Nano源码
推荐直接从官网下载最新稳定版(当前是v3.1.5):
wget https://www.rt-thread.org/download/nano/rt-thread-nano-3.1.5.zip解压后会看到这些关键目录:
bsp/板级支持包(含STM32示例)include/内核头文件libcpu/处理器架构相关代码src/核心源码
3. 手把手移植过程
3.1 添加内核到工程
在Keil MDK中操作最方便:
- 点击工具栏"Pack Installer"
- 搜索安装"RealThread::RT-Thread"
- 在Manage Run-Time Environment中勾选RTOS→Kernel
这时工程会自动添加:
- 内核源码(thread.c, timer.c等)
- 启动文件(startup_stm32f10x_md.s)
- 配置文件模板(rtconfig.h)
3.2 关键文件配置
rtconfig.h需要重点关注这些参数:
#define RT_TICK_PER_SECOND 1000 // 系统心跳频率(1ms) #define RT_USING_HEAP // 启用动态内存 #define RT_MAIN_THREAD_STACK_SIZE 512 // 主线程栈大小board.c要自己实现三个关键函数:
void rt_hw_board_init() { HAL_Init(); // 初始化HAL库 SystemClock_Config(); // 系统时钟 // 配置OS Tick SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND); // 初始化动态内存堆 rt_system_heap_init((void*)0x20000000, (void*)0x20005000); } // SysTick中断服务函数 void SysTick_Handler(void) { rt_tick_increase(); }3.3 解决常见编译错误
第一次编译大概率会遇到这些问题:
重复定义HardFault_Handler解决方法:删除原有stm32f1xx_it.c中的异常处理函数
链接错误:undefined symbol SystemCoreClock解决方法:在rtconfig.h添加:
#include "stm32f1xx_hal.h"堆空间不足修改rtconfig.h中的:
#define RT_HEAP_SIZE (4*1024) // 根据芯片RAM调整
4. 第一个多线程Demo
让我们创建两个线程:一个控制LED闪烁,另一个通过串口打印消息。
#include <rtthread.h> #include "usart.h" // LED线程 static void led_thread_entry(void *param) { while(1) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); rt_thread_mdelay(500); // 让出CPU } } // 串口线程 static void uart_thread_entry(void *param) { rt_kprintf("RT-Thread Nano running!\n"); while(1) { rt_kprintf("Heartbeat\n"); rt_thread_mdelay(1000); } } int main(void) { // 创建LED线程(静态方式) rt_thread_t led_thread = rt_thread_create( "led", led_thread_entry, RT_NULL, 256, 3, 20 ); rt_thread_startup(led_thread); // 创建串口线程(动态方式) rt_thread_init( &uart_thread, "uart", uart_thread_entry, RT_NULL, &uart_stack[0], sizeof(uart_stack), 4, 20 ); return 0; }烧录后你会看到:
- LED以1Hz频率闪烁
- 串口每秒输出"Heartbeat"
- 通过
list_thread命令可以查看线程状态
5. 进阶配置技巧
5.1 内存优化实战
对于只有20KB RAM的STM32F103:
- 将主线程栈改为256字节
- 禁用不需要的组件(如软件定时器)
- 使用静态内存池替代动态分配:
// 在rtconfig.h中关闭HEAP // #define RT_USING_HEAP // 改用内存池 static rt_uint8_t led_stack[256]; static rt_thread_t led_thread; rt_thread_init(&led_thread, ...);5.2 添加FinSH命令行
虽然Nano默认不带Shell,但可以手动添加:
- 复制components/finsh目录到工程
- 在rtconfig.h中开启:
#define RT_USING_FINSH #define FINSH_THREAD_STACK_SIZE 512 - 重定向串口输入输出
实测会多占用约3KB ROM和1KB RAM,但调试方便很多。
6. 性能调优经验
在智能灯控项目中,我总结出这些优化点:
Tick频率选择:
- 工业控制建议1000Hz(1ms响应)
- 消费电子可降到100Hz(省电)
优先级配置:
#define RT_THREAD_PRIORITY_MAX 8 // 减少优先级数量实测从32级降到8级可节省0.5KB内存
中断响应优化: 在stm32f10x_it.c中添加:
void PendSV_Handler(void) { rt_interrupt_enter(); /* 中断处理 */ rt_interrupt_leave(); }
移植完成后,建议用逻辑分析仪测量关键指标:
- 线程切换时间
- 中断延迟
- 内存使用峰值
7. 真实项目踩坑记录
去年给客户做电机控制器时遇到一个典型问题:系统运行几天后会死机。最后发现是线程栈溢出导致的,解决方法:
在rtconfig.h中开启栈检查:
#define RT_USING_OVERFLOW_CHECK使用
list_thread命令定期监控:thread pri status stack max used ---- --- ------ ----- -------- led 3 running 256/256 208关键线程增加看门狗喂狗机制
另一个常见问题是动态内存碎片化,我的应对策略:
- 在rtconfig.h中设置:
#define RT_USING_MEMHEAP_AS_HEAP - 定期调用
list_mem查看内存状态 - 关键任务使用静态内存分配
8. 工程管理建议
对于长期维护的项目,推荐这样组织代码结构:
project/ ├── drivers/ # 硬件驱动 ├── rt-thread/ # Nano源码 ├── applications/ # 应用代码 ├── utilities/ # 通用工具 └── README.md # 编译说明在Keil中设置包含路径时要注意:
- 添加rt-thread/include为首要搜索路径
- 使用相对路径而非绝对路径
- 不同编译配置(Debug/Release)共享同一份源码
我习惯在board.c中添加这些调试辅助函数:
// 打印内存信息 void mem_info(void) { rt_size_t total, used; rt_memory_info(&total, &used); rt_kprintf("Memory: %d/%d KB\n", used/1024, total/1024); }移植完成后,建议立即进行这些测试:
- 连续创建/删除线程100次
- 在中断中触发信号量
- 长时间运行内存泄漏检测