【GD32】RT-Thread实时操作系统移植(GD32F470ZGT6)

1. 简介

最近几年可以发现国产的实时操作系统越来越受欢迎了,本篇要移植的就是当中的翘楚------RT-Thread。

RT-Thread诞生于2006年,是国内以开源中立、社区化发展起来的一款高可靠实时操作系统 ,由睿赛德科技负责开发维护和运营 。并且在上一年度的市场欢迎程度中位列第一,第一次超过了老牌的FreeRTOS系统。

相比于FreeRTOS,它的优势是强大的第三方和官方软件库,这意味着在项目开发中可以大大减少软件库移植的时间,提高了开发效率。不过根据我以往的项目经验,这些库还是有不少bug存在的,所以RT-Thread社区对于库的维护和更新还是要更加努力才行。

RT-Thread库的代码风格、软件逻辑可以说是完全模仿Linux的 ,如果各位本身就有Linux系统的开发经验,那么RT-Thread可以说是轻松上手,甚至可以通过学习RT-Thread来反向学习Linux系统的原理(我就是这样的),毕竟Linux的官方文档确实一言难尽。对于新手,官网提供了比较完善的开发文档,我个人认为算是非常详尽的了。

2. 移植

RT-Thread为了尽可能地兼容多的单片机,它分为标准版、nano版和smart版。标准版就是给系统资源比较充足的单片机移植的,可以使用社区里面的所有库;nano版是给资源较少的单片机移植的,只能使用部分的官方库;smart版是给物联网设备移植,这个分支比较少用。

本篇我们移植的是标准版,目前最新的发行版本是5.1.0,在Github上面可以下载RT-Thread源码

2.1 导入文件

下载源码后解压,RT-Thread要导入的文件还是非常多的,先讲一下源代码里面关键文件夹的代码内容。

bsp目录主要存放各个芯片、开发板的外设驱动。

components目录主要存放一些系统部件,如HAL层驱动、C库支持、shell命令行支持等等。

documentation目录主要存放一些说明文档。

examples目录主要存放一些例程。

include目录主要是内核的头文件。

libcpu目录主要存放是是不同处理器底层的驱动。

src目录存放的是内核的源文件。

tools目录主要存放的是一些Python的工具脚本。

要导入的文件大致就是:

  1. src目录的大部分文件;
  1. libcpu目录中Arm Cortex-M4相关的文件,参考路径libcpu\arm\cortex-m4;

需要注意的是,如果使用AC5编译器,.S文件要导入context_rvds.S;如果使用AC6编译器,.S文件要导入context_gcc.S这个。

  1. bsp目录中GD32F470相关的外设驱动,参考路径bsp\gd32\arm\libraries\gd32_drivers;
  1. components目录中外设HAL层驱动相关文件,参考路径components\drivers;
  1. components目录中C库相关文件,参考路径components\libc\compilers;

要导入的头文件路径也比较多,参考下面:

其他设置要注意的是,不要勾选"Use MicroLIB"这一个选项。

另外就是,因为RT-Thread内核已经写好了所需的所有底层驱动,所以标准固件库里面导入的像systick、gd32f4xx_it类似的文件就不需要了。

2.2 配置文件

RT-Thread的配置文件应该是我见过最复杂的,不算第三方库至少也有上千个配置项,然后你要根据自己的配置项导入对应的源文件,导错的话就会编译不过。一般来说,官方会推荐使用RT-Thread Studio开发或使用Kconfig工具进行配置。不过这里只是进行简单移植即可,能成功跑起来就行,更深入的可以以后再慢慢学。

对于第一次进行移植的可以参考源码项目里面的例程,下面是我根据例程精简过的配置文件。

cpp 复制代码
#ifndef RT_CONFIG_H__
#define RT_CONFIG_H__

/* Automatically generated file; DO NOT EDIT. */
/* RT-Thread Configuration */

/* RT-Thread Kernel */

#define RT_NAME_MAX 8
#define RT_CPUS_NR 1
#define RT_ALIGN_SIZE 8
#define RT_THREAD_PRIORITY_32
#define RT_THREAD_PRIORITY_MAX 32
#define RT_TICK_PER_SECOND 1000
#define RT_USING_OVERFLOW_CHECK
#define RT_USING_HOOK
#define RT_HOOK_USING_FUNC_PTR
#define RT_USING_IDLE_HOOK
#define RT_IDLE_HOOK_LIST_SIZE 4
#define IDLE_THREAD_STACK_SIZE 256
#define RT_USING_TIMER_SOFT
#define RT_TIMER_THREAD_PRIO 4
#define RT_TIMER_THREAD_STACK_SIZE 512

/* kservice optimization */

#define RT_KSERVICE_USING_STDLIB
#define RT_USING_DEBUG
#define RT_DEBUGING_COLOR
#define RT_DEBUGING_CONTEXT

/* Inter-Thread communication */

#define RT_USING_SEMAPHORE
#define RT_USING_MUTEX
#define RT_USING_EVENT
#define RT_USING_MAILBOX
#define RT_USING_MESSAGEQUEUE

/* Memory Management */

#define RT_USING_MEMPOOL
#define RT_USING_SMALL_MEM
#define RT_USING_SMALL_MEM_AS_HEAP
#define RT_USING_HEAP
#define RT_USING_DEVICE
#define RT_USING_CONSOLE
#define RT_CONSOLEBUF_SIZE 128
#define RT_CONSOLE_DEVICE_NAME "uart0"
#define RT_VER_NUM 0x50100
#define RT_BACKTRACE_LEVEL_MAX_NR 32

/* RT-Thread Components */

#define RT_USING_COMPONENTS_INIT
#define RT_USING_USER_MAIN
#define RT_MAIN_THREAD_STACK_SIZE 2048
#define RT_MAIN_THREAD_PRIORITY 10

/* Device Drivers */

#define RT_USING_DEVICE_IPC
#define RT_UNAMED_PIPE_NUMBER 64
#define RT_USING_SERIAL
#define RT_USING_SERIAL_V1
#define RT_SERIAL_USING_DMA
#define RT_SERIAL_RB_BUFSZ 64
#define RT_USING_PIN

/* Using USB */


/* C/C++ and POSIX layer */

/* ISO-ANSI C layer */

/* Timezone and Daylight Saving Time */

#define RT_LIBC_USING_LIGHT_TZ_DST
#define RT_LIBC_TZ_DEFAULT_HOUR 8
#define RT_LIBC_TZ_DEFAULT_MIN 0
#define RT_LIBC_TZ_DEFAULT_SEC 0

/* POSIX (Portable Operating System Interface) layer */


/* Interprocess Communication (IPC) */


/* Socket is in the 'Network' category */


/* Network */


/* Memory protection */


/* Utilities */


/* RT-Thread Utestcases */


/* RT-Thread online packages */

/* IoT - internet of things */


/* Wi-Fi */

/* Marvell WiFi */


/* Wiced WiFi */


/* CYW43012 WiFi */


/* BL808 WiFi */


/* CYW43439 WiFi */


/* IoT Cloud */


/* security packages */


/* language packages */

/* JSON: JavaScript Object Notation, a lightweight data-interchange format */


/* XML: Extensible Markup Language */


/* multimedia packages */

/* LVGL: powerful and easy-to-use embedded GUI library */


/* u8g2: a monochrome graphic library */


/* tools packages */


/* system packages */

/* enhanced kernel services */


/* acceleration: Assembly language or algorithmic acceleration packages */


/* CMSIS: ARM Cortex-M Microcontroller Software Interface Standard */


/* Micrium: Micrium software products porting for RT-Thread */


/* peripheral libraries and drivers */

/* HAL & SDK Drivers */

/* STM32 HAL & SDK Drivers */


/* Kendryte SDK */


/* sensors drivers */


/* touch drivers */


/* AI packages */


/* Signal Processing and Control Algorithm Packages */


/* miscellaneous packages */

/* project laboratory */

/* samples: kernel and components samples */


/* entertainment: terminal games and other interesting software packages */


/* Arduino libraries */


/* Projects and Demos */


/* Sensors */


/* Display */


/* Timing */


/* Data Processing */


/* Data Storage */

/* Communication */


/* Device Control */


/* Other */


/* Signal IO */


/* Uncategorized */
#define __RT_KERNEL_SOURCE__
#define __RT_IPC_SOURCE__

/* Hardware Drivers Config */

#define SOC_SERIES_GD32F4xx
#define SOC_GD32470Z

/* Onboard Peripheral Drivers */

/* On-chip Peripheral Drivers */

#define BSP_USING_GPIO
#define BSP_USING_UART
#define BSP_USING_UART0

/* Board extended module Drivers */


#endif

除此之外,还需要添加一些全局宏定义------"RT_USING_LIBC, __CLK_TCK=RT_TICK_PER_SECOND, RTTHREAD,__STDC_LIMIT_MACROS, RT_USING_ARMLIBC"

至此,进行编译的话应该就能成功了。如果编译不成功的话,可以尝试通过错误打印查找原因,大部分都是因为文件导少了导多了、某个配置项没设置之类的。或者参考官方的例程,我的开发板例程在路径bsp\gd32\arm\gd32470z-lckfb下。

2.3 测试程序

我们也是简单写一个跑马灯程序,看看RT-Thread有没有移植成功。

cpp 复制代码
#include "gd32f4xx.h"
#include "rtthread.h"
#include "rtdevice.h"
#include "board.h"

#define DBG_TAG    "main"
#define DBG_LVL    DBG_INFO
#include "rtdbg.h"

#define LED1_PIN GET_PIN(E, 3)

int main(void)
{
    rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT);

	while (1) {
        rt_pin_write(LED1_PIN, PIN_HIGH);
        LOG_I("LED1 ON");
        rt_thread_mdelay(1000);
        rt_pin_write(LED1_PIN, PIN_LOW);
        LOG_I("LED1 OFF");
        rt_thread_mdelay(1000);
	}
}

RT-Thread自带一个调试子系统,感觉是模仿Easylogger的,需要导入rtdbg.h,还要定义DBG_TAG和DBG_LVL宏定义,前者是一个字符串标签,调试时会打印出来;后者是调试打印的等级。

这里我使用了pin子系统,这样定义GPIO管脚会方便很多,使用GET_PIN宏定义可以获取管脚,使用rt_pin_mode可以定义管脚,rt_pin_write可以设置管脚输出,感觉这个又是模仿Arduino的。

延时的话就调用rt_thread_mdelay即可,单位是毫秒。

细心的同学可能发现,为什么代码中没有创建线程这种操作?其实main函数就是一个线程。那是怎么做到的呢?这里简单分析一下源码,位于components.c文件。

RT-Thread在初始化系统的时候很巧妙地使用了MDK的Submain和Super$$main两个特性;前者是在main函数执行前插入一段代码,后者是跳转到main函数中。

cpp 复制代码
extern int $Super$$main(void);
/* re-define main function */
int $Sub$$main(void)
{
    rtthread_startup();
    return 0;
}

在main函数执行前,插入了rtthread_startup函数,用于初始化RT-Thread系统。

cpp 复制代码
int rtthread_startup(void)
{
#ifdef RT_USING_SMP
    rt_hw_spin_lock_init(&_cpus_lock);
#endif
    rt_hw_local_irq_disable();

    /* board level initialization
     * NOTE: please initialize heap inside board initialization.
     */
    rt_hw_board_init();

    /* show RT-Thread version */
    rt_show_version();

    /* timer system initialization */
    rt_system_timer_init();

    /* scheduler system initialization */
    rt_system_scheduler_init();

#ifdef RT_USING_SIGNALS
    /* signal system initialization */
    rt_system_signal_init();
#endif /* RT_USING_SIGNALS */

    /* create init_thread */
    rt_application_init();

    /* timer thread initialization */
    rt_system_timer_thread_init();

    /* idle thread initialization */
    rt_thread_idle_init();

#ifdef RT_USING_SMP
    rt_hw_spin_lock(&_cpus_lock);
#endif /* RT_USING_SMP */

    /* start scheduler */
    rt_system_scheduler_start();

    /* never reach here */
    return 0;
}

这个函数里面都是一些系统初始化的操作,重点在rt_application_init函数,main线程就是在这里面初始化的。

cpp 复制代码
static void main_thread_entry(void *parameter)
{
    extern int main(void);
    RT_UNUSED(parameter);

#ifdef RT_USING_COMPONENTS_INIT
    /* RT-Thread components initialization */
    rt_components_init();
#endif /* RT_USING_COMPONENTS_INIT */

#ifdef RT_USING_SMP
    rt_hw_secondary_cpu_up();
#endif /* RT_USING_SMP */
    /* invoke system main function */
#ifdef __ARMCC_VERSION
    {
        extern int $Super$$main(void);
        $Super$$main(); /* for ARMCC. */
    }
#elif defined(__ICCARM__) || defined(__GNUC__) || defined(__TASKING__) || defined(__TI_COMPILER_VERSION__)
    main();
#endif /* __ARMCC_VERSION */
}

void rt_application_init(void)
{
    rt_thread_t tid;

#ifdef RT_USING_HEAP
    tid = rt_thread_create("main", main_thread_entry, RT_NULL,
                           RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
    RT_ASSERT(tid != RT_NULL);
#else
    rt_err_t result;

    tid = &main_thread;
    result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,
                            main_thread_stack, sizeof(main_thread_stack), RT_MAIN_THREAD_PRIORITY, 20);
    RT_ASSERT(result == RT_EOK);

    /* if not define RT_USING_HEAP, using to eliminate the warning */
    (void)result;
#endif /* RT_USING_HEAP */

    rt_thread_startup(tid);
}

可以看到main线程的实现函数中,调用了Super$main,将代码跳转到了实际的main函数里面。

不得不感叹RT-Thread的骚操作!!!

2.4 运行结果

RT-Thread系统启动时会打印版本信息,接下来就是用户的打印,因为RT-Thread的调试子系统是默认使用彩色打印的,所以最好使用支持彩色调试打印的串口助手,像我使用的MobaXterm,不然会出现一些乱码。

移植成功的话就会看到板子上的LED灯闪烁了。

相关推荐
axuan1265123 分钟前
16.【NXP 号令者RT1052】开发——实战-FlexPWM 输出
单片机·嵌入式硬件·mcu
墨辰JC35 分钟前
基于STM32标准库的FreeRTOS移植与任务创建
数据库·stm32·嵌入式硬件·freertos
时光の尘1 小时前
【STM32】DMA超详细解析·入门级教程
stm32·单片机·嵌入式硬件·mcu·串口·dma·usart
chao1898441 小时前
基于TMS320F28069 DSP开发板实现RS485通信
单片机·嵌入式硬件
速易达网络1 小时前
C语言常见推理题
java·c语言·算法
沪漂的码农1 小时前
C语言队列与链表结合应用完整指南
c语言·链表
朱嘉鼎2 小时前
消费级MCU如何管理内存
单片机·嵌入式硬件
小龙报2 小时前
《算法通关指南:算法基础篇 --- 一维前缀和 — 1. 【模板】一维前缀和,2.最大子段和》
c语言·数据结构·c++·算法·职场和发展·创业创新·visual studio
R6bandito_3 小时前
STM32 HAL库原子操作编译问题解决指南
c语言·ide·经验分享·stm32·单片机·嵌入式硬件·mcu
Jerry丶Li3 小时前
三十、STM32的USART (串口发送+接收)
stm32·单片机·嵌入式硬件