最近个大网盘都发出通知,用户的数据将会被清楚,其中就有金山快盘,上大学的时候用的金山,很多重要的资料在其中都保存了一份。现在将其全部下载了下来,然后发现了我的毕业论文,看了一遍,就有了这系类文章。今天将矩阵键盘的设计和编程。
背景
我的毕业设计是基于ZigBee网络的一个hub系统,协调器节点作为中枢,控制器和被控制器都连接在这个中枢上。控制器上使用矩阵键盘那就很平常了。
CC2530的端口数比较小,这也是单片机开发中遇到的常见问题,I/O资源有限,那么我们这边的矩阵键盘也是,在这里我们为了减少I/O端口的使用,我们使用了移位寄存器来减少端口的使用。我们使用的是74HC164。下面对其进行简单的介绍。
74HC164移位寄存器
74HC164是一种由边沿触发的8位移位寄存器器件,其以串行方式一位一位的接受数据输入,然后以并行方式输出一个8位的结果数据。通过两个数据输入端(DSA或DSB)中的一个进行数据的串行输入或者将两个输入端连在一起共同输入。
每次时钟信号(CP)由低变高时,即发生边沿触发,寄存器中的数据都要右移一个比特,这时Q0的电位即为此次的数据输入的结果,是0还是1,也就是数据输入端DSA和DSB的逻辑与。
当主复位(MR)接低电平是,将会使其他端口失效,重置寄存器,输出端口置低电平。
引脚为下图所示:
各引脚说明:
键盘模块设计
矩阵键盘是一种通用的,在工控行业应用广,使用成熟的布局类似于矩阵的按键组。
当实际应用需要很多按钮时,为了尽量减少占用的I/O端口数,通常需要使用一个矩阵键盘。矩阵键盘中每个行线和列线在相交处不直接连通,而是用一个按键作为连接两者的点。这样,如果一个需要包含16个按键的键盘,排成一个4x4的矩阵,就只需要8个端口,比一个I/O连接一个按键减了少一倍的端口数使用,这对于I/O较紧张的系统来说是很重要的,而且当线数越多时,两者之间的区别就越突出,比如再多加一条行线就可以多包含4个按键,而直接使用端口线则只能构成9个按键的键盘。由此可见,在需要的键数较多的情况下,采用矩阵法是在合适不过的。
相比较而言,矩阵式结构肯定比直接相连结构在设计布局上要复杂的多,对于按键按下操作的识别也要复杂。在对矩阵式键盘处理按下事件时一般采用行列扫描法和反转法。本设计使用的是反转法。
一般的反转法的接法是行线和列线接I/O口,其中一列为低,一列为高。比如一个器件有两排端口分别为P0和P1,现在P0接行线,P1接列线,如果P0初始状态为高,那么P1则为低。当所有的按键都是处于未按下的状态,则P0继续保持高电平的状态,P1也保持的原来低电平的状态。如果其中有一个按键被按下,因为按键连接着行列线,P0为高电平状态,P1为低电平状态,所以P0中会有一个I/O端口的电位会被拉低,此时我们就可以知道按下的按键位于哪一行。在检测到P0口有I/O端口电位被拉低后,记录下此时P0口的状态,在C51编程中,一般使用一个8bit数保存。记录下P0状态后,立刻有规律的改变P1的各位的电位,改变的规则为从一个方向开始,依次拉高一个I/O的电位,其他位保持低电平,检查P0的状态是否恢复到原来的高电平,如果为高电平则记录下P1口的状态,此时记录下的P0口和P1口的状态的两个数据,就可以唯一的代表一个按键。
在本课题中,由于CC2530的I/O端口数量少,为了尽量地减少I/O端口的使用,以便以后能够对系统进行拓展,我们使用了8位串入、并出移位寄存器74HC164来减少I/O的使用。我们的主键盘设计为2x8的矩阵,另外还有两个独立的按键(左右按键,现阶段只使用了左按键),一共18个按键,使用中断来通知CPU发生了按键事件,其中主键盘共用一个中断,两个独立的按键各自使用一个中断,不使用轮询,这样有效的提高了按键的稳定性,在复杂系统中提高了按键的响应度。本来主键盘的列线需要8位I/O口,但是现在我们使用74HC164,则列线只需要使用一个I/O口,行线数量不变,现在就只需要3个I/O口就可以完成,这大大降低了I/O的使用数量,为以后扩展提供了I/O空间。
键盘布局如下:
因为使用了中断,所以当按钮按下时,两行按键中会有一行电平被拉低,使用一个与门来得出按键按下的信号,也就是当有按键按下时,与门输入为低电平,触发中断。
键盘驱动程序设计
键盘的第一行与P1_4(P1端口的第5个引脚)相连,第二行与P1_5(P1端口的第6个引脚)相连,单独的左按键与P1_7(P1端口的第8个引脚)相连,74HC164的数据输入端与P1_2(P1端口的第2个引脚)相连,74HC164的时钟线与P1_3(P1端口的第4个引脚)相连,与门的输出端,也就是中断信号线与P1_6相连。
在键盘驱动初始化过程中需要对使用到的端口进行对应的设置。行线所接的端口和中断信号接口都是输入口,74HC164的数据和时钟都是输出口,中断的端口还需要进行相应的功能设置。
P1DIR为P1口的方向寄存器,可位寻址,当某一位设置为0时,此I/O口用于输入,为1时,则用于输出。所以将P1DIR的4~7设置为0,2和3设置为1。P1IEN为P1口的中断使能寄存器,为1开中断,为0关中断,6和7分别用于主键盘和左键盘中断。
D0~D3位被用来对端口的中断触发方式进行设置,取值为0时设置上升沿触发,取值为1时设置下降沿触发。P1_6和P1_7使用下降沿触发。P1SEL为P1功能选择寄存器,0代表普通的I/O口,1代表功能口,中断为功能口,所以P1_6和P1_7为功能口。因为P1使用到中断,所以需要P1口中断使用。P1IFG为端口1的终端状态标志寄存器,当端口的某个I/O口有中断请求时,P1IFG寄存器中相应的位将被置1。例如如果P1_6有中断,那么P1IFG的第六位就会置1,在初始化时,可以将它清空。在P1口的中断函数中,需要先判断P1IFG的第六位和第七位是否为1,如果第六位为1,那就是主键盘有按键按下,否则为左按键。
在程序中,使用了两个变量,一个用来标示是否有键按下,另一个用来记录了按下的按键值。当有按键按下时,中断函数起作用,会改变着两个值,那么当操作系统查询按键标识时,发现它标示有按键发生,然后查询按键值,这样就可以根据按键值进行相关的功能设计。那怎样得出是那个按键按下呢?我们根据硬件和设计的算法得出了一个按键的编码表。
主键盘算法描述如下:
1.获得行线当前的状态Th;
2.将{0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}这八个数据依次发送给74HC164直到行线恢复到原来的状态,记录下此时的发送的数据Tl;
3.将Th和Tl进行或操作,得出的即为此按钮的编码。
对于左按键,使用独立的处理函数,给它的编码值为0xff。当有按键按下时,我们根据算法计算出编码,然后使用查表法,就能够确定按下的是那个按键。
code
void send_key(uint8 key_value)
{
int i = 0;
for (;i < 8;++i)
{
CLCK = 0;
S_IN = ((key_value & 0x80) >> 7);
CLCK = 1;
key_value <<= 1;
}
delay_ms(1);
get_row();
}
void get_row()
{
//00XX0000
//0x30
uint8 t = P2;
uint8 t2 = P1_5;
uint8 t3 = P1_4;
row |= t2;
row <<= 1;
row |= t3;
row <<= 6;
}
void get_column()
{
int j = 0;
uint8 temp = 0x00;
get_row();
if (row != 0xc0) {
delay_ms(4);
get_row();
if (row != 0xc0) {
temp = row;
} else {
return;
}
} else {
return;
}
for (;j < scan_size;++j)
{
send_key(scan[j]);
if (temp != row)
{
is_click = 1;
row = 0xc0;
result_key = temp | (j + 1);
CLEARZERO();
break;
}
}
}
//供中断调用
void do_button()
{
get_column();
}
uint8 click()
{
uint8 re = is_click;
if (is_click == 1)
{
is_click = 0;
}
return re;
}
uint8 key()
{
return result_key;
}
char parse_key(uint8 key_value)
{
char re;
int i = 0;
for (;i<16;i++)
{
if (button_code[i] == key_value)
{
if (i < 10)
{
re = '0' + i;
}
else
{
switch(i)
{
case 10:re = '=';break;
case 11:re = '+';break;
case 12:re = '-';break;
case 13:re = '*';break;
case 14:re = '/';break;
case 15:re = 'C';break;
}
}
break;
}
}
return re;
}
void button_init()
{
//高电平为输出方式
//000011xx
P1DIR &= 0x0f;
P1DIR |= 0x0c;
P1IEN |= 0xc0;//7 6中断是能
PICTL |= 0x04;//P1下降沿触发
P1INP &= 0x0f;//上拉
P1SEL |= 0xc0;//6 7功能口
P1SEL &= 0xc0;//0-5 I/O口
IEN2 |= 0x10;
P1IFG = 0;
CLEARZERO();
}
void do_left_soft_button()
{
is_click = 1;
result_key = 0xff;
}
#pragma vector = P1INT_VECTOR //外部中断函数
__interrupt void P1_INT(void){
IEN2 &= 0xef;;
if (P1IFG > 0){
if (P1IFG & 0x40) {
do_button();
} else {
//左软键
do_left_soft_button();
}
}
IEN2 |= 0x10;
P1IFG = 0;
}
其中有很多CC2530初始化中断和端口的代码。主要是send_key
函数,使用在移位过程中,器件特性导致了一定的延时,当时调试的时候,反转的时候一直没用,然后想到74HC164可能在输入和并出有一点的间隔,然后加了延时就OK了。在读行的时候也加了延时,而且读两遍,不然可以就读不出来。