打印
[STM32F1]

基于STM32的嵌入式微系统msOS成型记(连载)

[复制链接]
楼主: lldwsw
手机看帖
扫描二维码
随时随地手机跟帖
21
lldwsw|  楼主 | 2016-11-4 19:32 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
感谢用过msOS的各位网友的大力支持,现阶段轻松一些,大力推广msOS,让更多网友受益

使用特权

评论回复
22
hongqi1029| | 2016-11-4 22:34 | 只看该作者
感谢凤舞九天!!!

使用特权

评论回复
23
huangqi412| | 2016-11-5 10:02 | 只看该作者
顶一顶

使用特权

评论回复
24
lldwsw|  楼主 | 2016-11-5 11:02 | 只看该作者
本帖最后由 lldwsw 于 2016-11-5 11:08 编辑

早期的“实用单片机系统McuSystem”,可以说总结了MCU51的编写方式,第一批提出了在MCU51下如何编程的通用架构,自从发布以后,经常看到21ic的一些牛人发布的**代码,或多或少受到McuSystem的影响。而现在的msOS,更是在McuSystem基础上,把硬件平台扩展到ARM上,引入了C#编程风格,uC/OS-II,更系统的总结了嵌入式编程模式,填补国内嵌入式开发在架构这方面的空白。
msOS,是基于架构的模式化编程思想,不再是大家通常熟悉的模块化编程思想了。

使用特权

评论回复
25
lldwsw|  楼主 | 2016-11-11 19:40 | 只看该作者
本帖最后由 lldwsw 于 2016-11-11 19:42 编辑

五:从嵌入式微系统的软件架构说起

嵌入式微系统架构是C#与CMSIS架构的复合体,如下图所示:



基于C#标准,分为应用层App与系统层System两层。采用两个大结构体AppStruct和SystemStruct把它们各自封装起来。



Appstruct里封装了两个结构体:Data和Menu。两者都是应用层的数据类型。

Data是应用层业务逻辑所涉及的传感器、驱动器等全局变量、过程变量、存储参数、设置参数等数据,Data结构体类似数据库概念,只是嵌入式系统数据一般不大,用一个结构体实现即可。
Menu是应用层需要的各种菜单显示页面,一个页面下可以加载多种控件,比如背景文字(BackText)、表(Chart)、标签(Label)和文本(TextBox)四种。目前版本msOS因为主要针对工控,支持本地支持黑白字库屏,所以只需要这四种控件即可,菜单结构相对简单,今后需要支持黑白点阵屏甚至是彩屏,需要支持的控件就更多了。

AppStruct可以认为是应用层App的数据库,里面包含了业务逻辑的数据库和菜单界面显示的各个页面。基于这种结构体封装的好处在于把全局变量统一到数据库中管理,尤其是业务逻辑中的全局变量都放到Data区中统一管理,这充分的利用了全局变量的使用便利性,又有效的避免全局变量满天飞,解决了长期以来引起极大争议的全局变量问题。
应用层分为三块,除了数据库意思的AppStruct外,还有业务逻辑(Logic)及菜单界面(Menu),两者分别跟工业自动化控制系统的可编程控制器(PLC)和组态屏(HMI)对应。在工业自动化中,PLC内部有业务逻辑参数存储单元,HMI内部有菜单页面存储单元,现在等价于把PLC和HMI的存储单元合并在AppStruct中来管理。

业务逻辑(Logic)响应各种传感器过来的信息,通过给定的算法或操作流程,及时指挥驱动器工作,所以需要设定在较高优先级的任务中。菜单界面(Menu)显示业务逻辑处理过程中需要显示的各种参数,只需要满足眼睛的刷新频率即可,所以刷新速度相对较慢,一般放在最低优先级任务中处理。需要注意的是,菜单界面不包括按键部分,按键产生按键消息,归入业务逻辑中,菜单界面只是显示各种参数,而不会去修改参数,这个保证了数据库只被业务逻辑一个任务修改,避免了一个数据同时被多个任务修改产生错误的问题。



SystemStruct封装了整个底层系统,抽象成一个面向应用的接口芯片。
Initialize为初始化函数,在系统应用前,必须要先初始化。
Device结构体中封装了各类设备,比如LCD、ADC、USART、IO、Timer等。
OS中封装了uC/OS-II的各种常用接口函数。
Gui中封装了界面处理接口。
AppStruct和SystemStruct都是一个自定义的结构体类型,需要用这两个类型定义一个对象。



定义应用层结构体。
定义系统层结构体。需要注意的是,因为采用结构体模拟类功能,System在定义的时候就指定了System的Initialize初始化函数,这样系统开始就可以使用System.Initialize功能,便于形式上的完美。

一个System结构体,封装了整个系统层,中间件接口及设备接口,都在System中体现,等价于一个面向于应用层的处理器,App运行于系统层之上,App只需要调用System中的接口即可,简单、易用,这样设计做到了跟实际的芯片无关性,比较容易跨平台,当需要更换芯片的时候,只需要保证System接口一致,应用层无需修改代码。

使用特权

评论回复
26
qq2941070113| | 2016-11-15 09:36 | 只看该作者
期待MSOS的GUI部分, 添加按键复用(不同的界面,按键功能不一样),功能.  

使用特权

评论回复
27
qq2941070113| | 2016-11-15 09:51 | 只看该作者
菜单复用应用场景:
3个键, up, down, enter, 要求按 up,down在不同的界面状态下,可实现切换菜单焦点或更改菜单参数. enter进入功能或保存参数.

使用特权

评论回复
28
lldwsw|  楼主 | 2016-11-15 15:55 | 只看该作者
六、我们是怎样搭建嵌入式微系统的设备的

System结构体封装了整个系统层,让App很容易基于System跨平台,那么System内部该如何组织?

ARM公司推荐嵌入式开发遵循CMSIS架构,用户应用程序可以调用实时内核(OS)、中间件等,也可以直接调用底层硬件基于CMSIS标准的函数接口,比如ST公司发布的STM32的硬件驱动LIB库,甚至直接访问最底层的寄存器。这种架构编程比较灵活,对于规模不大的嵌入式系统比较适合,但这样的一个架构分层还比较模糊,应用层几乎可以访问所有的系统层资源,比较任意。各种底层接口没有封装,规模一大很容易引起重名、相互调用,若更换处理器芯片,整个系统层都要重新设计,不便于移植。为了解决这些问题,需要引入新的概念:设备,基于设备把底层的硬件驱动函数按功能封装起来,这样可以把系统层内部理清、分层次、模块化、可移植。

设备这个词,我们是经常接触的,我们知道PC机的组成,就是由中央处理器、内存、硬盘、主板、电源、显示器等几样组成,这是物理上讲的,若从Windows操作系统角度看,我们可以看Windows的设备管理器。一台PC机在Windows XP的眼中由DVD驱动器、IDE控制器、处理器、磁盘驱动器、电池等等组成,每一个设备里面又细分为很多小设备。所以设备是Windows XP管理的基本对象。

在VS.Net架构的C#编程角度看,系统层System由各种功能子类组成,子类下面又分子类。应用层开发程序,就是面向System各种类开发即可,所以类是C#编程的基础单元。


无论设备还是类,它们都是表征一组具有相对完整功能的集合。比如一个人,由头、四肢、五脏六腑组成,这些部件都是相对完整的功能集,而头呢,又由大脑、眼睛、鼻子、耳朵等组成,它们的功能也是相对完整的。相对于类这个概念来说,嵌入式人员更容易接受设备这个名词,因为有很多实际的对应物存在,比如LCD、按键、存储器、串口、ADC、定时器、IO等等,都是具体的一个设备,它们有非常明确的功能定义,基本上各个项目都需要这些设备组合起来完成,只是不同的项目可能涉及的设备种类不同罢了,但基本上脱离不了这么几种。所以我们可以认为一个系统层System就是由不同的设备组成的,系统层内的中间件也是由设备来支持的,而设备则需要编写设备驱动代码,需要调用各种硬件驱动接口函数来实现,比如ST公司发布的STM32硬件驱动库来完成。

中低端嵌入式编程,因为资源比较紧张,为了节省资源,代码编写比较紧凑,所以长期以来应用层与系统底层是分不清的,这就导致经常一个人既要懂上层应用,又要懂底层驱动,对开发者的要求较高,这也增加了项目的开发难度。而现在高性能嵌入式芯片的出现,可以不再被资源约束,所以引入设备概念,让嵌入式编程真正意义上解决了一人打天下的尴尬局面,对于应用层开发人员来说,他们虽然不懂底层硬件驱动,但可以很容易理解设备这个功能接口,轻松完成项目需求,而对于底层驱动人员来说,一个个明确独立的设备,更有助于自己清晰的编写底层驱动,适合多人协助开发。所以msOS,可以认为是面向设备开发的架构。

每一个设备,都有一个对应的结构体封装,因为中低端嵌入式系统规模不大,为了方便应用查看,所以直接封装在System的Device里面。
只有在System的Device中的基础设备才可以被应用层访问、调用,而设备内的其它函数,都需要定义为Static类型,防止被外部调用。

一个设备驱动的代码编写一般的讲要分为五部分,以LCD设备为例:
1、 建立一个按键设备文件device_lcd.c。
2、 在device_lcd.c中编写lcd设备驱动代码,除了外部接口函数之外,其它的都需要定义为Static类型的静态函数,防止被外部函数调用。
3、 在SystemStruct的Device中添加Lcd设备结构体,内部包含接口函数指针。
4、 在Lcd设备初始化函数InitializeLcd中把Lcd设备的接口函数指针与Lcd设备的接口函数关联起来:System.Device.Lcd.DisplayString = LcdDisplayString;
5、 有些需要基于系统节拍运行的设备,比如按键、定时器设备,需要由系统节拍提供系统节拍服务程序:KeySystick100Service();TimerSystick1000Service();

引入设备概念,把系统层分为一个个相对独立的模块,内部的函数都通过Static封装了,只是保留了设备接口与外界联系,这样大大简化了系统的复杂度,调理更加清晰,可以支撑项目做的更大更强。封装之后的设备功能相对完整,独立性强,那么它是如何运转的呢?必须要有一套支撑设备运行的规范。

1、 设备一般分为两类,一类不需要系统节拍支撑它运行的,比如Lcd、IO等设备,它们由上层函数直接调用接口即可。还有一类需要由系统节拍设备支撑其运行的,比如按键和定时器设备,按键是基于每秒100次的按键扫描获取按键值,定时器是基于每秒1000次的系统节拍实现多路虚拟定时器。所以这一类设备,需要由系统节拍设备提供支持,它们的工作基于系统节拍之上运行,系统节拍设备是它们运行的基础。device_systick.c是系统节拍设备,它是相对比较特殊的基础设备。

2、 调用方式分为三种:上层与下层可以直接调用,下层对上层提供了两种方式,一是利用OS的消息机制实现信息上传,二是采用注册机制,实现类似处理器中断的方式,给上层提供信息。

使用特权

评论回复
29
lldwsw|  楼主 | 2016-11-21 15:26 | 只看该作者
七:嵌入式微系统两**门:注册机制与消息机制
注册机制在嵌入式编程中很少提到,但回调函数大家经常接触,以软件定时器为例:

软件定时器设备通过函数Start把FunctionCallback作为参数传入定时器设备中保存,之后开启定时器,延时1000ms之后,定时器就会直接调用FunctionCallback,所以这个FunctionCallback就叫做回调函数。类似处理器的中断一样。

注册机制类似回调函数性质,只是把概念扩展到变量,上层应用通过注册函数地址或者变量地址到设备中保存,当设备中这个地址对应的函数被激活或者变量值有改变,就调用这个函数或者更改这个变量值,以此达到底层对上层的信息传递。

以ADC设备为例,我们看一下注册机制的应用。首先在ADC设备device_adc.c中定义一个用于注册变量地址的指针数组RegisterPointerBlock,初始值必须要指向一个空变量,否则若指向了一些有用的地址而导致这个地址对应的数据改变,会引起不可预测问题。


在设备中引入注册机制,应用层只需要把变量的地址注册到设备中,设备自己可以通过这个地址修改数据,这样解放了应用层,让应用层不需要关心如何获取数据。但是,注册机制也存在一些缺陷,那就是操作的数据,必须要原子操作,否则会导致两个应用层与设备同时修改一个数据的异常存在,出现数据错误,这个是需要避免的。


相对注册机制,消息机制是大家所熟悉的一种底层向上层传递的方式,msOS采用uC/OS-II,标配为业务逻辑与菜单界面两个任务,业务逻辑为高优先级,支持消息队列,设备发送的消息,都在业务逻辑中处理。


需要注意的是,msOS是采用的是uC/OS-II,它属于RTOS,业务逻辑与菜单界面虽然是两个独立的任务,但并不是同时运行的,一个时刻只能运行一个任务,业务逻辑的任务优先级高于菜单界面,所以在没有消息的时候,业务逻辑任务(LogicTask)在PendMessageQueue函数中挂起等待消息到来,这个时候退到菜单界面任务(MenuTask)执行,一旦按键设备检测到按键并通过PostMessageToLogicTask发送消息,就会激活业务逻辑任务,让业务逻辑任务抢占菜单界面任务运行。按键设备的运行是基于系统节拍的,它是在中断中运行,优先级比任务高,不会被任务抢占。


使用特权

评论回复
30
lldwsw|  楼主 | 2016-12-4 16:04 | 只看该作者
本帖最后由 lldwsw 于 2016-12-4 16:07 编辑

八 系统节拍与软件定时器

系统节拍是非常重要的一个设备,在早期的MCU51和ARM7芯片中,没有专门的系统节拍,往往由一路硬件定时器来实现其功能,到了Cortex系列,ARM提供了Systick硬件定时器专门用于系统节拍,可见现在的编程对系统节拍的依赖性。

系统节拍的概念比较早的出现在OS中,产生固定间隔的重复中断,用于任务的超时等待或者任务延时多少个节拍周期用。在前后台系统中,也引入了系统节拍,比如MS(基于MCU51)版本很早就引入了系统节拍,实现按键扫描、虚拟定时器等功能。此外还有一种基于时间片编程的架构,直接采用系统节拍来处理一些对时间精度要求比较高的需求。常用时间间隔是5mS或者10mS,也就是说每秒钟200次或者100次节拍。随着处理器速度的提高,尤其是在一些控制要求高的地方,可以采用1mS,msOS甚至采用了0.1mS的高频率系统节拍。这个具体的时间间隔跟项目需求有关,可以根据自己的项目灵活配置,但在满足需求的情况下,不建议设置的太高,否则影响处理器效率。

msOS中的很多设备,都需要基于系统节拍来运行,比如按键、软件定时器。这些设备不需要太高的系统时钟,按键只需要每秒钟100次,软件定时器需要每秒钟1000次节拍,但msOS因为面向时间精度要求比较高的行业,所以默认的系统节拍时间间隔设置的很短,达到0.1ms,也就是说一秒钟一万次节拍。所以需要对系统节拍进行分频处理,但因为系统节拍真实的间隔是0.1ms,以STM32工作在72MHz为例,这么短的时间最多只能执行7200个CPU指令,为了防止各个低速设备都挤在同一个系统节拍内运行,超过7200个指令,所以在分频的时候,不要把所有的设备都放在同一个节拍里面,需要把各个设备分散到各个节拍中运行。

Device_systick.c是系统节拍设备,它除了分散节拍直接调用固定的运行在其上的设备外,还提供了注册机制给上层应用程序提供系统节拍,具体如下:




定时器是一个项目中经常用到的设备,比如动画设计、闹钟、定时工作、超时处理等。然而一个处理器往往自带的硬件定时器是非常有限的,并且功能也比较有限,不能灵活应用,所以需要通过系统节拍虚拟出多路软件定时器。

软件定时器主要定时器服务、启动、停止三个函数组成,默认支持8个软件定时器,以下面代码为例加以说明。



FunctionCallback为定时器超时后的回调执行函数,是软件定时器初始化的第三个参数,超时时间到就执行这个函数,第二个参数1000为超时的时间,单位是软件定时器的系统时钟,msOS采用的是1mS,1000也就是一秒钟。第一个参数为回调函数执行的位置,TimerMessageHandle表示在业务逻辑中执行,适合处理代码量大的,处理时间长的函数。TimerSystickHandle表示在系统节拍中直接处理,系统节拍内一般适合代码量少的,处理时间短的函数。软件定时器功能非常有用,它的结构也非常简单,用户看懂之后可以任意扩展其功能完成自己特殊的用途。

使用特权

评论回复
31
woshichuanqi| | 2016-12-4 16:12 | 只看该作者
嵌入式进阶之秘籍

使用特权

评论回复
32
siccom| | 2016-12-4 16:16 | 只看该作者
正在学习中

使用特权

评论回复
33
CI-KE| | 2016-12-4 16:56 | 只看该作者
msOS更适合工控类产品,如果也能融合些消费电子常用的,将能走得更远!

使用特权

评论回复
34
ppdd6| | 2016-12-4 16:57 | 只看该作者
有更多的应用实例, 效果更好!

使用特权

评论回复
35
fly_high_0060| | 2016-12-4 18:17 | 只看该作者
不错,谢谢分享。支持下。顶起来

使用特权

评论回复
36
quray1985| | 2016-12-4 20:02 | 只看该作者
这种操作系统用的多不多呢?

使用特权

评论回复
37
戈卫东| | 2016-12-4 20:26 | 只看该作者
MSDOS, 久闻大名,如雷贯耳

使用特权

评论回复
38
lldwsw|  楼主 | 2016-12-4 21:23 | 只看该作者
quray1985 发表于 2016-12-4 20:02
这种操作系统用的多不多呢?

现在用的人不少了。

这是一个架构平台,自己没一点都理解透彻后,很容易扩展。

各位可以加入QQ群:291235815了解,近2000人的大群。

使用特权

评论回复
39
lldwsw|  楼主 | 2016-12-4 21:28 | 只看该作者
msOS的很多理念对于嵌入式系统来说,是非常实用,先进的

并且代码非常容易阅读。

使用特权

评论回复
40
lldwsw|  楼主 | 2016-12-13 22:36 | 只看该作者
本帖最后由 lldwsw 于 2016-12-13 22:40 编辑

九:面向对象的界面编程

msOS采用128*64的字库黑白屏,降低硬件设计复杂度,这个屏一行可以显示8个汉字或者16个字母,总共4行。界面开发基于面向对象方式,把界面抽象为几个页面,而页面又由控件组成,以我做的高频机为例,抽象了5个页面,每个页面上都由背景文字、标签和文本组成。
   


上图为工作页面,由一张背景文字(BackText),两个文本(TextBox)和五个标签(Label)组成。背景文字用于显示固定的说明文字,比如频率、功率等,这些文字在这个页面下是固定不能变化的。标签控件用于显示变量值,可以是字符串也可以是数字。文本控件具有标签的功能外,还可以通过按键等修改变量值。以上三种控件构成了最常用的页面。

接触过PC机编程的,对于控件概念是非常容易接受的,若没有接触过,可以通过msOS配套的硬件开发平台msPLC Demo了解界面编程,这样会相对容易很多,此外也可以接触一下C#编程,从跟msOS配套的msMenu源码入手,这个msMenu源码比较简单,在PC机上用C#虚拟了128*64的黑白字库屏。有了msMenu,各类STM32F103的开发板都可以运行msOS,通过串口跟PC机通讯,在msMenu上显示,不需要硬件LCD屏,方便大家学习。msMenu的两个主显示区,就是由两个大的TextBox组成。

下图为msMenu



   
界面编程基于面向对象设计,首先要建立页面及各个控件的对象类型。下图为页面的对象类型。



BackTextPointer:背景文字控件加载点,是一个16*4字节的数组,作为页面背景。
ChartPointer:图表控件加载点,高频机项目中用到,是一个特殊控件,一般项目用不到。
LabelPointer:标签控件加载点,一个Form可以支持多个Label,需要支持多控件级联。
TextBoxPointer:文本控件加载点,一个Form可以支持多个TextBox,需要支持多控件级联。
FocusTextBoxDataPointer:文本控件操作选择,设定焦点用于选择那个文本控件用于输入。


                    
                             
设计程序的时候,第一步要建立页面Form,如下图:                                          
                                       
第二步,定义各种控件和资源。


                                                      
第三步,各个页面加载控件,初始化控件参数,关联业务逻辑数据库中的数据。
                     



第四步,执行MenuTask解析当前页面


                                    
msOS采用的菜单界面,非常简单,是一个架构性的菜单界面,很适合用户看懂之后,自己修改、增删,也可以应用于彩屏。菜单界面的细节部分,请参考源码。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则