第五章为深入浅出AMetal,本文内容为5.3 键盘扫描接口和5.4 PWM 接口。
5.3 键盘扫描接口
>>> 5.3.1 单个独立按键
1. 按键行为
如图5.1 所示,从按键的操作行为来看,共有3 种确定的方式,即无键按下、有键按下和按键释放。并用ret_flag 标志来区分这3 种按键的操作行为,其分别为0xFF、0 和1(通常用0xFF表示无效值)。当然还有可能发生的错误触发,ret_flag 也为0xFF。
图5.1 独立按键电路图
由于一次按键的时间通常为上百毫秒,相对于MCU 来说是很长的,因此不需要时时刻刻地检测按键,只需要每隔一定的时间(如10ms)检测GPIO 的电平即可。其检测方法如下(1 表示高电平、0 表示低电平):
(1)无键按下时,由于PIO0_1 内部自带弱上拉电阻,因此PIO0_1 为1;
(2)当KEY 按下时,则PIO0_1 为0。在下一次扫描(延时10ms 去抖动)后,如果PIO0_1 为1,说明错误触发;如果PIO0_1 还是0,说明确实有键按下,则将ret_flag 标志置0,执行相应的操作;
( 3)当KEY 释放时,则PIO0_1 由0 回到1,在下一次扫描(延时10ms 去抖动)后,如果PIO0_1 为0,说明错误触发;如果PIO0_1 还是1,说明按键已经释放,则将ret_flag标志置1,执行相应的操作。
由此可见,无论是否有键按下,都要每隔10ms 去扫描GPIO 的状态,那不妨将每次扫描获得的值称为当前键值(key_current_value)。由于按键存在抖动,因此需要将当前键值与下一次扫描得到的键值比较,以排除错误触发。由于下一次扫描的键值还是未知的,因此必须将本次的键值保存起来,等到下次扫描获取键值后,再与保存的键值比较。所以在每次扫描结束时,将key_current_value 的值转存到key_last_value 变量中。那么,对于每一次新的扫描来讲,key_last_value 始终保存了上次键值,如果新扫描获得key_current_value 键值与key_last_value 上次扫描的键值相等,说明该键值为有效值,然后将该键值保存到最终键值key_final_value 变量中,否则是错误触发。
2. GPIO 状态
从GPIO 的状态来看,分别为1/1、1/0、0/0、0/1 四种状态,初始化时:
key_last_value = 1,key_final_value = 1,ret_flag = 0xFF
其中,1/0 的“1”与“0”分别表示扫描前后获得的当前键值,每次扫描时都将ret_flag初始化为0xFF。
(1)1/1:如果GPIO 始终为高电平,说明状态没有发生转移(无键按下)
key_last_value = 1,key_current_value = 1,key_final_value = 1,ret_flag = 0xff
(2)1/0:如果GPIO 的状态从1 转移到0(有键按下),延时10ms 去抖动key_last_value = 1,key_current_value = 0,key_final_value = 1,ret_flag = 0xff0/0:如果GPIO 的状态还是0(确实有键按下),执行相应的操作
key_last_value = 0,key_current_value = 0
因为两次扫描获得key_current_value 键值都为0,所以将键值保存到key_final_value 中,同时置ret_flag 为0,即key_final_value = 0,ret_flag = 0,说明确实有键按下,否则视为错误触发。
(3)0/1:如果GPIO 的状态从0 转移到1(按键释放),延时10ms 去抖动
key_last_value = 0,key_current_value = 1,key_final_value = 0,ret_flag = 0xff
1/1:如果GPIO 还是1(按键确实释放),执行相应的操作
key_last_value = 1,key_current_value = 1
因为两次扫描获得key_current_value 键值都为1,所以将键值保存到key_final_value 中,同时置ret_flag 为1,即key_final_value = 1,ret_flag = 1,说明按键已经释放,否则视为错误触发。
3. 相关函数与示例
板级初始化和独立按键扫描函数详见程序清单5.42。
程序清单5.42 独立按键扫描程序(key1.c)
用于判断连续扫描的当前键值是否相等的检测过程,没有使用一个循环语句,且在扫描程序中避免了延时10ms 所带来的影响,从而极大地提高MCU 的运行效率。为了便于后续使用,不妨将key1.c 封装为key1.h 接口,详见程序清单5.43。
程序清单5.43 key1.h 文件内容
如果key_scan()的返回值为0,说明确实有键按下;如果key_scan()的返回值为1,说明按键已经释放;如果返回值为0xFF,说明无键按下。其相应的测试程序详见程序清单5.44,如果有键按下,则蜂鸣器“嘀”一声;当按键释放后,则LED0 翻转。
程序清单5.44 独立按键范例程序
>>> 5.3.2 多个独立按键
多个独立按键与单个独立按键的扫描原理是一样的,在单个独立按键中,每个变量仅使用了一位。由于变量的一位的0 或1 值就可以表示按键的2 种状态,所以,当需要多个按键时,则可以充分利用多个位,每位对应一个独立按键。同时,由于独立按键的按下状态对应的电平并不一定全是低电平,因此将按键对应的管脚和按下键对应的电平保存到一个结构体数组中。比如:
上述代码段定义的4 个按键对应的管脚分别为PIO0_1、PIO0_2、PIO0_3、PIO0_5,并假设其对应键按下的电平分别为1、0、1、0。而在这里仅有一个独立按键,因此结构体数组中只包含一个按键的信息:
按照一位对应一个按键的思想,在key1.c 的基础上,修改支持多个独立按键的程序keyn.c,详见程序清单5.45。
程序清单5.45 支持多个独立按键的扫描程序(keyn.c)
同时,将函数接口的声明和相关类型的定义存放在keyn.h 中,详见程序清单5.46。
程序清单5.46 keyn.h 文件内容
显然,多个独立按键的程序设计思想与独立按键还是一样的,绝大部分程序都保持一样。所不同的是多个独立按键的操作是通过多个位操作来实现的。如果只有一个独立按键,则检测到键值变化时,就一定是该键状态变化。而对于多个独立按键来说,如果仅检测到键值的变化,还无法区分是哪个按键发生了变化。在这里巧妙地使用了一个change 变量,用于标志位的变化情况。由于存在多个按键,因此扫描函数的返回值不能简单地使用0、1 来表示按下和释放,还必须包含区分是哪一个按键的信息。其规则如下:
返回值的类型为8 位无符号数,使用最高位来表示按下(0)或释放(1),低7 位用于区分具体的按键,值为 0 ~ N-1,N 为按键的个数,0~N-1 具体表示的按键与数组g_key_info[]中元素的一一对应。因此,当第i 个按键按下时,其返回值为i“ret_flag = i;”。第i 个按键按下时,返回值应该在i 值的基础上,将最高位置1,即“ret_flag = (1 << 7) | i;”。
为了方便后续使用,先不考虑软件定时器,将上述程序添加到key.h 接口。使用多个按键的范例程序,同样按照键按下蜂鸣器发出“嘀”的一声,等按键释放后,LED0 灯翻转的要求编程,详见程序清单5.47。
程序清单5.47 支持多个独立按键范例程序
如果使用软件定时器,那么还需要新增一个软件定时器初始化函数。比如:
由于使用软件定时器后,会自动调用keyn_scan()函数,当发现按键事件时,则调用按键处理程序。现在的问题是,在封装模块时,并不知道按键处理程序的作用,所以必须使用回调机制。即通过应用程序注册一个函数,当事件发生时,然后自动调用已经注册的函数。即:
则重新定义带软件定时器的初始化函数,比如:
将上述函数声明和回调函数的定义添加到keyn.h 文件(程序清单5.48),同时将该函数的实现代码添加到keyn.c 文件(程序清单5.49)。
程序清单5.48 keyn.h 文件内容
显然,当按键事件发生时,即自动调用初始化函数时注册的回调函数。
程序清单5.49 新增带软件定时器的初始化接口
>>> 5.3.3 矩阵键盘
虽然矩阵连接法可以提高I/O 的使用效率,但要区分和判断按键动作的方法却比较复杂,所以这种接法一般多用在计算机中。下面仍然以图4.15 所示的2×2 的矩阵键盘电路为例,详细介绍逐行逐列键盘扫描发的程序设计方法,其相应的接口详见程序清单5.50 所示的matrixkey.h 和与接口相应的实现详见程序清单5.51 所示的matrixkey.c。
程序清单5.50 matrixkey.h 文件内容
程序清单5.51 matrixkey.c 文件内容
为了使其它代码尽可能复用之前多个独立按键的程序,因此将扫描到的按键状态与键值的位一一对应,KEY0 对应bit0 ,KEY1 对应bit1……当有键按下时,其对应位为0;当按键释放时,其对应位为1。同时为了获取键值,还必须在初始化函数中,将行线配置为输出,列线配置为输入。由此可见,矩阵键盘和独立按键的主要区别在于键值的获取方式,为了使代码尽可能地复用,也将矩阵键盘的各个按键分别与键值的位对应起来。即:当有键按下时,其对应位为0;当按键释放时,其对应位为1。
如图5.2 所示的数码管的2 个com 端与矩阵键盘的列是复用的,PIO0_17 与PIO0_23 既是数码管的com0、com1,又是矩阵键盘的列线KL0、KL1 这样设计反而节省了引脚。作为键盘扫描时需将列线配置为输入,作为数码管扫描时需将com 端设置为输出。显然,在矩阵键盘与数码管联合使用时都要进行上述操作,则完全有必要将其封装为一个接口,即将matrixkey_scan_with_digtron()添加到程序清单5.52 所示的matrixkey.c 中,并将新的接口函数添加到程序清单5.53 所示的matrixkey.h 中。
图5.2 LED 显示器电路图
程序清单5.52 matrixkey_scan_with_digtron()函数实现
在这里,新增了一个保存开始扫描前键盘状态的g_col_level[]数组。在矩阵键盘扫描结束后,不仅会将列引脚恢复为输出状态,而且还会将其引脚电平恢复为扫描前的状态,从而使得整个键盘扫描程序结束后不影响与数码管公共引脚的状态。
程序清单5.53 matrixkey.h 文件内容
5.4 PWM 接口
大小和方向随时间发生周期性变化的电流称为交流,交流中最基本的波形称为正弦波,除此之外的波形称为非正弦波。计算机、电视机、雷达等装置中使用的信号称为脉冲波、锯齿波等,其电压和电流波形都是非正弦交流的一种。
PWM(Pulse Width Modulation)就是脉冲宽度调制的意思,一种脉冲编码威廉希尔官方网站 ,即可以按照信号电平改变脉冲宽度。而脉冲宽度调制波的周期也是固定的,用占空比(高电平/周期,有效电平在整个信号周期中的时间比率,范围为0~100%)来表示编码数值。PWM可以用于对interwetten与威廉的赔率体系 信号电平进行数字编码,也可以通过高电平(或低电平)在整个周期中的时间来控制输出的能量,从而控制电机转速或LED 亮度。
PWM 信号是由计数器和比较器产生的,比较器中设定了一个阈值,计数器以一定的频率自加。当计数器的值小于阈值时,则输出一种电平状态,比如,高电平。当计数器的值大于阈值,则输出另一种电平状态,比如,低电平。当计数器溢出清0 时,又回到最初的电平状态,即I/O 引脚发生了周期性的翻转而形成PWM 波形,详见图5.3。
图5.3 PWM 波形图
当计数器的值小于阈值时,则输出高电平;当计数器的值大于阈值时,则输出低电平。阈值为45,计数器的值最大为100。PWM 波形有三个关键点:起始点①,此时计数器的值为0;计数器值达到阈值②,I/O 状态发生翻转;计数器达到最大值③,I/O 状态发生翻转,计数器的值回到0 重新开始计数。
>>> 5.4.1 初始化
在使用PWM 通用接口前,必须先完成PWM 的初始化,以获取到标准的PWM 实例句柄。在LPC82x 中,能够提供PWM 输出功能的外设有SCT(State Configurable Timer),其实质是一个状态可编程定时器,可以用作普通定时器、输入捕获、PWM 输出等,功能非常强大。在这里,仅仅将其作为PWM 功能使用,AMetal 提供了将SCT 用作PWM 功能的实例初始化函数。其函数原型为:
函数的返回值为am_pwm_handle_t 类型的PWM 实例句柄,该句柄将作为PWM 通用接口中handle 参数的实参。类型am_pwm_handle_t(am_pwm.h)定义如下:
由于函数返回的PWM 实例句柄仅作为参数传递给PWM 通用接口,不需要对该句柄作其它任何操作,因此,完全不需要对该类型作任何了解。需要特别注意的是,若函数返回的实例句柄的值为NULL,则表明初始化失败,该实例句柄不能被使用。
直接调用该实例初始化函即可完成SCT 的初始化,并获取对应的实例句柄:
SCT 用作PWM 功能时,支持6 个通道,即可同时输出6 路PWM,各通道对应的I/O口详见表5.8。
表5.8 各通道对应的I/O 口
>>> 5.4.2 PWM 接口函数
AMetal 提供了3 个PWM 标准输出接口函数,详见表5.9。
表5.9 PWM 标准接口函数
1. 配置PWM 通道
配置一个PWM 通道的周期时间和高电平时间,其函数原型为:
如果返回AM_OK,说明配置成功;如果返回-AM_EINVAL,说明配置失败,范例程序详见程序清单5.54。
程序清单5.54 am_pwm_config()范例程序
2. 使能通道输出
使能通道输出,使相应通道开始输出波形,其函数原型为:
若返回AM_OK,说明使能成功,开始输出波形;若返回-AM_EINVAL,说明使能失败,范例程序详见程序清单5.55。
程序清单5.55 am_pwm_enable()范例程序
3. 禁能通道输出
禁能通道输出,关闭相应通道的波形输出,其函数原型为:
若返回AM_OK,说明禁能成功;若返回-AM_EINVAL,说明禁能失败,范例程序详见程序清单5.56。
程序清单5.56 am_pwm_disable()范例程序
>>> 5.4.3 蜂鸣器接口函数
在蜂鸣器的发声程序中,虽然延时时间只有500us,但对于MCU 来说非常耗费资源,因为延时期间无法做其它任何想做的事情。我们不妨用MCU 的PWM 输出功能来实现,即通过PWM 直接输出一个脉冲宽度调制波形来驱动蜂鸣器发声。假定波形的周期为1ms,且高低电平占用的时间相等,即占空比为50%,范例程序详见程序清单5.57。
程序清单5.57 蜂鸣器发声范例程序
为了方便控制蜂鸣器,基于PWM 接口函数编写蜂鸣器通用接口,将接口函数的声明和实现分别放到buzzer.h 和buzzer.c 文件中。当需要使用蜂鸣器时,直接调用蜂鸣器相关接口即可,buzzer.h 详见程序清单5.58。
程序清单5.58 蜂鸣器通用接口
当接口定义好后,则将实现全部放到buzzer.c 文件,详见程序清单5.59。
程序清单5.59 蜂鸣器通用接口实现
-
PWM
+关注
关注
114文章
5186浏览量
213819
原文标题:周立功:深入浅出AMetal——键盘扫描接口和 PWM 接口
文章出处:【微信号:ZLG_zhiyuan,微信公众号:ZLG致远电子】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论