告别裸机:给STM32F103移植RT-Thread NANO V4.1.1,实现多线程串口打印

告别裸机:给STM32F103移植RT-Thread NANO V4.1.1,实现多线程串口打印 从裸机到RTOSSTM32F103移植RT-Thread NANO实战指南在嵌入式开发领域从裸机编程过渡到实时操作系统(RTOS)是一个重要的里程碑。对于使用STM32F103这类Cortex-M3内核MCU的开发者来说RT-Thread NANO提供了一个完美的切入点——它保留了RTOS的核心功能同时保持了极简的设计和极低的内存占用。本文将手把手带你完成从裸机到RT-Thread NANO的完整移植过程并通过多线程串口打印的实例验证移植效果。1. 环境准备与工程配置1.1 硬件与工具链选择本次移植基于STM32F103C8T6Blue Pill开发板和Keil MDK开发环境。你需要准备以下工具Keil MDK建议使用5.30或更高版本STM32CubeMX用于生成基础工程可选串口调试工具如Putty或Tera TermRT-Thread NANO源码从GitHub获取最新版本提示虽然本文以Keil为例但相同原理也适用于STM32CubeIDE只需调整相应的工程配置步骤。1.2 创建基础工程首先创建一个裸机工程作为起点# 使用STM32CubeMX生成基础工程 $ STM32CubeMX --mcu STM32F103C8 --toolchain MDK-ARM或者直接在Keil中新建工程选择STM32F103C8设备。确保工程包含以下基本外设初始化系统时钟配置通常72MHzGPIO初始化至少一个USART用于调试输出SysTick定时器配置2. RT-Thread NANO源码集成2.1 获取与添加源码从GitHub获取RT-Thread NANO V4.1.1源码git clone https://github.com/RT-Thread/rtthread-nano.git将以下文件添加到你的工程中文件/目录说明rtthread-nano/src内核源码目录rtthread-nano/bsp板级支持包rtthread-nano/include内核头文件目录在Keil中添加这些文件到工程分组右键点击Project → Add Group创建RT-Thread分组添加src目录下所有.c文件添加bsp/board.c文件添加include路径到工程设置2.2 关键配置调整修改rtconfig.h文件以适应STM32F103// rtconfig.h 关键配置示例 #define RT_THREAD_PRIORITY_MAX 8 #define RT_TICK_PER_SECOND 1000 #define RT_USING_HEAP #define RT_USING_SMALL_MEM_AS_HEAP #define RT_USING_CONSOLE #define RT_CONSOLE_DEVICE_NAME uart1注意V4.1.1版本必须使用RT_USING_SMALL_MEM_AS_HEAP而非RT_USING_SMALL_MEM否则会导致线程创建失败。3. 系统移植与硬件适配3.1 中断向量表处理RT-Thread NANO接管了部分系统中断需要修改startup_stm32f103xe.s文件; 注释掉原有的HardFault_Handler和SVC_Handler ; 添加RT-Thread需要的中断服务程序 EXTERN HardFault_Handler EXTERN SVC_Handler EXTERN PendSV_Handler EXTERN SysTick_Handler3.2 系统时钟配置在board.c中实现系统时钟初始化void rt_hw_board_init() { // 初始化系统时钟72MHz SystemInit(); // 配置SysTick为1ms中断 SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND); // 初始化控制台串口 rt_hw_usart_init(); // 打印RT-Thread版本信息 rt_console_set_device(RT_CONSOLE_DEVICE_NAME); rt_kprintf(\nRT-Thread Nano v4.1.1 on STM32F103\n); }3.3 串口驱动实现实现控制台输出所需的串口驱动// 实现rt_hw_console_output函数 void rt_hw_console_output(const char *str) { while(*str) { while(!(USART1-SR USART_SR_TXE)); USART1-DR (*str 0xFF); } }4. 多线程实现与测试4.1 创建测试线程在main.c中创建两个交替打印的线程#include rtthread.h // 线程1入口函数 static void thread1_entry(void *parameter) { while(1) { rt_kprintf(Thread1 is running!\n); rt_thread_mdelay(500); // 延时500ms } } // 线程2入口函数 static void thread2_entry(void *parameter) { while(1) { rt_kprintf(Thread2 is running!\n); rt_thread_mdelay(800); // 延时800ms } } int main(void) { // 创建线程1 rt_thread_t tid1 rt_thread_create(thread1, thread1_entry, RT_NULL, 256, 3, 20); if(tid1 ! RT_NULL) { rt_thread_startup(tid1); } // 创建线程2 rt_thread_t tid2 rt_thread_create(thread2, thread2_entry, RT_NULL, 256, 2, 20); if(tid2 ! RT_NULL) { rt_thread_startup(tid2); } // main线程也参与打印 while(1) { rt_kprintf(Main thread is alive!\n); rt_thread_mdelay(1000); } return 0; }4.2 线程优先级与调度观察通过上述代码我们创建了三个不同优先级的线程线程名称优先级延时周期预期行为thread22800ms优先级最高最先执行thread13500ms次高优先级main线程-1000ms默认优先级最低最后执行编译并下载程序后通过串口观察输出应该能看到三个线程按照预期交替打印信息验证了RT-Thread NANO的多任务调度功能正常工作。5. 常见问题与优化建议5.1 移植过程中的典型问题线程创建失败检查RT_USING_SMALL_MEM_AS_HEAP配置确认堆空间足够在startup_stm32f103xe.s中调整Heap_Size系统启动后无输出验证串口初始化是否正确检查rt_hw_console_output实现确认rt_kprintf是否重定向到正确设备系统卡在HardFault检查中断向量表处理确认栈空间分配足够5.2 性能优化技巧调整线程栈大小根据实际需求精确设置避免浪费内存合理设置优先级关键任务给予更高优先级使用软件定时器替代简单的延时循环启用钩子函数监控系统运行状态// 空闲钩子函数示例 static void idle_hook() { rt_kprintf(System idle...\n); } // 在main函数中注册 int main(void) { rt_thread_idle_sethook(idle_hook); // ...其他初始化代码 }6. 进阶应用线程间通信移植成功后可以进一步探索RT-Thread NANO的IPC功能信号量用于资源管理和线程同步邮箱简单的消息传递机制互斥锁保护共享资源// 信号量使用示例 static rt_sem_t test_sem; // 生产者线程 static void producer(void *param) { while(1) { rt_sem_release(test_sem); rt_thread_mdelay(1000); } } // 消费者线程 static void consumer(void *param) { while(1) { if(rt_sem_take(test_sem, RT_WAITING_FOREVER) RT_EOK) { rt_kprintf(Got semaphore!\n); } } } // 初始化信号量 test_sem rt_sem_create(test_sem, 0, RT_IPC_FLAG_FIFO);在实际项目中我发现合理使用信号量可以显著简化多线程编程模型特别是在处理传感器数据采集和处理的场景中。通过信号量同步数据生产者和消费者线程既能保证实时性又能避免资源竞争。