当前位置: 首页 > news >正文

STM32F407 + RT-Thread 实战:从工程结构到多线程 LED 闪烁

一、工程简介

最近看了一个基于 `STM32F407` 的 `RT-Thread` 工程,整体结构比较标准,功能上也比较适合作为入门练手项目。

这个工程的核心功能并不复杂,主要是通过 `RT-Thread` 创建多个线程,分别控制不同的 LED 引脚按不同节奏闪烁。虽然只是一个“多线程点灯”实验,但它已经把 RTOS 中几个关键概念串起
来了,比如:

- 线程创建
- 线程调度
- GPIO 控制
- 板级初始化
- 自动初始化机制

如果你刚开始接触 `RT-Thread`,这种工程非常适合拿来分析和练手。

---

二、工程目录结构分析

从目录上看,这个工程主要包含以下几个部分:

- `applications`
- `drivers`
- `cubemx`
- `rt-thread`
- `.config / rtconfig.h`

1. 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 闪烁”的实验。

对应引脚如下:

- `PA6`:LED1
- `PA4`:LED2
- `PA3`:LEDSYS

三个线程的运行节奏不同:

- `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,建议先把这种“多线程点灯”工程彻底跑通,再一步一步往里面加功能。先理解线程,再理解线程间通信,最后再逐步扩展成完整项目,这样学习路径会更清晰。

如果这篇文章对你有帮助,欢迎交流。

http://www.jsqmd.com/news/864423/

相关文章:

  • easyPoi使用
  • 如何用abap2xlsx将SAP报表开发带入Excel自动化时代
  • NoFences:免费开源的Windows桌面整理终极方案,告别杂乱桌面
  • [Triton笔记4]低内存 Dropout
  • 百度网盘资源获取革命:3秒智能破解提取码的技术方案
  • C语言算法-02哈夫曼树
  • 地铁刷卡数据分析第三关
  • Raw Accel 终极指南:如何精准控制鼠标加速提升游戏体验
  • 入行AI应用开发?收藏这份“先进去再补课”的学习路线,小白也能轻松上手大模型!
  • 写给前端的 CANN-ascend-devkit:昇腾开发套件到底是啥?
  • DLSS版本管理工具:5分钟完成游戏性能终极优化
  • 服务数百万开发者,likeshop 凭什么成为开源商城下载量前列?
  • 三分钟掌握B站视频下载:轻松保存4K大会员专属内容
  • Betaflight飞控固件:2026年无人机飞行性能的终极解决方案
  • 免费在线法线贴图生成器终极指南:3分钟为你的3D模型添加逼真细节
  • 你的代码 80% 可以由 AI 写——手把手教你搭建 Coding Agent
  • 在模型广场对比不同模型的响应速度与风格选择合适接口
  • 使用 Terraform Grafana Provider 实现 Grafana 全栈 IaC 一体化管理的完整方案
  • “杀!杀!杀!”、“我最讨厌事后道歉”——骂“杀哥”之前,谁还没当过情绪崩溃的人
  • DazToBlender:3D创作工作流的无缝桥梁
  • 河南中职医护院校怎么选,正规卫校盘点,各地医学中专择校避坑大全 - 海棠依旧大
  • Java 常用数据结构与工具类速查
  • 从 CLAS 目录学会 ABAP 类文件格式的读法
  • 如何用AutoLegalityMod插件实现宝可梦数据一键合法化
  • Claude Code 命令配置指南
  • OpenClaw 换 “大脑”!DeepSeek V4 默认集成,离线私有 AI 自由
  • AlwaysOnTop:终极Windows窗口置顶解决方案完全指南
  • Grammarly Premium免费使用终极指南:智能Cookie搜索技术完全解析
  • Navicat Premium试用期重置完整指南:三步恢复14天免费试用
  • 为何越来越多工厂选择无线式大屏幕熔炼测温系统?核心原因解读