第3章 访问硬件的设计模式
3.1 基本的硬件访问概念
嵌入式系统最明显的特征是必须直接访问硬件,软件可访问的硬件可分为四种:基础设施、通信、传感器和致动器。
基础设施硬件是指运行软件的计算机基础设施和设备,不仅包括CPU和内存,还包括存储设备、定时器、输入/输出设备、端口和中断等。
通信硬件是指在不同的计算机设备之间用于建立连接的硬件。
传感器和致动器是指用来检测和操纵物理单元的设备。
使用位域对硬件发出命令或返回数据是非常常见的。位域是可访问的内存单元中连续的比特块(如字节或字),组合在一起对硬件有一定的语义。例如,一个8位的字节可能分为四个不同的域映射到硬件设备。
0 0 0000 00
这些位表示内存映射硬件设备中的信息如下:
位域是使用C语言中的位运算符操作的,其中包括:&(按位与)、|(按位或)、~(按位非)、^(按位异或)、>>(右移)、<<(左移)。通常在C语言编程中的习惯做法是使用#define为与运算和或运算创建位掩码,并且赋予它们有意义的名字,如下:
#include <stdlib.h> #include <stdio.h> #define TURN_OFF (0x00) #define INITIALIZE (0x61) #define RUN (0x69) #define CHECK_ERROR (0x02) #define DEVICE_ADDRESS (0x01FFAFD0) void emergencyShutDown(void){ printf("OMG We're all gonna die!\n"); } int main() { unsigned char* pDevice; pDevice = (unsigned char *)DEVICE_ADDRESS; // pt to device // for testing you can replace the above line with // pDevice = malloc(1); *pDevice = 0xFF; // start with all bits on printf ("Device bits %X\n", *pDevice); *pDevice = *pDevice & INITIALIZE; // and the bits into printf ("Device bits %X\n", *pDevice); if (*pDevice & CHECK_ERROR) { // system fail bit on? emergencyShutDown(); abort(); } else { *pDevice = *pDevice & RUN; printf ("Device bits %X\n", *pDevice); }; return 0; };
左移和右移操作对于孤立的设置以及测试特殊的位非常有用,并且在串行位数据处理中也有用。如表达式“1<<3”设置3位得到值8,代码如下:
#define CHECKERROR (1<<3)C语言中的位域提供了另一种方法来表示设备接口位映射域。这个语法用结构体中的字段表示可变长度位域。: 运算符在定义中分离字段的长度和名字 ,代码如下:
#include <stdlib.h> #include <stdio.h> int main() { typedef struct _statusBits { unsigned enable : 1; unsigned errorStatus : 1; unsigned motorSpeed : 4; unsigned LEDColor : 2; } statusBits; statusBits status; printf("size = %d\n",sizeof(status)); status.enable = 1; status.errorStatus = 0; status.motorSpeed = 3; status.LEDColor = 2; if (status.enable) printf("Enabled\n"); else printf ("Disabled\n"); if (status.errorStatus) printf("ERROR!\n"); else printf("No error\n"); printf ("Motor speed %d\n",status.motorSpeed); printf ("Color %d\n",status.LEDColor); return 0; };
C语言中的位域有两个问题。首先,位序有编译器的处理器的依赖性;其次,编译器可能会强制字节填充规则。如上述代码中,在GUN C编译器返回状态长度为4个字节,即使一个无符号的字符的大小仅为1字节。而且,因为大多数CPU必须每次写一个字节或字,位域有可能不在一个原子步骤内写入,如果不同的位域使用单独的互斥信号,将导致线程安全问题。
另一个使用位域的潜在问题是,不可能在标量和用户自定义的结构体之间强制转换。因此,以下做法不被允许:
unsigned char f; f = 0xF0; status = (statusBits)f;
3.2 硬件代理模式
硬件代理模式(Hardware Proxy Pattern)创建软件单元负责访问硬件的一部分、硬件压缩封装以及编码实现。
3.2.1 抽象
硬件代理模式使用类(或结构体)封装所有硬件设备访问,无论其硬件接口是怎样的。代理为客户提供接口,用来从设备中读取或写入数据,以及初始化、配置和关闭设备等。
3.2.2 问题
通过提供位于客户和实际硬件之间的代理,解决了多个客户访问硬件所造成的各种问题,极大地限制了硬件改变的影响。同时,为了便于维护,设备使用的位编码、加密和压缩等细节将会通过硬件代理的内部私有方法来管理。
3.2.3 结构模式
模式结构如图所示,模式中可能有多个客户,但每个被控设备仅有单一的硬件代理。代理包括公有和私有方法、封装函数和数据等。
3.2.4 协作角色
3.2.4.1 硬件设备
该元素为具体硬件。
3.2.4.2 硬件代理
该元素包含为当前设备制定的数据和函数,通常包括init()、configure()和disable()等。其他一些公有方法提供向设备发送或接受设备数据的功能。硬件代理类中的关键功能有(以上述图中为例):
access() 此公有方法从设备中返回一个特殊值。
congfigure() 此公有方法提供配置设备的方法。
disable() 此公有方法提供设备安全关闭或禁用的方法。
deviceAddr 此私有变量提供底层直接访问硬件,它的数据类型由具体硬件设备来确定。在一些事件中,硬件代理模式提供的公有方法完全隐藏代理如何连接到实际设备。客户不能直接访问这个变量。
initialize() 此公有方法在第一次使用之前启动并初始化设备。
marshal() 此私有方法从各种其他方法中获取参数,并且可以执行任何需要加密、压缩或设备发送数据所需的位包装的操作。
mutate() 此公有方法向设备写入数据。
unmarshal() 此私有方法执行任何需要从设备中获取数据的解包、加密或解压缩操作。
3.2.4.3 代理客户
调用硬件代理提供的接口访问硬件设备。
3.2.7 相关模式
该模式简单的实现不能实现任何线程安全性。他可以与临界模式、守卫调用模式或队列模式组合使用以提供线程安全性。为避免死锁,它可以与排序模式和同时锁定模式组合使用。
3.2.8 实例
带有内存映射的马达系统,接口有16位宽。马达代理的作用是以硬件接口独立的方式提供服务来访问硬件。
马达管理方法
- configure() 此方法设置马达的内存映射地址与旋转臂的长度。
- disable() 此方法关闭马达,保持设置的值原封不动。
- enable() 此方法使用目前设定的值启动马达。
- initialize() 此方法使用默认的设置值启动马达。
马达状态方法
- accessMotorDirection() 此方法方法返回当前马达的方向。
- accessMotorSpeed() 此方法返回马达的速度。
马达控制方法
- writeMotorSpeed() 此方法设置马达的速度并且调整旋转臂的长度。
马达错误管理方法
- clearErrorStatus() 此方法清除所有的错误位。
- accessMotorState() 此方法返回错误状态。
内部数据格式化方法(私有)
- marshal() 把客户数据格式转换为马达数据格式。
- unmarshal() 把马达数据格式转换为客户格式。