单片机线反转法实现矩阵键盘
矩阵键盘原理
-
前文描述的独立按键需要每一个都需要占用一个引脚控制,如果按键数多了,控制将会变得麻烦,并且浪费资源
-
为此,使用矩阵键盘,每个引脚不连接单独的按键,而是连接一行或一列按键,当按下一个按键时,确定行列相交的坐标即可确定被按下的按键,如下图(最下面一排是独立按键,而上面的就是矩阵按键)
这样,只占用8个引脚,就实现了对16个按键的控制
-
为了确定哪一个的按键被按下,就需要确定按键连接到了哪一行、哪一列的端口,而进行检测的方式有多种
轮询方式
-
行列扫描法
进行逐行、逐列扫描,确定按下按键的行与列
-
首先,把矩阵的行线设置为上拉输入,列线输出高电平
-
配置列线轮流输出低电平,然后读取行线输入引脚的电平
如果没有按键被按下,则行线输入全为高电平;如果有按键按下,则行线输入引脚之一将检测到低电平
-
由于列线是轮流输出的,可以确定在输出到哪一列时哪一行出现了低电平,也即确定了按键是哪一行哪一列
实现简单,但是扫描速度较慢,如果有多个按键同时按下就会出现冲突
-
-
线反转法
其本质同扫描法,都是通过查询来看哪一行那一列的按键被按下,其不同之处在于其原理是将键盘的行线和列线交替设置为输入和输出模式
- 首先,把矩阵的行全部置为上拉输入,列线全部输出低电平
- 读取每行的电平,哪一行的电平变低,哪一行上就出现了被按下的键
- 然后进行线反转:把行置为推挽输出低电平、列置为上拉输入,重复一边读取流程,确定哪一列被按下
能够检测到所有按键的状态,多个按键按下也不会冲突,但是实现更复杂
-
关于输出方法
- 开漏输出不能输出高电平,需要上拉电阻才能输出高电平;但是在输出低电平时相当于接地,能够实现线与功能
- 推挽输出能够输出真正的高电平;但不能线与,同一行上两个或以上按键按下会造成损坏,使用推挽输出的矩阵键盘电路要在输出引脚接反向二极管进行保护
- 需要更详细的说明请在本合集查阅GPIO
事实上,尽管两种输出方式在硬件上有诸多不同,但是在实际使用中都能实现矩阵键盘的功能,可以随便选
借助中断实现按键扫描
-
上述的查询方式也称随机扫描法,本质就是用循环不停查询有没有按键被按下,占用CPU严重,进入键盘扫描程序后除了读取键盘输入状态外难以再进行其他工作,因此不具备实际价值
基于上述的原理,借助中断功能,实现实际应用中的按键检测方法
-
定时扫描法
同样属于查询方式,本质上是随机扫描的方法(线扫描法或线反转法)+定时器中断监控,每隔一段时间才中断进行扫描,规避了不停查询对CPU的占用
- 扫描方法同上,唯一的区别是像这样的扫描每隔一段时间(通常是20ms)进行一次,原理类似刷新数码管显示多位数
定时扫描法占用中断资源少,但是占用CPU资源多,会出现空扫描的情况,当工程复杂度变高时,按键不灵敏
-
中断监控法
不再使用定时器中断监控,而是使用按键输入中断,进一步降低了对CPU的占用
注意,一般这种用法适用于输入引脚来自同一组GPIO端口(因为同一组引脚共用一个中断出口,这样安排节省中断资源)的情况,而定时扫描法则可以是任意的引脚
- 首先,将矩阵的行配置为输出低电平,列配置为下降沿中断上拉输入
- 当有按键按下时,由于行被拉低、对应列的端口将会检测到下降沿,然后进入该列的中断服务函数
- 确定了列之后,在中断服务函数中通过查询来确定行:先把所有行置高,然后依次拉低每一行,检测该列的端口是否变为低电平,如果为低,证明是该行上的按键被按下
中断法占用中断资源多,但是规避了空扫描的情况,按键更为灵敏
定时扫描按键状态法
-
按键状态机
在按键一节中,提到为了提高CPU利用率应该尽可能规避使用延时来消抖;
定时扫描法有一种特殊的消抖方式:在定时中断服务程序中用3位存储单元记录最近三次定时中断检测到的按键状态(没有按键按下为1,有按键为0)111表示没有按键按下;000表示按键稳定闭合
按键状态的含义如下:
- 不需要扫描的状态:110:前两次没有检测到按键,最后才检测到,不作处理;001:可能是按键释放过程;011:按键被释放
- 需要进行按键扫描的状态:100:按下了按键;010:按键在按下途中无意松动;101:受负脉冲干扰,当作111处理;如果按键需要重复输入功能,那就还需要检测000状态
-
按键状态流程
综上,利用按键状态的流程是:
- 到按键检测的时间,输出全0扫描码,然后读出并记录按键状态
- 检测到100,则执行按键扫描;否则检测状态是否为010
- 检测到010,则表示受到干扰,重置为000;否则检查状态是否为000
- 检测到000,再去判断一次是否到了检测时间,如果到了就执行按键扫描,否则退出;没有检测到000则检测状态是否为101
- 检测到101,则表示受到干扰,重置为111;
- 退出检测程序
代码实现
-
定时扫描
使用线反转-定时扫描法实现矩阵键盘,其他方案不再演示
//设置定时器,每20ms中断一次进行扫描,这一部分省略,重点看扫描函数,将扫描函数放在定时器的中断服务中即可 //使用PC0-7作为矩阵键盘的扫描线,使用其他端口同理,修改代码即可 //此程序以高4位为行,低4位为列 //在主函数中调用之前的数码管显示函数,显示按下的键值 #define KeyPort_in PC_IDR #define KeyPort_out PC_ODR unsigned int keybefore = 0; //准备一个变量存放之前的键值,避免刷新重置了按键值,这是为了让数码管保持显示键值 //如果用作输入,可以直接判断0xFF为无效输入,或者使用标志位来判断是否更新键值 unsigned int KeyScan(unsigned int *keybefore) { u8 val=0xFF;//定义一个存放键值的变量 PC_DDR=0xF0; PC_CR1=0xFF; PC_CR2=0x00;//设置端口高四位推挽低速率输出,低四位为上拉输入 KeyPort_out=0x0F;//行线输出低电平,列线在上拉作用下为高电平 if(KeyPort_in!=0x0F)//检测有按键按下 { switch(KeyPort_in) { case 0x0E:val=0; break;//0000 1110 第0列被按下 case 0x0D:val=1; break;//以此类推,第1列按下 case 0x0B:val=2; break; case 0x07:val=3; break; default:val=0xFF;break;//非正常单列按下的情况 } } PC_DDR=0x0F;//配置线反转:高四位上拉输入,第四位推挽输出 PC_CR1=0xFF; PC_CR2=0x00; KeyPort_out=0xF0; if(KeyPort_in!=0xF0) { switch(KeyPort_in) { case 0xE0:val+=0; break; case 0xD0:val+=4; break; case 0xB0:val+=8; break; case 0x70:val+=12; break; default:val=0xFF;break; } } if(val != 0xFF) { *keybefore=val; } else { val=*keybefore; } return val; }
本文来自博客园,作者:无术师,转载请注明原文链接:https://www.cnblogs.com/untit1ed/p/18567087
本文使用知识共享4.0协议许可 CC BY-NC-SA 4.0
请注意: 特别说明版权归属的文章以及不归属于本人的转载内容(如引用的文章与图片)除外