电子说
周立功教授新书《面向AMetal框架与接口的编程(上)》,对AMetal框架进行了详细介绍,通过阅读这本书,你可以学到高度复用的软件设计原则和面向接口编程的开发思想,聚焦自己的“核心域”,改变自己的编程思维,实现企业和个人的共同进步。
第八章为深入理解AMetal,本文内容为8.2 HC595 接口。
8.2 HC595 接口
HC595 是一种“串转并”的外围器件,可以通过GPIO 控制数据引脚和时钟引脚实现数据的输出。但在一些具有SPI 外设的MCU 中,往往使用SPI 控制HC595 输出,使驱动程序更简洁。为了屏蔽底层数据输出方式的差异性,可以按照LED 通用接口的设计方法为HC595定义相关接口。
>>> 8.2.1 定义接口
1. 接口命名
由于操作的对象是HC595,因此接口命名以“am_hc595_”作为前缀。HC595 基本的操作是输出并行数据,其相应的接口名为:
am_hc595_send
特别地,HC595 的输出为三态输出,其由OE 引脚控制,因此可以定义相应接口使能输出(正常输出数据)或禁能输出(高阻状态)。其相应的的接口名为:
am_hc595_enable
am_hc595_disable
2. 接口参数
在LED 通用接口的设计中,通常可能存在多个LED,因而使用了唯一ID 号led_id 对多个LED 进行区分。按照这种逻辑,是否也需要使用hc595_id 来区分不同的HC595 呢?
在LED 通用接口中,使用了ID 号区分不同的LED,这就要求在接口实现中完成led_id到对应LED 设备之间的转换,即通过ID 号搜索到对应的LED 设备。显然,随着LED 设备的增加,搜索耗时也将增加。使用ID 号区分不同的LED,虽然简洁易懂,但效率不高。
对于操作LED 而言,通常都是秒级的,不会以极快的速率操作LED。如果LED 变化太快,则肉眼无法观察相应的现象是没有意义的。因此搜索耗时的影响对于LED 来说,可以忽略不计。虽然HC595 输出控制的具体器件是不确定的,但作为通用输出器件,其输出的效率应该尽可能高,不要因为驱动实现的策略而影响了输出效率,比如,常见的GPIO 能够以MHz 的速率输出,这种变化是us 级别的,显然此时搜索耗时会对输出效率产生一定的影响。
为了达到快速输出的目的,最好不存在任何搜索过程,直接操作相应的HC595 对象实现输出。在这种情况下,使用指向对象的指针就是很好的解决办法,只要具有指向对象的指针,就可以直接使用对象提供的方法。
显然使用指向对象的指针,只是为了提高接口实现的效率,与用户并无直接关系。对于用户来说,其无需关心这个指针的具体类型。为了对用户屏蔽“指针”的概念,可以为该指针单独定义一个类型,这就是本书中常常提及的“句柄”概念。由于现在还不清楚HC595对象的指针类型,可以先定义一个无类型的指针类型作为句柄类型。比如:
该类型的句柄本质上是指向HC595 对象的指针,其本身就代表了系统中确定的一个HC595 对象。基于此,所有接口均使用该类型作为第一个参数,即:
am_hc595_enable (am_hc595_handle_t handle);
am_hc595_disable (am_hc595_handle_t handle);
am_hc595_send (am_hc595_handle_t handle);
特别地,对于am_hc595_send(),还需要使用参数指定发送的数据,往往使用一个指向数据首地址的指针和数据的字节数表示一段数据。即可为am_hc595_send()新增两个参数:
am_hc595_send (am_hc595_handle_t handle, const void *p_data, size_t nbytes) ;
其中,p_data 指向了数据的首地址,使用void *类型,使其可以指向任意数据类型的首地址,使用const修饰符,表明本接口仅用于发送数据,不会改变数据内容;nbytes 指定了发送数据的字节数,若只有单个HC595,则输出是单个字节(8 位),若有多个HC595 级联,则输出是n 个字节(n 为HC595 的个数,共计8×n 位)。
实际中,单个HC595 只能输出8位数据,为了输出更多位数的数据,可以使用级联的方式将多个HC595级联起来。因此,这里的HC595“句柄”代表的HC595 设备可能包含多个级联的HC595,以实现多位数据的输出。
3. 返回值
接口无特殊说明,直接将所有接口的返回值定义为int 类型的标准错误号。基于此,HC595 接口的完整定义详见表8.4。其对应的类图详见图8.5。
表8.4 HC595 通用接口(am_hc595.h)
图8.5 HC595 对应的类图
特别注意,当前接口中的am_hc595_handle_t 类型为void *类型,最终,其需要是指向对象的指针类型。随着后文对接口实现的介绍,会定义相应的设备类型,到时再更新具体的定义。
>>> 8.2.2 实现接口
1. 抽象的HC595 设备类
与LED 通用接口的实现类似,为了屏蔽底层实现的差异性,可以将一些与底层硬件相关的功能进行抽象,根据三个接口,可以定义相应的三个抽象方法,并将其存放在一个虚函数表中。即:
类似地,将抽象方法和p_cookie 定义在一起,即为抽象的HC595 设备。比如:
显然,具体的HC595 设备直接从抽象的HC595 设备派生,然后由具体的HC595 设备根据实际的硬件,实现3 个抽象方法。
与抽象LED 设备的定义相比可以发现,这里定义抽象设备的方法和抽象LED 设备定义的方法是完全一致的,可以将这种方法作为定义一种抽象设备的模板,即:首先,根据接口的定义,整理需要具体设备实现那些功能,然后将这些功能一一抽象为方法,并将它们存放在一个虚函数表中,这些抽象方法的第一个参数均为p_cookie。最后,将虚函数表和p_cookie整合在一个新的结构体中,该结构体类型即为抽象设备类型。伪代码详见程序清单8.18。
程序清单8.18 抽象设备定义的一般方法
伪代码中,[name]表示当前模块的具体名字,如led。当然,如果需要,可以在抽象筹备中添加其它需要的成员,如LED 设备中,使用链表管理多个LED 设备,因此还具有p_next指针成员。
在HC595 接口中,使用了handle 作为第一个参数,其本质上是指向设备的指针,由于所有具体设备的都是从抽象的HC595 设备派生的,因此,handle 的类型可以定义为:
如此一来,所有接口的实现都可以直接调用抽象方法实现,而抽象方法的具体实现是由具体的HC595 设备完成的。各HC595 接口的实现详见程序清单8.19。
程序清单8.19 HC595 接口实现
由于handle 是直接指向设备的指针,可以通过handle 直接找到相应的方法,因此,在整个接口的实现过程中,没有任何查询搜索的过程,效率较高。除此之外,当handle 直接指向设备后,也就无需再集中对各个HC595 设备进行管理,如LED 设备,由于存在查询搜索过程,因此不得不使用单向链表将系统中的各个LED 设备链起来,便于查找。
在接口实现中,没有与硬件相关的实现代码,仅仅是简单的调用了抽象方法。抽象方法需要由具体的HC595 设备来完成。由于各个接口的实现非常简单,往往将其实现直接以内联函数的形式存放在.h 文件中。
为便于查阅,如程序清单8.20 所示展示了抽象HC59 设备接口文件(am_hc595.h)的内容。其对应的类图详见图8.6。
程序清单8.20 am_hc595.h 文件内容
图8.6 抽象的HC595 设备类
程序中,am_static_inline 是内联函数的标识,其在am_types.h 文件中定义,定义的实际内容与编译器相关,如使用GCC 编译器,则其定义如下:
由于在不同编译器中,内联函数的标识不尽相同,为了使用户使用统一的标识,AMetal统一将内联标识符定义为了am_static_inline,使得用户在任何编译器中均可使用该标识作为内联标识,无需关心与编译器相关的细节问题。
2. 具体的HC595 设备类
以使用SPI 控制HC595 输出数据为例,简述具体HC595 设备的实现方法。首先应该基于抽象设备类派生一个具体的设备类,其类图详见图8.7,
图8.7 具体的HC595 设备类
可直接定义具体的HC595 设备类:
am_hc595_spi_dev_t 即为具体的HC595 设备类。具有该类型后,即可使用该类型定义一个具体的HC595 设备实例:
在使用SPI 控制HC595 时,需要知道HC595 相关的信息,如锁存引脚、输出使能引脚、SPI 时钟频率等信息。
特别地,当SPI 输出数据时,可以指定数据输出时的位顺序:最高位先输出或最低位先输出。最先输出的位决定了HC595 输出端Q7 的电平,最后输出的位决定了HC595 输出端Q0 的电平。显然,位的输出顺序直接影响了HC595 的输出,因此,具体输出顺序应该是由用户来决定的。
基于此,将需要由用户提供的设备相关信息存放到一个新的设备信息结构体类型中:
若使用MiniPort-595,其与AM824-Core 联合使用时,则其对应的设备实例信息可以定义如下:
同理,在设备类中需要维持一个指向设备信息的指针。此外,由于使用SPI 控制HC595时,HC595 相当于是一个SPI 从设备,为了使用SPI 接口与之通信,需要为HC595 定义一个与之对应的SPI 从设备,新增两个成员,完整的HC595 设备定义即为:
显然,在使用SPI 控制HC595 前,需要完成设备中各成员的赋值,这些工作通常在驱动的初始化函数中完成,定义初始化函数的原型为:
其中:
p_dev 为指向am_hc595_spi_dev_t 类型实例的指针;
p_info 为指向am_hc595_spi_info_t 类型实例信息的指针;
handle 为SPI 句柄,便于使用SPI 输出数据,初始化函数的返回值即为HC595 句柄。基于前面定义的设备实例和实例信息,其调用形式如下:
返回值即为HC595 实例的句柄,可以作为HC595 通用接口的第一个参数(handle)的实参。初始化函数的实现范例详见程序清单8.21。
程序清单8.21 初始化函数实现范例(SPI 控制HC595)
程序中,首先建立了标准的SPI 从设备,便于后续使用SPI 接口发送数据,然后初始化了p_info 成员,接着完成了抽象HC595 设备中p_funcs 和p_cookie 的赋值,最后,返回设备地址作为用户操作HC595 的句柄。其中,pfuncs 赋值为了&__g_hc595_spi_drv_funcs,其中包含了3 个抽象方法的具体实现,完整定义详见程序清单8.22。
程序清单8.22 抽象方法的实现(SPI 控制HC595)
由此可见,使用GPIO 接口am_gpio_set()控制OE 引脚的输出电平实现了HC595 的使能和禁能函数,使用SPI 接口函数am_spi_write_then_write()实现了发送数据函数。
为了便于查阅,如程序清单8.23 所示展示了具体HC595 设备接口文件(am_hc595_spi.h)的内容。
程序清单8.23 am_hc595_spi.h 文件内容
全部0条评论
快来发表一下你的评论吧 !