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源码中的includesrclibcpu 文件夹拷贝到该路径下,其中libcpu仅保留与GD32VF103芯片架构相关的文件: bumblebeecommon。把bsp/_template下的board.crtconfig.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指令之前, 将mstatusMPIE域设为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_initrt_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

参考