Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
ART-Pi
RT-Thread
《玩转ART-Pi开发板》第5章 API-Pi自动初始化详解
发布于 2021-03-16 07:47:39 浏览:1081
订阅该版
[tocm] **开发环境:** RT-Thread版本:4.0.3 操作系统:Windows10 Keil版本:V5.30 开发板MCU:STM32H750XB 我们知道,在写裸机程序时,当我们完成硬件初始化的封装后,其初始化函数则需要在主函数中进行调用。当我们使用RT-Thread后,完全不需要这样做了,我们可以将硬件等自动初始化。**RT-Thread 自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。** ## 5.1普通初始化 前面也讲了,我们在写单片机的程序时,需要对硬件进行初始化操作,我们这里还是以LED为例。需要对LED的GPIO 进行初始化后才能进一步操作。 ```c /**Includes*********************************************************************/ #include
#include
#include "drv_common.h" /*define*/ #define LED_PIN GET_PIN(I, 8) /*define function*/ static int LED_init(void); /** * @brief mian * @param None * @retval None */ int main(void) { rt_uint32_t count = 1; LED_init(); while(count++) { rt_thread_mdelay(500); rt_pin_write(LED_PIN, PIN_HIGH); rt_thread_mdelay(500); rt_pin_write(LED_PIN, PIN_LOW); } return RT_EOK; } /** * @brief LED_init * @param None * @retval None */ static int LED_init(void) { rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT); return 0; } ``` 这个很简单,在main()函数中对LED的GPIO进行了初始化,也就是调用了LED_init()函数,而针对RT-Thread系统,我们在需要初始化的地方进行初始化即可,无需在main()函数或者board.c中初始化了。 ## 5.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 线程),并打开全局中断。 ![y3UPjP.png](https://s3.ax1x.com/2021/02/04/y3UPjP.png) 在图中标出颜色的部分需要用户特别注意(黄色表示 libcpu 移植相关的内容,绿色部分表示板级移植相关的内容)。 ## 5.3 RT-Thread自动初始化原理 既然是初始化,我们这里中的重点关注初始化过程,重新整理RT-Thread初始化如下图所示: ![y3UFnf.png](https://s3.ax1x.com/2021/02/04/y3UFnf.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部分的工作。那么接下来我们先看这两个函数源代码。 ```c /** * 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 volatile 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 volatile 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文件,我们在在文件中搜索上述的函数指针。 ![y3UAHS.png](https://s3.ax1x.com/2021/02/04/y3UAHS.png) 又问会问,找到又能怎样呢?怎么和上述的6个宏定义对应起来呢?不急哈,慢慢来,我们先找到上述6个宏定义又是如何实现的,找到啦,在rtdef.h中。 ![y3UU3R.png](https://s3.ax1x.com/2021/02/04/y3UU3R.png) 宏定义又是通过INIT_EXPORT宏函数定义的,那么INIT_EXPORT又是如何实现的呢? ```c /* initialization export */ #ifdef RT_USING_COMPONENTS_INIT typedef int (*init_fn_t)(void); #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 #else #define INIT_EXPORT(fn, level) #endif ``` 【注】上述代码在rtdef.h中实现。 上述代码最关键的代码是: ```c RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn ``` 这又是啥,看不懂啊,不急哈,我们先看看init_fn_t是啥?其定义如下: ```c #ifdef RT_USING_COMPONENTS_INIT typedef int (*init_fn_t)(void); ``` 这就是一个函数指针类型,其实就是个指针。那么SECTION又是啥呢?其定义如下: ![y3UZNQ.png](https://s3.ax1x.com/2021/02/04/y3UZNQ.png) \__ attribute__((used))表示这个标记这个东西是使用过的,避免出现如: warning: #177-D: variable "a" was declared but never referenced的警告。 **在GCC的宏中,##后面跟变量名。__ attribute__((section(x)))则表示fn被放置于指定段中。** 是不是还是一头雾水,不急,我们再看看一些代码或许你就明白了。 ```c /* * 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文件。 ![y3Un9s.png](https://s3.ax1x.com/2021/02/04/y3Un9s.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");也是这里插入的。下图就是我们插入的位置。** ![y3UGEF.png](https://s3.ax1x.com/2021/02/04/y3UGEF.png) 这下是不是很明白,当然啦,多看几遍,还是很好理解的。 ## 5.4 RT-Thread自动初始化实例 前文理论讲了很多,源代码也分析,估计初学者还很蒙,没关系,慢慢理解,我们先看个例子,先用起来,后面再慢慢理解。还会是LED的例子,前文已近给出了普通初始化话方式,下面我们看看如何使用自动化初始化。 ```c /**Includes*********************************************************************/ #include
#include
#include "drv_common.h" /*define*/ #define LED_PIN GET_PIN(I, 8) /*define function*/ static int LED_init(void); /** * @brief mian * @param None * @retval None */ int main(void) { rt_uint32_t count = 1; while(count++) { rt_thread_mdelay(500); rt_pin_write(LED_PIN, PIN_HIGH); rt_thread_mdelay(500); rt_pin_write(LED_PIN, PIN_LOW); } return RT_EOK; } /** * @brief LED_init * @param None * @retval None */ static int LED_init(void) { rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT); return 0; } INIT_BOARD_EXPORT(LED_init); ``` 我们注意最后一行代码,这里使用INIT_BOARD_EXPORT宏定义进行初始化,其初始化时间最早,值得注意的是,上述6个宏定义修饰的函数返回值都是int,最好将返回值改为int。 如果LED配置正确,其实验现象和普通初始化方式没有任何区别,接下来我们再来看看.map文件有何变化,当然我说的是自动初始化部分。 ![y3UDHO.png](https://s3.ax1x.com/2021/02/04/y3UDHO.png) ![y3U64H.png](https://s3.ax1x.com/2021/02/04/y3U64H.png) 我们可以看到多了LED初始化的信息,也说明我们前文分析的是合理的。 ## 5.5总结 好了,关于自动出初始化就讲完了,我估计初学者很蒙,没关系,我们再来总结下,还是先看看RT-Thread初始化过程,如下图所示: ![y3U28A.png](https://s3.ax1x.com/2021/02/04/y3U28A.png) rt_components_board_init() 与 rt_components_init()负责初始化,其中带底色方框内部的函数表示被自动初始化的函数。这部分应该没是啥问题。关键是如何将初始化函数和6个自动初始化宏定义联系起来这就有点烧脑子,我整理了一张关系图,如下图所示: ![y3URgI.png](https://s3.ax1x.com/2021/02/04/y3URgI.png) 结合前文的讲解,再结合上述两张图,我不想在赘述了,自行理解去吧。
[参考地址](https://www.rt-thread.org/document/site/programming-manual/basic/basic/) ### 欢迎访问我的网站 [BruceOu的哔哩哔哩](https://space.bilibili.com/338944611) [BruceOu的主页](https://www.bruceou.cn/) [BruceOu的博客](https://blog.bruceou.cn/) [BruceOu的CSDN博客](https://blog.csdn.net/bruceoxl) [BruceOu的gitee](https://gitee.com/ouxiaolong/)
1
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
BruceOu
这家伙很懒,什么也没写!
文章
27
回答
0
被采纳
0
关注TA
发私信
相关文章
1
RT-THREAD在STM32H747平台上移植lwip
2
正点原子miniSTM32开发板读写sdcard
3
反馈rtt串口驱动对低功耗串口lpuart1不兼容的问题
4
Keil MDK 移植 RT-Thread Nano
5
RT1061/1052 带 RTT + LWIP和LPSPI,有什么坑要注意吗?
6
RT thread HID 如何收发数据
7
求一份基于RTT系统封装好的STM32F1系列的FLASH操作程序
8
RT-Thread修改项目名称之后不能下载
9
rt-studio编译c++
10
有木有移植rt-thread(nano)到riscv 32位MCU上
推荐文章
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
DMA
USB
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
I2C_IIC
ESP8266
UART
WIZnet_W5500
ota在线升级
cubemx
PWM
flash
freemodbus
BSP
packages_软件包
潘多拉开发板_Pandora
定时器
ADC
flashDB
GD32
socket
编译报错
中断
Debug
rt_mq_消息队列_msg_queue
keil_MDK
SFUD
msh
ulog
C++_cpp
MicroPython
本月问答贡献
出出啊
1518
个答案
343
次被采纳
小小李sunny
1444
个答案
290
次被采纳
张世争
813
个答案
177
次被采纳
crystal266
549
个答案
161
次被采纳
whj467467222
1222
个答案
149
次被采纳
本月文章贡献
出出啊
1
篇文章
3
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
3
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
2
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部