当前位置:网站首页>【RTOS训练营】程序框架、预习、课后作业和晚课提问
【RTOS训练营】程序框架、预习、课后作业和晚课提问
2022-07-26 00:38:00 【韦东山】
一:程序框架
我们使用HAL库来开发项目,如果框架设计的好的话,在rtos上面代码不需要改动太多。
程序框架可以参考这本书,我在中兴的时候基本上人手一本。

我们来看看这个产品,可以通过手机发送网络数据到开发板上,
开发板根据这些指示来点灯、转风扇。

功能比较简单,但是我们的框架可以做的有很多层次。
很多同学都是过程化的编程,今天我们要介绍的是模块化的编程。
要引入面向对象的思想,我们先来讲一下理论知识。
一个程序,怎么设计?
今天的内容需要大家互动,需要大家把工作中的经验分享出来。
在《代码大全》第5章中,把程序设计分为这几个层次:
* 第1层:软件系统,就是整个系统、整个程序
* 第2层:分解为子系统或包。比如我们可以拆分为:输入子系统、显示子系统、业务系统
* 第3层:分解为类。在C语言里没有类,可以使用结构体来描述子系统
* 第4层:分解成子程序:实现那些结构体(结构体中有函数指针)
这几句话我用一个图来表示:

最外面这一层就整个系统,在里面我们又画了两个大圆圈,就是两个子系统。
子系统里面又出现出了类或者结构体。
我们在C语言里面用结构体,在C++里面用类。
在单片机的开发中,我们只能够用C,用不了C++,所以我们来讲结构体。
第3层是结构体,以前我们讲结构体的时候,说结构里里面可以放函数指针
一个结构体里面可以有:各种变量成员、有函数指针。
我们可以使用一个结构体来表示一个设备、一个处理、一个操作。
第4层就是结构体里面的函数了。
这都是一些比较虚的概念,我们来举例说明。
只讨论开发板上的程序,这个产品我们可以拆分成几个子系统?
并没有标准答案,我来讲一下我的分法。
我把这个系统分成了6个子系统:
我是怎么得出这6个子系统的呢?我们可以一步一步来。
按照数据的流向,分为输入和输出:

至少有两个系统,对于输入部分我们又可以细分:

对于输入:用户可以点击按键,点击触摸屏。
那传感器呢?传感器检测到火灾的时候,发出报警信号,这也是输入。
甚至说我们还有远程控制,就像我们举的例子,你可以使用手机来控制开发板。
所以对于输入部分,我们还可以细分成各类子系统。
对于输出,我们也可以继续细分:

输出,并不仅仅是我们在屏幕上看到的内容。
比如说去点灯、控制这些设备,它也是一种输出。
再比如说数据的保存,也算是一种输出。
所以输出也可以拆分成很多子系统。
谁把这些输入和输出组合起来?
我们又可以抽象出另外一个子系统:业务子系统

有同学称之为:输入,输出,控制逻辑三部分,基本上就是这三大类。
还有同学从应用和驱动程序的角度来:应用层、中间层,驱动层,这比较适合用来实现某一个硬件模块。
我们以LCD为例:

对于显示这么一个功能,他可以拆分成三层。
在Linux系统中,在驱动开发,有一个原则:驱动只提供功能,不提供策略。
这句话是什么意思呢?以点灯为例,
驱动程序,它可以提供开灯关灯的功能。
什么时候开灯什么时候关灯,这叫策略,这不应该由驱动程序来决定。
回到我们上面的这个图,为什么这个显示的功能,要拆分成三层?
看看最底下,最底下是驱动程序,他应该提供硬件的功能:像素操作。
就是在xy某个坐标上,设置像素的颜色,但是怎么显示字符、显示多大、在哪显示,这不关驱动的事。
各司其职,不要越界。驱动就只做驱动的事。
中间是文字、图片的显示,通过库函数或者某些功能函数来实现,提供显示字符、显示图片的功能。
但是显示什么字符、在哪显示,这不关中间层的事。
显示一个字符的时候,就显示一个字符的点阵。
怎么得到点阵,功能函数来实现;
怎么显示像素,驱动程序来实现。
但是,显示什么字符,在哪里显示?
显示什么图片?在哪里显示
跟驱动程序没有关系,跟功能函数也没有关系。
由最上面的那一层来决定:APP。
我们去设计一个子系统的时候,也要明白:想让子系统比较通用,比较独立的话,就不要去做无关的事情。
下面我们就来讲讲怎么写代码实现各类子系统。
对这个输入子系统,在上图里我只把它拆分成两层。
但是后面随着编程的进行,我最终把它分成了5层。
所以这些程序的划分,一开始我们可能想的不够全面,但是只要记住一个原则:可移植性、减少依赖、独立
分层的事情我们等会再说,现在假设这输入子系统,就分为两层。
怎么写出来呢?
首先我们要使用面向对象的思想,抽象出一些结构体。
就比如说我们要问你一个问题:我从输入子系统里面可以得到什么?
得到:按键、触摸屏的点击,甚至说网络数据。
那么能不能用一个结构体来抽象出这些数据?
举个例子,这里抽象出了一个InputEvent:

这个名字、还有里面的大部分内容,来自于Linux,后面这个字符串是我扩充的。
首先它有个类型,可以分辨是按键、还是触摸屏,还是网络数据。
对于按键的话,有意义的成员:iKey, iPressure。
就比如说是按键a、还是按键b,是按下还是松开。
里面还有一个时间,可以记录这个按键按下或者松开的时间,就可以用来识别长按还是短按。
对于触摸屏,点击哪个触点?使用xy坐标来表示。
是点击还是松开,用iPressure来表示。
后面这个str数组是我扩充的,我们通过手机给开板发送数据时,
输入事件就是网络数据:网络数据就可以保存在这个str数组里。
我们抽象出了输入事件这么一个核心的结构。
你问我怎么知道这个结构体?我是学习的linux后,再来教大家的。
所以对于初学者,一开始的时候先模仿。
来看这框图,底层的这个按键、网络、串口,都会向上面传递InputEvent。

那么对一些不同的硬件,比如说按键、网络输入设备、串口,
以面向对象的编程思想,也应该抽象出一个结构体。
这个结构体长什么样?需要想想怎么去操作这些硬件。
首先得有初始化:比如说设置gpio为中断功能;比如说设置串口的波特率。
所以这个结构体里面肯定会有一个初始化函数。
上层的代码,可以通过这个input device来获得数据,
可能每种设备去获得数据的方法都不一样,所以这个input device里面应该提供一个:获得数据的函数。
所以这个结构体我就抽象为:

里面有名字,名字在我们的程序里面不重要。
重要的是那三个函数指针,最后还有一个链表项。
为什么要加上一个链表?因为我想把多个输入设备统一管理。
就比如说,我想去初始化的时候,我就可以从链表里面把他们一个一个的取出来,调用它的DeviceInit函数。
我们已经抽象出两个结构体了,足够了吗?
我们下面的输入设备,会不断的产生数据。
就比如说我连续不断的按下按键,就会产生很多数据。
为了不让这些数据丢失,我们还需要一个缓冲区,
于是,我又抽象出另外一个结构体:环形缓冲区。

这里面有读和写的位置,就一个input even数组。
我们已经把整个系统,拆分成了几个子系统。
对于子系统,也抽象出了结构体。
最后,就是去实现结构体里面的函数。
简单的说,就是去写.h文件和 .c文件。
二:预习安排
布置一下预习的视频和文档:
10-5 输入子系统_实现按键输入
10-5 输入子系统_实现按键输入
10-7 输入子系统_单元测试
三:课后作业
- 作业1
10_6_input_unittest 中实现了按键功能:
在按键中断函数中,构造InputEvent,放入Buffer
请参考它实现:串口输入功能。
思路:
找到串口的接收中断函数
当串口接收到回车换行时,表示得到了一个完整的数据
将数据构造为InputEvent,放入Buffer
- 作业2
请思考,怎么设计"设备子系统",比如LED、风扇、OLED,它们的操作并不相同。
怎么抽象出一个结构体,可以支持它们?
写出这个结构体。
写好的作业,想老师批改的,请放在QQ群里。

四: 晚课学员提问
1. 问: 怎么理解何为硬件模块?
答: 比如说LCD、 Flash、各类传感器,这些都是单功能的硬件模块。
2. 问: 设备子系统是属于输出吗?网络子系统和字体子系统是属于输出还是输入?
答: 对于设备,有些设备只能够输出,有些设备只能够输入,有些设备既能输出也能输入。所以一个设备子系统,有时候并不能够简单的把它划入输入、或者输出。比如U盘,你可以写入数据,可以读出数据。这个时候单纯把它划为输入或者输出都不恰当。
3. 问: 按照什么去分层?
答: 先划出子系统,在实现子系统的时候再考虑分层。比如我把系统分为输入和输出,分成两个子系统,在实现输入子系统的时候,再考虑分层。所以我们首先要练的是,怎么把整个系统拆分成多个子系统。怎么拆分成多个子系统,刚才我们已经介绍了方法:

先把它拆分成:输入、输出、控制逻辑(业务)三个子系统。
再去细分这三个子系统,得到更多、功能更加独立的子系统。
我再举一个例子:

我一开始设计这个系统的时候,并没有这个字体子系统。
后来一想,我怎么得到字符的点阵?
我可以从点阵字库里面得到,也可以从Free type字库里面得到。
去显示文字的时候,字库的来源应该独立出来。
所以我就把它分成了显示子系统,字体子系统:
字体子系统,提供字模;
显示子系统,根据字模来显示文字。
甚至有时候在编程的时候发现,这个子系统功能不大纯粹,又去拆分它。
4. 问: 比如flash保存参数,这也算输出系统,怎么抽象,编程?老师项目上能加上这一个模块吗?
答: 后面的esp32芯片上会有。
5. 问: 这是属于项目一开始就做全局规划了,实际工作中感觉还是蛮难的?
答: 先从小项目开始练。
6. 问: 数据成员都不会同一时刻使用,可以用共用体吗?union?
答: 可以用union,也推荐使用它。
7. 问: 分层的第一步是用结构体去勾画对象吗?
答: 分成的第一步,你要去理清楚功能。
就比如说输入子系统:我之所以把它拆分成两层,主要是:
- 最下面是数据源
- 上层是汇总
汇总、管理,所以我就简单的把输入子系统划分为上下两层。
划分出上下两层之后,再去考虑结构体。
8. 问: 三个不同的输入内容都揉在一起嘛,需要再分类清晰点吗,比如结构体里再包括三个结构体?
答: 不管你怎么做,你得有一个分类type。你当然可以在里面再放三个结构体,就是比较浪费空间。
9. 问: 我如果有几个端口输入数据,例如uart,spi和网络,那应该创建几个不同的buf吧?
答: 每个输入设备,都可以产生自己的InputEvent,里面有自己的buff。定义类型的时候并不需要有出多个buff。每一个设备它都可以定义自己的InputEvent。
10. 问: 老师,头文件的开头将 用到的变量、函数指针封装成一个结构体有什么好处呢?还有#pragma pack(1) 解释下?

答: 分装成结构体,就使用面向对象的编程思想,用一个结构体来实现一个功能。
以后我去升级或者更换其他硬件,去修改这个结构体就可以了。
我来举一个例子,这个例子我以前曾经举过:

假设你们公司的产品会用到两个LCD,一开始的时候你这样写代码:

你使用一个宏,来决定使用lcd A还是lcd B。
在这个程序里面,他要么支持lcda,要么支持lcdb,不能够既支持a也支持b。
那如果你们公司的产品它既可以支持lcda,也可以支持lcdb的话,怎么办?

首先程序必须可以分辨LCD的类型,比如说可以去读取gpio,知道LCD的类型,代码就像上面一样。
这个代码它只有两款LCD,如果你们公司的产品支持100款LCD,怎么办?

这个时候,就可以使用结构体了,在结构体里面放函数指针。

对于第二个问题,我们可以试一下,不加这个pack的话,这个结构体是多大:

其实这个结构体,它加不加那个pack都没有影响。
去解析某些文件的头部的时候,这个pack才有用,比如BMP头部。
我给大家找一下这个BMP头部:

BMP文件的头部,它就是这么一个结构。
如果不加pack的话,或者说不加上那些attibute的话,bfType占据4字节(浪费2字节)。
使用这个结构体去构造头部,并且写入文件的时候,就会出错。
结构体的大小,比bmp文件的头部,增大了。
11. 问: 结构体的声明放在.h还是.c里好,如果在.h里,开放接口函数的时候,别人是不是也可以引用这个结构体了?
答: 你想让别人看见的东西,就放在头文件里。
12. 问: 只构造一个环形缓冲区怎么同时接收多个设备的数据呢?
答: 多个设备往里面放数据,多个设备调用:PutInputEvent。
13. 问: 如果只有按键输入的话,那创建的事件结构体岂不是有点浪费空间了?
答: 是的,浪费空间,所以使用union会比较好。
14. 问: 我用同一套板卡,但是不同的课题会用到不同的外设,不同的IO 这样底层硬件就理解为不同吗?不同的课题的话任务也不同。 这样该怎么考虑框架设计?
答: 我说一下我的想法。
同一套板卡,但是不同的课题会用到不同的外设,不同的IO:
这句话就可以细分,细分成两种情况:
第1种情况:他们都是使用这个引脚的gpio功能,项目一里面是用来输出,项目二里面是用来输入

这个时候,我们的程序就可以这样拆分:

看上面这个驱动,他可以兼容你的两个项目。
这个时候,这两个程序只有业务上的差别。
我们再来举第2个例子:
第2个项目,这个引脚可以用来控制灯,也可以用来作为adc,就是读取模拟信号

这个时候,框架就这样的:

我觉得没有必要把他们强制融合在一起
再来讲讲第3种情况,你们的程序既有业务1,也有业务2,
业务1把引脚当做gpio,
业务2把引脚当做adc
我们可以考虑这样一种框架:这是我临时想的,有可能考虑不周。

首先你得有一个输入,这个输入是用来触发一个切换的动作:
这时候就得把这个引脚,设置为普通的gpio,或者设置为adc。
业务1会使用到gpio子系统,
业务2使用到adc子系统,
如果非要在一个程序里面,即实现业务1,也实现业务2,那么里面必定有一个“切换的子系统”。
15. 问: 我感觉两个业务不一定要放一起,但是希望框架移植方便,不同的任务可以很方便移植,不伤筋动骨?
答: 实际上我也建议这个两个业务分开,我们同事前阵子还讨论过这个框架。
我们在做一个lvgl的桌面,
一种方法是:点击桌面上每一个图标,就启动一个独立的APP
另外一种方法是:点击桌面上的每一个图标,就加载一个动态库,
后来我们决定使用第1种方法,让这些APP尽可能独立。
16. 问: 老师,我的项目里面有can 422 flash,按照你的方法,是不是可以划分为输入,输出子系统,两个子系统中都有can 422 flash,但是这看起来很多余,有更好的方法吗?
答: 我们说的输入,是指那些可以直接影响到控制逻辑的,
一般的传感器我们只是去读取它的数据,显示它的数据,这些传感器不应该归到输入子系统。
不同功能的设备,我们干嘛要把它强制的放入同一个系统。

你就直接有4个系统:业务、存储、422、CAN不就可以了?
在我举的例子里面,开发板就在等待用户的按键、或者手机发来的数据。
等待这些数据,然后作出反应,所以我把按键、网络输入,还有串口输入,放到输入子系统。
422、CAN,没有必要强迫他们融合在一起。
17. 问: 这个环形缓冲器前面看视频觉得是收到的数据缓冲,现在怎么感觉是事件集呢?
答: 如果你使用rtos之后,事件集不能传递数据,用queue比较合适。
18. 问: 现在讲的架构 主要是针对外设的,那在做项目的时候,我们应该有个基本架构,根据项目不同进行裁剪,这个基本架构怎么做呢?
答: 这些基本的架构,我曾经做过。
我实现了很多比较独立的子系统:文件读写、图像文件解析、字模提取等。
这些子系统,你做得比较独立的话,在你的项目中基本上就是把他们组装起来就可以了,再加上你的业务逻辑。
19. 问:int (*DeviceInit)(void);中,函数指针如果函数名这个位置不加框号,默认是什么情况?
答: 不加括号的话它就是个函数声明,写在结构体里面是一个错误的用法。
20. 问: InputDevice可以放在设备子系统里吗?
答: InputDevice在rtos里面,我将会为每一个设备创建一个任务,所以把它放到设备子系统去,不合适。
InputDevice,会调用设备子系统的函数,去获得硬件数据。
在裸机程序里, InputDevice解析数据,设备子系统提供原始的数据,也不应该把他们放在一起。
比如说,设备子系统,他可以提供说哪一个gpio被按下、被松开。
但是,这个gpio,对应哪一个按键,什么时候发生,不应该由它来做。
应该有更上一层的InputDevice,根据gpio电平、根据时间,构造出InputEvent。
这就回到我们刚才说的原则:各司其职,不要越界。
21. 问: 老师,能总结一下今天的课程吗?程序设计的时候是 列出功能模块>划分为子系统>子系统分层?>定义每一个子系统的数据结构和接口>开始写.c?
答:
列出功能模块>划分为子系统>子系统分层>定义每一个子系统的数据结构和接口>开始写.c
列出功能模块>划分为子系统>定义每一个子系统的数据结构和接口>子系统分层>开始写.c。
我用的是后面这种流程。
边栏推荐
- YOLOV2 YOLO9000
- 【MATLAB appdesigner】27_ How to debug and view variables in appdesigner? (examples + skills)
- The task will be launched before the joint commissioning of development
- [array related methods in numpy]
- 旅行+战略加速落地 捷途新产品矩阵曝光
- 独家下载|《阿里云MaxCompute百问百答》 解锁SaaS模式云数据仓库尽在本电子手册!
- 从另一个角度告诉你单元测试的意义
- ShardingSphere数据分片
- SQL statement exercise
- What is the difference between request forwarding and request redirection?
猜你喜欢
随机推荐
AI knows everything: build and deploy sign language recognition system from 0
Getting started with D3D calculation shaders
Verilog语法基础HDL Bits训练 05
Compile openfoam solver with cmake
【无标题】如何实现可插拔配置?
Curd used by hyperf
sql语句练习
以数据驱动管理转型,元年科技正当时
SQL server failed to send mail, prompting that the recipient must be specified
What is the difference between request forwarding and request redirection?
Azure synapse analytics Performance Optimization Guide (1) -- optimize performance using ordered aggregate column storage indexes
测试左移和测试右移的概念
Hcip day 12
How to use if in sql service
Biological JC uvssa complex alleviates myc driven transcription pressure ⼒ English
分布式事务和Seata的AT模式原理
参数解析器HandlerMethodArgumentResolver分析与实战
【NumPy中数组创建】
Zabbix监控主机及资源告警
Research on visualization method of technology topic map based on clustering information









