C 嵌入式系统设计模式 08:硬件代理模式
本书的原著为:《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》,讲解的是嵌入式系统设计模式,是一本不可多得的好书。
本系列描述我对书中内容的理解。本文章描述访问硬件的设计模式之一:硬件代理模式。
硬件代理模式
(Hardware Proxy Pattern) 是硬件抽象的典型模式。目的是封装细节。该模式通过创建软件模块来封装对特定硬件设备的操作,隐藏底层硬件的实现细节和复杂性,提供标准的接口给上层应用程序使用。
假设有一个嵌入式系统需要访问内存、传感器等硬件设备。如果每个应用都直接在程序中访问操作底层硬件,当更换了相同功能的不同硬件设备时,有可能硬件接口并不一致,而且对硬件的操作与控制方式也并不一样。这种情况下,就需要为每个硬件设备编写特定的访问代码,增加了软件开发的复杂性和工作量。
而采用硬件代理模式后,可以创建一个硬件代理来封装对内存、传感器等硬件设备的操作。应用程序通过调用硬件代理提供的标准接口来访问硬件设备,无需关心底层硬件的具体实现。当更换了相同功能的不同硬件设备时,只需要修改硬件代理的实现即可,无需修改客户程序的代码。这样就简化了软件与硬件的交互过程,提高了软件的可移植性和可维护性。
在这个过程中,最难的是定义一系列标准接口。标准接口是对硬件的抽象,而只要涉及到抽象,大都伴随艰难的脑力劳动。一个好的接口,能带来什么益处?答案是稳定。硬件可能随时会变,但好的接口通常不变,它比硬件要稳定。因此在接口之上的应用层,都不用变。这就将变化控制在硬件层这个狭小的范围内,从而让变更变得容易和可控。
摘要
硬件代理模式使用类或结构体来封装对硬件设备的所有访问,而不管其物理接口如何。硬件可以是内存、中断映射,也可以是通过总线、网络连接的设备。硬件代理提供一些服务,这些服务可以与硬件设备交互:初始化、配置、关闭、读写数据等。硬件代理为上层应用提供了一个与编码和连接无关的接口,因此如果硬件设备接口或者连接方式发生变化,则可以方便的修改现有代码。
问题
如果每个上层应用(书中称为“客户端” )都直接访问硬件设备,则由于硬件更改而导致的问题会加剧。比如数据编码方式、内存地址或连接方式发生变化,则必须跟踪并修改每一个上层应用。通过提供位于上层应用和硬件之间的代理,可以极大的减少硬件更改带来的影响。为了便于维护,高层应用不应该知道底层次代码的细节,包括数据编码方式、加密方式、压缩方式等。这些详细信息应由具有内部私有函数的硬件代理进行管理。
模式结构
模式结构见下图。
硬件代理客户端可能有多个,但是硬件设备只有 1 个硬件代理。硬件代理具有公共函数和私有的函数和数据。在 UML 图中,数据放在首部,比如 deviceAddr: void *
,函数放在数据的下方,比如 initialize():void
;公用函数和数据使用正常字体,私有函数和数据用带下划线的斜体。
模式详情
硬件设备
硬件设备
是一个具体的硬件实体,它本身无需编程即可运作。把它放到图中,只是为了便于理解模式结构。硬件代理
与 硬件设备
之间的 关联
是通过硬件接口实现的,这些接口可以是一个寄存器地址或其他类似机制。关联关系使一个类知道另外一个类的属性和方法,这里硬件代理与硬件设备是双向关联,大家相互知道对方的细节。
硬件代理
硬件代理封装了针对特定硬件的数据和函数,为每种硬件设备提供了一套统一的接口。通常每个硬件都会有 initialize()
、configure()
和 disable()
等基本操作函数,此外,硬件代理还提供了一组公共函数,用于对硬件进行读写访问。
尽管模式结构图中只标识出一个 access()
和 mutate()
函数,但实际应用中通常有多个这样的函数,每个函数都针对特定的读写目标,具有明确的语义和用途。例如,accessMotorSpeed()
函数用于读取电机的速度,而 accessMotorDirection()
函数则用于读取电机的旋转方向。
这种设计方式使得上层应用程序(客户端)可以通过调用硬件代理提供的函数来与硬件进行交互,而无需关心底层硬件的具体实现细节。这不仅简化了软件与硬件之间的交互过程,还提高了软件的可移植性和可维护性。
硬件代理关键函数和数据为:
initialize()
:在首次使用之前调用,初始化设备。configure()
:用于配置硬件设备。disable()
:关闭或禁用硬件设备。access_xxx()
:从硬件设备读取数据。但在实际编程中,更常见的做法是使用get_xxx()
。mutate_xxx()
:向硬件设备写入数据。但在实际编程中,更常见的做法是使用set_xxx()
。
在编程和软件设计中,函数名通常会给出关于函数功能的一些提示。
access
和mutate
这两个名字就很有代表性,它们分别暗示了读取(或访问)数据和修改(或变更)数据的操作。
- access 函数通常用于读取或检索数据,但不修改它。例如,在数据库编程中,一个 access 函数可能是用来从数据库表中读取记录的。在对象导向编程中,access 方法(也称为 getter 方法)可能用于读取对象的某个属性值。
- mutate 函数则用于修改或变更数据。在数据库编程中,这可能意味着更新数据库表中的记录。在对象导向编程中,mutate 方法(也称为 setter 方法)可能用于设置或修改对象的某个属性值。
marshal()
:私有函数。将上层应用的数据格式转换成硬件可以理解的数据格式。可能需要加密、压缩或打包。这确保了硬件设备接口的特殊性对上层应用(客户端)是隐藏的。实际硬件设备所需格式的数据被称为“原生格式”数据。容易被软件操作的数据被称为“呈现格式”数据。由于原生格式对上层应用是隐藏的,因此上层应用无法访问此函数。
通过将数据处理和转换的逻辑封装在私有函数中,软件设计可以确保上层应用代码与硬件设备接口的复杂性隔离开来。这样,上层应用开发者只需要关心如何操作
呈现格式
的数据,而不需要了解如何将这些数据转换为硬件设备能理解的原生格式
。这简化了上层应用的开发工作,并提高了系统的可维护性。
unmarshal()
:私有函数。将硬件数据转换成上层应用可以理解的数据格式。可能需要解密、解压缩或解包。也就是将原生格式数据转换成呈现格式数据。与marshal()
函数一样,隐藏了硬件设备的细节,因此上层应用无法访问此函数。deviceAddr
:私有变量。提供了对硬件的低层次直接访问。在代理模式中,它显示为void*
,但它可能是一个整形(int*
)或其它数据类型。如果使用了更复杂的方式来访问设备,如 RS232 串行端口或以太网连接,那么这个数据类型及其访问方法将更加复杂。无论如何,硬件代理提供的公共函数完全隐藏了代理如何连接到实际硬件设备的过程。上层应用无法直接访问这个变量。
代理客户端
也就是使用硬件代理的上层应用。上层应用知道硬件代理的公用函数和数据,然后调用这些服务来访问硬件设备。
结果
这种模式非常常见,它提供了封装硬件接口和编码细节的所有好处。它为实际的硬件接口提供了灵活性,使其可以在不改变上层应用代码的情况下变更硬件。这是因为硬件细节都封装在硬件代理中。这意味着上层应用通常不知道数据的原生格式,而只以呈现格式操作它们。
然而,这可能会对运行时性能产生负面影响。有时,让上层应用了解编码细节并以原生格式操作数据可能更高效。但是,这会降低系统的可维护性,因为如果硬件接口或编码发生变化,就需要修改上层应用。
实现策略
如第1章所述,在 C 语言中,类可以通过不同的方式实现,从简单的文件到使用结构体来存储类属性,再到使用支持真正多态性的虚函数表。所有这些方法都可以用来实现这个非常简单的模式。
通常,一个硬件代理支持特定设备的所有功能,并且每个独立的设备都使用一个不同的硬件代理,但这并不是一条绝对的规则。将设备分离成不同的部分意味着这些设备可以遵循独立的维护路径,因此对于未来来说非常灵活。
一般来说,最好将实际硬件的位编码、加密和数据压缩隐藏在上层应用之外。然而,也可以通过使原生格式对上层应用可见来实现该模式,在在这种情况下,无需使用 marshal()
和 unmarshal()
函数。
实例
见原书。
相关文章:

C 嵌入式系统设计模式 08:硬件代理模式
本书的原著为:《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》,讲解的是嵌入式系统设计模式,是一本不可多得的好书。 本系列描述我对书中内容的理解。本文章描述访问硬件的设计模式之一&…...

【k8s配置与存储--持久化存储(PV、PVC、存储类)】
1、PV与PVC 介绍 持久卷(PersistentVolume,PV) 是集群中的一块存储,可以由管理员事先制备, 或者使用存储类(Storage Class)来动态制备。 持久卷是集群资源,就像节点也是集群资源一样…...

【Vite】解决Vite http proxy error: Error: connect ECONNREFUSED
今天写bug,发现了这个问题 我经过我一晚上的搜索努力,在github上找到了解决办法,不得不说,交友网站还是很好用的。 参考 这一行是关键代码。 因为我连的是本地后台服务,所以最后配置成这样 server: {open: true,pro…...

FPGA领域顶级学术会议
FPGA领域顶级学术会议主要有FPGA,FCCM,FPL和FPT。 1 FPGA 会议全名是: ACM/SIGDA International Symposium on Field-Programmable Gate Arrays 网站是:https://dl.acm.org/conference/fpga FPGA常年在美国举办,每年2月,偏FPGA基础研究; 该会议的论文免费下载。这个比…...
罗技鼠标滚轮模式介绍 | 鼠标滚轮异响 - 解决方案
滚轮模式介绍 针对罗技的滚轮模式进行介绍: 普通滚轮:滚动时有明显段落感,无法快速滚动。 智能滚轮:滚动力量较弱时,与普通滚轮无异;滚动力量大时,鼠标会自动减小滚轮阻尼,从而使滚…...
Scrapy与分布式开发(2.2):正则表达式
使用Python的re模块进行正则表达式操作详细讲解 一、引言 正则表达式是一种强大的文本处理工具,它使用特定的模式来搜索、匹配和替换文本。Python的re模块(正则表达式模块)提供了正则表达式匹配操作的所有功能。下面我们将详细讲解如何使用re模块进行正则表达式的操作。 …...

今年“全国爱耳日”主题确定!立聪堂助听器组织社区义诊
2024年3月3日是第二十五个“全国爱耳日”。 今年的活动主题定为“科技助听,共享美好生活”,旨在倡导全社会关注听力健康,并鼓励听力受损人群利用科技手段实现早期康复。 为响应这一主题,知名助听器验配连锁机构立聪堂迅速行动起…...

区块链智能合约开发
一.区块链的回顾 1.区块链 区块链实质上是一个去中心化、分布式的可进行交易的数据库或账本 特征: 去中心化:简单来说,在网络上一个或多个服务器瘫痪的情况下,应用或服务仍然能够持续地运行,这就是去中心化。服务和应用部署在…...
Android 启动流程及 init 进程解析
一、Android 启动流程概括 按下电源键触发开机,从 ROM 加载引导程序 BootLoader 到 RAM 中,BootLoader 执行启动 Linux kernel,然后启动第一个用户进程 init,init 进程的工作包括挂载文件、创建文件目录、设置 selinux 安全策略&…...
Java设计模式:核心概述(一)
在软件开发中,设计模式是一种被广泛认可的解决方案,用于解决在软件设计中经常遇到的一些特定问题。Java作为一种面向对象的编程语言,特别适合于应用各种设计模式。本文将带您深入了解Java中的设计模式,包括它们的定义、出现的原因…...

计算机网络:IP
引言: IP协议是互联网协议族中的核心协议之一,负责为数据包在网络中传输提供路由寻址。它定义了数据包如何在互联网上从源地址传输到目的地址的规则和流程。IP协议使得各种不同类型的网络设备能够相互通信,实现了全球范围内的信息交换。 目录…...
CSS中使用变量的两个函数var和calc
CSS/CSS3 变量var()函数和calc()函数计算的使用 var()变量 var变量的定义语法 : --变量名 (两个短横线加上变量名) calc() 函数计算 calc使用的时候有几个地方需要注意: 1. 运算符前后都需要保留一个空格 2. 任何长度值都可以使用calc()函数进行计算; 3. 支持 - …...

了解docker与k8s
随着 k8s 作为容器编排解决方案变得越来越流行,有些人开始拿 Docker 和 k8s 进行对比,不禁问道:Docker 不香吗? k8s 是 kubernetes 的缩写,8 代表中间的八个字符。 其实 Docker 和 k8s 并非直接的竞争对手两者相互依存…...
服务器防火墙的应用技术有哪些
服务器防火墙的应用技术有哪些 1.数据包过滤技术 数据包过滤是最基本的服务器防火墙技术之一,它根据一系列预定义规则过滤进出网络的数据包。数据包过滤器通常基于IP地址、端口号和协议类型等信息来判断数据包是否合法,如果不符合规则,则将被…...

打开 Camera app 出图,前几帧图像偏暗、偏色该怎样去避免?
1、问题背景 使用的安卓平台,客户的应用是要尽可能快的获取到1帧图像效果正常的图片。 但当打开 camera 启动出流后,前3-5帧图像是偏暗、偏色的,如下图所示,是抓取出流的前25帧图像, 前3帧颜色是偏蓝的,…...

SD-WAN技术:优化国内外服务器访问的关键
在全球化的商业环境中,企业经常需要在国内访问国外的服务器。然而,由于地理位置和网络架构的限制,这种跨国访问往往会遇到速度慢、延迟高等问题。SD-WAN(软件定义广域网)技术的兴起,为企业提供了一种新的解…...

【MySQL】学习和总结标量子查询
🌈个人主页: Aileen_0v0 🔥热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 💫个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-kLo6jykc7AcEVEQk {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…...

vue3第三节(v-model 执行原理)
特殊说明: 以下vue3语法是基于 3.4之前版本进行使用的,3.4之后的版本 引入了 defineModel 宏,后续会介绍defineModel 1、vue3 与vue2 中v-model区别 vue3 中v-model绑定的不再是value,而是modelValue,接收的方法也不再…...

RunnerGo UI自动化测试脚本如何配置
RunnerGo提供从API管理到API性能再到可视化的API自动化、UI自动化测试功能模块,覆盖了整个产品测试周期。 RunnerGo UI自动化基于Selenium浏览器自动化方案构建,内嵌高度可复用的测试脚本,测试团队无需复杂的代码编写即可开展低代码的自动化…...
Android 指南针校准进度计算实现
关于Android中指南针的实现,我们可以使用传感器来获取设备的方向信息,从而实现指南针功能。 Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);sensorManager (SensorManager) getSystemService(Conte…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...
redis和redission的区别
Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...
boost::filesystem::path文件路径使用详解和示例
boost::filesystem::path 是 Boost 库中用于跨平台操作文件路径的类,封装了路径的拼接、分割、提取、判断等常用功能。下面是对它的使用详解,包括常用接口与完整示例。 1. 引入头文件与命名空间 #include <boost/filesystem.hpp> namespace fs b…...