前言
-
前面已经让 RT-Thread 进入了 entry 入口函数,并且 调整 链接脚本,自动初始化与 MSH shell 的符号已经预留, 进入了 RT-Thread 的初始化流程
-
接下来:从 内存管理、系统tick 定时器、适配串口 uart 驱动三个模块入手,让RT-Thread 真正运行起来
系统tick定时器
-
可以称之为 操作系统的心跳,一般是个周期性的定时器,比如 1ms 为周期,周期性的执行。
-
通过验证,
mps2-an385
支持 systick 定时器,简单配置后,就可以实现 系统 tick 定时器功能 -
修改完善
drv_common.c
#include <rtthread.h>
#include <board.h>
#include "CMSDK_CM3.h"
#include "system_CMSDK_CM3.h"
static uint32_t _systick_ms = 1;
/**
* 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 configuration */
void rt_hw_systick_init(void)
{
SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);
NVIC_SetPriority(SysTick_IRQn, 0xFF);
_systick_ms = 1000u / RT_TICK_PER_SECOND;
if(_systick_ms == 0)
_systick_ms = 1;
}
-
rt_hw_systick_init
当前被board.c
中的rt_hw_board_init
调用,而rt_hw_board_init
又被 RT-Threadrtthread_startup
调用,rtthread_startup
被 RT-Thread 入口函数entry
调用,这个entry
又被 启动文件Reset_Handler
调用,Reset_Handler
是 MCU 上电执行的函数。 -
初始化
rt_hw_systick_init
后,VS Code gdb 调试,发现可以周期性进入SysTick_Handler
,也就是 systick 定时器的中断处理函数,在这个函数中,执行rt_tick_increase
,基于时间片的系统调度、系统定时与延时等,都依赖 系统 tick 定时器,也就是移植 RT-Thread,必须有周期性 tick 定时器
系统内存管理
mps2-an385
的 RAM 4MB,当前内存配置在 board.h 中实现
#ifndef __BOARD_H__
#define __BOARD_H__
#include <rtconfig.h>
#if defined(__CC_ARM)
extern int Image$$RW_IRAM1$$ZI$$Limit;
#define HEAP_BEGIN ((void*)&Image$$RW_IRAM1$$ZI$$Limit)
#elif defined(__GNUC__)
extern int __bss_end__;
#define HEAP_BEGIN ((void*)&__bss_end__)
#endif
#define HEAP_END (void*)(0x20000000 + 4 * 1024 * 1024)
void rt_hw_board_init(void);
void rt_hw_systick_init(void);
#endif
mps2-an385
qemu 不需要配置系统的时钟,所以board.c
主要用于实现rt_hw_board_init
,初始化内存、串口、系统 tick 定时器,并设置 MSH shell 串口终端
#include <rthw.h>
#include <rtthread.h>
#include "board.h"
#include "drv_uart.h"
void idle_wfi(void)
{
asm volatile ("wfi");
}
/**
* This function will initialize board
*/
void rt_hw_board_init(void)
{
/* initialize system heap */
rt_system_heap_init(HEAP_BEGIN, HEAP_END);
/* initialize hardware interrupt */
rt_hw_systick_init();
rt_hw_uart_init();
rt_components_board_init();
rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
rt_thread_idle_sethook(idle_wfi);
}
串口驱动适配
-
RT-Thread 具有 MSH shell 组件,这个组件在程序调试中非常的有用,合理的利用 shell,可以实现一些复杂的操作
-
mps2-an385
的串口配置并不复杂,不像STM32 那样有各种配置,所以当前简单的适配了一下,实现了串口中断接收、串口发送,即可让 MSH shell 串口正常工作 -
当前初步验证,
mps2-an385
uart0
可以正常用于 MSH shell -
drv_uart.c
适配如下:
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#include "board.h"
#include "CMSDK_CM3.h"
enum
{
#ifdef BSP_USING_UART0
UART0_INDEX,
#endif
#ifdef BSP_USING_UART1
UART1_INDEX,
#endif
};
/* qemu uart dirver class */
struct uart_instance
{
const char *name;
CMSDK_UART_TypeDef *handle;
IRQn_Type irq_num;
int uart_index;
struct rt_serial_device serial;
};
#if defined(BSP_USING_UART0)
#ifndef UART0_CONFIG
#define UART0_CONFIG \
{ \
.name = "uart0", \
.handle = CMSDK_UART0, \
.irq_num = UART0RX_IRQn, \
.uart_index = UART0_INDEX, \
}
#endif /* UART0_CONFIG */
#endif /* BSP_USING_UART0 */
#if defined(BSP_USING_UART1)
#ifndef UART1_CONFIG
#define UART1_CONFIG \
{ \
.name = "uart1", \
.handle = CMSDK_UART1, \
.irq_num = UART1RX_IRQn, \
.uart_index = UART1_INDEX, \
}
#endif /* UART1_CONFIG */
#endif /* BSP_USING_UART1 */
static struct uart_instance uart_obj[] =
{
#ifdef BSP_USING_UART0
UART0_CONFIG,
#endif
#ifdef BSP_USING_UART1
UART1_CONFIG,
#endif
};
static void uart_isr(struct rt_serial_device *serial)
{
/* UART in mode Receiver */
rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_IND);
}
void UART0RX_Handler(void)
{
#ifdef BSP_USING_UART0
uint32_t irq_status = 0x00;
/* enter interrupt */
rt_interrupt_enter();
uart_isr(&(uart_obj[UART0_INDEX].serial));
irq_status = uart_obj[UART0_INDEX].handle->INTCLEAR;
uart_obj[UART0_INDEX].handle->INTCLEAR = irq_status;
/* leave interrupt */
rt_interrupt_leave();
#endif
}
void UART1RX_Handler(void)
{
#ifdef BSP_USING_UART1
uint32_t irq_status = 0x00;
/* enter interrupt */
rt_interrupt_enter();
uart_isr(&(uart_obj[UART1_INDEX].serial));
irq_status = uart_obj[UART1_INDEX].handle->INTCLEAR;
uart_obj[UART1_INDEX].handle->INTCLEAR = irq_status;
/* leave interrupt */
rt_interrupt_leave();
#endif
}
static rt_err_t uart_configure(struct rt_serial_device *serial, struct serial_configure *cfg)
{
struct uart_instance *instance;
RT_ASSERT(serial != RT_NULL);
instance = (struct uart_instance *)serial->parent.user_data;
uart_obj[instance->uart_index].handle->BAUDDIV = 16;
uart_obj[instance->uart_index].handle->CTRL = CMSDK_UART_CTRL_RXIRQEN_Msk | CMSDK_UART_CTRL_RXEN_Msk | CMSDK_UART_CTRL_TXEN_Msk;
NVIC_EnableIRQ(uart_obj[instance->uart_index].irq_num);
uart_obj[instance->uart_index].handle->STATE = 0;
return RT_EOK;
}
static rt_err_t uart_control(struct rt_serial_device *serial, int cmd, void *arg)
{
struct uart_instance *instance;
RT_ASSERT(serial != RT_NULL);
instance = (struct uart_instance *)serial->parent.user_data;
switch (cmd)
{
case RT_DEVICE_CTRL_CLR_INT:
/* disable rx irq */
instance->handle->CTRL &= ~CMSDK_UART_CTRL_RXIRQEN_Msk;
break;
case RT_DEVICE_CTRL_SET_INT:
/* enable rx irq */
instance->handle->CTRL |= CMSDK_UART_CTRL_RXIRQEN_Msk;
break;
}
return RT_EOK;
}
static int uart_putc(struct rt_serial_device *serial, char c)
{
struct uart_instance *instance;
RT_ASSERT(serial != RT_NULL);
instance = (struct uart_instance *)serial->parent.user_data;
instance->handle->DATA = c;
return 1;
}
static int uart_getc(struct rt_serial_device *serial)
{
int ch;
uint32_t state = 0;
struct uart_instance *instance;
RT_ASSERT(serial != RT_NULL);
instance = (struct uart_instance *)serial->parent.user_data;
ch = -1;
if (!instance)
return ch;
state = instance->handle->STATE;
if (state)
{
ch = instance->handle->DATA & 0xff;
instance->handle->STATE = 0;
}
return ch;
}
static const struct rt_uart_ops _uart_ops =
{
uart_configure,
uart_control,
uart_putc,
uart_getc,
};
int rt_hw_uart_init(void)
{
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
rt_err_t result = 0;
for (rt_size_t i = 0; i < sizeof(uart_obj) / sizeof(struct uart_instance); i++)
{
/* init UART object */
uart_obj[i].serial.ops = &_uart_ops;
uart_obj[i].serial.config = config;
/* register UART device */
result = rt_hw_serial_register(&uart_obj[i].serial, uart_obj[i].name,
RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
&uart_obj[i]);
RT_ASSERT(result == RT_EOK);
}
return result;
}
- 串口适配的主要流程: 定义串口设备结构,实现
uart_putc
,uart_getc
,uart_configure
,通过rt_hw_serial_register
注册串口设备,编写 串口中断的处理函数
系统运行
- 以上适配了 内存、系统 tick 定时器与 MSH shell 串口后,通过
scons --menuconfig
配置 MSH shell 串口为uart0
,RT-Thread 运行起来了
./qemu.sh
运行信息
-
以上,说明RT-Thread qemu mps2-an385 bsp 制作初步完成,当前初步验证,无法支持文件系统,并且其他的外设欠缺资料,因为移植宣告完成。
-
可以通过 VS Code gdb 调试,熟悉 RT-Thread 系统调用、内存分配、测试验证各个 RT-Thread 功能模块
小结
- 本篇通过 bsp 适配 内存管理、串口驱动、系统 tick 定时器,让 RT-Thread 跑起来,qemu mps2-an385 bsp 在 RT-Thread 上移植适配完成。