修改RT-Thread 的启动流程,实现显式调用rtthread_startup

news2024/11/18 13:58:41

一、STM32 单片机的启动流程

单片机上电后,会首先执行定义在startup 文件 中的Reset_Handler 函数,Reset_Handler 函数会首先执行SystemInit 函数,执行完之后,再执行我们常见的main 函数。
在这里插入图片描述

在这里插入图片描述

二、RT-Thread 启动函数是怎么被调用的?

2.1 启动流程概述

RT-Thread 系统的统一入口函数为 rtthread_startup(),通过利用各个编译器的特性,实现隐式调用 rtthread_startup() 函数完成系统的初始化,启动流程如下:

在这里插入图片描述

这样做的一个好处是:用户可以不用自己去初始化RT-Thread,按照以前编写单片机程序的经验,直接在main函数编写自己的任务程序即可。

2.2 keil mdk 下实现在main函数之前执行rtthread_startup 函数

扩展main函数

keil mdk 编译器用 $Sub$$ $Super$$ 这两个符号来扩展main 函数。可以实现在

  • 在调用main函数时,自动调用$$Sub$$main 这个新定义的函数代替main函数。
  • 通过调用 $Super$$main 函数来实现对main 的调用。

如下是官方文档(https://developer.arm.com/documentation/dui0474/m/accessing-and-managing-symbols-with-armlink/use-of–super—and–sub—to-patch-symbol-definitions)的一个例程

void ExtraFunc(void); extern void $Super$$foo(void):
/* this function is called instead of the original foo() */
void $Sub$$foo(void)
{
  ExtraFunc();    /* does some extra setup work */
  $Super$$foo();  /* calls the original foo() function */
                  /* To avoid calling the original foo() function
                   * omit the $Super$$foo(); function call.
                   */
}

这里新定义了一个 函数$Sub$$foo,在程序中调用foo 函数的地方,编译器调用$Sub$$foo 函数代替foo 函数,如果还想调用foo函数,则通过调用$Super$$foo() 来实现。

实现分析

首先定义$Sub$$main ,在启动文件中需要调用main 函数的时候,调用$Sub$$main 函数。后面再统一介绍 $Super$$main(); 是怎么被调用的。

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

2.3 gcc 下实现在main函数之前执行rtthread_startup 函数

gcc 开发环境下,是通过修改启动文件实现的。在启动文件调用main 函数的地方,改成调用一个新的函数entry(),然后在entry 函数中,调用rtthread_startup 函数。

启动文件修改

//修改前:
  bl  SystemInit
  bl  main

//修改后:
  bl  SystemInit
  bl  entry            /* 修改此处,由 main 改为 entry */

entry 函数的定义

int entry(void)
{
    rtthread_startup();
    return 0;
}

三、rtthread_startup 的实现分析

主要介绍如下三个函数:

3.1 rt_application_init

这个函数创建main 线程

tid = rt_thread_create( "main", main_thread_entry, RT_NULL,
                            RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20 );

main 线程的入口函数为main_thread_entry 函数,该函数定义如下:

void main_thread_entry( void *parameter )
{
    extern int main( void );
    extern int $Super$$main( void );

#ifdef RT_USING_COMPONENTS_INIT
    /* RT-Thread components initialization */
    rt_components_init();
#endif

/* invoke system main function */
#if defined(__CC_ARM) || defined(__CLANG_ARM)
    $Super$$main(); /* for ARMCC. */
#elif defined(__ICCARM__) || defined(__GNUC__)
    main();
#endif

}

该函数主要是调用rt 组件的初始化 和我们编写的main函数。

3.2 rt_thread_idle_init

这个函数负责创建idle 线程,以后设计idle 线程的信息,可以通过这个入口查询。

3.3 rt_system_scheduler_start

在rtthread_startup 函数的最后,使能系统调度,程序将不在从rtthread_startup 函数 后面的语句。

四、修改RT-THREAD 的启动流程

4.1 为什么要修改这个流程?

4.1.1 当程序中要实现OAT 功能时

实现ota 功能时,我们的程序需要重定位中断向量表,按照RT_Thread 的启动流程,我们只能在SystemInit 的末尾 或者在执行rtthread_startup 函数前重设中断向量表,因为rtthread_startup 函数中涉及到中断的处理和中断的调用,在它之后已经不合适。

我既不想修改SystemInit 函数,也不想每个工程都去修改一次RT-Thread 的源码去插入一句格格不入的中断向量重定向,我希望在main函数中调用中断向量重定向。

4.1.2 当工程使用gcc 开发时

在使用gcc 开发时,需要修改启动文件。当切换不同容量的芯片时,需要更换启动文件,这时候容易忘记更改启动文件。

另外,我也不想修改启动文件。

4.1.3 当工程有些信号需要上电即刻处理时

按照默认流程,上电先执行rtthread_startup 函数,在任务调度至main线程时,才执行我们的代码。当有些信号需要上电即刻处理时,我们只能去修改RT-Thread 源码了,在RT-Thread 源码中插入一堆应用相关的代码,不合适。

4.2 尝试修改?

直接注释components.c 中 rtthread_startup() 函数的调用处理,然后将rtthread_startup 的调用放到main 中执行,这样行不行?

不行!!!

通过上面的分析可知rtthread_startup 创建了main 线程,main 线程的入口函数调用了main函数,如果我们直接把rtthread_startup 放到main 中执行,相当于形成了一种递归调用,直接死机。

4.3 目前可行的一种修改方式

  • 在main 中调用rtthread_startup 初始化系统;
  • 重新定义 main 线程的入口函数执行的函数
int main( void )
{
    rtthread_startup();
    return 0;
}

简化main 线程的入口函数,在调用main 的地方,调用_main_thread_entry();

void main_thread_entry( void *parameter )
{

#ifdef RT_USING_COMPONENTS_INIT
    /* RT-Thread components initialization */
    rt_components_init();
#endif

    _main_thread_entry();
}

新定义 _main_thread_entry 函数,调用我们的应用程序

int _main_thread_entry( void )
{
    app_log( "_main_thread_entry\n" );
    //...
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/163798.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

什么是最少知识原则?-外观模式

外观模式将一个或数个类的复杂的一切都隐藏在背后,只显露出一个干净美好的外观。 构建自己的家庭影院 //打开爆米花机,开始爆米花 popper.on(); popper.pop();//调整灯光亮度 lights.dim(10);//把屏幕放下 screen.down();//打开投影仪 projector.on();…

【阶段三】Python机器学习32篇:机器学习项目实战:关联分析的基本概念和Apriori算法的数学演示

本篇的思维导图: 关联分析模型:Apriori算法 关联分析的基本概念和Apriori算法 关联分析是数据挖掘中一种简单而实用的技术,它通过深入分析数据集,寻找事物间的关联性,挖掘频繁出现的组合,并描述组合内对象同时出现的模式和规律。例如,对超市购物的数据进行关联…

缓存Cache-Control

可缓存性指定哪些地方可以缓存publichttp请求返回的过程中,http请求返回的内容所经过的任何路径包括:中间的代理服务器,发出请求的客户端浏览器,都可以对返回的内容进行缓存。private发起请求的浏览器可以缓存。no-cache任何节点都…

【程序员高效率工具】PlantUML —— 使用代码快速绘制时序图、思维导图

本篇思维导图 前言 不管是在工作还是学习,特别是在项目计划初期,我们需要画大量的图将工作内容、项目方案等进行可视化描述,包括但不限于时序图、类图、思维导图等等。 但是对于不经常画图,或者经常使用键盘的孩子,手…

VMware三种网络模式的摸索

VMware三种网络模式的摸索 文章目录VMware三种网络模式的摸索前言一、桥接模式简要描述拓扑图展示配置测试优缺点二、NAT模式简要描述拓扑图展示配置测试优缺点三、仅主机模式简要描述拓扑图展示配置测试优缺点3.总结前言 注意:所有的测试请关闭虚拟机和主机的防火…

微信小程序 - 实现手机号登录--授权并获取手机号保存至本地

详细代码请见文档最下方,仅供参考,更多需要请查看官方文档 一、 微信官方文档 | 获取手机号 这是服务端的 这是我们前端获取手机号需要给接口传递的两个参数 注意: 参数一:获取access_token需要用到小程序密钥,这个…

你可能不知道的20个Git命令,但真的很实用

如果您曾经浏览过git 手册(或 run man git),那么您会注意到 git 的功能比我们大多数人每天使用的要多得多。很多这些命令都非常强大,可以让你的生活更轻松(其他命令有点小众,但仍然很高兴知道)。…

QT-QStackedWidget多窗口应用

前言: 多窗口应用,例如某微信,页面由1,2,3个布局组成。 1-基本流程 页面1控制页面2,通过选择页面1上的按钮或控件 页面2控制页面3,通过选择页面2上的按钮或控件 2-其中页面2中的页面很…

100、【树与二叉树】leetcode ——105. 从前序与中序遍历序列构造二叉树+106. 从中序与后序遍历序列构造二叉树(C++版本)

106. 从中序与后序遍历序列构造二叉树 题目描述 原题链接:106. 从中序与后序遍历序列构造二叉树 解题思路 中序的特点:左中右,后序的特点:左右中。因此可通过后序序列找到中间结点,然后再根据中间结点,分…

3、关键词与标识符

目录 一、关键词 二、标识符 一、关键词 C语言中有32个关键字: 注意:在C语言中,关键字是不允许作为标识符出现在程序中的。 二、标识符 C语言标识符的命名规则: (1)所有标识符必须由字母或下画线开头…

KMP算法 看这一篇就够了 图解刨析+代码

目录 问题背景 逐步剖析 KMP如何优化暴力做法 思考 公共前后缀 next数组 如何构建next数组: 代码实现 问题背景 给定一个字符串 S,以及一个模式串P, P 在字符串 S 中多次作为子串出现。 求出模式串 P 在字符串 S 中所有出现的位置的起始下标。 …

说话人识别中的Temporal pooling(时序池化)

概述 Temporal pooling(时序池化)是说话人识别神经网络中,声学特征经过frame-level变换之后,紧接着会进入的一个layer。目的是将维度为(bs,F,T)(bs,F,T)(bs,F,T)的特征图,变换成维度为(bs,F)(bs,F)(bs,F)的特征向量 …

再不来看看常用的PyCharm快捷键就out了,玩转PyCharm仅此一篇!

最近在学习Python的数据可视化项目,在大学有学过Python,还有一些基础的。目前虽说已经工作,但是兴趣使然,依然想在空闲时间学一些其他技能来充实自己,未雨绸缪! 在使用工具的时候,必定会对工具有…

【话题:工作生活】2021年工作总结--这些人,那些事。

Hello Everyone, 我又开始撰写自己的工作总结了。2021年的工作总结,拖得太久,拖得我也不想写了。每次写自己一年的工作总结,总是要耗费我大量的时间与心力,有时,我也真的是心好累。 好了,懒散、…

【人工智能原理自学】隐藏层:神经网络为什么Working

😊你好,我是小航,一个正在变秃、变强的文艺倾年。 🔔笔记来自B站UP主Ele实验室的《小白也能听懂的人工智能原理》。 🔔本文讲解隐藏层:神经网络为什么Working,一起卷起来叭! 目录一、…

深度学习 11 梯度下降算法改进

数据初始化要点: 1. 梯度下降算法的三种方式: 批量梯度下降法(batch),即同时处理整个训练集.小批量梯度下降法(Mini-Batch )每次同时处理固定大小的数据集.随机梯度下降法(stochastic gradient descent), 每次随机选…

acwing基础课——约数

由数据范围反推算法复杂度以及算法内容 - AcWing 常用代码模板4——数学知识 - AcWing 基本思想: 首先,约数,又称因数。整数a除以整数b(b≠0)除得的商正好是整数而没有余数,我们就说a能被b整除,或b能整除a。a称为b的…

怎样才能过好这一生?

文章目录1. 日拱一卒,功不唐捐1.1 适当的时候给自己一个奖励1.2 一个人可能走的更快,但一群人才能走的更远1.3 通过一些事情去逼自己一把1.4 从真理中去感悟1.5 当你面临绝路时2. 梦想的意义不在于实现3. 孤独4. 烦恼5. 别总说来日方长6. 忍和韧性7. 事情…

【linux kernel】linux内核重要函数 | do_initcalls

文章目录一、导读二、do_initcalls三、构造section并添加函数(3-1)构造初始化调用section(3-2)向section中添加函数四、总结一、导读 在linux内核启动过程中,会向终端打印出很多的日志信息,从这些日志信息…

c++开源协程库libgo介绍及使用

协程这个概念,最近这几年可是相当地流行了。尤其 go 语言问世之后,内置的协程特性,完全屏蔽了操作系统线程的复杂细节。甚至使 go 开发者“只知有协程,不知有线程”了。当然 C也有高性能的协程库,比如我了解到的微信的…