前言
最近做的一个小项目需要使用到RT-Thread,借此机会了解了一下RT-Thread和它提供的BSP框架,并简单分析移植方法。
关于RT-Thread和它的BSP,官方已经提供了比较详尽的文档,并且目前已经有部分厂家(比如最常见的STM32)的BSP模版可供使用:
BSP工程目录总览
本文以笔者手上的weact的stm32h750开发板BSP为例进行分析
- Applications:应用层代码
- Compiler:ARM的C语言库
- DeviceDrivers:RT-Thread的Device框架驱动程序
- Drivers:包括Device框架中各种设备在STM32外设上的实现以及开发板上外设的驱动程序
- FAL,Filesystem,Finsh:都是一些RT-Thread的可选组件
- Kernel:RT-Thread内核文件
- libcpu:RT-Thread内核移植需要进行实现的API和一些与单片机内核相关的函数
- Libraries:HAL库
如何让内核在你的芯片上跑起来
内核的移植首先是拷贝操作系统内核源码,然后对中断使能,上下文切换等依赖于具体单片机内核的功能进行实现
操作系统源码
这部分代码放在rt-thread仓库的src目录下,对应的头文件在include目录下。注意到这堆文件都加了个编译预处理选项:
找了一下发现这个宏定义出现在了include/rtsched.h里:
/**
* NOTE: user should NEVER use these APIs directly. See rt_thread_.* or IPC
* methods instead.
*/
#if defined(__RT_KERNEL_SOURCE__) || defined(__RT_IPC_SOURCE__)
/* thread initialization and startup routine */
void rt_sched_thread_init_ctx(struct rt_thread *thread, rt_uint32_t tick, rt_uint8_t priority);
void rt_sched_thread_init_priv(struct rt_thread *thread, rt_uint32_t tick, rt_uint8_t priority);
void rt_sched_thread_startup(struct rt_thread *thread);
/* scheduler related routine */
void rt_sched_post_ctx_switch(struct rt_thread *thread);
rt_err_t rt_sched_tick_increase(void);
/* thread status operation */
rt_uint8_t rt_sched_thread_get_stat(struct rt_thread *thread);
rt_uint8_t rt_sched_thread_get_curr_prio(struct rt_thread *thread);
rt_uint8_t rt_sched_thread_get_init_prio(struct rt_thread *thread);
rt_err_t rt_sched_thread_yield(struct rt_thread *thread);
rt_err_t rt_sched_thread_close(struct rt_thread *thread);
rt_err_t rt_sched_thread_ready(struct rt_thread *thread);
rt_err_t rt_sched_thread_suspend(struct rt_thread *thread, rt_sched_lock_level_t level);
rt_err_t rt_sched_thread_change_priority(struct rt_thread *thread, rt_uint8_t priority);
rt_err_t rt_sched_thread_bind_cpu(struct rt_thread *thread, int cpu);
rt_uint8_t rt_sched_thread_is_suspended(struct rt_thread *thread);
rt_err_t rt_sched_thread_timer_stop(struct rt_thread *thread);
rt_err_t rt_sched_thread_timer_start(struct rt_thread *thread);
void rt_sched_insert_thread(struct rt_thread *thread);
void rt_sched_remove_thread(struct rt_thread *thread);
#endif /* defined(__RT_KERNEL_SOURCE__) || defined(__RT_IPC_SOURCE__) */
这个文件主要是一些线程管理相关的API,可以看出来这个宏定义是为了确保这些API只能由内核调用,防止用户在奇怪的地方调用。如果内核文件没加上这个宏定义会报符号未定义错误(笔者自己踩的坑),如果自己手动移植要注意这一点。
内核API实现
虽然RT-Thread的内核教程说要自己实现开关中断之类的函数,但是它已经针对一些常见单片机内核进行了实现,比如说h750的cortex-m7内核。这些API实现在rt-thread仓库的libcpu
目录下,主要在context_rvds.S
和cpuport.c
这两个文件里。
经过这两步RT-Thread内核已经可以成功编译并且在你的芯片上运行起来了,如果你只需要使用到这些功能的话其实可以选择RT-Thread的阉割nano版本,nano版本提供了完整的内核功能和一个不需要Device框架(后面会提到)版本的Finsh组件(就是一个命令行工具)
Device框架
嵌入式系统经常需要用到各种片上或者片外的设备,RT-Thread把硬件上的外设进行了抽象,由此诞生出了Device框架,也许是为了方便应用在不同硬件平台之间的移植?RT-Thread中的内核对象和设备等都采用了类似c++的类继承思想,常用的PIN设备,ADC设备,UART设备等都是在rt_device
这个基类上派生出来的,当然你也可以创建自己的设备。
每种设备需要实现的API在官方文档中都有详细说明,以最简单的PIN设备为例,下面是在drv_gpio.c
中实现的一个PIN设备硬件初始化函数:
int rt_hw_pin_init(void)
{
#if defined(__HAL_RCC_GPIOA_CLK_ENABLE)
__HAL_RCC_GPIOA_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOB_CLK_ENABLE)
__HAL_RCC_GPIOB_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOC_CLK_ENABLE)
__HAL_RCC_GPIOC_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOD_CLK_ENABLE)
__HAL_RCC_GPIOD_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOE_CLK_ENABLE)
__HAL_RCC_GPIOE_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOF_CLK_ENABLE)
__HAL_RCC_GPIOF_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOG_CLK_ENABLE)
#ifdef SOC_SERIES_STM32L4
HAL_PWREx_EnableVddIO2();
#endif
__HAL_RCC_GPIOG_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOH_CLK_ENABLE)
__HAL_RCC_GPIOH_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOI_CLK_ENABLE)
__HAL_RCC_GPIOI_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOJ_CLK_ENABLE)
__HAL_RCC_GPIOJ_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOK_CLK_ENABLE)
__HAL_RCC_GPIOK_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOM_CLK_ENABLE)
__HAL_RCC_GPIOM_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPION_CLK_ENABLE)
__HAL_RCC_GPION_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOO_CLK_ENABLE)
__HAL_RCC_GPIOO_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOP_CLK_ENABLE)
__HAL_RCC_GPIOP_CLK_ENABLE();
#endif
return rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL);
}
该函数在rt_hw_board_init()
调用(RT-Thread初始化过程可以参见官方文档),可以看到它的硬件初始化就是把用到的I/O口时钟打开,然后注册一个PIN设备, rt_device_pin_register
中的第二个参数就是一堆API的函数指针:
static const struct rt_pin_ops _stm32_pin_ops =
{
stm32_pin_mode,
stm32_pin_write,
stm32_pin_read,
stm32_pin_attach_irq,
stm32_pin_dettach_irq,
stm32_pin_irq_enable,
stm32_pin_get,
};
其他类型设备的初始化也大体遵循这个顺序,先把硬件初始化好,然后把相关的API注册上去
CUBEMX配置
经常用STM32的朋友都知道,我们可以用CUBEMX配置各种初始化配置。RT-Thread的Device框架写了很多现成的HAL库外设初始化函数,在rt-thread/bsp/stm32/libraries
目录下,因此不需要用CUBEMX进行外设的配置,但是时钟树和外设的引脚配置还是需要用到CUBEMX,可以看到BSP目录底下开了一个CUBEMX工程:
这个工程里面其实就只有它生成的那个msp文件有用
注意这个选项不要勾,这样所有的初始化函数都会生成到一个c文件里,在外面的工程中包含这个c文件即可
至于时钟树的初始化函数,CUBEMX会把它生成到main.c
里面,需要把它复制下来粘到工程里的board.c
文件里
KConfig文件
RT-Thread提供了一个ENV工具方便对不需要的设备或组件进行裁剪,其实就是自动生成一个rtconfig.h
文件,里面有一堆宏定义,用户在初始化函数中用#ifdef
进行条件编译实现裁剪,下面是该BSP的KConfig文件:
menu "Hardware Drivers Config"
config SOC_STM32H750VBT6
bool
select SOC_SERIES_STM32H7
select RT_USING_COMPONENTS_INIT
select RT_USING_USER_MAIN
default y
menu "Onboard Peripheral Drivers"
config BSP_USING_SPI_FLASH
bool "Enable SPI FLASH (W25Q64 spi1)"
select BSP_USING_SPI
select BSP_USING_SPI1
select RT_USING_DFS
select PKG_USING_LITTLEFS
select RT_USING_MTD_NOR
select RT_USING_FAL
select FAL_USING_SFUD_PORT
select RT_USING_SFUD
select RT_SFUD_USING_SFDP
default n
config BSP_USING_QSPI_FLASH
bool "Enable QSPI FLASH (W25Q64)"
select BSP_USING_QSPI
select FAL_USING_SFUD_PORT
select RT_USING_SFUD
select RT_SFUD_USING_QSPI
default n
config BSP_USING_LCD_SPI
bool "Enable 0.96' TFT-LCD(ST7735S)"
select BSP_USING_GPIO
select BSP_USING_SPI
select BSP_USING_SPI4
# select BSP_SPI4_TX_USING_DMA
select BSP_USING_PWM
select BSP_USING_PWM1
select BSP_USING_PWM1_CH2
default n
if BSP_USING_LCD_SPI
choice
prompt "choice back light"
default LCD_BACKLIGHT_USING_GPIO
config LCD_BACKLIGHT_USING_PWM
bool "LCD_BACKLIGHT_USING_PWM(tim1_ch2 pwm1)"
config LCD_BACKLIGHT_USING_GPIO
bool "LCD_BACKLIGHT_USING_GPIO(PE10)"
endchoice
endif
endmenu
menu "On-chip Peripheral Drivers"
config BSP_USING_GPIO
bool "Enable GPIO"
select RT_USING_PIN
default y
menuconfig BSP_USING_UART
bool "Enable UART"
default y
select RT_USING_SERIAL
if BSP_USING_UART
config BSP_USING_UART1
bool "Enable UART1"
default y
config BSP_UART1_RX_USING_DMA
bool "Enable UART1 RX DMA"
depends on BSP_USING_UART1 && RT_SERIAL_USING_DMA
default n
endif
config BSP_USING_QSPI
bool "Enable QSPI BUS"
select RT_USING_QSPI
select RT_USING_SPI
default n
menuconfig BSP_USING_SPI
bool "Enable SPI BUS"
default n
select RT_USING_SPI
if BSP_USING_SPI
config BSP_USING_SPI1
bool "Enable SPI1 BUS"
default n
config BSP_USING_SPI4
bool "Enable SPI4 BUS"
default n
endif
menuconfig BSP_USING_I2C1
bool "Enable I2C1 BUS (software simulation)"
default n
select RT_USING_I2C
select RT_USING_I2C_BITOPS
select RT_USING_PIN
if BSP_USING_I2C1
config BSP_I2C1_SCL_PIN
int "i2c1 scl pin number"
range 1 216
default 135
config BSP_I2C1_SDA_PIN
int "I2C1 sda pin number"
range 1 216
default 95
endif
menuconfig BSP_USING_PWM
bool "Enable pwm"
default n
select RT_USING_PWM
if BSP_USING_PWM
menuconfig BSP_USING_PWM1
bool "Enable timer1 output pwm"
default n
if BSP_USING_PWM1
config BSP_USING_PWM1_CH1
bool "Enable PWM1 channel1"
default n
config BSP_USING_PWM1_CH2
bool "Enable PWM1 channel2"
default n
config BSP_USING_PWM1_CH3
bool "Enable PWM1 channel3"
default n
config BSP_USING_PWM1_CH4
bool "Enable PWM1 channel4"
default n
endif
endif
menuconfig BSP_USING_ADC
bool "Enable ADC"
default n
select RT_USING_ADC
if BSP_USING_ADC
config BSP_USING_ADC1
bool "Enable ADC1"
default n
config BSP_USING_ADC2
bool "Enable ADC2"
default n
config BSP_USING_ADC3
bool "Enable ADC3"
default n
endif
config BSP_USING_ON_CHIP_FLASH
bool "Enable on-chip FLASH"
default n
config BSP_USING_SDIO
bool "Enable SDIO"
select RT_USING_SDIO
select RT_USING_DFS
default n
config BSP_USING_USBD
bool "Enable OTGHS as USB device"
select RT_USING_USB_DEVICE
select BSP_USBD_TYPE_HS
select BSP_USBD_SPEED_HS
select BSP_USBD_PHY_ULPI
default n
source "$BSP_DIR/../libraries/HAL_Drivers/drivers/Kconfig"
endmenu
menu "Board extended module Drivers"
endmenu
endmenu
关于详细的语法描述可以参考KConfig语法及示例,配置好该文件后在BSP目录下打开ENV工具就可以看到一个这样的菜单:
在菜单里面勾选或者取消勾选,对应的宏定义就会在生成的rtconfig.h
头文件里面出现或者消失,也可以进行一些参数的配置,最后在工程中包含该头文件即可