0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

RT-Thread自动初始化详解

嵌入式大杂烩 来源:嵌入式大杂烩 作者:嵌入式大杂烩 2022-06-25 21:38 次阅读

我们知道,在写裸机程序时,当我们完成硬件初始化后,就需要在主函数中进行调用。当我们使用RT-Thread后,完全不需要这样做了,我们可以将硬件等自动初始化。RT-Thread自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行,非常的方便。

1 普通初始化

前面也讲了,我们在写单片机的程序时,需要对硬件进行初始化操作,我们这里还是以LED为例。需要对LED的GPIO进行初始化后才能进一步操作。

int main(void)
{
  rt_err_t rst; 
  /* LED初始化 */
  LED_GPIO_Config();  
  
  rst = rt_thread_init(&led_thread,
  "ledshine",
  led_thread_entry,
  RT_NULL,
  &led_thread_stack[0],
  sizeof(led_thread_stack),
  RT_THREAD_PRIORITY_MAX-2,
  20);
  if(rst == RT_EOK)
  {
  rt_thread_startup(&led_thread);
  }
}

上述代码很简单,就是在main()函数中对LED的GPIO进行初始化,也就是调用了LED_GPIO_Config() 函数,而针对RT-Thread系统,我们在需要初始化的地方进行初始化即可,无需在main()函数或者board.c中初始化了。

2 RT-Thread初始化流程

要想搞清楚RT-Thread的自动初始化流程,那么必须的了解RT-Thread初始化流程,这一部分前文也就有讲,官方也有,我们还是再来复习下。

RT-Thread支持多种平台和多种编译器。RT-Thread启动代码统一入口为 rtthread_startup(),芯片启动文件在完成必要工作(如初始化时钟、配置中断向量表、初始化堆栈等)后,跳转至 RT-Thread的启动入口中,最后进入用户入口 main()。RT-Thread的启动流程如下:

1.全局关中断,初始化与系统相关的硬件。

2.打印系统版本信息,初始化系统内核对象(如定时器、调度器)。

3.初始化用户 main线程(同时会初始化线程栈),在 main线程中对各类模块依次进行初始化。

4.初始化软件定时器线程、初始化空闲线程。

5.启动调度器,系统切换到第一个线程开始运行(如 main线程),并打开全局中断。

pYYBAGK25hOANpYRAAERUWXJgTY108.png

在图中标出颜色的部分需要用户特别注意(黄色表示 libcpu移植相关的内容,绿色部分表示板级移植相关的内容)。

3 RT-Thread自动初始化原理

既然是初始化,我们这里中的重点关注初始化过程,重新整理RT-Thread初始化如下图所示:

poYBAGK25iyATmbSAAEolP_gziE566.png

在系统启动流程图中,有两个函数:rt_components_board_init() 与 rt_components_init(),其后的带底色方框内部的函数表示被自动初始化的函数,其中:

“board init functions”为所有通过 INIT_BOARD_EXPORT(fn)申明的初始化函数。

“pre-initialization functions”为所有通过 INIT_PREV_EXPORT(fn)申明的初始化函数。

“device init functions”为所有通过 INIT_DEVICE_EXPORT(fn)申明的初始化函数。

“components init functions”为所有通过 INIT_COMPONENT_EXPORT(fn)申明的初始化函数。

“enviroment init functions”为所有通过 INIT_ENV_EXPORT(fn)申明的初始化函数。

“application init functions”为所有通过 INIT_APP_EXPORT(fn)申明的初始化函数。

rt_components_board_init() 函数执行的比较早,主要初始化相关硬件环境,执行这个函数时将会遍历通过 INIT_BOARD_EXPORT(fn)申明的初始化函数表,并调用各个函数。

rt_components_init() 函数会在操作系统运行起来之后创建的 main线程里被调用执行,这个时候硬件环境和操作系统已经初始化完成,可以执行应用相关代码。rt_components_init() 函数会遍历通过剩下的其他几个宏申明的初始化函数表。

RT-Thread的自动初始化机制使用了自定义 RTI符号段,将需要在启动时进行初始化的函数指针放到了该段中,形成一张初始化函数表,在系统启动过程中会遍历该表,并调用表中的函数,达到自动初始化的目的。

自动初始化功能的宏接口定义详细描述如下表所示:

初始化顺序 宏接口 描述
1 INIT_BOARD_EXPORT(fn) 非常早期的初始化,此时调度器还未启动
2 INIT_PREV_EXPORT(fn) 主要是用于纯软件的初始化、没有太多依赖的函数
3 INIT_DEVICE_EXPORT(fn) 外设驱动初始化相关,比如网卡设备
4 INIT_COMPONENT_EXPORT(fn) 组件初始化,比如文件系统或者 LWIP
5 INIT_ENV_EXPORT(fn) 系统环境初始化,比如挂载文件系统
6 INIT_APP_EXPORT(fn) 应用初始化,比如 GUI应用

初始化函数主动通过这些宏接口进行申明,如 INIT_BOARD_EXPORT(rt_hw_usart_init),链接器会自动收集所有被申明的初始化函数,放到 RTI符号段中,该符号段位于内存分布的 RO段中,该 RTI符号段中的所有函数在系统初始化时会被自动调用。

好了,介绍性文字我就不贴了,下面直接看源代码进一步分析。前文说过,在RT-Thread的启动流程中,调用了两个函数 rt_components_board_init()与 rt_components_init()就完成了上述6个部分的初始化工作。从初始化启动流程图中我们可以看出: rt_components_board_init()完成了第 1部分工作, rt_components_init()完成了第2-6部分的工作。那么接下来我们先看这两个函数源代码。


/**
 * 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-Thread Components Initialization
 */
void rt_components_init(void)
{
#if RT_DEBUG_INIT
    int result;
    const struct rt_init_desc *desc;

    rt_kprintf("do components initialization.\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_components_board_init() 与 rt_components_init()函数在components.c文件中实现。

可以看到两个函数在非调试模式下都是通过for循环会遍历位于__rt_init_rti_board_start 到 __rt_init_rti_board_end以及__rt_init_rti_board_end 到 __rt_init_rti_end之间保存的函数指针,然后依次执行这些函数。那么接下来我们看看上述函数指针。我们先编译下,找到.map文件,我们在在文件中搜索上述的函数指针。

poYBAGK25lCAF7g2AAIZwqwwSs0219.png

又问会问,找到又能怎样呢?怎么和上述的6个宏定义对应起来呢?不急哈,慢慢来,我们先找到上述6个宏定义又是如何实现的,找到啦,在rtdef.h中。

poYBAGK25mKAcQwYAAKv6PeRkr0395.png

宏定义又是通过INIT_EXPORT宏函数定义的,那么INIT_EXPORT又是如何实现的呢?

#ifdef _MSC_VER /* we do not support MS VC++ compiler */
    #define INIT_EXPORT(fn, level)
#else
    #if RT_DEBUG_INIT
        struct rt_init_desc
        {
            const char* fn_name;
            const init_fn_t fn;
        };
        #define INIT_EXPORT(fn, level)                                                     \
            const char __rti_##fn##_name[] = #fn;                                           \
            RT_USED const struct rt_init_desc __rt_init_desc_##fn SECTION(".rti_fn."level) = \
            { __rti_##fn##_name, fn};
    #else
        #define INIT_EXPORT(fn, level)                                                      \
            RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
    #endif
#endif

【注】上述代码在rtdef.h中实现。

上述代码最关键的代码是:

RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn

这又是啥,看不懂啊,不急哈,我们先看看init_fn_t是啥?其定义如下:

#ifdef RT_USING_COMPONENTS_INIT
typedef int (*init_fn_t)(void);

这就是一个函数指针类型,其实就是个指针。那么SECTION又是啥呢?其定义如下:

poYBAGK25pCAErH7AAFfuVXN8u8707.png

__attribute__((used))表示这个标记这个东西是使用过的,避免出现如: warning: #177-D: variable "a" was declared but never referenced的警告。

在GCC的宏中,##后面跟变量名。__attribute__((section(x)))则表示fn被放置于指定段中。是不是还是一头雾水,不急,我们再看看一些代码或许你就明白了。

/*
 * Components Initialization will initialize some driver and components as following
 * order:
 * rti_start        --> 0
 * BOARD_EXPORT     --> 1
 * rti_board_end    --> 1.end
 *
 * DEVICE_EXPORT    --> 2
 * COMPONENT_EXPORT --> 3
 * FS_EXPORT        --> 4
 * ENV_EXPORT       --> 5
 * APP_EXPORT       --> 6
 *
 * rti_end          --> 6.end
 *
 * These automatically initialization, the driver or component initial function must
 * be defined with:
 * INIT_BOARD_EXPORT(fn);
 * INIT_DEVICE_EXPORT(fn);
 * ...
 * INIT_APP_EXPORT(fn);
 * etc.
 */
static int rti_start(void)
{
    return 0;
}
INIT_EXPORT(rti_start, "0");

static int rti_board_start(void)
{
    return 0;
}
INIT_EXPORT(rti_board_start, "0.end");

static int rti_board_end(void)
{
    return 0;
}
INIT_EXPORT(rti_board_end, "1.end");

static int rti_end(void)
{
    return 0;
}
INIT_EXPORT(rti_end, "6.end");

【注】以上代码再在components.c文件中实现。

好了,我们结合上述代码和6个宏定义,以及.map文件。

pYYBAGK25rOAR3UiAAJaDX-u5M0749.png

上图中框选的第一行是一个Section,叫做.rti_fn.0,这个内容实际是我们通过INIT_EXPORT(rti_start, "0");完成的,我们把函数rti_start改名为__rt_init_rti_start,存入.rti_fn.0这个地方。同样的,INIT_EXPORT(rti_board_start, "0.end");、INIT_EXPORT(rti_board_end, "1.end");、INIT_EXPORT(rti_end, "6.end");也是这里插入的。下图就是我们插入的位置。

pYYBAGK25riADKagAAI28bnZbrg840.png

这下是不是很明白,当然啦,多看几遍,还是很好理解的。

4 RT-Thread自动初始化实例

前文理论讲了很多,源代码也分析,估计初学者还很蒙,没关系,慢慢理解,我们先看个例子,先用起来,后面再慢慢理解。还会是LED的例子,前文已近给出了普通初始化方式,下面我们看看如何使用自动化初始化。

/**
 * @brief 初始化LED的GPIO
 * @param None
 * @retval None
 */
int LED_GPIO_Config(void)
{ 
  /*定义一个GPIO_InitTypeDef类型的结构体*/
  GPIO_InitTypeDef GPIO_InitStructure;

  /*开启LED的外设时钟*/
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOG, ENABLE); 

  /*设置IO口*/   
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置引脚模式为通用推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置引脚速率为50MHz 

  /*调用库函数,初始化GPIOB0*/
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //选择要控制的GPIOB引脚 
  GPIO_Init(GPIOB, &GPIO_InitStructure); 
    
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;/*选择要控制的引脚*/ 
  GPIO_Init(GPIOG, &GPIO_InitStructure); 
   
  /* 开启所有led灯 */
  GPIO_SetBits(GPIOB, GPIO_Pin_0);
  GPIO_SetBits(GPIOG, GPIO_Pin_6|GPIO_Pin_7);  
  
   return 0;
}

/* LED初始化 */
INIT_BOARD_EXPORT(LED_GPIO_Config); 

我们注意最后一行代码,这里使用INIT_BOARD_EXPORT宏定义进行初始化,其初始化时间最早,值得注意的是,上述6个宏定义修饰的函数返回值都是int,最好将返回值改为int。

如果LED配置正确,其实验现象和普通初始化方式没有任何区别,接下来我们再来看看.map文件有何变化,当然我说的是自动初始化部分。

pYYBAGK25uOARarPAAK0mM8C1Xw687.pngpYYBAGK25veAZhEsAAIrD_z92Ds321.png

我们可以看到多了LED初始化的信息,也说明我们前文分析的是合理的。

5 总结

好了,关于自动出初始化就讲完了,我估计初学者很蒙,没关系,我们再来总结下,还是先看看RT-Thread初始化过程,如下图所示:

poYBAGK25iyATmbSAAEolP_gziE566.png

rt_components_board_init() 与 rt_components_init()负责初始化,其中带底色方框内部的函数表示被自动初始化的函数。这部分应该没是啥问题。关键是如何将初始化函数和6个自动初始化宏定义联系起来这就有点烧脑子,我整理了一张关系图,如下图所示:

poYBAGK25xOAZEjcAAGLam2avME478.png

结合前文的讲解,再结合上述两张图,我不想在赘述了,自行理解去吧。

参考地址:

https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/basic/basic

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 初始化
    +关注

    关注

    0

    文章

    50

    浏览量

    11850
  • RT-Thread
    +关注

    关注

    31

    文章

    1285

    浏览量

    40078
收藏 人收藏

    评论

    相关推荐

    RT-Thread qemu mps2-an385 bsp移植制作 :系统运行篇

    前面已经让 RT-Thread 进入了 entry 入口函数,并且 调整 链接脚本,自动初始化与 MSH shell 的符号已经预留, 进入了 RT-Thread
    的头像 发表于 11-14 12:27 827次阅读
    <b class='flag-5'>RT-Thread</b> qemu mps2-an385 bsp移植制作 :系统运行篇

    RT-thread初始化过程是怎样进行的

    RT-thread初始化过程是怎样进行的?扩展补丁Sub和super的作用是什么?如何去使用它们呢?
    发表于 11-29 07:42

    如何对RT-Thread系统进行初始化

    RT-Thread是如何启动的?如何对RT-Thread系统进行初始化呢?
    发表于 11-30 07:54

    为什么RT-Thread要采用这种复杂的方式来进行自动初始化操作呢

    在分析之前首先查阅 RT-Thread 的官方文档RT-Thread 自动初始化机制,根据官方文档的讲述在 RTT 源码中一共使用了 6 中顺序的
    发表于 04-06 17:49

    RT-Thread自动初始化机制简介

    RT-Thread 的时钟管理以时钟节拍为基础,时钟节拍是 RT-Thread 操作系统中最小的RT-Thread 自动初始化机制时钟单位。
    发表于 04-06 18:08

    RT-Thread系统自动初始化机制简介

    RT-Thread 自动初始化机制1、自动初始化机制简介在系统启动流程图中,有两个函数:rt_c
    发表于 04-12 17:43

    【原创精选】RT-Thread征文精选技术文章合集

    RT-Thread自动初始化详解GD32 RISC-V系列 BSP框架制作与移植GD32407V-START开发板的BSP框架制作与移植基于Select/Poll实现并发服务器(一)
    发表于 07-26 14:56

    RT-Thread系统初始化与启动流程详细描述

    系统定时器线程voidrt_application_init ()创建用户线程详细描述RT-Thread 的启动流程RT-Thread 的启动流程,大致可以分为四个部分:(1)初始化与系统相关的硬件
    发表于 08-25 15:15

    RT-Thread自动初始化原理分析

    ;}这里我们直接就可以使用 printf 进行打印,而没有进行一些其它的初始化,参考这个思路引出了 RT-Thread自动初始化机制。
    发表于 12-05 14:17

    一文详解RT-Thread自动初始化

    在学RT-Thread时,经常能听到这个词:自动初始化。用起来也非常容易,一个宏就解决了,但是原理是什么呢?
    的头像 发表于 07-21 10:17 7587次阅读
    一文<b class='flag-5'>详解</b><b class='flag-5'>RT-Thread</b><b class='flag-5'>自动</b><b class='flag-5'>初始化</b>

    RT-Thread学习笔记 --(3)RT-Thread自动初始化机制分析

    相信不少工程师在阅读RT-Thread相关源代码的时候,都会经常看到如下图所示的宏定义,按照宏定义的命名来理解,这些宏定义似乎都是对一些...
    发表于 01-25 18:55 1次下载
    <b class='flag-5'>RT-Thread</b>学习笔记 --(3)<b class='flag-5'>RT-Thread</b><b class='flag-5'>自动</b><b class='flag-5'>初始化</b>机制分析

    RT-Thread全球技术大会:如何使用组件以及自动初始化流程

    RT-Thread全球技术大会:如何使用组件和自动初始化流程           审核编辑:彭静
    的头像 发表于 05-27 15:16 931次阅读
    <b class='flag-5'>RT-Thread</b>全球技术大会:如何使用组件以及<b class='flag-5'>自动</b><b class='flag-5'>初始化</b>流程

    RT-Thread自动初始化机制

      在分析之前首先查阅 RT-Thread 的官方文档 [RT-Thread 自动初始化机制](https://www.rt-thread.
    的头像 发表于 06-17 08:52 2633次阅读
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>自动</b><b class='flag-5'>初始化</b>机制

    rt-thread线程栈初始化参数分析

    RT-Thread 在线程初始化的代码内有一段初始化线程堆栈的代码
    的头像 发表于 08-14 16:50 1716次阅读
    <b class='flag-5'>rt-thread</b>线程栈<b class='flag-5'>初始化</b>参数分析

    RT-Thread使用经验分享:链表未初始化造成死机

    最近在开发调试基于RT-Thread 的驱动时,遇到一个比较奇怪的死机问题,后来经过一步步排查,终于发现是驱动的链表节点没有初始化造成的死机
    的头像 发表于 10-08 14:49 942次阅读
    <b class='flag-5'>RT-Thread</b>使用经验分享:链表未<b class='flag-5'>初始化</b>造成死机