GD32F470ZGT6 RT-Thread CMake 模板适配指南
本文档详细说明如何基于 GD32F470ZGT6 裸机模板,移植 RT-Thread v4.1.1 LTS 实时操作系统。
一、参考标准
| 参考来源 | 版本/路径 | 用途 |
|---|---|---|
| RT-Thread 内核 | v4.1.1 LTS | 内核源码、Cortex-M 端口 |
| RT-Thread Cortex-M4 BSP | rt-thread/libcpu/arm/cortex-m4/ |
ARM_CM4 标准端口实现 |
| RT-Thread GD32 BSP | rt-thread/bsp/gd32450z-eval/ |
GD32F4xx 官方参考移植 |
| RT-Thread STM32 BSP | rt-thread/bsp/stm32/ |
STM32F4xx 官方参考移植(HAL 库方案) |
| GD32F4xx 标准外设库 | V3.3.3 | GPIO、时钟初始化 |
| RT-Thread 编程规范 | RT-Thread 官方文档 | 代码风格、API 使用规范 |
二、RT-Thread 官方标准解析
2.1 RT-Thread 启动流程
RT-Thread 采用标准的启动流程,与裸机和 FreeRTOS 有本质区别:
Reset_Handler (启动汇编)
└── entry() (components.c)
└── rtthread_startup() (components.c)
├── rt_hw_interrupt_disable() /* 关闭全局中断 */
├── rt_hw_board_init() /* 板级初始化 */
├── rt_show_version() /* 打印版本信息 */
├── rt_system_timer_init() /* 系统定时器初始化 */
├── rt_system_scheduler_init() /* 调度器初始化 */
├── rt_application_init() /* 创建 main 线程 */
├── rt_system_timer_thread_init() /* 定时器线程初始化 */
├── rt_thread_idle_init() /* 空闲线程初始化 */
└── rt_system_scheduler_start() /* 启动调度器 */
关键区别:
- RT-Thread 的程序入口是
entry()而非main() main()是在调度器启动后作为 "main 线程" 被调用的rt_hw_board_init()是用户必须实现的板级初始化函数
2.2 SysTick 标准实现
参考 GD32 官方 BSP (rt-thread/bsp/gd32450z-eval/drivers/board.c):
c
/** System Clock Configuration */
void SystemClock_Config(void)
{
SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
NVIC_SetPriority(SysTick_IRQn, 0); /* 最高优先级! */
}
/**
* This is the timer interrupt service routine.
*/
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
rt_tick_increase();
/* leave interrupt */
rt_interrupt_leave();
}
关键要点:
- SysTick 在
rt_hw_board_init()中启用(调度器启动前) - SysTick 优先级必须设为 最高 (0),确保 tick 计数不被中断阻塞
SysTick_Handler中调用rt_tick_increase()驱动 RT-Thread 调度器
2.3 board.c 标准结构
参考 RT-Thread BSP 标准,board.c 必须包含:
rt_hw_board_init()--- 板级初始化入口SysTick_Handler()--- SysTick 中断处理- 堆内存初始化 ---
rt_system_heap_init()
2.4 rtconfig.h 标准结构
RT-Thread 的内核配置通过 rtconfig.h 管理:
c
#ifndef __RTTHREAD_CFG_H__
#define __RTTHREAD_CFG_H__
/* RT-Thread Kernel */
#define RT_NAME_MAX 8
#define RT_ALIGN_SIZE 4
#define RT_THREAD_PRIORITY_MAX 32
#define RT_TICK_PER_SECOND 1000
/* ... 更多配置 ... */
#endif
三、适配步骤
步骤 1:准备 RT-Thread 源码
将 RT-Thread 源码放置到 SDK 目录:
sdk/utilities/Third_Party/RT-Thread/rt-thread/
├── include/ # RT-Thread 内核头文件
├── src/ # 内核源码
│ ├── clock.c # 系统时钟与 tick 管理
│ ├── components.c # 组件初始化(entry、rtthread_startup)
│ ├── cpu.c # CPU 管理
│ ├── device.c # 设备管理
│ ├── idle.c # 空闲线程
│ ├── ipc.c # 进程间通信
│ ├── irq.c # 中断管理
│ ├── kservice.c # 内核服务
│ ├── mem.c # 内存管理
│ ├── mempool.c # 内存池
│ ├── object.c # 对象管理
│ ├── scheduler.c # 调度器
│ ├── thread.c # 线程管理
│ └── timer.c # 定时器
└── libcpu/
└── arm/
└── cortex-m4/
├── cpuport.c # Cortex-M4 端口(上下文切换、中断管理)
└── context_gcc.S # GCC 汇编实现(PendSV、上下文切换)
步骤 2:创建 board.c
在 myprog/app/board.c 中实现板级初始化:
c
#include "gd32f4xx.h"
#include "rtthread.h"
/* 定义 64KB 静态堆内存 */
static rt_uint8_t rt_heap[64 * 1024];
/**
* RT-Thread 板级初始化入口
* 按照 RT-Thread 官方 BSP 标准实现
*/
void rt_hw_board_init(void)
{
/* 更新系统时钟 */
SystemCoreClockUpdate();
/* 配置 SysTick,每 1ms 一次中断 */
SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
/*
* 关键:将 SysTick 优先级设为最高 (0)
* 参考 GD32 官方 BSP: rt-thread/bsp/gd32450z-eval/drivers/board.c
*/
NVIC_SetPriority(SysTick_IRQn, 0);
/* 初始化堆内存 */
rt_system_heap_init((void *)rt_heap,
(void *)((uint32_t)rt_heap + sizeof(rt_heap)));
}
/**
* SysTick 中断处理函数
* 按照 RT-Thread 官方标准实现
* 参考: rt-thread/src/clock.c 中的 rt_tick_increase()
*/
void SysTick_Handler(void)
{
/* 进入中断上下文 */
rt_interrupt_enter();
/* tick 计数增加,驱动 RT-Thread 调度器 */
rt_tick_increase();
/* 离开中断上下文 */
rt_interrupt_leave();
}
标准参考对照:
| 实现 | 参考来源 |
|---|---|
rt_hw_board_init() 结构 |
rt-thread/bsp/gd32450z-eval/drivers/board.c |
SysTick_Config() 调用位置 |
rt-thread/bsp/gd32450z-eval/drivers/board.c:37 |
NVIC_SetPriority(SysTick_IRQn, 0) |
rt-thread/bsp/gd32450z-eval/drivers/board.c:38 |
SysTick_Handler 结构 |
rt-thread/src/clock.c:91-127 + BSP 实现 |
rt_system_heap_init() |
RT-Thread BSP 标准要求 |
步骤 3:创建 rtconfig.h
在 myprog/include/rtconfig.h 中创建内核配置:
c
#ifndef __RTTHREAD_CFG_H__
#define __RTTHREAD_CFG_H__
/* RT-Thread Kernel */
#define RT_NAME_MAX 8 /* 对象名最大长度 */
#define RT_ALIGN_SIZE 4 /* 内存对齐字节数 */
#define RT_THREAD_PRIORITY_MAX 32 /* 最大优先级数量 */
#define RT_TICK_PER_SECOND 1000 /* tick 频率 (Hz) */
#define RT_USING_OVERFLOW_CHECK /* 启用栈溢出检测 */
#define RT_USING_HOOK /* 启用钩子函数 */
#define RT_USING_IDLE_HOOK /* 启用空闲钩子 */
#define RT_IDLE_HOOK_LIST_SIZE 4 /* 空闲钩子列表大小 */
#define IDLE_THREAD_STACK_SIZE 256 /* 空闲线程栈大小 */
/* 内存管理 */
#define RT_USING_MEMPOOL /* 启用内存池 */
#define RT_USING_SMALL_MEM /* 启用小内存管理 */
#define RT_USING_SMALL_MEM_AS_HEAP /* 使用小内存作为堆 */
#define RT_USING_HEAP /* 启用堆内存 */
/* 线程间通信 */
#define RT_USING_SEMAPHORE /* 信号量 */
#define RT_USING_MUTEX /* 互斥量 */
#define RT_USING_EVENT /* 事件 */
#define RT_USING_MAILBOX /* 邮箱 */
#define RT_USING_MESSAGEQUEUE /* 消息队列 */
/* 控制台 */
#define RT_USING_CONSOLE /* 启用控制台 */
#define RT_CONSOLEBUF_SIZE 128 /* 控制台缓冲区大小 */
#define RT_CONSOLE_DEVICE_NAME "uart0" /* 控制台设备名 */
/* 组件自动初始化 */
#define RT_USING_COMPONENTS_INIT /* 启用组件自动初始化 */
#define RT_USING_USER_MAIN /* 启用用户 main 线程 */
#define RT_MAIN_THREAD_STACK_SIZE 2048 /* main 线程栈大小 */
#define RT_MAIN_THREAD_PRIORITY 10 /* main 线程优先级 */
/* CPU 架构 */
#define ARCH_ARM /* ARM 架构 */
#define ARCH_ARM_CORTEX_M /* Cortex-M 内核 */
#define ARCH_ARM_CORTEX_M4 /* Cortex-M4 */
#endif
标准参考 :RT-Thread 官方 BSP 的 rtconfig.h(rt-thread/bsp/gd32450z-eval/rtconfig.h)。
步骤 4:配置 CMakeLists.txt
cmake
# RT-Thread 源码路径
set(RTTHREAD_ROOT "${SDK_ROOT}/utilities/Third_Party/RT-Thread/rt-thread")
# 头文件路径
include_directories(
${RTTHREAD_ROOT}/include
${RTTHREAD_ROOT}/libcpu/arm/cortex-m4
)
# RT-Thread 内核源码
set(RTTHREAD_SOURCES
${RTTHREAD_ROOT}/src/clock.c
${RTTHREAD_ROOT}/src/components.c
${RTTHREAD_ROOT}/src/cpu.c
${RTTHREAD_ROOT}/src/device.c
${RTTHREAD_ROOT}/src/idle.c
${RTTHREAD_ROOT}/src/ipc.c
${RTTHREAD_ROOT}/src/irq.c
${RTTHREAD_ROOT}/src/kservice.c
${RTTHREAD_ROOT}/src/mem.c
${RTTHREAD_ROOT}/src/mempool.c
${RTTHREAD_ROOT}/src/object.c
${RTTHREAD_ROOT}/src/scheduler.c
${RTTHREAD_ROOT}/src/thread.c
${RTTHREAD_ROOT}/src/timer.c
${RTTHREAD_ROOT}/libcpu/arm/cortex-m4/cpuport.c
${RTTHREAD_ROOT}/libcpu/arm/cortex-m4/context_gcc.S
)
# 加入编译
set(PROJECT_SOURCES
${PERIPHERAL_SOURCES}
${SYSTEM_SOURCES}
${STARTUP_SOURCES}
${APP_SOURCES}
${RTTHREAD_SOURCES}
)
关键文件说明:
| 源码文件 | 功能 |
|---|---|
components.c |
启动入口(entry() → rtthread_startup()) |
scheduler.c |
调度器实现 |
thread.c |
线程管理(创建、销毁、挂起、恢复) |
clock.c |
tick 管理(rt_tick_increase()) |
cpuport.c |
Cortex-M 端口(上下文切换、中断禁用/使能) |
context_gcc.S |
GCC 汇编(PendSV 处理、上下文切换汇编) |
步骤 5:修改启动文件
确保启动文件中 FPU 配置正确:
assembly
.cpu cortex-m4
.fpu fpv4-sp-d16 /* Cortex-M4F 硬件浮点 */
.thumb
.section .isr_vector,"a",%progbits
g_pfnVectors:
.word _estack
.word Reset_Handler
/* ... 其他中断向量 ... */
.word SysTick_Handler /* SysTick 中断向量 */
关键点:
.fpu fpv4-sp-d16必须与 CMake 中的-mfpu=fpv4-sp-d16一致- 向量表中必须包含
SysTick_Handler,指向board.c中的实现
步骤 6:实现 main.c
c
#include "gd32f4xx.h"
#include "rtthread.h"
#define LED1_PIN GPIO_PIN_3
#define LED1_PORT GPIOE
/* ... 其他 LED 定义 ... */
/**
* LED 跑马灯线程入口
*/
static void led_thread_entry(void* parameter)
{
while(1)
{
gpio_bit_write(LED1_PORT, LED1_PIN, 1);
gpio_bit_write(LED2_PORT, LED2_PIN, 0);
gpio_bit_write(LED3_PORT, LED3_PIN, 0);
gpio_bit_write(LED4_PORT, LED4_PIN, 0);
rt_thread_mdelay(1000);
/* 切换下一个 LED ... */
}
}
/**
* main 函数(在 RT-Thread 调度器启动后作为 main 线程执行)
*/
int main(void)
{
/* 使能 GPIO 时钟 */
rcu_periph_clock_enable(RCU_GPIOE);
rcu_periph_clock_enable(RCU_GPIOD);
rcu_periph_clock_enable(RCU_GPIOG);
rcu_periph_clock_enable(RCU_GPIOA);
/* 配置 LED 引脚 */
gpio_mode_set(LED1_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, LED1_PIN);
gpio_output_options_set(LED1_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, LED1_PIN);
/* ... 其他 LED 配置 ... */
/* 创建 LED 线程 */
rt_thread_t led_thread = rt_thread_create("led",
led_thread_entry, RT_NULL, /* 线程名、入口、参数 */
512, 10, 20); /* 栈大小、优先级、时间片 */
if (led_thread != RT_NULL)
{
rt_thread_startup(led_thread);
}
/* main 线程空循环 */
while(1)
{
rt_thread_delay(RT_TICK_PER_SECOND);
}
}
关键说明:
main()由 RT-Thread 调度器自动调用,不是程序入口- 程序入口是
components.c中的entry()→rtthread_startup() rt_thread_create()参数:名称、入口函数、参数、栈大小、优先级、时间片rt_thread_mdelay(1000)使当前线程挂起 1000ms,期间调度器运行其他线程
四、关键差异对比
| 项目 | 裸机模板 | FreeRTOS 模板 | RT-Thread 模板 |
|---|---|---|---|
| 程序入口 | main() |
main() → vTaskStartScheduler() |
entry() → rtthread_startup() |
| SysTick 处理 | 用户手动实现 | port.c 自动处理 |
用户实现 SysTick_Handler + rt_tick_increase() |
| 延时方式 | delay_ms() 阻塞 |
vTaskDelay() 非阻塞 |
rt_thread_mdelay() 非阻塞 |
| 线程创建 | 无 | xTaskCreate() |
rt_thread_create() |
| 堆内存 | 无 | heap_4.c 自动管理 |
rt_system_heap_init() 手动初始化 |
| 板级初始化 | main() 中直接调用 |
main() 中直接调用 |
rt_hw_board_init() 标准接口 |
| 配置文件 | 无 | FreeRTOSConfig.h |
rtconfig.h |
| 启动文件 | Reset_Handler → main |
Reset_Handler → main |
Reset_Handler → entry |
五、关键修复说明
SysTick 中断优先级修复
问题 :SysTick_Config() 默认将 SysTick 中断优先级设为最低(0xFF)。
后果:当其他中断正在执行时,SysTick 中断会被延迟处理,导致:
rt_tick计数不准确rt_thread_mdelay()定时器无法按时触发- 线程被挂起后无法被唤醒,系统卡死
修复:
c
NVIC_SetPriority(SysTick_IRQn, 0); /* 最高优先级 */
参考标准:
- RT-Thread GD32 BSP:
rt-thread/bsp/gd32450z-eval/drivers/board.c:38 - 该配置将 SysTick 优先级设为最高(0),确保 tick 中断不被任何中断阻塞
六、编译与烧录
6.1 编译
bash
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug
make -j8
6.2 烧录
bash
sudo ./flash/flash.sh
6.3 验证
4 个 LED 每 1 秒依次切换,形成跑马灯效果。
七、关键配置汇总
| 项目 | 值 | 说明 |
|---|---|---|
| RT-Thread 版本 | v4.1.1 LTS | 长期支持版 |
| CPU 端口 | cortex-m4 | Cortex-M4 标准端口 |
| 堆大小 | 64KB | rt_heap[64 * 1024] |
| Tick 频率 | 1000Hz (1ms) | RT_TICK_PER_SECOND |
| 优先级数量 | 32 | RT_THREAD_PRIORITY_MAX |
| main 线程栈 | 2048 字节 | RT_MAIN_THREAD_STACK_SIZE |
| main 线程优先级 | 10 | RT_MAIN_THREAD_PRIORITY |
| SysTick 优先级 | 0 (最高) | NVIC_SetPriority(SysTick_IRQn, 0) |
| 调度方式 | 基于优先级的抢占式 | RT-Thread 标准调度 |
八、注意事项
- 入口函数 :RT-Thread 使用
entry()作为入口,不是main()。启动文件中的Reset_Handler调用SystemInit()后跳转到entry() - 堆内存初始化 :必须在
rt_hw_board_init()中调用rt_system_heap_init(),否则rt_thread_create()等动态分配函数会失败 - SysTick 优先级:必须设为最高(0),参考 GD32 官方 BSP 标准
rtconfig.h必须包含 :ARCH_ARM、ARCH_ARM_CORTEX_M、ARCH_ARM_CORTEX_M4三个宏,否则 RT-Thread 无法正确编译 Cortex-M 端口代码- 组件自动初始化 :启用
RT_USING_COMPONENTS_INIT后,使用INIT_COMPONENT_EXPORT()宏声明的函数会被自动调用 - 中断上下文 :
SysTick_Handler中必须调用rt_interrupt_enter()和rt_interrupt_leave(),否则 RT-Thread 无法正确管理中断嵌套