笔者刚入门学习STM32的时候就接触了uCOSII,它的多任务让我眼前一亮:原来程序还可以这么写!它让很多冲突的代码都能各自运行,在一个单片机上实现了多个单片机共同运行的效果。但是当开始真正去学习它时,却感觉寸步难行,而网上的有关学习资料又大多是直接甩给你一个可用的工程,很难有一个把它的原理和使用方法讲得清楚的,原因也简单,一是大多数人也没有深入地去了解过它的实现原理从而无法把它讲得明白,二是大多数资深的工程师大多不善表达也不愿花时间在网上发表教程,当然也可能有更多的原因,但是对于大多数在用裸机工程的人来说,对RTOS一直都是处于:听说过RTOS大名,但不敢轻易去接触。大部人对RTOS的态度是:1.我接触到的设计一般99%都能用裸机实现,没必要上RTOS;2.RTOS没有裸机稳定,学了也白学;3.RTOS太难了,我裸机都整不明白,不想去折腾RTOS了!
笔者就是遇到了第3条,RTOS太难了,在学习uCOS那段时间,几乎都是处于懵的状态,但是还是坚持了下来,在经历了这段痛苦的学习阶段后,我想,能不能做一个比uCOS或FreeRTOS简单的适合用来学习RTOS的简易系统呢?于是就写了eOS这个简易的OS,e就是easy简易的意思,以下就是eOS的简介。
eOS是针对新手开发的一款学习型RTOS,其特点是文件少、函数少、代码少、函数名简单易懂,系统结构清晰明了,应用切换原理一目了然,开源、免费。声明:由于是学习型RTOS,只适合用来学习RTOS,如商用出现问题作者概不负责。
一、eOS适合场景:
RTOS新手,作为入门级RTOS学习使用,是了解RTOS运行原理的更好工具。小作品(不涉及安全及有可能有重大损失的),可以DIY小作品。学生实验,对于学生实验级别的,很大程序上是不需要考虑商用产品的安全可靠性,一般只需要实现功能为主的。学习研究,适合对写RTOS感兴趣的人,这是一份很好的资料,从汇编代码到C语言都有把RTOS的运行原现讲得通俗易懂,更能让人快速理解原理。成熟可靠商用RTOS的学前班,学习eOS后再去看uCOS/FreeRTOS/RT-Thread等会更简单轻松。
二、解惑:
eOS和其他RTOS有什么区别?
这个问题其实不好回答,因为所有RTOS的本质都是一样的,虽然外表包装不同、名字不同,但是其原理和算法基本如出一辙。举例子:uCOSII是早期的RTOS产品,其以稳定安全易用而著称,也是最早开源的一批RTOS,其代码风格对于新手来说是非常友好的,它是一款抢占式RTOS,即系统只要有一个任务就绪,系统就会对比此任务和当前正在运行任务的优先级别大小,优先级大的运行。有人说,为什么要抢占,因为RTOS叫做RealTime OS实时操作系统,强调的是实时性,即有一段代码必须立刻马上得到运行,十万火急,它要运行的时候,系统必须要保证它能第一时间得到运行,这样势必可能会抢占当前正在运行的程序,所以叫做抢占式。一般这种任务是不经常运行,但是一旦它需要运行,就要保证能第一时间获得CPU使用权来运行。为了实现这种“特殊”要求,uCOS运用了一种算法来保证每次比较(也叫查找)最高优先级所耗费的时间是恒定不变的。听起来似乎不可能,因为在学C语言的时候,见到过一种求最大值和排序的算法—冒泡排序法。只排一次,就能得到最大值,但是却要扫描(遍历)完整个数组才能保证得到的一定是最大值,这带来的问题就是当任务数量(优先级)很多时,随着数量的增加耗费的时间也在增加,这不利于“RT”的要求。所以uCOS的作者运用了查表法来查找最高优先级,这个算法的核心思想就是事先把所有可能出现的结果都列成一张表(数组),然后只要想取结果时,只要往里面放入要比较的序号值,就能直接得出结果。这种方法优点是查找时间短,不受任务数量影响(在允许数量范围内变动),这种方法也叫“空间换时间”算法。因为可能出现的结果是非常多的,就要提前准备一张包含所有结果的数组,可想而知,这张表是可能需要耗费很多内存的,比如最大16个优先级情况下,就需要2^4=16个字节,最大64个优先级情况下,就需要2^8=256字节,如果是256个优先级,需要2^16=65536字节,而且这种方法是一张表用了利用了2次了的,不然还会更多。即把任务数量分成X*Y的坐标表,16个任务为4*4,所以每一行必然可能出现2^4=16结果,每一列也必然出现2^4=16个结果,这样只要它们的结果在行和列方向上都适用,就能一张表用于行和列上的查表,这就是一张表用2次的原因,X方向查一次,Y方向查一次,就能得出最高优先级的准确坐标值了。具体实现方法可以参考eOS源码,eOS就是采用了这种核心思想来实现查找最高优先级应用的,当然,其他RTOS也是采用的这种查表方法来实现查找最高优先级的,如FreeRTOS,RT-Thread等,每家的具体实现方法大同小异,但核心思想都是查表。所以说RTOS大多如出一辙就是这个原因,在此基础上,每家都有在外在功能上作出了不同特色,如代码风格、文件结构、接口、内存管理等。这也造就了他们不同的风格。说到这,其实你就知道选择学哪个已经不重要了,只要你明白其深层次的原理,其他的都可轻松学会。有的初学者或用了RTOS多年的程序员为何总是面对RTOS出现的”BUG”一筹莫展,我想,和不熟悉RTOS原理就直接上手RTOS硬依样画葫芦堆出程序也有一定关系。
2. eOS优势是什么?
开头就说过了,eOS是针对新手开发的,但绝不是uCOS的精简版或盗版之类的。由于RTOS比较简单,所以很难在开源代码上做出亮丽的区别,就好比如手机的外形,众多厂家无论如何都避免不了“同质化”。但是当你学习了其中的原理后,你会发现在同质化的当中可以添加一些个人想法,在别人眼里或许不值一提,但对于你来说,就是又一次成长的收获。所以eOS不是强调比其他OS怎么怎么好,而强调的是怎么去更简单地学好、轻松地学会。当你消化了eOS的原理,你再去看FreeRTOS或uCOS时你会发现无师自通,其本质也就那样。
三、eOS之旅
1>
1. 任务的概念
任务(或线程)是指一个单一顺序的控制流程序,也就是一条路走到底的程序,比如我们常见的main()函数,它就是一条路走到底,只是在底部又被我们用循环从头再运行,本质还是一条路,好比加工厂的流水线,main就是一条流水线,很明显一条流水线的效率是很低的,只要其中任何一个环节脱节,就能使整条流水线阻塞死。举个例子:
当程序中有一个LED需要以1Hz频率不断闪烁,同时,数码管需要5ms刷新一次数据(动态扫描),这是一个经典的程序,我想应该有不少刚入门51单片机的朋友碰到过,虽然解决的办法多种多样,但是这个程序却刷新了我们对程序的认识,在以后的生涯中,肯定或多或少不可避免地碰到这种需要同时且矛盾的运行情况,当涉及的对象过多时,安排程序可能就需要费上一翻工夫,遇到需要移植的程序那可能会让人痛不欲生,或许有的人也想过,我能不能用多个单片机来共同完成呢?肯定是可以的,多个矛盾的对象分别使用不同芯片单独工作,它们之间可用通讯接口来传递消息和同步程序,这是一个办法,但是成本之高可以想象。在此种种需求下,多任务系统应运而生,听名字就知道它是一个由程序代码构成的系统,开发人员可以在这个软件系统平台上开发满足一定需求的二次代码,这种需要依赖软件系统平台的代码我们称为应用程序,那么脱离了系统平台还能运行吗?是可以的,但是一片mcu就只能运行一个任务了,不能运行多个任务了,现在你应该对软件系统有个模糊的认识了,它就是一个任务“组织管理者”的角色,没有了组织者,当然就不能同时运行了。所以不必担心自己的代码万一没有OS的情况下不能使用的问题,如今RTOS泛滥,但其实都已接近套路化和模式化,只要了解其中一个的套路和内涵,其他的也能依葫芦画瓢出来。话说回来,接着上面的问题,为了解决这个矛盾,所以我们不得不引入中断机制,中断是中止当前程序去运行另一程序的有效手段,这就好比当流水线中有工人临时有事需要暂时离开一会时,可以关掉这个流水线的电源,让其也暂停运行,然后把剩下的工人叫去另一条新的流水线上工作(假设此流水线需要的人不多),这样就不会因为第一条流水线人手不够而影响生产效率,有人会说重开一条流水线多麻烦,这只是一个假设,在单片机中,跳转去运行另一段程序是瞬间的事,这里不必较真。也可能有人想说,为什么不多条流水线工作呢,因为我们只有一个芯片(单核),一个芯片同一时间只能做一件事。为此我们再举个例子,当只有一条流水线时,由于流水线中,有的工序比较耗时,有的不怎么耗时间,假如其中有一个工序是需要等产品的温度下降到一定值才能进行下一工序,那怎么提高效率呢?让工人干等着降温太浪费人力资源,所以为提高效率,可以在冷却的过程中让工人转去做其他事,同时定一个闹钟,闹钟响了再回来继续工作。这种把等待的时间利用起来的角色就是组织管理者。在单片机中,这种中断资源是是有限的,我们不能任意使用,而且多个中断嵌套也造成了程序设计的复杂化。所以,如果有一个程序,已经把管理程序写好,开发人员只需要在上面写任务程序,这样就很方便开发。
我们来看个简单程序,
这是常见的利用定时器中断实现1HzLED闪烁和5ms刷新数码管的方法,
再看另一个常用的方法
可以看到,裸机环境下实现方法多种,但是不可避免的是,程序的可读性和模块之间的相互独立性都较差,所以移植性也较差。
再看一组多任务操作系统的
对比可以看到,在多任务操作系统的加持下,每个模块的代码都是“同时”独立运行的,就像是多个单片机的main()函数被组织到同一片mcu上运行一样。可读性、移植性、代码编写等都变得非常轻松,不需要过多考虑安排解决各个模块的冲突关系,而且代码离开操作系统后单独拿出复制粘贴在裸机工程的main函数上面就能使用,不用担心代码的重复使用性和适用性。从上面程序可简单看出,任务的编程其实和裸机的main()是一样的,所以操作上完成的应用程序并不是特别依赖操作系统,单独拿出来到裸机中也照样能运行,只是多个模块不能同时运行了而已。
读到此处,相信也应该对多任务和裸机区别有了一定的认识,但操作系统不仅包含多任务功能,在如今的RTOS中,已经在多任务基础上扩展出了许多实用性功能,比如任务之间的同步信号、互斥信号、消息队列、邮箱传递等功能,有的还加入了人机交互的如linux上的命令行功能等,在单片机性能越来越强大的今天,RTOS的作用将会越来越大,人们的需求也会越来越高,快速高效兼功能丰富的开发更符合飞速发展的时代要求。
有眼尖的朋友可以看到,上面提到的“同时”加了引号,RTOS表面上看起来像是多个任务并发运行,其实本质并不是,在单核心mcu下,是无法做到程序并发运行的,但是由于多个任务之间的切换比较快,欺骗了人的眼睛,所以眼见不一定为实,其原理和数码管的动态扫描刷新是一样的,在快速扫描的情况下,多位数码管在人的眼睛看来就是同时显示的,但其本质是同一时间只有一个数码管在发光。
明白了需求,就有了明确的目标,那么如何才能学好RTOS呢?就让eOS带你进入RTOS的大门吧!
2>
系统详解
1.系统组成
RTOS的核心共性就是都包含任务切换内核(程序代码)、任务优先级查找算法、任务的创建、任务的调度。
就目前来说,任务切换的代码是由汇编语言完成的,其他都可以由C语言全部完成。
eOS全部文件共有5个:
为了让新手入门更轻松,系统文件数量压缩到5个。其中带port命名的为和移植有关文件,其余的为和平台移植无关的文件。
系统运行流程:
文字说明:os_user_init()是包含在os_init()内的,是统一存放用户初始化代码的地方。在这里可以使用delay_ms()和delay_us()延时函数,但是禁止使用os_delay()函数,因为此时只打开了系统定时器,还没有打开系统运行。
上面图示的app_x是假设的最高优先级任务,app_y是次最高优先级任务,而app_01是最低优先级任务,只有当app_x和app_y同时处于系统延时时,app_01才能得到运行。eOS中,任务调度有两个地方能触发调度和切换,一是os_delay(),二是每隔1ms(系统时钟)也会调度一次。但两个是有区别的,os_delay()一定调度且切换,而系统时钟是定时一个单位时钟到时后一定产生调度1次,但是会和当前任务对比,如果调度后查找到的最高优先级任务还是当前正在运行的任务,就不会产生切换,如果不是,就会产生切换,这是唯一区别。
Cortex-M内核任务切换的原理重点提示:
操作系统的运行环境保存和恢复(上下文),其实就是备份和恢复所有cpu寄存器。
程序的保存和恢复在裸机中就有的,只是很多人没有发现,那就是中断,如外部引脚中断、定时器中断、串口中断等等,发生中断时,单片机会把当前的环境全部压栈,退出中断时会恢复环境,不过这些操作一般编译器或硬件会替我们完成,所以玩C语言的人没有发现是正常的,在cortex-m内核中,发生中断只保存8个寄存器,剩下的R4-R11是不保存的,那会不会出错?不会,因为C编译器编译成的汇编都是只使用了R0-R3、R12这几个寄存器来保存临时变量或中间数据,不够用的话会临时把不是当前需要运行的临时数据压栈从而腾出寄存器来使用,处理好当前的数据后就可以从栈中弹出压栈的数据来处理,所以不存在不够用的问题。所以产生中断只保存8个寄存器是有原因的,那R4-R11需不需要用到呢?也可能需要用到,所以RTOS除了保存那8个必要的寄存器外,一般也会对R4-R11进行手动保存起来以防万一。
所以说RTOS的切换其实就是人为的中断,而且是有目的有组识有计划的中断。
“当 CONTROL[1]=1 时,线程模式将不再使用 PSP,而改用 MSP(handler 模式永远使用MSP)。”这句话翻译得有些生硬,意思就是说CONTROL[1]=1 时,普通程序用的堆栈指针是PSP,而所有中断里面的程序用的是MSP指针,这样的好处是中断程序和普通程序能够井水不犯河水。而eOS也是采用了这种模式,但是要特别注意的是:[译注 10]:此时,进入异常时的自动压栈使用的是进程堆栈,进入异常 handler 后才自动改为 MSP,退出异常时切换回 PSP,并且从进程堆栈上弹出数据。
进入PendSV中断时,cortex-m3会自动保存八个寄存器(xPSR、PC、LR、R12、R3-R0)到PSP中,剩下的几个(R4-R11)需要我们手动保存,而SP(R13)是保存在结构体中
任务栈:是用来给任务保存临时变量的,程序在运行过程中会产生各种临时变量、中间变量等,而eOS中的任务栈是用数组来实现的,刚开始运行任务时,程序是从栈顶(数组最后一个成员)开始向下使用的,假设有一任务栈数组长度为64,任务运行到一半时,假设任务临时变量、中间变量用了20个内存,则PSP指向了数组[43],被切换出去时,PendSV中断自动保存8个寄存器到PSP,则PSP指向数组[35],在eos_port.asm中的PendSV_Handler中断函数中,手动对R4-R11进行了保存,则PSP指向数组[27],即当前使用了数组[27]-数组[63],而数组[0]-数组[26]处于空闲。恢复数据时,把[27]-[35]手动恢复到R4-R11,把PSP指向[35],退出PendSV中断函数时,会自动恢复8个自动保存的寄存器(同时PSP会自动+8),此时PSP会指向数组[43],这样PSP也正确指向了程序被打断前临时变量栈底的PSP位置,保证程序临时数据不会出错。
以上这些是任务切换的细节,有兴趣的朋友可参考<<Cortex-M3权威指南>>。
2.系统配置
在使用eOS前,要先打开eOS.h,按照自己的需求把系统资源配置好,合理的配置能让系统更加高效和节省内存。配置界面如下:
1.OS_APP_MAX
APP数量也就是任务数量,实际用多少就配置成多少,需要注意的是,系统本身就已经内置了一个空闲任务,所以该值= 1 + 用户需要的数量。
2. OS_APP_SLICETIME
时间切片,一个任务持续运行代码的时间。在此时间范围内,一般不会被系统切换出去。该值一般设为1-10ms较合理。值越小,实时性越高,但代码的运行效率会降低;值越大,与前者相反。
3.STK_IDLE_SIZE
空闲任务的堆栈内存大小,默认18,当用户在空闲任务中加入自己的代码时,要适当加大堆栈大小,建议不要使用。注意:在Cortex-M0/M3中,空任务的堆栈至少16,在Cortex-M4/7及以其他带FPU的mcu内核,需要增加额外32个单位的堆栈空间用来供给FPU寄存器(32个)的保存,所以在Cortex-M4/7及以其他带FPU的mcu内核上,空任务至少为48。由于空闲任务是默认是空的,所以空闲任务的默认堆栈大小是按最小值+2额外值来设计的。
4.下面5个参数属于操作系统额外功能,将在入门之后的篇章讲解。在此建议初学者将以下参数全配置为0
OS_TIMER_MAX 0
OS_APP_FLAG_MAX 0
OS_APP_MUTEX_MAX 0
OS_QUEUE_MAX 0
OS_MBOX_MAX 0
5._RAM_ALGO_
查表法数组的保存位置设置,如果内存RAM够用可以选择保存在RAM。
0-保存在rom,1-保存在ram。默认保存在rom。
6.OSClock_1US
系统定时器计数1us所需要计数的次数值。这个配置需要了解当前所使用的mcu的硬件定时器的相关信息。系统默认使用的是STM32的滴答定时器作为系统定时器,并且该定时器使用的是时钟频率是mcu主时钟8分频后的时钟,所以1us需要计数9次,1ms需要计数9000次,这里是1us的次数,所以为9。不同平台的单片机这个值可能不同。在stm32上一般不需要改这个值。
3.os_main.c文件详解
(1)系统的入口函数所在的文件,也是任务函数体所在的文件,当用户任务数为0时,os_main.c的内容可以只包含这些内容:
包含eos.h头文件,必须包含。os_user_init()函数体,必须存在,但内容可以为空。os_main()函数,不能为空,其最少包含的内容如上图所示,一个os_init()系统初始化函数,一个os_startup()系统启动函数。只有这些内容时,系统是可以正常运行的,只是一直在运行自带的空闲任务,用户看不到效果。
(2)创建一个用户任务
创建任务三步曲:
创建任务堆栈(+任务函数声明)在系统中创建任务 创建任务函数体
详解:
os_astk stk_app_led[128]
astk是堆栈变量类型,其本质是unsigned int变量。但不可用unsigned int代替。stk_app_led堆栈数组名取名任意,但要注意和os_app_create()中的堆栈名要保持一致。[128]堆栈大小(长度),一般设为>=64,任务为空代码时最小为16,建议先设置64,不够再继续增大,这样可以节省内存,如果堆栈内存不足,可能会引起系统无法运行。Ps:那么有没有办法知道任务实际使用了多少堆栈呢?有,后续版本会更新系统状态信息查看功能。
os_user_init()函数
用于存放用户的各种器件的初始化函数的。方便统一管理。当然用户的初始化函数也不一定非要放在这个地方,放在任务函数内while(1)前面也是可以的,就像裸机工程的main函数一样。需要注意的是,如果多个任务共同使用到同一模块,那么建议将此模块的初始化函数放在本函数内,当然,也可以放在优先级最高的函数当中。如果放在优先级较低的任务中,由于系统启动时,第一个运行的任务是空闲任务,在一个时间切片后会进行查找最高优先级任务,所以下一运行的将是最高优先级任务。所以,如果将初始化函数放在低优先级任务中,如果较高优先级任务用到此模块,将可能因为模块没有初始化而导致模块数据出错。
os_app_create(任务函数名,任务堆栈栈顶地址,任务优先级)
形参中,任务函数名要和任务函数体的名字一样,其次,栈顶地址其实就是数组的最后一个元素的地址,至于其他单片机是第一个元素的地址还是最后一个元素的地址,这个和栈生长方向有关。
app_led()任务函数
函数名任意取,只要任务函数名和os_app_create()函数中的任务名保持一致即可。
函数必须是无返回值,无输入参数的函数。任务函数内容必须包含一个无限死循环。如果只想让该任务中的代码只运行一次,可以将代码放在while(1)的前面,也可以设置一个标志变量来控制运行的次数。
如果想要创建多个任务,重复以上步骤,只要注意任务名、堆栈名不要重复即可。
建议初次使用的朋友使用例程工程来创建,可以在os_main.c中先删除所有用户任务,再根据本手册步骤新建任务作为练习。熟练之后再学习将eOS源码移植到裸机工程中。
3>系统移植(到裸机工程)
移植到Cortex-M0/M3
将这两个内核的移植放到一起是因为它们的移植方法是一样的。准备一个裸机工程,这里使用一个stm32f103xx 的LED闪烁的工程示例:
(1)裸机工程
(2)打开裸机工程文件夹,打开后先放一边
(3)下载eOS源码压缩包,解压后打开eOS源码文件夹(解压后如图)
(3)打开Cortex-M3文件夹
(4)打开Source-Cortex_M3文件夹
确保里面有这4个文件
(5)复制eOS文件夹到步骤(2)里
(6)打开工程
(7)先编译工程,确保原工程无错误
(8)在工程中添eOS文件夹
(9)往文件夹中添加eOS文件
(10)添加eOS头文件路径
然后点击OK退出。
提示:如果C99没有勾选的请勾上。优化等级先设为Level 0,其他的设不设置不影响
(11)添加eOS入口函数,方法一: (直接工程中的main.c文件上添加)
双击打开main.c文件
添加包含eOS头文件
将main()函数改成任务函数 (函数名任意,这里取为app_led) 注意 int -> void
再创建os_user_init()和main()函数
在maiin()函数中加入两个函数 os_init()和os_startup()
创建任务 (将app_led任务函数加入到eOS中 ) 三步曲:
1>创建任务堆栈(一维数组),大小>32即可。如果任务函数中的代码或调用的函数较多,请将堆栈设置大一点,如128以上。
2>在main()函数中调用os_app_create()函数创建任务
参数依次为:任务函数名、堆栈数组最后一个元素的地址、任务优先级(1 – 63)
创建任务函数,之前已经创建好,这里只需要声明一下即可。
(12)删除裸机工程中的延时函数,使用系统自带的延时函数代替
移除工程中的delay.c/.h ,右键点remove …
删除main.c文件中的#include "delay.h" 和延时函数初始始化函数delay_init()
这里的延时函数和系统的延时函数名字相同,所以不修改了,如果是其他函数名请改成delay_ms()或delay_us(),这是系统自带的延时函数。
(13)将裸机有关延时的代码全部处理完成后,编译。(如果其他文件还有,请在其他文件中#include “eos.h”,然后将延时函数替换为系统延时函数即可)
编译后出现2个错误,是正常的。故意将其列出来是为了让用户移植印象更深。
如果工程中没有stm32f10x_it.c这个文件可能不会报错就不用管。
提示的这两个中断函数是可悬挂中断和嘀嗒定时器中断函数,这两个函数在系统中是被用到了的,前者用来作为任务切换的触发手段,后者用来给系统提供系统时钟。
按照提示,打开stm32f10x_it.c文件,找到这两个中断函数,将其注释掉即可。
再次编译
下载烧录测验 LED正常闪烁….. 移植成功!
优先建议:将LED初始化函数放到os_user_init()函数中去。
移植到Cortex-M4/7
下面以Cortex-M4为例,前面步骤参照Cortex-M0/3移植,需要注意的是复制eOS文件夹时,请从Cortex-M4的源文件中复制。按照上面的步骤移做完后,再照下面的步骤修改一下:
(1)修改启动文件,打开stm32的启动文件 startup_stm32xx,s
找到Reset_Handler这个标号,在__main下面添加如下代码,此代码功能是打开FPU(浮点运算单元)
IF {FPU} != "SoftVFP"
LDR.W R0, =0xE000ED88
LDR R1, [R0]
ORR R1, R1, #(0xF <<20)
STR R1, [R0]
DSB
LDR.W R0, =0xE000EF34
LDR R1, [R0]
AND R1, R1, #(0x3FFFFFFF)
STR R1, [R0]
ISB
ENDIF
(2)在魔法棒图标设置中选中FPU选项
(3)在全局宏定义中加入使用FPU的宏,注意是加入,不要覆盖了原来有的宏,在原来有的宏后面加入即可
需要加入的宏:ARM_MATH_CM4,__CC_ARM,ARM_MATH_MATRIX_CHECK,ARM_MATH_ROUNDING,__FPU_PRESENT=1
加入后全部宏如下:
USE_STDPERIPH_DRIVER,STM32F429_439xx,ARM_MATH_CM4,__CC_ARM,ARM_MATH_MATRIX_CHECK,ARM_MATH_ROUNDING,__FPU_PRESENT=1
完成上面这些修改STM32F4上就能正常运行了。这些修改是因为M4比M3多了FPU单元,需要修改以便让eOS支持FPU,如果用户不想使用FPU,可以不需要修改,直接按M0/M3的步骤移植就能用,只是需要注意的是要确保FPU是关闭的,关闭FPU方法:
5>
系统功能---os_delay()系统延时函数
os_delay()与delay_ms()/delay_us()函数的区别是:前者在延时的时候会自动放弃任务的运行权,等延时时间到了之后系统会把运行权交还给它(延时到了后如果它是最高优先级的话,不是就要等待),后者不会将任务切换出去,会一直阻塞在那傻等,除非有比它的任务优先级更高的任务就绪从而把它抢占,不然会一直在那傻等也不让别的任务运行(明显降低了效率)。既然os_delay()能提高效率为什么还要让delay_ms()和delay_us()存在呢?原因是,有的代码不能切换出去,必须一次性完成,这里面的延时就必须使用delay_ms()/delay_us()函数来延时,否则使用 os_delay()就会被切换出去。怎么保证代码能一次性运行请参考下一小节。
6>
系统功能---任务切换锁定和解锁
在系统运行过程中,任务之间是通过快速切换来获得CPU运行的,这样会带来一个问题,当有的代码必须一次性运行,中间不能被打断的时候,系统这种快速切换运行的机制就会破坏了程序运行的完整性,如使用单总线读取DS18B20温度值、使用IIC读取DS3231等器件的数据时,在读取的过程中是不能被打断的,因为这些通讯协议是有严格的时序(延时)要求的,如果在通讯过程中任务被切出去几ms,虽然时间很短,但是非常有可能会导致通讯失败或数据出错。为了解决这个问题,eOS提供了任务切换锁定/解锁函数(各大RTOS也有提供,函数名可能和eOS的不相同),os_lock()和os_unlock()。
在调用读取温度函数之前,先调用os_lock()锁定任务,在读取温度函数之后调用os_unlock()解锁任务即可,如果不解锁,其他任务就得不到运行了。在解锁之后最好调用os_delay()延时1-10,这样能触发系统去调度任务,检查有没有更高优先级任务需要运行。(本来系统设计的是:在解锁函数中加入调度的功能,这样就有点强制切换了,所以把解锁后自动调度一次的功能去掉了,解锁后需不需要调度的决定让给用户自行决定,想要调度就调用os_delay(1)延时就能实现一次调度)。具体使用请参考相应例程。
7>
系统功能---任务挂起和恢复
这个是用来控制任务的运行和暂停运行的,使用此功能就能让任务之间按照开发人员想要的顺序运行或同步运行。具体使用请参考相应例程。
8>
系统功能---软件定时器
这个是用来解决硬件定时器不够用或提高移植性的。需要注意的是,软件定时器的精度不如硬件定时器,使用软件定时器时,只适合对延时精度不高的场合,其误差跟任务数量有关,但一般误差不超过10ms。任务数量少时误差不超过1ms。具体使用请参考相应例程。
9>
系统信号量
用来使任务同步、任务互斥、任务传递消息等。举个例子:任务1是扫描温度,任务2是继电器控制,那么当任务2中有某个步骤需要等待温度达到一定温度时才能进入下一步程序,这时使用OS_FLAG或OS_MBOX就能轻易实现这个功能,简单方便。具体使用请参考相应的例程。
eOS最新版本源码链接: /s/1Dk3zAPOT5NK3k_AZ8dQgIA @2793
/s/1GPWswyJpfyztJdCw1IjZtw@3ggt
如果链接失效,使用下列方法之一获取:
1.关注V“银穗之家”回复“eOS”获取;
2.群1002174602获取。