RT-Thread Nano移植到GD32VF103
本文记录了在GD32VF103下移植RT-Thread Nano的过程,基于RV-STAR开发板。
git仓库地址:https://github.com/dwniaoniao/gd32vf103_template/tree/rtthread
RT-Thread Nano 简介
RT-Thread Nano 是一个极简版的硬实时内核,它是由 C 语言开发, 采用面向对象的编程思维,具有良好的代码风格,是一款可裁剪的、抢占式实时多任务的 RTOS。
更多内容见RT-Thread Nano 简介。
准备工作
移植RT-Thread Nano需要一份基础的裸机源码工程和RT-Thread Nano源码。
使用从GD32VF103固件库创建的Makefile工程作裸机源码工程,关于该 Makefile工程的介绍和Demo获取,见本站文章:使用GD32VF103固件库创建Makefile工程。
RT-Thread Nano 源码可从官方github仓库获取:https://github.com/RT-Thread/rtthread-nano
添加 RT-Thread Nano 到工程
在裸机源码工程下新建rtthread路径,把Nano源码中的include、src、libcpu
文件夹拷贝到该路径下,其中libcpu仅保留与GD32VF103芯片架构相关的文件:
bumblebee和common。把bsp/_template下的board.c和rtconfig.h也拷贝
进来。拷贝完成后,rtthread路径下的内容如下:
rtthread
├── include
├── libcpu
│ └── risc-v
│ ├── bumblebee
│ └── common
├── src
├── rtconfig.h
└── board.c
修改源码工程的Makefile文件,添加rtthread路径下的汇编、C和头文件路径:
# ASM sources
ASM_SOURCES = \
$(FIRMWARE_DIR)/RISCV/env_Eclipse/start.S \
$(FIRMWARE_DIR)/RISCV/env_Eclipse/entry.S \
rtthread/libcpu/risc-v/bumblebee/interrupt_gcc.S \
rtthread/libcpu/risc-v/common/context_gcc.S
C_SOURCES = \
$(wildcard $(FIRMWARE_DIR)/GD32VF103_standard_peripheral/*.c) \
# Other C source files...
$(wildcard rtthread/src/*.c) \
rtthread/board.c \
rtthread/libcpu/risc-v/common/cpuport.c
# C includes
C_INCLUDES = \
-I. \
-I inc \
-I$(FIRMWARE_DIR)/GD32VF103_standard_peripheral/Include \
-I$(FIRMWARE_DIR)/GD32VF103_standard_peripheral \
-I$(FIRMWARE_DIR)/RISCV/drivers \
-I rtthread \
-I rtthread/include \
-I rtthread/libcpu/risc-v/common
适配 RT-Thread Nano
修改入口函数
修改start.S启动代码,实现RT-Thread的启动,
在启动时先跳转到entry()函数执行,而不是跳转
到main():
/* argc = argv = 0 */
li a0, 0
li a1, 0
call entry
tail exit
系统时钟配置
在board.c中实现系统时钟和OS Tick配置:
void rt_hw_board_init(void)
{
SystemInit();
//ECLIC init
eclic_init(ECLIC_NUM_INTERRUPTS);
eclic_mode_enable();
*(rt_uint64_t *)(TIMER_CTRL_ADDR + TIMER_MTIMECMP) = TIMER_FREQ / RT_TICK_PER_SECOND;
*(rt_uint64_t *)(TIMER_CTRL_ADDR + TIMER_MTIME) = 0;
eclic_set_intattr(CLIC_INT_TMR, 0x0);
eclic_irq_enable(CLIC_INT_TMR, 0, 0);
/* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
}
注意: RT-Thread Nano 文档提供的RISC-V移植说明中,在rt_hw_board_init()
函数中启用了全局中断,这里一开始也是这样做的。但经过测试,此处启用
全局中断会出错。rt_hw_board_init()结束后执行rt_show_version()函数,
如果启用了串口打印功能,rt_show_version()可能是一个耗时的操作,
在其执行过程中有可能出现定时器中断,错误进入了rt_tick_increase()。
为了解决这个问题,不在rt_hw_board_init()中启用全局中断,全局中断
在调度器切换到main线程后自动启用(进行上下文切换时,执行mret指令之前,
将mstatus的MPIE域设为1)。
在定时器的中断服务例程中调用RT-Thread提供的rt_tick_increase():
void rt_os_tick_callback(void)
{
*(rt_uint64_t *)(TIMER_CTRL_ADDR + TIMER_MTIME) = 0;
rt_interrupt_enter();
rt_tick_increase();
rt_interrupt_leave();
}
void eclic_mtip_handler()
{
rt_os_tick_callback();
}
添加串口打印功能
在rtconfig.h中启用宏定义RT_USING_CONSOLE。
修改board.c中的uart_init和rt_hw_console_output函数,
实现串口外设初始化和通过串口输出字符串功能。
#ifdef RT_USING_CONSOLE
static int uart_init(void)
{
/* enable GPIO TX and RX clock */
rcu_periph_clock_enable(RCU_GPIOC);
rcu_periph_clock_enable(RCU_GPIOD);
/* enable USART clock */
rcu_periph_clock_enable(RCU_UART4);
/* connect port to USARTx_Tx */
gpio_init(GPIOC, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_12);
/* connect port to USARTx_Rx */
gpio_init(GPIOD, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_2);
/* USART configure */
usart_deinit(UART4);
usart_baudrate_set(UART4, 115200U);
usart_word_length_set(UART4, USART_WL_8BIT);
usart_stop_bit_set(UART4, USART_STB_1BIT);
usart_parity_config(UART4, USART_PM_NONE);
usart_hardware_flow_rts_config(UART4, USART_RTS_DISABLE);
usart_hardware_flow_cts_config(UART4, USART_CTS_DISABLE);
usart_receive_config(UART4, USART_RECEIVE_ENABLE);
usart_transmit_config(UART4, USART_TRANSMIT_ENABLE);
usart_enable(UART4);
return 0;
}
INIT_BOARD_EXPORT(uart_init);
void rt_hw_console_output(const char *str)
{
rt_size_t i = 0, size = 0;
char a = '\r';
size = rt_strlen(str);
for (i = 0; i < size; i++){
if (*(str + i) == '\n'){
usart_data_transmit(UART4, (uint32_t)a);
while(usart_flag_get(UART4, USART_FLAG_TBE) == RESET);
}
usart_data_transmit(UART4, (uint32_t)(*(str + i)));
while(usart_flag_get(UART4, USART_FLAG_TBE) == RESET);
}
}
串口外设初始化使用RT-Thread的自动初始化功能,INIT_BOARD_EXPORT(uart_init)
会在段.rti_fn*中生成函数__rt_init_uart_init(),该函数在板初始化过程中
调用。为了使自动初始化正常执行,需要修改链接脚本,使自动初始化相关的段
位置正常:
.text :
{
*(.rodata .rodata.*)
*(.text.unlikely .text.unlikely.*)
*(.text.startup .text.startup.*)
*(.text .text.*)
*(.gnu.linkonce.t.*)
KEEP (*(SORT(.rti_fn*)))
} >flash AT>flash
移植控制台/FinSH
上一节已经添加了UART串口打印功能,以下移植FinSH组件实现命令 输入,用于在控制台输入命令调试系统。
添加FinSH源码到工程
把Nano源码中的components/finsh文件夹拷贝到rtthread路径下,
修改源码工程的Makefile文件,添加finsh路径下的C和头文件:
C_SOURCES = \
$(wildcard $(FIRMWARE_DIR)/GD32VF103_standard_peripheral/*.c) \
# Other C source files...
$(wildcard rtthread/components/finsh/*.c)
# ...
C_INCLUDES = \
-I. \
# Other C include files...
-I rtthread/components/finsh
适配FinSH
在rtconfig.h文件添加如下宏定义:
#define RT_USING_FINSH // 使能FinSH
#define FINSH_USING_SYMTAB // 可以在FinSH中使用符号表
#define FINSH_USING_DESCRIPTION // 给每个FinSH的符号添加一段描述
#define MSH_USING_BUILT_IN_COMMANDS // 启用命令ps和free
修改链接脚本,在.text段添加以下内容,使FinSH命令功能
正常工作:
__fsymtab_start = .;
KEEP(*(FSymTab))
__fsymtab_end = .;
实现rt_hw_console_getchar:
在board.c添加控制台输入函数:
char rt_hw_console_getchar()
{
while (usart_flag_get(UART4, USART_FLAG_RBNE) == RESET);
return (usart_data_receive(UART4));
}
编写第一个应用
移植完RT-Thread Nano之后,main()函数变成操作系统的一个
线程,以下实现板载LED闪烁并通过串口输出字符串hello.:
#include "rtthread.h"
#include "gd32vf103_rcu.h"
#include "gd32vf103_gpio.h"
#define LEDG_PIN GPIO_PIN_1
#define LEDG_GPIO_PORT GPIOA
void main()
{
rcu_periph_clock_enable(RCU_GPIOA);
gpio_bit_set(LEDG_GPIO_PORT, LEDG_PIN);
gpio_init(LEDG_GPIO_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_10MHZ, LEDG_PIN);
rt_kprintf("hello.\n");
while(1){
gpio_bit_set(LEDG_GPIO_PORT, LEDG_PIN);
rt_thread_mdelay(500);
gpio_bit_reset(LEDG_GPIO_PORT, LEDG_PIN);
rt_thread_mdelay(500);
}
}
打开串口调试工具,板复位后可以看到如下输出:
\ | /
- RT - Thread Operating System
/ | \ 4.1.1 build Oct 11 2025 13:50:29
2006 - 2022 Copyright by RT-Thread team
hello.
msh >
可以使用FinSH基本命令,效果如下:
msh >help
RT-Thread shell commands:
list - list objects
list_timer - list timer in system
list_mailbox - list mail box in system
list_sem - list semaphore in system
list_thread - list thread
version - show RT-Thread version information
clear - clear the terminal screen
free - Show the memory usage in the system.
ps - List threads in the system.
help - RT-Thread shell help.
msh >ps
thread pri status sp stack size max used left tick error
-------- --- ------- ---------- ---------- ------ ---------- ---
tshell 20 running 0x000000b0 0x00000800 27% 0x00000009 OK
tidle0 31 ready 0x00000080 0x00000100 50% 0x00000020 OK
main 10 suspend 0x000000f0 0x00000200 46% 0x00000013 OK
msh >
msh >free
total : 15280
used : 2984
maximum : 2984
available: 12296