一、工程简介最近看了一个基于 STM32F407 的 RT-Thread 工程整体结构比较标准功能上也比较适合作为入门练手项目。这个工程的核心功能并不复杂主要是通过 RT-Thread 创建多个线程分别控制不同的 LED 引脚按不同节奏闪烁。虽然只是一个“多线程点灯”实验但它已经把 RTOS 中几个关键概念串起来了比如- 线程创建- 线程调度- GPIO 控制- 板级初始化- 自动初始化机制如果你刚开始接触 RT-Thread这种工程非常适合拿来分析和练手。---二、工程目录结构分析从目录上看这个工程主要包含以下几个部分- applications- drivers- cubemx- rt-thread- .config / rtconfig.h1. applications这是应用层代码目录也是我们最需要关注的部分。当前工程中主要有两个文件- main.c- led_blink.c它们分别完成不同线程的创建与启动。2. drivers这个目录主要是板级支持包相关代码比较关键的是 board.c负责- 堆内存初始化- 底层硬件初始化- 控制台设备设置- 板级组件初始化也就是说RT-Thread 跑起来之前底层环境准备工作主要在这里完成。3. cubemx这个目录里是 STM32CubeMX 生成的底层代码包括- GPIO 初始化- 时钟配置- 中断处理文件- HAL 驱动源码- 启动文件说明这个项目是通过 CubeMX RT-Thread BSP 结合起来搭建的。4. rt-thread这里是 RT-Thread 内核和组件源码包括- 线程调度- 内存管理- 设备驱动框架- FinSH/MSH 命令行组件- 各类内核组件通常做应用开发时不会频繁修改这里的源码但理解其结构对后续学习很有帮助。---三、这个工程实现了什么功能这个工程本质上是一个“多线程控制 3 个 LED 闪烁”的实验。对应引脚如下- PA6LED1- PA4LED2- PA3LEDSYS三个线程的运行节奏不同- LED1 每 100ms 闪烁一次- LED2 每 500ms 闪烁一次- LEDSYS 每 1000ms 翻转一次从实验现象上看就是三个 LED 同时以不同频率闪烁这正好体现了 RTOS 多线程并发运行的基本效果。---四、main.c 中的动态线程创建在 main.c 中定义了两个线程入口函数分别控制 LED1 和 LED2。1. LED1 与 LED2 线程入口函数#include rtthread.h #define DBG_TAG main #define DBG_LVL DBG_LOG #include rtdbg.h #include main.h #include board.h #include rtdbg.h #define LED1_PIN GET_PIN(A,6) #define LED2_PIN GET_PIN(A,4) #define LEDSYS_PIN GET_PIN(A, 3) #define THREAD_PRIORITY 25 #define THREAD_TIMESLICE 5 void led1_thread_entry(void *parameter) { while(1) { rt_pin_write(LED1_PIN, PIN_LOW); rt_thread_mdelay(100); rt_pin_write(LED1_PIN, PIN_HIGH); rt_thread_mdelay(100); } } void led2_thread_entry(void *parameter) { while(1) { rt_pin_write(LED2_PIN, PIN_LOW); rt_thread_mdelay(500); rt_pin_write(LED2_PIN, PIN_HIGH); rt_thread_mdelay(500); } }这里比较容易看懂- led1_thread_entry() 控制 PA6- led2_thread_entry() 控制 PA4- 两个线程都在 while(1) 中循环运行- 通过 rt_thread_mdelay() 实现周期性闪烁2. main 函数中创建线程int main(void) { int count 1; rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT); rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT); rt_thread_t thread_led1 rt_thread_create(LED1, led1_thread_entry, RT_NULL, 1024, THREAD_PRIORITY, THREAD_TIMESLICE); if (thread_led1 ! RT_NULL) { rt_thread_startup(thread_led1); } rt_thread_t thread_led2 rt_thread_create(LED2, led2_thread_entry, RT_NULL, 1024, THREAD_PRIORITY, THREAD_TIMESLICE); if (thread_led2 ! RT_NULL) { rt_thread_startup(thread_led2); } while (count) { rt_thread_mdelay(1000); } return RT_EOK; }这部分展示了 RT-Thread 最常见的动态线程创建方式- rt_thread_create()创建线程- rt_thread_startup()启动线程线程创建时传入的参数分别包括- 线程名称- 线程入口函数- 入口参数- 线程栈大小- 线程优先级- 时间片这种方式的优点是使用方便适合快速创建任务缺点是依赖动态内存分配。———五、led_blink.c 中的静态线程创建除了动态线程这个工程还演示了静态线程初始化方式代码在 led_blink.c 中。#include rtthread.h #include board.h #include rtdbg.h #define LEDSYS_PIN GET_PIN(A, 3) #define THREAD_PRIORITY 25 #define THREAD_TIMESLICE 5 ALIGN(RT_ALIGN_SIZE) static char ledsys_stack[256]; static struct rt_thread ledsys; void ledsys_thread_entry(void *parameter) { while(1) { rt_pin_write(LEDSYS_PIN, !rt_pin_read(LEDSYS_PIN)); rt_thread_mdelay(1000); } } int ledsys_thread_init(void) { rt_pin_mode(LEDSYS_PIN, PIN_MODE_OUTPUT); rt_thread_init(ledsys, LEDSYS, ledsys_thread_entry, RT_NULL, ledsys_stack, sizeof(ledsys_stack), THREAD_PRIORITY - 1, THREAD_TIMESLICE); rt_thread_startup(ledsys); return RT_EOK ; } INIT_APP_EXPORT(ledsys_thread_init);这个文件的重点有两个。1. 静态线程对象和栈空间ALIGN(RT_ALIGN_SIZE) static char ledsys_stack[256]; static struct rt_thread ledsys;这表示- 线程栈由用户手动分配- 线程控制块也是静态定义的- 不依赖动态内存这种方式在资源可控、稳定性要求高的场景更常见。2. 自动初始化机制INIT_APP_EXPORT(ledsys_thread_init);这一句非常关键。它的作用是把 ledsys_thread_init() 注册到应用初始化阶段在系统启动时自动调用。也就是说这个线程不需要在 main() 里手动创建系统启动后会自动完成初始化和启动。六、board.c 中的板级初始化在 drivers/board.c 中可以看到 RT-Thread 板级初始化入口RT_WEAK void rt_hw_board_init() { extern void hw_board_init(char *clock_src, int32_t clock_src_freq, int32_t clock_target_freq); #if defined(RT_USING_HEAP) rt_system_heap_init((void *) HEAP_BEGIN, (void *) HEAP_END); #endif hw_board_init(BSP_CLOCK_SOURCE, BSP_CLOCK_SOURCE_FREQ_MHZ, BSP_CLOCK_SYSTEM_FREQ_MHZ); #if defined(RT_USING_DEVICE) defined(RT_USING_CONSOLE) rt_console_set_device(RT_CONSOLE_DEVICE_NAME); #endif #ifdef RT_USING_COMPONENTS_INIT rt_components_board_init(); #endif }这段代码说明系统上电后主要做了以下几件事- 初始化堆内存- 初始化板级底层硬件- 设置控制台设备- 执行组件初始化这部分代码虽然不直接控制 LED但它是整个 RT-Thread 工程正常运行的基础。———七、工程配置项分析从 rtconfig.h 可以看到该工程启用了一些比较常用的 RT-Thread 功能。例如#define RT_USING_COMPONENTS_INIT #define RT_USING_USER_MAIN #define RT_USING_MSH #define RT_USING_FINSH #define RT_USING_SERIAL #define RT_USING_PIN #define RT_USING_SEMAPHORE #define RT_USING_MUTEX #define RT_USING_EVENT #define RT_USING_MAILBOX #define RT_USING_MESSAGEQUEUE这说明当前工程已经具备- 用户 main() 入口- MSH/FinSH 命令行- 串口驱动支持- GPIO 引脚操作- 常见线程间通信机制不过需要注意的是虽然配置中打开了信号量、互斥锁、事件、邮箱、消息队列这些功能但当前应用层代码还没有实际使用到现阶段主要还是一个线程调度和 GPIO 输出实验。———八、这个工程适合学什么虽然这个工程功能简单但它非常适合作为 RT-Thread 入门项目尤其适合学习以下内容- RT-Thread 工程结构- 动态线程创建方式- 静态线程创建方式- 线程优先级与时间片- GPIO 控制 LED- 自动初始化宏 INIT_APP_EXPORT- 板级初始化流程对于初学者来说这种工程的价值不在于功能复杂而在于它足够清晰能帮助我们快速建立对 RTOS 工程组织方式的理解。———九、总结这个 STM32F407 RT-Thread 工程本质上就是一个“多线程点灯”实验工程。它主要完成了三件事- 在 main.c 中动态创建两个线程- 在 led_blink.c 中静态创建一个自动启动线程- 在 board.c 中完成底层板级初始化虽然目前业务功能比较简单但它已经具备了一个标准 RT-Thread 工程的基本框架。后续如果继续扩展可以很自然地加入- 按键输入任务- 串口通信任务- 消息队列通信- 软件定时器- 传感器采集线程对于刚开始学习 RT-Thread 的同学来说这样一个工程非常适合作为起点。———十、结尾如果你刚开始接触 RT-Thread建议先把这种“多线程点灯”工程彻底跑通再一步一步往里面加功能。先理解线程再理解线程间通信最后再逐步扩展成完整项目这样学习路径会更清晰。如果这篇文章对你有帮助欢迎交流。