rt_thread nano GD32F407移植
1、简介
RT-Thread Nano 是一个极简版的硬实时内核,它是由 C 语言开发,采用面向对象的编程思维,具有良好的代码风格,是一款可裁剪的、抢占式实时多任务的 RTOS。其内存资源占用极小,功能包括任务处理、软件定时器、信号量、邮箱和实时调度等相对完整的实时操作系统特性。适用于家电、消费电子、医疗设备、工控等领域大量使用的 32 位 ARM 入门级 MCU 的场合。
RT-Thread Nano版本与其他RTOS的区别在于新增加了一个可裁剪的FinSH控制台,主要是通过串口实现的类似linux命令行终端的功能。
2、RT-Thread Nano移植
移植目结构
在 rtthread-nano 源码中,与移植相关的文件位于下图中有颜色标记的路径下(黄色表示 libcpu 移植相关的文件,绿色部分表示板级移植相关的文件):
移植步骤
添加文件
Cortex-M 芯片内核移植代码:
context_rvds.s
cpuport.c
Kernel 文件包括
clock.c
components.c
device.c
idle.c
ipc.c
irq.c
kservice.c
mem.c
mempool.c
object.c
scheduler.c
thread.c
timer.c
配置文件:
board.c
rtconfig.h
屏蔽中断与异常处理
RT-Thread 会接管异常处理函数 HardFault_Handler() 和悬挂处理函数 PendSV_Handler() ,这两个函数已由 RT-Thread 实现,所以需要删除工程里中断服务例程文件中的这两个函数,避免在编译时产生重复定义。如果此时对工程进行编译,没有出现函数重复定义的错误,则不用做修改。
系统时钟配置
需要在 board.c 中实现 系统时钟配置 (为 MCU、外设提供工作时钟)与 os tick 的配置 (为操作系统提供心跳 / 节拍)。
如下代码所示,用户需要在 board.c 文件中系统初始化和 OS Tick 的配置,用户需在 timer 定时器中断服务函数调用 rt_os_tick_callback function,cortex-m 架构使用 SysTick_Handler()
内存堆初始化
系统内存堆的初始化在 board.c 中的 rt_hw_board_init() 函数中完成,内存堆功能是否使用取决于宏 RT_USING_HEAP 是否开启,RT-Thread Nano 默认不开启内存堆功能,这样可以保持一个较小的体积,不用为内存堆开辟空间。
开启系统 heap 将可以使用动态内存功能,如使用 rt_malloc、rt_free 以及各种系统动态创建对象的 API。若需要使用系统内存堆功能,则打开 RT_USING_HEAP 宏定义即可,此时内存堆初始化函数 rt_system_heap_init() 将被调用,如下所示:
初始化内存堆需要堆的起始地址与结束地址这两个参数,系统中默认使用数组作为 heap,并获取了 heap 的起始地址与结束地址,该数组大小可手动更改,如下所示:
3、FinSH组件移植
kprintf()移植
void rt_hw_console_output(const char *str)
{
rt_enter_critical();
while(*str!='\0')
{
if (*str == '\n')
{
usart_data_transmit(USART1, '\r');
while(usart_flag_get(USART1, USART_FLAG_TC)== RESET);
}
usart_data_transmit(USART1, *(str++));
while(usart_flag_get(USART1, USART_FLAG_TC)== RESET);
}
rt_exit_critical();
}
Finsh组件移植
char rt_hw_console_getchar(void)
{
int ch = -1;
if(usart_flag_get(USART1, USART_FLAG_RBNE) != RESET)
{
ch = (int)usart_data_receive(USART1);
usart_flag_clear(USART1, USART_FLAG_RBNE);
}
else
{
if(usart_flag_get(USART1, USART_FLAG_ORERR) != RESET)
{
usart_flag_clear(USART1, USART_FLAG_ORERR);
}
rt_thread_mdelay(10);
}
return ch;
}
移植成功后打开串口输入help 可以看见如下信息表示移植成功
4、FinSH调试技巧
1、函数初始化功能
rt_thread的初始化有两种方式
方法一:将函数放入rt_hw_board_init()函数
int rtthread_startup(void)
{
rt_hw_interrupt_disable();
rt_hw_board_init();
rt_show_version();
rt_system_timer_init();
rt_system_scheduler_init();
#ifdef RT_USING_SIGNALS
rt_system_signal_init();
#endif
rt_application_init();
rt_system_timer_thread_init();
rt_thread_idle_init();
rt_system_scheduler_start();
return 0;
}
方法二: 使用宏 INIT_BOARD_EXPORT() 进行自动初始化,不需要显式调用 (推荐使用)
#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")
#define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2")
#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3")
#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4")
#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5")
#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6")
推荐使用这个函数,可以有效的降低模块之间的耦合性
2、串口调试功能
添加自定义命令
除了 FinSH 自带的命令,FinSH 还也提供了多个宏接口来导出自定义命令,导出的命令可以直接在FinSH 中执行。
MSH_CMD_EXPORT(name, desc);
如下添加两个控制led打开和关闭的finsh命令
void LedpOn(void)
{
gpio_bit_set(GPIOF,GPIO_PIN_0);
}
MSH_CMD_EXPORT(LedpOn, Control Beep On);
void LedpOff(void)
{
gpio_bit_reset(GPIOF,GPIO_PIN_0);
}
MSH_CMD_EXPORT(LedpOff, Control Beep Off);
打开终端就可以看见自己添加的命令了
d打开和关闭的finsh命令
void LedpOn(void)
{
gpio_bit_set(GPIOF,GPIO_PIN_0);
}
MSH_CMD_EXPORT(LedpOn, Control Beep On);
void LedpOff(void)
{
gpio_bit_reset(GPIOF,GPIO_PIN_0);
}
MSH_CMD_EXPORT(LedpOff, Control Beep Off);
打开终端就可以看见自己添加的命令了
|