C 嵌入式系统设计模式 08:硬件代理模式

news2025/1/12 6:08:52

本书的原著为:《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() 函数则用于读取电机的旋转方向。

这种设计方式使得上层应用程序(客户端)可以通过调用硬件代理提供的函数来与硬件进行交互,而无需关心底层硬件的具体实现细节。这不仅简化了软件与硬件之间的交互过程,还提高了软件的可移植性和可维护性。

硬件代理关键函数和数据为:

  1. initialize():在首次使用之前调用,初始化设备。
  2. configure():用于配置硬件设备。
  3. disable():关闭或禁用硬件设备。
  4. access_xxx():从硬件设备读取数据。但在实际编程中,更常见的做法是使用get_xxx()
  5. mutate_xxx():向硬件设备写入数据。但在实际编程中,更常见的做法是使用set_xxx()

在编程和软件设计中,函数名通常会给出关于函数功能的一些提示。accessmutate 这两个名字就很有代表性,它们分别暗示了读取(或访问)数据和修改(或变更)数据的操作。

  • access 函数通常用于读取或检索数据,但不修改它。例如,在数据库编程中,一个 access 函数可能是用来从数据库表中读取记录的。在对象导向编程中,access 方法(也称为 getter 方法)可能用于读取对象的某个属性值。
  • mutate 函数则用于修改或变更数据。在数据库编程中,这可能意味着更新数据库表中的记录。在对象导向编程中,mutate 方法(也称为 setter 方法)可能用于设置或修改对象的某个属性值。
  1. marshal():私有函数。将上层应用的数据格式转换成硬件可以理解的数据格式。可能需要加密、压缩或打包。这确保了硬件设备接口的特殊性对上层应用(客户端)是隐藏的。实际硬件设备所需格式的数据被称为“原生格式”数据。容易被软件操作的数据被称为“呈现格式”数据。由于原生格式对上层应用是隐藏的,因此上层应用无法访问此函数。

通过将数据处理和转换的逻辑封装在私有函数中,软件设计可以确保上层应用代码与硬件设备接口的复杂性隔离开来。这样,上层应用开发者只需要关心如何操作 呈现格式 的数据,而不需要了解如何将这些数据转换为硬件设备能理解的 原生格式。这简化了上层应用的开发工作,并提高了系统的可维护性。

  1. unmarshal():私有函数。将硬件数据转换成上层应用可以理解的数据格式。可能需要解密、解压缩或解包。也就是将原生格式数据转换成呈现格式数据。与 marshal() 函数一样,隐藏了硬件设备的细节,因此上层应用无法访问此函数。
  2. deviceAddr:私有变量。提供了对硬件的低层次直接访问。在代理模式中,它显示为 void* ,但它可能是一个整形( int* )或其它数据类型。如果使用了更复杂的方式来访问设备,如 RS232 串行端口或以太网连接,那么这个数据类型及其访问方法将更加复杂。无论如何,硬件代理提供的公共函数完全隐藏了代理如何连接到实际硬件设备的过程。上层应用无法直接访问这个变量。

代理客户端

也就是使用硬件代理的上层应用。上层应用知道硬件代理的公用函数和数据,然后调用这些服务来访问硬件设备。

结果

这种模式非常常见,它提供了封装硬件接口和编码细节的所有好处。它为实际的硬件接口提供了灵活性,使其可以在不改变上层应用代码的情况下变更硬件。这是因为硬件细节都封装在硬件代理中。这意味着上层应用通常不知道数据的原生格式,而只以呈现格式操作它们。

然而,这可能会对运行时性能产生负面影响。有时,让上层应用了解编码细节并以原生格式操作数据可能更高效。但是,这会降低系统的可维护性,因为如果硬件接口或编码发生变化,就需要修改上层应用。

实现策略

如第1章所述,在 C 语言中,类可以通过不同的方式实现,从简单的文件到使用结构体来存储类属性,再到使用支持真正多态性的虚函数表。所有这些方法都可以用来实现这个非常简单的模式。

通常,一个硬件代理支持特定设备的所有功能,并且每个独立的设备都使用一个不同的硬件代理,但这并不是一条绝对的规则。将设备分离成不同的部分意味着这些设备可以遵循独立的维护路径,因此对于未来来说非常灵活。

一般来说,最好将实际硬件的位编码、加密和数据压缩隐藏在上层应用之外。然而,也可以通过使原生格式对上层应用可见来实现该模式,在在这种情况下,无需使用 marshal()unmarshal() 函数。

实例

见原书。

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

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

相关文章

ElasticStack安装(windows)

官网 : Elasticsearch 平台 — 大规模查找实时答案 | Elastic Elasticsearch Elastic Stack(一套技术栈) 包含了数据的整合 >提取 >存储 >使用,一整套! 各组件介绍: beats 套件:从各种不同类型的文件/应用中采集数据。比如:a,b,cd,e,aa,bb,ccLogstash:…

堆的结构实现与应用

目录 前言: 1.认识堆 a.如何认识堆? b.大根堆与小根堆 c.堆应用的简单认识 2.堆的结构与要实现的功能 3.向上调整算法 4.向下调整算法 5.向堆插入数据并建堆 6.堆的大小 7.堆的判空 8.取堆顶数据 9.删除堆顶数据 10.向上调整时间复杂度 11.向下调整时…

Js如何判断两个数组是否相等?

本文目录 1、通过数组自带方法比较2、通过循环判断3、toString()4、join()5、JSON.stringify() 日常开发,时不时会遇到需要判定2个数组是否相等的情况,需要实现考虑的场景有: 先判断长度,长度不等必然不等元素位置其他情况考虑 1…

jmeter下载base64加密版pdf文件

一、何为base64加密版pdf文件 如下图所示,接口jmeter执行后,返回一串包含大小写英文字母、数字、、/、的长字符串,直接另存为pdf文件后,文件有大小,但是打不开;另存为doc文件后,打开可以看到和…

Docker技术仓库

数据卷 为什么用数据卷? 宿主机无法直接访问容器中的文件容器中的文件没有持久化,导致容器删除后,文件数据也随之消失容器之间也无法直接访问互相的文件 为解决这些问题,docker加入了数据卷机制,能很好解决上面问题…

MT8788|MTK8788安卓核心板参数_4G联发科MTK模块

MT8788核心板是一款功能强大的4G全网通安卓智能模块。该模块采用了联发科AIOT芯片平台,具有长达8年的生命周期。MT8788模块内置了12nm制程的八核处理器,包括4个Cortex A73和4个Coretex A53,主频最高可达2.0GHZ。标配内存为4GB64GB&#xff0c…

Github 2024-02-22 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-02-22统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目4非开发语言项目2Go项目2HTML项目1Dart项目1Vue项目1JavaScript项目1TypeScript项目1 《Hello 算法…

HarmonyOS开发技术全面分析

系统定义 HarmonyOS 是一款 “ 面向未来 ” 、面向全场景(移动办公、运动健康、社交通信、媒体娱乐等)的分布式操作系统。在传统的单设备系统能力的基础上,HarmonyOS提出了基于同一套系统能力、适配多种终端形态的分布式理念,能够…

node版本管理工具之nvm的安装和使用

一、nvm的介绍 1,什么是nvm? nvm是Node Version Manager的简称,即为node版本管理工具 2,为什么要选择使用nvm? nodejs使用的场景越来越多,因为是开源软件,具备大多数开源软件都存在的"…

测试多线程架构的问题

在测试多线程架构时,需要考虑多个方面以确保系统的稳定性和性能。以下是一些关键问题,需要在测试过程中特别关注: 线程同步 多线程环境中,线程同步是非常重要的问题。由于多个线程可能同时访问共享资源,因此需要使用…

启动node服务报错Error: listen EACCES: permission denied 0.0.0.0:5000

启动node服务报错: 解决方案: 将监听端口改成3000或者其他 修改后结果: 参考原文: Error: listen EACCES: permission denied_error when starting dev server: error: listen eacc-CSDN博客

git版本回退在eclipse和命令中的操作

一.背景 老程序员了,熟悉eclipsesvn,git用的不溜。近几年用了git,偶尔修改了某个文件希望放弃本次修改重新恢复到最新版本重新修改。或者回退到某个版本,再修改。记录一下Eclipse中的操作,和命令操作的情况。 二.Ecli…

六、回归与聚类算法 - 线性回归

目录 1、线性回归的原理 1.1 应用场景 1.2 什么是线性回归 1.2.1 定义 1.2.2 线性回归的特征与目标的关系分析 2、线性回归的损失和优化原理 2.1 损失函数 2.2 优化算法 2.2.1 正规方程 2.2.2 梯度下降 3、线性回归API 4、回归性能评估 5、波士顿房价预测 5.1 流…

Java 注解机制解密并发编程的时间之谜:揭开Happens-Before的神秘面纱

优质博文:IT-BLOG-CN 一、简介 为什么需要happens-before原则: 主要是因为Java内存模型 , 为了提高CPU效率,通过工作内存Cache代替了主内存。修改这个临界资源会更新work memory但并不一定立刻刷到主存中。通常JMM会将编写的代码…

2024.4.21

多进程实现拷贝 #include <myhead.h> //定义结构体 typedef struct INFO {const char *src_file;const char *dest_file;int mv;int size;}info_t;//获取源文件的大小并且创建目标文件 int size_creat(const char *src_file,const char *dest_file) {//获取源文件的大小…

Linux多线程服务端编程:使用muduo C++网络库 学习笔记 第十二章 C++经验谈(一)

作者对C的基本态度是“练从难处练&#xff0c;用从易处用”&#xff0c;因此本章有几节“负面”的内容。作者坚信软件开发一定要时刻注意减少不必要的复杂度&#xff0c;一些花团锦簇的招式玩不好反倒会伤到自己。作为应用程序的开发者&#xff0c;对技术的运用要明智&#xff…

说一说Eclipse的项目类型和常用项目的区别

Eclipse在新建项目的时候有很多类型&#xff0c;包括Java project、Web project等等&#xff0c;如下&#xff1a; 那么这些项目类型有什么区别呢&#xff1f;我们在创建项目的时候应该如何选择&#xff0c;了解清楚这一点还是非常重要的&#xff0c;但记住一个出发点&#xff…

Simulink算法仿真注意事项

一、背景 首先&#xff0c;需要区分Simulink仿真和FPGA仿真的区别&#xff0c;即使最终算法可能要落地到FPGA上。 Simulink是MATLAB的一个重要组成部分&#xff0c;主要用于建模、仿真等&#xff0c;它可将理论研究和实践有机的相结合&#xff0c;并且用户不需要书写大量的代…

⭐北邮复试刷题LCR 037. 行星碰撞__栈 (力扣119经典题变种挑战)

LCR 037. 行星碰撞 给定一个整数数组 asteroids&#xff0c;表示在同一行的小行星。 对于数组中的每一个元素&#xff0c;其绝对值表示小行星的大小&#xff0c;正负表示小行星的移动方向&#xff08;正表示向右移动&#xff0c;负表示向左移动&#xff09;。每一颗小行星以相…

Elasticsearch:使用 ELSER v2 进行语义搜索

在我之前的文章 “Elasticsearch&#xff1a;使用 ELSER 进行语义搜索”&#xff0c;我们展示了如何使用 ELESR v1 来进行语义搜索。在使用 ELSER 之前&#xff0c;我们必须注意的是&#xff1a; 重要&#xff1a;虽然 ELSER V2 已正式发布&#xff0c;但 ELSER V1 仍处于 [预览…