A VR学习笔记十九、4X4矩阵键盘实验19.1 实例功能在前面的实例中我们已经学习了在单片机系统中检测独立式按键的接口电路和程序设计,独立式按键的每个按键占用1位I/O口线,其状态是独立的,相互之间没有影响,只要单独测试链接案件的I/O口线电平的高低就能判断键的状态。
独立式按键电路简单、配置灵活,软件结构也相对简单。
此种接口方式适用于系统需要按键数目较少的场合。
在按键数量较多的情况下,如系统需要8个以上按键的键盘时,采用独立式接口方式就会占用太多的I/O口,这对于I/O口资源不太丰富的单片机系统来说显得相当浪费,那么当按键数目相对较多的时候,为了减少I/O口资源的占用,应该采取什么样的方式才能够既满足多按键识别,又减少I/O口的占用呢?当然我们可以采用端口扩展器件比如串并转换芯片实现单片机I/O口的扩展,但是这种方式既增加了电路的复杂性,又增加了系统的成本开销。
有没有比较经济实惠的方法呢?事实上,在实际引用中我们经常采用矩阵式键盘的方式来节约I/O口资源和系统成本。
在这个实验中,我们采用4X4矩阵键盘来实现使用8个I/O口识别16个按键的实验,本实例分为三个功能模块,分别描述如下:●单片机系统:利用A Tmega16单片机与矩阵键盘电路实现多按键识别。
●外围电路:4X4矩阵键盘电路、LED数码管显示电路。
●软件程序:编写软件,实现4X4矩阵键盘识别16个按键的程序。
通过本实例的学习,掌握以下内容:●4X4矩阵键盘的电路设计和程序实现。
19.2 器件和原理19.2.1 矩阵键盘的工作原理和扫描确认方式当键盘中按键数量较多时,为了减少对I/O口的占用,通常将按键排列成矩阵形式,也称为行列键盘,这是一种常见的连接方式。
矩阵式键盘接口见图1所示,它由行线和列线组成,按键位于行、列的交叉点上。
当键被按下时,其交点的行线和列线接通,相应的行线或列线上的电平发生变化,MCU通过检测行或列线上的电平变化可以确定哪个按键被按下。
图1为一个4 x 4的行列结构,可以构成16个键的键盘。
很明显,在按键数量多的场合,矩阵键盘与独立式按键键盘相比可以节省很多的I/O口线。
图1 4X4键盘扫描电路矩阵键盘不仅在连接上比单独式按键复杂,它的按键识别方法也比单独式按键复杂。
在矩阵键盘的软件接口程序中,常使用的按键识别方法有行扫描法和线反转法。
这两种方法的基本思路是采用循环查循的方法,反复查询按键的状态,因此会大量占用MCU的时间,所以较好的方式也是采用中断的方法来设计,尽量减少键盘查询过程对MCU的占用时间。
在本实例中只是简单演示矩阵键盘的按键识别技术,所以仍然采用查询方法。
19.2.2采用行扫描法对矩阵键盘进行判别的思路下面以图2为例,介绍采用行扫描法对矩阵键盘进行判别的思路。
图2中,PA0、PA1、PA2、PA3为4根列线,这4根列线通过电阻接正电源,即上拉(当然AVR单片机I/O口有内部上拉电阻,可以设置内部上拉电阻使能,从而不用连接4个外部上拉电阻),PA4、PA5、PA6、PA7为4根行线。
将行线所接的I/O口作为输出端,列线所接的I/O口作为输入端。
这样,当没有按键按下时,所有的输入端都是高电平,。
设置行线输出低电平,一旦有键按下,则输入线会被拉低,这样通过读取输入线的状态就可以得知是否有按键按下。
行扫描法按键识别的过程如下。
图2 ATmega16与4X4键盘的连接1)、判断键盘中是否有按键按下。
将全部行线PA4-PA7置低电平输出,然后读PA0-PA3四根输入列线的状态。
只要有低电平出现,则说明有键按下(实际编程时,还要考虑按键的消抖)。
如读到的都是高电平,则表示无键按下。
2)、判断闭合键所在位置。
在确认有键按下后,即可进入确定具体哪个键按下的过程。
其思路是:依次将4根行线分别置为低电平,即在某根行线置为低电平时,其余行线为高电平,在确定某根行线置为低电平后,再逐列检查各列线的电平状态,若某列为低电平,则该列线与置为低电平的行线交叉处的按键就是闭合的按键。
矩阵按键的识别仅仅是确认和定位了行和列的交叉点上的按键,接下来还要考虑键盘的编码,即对各个按键进行编号。
在软件中常通过计算的方法或查表的方法对按键进行具体的定义和编号。
在单片机嵌入式系统中,键盘扫描只是MCU的工作内容之一。
MCU除了要检测键盘和处理键盘操作之外,还要进行其他事物的处理,因此,MCU如何响应键盘的输入需要在实际系统程序设计时认真考虑。
通常,完成键盘扫描和处理的程序是系统程序中的一个专用子程序,MCU调用该键盘扫描子程序对键盘进行扫描和处理的方式有三种:程序控制扫描、定时扫描和中断扫描。
✓程序控制扫描方式。
在主控程序中的适当位置调用键盘扫描程序,对键盘进行读取和处理。
✓定时扫描方式。
在该方式中,要使用MCU的一个定时器,使其产生一个10ms 的定时中断,MCU响应定时中断,执行键盘扫描,当在连续两次中断中都读到相同的按键按下(间隔10ms作为消抖处理),MCU才执行相应的键处理程序。
✓中断方式。
使用中断方式时,键盘的硬件电路要做一定的改动,增加一个按键产生中断信号的输入线,当键盘有按键按下时,键盘硬件电路产生一个外部的中断信号,MCU响应外部中断,进行键盘处理。
在本实例中我们介绍基于程序控制扫描方式的键盘处理系统的设计方法。
19.3 电路和连接本实验主要有两部分电路模块组成:数码管显示电路,4X4键盘电路。
数码管显示电路电路在前面的实例中我们已经做过介绍,在此不再重复。
这里我们重点介绍一下4X4键盘电路。
4X4键盘电路如图3所示图3 4X4键盘电路18.4 程序设计1、程序功能在本实例中,我们利用数码管将4X4键盘中按下的按键的键码值显示出来。
2、函数说明本实例主要有数码管显示程序和4X4键盘识别程序,数码管显示程序我们前面例子中已经介绍过,本实例的程序中不再详细说明。
3、编程说明使用WINA VR开发环境,使用的是外部12M的晶振,所以需要将makefile文件中的时钟频率修改为12M。
另外在程序烧录到单片机的时候,熔丝位也要选择为外部12M晶振(注意是晶振,不是外部振荡器,一定不要选择错了,否则会导致单片机不能再烧写程序)。
4、程序代码关于数码管显示程序,在此不再列出,直接打包到程序文件夹中。
下面列出主程序以及4X4键盘识别程序。
/*************************************************** AVR使用范例4*4矩阵键盘检测******* MCU: ATmega16 ******* 作者:maweili ******* 编译器:usbisp ******* ******* 2009.04.03**************************************************/#include <avr/io.h> //io端口寄存器配置文件,必须包含#include <util/delay.h> //GCC中的延时函数头文件unsigned char Disp_Buff[16] = {0xaf,0xa0,0xc7,0xe6,0xe8,0x6e,0x6f,0xa2,0xef,0xee,0xeb,0x6d,0x0f,0xe5,0x4f,0x4b};//数码管字型码表显示:0,1,2,3,4,5,6,7,8,9,A,b,C,d,E,F unsigned char KeyNumber;//函数声明void Delayus(unsigned int lus); //us延时函数void Delayms(unsigned int lms); //ms延时函数unsigned char Read_Key(void); // 读取键值int main(void) //GCC中main文件必须为返回整形值的函数,没有参数{PORTB = 0X00; //PORTB口全部输出低电平,使数码管的段位全部为低电平,不亮DDRB = 0XFF; //配置端口PB全部为输出口PORTC &= ~(1 << PC6); //配置数码管0的位选通口为低电平,不导通数码管DDRC |= (1 << PC6); ///KeyNumber = 16; //开始没有按键按下,不显示while(1){Read_Key(); //读取键值PORTB = Disp_Buff[KeyNumber]; //键值送数码管显示PORTC |= (1 << PC6);// 数码管的位选通端口输出高电平,使数码管显示}}//us级别的延时函数void Delayus(unsigned int lus){while(lus--){_delay_loop_2(3); //_delay_loop_2(1)是延时4个时钟周期,参数为3则延时12//个时钟周期,本实验用12M晶体,则12个时钟周期为12/12=1us }}//ms级别的延时函数void Delayms(unsigned int lms){while(lms--){Delayus(1000); //延时1ms}}//4*4矩阵键盘扫描,PD高四位为行输出口,低四位为列输入口unsigned char Read_Key(void){unsigned char i,j;DDRD = 0xf0; //设置PD高四位为输出口,低四位为输入口PORTD = 0x00; // 初始运行输出全为0if((PIND & 0x0f) == 0x0f) return 0; // 判断有无按键动作,没有,返回0else{Delayms(20); //按键消抖if((PIND & 0x0f) == 0x0f) return 0; //再次判断是否有按键动作else{for(i = 4;i < 8;i++) //逐行输出0{PORTD = ~(1 << i) | 0x0f; //第i行输出0for(j = 0;j < 4;j++){if((PIND & (1 << j)) == 0) //逐列检测KeyNumber = (i - 4) * 4 + j; //计算键值}}return 0; //}}}。