嵌入式裸机轻量级架构探索总结

为什么会想着探索下嵌入式裸机的架构呢?是因为最近写了一个项目,项目开发接近尾声时,发现了一些问题:

1、项目中,驱动层和应用层掺杂在一起,虽然大部分是应用层调用驱动层,但是也存在驱动层调用业务层的情况,这导致了层次间的耦合;

2、应用程序全都放在了一个app.c文件夹里,代码高达1万行,实在是过于庞大,我想着将代码拆分下,发现实在是太困难,牵一发动全身;

3、全局变量满天飞,代码量大了之后,自己都晕了,虽然写了注释,但是想想,如果注释没写清楚,那么时间久了,自己回来看都不知道是啥~~~~~~;

那么,如何在后续项目中有所改进呢?

架构1.0

关于程序的架构和规范化,要做到:

层次分明,模块化,高內聚低耦合,风格规范易懂。

自顶向下设计,自底向上开发,花一两天来设计,设计好之后再开发。

层次分明

根据需求,有各种各样的功能要实现,但是因为嵌入式不仅涉及到软件,还会涉及到硬件,所以,需要分层,思维才能更清晰,更有利于后期的开发和维护。

根据我自己的开发经验,先说下我的最初裸机分层习惯。

将整体的架构设计分成3层,再多层次对于裸机感觉没什么必要了。

模版示例:

APP存放业务层代码;

DRIVER存放硬件驱动层代码;

SYSPERIPHERALS存放系统外设代码;

FWLIB存放固件库;

CORE存放一些板级核心代码;

OBJ存放keil的输出文件;

MIDDLEWARE存放中间件;

RESOURCE存放一些资源比如字库等;

USER存放工程;

UTILITIES存放其他内容;

除了一些固定的文件,在开发时分为系统外设、驱动层,再加上一个业务层。

系统外设层主要是对用到的各种片上外设进行初始化,之前经常跟驱动层写到一起,但时间久了就发现二者其实是不同的层次,写到一起容易混乱。

理想情况下,系统外设层向驱动层提供接口,驱动层向业务层提供接口。

系统外设层的各个硬件口,最后都用宏定义给重命名,如果要移植,就只用该硬件口就行,而不用去动驱动层,比如,如果就用GPIOA去开发,那么如果换了板子,就要改驱动层的书写,但是如果重命名,就只用改系统外设层的头文件即可。

另外,对于业务层来说,不推荐将所有的功能都放在同一个文件中,虽然比较方便,但是这会导致文件特别大,不利于后续开发和维护。最好按照功能模块进行开发,然后有一些各模块共用的功能,可以抽离出来,单独一个文件。通常,拆一个总的入口文件,再按大模块拆一拆,然后就是共用的部分。按模块其实是同级拆分,将共用功能分离出来,其实是上下层次的拆分,不过,也没啥必要再分不同目录来放了。

上面实例中,其实拆的太多了,就多了文件跟文件之间的纠缠,后续也很难理清。

前期一定就要做好设计和规划,不要试图想着先开发,后续再修改,惨痛的教训告诉你,修改比重新开发更让人烦躁,很费时间,分分钟有牵一发动全身的风险。

总之就是,越往上层,就应当越抽象。

层数越多,越复杂。

请合理平衡。

关于系统外设和驱动层的初始化,如果系统外设是和具体的驱动关联的,就可以放在驱动里,如果不能跟具体的驱动关联,就直接在系统外设层定义初始化接口即可,比如定时器。

另外,注意编码规范,如果太随意,越往后代码量越大就越难开发。就按照常规推荐的那些编码规范来写就行了,也不必特立独行。

关于变量还有头文件中的宏定义,有共用的,有专用的,专用的肯定是放在自己的c中,共用的可以放在common中,该static的就static。关于程序中的全局变量,建议如果超过3个,就用结构体封装起来,函数最好也是用函数指针结构体封装起来(借鉴硬件家园的风格)。区分仅自己使用和需要共享使用的情况,然后决定是用static限定或者加入到相应结构体中。

模块化就比较好理解,各个模块单独开发,最好可以实现独立编译。

如果是已经写好的代码,不要试图去重构,这会让你陷入无尽的烦恼之中,不必重新开发更轻松。

已经写好的,就将就用吧。

另外就是,不要试图追求完美。

架构2.0

改进点:不要将硬件驱动层再分两层了。

看了很多的代码,发现也没有将驱动层分成系统外设和驱动层的。

其实,将二者合并在一起的好处也是有的:

1、减少了层次间的相互调用,而且,代码量也不会增加多少;

2、各系统外设的初始化本来就是外设的一部分,直接放在驱动文件里,也是合理的,更清晰明了,如果单独把所有外设的初始化都放在一起,也容易搞混;

3、不用考虑中断响应函数到底放在哪一层;

4、初始化时,直接按外设模块来进行即可,不用纠结到底放在哪一层来初始化;

5、照样可以用宏定义来定义。

基于以上几点考虑,还是将架构就分为两层,即硬件驱动层和业务层。

注意,将USER改名为PROJECT了,不过不重要。

架构3.0

要实现的目标:

1、硬件驱动层,各模块之间可以独立编译,互不影响;

2、硬件驱动层不会反向调用业务层的API;

3、硬件驱动层不会向外暴露自身的全局变量;

以上三点,我们来依次看一下。

第一点,很容易做到,只要各模块独立c和h即可;

第二点,开发时注意些就行,千万不要反向调用;

第三点,要多说一些。

通常,驱动层和业务层的关系,分成两种:

一种是业务层主动调用驱动层的API,比如业务层调用驱动层的打开LED函数实现点亮LED,或者主动调用数据发送函数发送数据等;

还有一种是被动响应式的,即驱动层响应之后,需要向业务层上报,此时业务层就是被动响应的,有很多的例子,比如按键按下,串口接收数据,ADC采集等等,都是驱动层响应后,需要向业务层上报数据。

我们通常的做法是,在驱动层定义一个全局变量,然后声明出去,业务层的任务中循环判断这些全局变量,从而做出相应的动作。

可参考:单片机模块化编程框架篇-编写回调函数及产品应用_哔哩哔哩_bilibili

这里说的就是业务层主动发起的调用。

那么,业务层被动响应式的情况呢?

那么,回调函数的开发思路是怎么样的呢?

说实话,回调函数其实是个不太好理解的东西。

这名字听着就不知道啥意思。

其实,在本文的场景下,我们可以这样理解:业务层调用驱动层时,是直接调用的,但是业务层被动响应的情况下,驱动层基本都是由中断来触发的,通常如果直接在驱动层的中断里调用业务层的函数,一来不符合中断快进快出的理念,二来不符合下层不应该调用上层的理念。

这种情况下,我们可以在驱动层间接调用业务层的处理函数。

在驱动层定义一个回调函数的函数指针,函数里传入的是需要传递的全局变量

同时定义一个注册函数

还要在业务层定义一个跟函数指针同类型的处理函数

然后在业务层调用注册函数,将业务层的处理函数传入驱动层的函数指针

然后在中断里只需要调用函数指针即可实现间接调用业务层的目的

但实际上,访问的只是驱动文件中的函数指针。

因为,这个实现了下层调用上层的目的,是在上层定义,但是由下层调用,所以,被叫做回调函数,也是很合理的。

至此,就进步了一个台阶,至少,解决了驱动层和业务层之间的全局变量的传递问题。

另外,建议如果全局变量超过3个,就定义成结构体吧。

这也是一种简单的封装。

后续再优化架构估计就是在这上面琢磨了。

总之,先把上面三种架构版本熟练掌握。

 

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

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

相关文章

笔试面试相关记录(4)

(1)实现防火墙的主流技术有哪些? 实施防火墙主要采用哪些技术 - 服务器 - 亿速云 (yisu.com) (2) char arr[][2] {a, b, c, d}; printf("%d", *(arr1)); 输出的是谁的地址?字符c 测试代码如下…

ThreeJS-3D教学一基础场景创建

Three.js 是一个开源的 JS 3D 图形库,用于创建和展示高性能、交互式的 3D 图形场景。它建立在 WebGL 技术之上,并提供了丰富的功能和工具,使开发者可以轻松地构建令人惊叹的 3D 可视化效果。 Three.js 提供了一套完整的工具和 API&#xff0…

JUC中创建的组件 多线程使用“哈希表”

JUC中创建的组件 JUC中创建的组件这些内容都不太常用,偶尔用到面试的时候,偶尔用到!到时候自行查找即可,本文主要来快速的过一下,留个印象即可~ JUC(java.util.concurrent)和多线程相关的工具…

python从入门到精通(一)

自己也有三四年的码龄了,目前,重拾起自己的博客,记录自己的学习笔记,为大家提供优质内容,也来巩固自己的学习内容。 很开心也成功成为了一名研究生,张张的研究方向是图像处理和计算机视觉这一块&#xff0c…

C++QT 作业8

#include "mywind.h" #include "ui_mywind.h" #include <iostream> #include <QIcon> #include <QLabel> #include <QLineEdit> #include <QDebug>//信息调试类 用于输出数据 Mywind::Mywind(QWidget *parent): QWidget(pa…

python3.11版本pip install ddddocr调用时报错got an unexpected keyword argument ‘det‘ 解决

一、如图出现如下问题 ddddocr.__init__() got an unexpected keyword argument det出现问题原因&#xff1a;python3.11默认安装版本就旧版的ddddocr1.0的&#xff0c;所以导致如下报错 二、解决方案一&#xff08;推荐&#xff09; python3.11的环境直接安装这个即可&…

物 理 层

二、物理层 1、物理层的基本概念 物理层的作用:尽可能的屏蔽掉传输媒体和通信手段的差异&#xff0c;使物理层上面的数据链路层感觉不到这些差异&#xff0c;使其只需要考虑如何完成本层的协议和服务 1.1、物理层的主要任务 机械特性&#xff1a;指明接口所用的接线器的形状…

关闭禁用chrome浏览器的阅读清单/强力书签

文章目录 前言操作 前言 阅读清单对我没啥用&#xff0c;还占用我位置&#xff0c;不小心点击到啥的&#xff0c;必须弃用 操作 chrome地址栏输入 chrome://flags/ 搜索book &#xff0c;关掉下面几个功能

Java 华为真题-猴子爬山

需求&#xff1a; 一天一只顽猴想去从山脚爬到山顶&#xff0c;途中经过一个有个N个台阶的阶梯&#xff0c;但是这猴子有一个习惯&#xff1a;每一次只能跳1步或跳3步&#xff0c;试问猴子通过这个阶梯有多少种不同的跳跃方式&#xff1f; 输入描述 输入只有一个整数N&#xff…

MySQL版数据库原理与应用期末复习重点(3)---画E-R图

文章目录 一、题目一1.1 题目描述1.2 解答 二、题目二2.1 题目描述2.2 解答 一、题目一 1.1 题目描述 设开发一个校园公共自行车管理系统&#xff0c;系统需要达到如下要求&#xff1a; &#xff08;1&#xff09;用户能够注册登录&#xff0c;能够根据借车点的名称查询借车…

若依注册的时候给个默认部门出现获取用户信息异常

想在注册的时候在数据库中查询一个部门给它一个默认部门&#xff0c;结果出现异常——【[handleServiceException,59] - 获取用户信息异常】 经分析代码&#xff0c;此方法有如下注解 以上注解会在mapper.xml中做如下操作 在做此操作之前会进入一个拦截器&#xff0c;根据token…

PHP-composer安装扩展安装,批量操作合并pdf

清除Composer缓存&#xff1a; 运行以下命令来清除Composer的缓存&#xff0c;并再次尝试安装包。 bash composer clear-cache 使用不同的镜像源&#xff1a; Composer使用的默认包源可能会受到限制或访问问题。你可以切换到使用其他镜像源&#xff0c;如阿里云、Composer中国…

安理【2022】

关键字&#xff1a; 出栈序列s2固定、快速排序2趟、next数组、二分查找比较次数log2n向上取整、 一、选择 二、填空 三、应用

Zookeeper 启动失败【Cannot open channel to 3 at election address...】

文章目录 完整报错信息解决方法1.检查文件夹权限2.未监听所有IP3.IP映射名称与 ID 不对应 完整报错信息 Cannot open channel to 3 at election address hadoop121/192.168.10.121:3888 java.net.ConnectException 解决方法 1.检查文件夹权限 检查当前用户是否拥有 Zookeep…

《Kubernetes部署篇:Ubuntu20.04基于containerd部署kubernetes1.25.14集群(多主多从)》

一、架构图 如下图所示: 二、环境信息 1、资源下载基于containerd部署容器版kubernetes1.25.14集群资源合集 2、部署规划主机名K8S版本系统版本内核版本IP地址备注k8s-master-121.25.14Ubuntu 20.04.5 LTS5.15.0-69-generic192.168.1.12master节点 + etcd节点k8s-master-131.…

adb shell命令查看当前屏幕可见最顶层Activity和Fragment及其调用栈

adb shell命令查看当前屏幕可见最顶层Activity和Fragment及其调用栈 &#xff08;1&#xff09;当前屏幕可见页面最顶层是哪个Activity: adb shell "dumpsys activity top | grep ACTIVITY | tail -n 1"&#xff08;2&#xff09;当前屏幕可见页面最顶层是哪个Fragm…

(图论) 827. 最大人工岛 ——【Leetcode每日一题】

❓ 827. 最大人工岛 难度&#xff1a;困难 给你一个大小为 n x n 二进制矩阵 grid 。最多 只能将一格 0 变成 1 。 返回执行此操作后&#xff0c;grid 中最大的岛屿面积是多少&#xff1f; 岛屿 由一组上、下、左、右四个方向相连的 1 形成。 示例 1: 输入: grid [[1, 0]…

解决Sublime Text用浏览器打开没反应

点击Open in Browser用浏览器打开没反应怎么解决&#xff1f; 点击Browse Packages 会跳转到这个文件夹页面 打开User文件夹 在文件中添加下面代码即可,chrome是默认打开浏览器&#xff0c;可以自定义为你想用的浏览器 { "browser": "chrome" }

Centos7 安装部署 Kubernetes(k8s) 高可用集群

1&#xff1a;基础环境准备 宿主机系统集群角色服务器IP主机名称容器centos7.6master192.168.2.150ks-m1dockercentos7.6master192.168.2.151ks-n1dockercentos7.6master192.168.2.152ks-n2docker 1.1 服务器初始化及网络配置 VMware安装Centos7并初始化网络使外部可以访问*…

华为HCIA学习(一)

文章目录 一.根据考试题总结知识点&#xff08;一题一点&#xff09;二.上午学习三.下午学习四.今天只做了70题&#xff0c;需要的可以找我 一.根据考试题总结知识点&#xff08;一题一点&#xff09; 二.上午学习 ① VRP系统是VRP是华为公司从低端到高端的全系列路由器、交换…