如何理解软件设计原则和面向接口编程的开发思想

电子说

1.3w人已加入

描述

周立功教授新书《面向AMetal框架与接口的编程(上)》,对AMetal框架进行了详细介绍,通过阅读这本书,你可以学到高度复用的软件设计原则和面向接口编程的开发思想,聚焦自己的“核心域”,改变自己的编程思维,实现企业和个人的共同进步。

第七章为面向通用接口的编程,本文内容为7.1 LED 控制接口、7.2 HC595 接口、7.3 蜂鸣器控制接口。

本章导读

虽然面向接口的编程简单易懂,但无法做到最大程度上地重用应用程序,这是导致软件开发成本居高不下的原因之一。而面向通用接口的编程就是基于AMetal 框架的应用程序设计,其核心是制定统一的接口规范,使程序员脱离非核心域的束缚聚焦于核心竞争力。

7.1 LED 控制接口

>>> 7.1.1 LED 通用接口

为了实现跨平台开发应用软件,AMetal 提供了操作LED 的通用接口,详见表7.1。

表7.1 LED 通用接口(am_led.h)

周立功

1. 设置LED 的状态

设置LED 状态的函数原型为:

周立功

其中,led_id 为LED 编号,AM824-Core 开发板共有两个LED:LED0 和LED1,其编号分别为0 和1。如果LED 的状态state 值为AM_TRUE,则点亮LED;反之state 值为AM_FALSE,则熄灭LED,其相应的范例程序详见程序清单7.1。

程序清单7.1 am_led_set()范例程序

周立功

2. 点亮LED

点亮LED 的函数原型为:

周立功

其中,led_id 为LED 编号,其相应的范例程序详见程序清单7.2。

程序清单7.2 am_led_on()范例程序

周立功

3. 熄灭LED

熄灭LED 的函数原型为:

周立功

其中,led_id 为LED 编号,其相应的范例程序详见程序清单7.3。

程序清单7.3 am_led_off()范例程序

周立功

4. 翻转LED 的状态

翻转LED 的状态就是使LED 由点亮状态转变为熄灭状态或由熄灭状态转变为点亮状态。

其函数原型为:

周立功

其中,led_id 为LED 编号,其相应的范例程序详见程序清单7.4。

程序清单7.4 am_led_toggle()范例程序

周立功

通过LED 通用接口控制AM824-Core 板载的两个LED,使两灯交替点亮(2 个LED 的流水灯效果),其相应的范例详见程序清单7.5。

程序清单7.5 两个LED 灯交替点亮(LED 流水灯)

周立功

>>> 7.1.2 LED 驱动

显然,如果要想使用通用接口操作LED,则必须为具体的LED 设备提供相应的驱动。基于此,AMetal 提供了相应的驱动初始化函数。当使用该函数初始化一个LED 实例后,即可使用通用LED 接口操作LED。其函数原型为:

周立功

  • p_dev 为指向am_led_gpio_dev_t 类型实例的指针;

  • p_info 为指向am_led_gpio_info_t 类型实例信息的指针。

1. 实例

定义am_led_gpio_dev_t 类型(am_led_gpio.h)实例如下:

周立功

其中,g_led_gpio 为用户自定义的实例,其地址作为p_dev 的实参传递。

2. 实例信息

实例信息主要描述了LED 的相关信息,比如,使用的GPIO 引脚号,LED 为低电平点亮与相应的LED 编号等信息。其类型am_led_gpio_info_t 的定义(am_key_gpio.h)如下:

周立功

(1)serv_inf0

serv_info 包含LED 编号信息,其类型am_led_servinfo_t 定义如下:

周立功

一个LED 设备可能包含多个LED,start_id 为LED 的起始编号,end_id 为LED 的结束编号,LED数目为end_id–start_id+1。由于AM824-Core 开发板仅有LED0和LED1两个LED,因此其起始编号为0,结束编号为1。

(2)p_pins

p_pins 指向存放各个LED 相应引脚号的数组,比如,AM824-Core 开发板的LED0 通过J9 与MCU 的PIO0_20 相连,LED1 通过J10 与MCU 的PIO0_21 相连。基于此,定义一个存放引脚号的数组。比如:

周立功

该数组的地址为p_pins 的值。

(3)active_low

当引脚输出低电平时,则点亮LED,因此active_low 的值为AM_TRUE。实例信息定义如下:

周立功

基于实例和实例信息,即可完成LED 的初始化。比如:

周立功

当完成初始化后,即可调用通用LED 接口操作LED0 和LED1。为了便于配置LED(修改实例信息),基于模块化编程思想,将初始化相关的实例和实例信息等定义存放在LED 配置文件中,通过头文件引出实例初始化函数接口,源文件和头文件的程序范例分别详见程序清单7.6 和程序清单7.7。

程序清单7.6 LED 实例初始化函数实现(am_hwconf_led_gpio.c)

周立功

程序清单7.7 LED 实例初始化函数声明(am_hwconf_led_gpio.h)

周立功

后续只需要使用无参数的实例初始化函数,即可完成LED 实例的初始化:

周立功

AM824-Core 的LED0 和LED1 作为一种板载资源,在系统启动时默认进行了初始化操作,因此应用程序无需再调用实例初始化函数,即可直接使用LED0 和LED1。

如果用户不需要使用LED,为了节省内存空间,可以将工程配置文件(am_prj_config.h)中的AM_CFG_LED_ENABLE 宏值修改为0,裁掉LED 程序,该宏本质上控制了板级初始化函数中的一段程序,详见程序清单7.8。

程序清单7.8 在板级初始化中裁剪LED 的原理

周立功

>>> 7.1.3 MiniPort-LED

MiniPort-LED 模块由8 个LED 组成的, 当MiniPort-LED 与AM824-Core 的PIO0_8~PIO0_15 相连时,如果GPIO 输出低电平,则点亮LED。由于MiniPort-LED 也是GPIO 驱动型LED,因此可以使用与板载LED 相同的LED 驱动。其实例定义如下:

周立功

为了避免与板载LED 编号冲突,Miniport-LED 应该使用与板载LED 不同的编号,比如,将编号定义为2~9。如果系统不使用板载LED0 和LED1(已将工程配置文件中的AM_CFG_LED_ENABLE 宏值修改为0),仅使用MiniPort-LED,则编号定义为 0~7。其实例信息定义如下:

周立功

基于实例和实例信息,即可完成Miniport-LED 的初始化。比如:

周立功

当完成初始化后,即可调用通用LED 接口操作LED2~LED9。为了便于配置(修改实例信息)Miniport-LED,基于模块化编程思想,将初始化相关的实例和实例信息等定义存放在Miniport-LED 的配置文件中,通过头文件引出实例初始化函数接口,源文件和头文件的程序范例分别详见程序清单7.9 和程序清单7.10。

程序清单7.9 Miniport-LED 实例初始化函数实现(am_hwconf_miniport_led.c)

周立功

程序清单7.10 MiniPort-LED 实例初始化函数声明(am_hwconf_miniport_led.h)

周立功

后续只需要使用无参数的实例初始化函数,即可完成Miniport-LED 实例的初始化:

周立功

当完成初始化后,即可使用通用LED 接口操作LED2~LED9。在AM824-Core 中,MiniPort-LED 作为可选的配板资源,在系统启动时没有像板载LED0 和LED1 那样默认执行初始化操作。如果要使用MiniPort-LED,则必须调用MiniPort-LED 实例初始化函数。

7.2 HC595 接口

>>> 7.2.1 HC595 通用接口

AMetal 提供了一套操作HC595 的通用接口,详见表7.2。

表7.2 HC595 通用接口(am_hc595.h)

周立功

1. HC595 输出

使能HC595 输出的函数原型为:

周立功

其中,handle 为HC595 的实例句柄,可通过具体的HC595 驱动初始化函数获得。其类型am_hc595_handle_t(am_hc595.h)定义如下:

周立功

未使能时,HC595 的输出处于高阻状态,使能后才能正常输出0 或1,范例程序详见程序清单7.11。

程序清单7.11 am_hc595_enable()范例程序

周立功

其中,hc595_handle 可以通过具体的HC595 驱动获得,若HC595 使用SPI 驱动,则可以通过如下语句获得:

周立功

该实例初始化函数am_miniport_595_inst_init()会在后面详细介绍。

2. 禁能HC595 输出

禁能后HC595 输出处于高阻状态,其函数原型为:

周立功

其中,handle 为HC595 的实例句柄,范例程序详见程序清单7.12。

程序清单7.12 am_hc595_disable()范例程序

周立功

3. 输出数据

输出数据的函数原型为:

周立功

其中,handle 为HC595 的实例句柄,p_data 为指向待输出数据的缓冲区,nbytes 指定了输出数据的字节数。对于单个HC595,其只能并行输出8 位数据,即只能输出单字节数据,其范例程序详见程序清单7.13。

程序清单7.13 输出单字节数据的范例程序

周立功

当需要并行输出超过8 位数据时,可以使用多个HC595 级联,此时即可输出多字节数据,范例程序详见程序清单7.14。

程序清单7.14 输出多字节数据的范例程序

周立功

对于MiniPort-HC595,仅包含一个HC595,因此每次只能输出1 字节数据。

>>> 7.2.2 HC595 驱动

AMetal 已经提供基于SPI 的HC595 的驱动,该驱动提供了一个初始化函数,使用该函数初始化一个HC595 实例后,即可得到一个通用的HC595 实例句柄。其函数原型为:

周立功

  • p_dev 为指向am_hc595_spi_dev_t 类型实例的指针;

  • p_info 为指向am_hc595_spi_info_t 类型实例信息的指针。

1. 实例

定义am_hc595_spi_dev_t 类型(am_hc595_spi.h)实例如下:

周立功

其中,g_miniport_595 为用户自定义的实例,其地址作为p_dev 的实参传递。

2. 实例信息

实例信息主要描述了HC595 的相关信息,比如,锁存引脚、输出使能引脚以及SPI 速率等,其类型am_hc595_spi_info_t 的定义(am_hc595_spi.h)如下:

周立功

其中,pin_lock 指定了HC595 的锁存引脚,即STR 引脚,该引脚与LPC824 连接的引脚为PIO0_14,因此pin_lock 的值应设置为PIO0_14。

pin_oe 指定了HC595 的输出使能引脚,该引脚未与LPC824 连接,固定为低电平,因此pin_oe 的值应设置为-1。

clk_speed 指定了SPI 的速率,可根据实际需要设定,若HC595 的输出用于驱动LED 或数码管等设备,则对速率要求并不高,可设置为 300000(300KHz)。

lsb_first 决定了一个8 位数据在输出时,输出位的顺序,若该值为AM_TRUE,则表明最低位先输出,最高位后输出,若该值为AM_FALSE,则表明最低位后输出,最高位先输出,最先输出的位决定了HC595 输出端Q7 的电平,最后输出的位决定了HC595 输出端Q0的电平。如设置为AM_TRUE,当后续发现输出顺序与期望输出的顺序相反时,可再将该值修改为AM_FALSE。基于以上信息,实例信息可以定义如下:

周立功

3. SPI 句柄handle

若使用LPC824 的SPI0 驱动HC595 输出,则通过LPC82x 的SPI0 实例初始化函数am_lpc82x_spi0_inst_init()获得SPI 句柄。即:

周立功

SPI 句柄即可直接作为handle 的实参传递。

3. 实例句柄

HC595 初始化函数am_hc595_spi_init ()的返回值即为HC595 实例的句柄,其作为HC595通用接口第一个参数(handle)的实参。其类型am_hc595_handle_t(am_hc595.h)定义如下:

周立功

若返回值为NULL,说明初始化失败;若返回值不为NULL,说明返回了有效的handle。

基于模块化编程思想,将初始化相关的实例和实例信息等定义存放在对应的配置文件中,通过头文件引出实例初始化函数接口,源文件和头文件的程序范例分别详见程序清单7.15和程序清单7.16。

程序清单7.15 实例初始化函数范例程序(am_hwconf_miniport_595.c)

周立功

程序清单7.16 实例初始化函数接口(am_hwconf_miniport_595.h)

周立功

后续只需要使用无参数的实例初始化函数,即可获取到HC595 的实例句柄:

周立功

当直接使用AM824-Core 与MiniPort-LED连接时,使用8 个GPIO 控制8 个LED,得益于MiniPort 接口的灵活性。当GPIO 资源不足时,可以在AM824-Core 与MiniPort-LED 之间增加MiniPort-595,使用595 的输出控制LED,达到节省引脚的目的。

当将MiniPort-595 和MiniPort-LED 连接后,等效的原理图详见图7.1。

周立功

图7.1 MiniPort-595+MiniPort-LED

通过控制HC595的输出,可以达到控制LED 点亮和熄灭的效果,范例详见程序清单7.17。

程序清单7.17 74HC595 驱动LED 的范例程序

周立功

由此可见,通用接口的好处就是屏蔽了底层的差异性,使得无论底层硬件怎么变化,应用程序都可以使用同一套接口操作LED。显然,无论是使用GPIO 直接驱动MiniPort-LED还是使用HC595 驱动MiniPort-LED,对于用户来说,都可以使用标准接口访问。AMeatl 提供了使用HC595 控制LED 的驱动,使用初始化函数完成相应实例的初始化后,即可使用通用接口操作LED。

>>> 7.2.3 使用HC595 驱动LED

AMetal 已经提供了使用HC595 控制LED 的驱动,该驱动提供了一个初始化函数,使用该函数初始化一个LED 实例后,即可使用通用LED 接口操作LED。其函数原型为:

周立功

  • p_dev 为指向am_led_hc595_dev_t 类型实例的指针;

  • p_info 为指向am_led_hc595_info_t 类型实例信息的指针。

1. 实例

定义am_led_hc595_dev_t 类型(am_led_hc595.h)实例如下:

周立功

其中,g_miniport_led_595 为用户自定义的实例,其地址作为p_dev 的实参传递。

2. 实例信息

实例信息主要描述了HC595 驱动LED 的相关信息,比如,LED 是否低电平点亮,对应的LED 编号等信息。其类型am_led_hc595_info_t 的定义(am_led_hc595.h)如下:

周立功

(1)serv_info

serv_info 包含了通用接口访问LED 的编号信息,当使用HC595 驱动时,编号信息可以保持不变,同样为2~9。

(2)hc595_num

hc595_num 表示HC595 的个数,显然一个HC595 只能输出8 位数据,因此最多控制8个LED。当需要控制的LED 数目超过8 个时,则需要使用多个HC595 级联。对于MiniPort-LED,其仅仅只有8 个LED,恰好使用一个HC595 控制8 个LED,因此hc595_num的值应设置为1。

(3)p_buf

p_buf 是指向一个大小为hc595_num 的缓冲区的指针,用于缓存各个595 当前的输出值。由于hc595_num 的值设置为1,因此缓存的大小也为1,缓存定义如下:

周立功

其中,g_miniport_led_595_buf 为缓存首地址,即可作为p_buf 的值。

基于以上信息,实例信息定义如下:

周立功

3. HC595 句柄handle

若使用Miniport-595 的输出控制MiniPort-LED,则应通过MiniPort-595 的实例初始化函数am_miniport_595_inst_init()获得HC595 的句柄。即:

周立功

HC595 句柄即可直接作为handle 的实参传递,基于实例、实例信息和HC595 句柄,即可完成LED 实例的初始化:

周立功

由于HC595 是LED 的另一种驱动方式,因此将其新增到am_hwconf_miniport_led.c 文件中。为了便于使用,将实例初始化函数的声明新增到am_hwconf_miniport_led.h 文件中,详见程序清单7.18 和程序清单7.19。

程序清单7.18 实例初始化函数范例程序(am_hwconf_miniport_led.c)

周立功

程序清单7.19 am_hwconf_miniport_led.h 文件更新

周立功

后续只需要使用无参数的实例初始化函数,即可完成Miniport-LED 实例的初始化:

周立功

当完成初始化后,即可调用通用LED 接口操作LED~LED9。MiniPort-LED 有两种驱动方式:GPIO 驱动和HC595 驱动,当使用MiniPort-LED 时,应该根据实际情况选择对应的实例初始化函数。但无论何种驱动方式,在完成初始化后,对于应用程序来说都是调用通用LED 接口操作LED2~LED9。

7.3 蜂鸣器控制接口

>>> 7.3.1 蜂鸣器通用接口

为了实现跨平台开发应用软件,AMetal 提供了操作蜂鸣器的通用接口,详见表7.3。

表7.3 蜂鸣器通用接口(am_buzzer.h)

周立功

1. 打开蜂鸣器

打开蜂鸣器的函数原型为:

周立功

打开蜂鸣器,使蜂鸣器开始鸣叫的范例程序详见程序清单7.20。

程序清单7.20 am_buzzer_on()范例程序

周立功

2. 关闭蜂鸣器

关闭蜂鸣器的函数原型为:

周立功

关闭蜂鸣器,使蜂鸣器停止鸣叫的范例程序详见程序清单7.21。

程序清单7.21 am_buzzer_off()范例程序

周立功

3. 蜂鸣器鸣叫指定时间(同步)

该函数用于打开蜂鸣器,使蜂鸣器鸣叫指定时间后自动关闭,该函数会一直等到蜂鸣器鸣叫结束后返回。其函数原型为:

周立功

使蜂鸣器鸣叫50 毫秒(“嘀”一声)的范例程序详见程序清单7.22。

程序清单7.22 am_buzzer_beep()范例程序

周立功

注意,由于该函数会一直等到蜂鸣器鸣叫结束后才会返回,因此主程序调用该函数后,会阻塞50ms。

4. 蜂鸣器鸣叫指定时间(异步)

该函数用于打开蜂鸣器,使蜂鸣器鸣叫指定时间后自动关闭,与am_buzzer_beep()函数不同的是,该函数会立即返回,不会等待蜂鸣器鸣叫结束。其函数原型为:

周立功

使蜂鸣器鸣叫50 毫秒(“嘀”一声)的范例程序详见程序清单7.23。

程序清单7.23 am_buzzer_beep_async()范例程序

周立功

注意,由于该函数不会等待蜂鸣器鸣叫结束,因此,主程序调用该函数后,会立即返回,不会被阻塞。显然,要使应用程序可以使用通用接口操作蜂鸣器,就需要为具体的蜂鸣器设备提供相应的驱动。

>>> 7.3.2 无源蜂鸣器驱动

无源蜂鸣器内部没有震荡源,需要外部使用一定频率的方波信号驱动才能发声。AMetal已经提供了无源蜂鸣器的驱动,直接输出PWM 驱动无源蜂鸣器发声。其函数原型为:

周立功

其中,pwm_handle 为标准的PWM 服务句柄,chan 为PWM 通道号,duty_ns 为和period_ns分别指定了输出PWM 波形的脉宽和周期,决定了蜂鸣器鸣叫的响度和频率。AM824-Core板载了一个无源蜂鸣器。只要短接J7_1 与J7_2,则蜂鸣器接入PIO0_24。

LPC82x 能够提供PWM 输出功能的外设是SCT(State Configurable Timer),AMetal 提供了对应的实例初始化函数,其原型为:

周立功

若需使用SCT 输出PWM,只需调用其对应的实例初始化函数即可获取标准的PWM 服务句柄:

周立功

获取的PWM 服务句柄即可作为am_buzzer_pwm_init()函数pwm_handle 的实参传递,当SCT 用作PWM 功能时,支持6 个通道,即可同时输出6 路PWM,各通道对应的I/O 口详见表7.4。

表7.4 各通道对应的IO 口

周立功

由于无源蜂鸣器使用的引脚为PIO0_24,其对应的通道为1,因此,chan 参数的值应设置为1。duty_ns 为和period_ns 分别指定了输出PWM波形的脉宽和周期,若频率设置为2500Hz,则对应的周期时间为:400000ns(1000000000 / 2500),占空比通常为50%(脉宽时间为周期时间的一半),即脉宽时间为:200000ns。实际中,可以根据实际发声效果修改脉宽时间和周期时间。

基于对各个参数的分析,即可调用am_buzzer_pwm_init()完成无源蜂鸣器的初始化:

周立功

无源蜂鸣器作为一种板载资源,在系统启动时已经默认进行了初始化操作,因此应用程序无需再手动调用无源蜂鸣器初始化函数,就可以直接使用通用接口操作蜂鸣器。

若用户不需要使用蜂鸣器,为了节省内存空间,可以将工程配置文件(am_prj_config.h)中的AM_CFG_BUZZER_ENABLE 宏值修改为0,以裁剪掉蜂鸣器,该宏本质上控制了板级初始化函数中的一段程序,详见程序清单7.24。

程序清单7.24 在板级初始化中裁剪蜂鸣器的原理

周立功

注:板级初始化函数在系统启动时自动调用,初始化完毕后才会进入应用程序入口,即am_main()。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分