从零开始,在Keil环境下为STM32F103构建RT-Thread Nano最小系统

发布时间:2026/6/30 10:51:17
从零开始,在Keil环境下为STM32F103构建RT-Thread Nano最小系统 1. 环境准备与基础概念在开始构建RT-Thread Nano最小系统之前我们需要先准备好开发环境。Keil MDK是STM32开发中最常用的IDE之一它提供了完善的编译、调试工具链。我建议使用Keil MDK 5.25以上版本这个版本对Cortex-M3内核的支持比较稳定。安装Keil时记得勾选STM32F1系列的Device Family Pack这是很多新手容易忽略的一步。RT-Thread Nano是RT-Thread的精简版本它保留了任务调度、内存管理、线程间通信等核心功能去掉了文件系统、网络协议栈等组件。对于STM32F103这样的资源受限芯片特别合适。实测下来Nano版本的内核ROM占用可以控制在3KB以内RAM占用约1KB这对于只有64KB Flash和20KB RAM的STM32F103C8T6来说非常友好。硬件方面你需要一块STM32F103最小系统板比如常见的蓝色药丸开发板。这类板子通常自带了一个8MHz的外部晶振和32.768kHz的RTC晶振。如果没有外部晶振也没关系可以使用内部RC振荡器但在做串口通信时可能会有轻微误差。2. 创建裸机工程框架首先在Keil中新建一个Project选择Device为STM32F103C8。这里有个坑要注意Keil的器件列表中有多个STM32F103系列比如STM32F103C8和STM32F103CB它们的Flash大小不同选错了会导致后续下载失败。我建议直接选择STM32F103C8这是最通用的型号。创建工程时建议采用这样的目录结构Project/ ├── CMSIS/ # 存放核心启动文件 ├── Drivers/ # 标准外设库 ├── User/ # 用户代码 └── Output/ # 编译输出在User文件夹中我们需要手动添加几个关键文件main.c程序入口stm32f10x_conf.h外设配置头文件stm32f10x_it.c中断服务程序这里有个实用技巧直接从ST官网下载STM32F10x标准外设库把Libraries/CMSIS/Device/ST/STM32F10x/Source/Templates/arm目录下的startup_stm32f10x_md.s文件复制到你的工程中。这个启动文件针对中等容量STM32F103芯片做了优化比Keil自带的更可靠。3. 集成RT-Thread Nano内核现在开始集成RT-Thread Nano内核。与直接使用Pack安装不同我们采用源码集成的方式这样可以更深入理解内核工作原理。首先从RT-Thread官网下载Nano版本源码我推荐使用3.1.5版本这个版本在STM32F103上稳定性最好。关键步骤分为以下几个部分3.1 源码组织结构将RT-Thread Nano源码按功能拆分到工程中内核核心(src目录)包含线程调度、定时器、IPC等核心功能CPU移植(libcpu/arm/cortex-m3)包含上下文切换、中断处理等架构相关代码板级支持(bsp目录)包含时钟配置、串口初始化等硬件相关代码我习惯在工程中新建一个RT-Thread分组下面再建source和ports两个子组。source组添加所有src目录下的.c文件ports组添加context_gcc.S和cpuport.c这两个关键文件。3.2 关键文件修改rtconfig.h是RT-Thread的配置中枢需要重点关注这几个参数#define RT_THREAD_PRIORITY_MAX 8 // 根据实际需求设置优先级数量 #define RT_TICK_PER_SECOND 1000 // 系统时钟频率影响任务调度粒度 #define RT_ALIGN_SIZE 4 // 内存对齐大小STM32是32位架构 #define RT_USING_HEAP // 启用动态内存管理board.c需要实现三个关键函数rt_hw_board_init()硬件初始化入口SysTick_Handler()系统时钟中断堆内存初始化函数如果使用动态内存这里有个实用技巧在rt_hw_board_init()中先初始化系统时钟再初始化外设。我遇到过因为时钟未初始化导致串口乱码的问题调试了很久才发现是这个顺序问题。4. 系统初始化流程详解RT-Thread的启动过程比裸机程序复杂一些理解这个流程对调试很有帮助。完整的启动序列如下复位后首先执行启动文件(startup_stm32f10x_md.s)中的Reset_Handler初始化.data段和.bss段调用SystemInit()函数配置时钟树跳转到main()函数在main()中调用rtthread_startup()启动RT-Thread内核内核初始化硬件(board.c中的rt_hw_board_init)创建main线程并开始调度在实际项目中我建议在main()函数中这样组织代码int main(void) { rtthread_startup(); // 启动RT-Thread内核 return 0; // 理论上不会执行到这里 }所有硬件初始化代码都应该放在board.c的rt_hw_board_init()函数中。这个函数会在内核初始化阶段自动调用比main()函数执行得更早。如果需要在main线程中执行特定操作可以使用RT-Thread的main线程钩子void main_thread_entry(void *parameter) { // 在这里添加你的应用代码 while(1) { rt_thread_mdelay(500); // 周期性任务 } } int main(void) { rt_thread_t tid; tid rt_thread_create(main, main_thread_entry, RT_NULL, 1024, 8, 20); if(tid ! RT_NULL) rt_thread_startup(tid); rtthread_startup(); return 0; }5. 常见问题与调试技巧在移植过程中我遇到过几个典型问题这里分享下解决方案5.1 内存不足问题STM32F103的资源有限如果出现HardFault很可能是栈或堆空间不足。可以通过以下方式优化在startup_stm32f10x_md.s中增大堆栈大小调整rtconfig.h中的RT_MAIN_THREAD_STACK_SIZE使用rt_memheap管理多个内存区域5.2 中断冲突问题RT-Thread接管了SysTick和PendSV中断如果其他代码也使用了这些中断会导致冲突。解决方法删除stm32f10x_it.c中的SysTick_Handler确保没有其他代码修改中断优先级组5.3 线程调度异常如果发现线程不能正常调度可以检查RT_TICK_PER_SECOND设置是否合理确认SysTick中断正常触发使用rt_kprintf输出调试信息我习惯在board.c中添加一个简单的LED闪烁线程作为系统运行指示灯static void led_thread_entry(void *parameter) { rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT); while(1) { rt_pin_write(LED_PIN, PIN_HIGH); rt_thread_mdelay(200); rt_pin_write(LED_PIN, PIN_LOW); rt_thread_mdelay(800); } } void rt_hw_board_init() { // ...其他初始化代码... rt_thread_t tid; tid rt_thread_create(led, led_thread_entry, RT_NULL, 256, 20, 10); if(tid ! RT_NULL) rt_thread_startup(tid); }6. 进阶优化与功能扩展基础系统搭建完成后可以考虑添加一些常用功能6.1 FinSH控制台FinSH是RT-Thread的交互式命令行工具非常实用。添加步骤在rtconfig.h中启用RT_USING_FINSH添加finsh组件源码实现串口驱动并注册为控制台设备6.2 软件定时器RT-Thread的软件定时器功能强大且资源占用低static rt_timer_t timer1; static void timeout(void *parameter) { rt_kprintf(timer timeout\n); } void timer_init(void) { timer1 rt_timer_create(timer1, timeout, RT_NULL, 1000, RT_TIMER_FLAG_PERIODIC); if(timer1 ! RT_NULL) rt_timer_start(timer1); }6.3 动态线程创建相比静态线程动态线程更灵活但需要内存管理支持void dynamic_thread_sample(void) { rt_thread_t tid; tid rt_thread_create(dyn, thread_entry, RT_NULL, 512, 8, 10); if(tid ! RT_NULL) rt_thread_startup(tid); }在实际项目中我发现RT-Thread的信号量和互斥量特别好用可以大大简化多线程编程。比如用互斥量保护共享资源static rt_mutex_t mutex; void thread1_entry(void *parameter) { while(1) { rt_mutex_take(mutex, RT_WAITING_FOREVER); // 访问共享资源 rt_mutex_release(mutex); } }移植完成后建议运行RT-Thread提供的示例程序测试内核功能是否正常。比如创建一个生产者-消费者模型验证线程调度和IPC机制是否工作正常。