第五章为深入浅出AMetal,本文内容为5.1 接口与实现。
本章导读:
对于初学者来说,要想实现一个温度采集是很难的,但AMetal 可以做到。AMetal 构建了一套抽象度更高的标准化接口,封装了各种MCU 底层的变化,为应用软件提供了更稳定的抽象服务,延长了软件系统的生命周期。因此无论你选择什么MCU,只要支持AMetal,开发者无需阅读用户手册,甚至不需要知道什么是AMetal,就可以高度复用原有的代码。
尽管你已经得心应手地使用AMetal 编写了很多的程序,但还是想深入了解更多的接口是如何实现的?那么我们不妨从这里开始AMetal 的神奇之旅!
5.1 接口与实现
>>> 5.1.1 GPIO 接口函数
AMetal 提供了操作GPIO 的标准接口函数,所有GPIO 的标准接口函数原型位于ametal_am824_core_1.00\ametal\common\interface\am_gpio.h 文件中,其中包括宏定义和提供给用户操作GPIO 的函数原型的声明。即:
-
配置引脚功能和模式:int am_gpio_pin_cfg(int pin, uint32_t flags)
-
获取引脚电平:int am_gpio_get(int pin)
-
设置引脚电平:int am_gpio_set(int pin, int value)
-
翻转引脚电平:int am_gpio_toggle(int pin)
1. 配置引脚功能和模式
其中的pin 为引脚编号,格式为PIOx_x,比如,PIO0_0 用于指定配置相应引脚。在GPIO标准接口层中,所有函数的第一个参数均为pin,用于指定具体操作的引脚,相关的宏定义在ametal_easy_arm_lpc8xx\ametal\lpc8xx\drivers\include\lpc8xx_pin.h 中定义。
flags 为配置标志,由“通用功能 | 通用模式 | 平台功能 | 平台模式”(‘|’就是C 语言中的按位或)组成。通用功能和模式在am_gpio.h 文件中定义,是从标准接口层抽象出来的GPIO 最通用的功能和模式,格式为AM_GPIO_*。通用功能相关宏定义与含义详见表5.1,通用模式相关宏定义与含义详见表5.2。
表5.1 引脚通用功能
表5.2 引脚通用模式
平台功能和模式与具体芯片相关,会随着芯片的不同而不同,平台功能和模式相关的宏定义在lpc8xx_pin.h 文件中定义,芯片引脚的复用功能和一些特殊的模式都定义这个文件中,格式为PIO*_*_*,比如,PIO0_4_UART0_TX,PIO0_4 的串口0 发送。
如果需要找到PIO0_0 相关的平台功能和平台模式,可以打开lpc8xx_pin.h 这个文件,找到PIO0_0为前缀的宏定义,PIO0_0 相关的平台功能详见表5.3,平台模式详见表5.4。
表5.3 PIO0_0 平台功能
表5.4 PIO0_0 平台模式
在这里,读者可能会问,为什么要将功能分为通用功能和平台功能呢?各自相关的宏存放在各自的文件中,文件数目多了,会不会使用起来更加复杂呢?
通用功能定义在标准接口层中,不会随芯片的改变而改变。而GPIO 复用功能等,会随着芯片的不同而不同,这些功能是由具体芯片决定的,因此必须放在平台定义的文件中。如果这部分也放到标准接口层文件中,就不能保证所有芯片标准接口的一致性,从而也就失去了标准接口的意义。这样分开使用让使用者更清楚,哪些代码是全部使用标准接口层实现的,全部使用标准接口层的代码与具体芯片是无关的,是可跨平台复用的。
如果返回AM_OK,说明配置成功;如果返回- AM_ENOTSUP,说明配置的功能不支持,配置失败,配置引脚为GPIO 功能详见程序清单5.1。
程序清单5.1 配置管脚为GPIO 功能
程序清单5.2 配置引脚为AD 模拟输入功能
配置引脚为UART 功能详见程序清单5.3。
程序清单5.3 配置引脚为UART 功能
2. 获取引脚电平
其中的pin 为引脚编号,格式为PIOx_x,比如PIO0_0,用于获取引脚的电平状态。使用范例详见程序清单5.4。
程序清单5.4 am_gpio_get()范例程序
3. 设置引脚电平
其中的pin 为引脚编号,格式为PIOx_x。比如,PIO0_0 用于设置PIO0.0 引脚的电平。Value 为设置的引脚状态,0-低电平,1-高电平。如果返回AM_OK,说明操作成功,使用范例详见程序清单5.5。
程序清单5.5 am_gpio_set()范例程序
4. 翻转引脚电平
翻转 GPIO 引脚的输出电平,如果GPIO 当前输出低电平,当调用该函数后,GPIO 翻转输出高电平,反之则翻转为低电平。
其中的Pin 为引脚编号,格式为PIOx_x。比如,PIO0_0 用于翻转PIO0.0 引脚的电平状态。如果返回AM_OK,说明操作成功,使用范例详见程序清单5.6。
程序清单5.6 am_gpio_toggle()范例程序
5. 范例
显然,控制LED0 点亮或熄灭是通过GPIO 输出0 或1 实现的,因此需要先调用am_gpio_pin_cfg()函数将GPIO 配置为输出模式,并初始化为高电平,确保初始时LED0 处于确定的熄灭状态,接着调用am_gpio_set()函数,使PIO0_20 输出低电平点亮LED0,其相应的代码详见程序清单5.7。
程序清单5.7 点亮LED 范例程序
LED 不停地闪烁就是让一个I/O 不断翻转的过程,详见程序清单5.8。
程序清单5.8 单个LED 闪烁范例程序(1)
AMetal 针对I/O 提供了引脚电平翻转函数am_gpio_toggle(),详见程序清单5.9。
程序清单5.9 单个LED 闪烁范例程序(2)
假如使蜂鸣器发出1KHz 频率的声音,1KHz 频率对应的周期为:T=1/1000(s)=1(ms),由于一个周期是低电平(接通)时间和高电平(断开)时间的总和,因此在一个周期内,高、低电平保持的的时间分别为500us。由此可见,要使蜂鸣器不间断地发声,只要以500us 的时间间隔不断的翻转引脚的输出电平即可,详见程序清单5.10。
程序清单5.10 蜂鸣器发声范例程序
>>> 5.1.2 LED 接口与实现
下面将使用这些程序设计的概念实现通用函数接口,比如,家用电器的某个动作完成时,或工业现场数据采集的上下限报警,都会通过LED 提醒操作者。显然LED 驱动软件应用非常广泛,完全有必要编写一个通用函数作为接口以便复用。编写通用函数应该建立一个“.h”文件和一个“.c”文件,.h 文件用于提供接口,告知调用者提供了哪些接口,.c 文件用于实现各个接口函数,所以需要建立一个led.c 文件和led.h 文件,并将.c 文件添加到工程中。
显然LED 只有点亮、熄灭和翻转3 种操作,如果我们不在乎抽象性的话,则可以直接调用AMetal 函数实现。抽象的方法在操作LED 的实现代码和使用操作LED 的代码之间添加一个函数层,创建一个定义明确的接口,正确的抽象性是将对象的实现和它的接口分离。即将操作LED 的方法“声明”函数原型如下:
其中的led_id 对应的LED 编号,为了方便调用者以后不用再查看原理图,则将LED 与GPIO 的对应关系定义在一个数组中,其相应的代码详见程序清单5.11。
程序清单5.11 定义LED 对应的GPIO 口
那么调用者只要将索引号传入数组即可。由于I/O 口的数量只有8 个,则led_id 的有效值是0 ~ 7,所以需要判定led_id 是否合法防止数组越界,其相应的代码详见程序清单5.12。
程序清单5.12 通用接口函数(led.c)的实现(1)
编程到这里貌似已经很完善了,但LED 还是不能工作,因为还没有将GPIO 配置为输出模式,其相应的代码详见程序清单5.13。
程序清单5.13 添加初始化函数
这里并没有简单地将GPIO 初始化为输出,而是在配置为输出模式的同时,初始化GPIO为高电平,以保证LED 处于熄灭状态。此时编程完毕,则将相关的函数接口声明封装到led.h文件中,详见程序清单5.14。当后续需要调用时,只需要 #include "led.h"就可以了。
程序清单5.14 在led.h 中添加函数声明
在实际的使用中,接口函数都应添加详细的描述,告诉调用者应该如何调用这些接口。为了方便调用,可以在led.h 中将LED 的编号与实际数组中的索引号的对应关系使用宏定义出来。那么在调用LED 接口函数时,就不再需要关心led_id 的具体数值,直接使用宏即可,其相应的代码详见程序清单5.15。
程序清单5.15 LED_ID 的定义
此时,如果要点亮LED0,则调用led_on(LED0)即可。这个接口是否已经做到很通用了呢?虽然LED 对应的GPIO 信息中包含了I/O 信息,却没有包括对应的电平信息。如果仅仅看数组,而不看硬件原理图,还是不知道点亮或熄灭LED 究竟是高电平还是低电平?
由于LED 对应的管脚信息和相应的电平信息分别属于不同的数据类型,显然只有使用结构体,才能将不同类型的数据放在一起作为一个整体来对待。同时注意在声明结构体时给出typedef 定义,且在定义的类型名称后面追加标签,比如,led_info,其相应的数据结构详见程序清单5.16(3)~ (8)通用接口函数的实现。
程序清单5.16 通用接口函数(led.c)的实现(2)
如果我们需要改变处理数据的方法,则只需要在一个地方进行修改就可以了,而不必改动程序中所有直接访问数据的地方。正确的封装机制,不仅鼓励而且强迫隐藏实现细节。它使你的代码更可靠,而且更容易维护。文件led.h 仅包含了相应的接口函数的声明,而在led.c中对它们进行定义,实际上用户是看不到led.c 的。
在实际的应用中,用户使用LED 有两种情况,绝大部分情况都是使用AM824-Core 板载的两个LED,但在流水灯实验中,使用的是MiniPort-LED 上的8 个LED,它们对应的引脚是不同的,基于此,可以在led.h 文件中定义一个宏USE_MINIPORT_LED,默认值为0,使用板载LED,为1 时使用MiniPort-LED。引脚信息数组g_led_info 的定义修改如下:
显然,根据抽象定义的接口操作对象,将极大地减少了子系统实现之间的相互依赖关系,也产生了可复用的程序设计的原则:只针对接口编程而不是针对实现编程。因为针对接口编程的组件不需要知道对象的具体类型和实现,只需要知道抽象类定义了哪些接口,从而减少了实现上的依赖关系。
实际上,这些接口并不妨碍你将一个对象和其它对象一起使用,因而对象只能通过接口来访问,所以并不会破坏封装性。
>>> 5.1.3 I/O 接口与中断
GPIO 触发部分主要是使GPIO 工作在中断状态的相关操作接口,详见表5.5。
表5.5 GPIO 触发相关接口函数
1. 配置引脚触发条件函数
配置引脚触发函数原型如下:
其中的pin 为引脚编号,格式为PIOx_x,比如PIO0_0,配置相应引脚的触发条件。Flag为触发条件,所有可选的触发条件详见表5.6。
表5.6 GPIO 触发条件配置宏
注意,这些触发条件并不一定每个GPIO 口都支持,当配置触发条件时,应检测返回值,确保相应引脚支持所配置的触发条件。细心的人可能会发现,这里的参数flag 为单数形式,而am_gpio_pin_cfg()函数的参数flag 为复数形式。当参数为单数形式时,则表明只能从可选宏中选择一个具体的宏值作为实参;当参数为复数形式时,则表明可以选多个宏值的或值(C 语言中的“|”运算符)作为实参。
如果返回AM_OK,说明配置成功,如果返回-AM_ENOTSUP,说明引脚不支持该触发条件,配置失败,使用范例详见程序清单5.17。
程序清单5.17 am_gpio_trigger_cfg ()范例程序
2. 连接引脚触发回调函数
连接一个回调函数到触发引脚,当相应引脚触发事件产生时,则会调用本函数连接的回调函数。其函数原型为:
其中的pin 为引脚编号,格式为PIOx_x,比如PIO0_0,将函数与相应引脚关联。pfn_callback 为回调函数,类型为am_pfnvoid_t (void (*)(void *) ),即无返回值,参数为void*型的函数。p_arg 为回调函数的参数为void *型,这个参数就是当回调函数调用时,传递给回调函数的参数。如果返回AM_OK,说明连接成功,使用范例详见程序清单5.18。
程序清单5.18 am_gpio_trigger_connect()范例程序
3. 断开引脚触发回调函数
与am_gpio_trigger_connect()函数的功能相反,当不需要使用一个引脚中断时,应该断开引脚与回调函数的连接;或者当需要将一个引脚的回调函数重新连接到另外一个函数时,应该先断开当前连接的回调函数,再重新连接到新的回调函数。其函数原型为:
其中的Pin 为引脚编号,格式为PIOx_x,比如PIO0_0,断开相应引脚的连接函数;pfn_callback 为回调函数,应该与连接函数对应的回调函数一致;p_arg 为回调函数的参数为void *型,应该与连接函数对应的回调函数参数一致。如果返回AM_OK,说明断开连接成功,使用范例详见程序清单5.19。
程序清单5.19 am_gpio_trigger_disconnect()范例程序
4. 打开引脚触发
打开引脚触发,只有打开引脚触发后,引脚触发才开始工作。在打开引脚触发之前,应该确保正确连接了回调函数并设置了相应的触发条件。其函数原型为:
其中的pin 为引脚编号,格式为PIOx_x,比如PIO0_0,打开相应引脚的触发。如果返回AM_OK,说明打开成功,使用范例详见程序清单5.20。
程序清单5.20 am_gpio_trigger_on ()范例程序
5. 关闭引脚触发
关闭后,引脚触发将停止工作,即相应触发条件满足后,不会调用引脚相应的回调函数。如需引脚触发继续工作,可以使用am_gpio_trigger_on()重新打开引脚触发。其函数原型为:
其中的pin 为引脚编号,格式为PIOx_x,比如,PIO0_0,关闭相应引脚的触发。如果返回AM_OK,说明关闭引脚触发成功,使用范例详见程序清单5.21。
程序清单5.21 am_gpio_trigger_off()范例程序
原文标题:周立功:深入浅出AMetal——接口与实现
文章出处:【微信号:ZLG_zhiyuan,微信公众号:ZLG致远电子】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论