小程序
传感搜
传感圈

RT-Thread启动流程?RT-Thread如何支持不同开发板?

2023-08-11
关注

启动流程
一个开发板上的RT-Thread的启动流程可能是首先从bsp​当中链接脚本指定的startup_xxx.S​中的入口函数(ENTRY)或者复位异常处理函数(ResetHandler)开始运行,这部分我们在讲​bsp​支持时会详细讲解。

之后跳入entry​函数(GCC,使用不同编译器会进入不同的函数)执行,这里也可以跳入用户自己的main函数,但是需要用户自己完成rtthread_startup​的工作。

这个函数十分简单,首先先关中断(关中断操作由cpu支持部分提供),然后进入RT-Thread的全局初始化中。

#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 例程来说,会通过设备驱动一路调用到bsp支持部分提供的串口输出。

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系统调度器相关的数据结构:

线程优先级链表:每一个优先级对应一个链表,通过rt_thread​结构中的tlist​成员来进行相同优先级线程的连接
当前线程优先级
当前线程控制块
​rt_thread_ready_priority_group​中的每一位代表1个优先级,该位为1表示该优先级下有就绪线程,该位为0表示该优先级下没有就绪线程
僵尸线程链表
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%02xn",
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支持部分讲解。

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 donen", 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​。

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 例程为例:rt_hw_board_init​中主要完成HAL库的初始化、时钟配置和RT-Thread系统堆的初始化,以及rt_components_board_init​会完成硬件的初始化。

#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 donen", 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​

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​节之间。

#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")
所以RT-Thread提供了另一个宏,它的主要作用就是用来在初始化硬件时调用相应的函数。所以一些外设驱动初始化都展开了这个宏。以RT-Thread Simulator 例程为例:

INIT_BOARD_EXPORT(rt_hw_usart_init);
INIT_BOARD_EXPORT(rt_hw_pin_init);
总的来说,一个基本的BSP主要任务是建立让操作系统运行的基本环境,所以大致需要完成的主要工作是:

初始化CPU内部寄存器,设定RAM工作时序。
实现时钟驱动及中断控制器驱动,完善中断管理。
实现串口和 GPIO 驱动。
初始化动态内存堆,实现动态堆内存管理。
CPU支持
这部分官方文档很详细,可参考内核移植 (rt-thread.org)

嵌入式领域有多种不同 CPU 架构,例如 Cortex-M、ARM920T、MIPS32、RISC-V 等等。为了使 RT-Thread 能够在不同 CPU 架构的芯片上运行,RT-Thread 提供了一个 libcpu 抽象层来适配不同的 CPU 架构。libcpu 层向上对内核提供统一的接口,包括全局中断的开关,线程栈的初始化,上下文切换等。

RT-Thread 的 libcpu 抽象层向下提供了一套统一的 CPU 架构移植接口,这部分接口包含了全局中断开关函数、线程上下文切换函数、时钟节拍的配置和中断函数、Cache 等等内容。下表是 CPU 架构移植需要实现的接口和变量。

libcpu 移植相关 API

  • rt-thread
  • 初始化
  • handler
  • main函数
  • 指针初始化
  • 线程
  • 优先级
  • const
您觉得本篇内容如何
评分

评论

您需要登录才可以回复|注册

提交评论

慧生活

这家伙很懒,什么描述也没留下

关注

点击进入下一篇

英特尔14代酷睿处理器参数曝光:14900KS可达6.2GHz

提取码
复制提取码
点击跳转至百度网盘