Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
学习笔记
新手学习
RT-Thread启动流程以及开发板支持
发布于 2023-05-07 04:57:11 浏览:783
订阅该版
[tocm] 本篇文章讲解RT-Thread的启动流程,并从两个角度来整理总结RT-Thread如何支持不同开发板(bsp支持和cpu支持)。 代码示例都来自[RT-Thread Simulator 例程](https://gitee.com/rtthread/docs-online/raw/master/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/rtthread_simulator_v0.1.0.7z)。 # 启动流程 一个开发板上的RT-Thread的启动流程可能是首先从bsp当中链接脚本指定的`startup_xxx.S`中的入口函数(ENTRY)或者复位异常处理函数(ResetHandler)开始运行,这部分我们在讲bsp支持时会详细讲解。 之后跳入`entry`函数(GCC,使用不同编译器会进入不同的函数)执行,这里也可以跳入用户自己的main函数,但是需要用户自己完成`rtthread_startup`的工作。 这个函数十分简单,首先先关中断(关中断操作由cpu支持部分提供),然后进入RT-Thread的全局初始化中。 ```c #if defined (__CC_ARM) extern int $Super$$main(void); /* re-define main function */ int $Sub$$main(void) { rt_hw_interrupt_disable(); rtthread_startup(); return 0; } #elif defined(__ICCARM__) extern int main(void); /* __low_level_init will auto called by IAR cstartup */ extern void __iar_data_init3(void); int __low_level_init(void) { // call IAR table copy function. __iar_data_init3(); rt_hw_interrupt_disable(); rtthread_startup(); return 0; } #elif defined(__GNUC__) extern int main(void); /* Add -eentry to arm-none-eabi-gcc argument */ int entry(void) { rt_hw_interrupt_disable(); rtthread_startup(); return 0; } #endif ``` `rtthread_startup`是启动流程当中的关键,首先`rtthread_startup`会先调用`rt_hw_board_init`,这个函数也由bsp支持部分提供,一般来说主要完成例如初始化中断向量表、系统时钟的初始化,设置输出控制台,同时初始化系统堆内存。 紧接着会调用`rt_show_version`打印RT-Thread内核的系统版本信息,其中主要是利用控制台(`rt_printf`)进行输出,通常来说是用户在bsp支持中提供串口的注册来实现的。以[RT-Thread Simulator 例程](https://gitee.com/rtthread/docs-online/raw/master/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/rtthread_simulator_v0.1.0.7z)来说,会通过设备驱动一路调用到bsp支持部分提供的串口输出。 ```c void rt_kprintf(const char *fmt, ...) -> rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) -> static rt_size_t rt_serial_write(struct rt_device *dev, rt_off_t pos, const void *buffer, rt_size_t size) -> rt_inline int _serial_poll_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length) -> - static int stm32_putc(struct rt_serial_device *serial, char c) ``` 然后会调用`rt_system_timer_init`初始化系统定时器链表,这个函数比较简单,主要就是数据结构进行初始化。 紧接着会调用`rt_system_scheduler_init`初始化RT-Thread系统调度器相关的数据结构: 1. 线程优先级链表:每一个优先级对应一个链表,通过`rt_thread`结构中的`tlist`成员来进行相同优先级线程的连接 2. 当前线程优先级 3. 当前线程控制块 4. `rt_thread_ready_priority_group`中的每一位代表1个优先级,该位为1表示该优先级下有就绪线程,该位为0表示该优先级下没有就绪线程 5. 僵尸线程链表 ```c void rt_system_scheduler_init(void) { register rt_base_t offset; rt_scheduler_lock_nest = 0; RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("start scheduler: max priority 0x%02x\n", RT_THREAD_PRIORITY_MAX)); for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++) { rt_list_init(&rt_thread_priority_table[offset]); } rt_current_priority = RT_THREAD_PRIORITY_MAX - 1; rt_current_thread = RT_NULL; /* initialize ready priority group */ rt_thread_ready_priority_group = 0; #if RT_THREAD_PRIORITY_MAX > 32 /* initialize ready table */ rt_memset(rt_thread_ready_table, 0, sizeof(rt_thread_ready_table)); #endif /* initialize thread defunct */ rt_list_init(&rt_thread_defunct); } ``` 接下来会调用`rt_application_init`初始化一个main主线程(并不会马上运行),主要完成组件的初始化以及多核的处理,最后跳入用户的`main`函数 完成组件初始化的实现与`rt_components_board_init`类似,在BSP支持部分讲解。 ```c 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_stack, sizeof(main_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_thread_startup(tid); } void main_thread_entry(void *parameter) { extern int main(void); extern int $Super$$main(void); /* RT-Thread components initialization */ rt_components_init(); /* invoke system main function */ #if defined (__CC_ARM) $Super$$main(); /* for ARMCC. */ #elif defined(__ICCARM__) || defined(__GNUC__) main(); #endif } void rt_components_init(void) { #if RT_DEBUG_INIT int result; const struct rt_init_desc *desc; rt_kprintf("do components intialization.\n"); for (desc = &__rt_init_desc_rti_board_end; desc < &__rt_init_desc_rti_end; desc ++) { rt_kprintf("initialize %s", desc->fn_name); result = desc->fn(); rt_kprintf(":%d done\n", result); } #else const init_fn_t *fn_ptr; for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++) { (*fn_ptr)(); } #endif } ``` 然后,`rt_system_timer_thread_init`主要是初始化软件定时器的列表,并且创建软件定时器线程。而`rt_thread_idle_init`创建空闲线程,在系统没有任何用户线程调度的时候,就会被调度起来,这个空闲线程主要是检查系统有没有已经消亡的线程,如果有,则把消亡线程的资源进行回收,如果系统使能了电源管理,则会让系统进行低功耗模式。 最后通过`rt_system_scheduler_start`开启调度器。 --- # BSP支持 首先需要提供`startup_xxx.S`类似的启动文件,一般来说可能包含中断向量表以及默认的中断服务函数,以及选择入口函数,一般可能为`_start`或者`ResetHandler`。 ```asm AREA RESET, DATA, READONLY EXPORT __Vectors EXPORT __Vectors_End EXPORT __Vectors_Size __Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler DCD NMI_Handler ; NMI Handler DCD HardFault_Handler ; Hard Fault Handler DCD MemManage_Handler ; MPU Fault Handler DCD BusFault_Handler ; Bus Fault Handler DCD UsageFault_Handler ; Usage Fault Handler DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD SVC_Handler ; SVCall Handler DCD DebugMon_Handler ; Debug Monitor Handler DCD 0 ; Reserved DCD PendSV_Handler ; PendSV Handler DCD SysTick_Handler ; SysTick Handler ; External Interrupts DCD WWDG_IRQHandler ; Window Watchdog DCD PVD_IRQHandler ; PVD through EXTI Line detect DCD TAMPER_IRQHandler ; Tamper DCD RTC_IRQHandler ; RTC DCD FLASH_IRQHandler ; Flash DCD RCC_IRQHandler ; RCC DCD EXTI0_IRQHandler ; EXTI Line 0 DCD EXTI1_IRQHandler ; EXTI Line 1 DCD EXTI2_IRQHandler ; EXTI Line 2 DCD EXTI3_IRQHandler ; EXTI Line 3 DCD EXTI4_IRQHandler ; EXTI Line 4 DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1 DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2 DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3 DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4 DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5 DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6 DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7 DCD ADC1_2_IRQHandler ; ADC1_2 DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0 DCD CAN1_RX1_IRQHandler ; CAN1 RX1 DCD CAN1_SCE_IRQHandler ; CAN1 SCE DCD EXTI9_5_IRQHandler ; EXTI Line 9..5 DCD TIM1_BRK_IRQHandler ; TIM1 Break DCD TIM1_UP_IRQHandler ; TIM1 Update DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare DCD TIM2_IRQHandler ; TIM2 DCD TIM3_IRQHandler ; TIM3 DCD 0 ; Reserved DCD I2C1_EV_IRQHandler ; I2C1 Event DCD I2C1_ER_IRQHandler ; I2C1 Error DCD 0 ; Reserved DCD 0 ; Reserved DCD SPI1_IRQHandler ; SPI1 DCD 0 ; Reserved DCD USART1_IRQHandler ; USART1 DCD USART2_IRQHandler ; USART2 DCD 0 ; Reserved DCD EXTI15_10_IRQHandler ; EXTI Line 15..10 DCD RTC_Alarm_IRQHandler ; RTC Alarm through EXTI Line DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend __Vectors_End __Vectors_Size EQU __Vectors_End - __Vectors AREA |.text|, CODE, READONLY ; Reset handler routine Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT __main IMPORT SystemInit LDR R0, =SystemInit BLX R0 LDR R0, =__main BX R0 ENDP ``` bsp支持主要提供对开发板的硬件初始化(通过`rt_hw_board_init`向上提供接口)以及各类外设的驱动。 以[RT-Thread Simulator 例程](https://gitee.com/rtthread/docs-online/raw/master/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/rtthread_simulator_v0.1.0.7z)为例:`rt_hw_board_init`中主要完成HAL库的初始化、时钟配置和RT-Thread系统堆的初始化,以及`rt_components_board_init`会完成硬件的初始化。 ```c #define RT_DEBUG_INIT 0 /** * RT-Thread Components Initialization for board */ void rt_components_board_init(void) { #if RT_DEBUG_INIT int result; const struct rt_init_desc *desc; for (desc = &__rt_init_desc_rti_board_start; desc < &__rt_init_desc_rti_board_end; desc ++) { rt_kprintf("initialize %s", desc->fn_name); result = desc->fn(); rt_kprintf(":%d done\n", result); } #else const init_fn_t *fn_ptr; for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++) { (*fn_ptr)(); } #endif } ``` - `RT_USED`:`__attribute__((used))`,标识不允许编译器进行优化 - `init_fn_t`:`typedef int (*init_fn_t)(void)`,函数指针 - `##`:连接符 - `SECTION`:`__attribute__((section(x)))`,执行输入节名称 所以`INIT_EXPORT(rti_board_start, "0.end")`等价于`__attribute__((used)) const init_fn_t __rt_init_rti_board_start __attribute__((section(".rti_fn.""0.end"))) = rti_board_start` ```c static int rti_board_start(void) { return 0; } INIT_EXPORT(rti_board_start, "0.end"); #define INIT_EXPORT(fn, level) RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn ``` `rt_board_end`同理,所以`rt_components_board_init`的含义则为执行`__rt_init_rti_board_start`到`__rt_init_rti_board_end`之间函数 指定节`.rti_fn.1`,根据链接器节放置规则,将放置在`.rti_fn.0.end`节和`.rti_fn.1.end`节之间。 ```c #define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") ``` 所以RT-Thread提供了另一个宏,它的主要作用就是用来在初始化硬件时调用相应的函数。所以一些外设驱动初始化都展开了这个宏。以[RT-Thread Simulator 例程](https://gitee.com/rtthread/docs-online/raw/master/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/rtthread_simulator_v0.1.0.7z)为例: ```c INIT_BOARD_EXPORT(rt_hw_usart_init); INIT_BOARD_EXPORT(rt_hw_pin_init); ``` 总的来说,一个基本的BSP主要任务是建立让操作系统运行的基本环境,所以大致需要完成的主要工作是: 1. 初始化CPU内部寄存器,设定RAM工作时序。 2. 实现时钟驱动及中断控制器驱动,完善中断管理。 3. 实现串口和 GPIO 驱动。 4. 初始化动态内存堆,实现动态堆内存管理。 --- # CPU支持 > 这部分官方文档很详细,可参考[内核移植 (rt-thread.org)](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/porting/porting?id=cpu-%e6%9e%b6%e6%9e%84%e7%a7%bb%e6%a4%8d) 在嵌入式领域有多种不同 CPU 架构,例如 Cortex-M、ARM920T、MIPS32、RISC-V 等等。为了使 RT-Thread 能够在不同 CPU 架构的芯片上运行,RT-Thread 提供了一个 libcpu 抽象层来适配不同的 CPU 架构。libcpu 层向上对内核提供统一的接口,包括全局中断的开关,线程栈的初始化,上下文切换等。 RT-Thread 的 libcpu 抽象层向下提供了一套统一的 CPU 架构移植接口,这部分接口包含了全局中断开关函数、线程上下文切换函数、时钟节拍的配置和中断函数、Cache 等等内容。下表是 CPU 架构移植需要实现的接口和变量。 libcpu 移植相关 API | **函数和变量** | **描述** | | --- | --- | | rt_base_t rt_hw_interrupt_disable(void); | 关闭全局中断 | | void rt_hw_interrupt_enable(rt_base_t level); | 打开全局中断 | | rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter, rt_uint8_t *stack_addr, void *texit); | 线程栈的初始化,内核在线程创建和线程初始化里面会调用这个函数 | | void rt_hw_context_switch_to(rt_uint32_t to); | 没有来源线程的上下文切换,在调度器启动第一个线程的时候调用,以及在 signal 里面会调用 | | void rt_hw_context_switch(rt_uint32_t from, rt_uint32_t to); | 从 from 线程切换到 to 线程,用于线程和线程之间的切换 | | void rt_hw_context_switch_interrupt(rt_uint32_t from, rt_uint32_t to); | 从 from 线程切换到 to 线程,用于中断里面进行切换的时候使用 | | rt_uint32_t rt_thread_switch_interrupt_flag; | 表示需要在中断里进行切换的标志 | | rt_uint32_t rt_interrupt_from_thread, rt_interrupt_to_thread; | 在线程进行上下文切换时候,用来保存 from 和 to 线程 |
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
dejavudwh
这家伙很懒,什么也没写!
文章
7
回答
0
被采纳
0
关注TA
发私信
相关文章
1
大神们,rt-thread启用WDT了,但是还是没启动,怎么办?
2
求一个师傅带带队,有偿交学费 肯吃苦
3
自己按照官方手册 在drv_gpio.c里面找不到PIN脚信息
4
rtt studio f4默认生成的代码无法使用
5
官方例程中的 USB设置配置不成功
6
STM32F4的虚拟串口 的USB时钟如何配置
7
AT24CXX 软件包函数 at24cxx的问题
8
rtthread studio和bsp文件之间生成的区别和联系?
9
pwm根据手册修改为对应的引脚后无效
10
文件系统挂实验 ls命令异常
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
SFUD
msh
rt_mq_消息队列_msg_queue
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
出出啊
1517
个答案
342
次被采纳
小小李sunny
1443
个答案
289
次被采纳
张世争
805
个答案
174
次被采纳
crystal266
547
个答案
161
次被采纳
whj467467222
1222
个答案
148
次被采纳
本月文章贡献
出出啊
1
篇文章
4
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
1
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部