5655|9

7815

帖子

57

TA的资源

裸片初长成(中级)

楼主
 

【自制DA14531手环(仿小米手环4/5)开发套件】配套教程之2:FreeRTOS移植 [复制链接]

本帖最后由 辛昕 于 2024-1-21 01:04 编辑

写这个帖子呢,我多多少少有点虚。

但是,本着 干就是了 的原则,我决定还是写吧。

 

重点,当然是在实际的移植上,不会像第一个帖子那么,写的一发不可收拾。

但是,实际的操作之前,还是简单说一下 FreeRTOS,或者说 RTOS 的 基本原理。

因为,从我自己个人的学习过程经历而言,我觉得,我是一个没办法 只知其一不知其二 的人。

这个过程,会涉及一部分 ARM内核、汇编的一些东西。

所以整个说起来会有一点点零碎,但本着够理解就成,大家加班这么忙,没必要纠结的太细。

 

好吧,这个主楼就写到这里,我们在下面来继续写。

最后,先补个图——是目前DA14531项目工程里,FreeRTOS的部分

 

最新回复

点赞分享分享   详情 回复 发表于 2024-1-31 22:21
点赞 关注
个人签名

强者为尊,弱者,死无葬身之地

 

回复
举报

7815

帖子

57

TA的资源

裸片初长成(中级)

沙发
 
本帖最后由 辛昕 于 2024-1-25 14:41 编辑

进程 和 线程 的简单介绍

 

        在写这个帖子的时候,对我来说,最难的东西,并不是如何描述移植的

——其实,那是相对很简单的事情。

        难的也就是后续在实际测试、运行的时候,会让你加深一些概念的认知过程。

 

        最难的,其实是如何简单介绍 线程 的概念,和它其实是什么。

 

        为此,我百度了很久,也翻了很久的 Cortex M0权威指南,却始终没找到很好

的解释。最终,我不得不去参考更一般的操作系统——或者你可以理解为那些真正

在电脑上跑的Windows Linux这样的操作系统语境下的 进程 和 线程 的概念。

 

《现代操作系统》(第4版) 这本书我已经上传到下载中心,可0积分点击下载

 

 

参考这一段文字。

    程序的运行,用最简单的方式来说就是——

    1、CPU取指,从代码区里取得后续要执行的指令内容,这个过程,需要用PC指针

来记录当前执行的代码位置。

    2、在代码执行过程,会有函数调用、响应中断等代码跳转,而这并不只是简单

的PC的跳转,因为它还存在需要对当前运行状态的上下文保存——所谓运行状态,

说到底,它们被一组CPU能直接访问的寄存器所保存;

        所以,所谓保存运行状态,说的就是这组寄存器的值;而我们一般常说的“上下文”

说的也就是在这些调用、跳转过程中,需要保存的跳转前后的运行状态;

 

        而在这个过程中,从CPU取指、(译码)、运行,以及这个过程里,所涉及到的寄存

器等,它们组合起来,可以说,就是一个 进程概念

        至于线程,它和进程,是一种包含关系,而线程和进程最大最大的根本区别,也就是,

进程拥有自己的独立的运行状态(用一组寄存器数据来保存),以及相应的其他资源,例如内存等;

        而线程...

 

 

        关于进程和线程的区别,其实还比较复杂,尤其是对于主要只有MCU编程开发经验的我,如果

有需要,后面我们可以继续去阅读《现代操作系统》(第4版)这本书后续关于线程的详细介绍——它

确实解释的非常详细。

            但我们从中可以得知,线程,类似于进程

        不同的线程,为了保存程序运行的上下文,它,同样需要对运行状态(寄存器)的保存和恢复;

 

        对于了解ARM内核(或者其他内核)的编程模型的人来说,会觉得很熟悉。

        ARM有16个通用32位寄存器组,当然,其中也包含了我们熟悉的PC SP(栈指针,ARM其实是两

个MSP和PSP,但同一时期只能用一个)。

        所有这些的存在,当然是为了配合CPU来工作。

 

        我们以此,来对比,重新来简单看看MCU的工作过程。

        首先,是我们最基本的常见的裸机编程,也就是 main() 和 一系列 中断 isr。这个过程中,因为响

应中断isr的过程,其实要比函数调用过程复杂,所以,两者还是有区别——响应中断需要压入更多寄存器;

      

       当然,和Windows Linux这类系统不同,MCU上,往往永远只存在一个进程,主进程——我们写程序

的人就知道了——我们不就从头到尾只有一个main()函数么?

       这就好像我们在Windows/Linux上写程序一样,我们每次写,也是一个main函数,而除非我们在程序

创建新子进程,新进程,否则,永远这个程序跑起来,在内存中也只有一个进程。

       相比之下,我们在MCU里写程序,是没有办法创建子进程、新进程的。  

 

 

 

个人签名

强者为尊,弱者,死无葬身之地

 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

板凳
 

        至此,我们可以以上面 进程 和 线程 的基础概念介绍,现在,我们可以来看一下,FreeRTOS,作为一个

具有多任务,且任务具有独立栈的RTOS内核,它其实是什么。

       也可以说,它需要实现什么。

 

       ——说得简单点,就是要在不同的任务代码里切换,切换的过程需要修改PC指针,而且要对上下文进行

保护(当然,我对于这个过程,是否需要一个独立栈的理由,并不十分清楚,以后有机会弄清楚再补充说明。)

       但无论如何,我们都知道,FreeRTOS在实现这个线程之间的切换过程中,它不仅需要修改PC指针,实现

寄存器组、堆栈等的上下文保存恢复,它还需要实现自己的独立任务栈。

      

       而对于ARM内核来说,它存在一个不同编程模式的问题——

       平常运行应用代码的时候,ARM处理器处于线程模式,而当它进入异常(复位啊、Hardfault等异常模式,

类似于中断)时,它会切换到处理模式。

       而对于我们实现多线程需要的修改PC、寄存器等内容的时候,我们需要进入SVC异常。

 

       与此同时,为了及时响应不同的事件——这就涉及了优先级问题了,我们需要实现一个何时作为进入SVC

异常去切换任务的机制——

       因此,我们需要实现一个时间片机制。

 

总的来说,为了实现多线程,优先级可抢占的RTOS内核,它需要实现以下几个基本机制:

    1、一个时间片,说到底,无非就是对运行时间进行定时计数;

一般情况下,对于ARM处理器来说,我们自然就选择SysTick,当然也可以选择别的;

    2、能够进入SVC异常,以便修改PC切换任务,保存恢复寄存器组内容以实现上下文切换;

    3、当然,另外,RTOS还要实现一个对中断服务的上下片处理机制——关于上下片的介绍,这是一个在

Linux等操作系统里可以找到的详细介绍,这里就不多介绍。

    只简单介绍它的实现方式:它允许把一个处理过程分成两部分:其中一部分,需要尽快,及时处理,否则

就会丢失的——比如说中断发生时,我们需要马上把现场接收到的数据或者采集到的传感器数据马上保存,

否则就会丢。

    而随后对他们的处理则可以稍微往后处理。

    于是,我们把前者放在中断isr里直接处理掉,称为上片,把后续可以延迟处理的称为下片。

    而它的实现,涉及到ARM内核里的另一个异常——PendSV。

 

    对此,我们需要特别注意的一个问题是:

    既然,是采用时间片机制来实现多任务的切换时机,那也就是说,无论设置了多高的优先级,某个事件的响应时间,最坏情况,是至少是一个时间片的时长——

    而这,在我们考虑如何设置时间片时长,以满足我们实际设计的的保证响应时间要求时,是必须考虑的

个人签名

强者为尊,弱者,死无葬身之地

 
 
 

回复

135

帖子

0

TA的资源

一粒金砂(中级)

4
 

愿意写并分享出来就很不错了,能讲清楚过程和原理那就是妥妥的大佬级别

点评

哈哈,看起来,意思是我还不够大佬,我努力,我努力  详情 回复 发表于 2024-1-25 14:39
 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

5
 
中国商风 发表于 2024-1-25 13:41 愿意写并分享出来就很不错了,能讲清楚过程和原理那就是妥妥的大佬级别

哈哈,看起来,意思是我还不够大佬,我努力,我努力

个人签名

强者为尊,弱者,死无葬身之地

 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

6
 

移植过程<一>:

为了来个完整,下面说的这些,其实网上很多都有,但是,还是来一趟

FreeRTOS的结构

你把FreeRTOS下载下来,解压,它长这样。

但是对于一个只是想移植内核的人来说,你压根不用管什么aws

 

 

打开后有很多东西,然后只需直入 Kernel

当然,我温馨提示你,还是先按类型查看,因为真的有太多影响视觉的东西。

 

好了,关于移植的具体过程,从这里开始说——

include文件夹,人如其名,肯定是头文件,不管。

剩下的几个.c,随后,直接哐哐哐,全加上就行,别客气。

 

当然,怎么安排文件夹是你的事,比如你也可以把上面这两个合到一起。

 

下面开始一些,只和我们这个DA14531有关系的东西

个人签名

强者为尊,弱者,死无葬身之地

 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

7
 

移植<二>DA14531相关

 

我相信,你的第一个问题会是 port.c 在哪?

因为你一搜索,会有很多个port.c

所以,我们现在要说的是——

 

打开就这玩意

 

这里说一下——

等到你后面走完整个FreeRTOS-Kernel,你会知道,整个过程,基本没别的什么事,全都只和内核有关。

 

换句话说,只要你把内核里,用来实现我们前面提到的那三个事情,就可以完成移植了。

1.一个定时器——老实说,这个也没内核什么事,MCU谁还没个定时器。

2.需要一个可以修改PC 栈这样的机制——比如ARM内核里的SVC;

3.可以实现异常延迟的机制,例如ARM内核里的PendSV——这个东西是拿来实现上下片机制的;

 

简单地说——FreeRTOS能不能移植和好不好移植——除了你ROM和RAM不能太少之外,剩下的就是

你的这个内核,如果——如果本来就有移植,那就真的太好了。

 

而FreeRTOS内核里已经为我们做好了这种移植。

但是,如上述所言,这些东西,一般都是用汇编语言写的,而不是C/C++代码,但汇编代码的格式,和不同的IDE

和编译器是有关系的。

所以,你能看到上述这个portable里,它首先区分的是不同的工具链,而不是别的。

 

我们随便挑一个GCC,往里头看

 

现在,才是内核架构。

 

现在,来说说,DA14531,你得选谁。

鉴于官方给的例子,全是基于 Keil5 MDK。

所以一开始,你也会想到,选择 RVDS——RVDS和MDK的关系,怎么说呢,他俩是一家吧,大概这么解释。

 

然而,等你编译的时候,你就会发现,它哐哐哐地报错。

 

理由是——

前面我就提到过,Keil5 MDK最近几个版本,开始使用新的AC6编译器替代老的AC5编译器,很遗憾的是

FreeRTOS-Kernel的RVDS移植代码,仍然使用的是AC5。

 

这个时候,你可能会以为你有两个选择——

一个比较爽的是——那我就用AC5编译DA14531例程就行了呀——

想得太美,就像CSDN上一个哥们说的一样——

 也不知道咋回事,说真话就给屏蔽了,只留下这个小可爱~~~~

 

但是,讲真,你要是想试试你就知道了。

那人家DA14531整个SDK都是基于AC6编译的,你就说你是想换一个FreeRTOS portable还是想整个SDK给它整一遍。

 

对了

那被屏的哥们的原话是——

“真牛逼,编译器说改就改”。

 

现在,我们来看看这哥们自己写的文章,这才是正路,当然,其实重点就是一句话

 

当然,其实我想说的是——

为毛要把 GCC里的文件复制到 RVDS里面呢?

直接用GCC里的不就完事了。

 

 

对,不要想不开,尝试自己去把原来RVDS里的代码进行修改——

这事一开始我真干过,挺麻烦——搞到我当时以为,完犊子了,看来为了完成这个移植,我可能真的要变成精通

ARM汇编才行。

 

但是,感谢上面这个被折叠了的哥们——

它让我体会到什么叫 ——会者不难,难者不会,是的。

使用GCC下的port,直接一步编译通过。

 

一开始我还担心,这样子,能不能正常跑起来呢......

还好,现在证明,真的可以,而且很正常。

  

 

 

 

个人签名

强者为尊,弱者,死无葬身之地

 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

8
 

FreeRTOS移植<三>:Task的Static和(Dynamic)

 

一般来说,许多教程,上来都会用 xTaskCreate

由于这个名字是如此的名正言顺,以至于在很长一段时间里,老实说,我都不知道还有个东西叫

 

那这个东西的意义在哪里呢?

意义就在对heap的大小。

这个heap——

 

一开始,限于DA14531的RAM比较有限——别看大咧咧的有48K, 但人家代码也要LOAD入RAM,那就是说,大概率最后你只能有最多16K左右的DATA-RAM使用。

所以,初始,我只给它安排了256B。

 

结果我就发现了一个很神奇的现象——

它能顺顺利利地编译,但是,它跑不起来。

这个时候,我会胡思乱想了一个晚上,各种百度,愣是没想明白——那会我还没想到用JLink,主要是我也不知道它死机了。

虽然它进HardFault了。

 

后来,由于我在百度的时候,看到 xTaskCreate居然还有static。

在我百度这个static的时候,我突然联想到会不会是heap小了。

于是我才搞定了问题。

 

后来我实测的结果—

 

这里,我顺带验证了一个问题。

使用jlink-rtt,当然,我相信,并不是rtt本身使用的ram大,而是因为,我用到了rtt的打印函数

 

我的意思是,如果你用了printf函数,那么,大概率,你也得比不用多消耗128Byte RAM

而且,这还是不支持浮点数的版本——RTT本身不支持浮点数 %f 这个格式控制符。

 

排除这件事情,我们看下面的这个 1024 + 256 + 128 这 1.75KB左右的 heap。

 

后来进一步,如果我关掉 dynamic_heap 的支持

 

这时,我们再来测试,到底需要多少 HEAP。

上面的截图是,15Byte都可以。

当然,这是只建立一个简单任务的情形,但这两者的差别告诉我们——

 

默认使用的 动态heap情况,比起不用,heap的消耗差别 可以差 1.75KB左右。

 

 

个人签名

强者为尊,弱者,死无葬身之地

 
 
 

回复

7815

帖子

57

TA的资源

裸片初长成(中级)

9
 

上一楼图有点多,

所以关于 dynamic 和 static TASK的区别

我们在这里说——

其实不复杂:

如果是使用static,你需要自己传递进去一个已经分配好的空间——

可以是malloc分出来的,也可以是一个静态数组。

但是,如果你在这个任务里,用到的heap——这个heap对于FreeRTOS来说,它可不只是这个

任务的heap,它意味着这整个task的RAM。

 

所以,就像平常我们的裸机程序而言,一旦ram用超了,自然也就爆栈了。

 

而使用Dynamic的情况,它会灵活一点。

当然,我必须很老实承认,由于我没有去看具体的FreeRTOS内核的实现源码,所以,目前,我的确不知道它

是如何灵活的。

 

不过——有一点是我曾经想测试,但还没测试的部分:

我以前在使用其他RTOS时有碰到类似的情形,我怀疑dynamic的灵活,其中一个表现的地方就在这里。

例如说——

如果我两个任务里都用到了比较消耗RAM的代码,例如 RTT的 Printf,那么——

在static的情况下,是两个任务会分别消耗一次,还是说,两个任务共用一份?

 

不过有一点就是——

默认情况下,我们使用的都是dynamic方式,但是,它的代价就是,要多使用1.75KB左右的RAM。

而对于 DA14531来说,这不是一个小比例。

个人签名

强者为尊,弱者,死无葬身之地

 
 
 

回复

40

帖子

0

TA的资源

一粒金砂(中级)

10
 

点赞分享分享

 
 
 

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

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表