从裸机到RTOS:手把手教你为正点原子Nano STM32F103移植RT-Thread Nano内核(MDK5环境)
从裸机到RTOS:手把手教你为正点原子Nano STM32F103移植RT-Thread Nano内核(MDK5环境)
当你已经能够熟练地在STM32上编写裸机程序,却发现在处理多任务时越来越力不从心——比如需要同时控制LED闪烁、读取传感器数据并响应按键事件。这时候,RTOS(实时操作系统)就像给你的开发板装上了"多任务大脑",而RT-Thread Nano作为轻量级内核,正是从裸机过渡到RTOS的绝佳选择。
正点原子Nano STM32F103开发板凭借其小巧体积和丰富外设,成为许多开发者学习RTOS的首选平台。本文将带你完成三个关键跨越:从单线程到多线程的思维转换、从手动外设管理到系统自动调度、从简单轮询到事件驱动架构。不同于直接运行现成示例,我们会从零开始构建工程,让你真正掌握RT-Thread Nano的移植精髓。
1. 环境准备与工程创建
在开始移植前,需要准备好以下环境组件:
- MDK5.24+:建议使用最新版本以避免兼容性问题
- STM32CubeMX:用于生成基础时钟配置(非必须但推荐)
- RT-Thread Nano 3.1.5+:从官网获取最新稳定版源码
- ST-Link驱动:确保能正常识别开发板
创建基础工程的步骤如下:
- 在MDK中新建STM32F103RB工程
- 选择CMSIS核心和Device Startup文件
- 添加基础外设驱动(GPIO、USART等)
- 配置时钟树使系统运行在72MHz
关键点在于system_stm32f1xx.c文件的配置。对比裸机工程,RTOS需要额外关注:
#define SYSTICK_CLK_HZ 1000 // RT-Thread的时钟节拍通常设为1kHz #define TICK_RATE_HZ 1000提示:建议先验证裸机工程能正常点亮LED后再进行RTOS移植,这样可以排除硬件基础问题。
2. RT-Thread Nano内核移植
2.1 源码裁剪与添加
从RT-Thread官方仓库获取的Nano包通常包含以下核心文件:
rtthread-nano/ ├── include // 内核头文件 ├── libcpu // CPU相关移植层 └── src // 内核源码在MDK工程中添加这些文件时,需要特别注意context_rvds.s汇编文件——它实现了任务切换的关键上下文保存与恢复。针对STM32F103,我们需要修改以下几点:
- 在
rtconfig.h中启用基础组件:
#define RT_USING_TIMER_SOFT 1 // 启用软件定时器 #define RT_THREAD_PRIORITY_MAX 8 // 根据需求设置优先级数量 #define RT_TICK_PER_SECOND 1000- 调整堆栈大小(根据开发板20KB RAM的实际情况):
#define RT_HEAP_SIZE (8*1024) // 建议保留至少8KB堆空间2.2 启动文件改造
STM32的标准启动文件startup_stm32f103xb.s需要做两处关键修改:
- 在Reset_Handler中移除原有循环,替换为RT-Thread初始化:
IMPORT __main IMPORT rtthread_startup ... LDR R0, =rtthread_startup BX R0- 重定向PendSV_Handler和SysTick_Handler:
PendSV_Handler PROC EXPORT PendSV_Handler IMPORT rt_hw_context_switch B rt_hw_context_switch ENDP SysTick_Handler PROC EXPORT SysTick_Handler IMPORT rt_tick_increase PUSH {LR} BL rt_tick_increase POP {PC} ENDP3. 多任务实践:双LED不同频闪烁
3.1 线程创建与管理
下面创建两个线程分别控制开发板上的PC0和PC1引脚LED:
// 定义线程控制块和栈空间 static struct rt_thread led1_thread; static rt_uint8_t led1_stack[256]; static struct rt_thread led2_thread; static rt_uint8_t led2_stack[256]; // LED1线程入口函数 void led1_entry(void *parameter) { rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT); while(1) { rt_pin_write(LED1_PIN, PIN_HIGH); rt_thread_mdelay(500); // 500ms间隔 rt_pin_write(LED1_PIN, PIN_LOW); rt_thread_mdelay(500); } } // LED2线程入口函数 void led2_entry(void *parameter) { rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT); while(1) { rt_pin_write(LED2_PIN, PIN_HIGH); rt_thread_mdelay(200); // 200ms间隔 rt_pin_write(LED2_PIN, PIN_LOW); rt_thread_mdelay(200); } } // 线程初始化函数 int led_thread_init(void) { rt_thread_init(&led1_thread, "led1", led1_entry, RT_NULL, &led1_stack[0], sizeof(led1_stack), 5, 10); rt_thread_startup(&led1_thread); rt_thread_init(&led2_thread, "led2", led2_entry, RT_NULL, &led2_stack[0], sizeof(led2_stack), 5, 10); rt_thread_startup(&led2_thread); return 0; } INIT_APP_EXPORT(led_thread_init); // 自动初始化3.2 调度器启动与观察
在main.c中简化为仅启动调度器:
int main(void) { rt_kprintf("RT-Thread Nano on ATK-NANO\n"); rt_thread_mdelay(1000); // 等待初始化完成 while (1) { rt_thread_mdelay(1000); } }使用串口调试工具(波特率115200)可以看到内核启动日志:
\ | / - RT - Thread Operating System / | \ 3.1.5 build Jun 12 2023 msh >4. 调试技巧与性能优化
4.1 常见问题排查
当移植出现问题时,可按以下步骤排查:
- HardFault处理:
void HardFault_Handler(void) { rt_kprintf("HardFault at 0x%08x\n", __get_PC()); while(1); }- 堆栈溢出检测:
#define RT_USING_OVERFLOW_CHECK 1- 使用
list_thread命令查看线程状态:
msh >list_thread thread pri status sp stack size max used left tick ------ --- ------ --- ---------- ------- --------- led2 5 running 0x50 256 56% 10 led1 5 ready 0x50 256 52% 15 tshell 20 ready 0x60 512 38% 204.2 性能优化建议
针对STM32F103的资源配置建议:
| 组件 | 推荐配置 | 说明 |
|---|---|---|
| 空闲线程栈 | 128字节 | 可适当减小 |
| 定时器线程栈 | 256字节 | 若使用软件定时器需保留 |
| 系统时钟频率 | 1kHz | 响应与功耗的平衡点 |
| 优先级数 | 8级 | 满足大多数应用场景 |
当需要进一步优化时,可以:
- 关闭不需要的组件(如finish、shell)
- 使用
rt_memheap_realloc替代标准malloc - 启用
RT_USING_HOOK来监控任务切换
