RT-Thread启动过程

RT-Thread启动流程

一般了解一份代码大多从启动部分开始,同样这里也采用这种方式,先寻找启动的源头。
RT-Thread支持多种平台和多种编译器,而rtthread_startup()函数是RT-Thread规定的统一启动入口。

一般执行顺序是:系统先从启动文件开始运行,然后进入RT-Thread的启动函数rtthread_startup(),最后进入用户入口函数main(),如下图所示:
在这里插入图片描述
以MDK-ARM为例,用户程序入口为main函数,位于main.c文件中。系统启动后先从汇编代码startup_stm32f103xe.s开始运行,然后跳转到C代码,进行RT-Thread系统启动,最后进入用户程序入口函数main()。

为了在进入main()之前完成RT-Thread系统功能初始化,我们使用了MDK的扩展功能。
可以给main添加 S u b Sub Sub 的前缀符号作为一个新功能函数 的前缀符号作为一个新功能函数 的前缀符号作为一个新功能函数SubKaTeX parse error: Can't use function '$' in math mode at position 8: main,这个$̲Submain,这个 S u b Sub Sub m a i n 可以先调用一些补充在 m a i n 之前的功能函数,再调用 main可以先调用一些补充在main之前的功能函数,再调用 main可以先调用一些补充在main之前的功能函数,再调用Super$$main转到main()函数执行,这样可以让用户不去管main()之前的系统初始化操作。

在components.c中定义的这段代码:

int $Sub$$main(void)
{
	rtthread_startup();
	return 0;
}
int rtthread_startup(void)
{
	rt_hw_interrupt_disable();

	/* 板级初始化:需要在该函数内部进行系统堆的初始化 */
	rt_hw_board_init();

	/* 打印RT-Thread版本信息 */
	rt_show_version();

	/* 定时器初始化 */
	rt_system_timer_init();

	/* 调度器初始化 */
    rt_system_scheduler_init();

#ifdef RT_USING_SIGNALS
    /* 信号初始化 */
    rt_system_signal_init();
#endif

    /* 由此创建一个用户 main 线程 */
    rt_application_init();

    /* 定时器线程初始化 */
    rt_system_timer_thread_init();

    /* 空闲线程初始化 */
    rt_thread_idle_init();

    /* 启动调度器 */
    rt_system_scheduler_start();

    /* 不会执行至此 */
    return 0;
}

这部分启动代码,大致可以分为四个部分:

  1. 初始化与系统相关的硬件。
  2. 初始化系统内核对象,例如定时器、调度器、信号;
  3. 创建main线程,在main线程中对各类模型依次进行初始化;
  4. 初始化定时器线程、空闲线程,并启动调度器。

启动调度器之前,系统所创建的线程并不会立马运行,它们会处于就绪状态等待系统调度;待启动调度器之后,系统才转入第一个线程开始运行,根据调度规则,选择的是就绪队列中优先级最高的线程。

rt_hw_board_init()中完成系统时钟设置,为系统提供心跳、串口初始化,将系统输入输出端绑定到这个串口,后续系统运行信息就会从串口打印出来。

main()函数是RT-Thread的用户代码入口,用户可以在main()函数里添加自己的应用。

RT-Thread程序内存分布

一般MCU包含的存储空间有:片内Flash与片内RAM。
RAM相当于内存,Flash相当于硬盘。
编译器会将一个程序分类为好几个部分,分别存储在MCU不同的存储区。

Keil工程在编译完之后,会有相应的程序所占用的空间提示信息。

linking...
Program Size: Code=48008 RO-data=5660 RW-data=604 ZI-data=2124
After Build - User command \#1: fromelf --bin.\\build\\rtthread-stm32.axf--output rtthread.bin
".\\build\\rtthread-stm32.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:07

上面提到的 Program Size 包含以下几个部分:

1)Code:代码段,存放程序的代码部分;

2)RO-data:只读数据段,存放程序中定义的常量;

3)RW-data:读写数据段,存放初始化为非 0 值的全局变量;

4)ZI-data:0 数据段,存放未初始化的全局变量及初始化为 0 的变量;

编译完工程会生成一个.map的文件,该文件说明了各个函数占用的尺寸和地址,在文件最后几行也说明了上面几个字段的关系:

Total RO Size (Code + RO Data) 53668 ( 52.41kB)
Total RW Size (RW Data + ZI Data) 2728 ( 2.66kB)
Total ROM Size (Code + RO Data + RW Data) 53780 ( 52.52kB)
  1. RO Size:表示程序占用Flash空间的大小。
  2. RW Size:表示运行时占用的RAM的大小。
  3. ROM Size:表示烧写程序所占用的Flash空间的大小。

程序运行之前,需要有文件实体被烧录到STM32的Flash中,一般是bin或者hex文件,该被烧录文件称为可执行映像文件。
在这里插入图片描述
RO段中保存了Code、RO-data的数据,RW段保存了RW-data的数据,由于ZI-data都是0,所以未包含在映像文件中。

STM32在上电启动之后默认从Flash启动,启动之后会将RW段中的RW-data搬运到RAM中,但不会搬运RO段,即CPU的执行代码从Flash中读取,另外根据编译器给出的ZI地址和大小分配出ZI段,并将这块RAM区域清零。

其中动态内存堆为未使用的RAM空间,应用程序申请和释放的内存块都来自该空间。

如下面的例子:

rt_uint8_t *msg_ptr;
msg_ptr = (rt_uint8_t *)rt_malloc(128);
rt_memset(msg_ptr, 0, 128);

代码中的msg_ptr指针指向128字节内存空间位于动态内存堆空间中。

而一些全局变量则是存放于 RW 段和 ZI 段中,RW 段存放的是具有初始值的全局变量(而常量形式的全局变量则放置在 RO 段中,是只读属性的),ZI 段存放的系统未初始化的全局变量。

RT-Thread自动初始化机制

自动初始化机制是指初始化函数不需要被显示调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。

例如在串口驱动中调用一个宏定义告知系统初始化需要调用的函数,代码如下:

int rt_hw_usart_init(void) /* 串口初始化函数 */
{
	rt_hw_serial_register(&serial1, "uart1",RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,uart);
	return 0;
}
INIT_BOARD_EXPORT(rt_hw_usart_init); /* 使用组件自动初始化机制 */

示例代码最后的INIT_BOARD_EXPORT(rt_hw_usart_init)表示使用自动初始化功能,按照这种方式,rt_hw_usart_init()函数就会被系统自动调用,那么它是在哪里被调用的呢?

在系统启动流程图中,有两个函数: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_conponents_board_init()函数执行的比较早,主要初始化相关硬件环境,执行这个函数时将会遍历通过INIT_BOARD_EXPORT(fn)申明的初始化函数表,并调用各个函数。

rt_conponents_init()函数会在操作系统运行起来之后创建的main线程里被调用执行,这个时候硬件环境和操作系统已经初始化完成,可以执行应用相关代码。

rt_components_init() 函数会遍历通过剩下的其他几个宏申明的初始化函数表。

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

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

静态对象和动态对象

RT-Thread内核采用面向对象的设计思想进行设计,系统级的基础设施都是一种内核对象,例如线程,信号量,互斥量,定时器等。
内核对象分为两类:静态内核对象和动态内核对象。
静态内核对象通常放在RW段和ZI段,在系统启动后在程序中初始化;
动态内核对象则是从内存堆中创建的,而后手工做初始化。

thread1 是一个静态线程对象,而 thread2 是一个动态线程对象。
thread1对象的内存空间,包括线程控制块thread1与栈空间thread1_stack都是编译时决定的,因为代码中不存在初始值,都统一放在未初始化数据段中。

静态对象会占用RAM空间,不依赖于内存堆管理器,内存分配时间确定。
动态对象则依赖于内存堆管理器,运行时申请RAM空间,当对象被删除后,占用的RAM空间被释放。

  • 38
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
RT-Thread作品秀】智能家居的语音控制方案。作者:xqyjlj 概述智能家居是现在一个很火热的方向,随着人们生活水平的提高,我们都逐渐开始希望身边的电器都可以不用手来控制,本作品就是基于这种需求,开发出来了一个基于语音控制的智能家居方案。本智能家居方案,分为联网与不联网两种版本。其中,无论是联网与不联网,语音识别功能均能正常使用。在本作品没有连上网的情况下,可以进行语音对开关的控制,进行MP3等常用音频文件的播放,以及具有拍照功能,可以将拍下来的照片储存在SD卡内,在本作品连上网的情况下,还附加了以下功能,可以将一些数据上报到云平台(onenet),并且将一些工作日志通过邮件发送给用户。 开发环境硬件:ART_PI(主控) LD3320(语音识别芯片) VS1053(音频解码芯片) OV2640(摄像头) RT-Thread版本:RT-Thread V 4.0.3 开发工具及版本:RT-Thread Studio 2.0.0 RT-Thread使用情况概述内核:调度器,信号量。 组件:UART,DFS,SPI,IIC,PIN。 软件包:smtp_client,btstack,netuils,fal,easyflash,littlefs,onenet。 硬件框架主控:ART-PI ART-Pi 是 RT-Thread 团队为嵌入式软件工程师、开源创客设计的一款极具扩展功能的 DIY 开源硬件。致力打造一个开源的软硬件平台。 语音识别芯片:LD3320 LD3320是非特定人(不用针对指定人)语音识别芯片,即语音声控芯片。最多可以识别50条预先内置的指令。 音频解码芯片:VS1053 VS1053是一款利用SPI通讯的音频解码模块,支持大部分的音频文件的解码播放和编码保存 摄像头:OV2640 OV2640是OmniVision公司生产的一颗1/4寸的CMOSUXGA(1632*1232)图像传感器;支持自动曝光控制、自动增益控制、自动白平衡、自动消除灯光条纹等自动控制功能。 软件框架说明本项目采用的是多级联控模式。将多个模块的任务分为多个级别,以语音识别为主体,摄像头和语音播报为从体,在语音识别到关键字的时候,会出发相对应的函数,实现不同的效果,而onenet是与语音识别互相独立的,会源源不断的把数据实时发送到云端。 软件模块说明smtp_thread_entry:邮件线程,用于发送邮件。 ld3320_asr_thread:语音识别线程,用于语音识别,调用其他程序。 onenet_upload_entry:onenet线程,用于向云端发送数据。 语音识别过程: 首先初始化LD3320芯片,在对其写入需要识别的关键字数据,之后启动LD3320,之后等待其信号引脚拉低,在其信号引脚拉低后,通过读取其寄存器,我们便能知道LD3320获取到了什么样的关键字。之后便从关键字调取我们的相对应的函数。使之工作于不同的功能。 演示效果演示视频: 比赛感悟在报名这个比赛之后,我的目标其实是比现在的功能多得多的,这一点可以从我的Gitee上面的进度表可以看出,奈何原本打算三个人做的项目,到最后,也是我一个人来完成。自己之前画的板子,也是因为自己画板功力不足,还有焊接能力不足,导致本来拥有更多功能的拓展板也没完成。 不过比赛嘛,重要的不是结果,而是过程。通过这次比赛,我最起码收货到了以下知识: 安卓开发,微信小程序设计,web开发(以上由于个人能力问题,其实并未实现),音频处理,STM32H750的众多DMA的设置,以及bootloader,同时也知道了STM32还能片外运行的神奇事情,也学会了RT-Thread Studio的使用。最重要的事情就是明白了不要过分依赖别人,自己想的计划,就要自己去实现,不要总是把任务布置给其他人。
RT-Thread作品秀】远程调试终端作者:钟良涛 概述本设计灵感来源于调试人员出差调试设备遇到困难时,提供指导的工程师可以远程实时查看调试数据,以提供指导。本设计实现了一个远程调试助手,ART-Pi接收到无线串口按一定协议发送的数据后,将数据解析并打包为json格式发送给订阅该话题的MQTT上位机。搭配ART-Pi Mate可实现多种通信接口的调试。 开发环境硬件:ART-Pi ART-Pi Mate(自主设计) RT-Thread版本:4.0.3 开发工具及版本:RT-Thread Studio 2.0.0 PyCharm20.2 Python3.7 RT-Thread使用情况概述组件:finsh、lwIP、SPI、串口、Pin 软件包:cjson、wlan_wiced、phaomqtt、AT command 内核:信号量、多线程 硬件框架硬件框图如上图所示,硬件部分由ART-Pi和ART-Pi Mate和以CC2530无线透传模块模拟的调试设备组成。其中ART-Pi Mate为专为ART-Pi设计的扩展板,包含LCD、UART、NRF24L01等功能。 以CC2530无线透传模块模拟待调试的设备,将该模块连接到PC机,按照固定的协议格式发送数据包到ART-Pi Mate上焊接的CC2530无线透传,ART-Pi以串口中断的方式进行数据读取并解析,随后将数据打包为Json格式通过Wifi发送至MQTT上位机。 软件框架说明该应用中软件分为嵌入式软件部分和桌面端软件。嵌入式软件实现调试终端的数据接收、解析、打包、发布等功能,桌面端软件实现数据的接收和显示功能。软件流程如上图所示,调试终端数据处理线程对CC2530无线透传模块发送的数据进行解析、打包,然后由MQTT客户端线程进行数据发布。桌面端软件的MQTT客户端线程订阅调试终端发送的话题,然后通过信号槽的方式通知Qt界面线程刷新界面以显示数据。 软件模块说明数据接收模块: 数据接收模块通过串口中断的方式实现,首先以中断的方式打开串口设备,然后每当有数据达到时,触发相应的中断回调函数,在中断回调函数里发送有数据到达的信号量,通知数据处理线程进行数据读取。 数据解包: 数据包的格式定义如下: 以状态机的方式进行数据解包,状态机转移图如下图所示: 数据打包: 该设计将JSON打包为如下格式: 演示效果演示视频: 比赛感悟通过本次比赛熟悉了rt-thread的操作系统以及rt-thread studio的使用方式,被rt-thread优雅的设计所折服。通过这次比赛,使用了rt-thread的线程、信号量、设备模型、组件、软件包等功能,在搭配rt-thread studio进行开发时,是非常的便捷的,直接在设置中打开对应的功能,rt-thread studio就会自动下载好相应的资源并为我们配置好,使原本复杂的配置过程简单化,极大程度的提高了开发效率。 在参赛过程中也遇到了不少问题,其中包括开发板启动不成功,最后通过重新刷写bootloader解决;还有出现了线程堆栈溢出,通过调整堆栈大小解决;还有自己编写的软件模块在重新配置软件包后在工程中消失的问题,最后重新新建源代码文件夹解决;选择MQTT软件包并使能例程后,源代码中没有对应代码,最后发现和软件包版本有关系。总之,开发过程中遇到了一些大大小小的问题,但解决问题的过程就是能力提升的过程,也很感谢主办方提供的这次宝贵的比赛机会。
RT-Thread作品秀】基于ST传感器的跌落及倾倒检测作者:pto1011 概述在消费电子领域,跌落通常会对便携式电子产品(如手机,平板,笔记本电脑)的零部件(屏幕及机械硬盘)造成损伤,如何可靠的检测这些跌落事件并实时启动保护装置,采取主动避震措施来减少触地带来的冲击。中国即将步入老年社会,如何实时检测老人摔倒事件并做到事前主动启动保护减震装置和事后第一时间发出告警。 这些都需要有快速(Fast)准确(Accurate)可靠(Reliable)的跌落及倾倒检测能力。 本应用即是基于ST公司的LSM6DSOX六轴传感器,高速采集加速度计和陀螺仪的信号,通过算法来提供跌落及倾倒实时检测,其结果显示于交互界面(串口输出)和板载LED状态(未进入检测模式:LED blink; 开始进入检测:LED OFF; 检测到跌落或倾倒:LED ON)。 开发环境硬件:ART-Pi STM32H750 开发板,STEVAL-MKI197V1(LSM6DSOX)六轴传感器,PARALAX USB2SERIAL转接板 RT-Thread版本:4.0.3 开发工具及版本:RT-Thread Studio 1.1.15 RT-Thread使用情况概述内核部分:创建了一个独立线程用于实现读取ST传感器参数,输出至高速串口或实时检测,详情见下图。 组件部分:finsh组件是本应用开发过程中最重要的程序调试工具及交互窗口 硬件框架主要使用了ART-PI开发板上的STM32H750处理器的SPI,Timer, UART, 板载资源使用了LED,其它硬件模块包括ST的六轴传感器以及自备的USB转串口板。 软件框架说明软件模块说明演示效果视频演示: 比赛感悟通过参与比赛,第一次接触并了解RT-Thread及其生态系统, 用户体验很好。尤其是Finsh组件,作为交互界面,程序的调试过程中几乎完全依赖于之。 本应用中分别使用了MCU的SPI,UART和硬件定时器组件,UART的使用较为简单,硬件定时器在使用时则遇到了问题,在RT-Thread Setting中配置之后,一直无法进入中断,通过单步调试,发现寄存器在写入数值之后仍全为0,进一步检测发现其时钟未enable。 在拿到ART-Pi板之前,曾设想实现所有传感器参数读取及detection算法都在1ms定时器中断服务自程序中完成,但编译时遇到错误 ,ISR中无法调用mutex,曾尝试移除mutex,但读取的sensor参数几乎全部错误。后来选择在新建的独立线程中来实现,读取的sensor数据全部正确。 通过实测,本应用基本实现了最初的设计目标。仍存在如下问题:在1ms采样间隔下仍会观测到存在数十毫秒未读取传感器信号的现象,期待将来有可能实现RT-Thread下对于高速(亚毫秒级别)的实时关键任务的支持。 最后,感谢比赛组织方提供的硬件和软件开发环境,以及工作人员的热情帮助!
RT-Thread作品秀】家庭医用多功能检测仪作者:李元江 概述背景随着人们生活水平不断提高,人们越来越注重身心健康。现在家用型的医用检查仪器太少,很多人想要检查一些简单的项目,也要专门去医院检查,这样很不方便。普通的家用医用型检查仪器,功能简单,缺少智能性。所以智能家庭医用多功能检测仪器是很有需要的。 功能实时时间显示 网络时间获取、自动对时 天气情况获取、显示 显示室内温度、气压 检测心率、血氧浓度,检测时可以实时查看心率变化值。 开发环境硬件:APT-Pi V1.5 、ESP32-01S 、LPS22HH 、MAX30102 、4.3寸触摸屏(480*272) RT-Thread版本:V4.0.2 开发工具及版本:RT_Thread StudioV1.1.5 RT-Thread使用情况概述内核部分: 任务调度,本软件设计五个执行线程,分别为Main线程、RTC线程、LPS22HH线程、MAX30102线程、TouchGFX线程。 组件部分: Finsh、C++、libc、i2c框架。 C++,i2c在移植TouchGFX需要使用到。 软件包部分: cJson、gt9147 通过cJson解析从网络上获取到的天气数据。 触摸屏触摸芯片为gt9147,所以需要gt9147软件包。 库部分: TouchGFX库 GUI界面显示框架。 硬件框架该设计硬件大体有五大部分:主控、触摸屏、wifi模块、温度传感器、心率血氧传感器。它们的连接框图如下图所示。 主控板使用的是ART_Pi。 触摸屏使用RGB888和I2C接口与主控板进行连接,触摸屏芯片为GT9147。 温度传感器使用的是LSP22HH,LSP22HH是一款兼容IIC和SPI接口的芯片,能够采集温度和气压值,这里使用使用SPI与主控板进行通信。 心率血氧传感器使用的是MAX30102,主要通过IIC接口与主控板进行通信,INT中断输出引脚与主控GPIO进行连接,主控可以根据INT信号,判断数据是否可读。 Wifi模块使用的是ESP32-01S,ART_Pi自带有Wifi模块但是最近没有时间去研究该自带模块该怎么去使用,所以先使用ESP32-01S进行开发。ESP32-01S主要使用UART串口与主控板进行通信。主控板的两个GPIO口与ESP32-01S的CH_PD和RST连接。CH_PD是ESP32-01S的使能信号,RST是重启信号。 软件框架说明本设计使用RT_Thread进行开发,主要设计有五个执行线程,分别为Main线程、RTC线程、LPS22HH线程、MAX30102线程、TouchGFX线程。 总的软件框图如下图所示。 系统开启后,主要进行RT_Thread系统内部初始化,然后进行各个线程的调用。 软件模块说明Main线程进入Main线程后,会先通过Wifi模块获取网络时间和天气情况。这就相当于系统启动后,会先获取网络时间和天气情况,到达开机自动对时功能。之后会进入while(1)循环,在While循环中根据RefreshFlag标志位,来判断是否手动获取网络时间和天气数据。在触摸屏主界面点击刷新按钮,会置位RefreshFlag标志位,从而手动获取网络时间个天气数据。 其流程图如下所示。 RTC线程我这里没使用RT_Thread的RTC,而是自己实现了一个RTC。线程设置为每1S进入该线程,进入后,时间戳数据+1,然后通过把时间戳转化为北京时间,就可以得到时间数据。当然,这里如果长时间运行,时间偏差肯定会大,考虑到设备一般不会长时间使用,所以暂时使用该方案。 LPS22HH线程LPS22HH线程任务主要是获取温度和气压情况。刚开始进入该线程时,先进行SPI的初始化和LPS22HH传感器的初始化,然后每2S调度一次进行获取温度和气压值。 其流程图如下所示。 MAX30102线程MAX30102线程任务主要是获取血氧和心率值。刚进入该线程时,先进行IIC的初始化和MAX30102器件初始化。然后进入一个循环调度过程,在循环调度过程中,根据checkFlag标志位状态,来判断是否进行获取血氧和心率值。CheckFlag可以在触摸屏检测界面进行置位和清零。点击第一次checkFlag置位,点击第二次chechFlag清零。 其流程图如下所示。 TouchGFX线程触摸屏GUI方面用的TouchGFX框架。为TouchGFX设置了一个循环调度线程。该线程主要包括两个任务处理,显示处理和触摸处理。显示处理主要是更新时间、温度气压情况、血氧、心率值和心率曲线值。触摸处理主要是进行界面更换,获取网络时间、实时天气情况,心率、血氧检测操作。 其流程图如下所示。 GUI界面GUI使用TouchGFX框架,主要设计有两个界面:主界面和检测界面。 主界面 主界面主要功能是显示时间、

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

饼干饼干圆又圆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值