前言
本文是在学习普中TMS320F28335开发板矩阵键盘的基础上总结得到的。
本文首先利用延时消抖的方法,给出矩阵键盘3种工作方式的代码,即按下工作,按下再抬起工作,按下连续工作。之后根据GPIO的特性,讨论是否能利用GPIO的输入量化实现按键消抖。
1. 延时消抖
1.1 硬件电路
开发板上矩阵键盘的硬件连接如图1所示。
1.2 3种工作方式的代码
对于一个矩阵键盘而言,利用行列扫描法来判断键值是最容易的。本次所给的代码中令GPIO12、GPIO13、GPIO14依次作为输出口,输出低电平;GPIO48、GPIO49、GPIO50作为输入口。
对于一个按键而言,其按下与抬起的瞬间都需要消抖,抖动的时间大约为5~10ms,本次所给的代码中以延时10ms来消抖。
一般来说按键按下后实现的效果主要有3种:
- 按下按键后立即返回键值,执行相应的功能。例如大多数开关的开启。
- 按下按键并抬起后返回键值,执行相应的功能。例如大多数开关的关闭。
- 按下按键后持续返回键值,执行相应的功能。例如遥控器、汽车的喇叭等。
实现代码如下(注意:以下代码并没有给出开发板上LED的初始化程序):
Key.c文件
/*
* Key.c
*/
#include "DSP2833x_Device.h"
#include "DSP2833x_Examples.h"
#include "Key.h"
void Key_Init()
{
EALLOW;
// TZ1
GpioCtrlRegs.GPAMUX1.bit.GPIO12 = 0;
GpioCtrlRegs.GPAPUD.bit.GPIO12 = 0;
GpioDataRegs.GPADAT.bit.GPIO12 = 1;
GpioCtrlRegs.GPADIR.bit.GPIO12 = 1;
// TZ2
GpioCtrlRegs.GPAMUX1.bit.GPIO13 = 0;
GpioCtrlRegs.GPAPUD.bit.GPIO13 = 0;
GpioDataRegs.GPADAT.bit.GPIO13 = 1;
GpioCtrlRegs.GPADIR.bit.GPIO13 = 1;
// TZ3
GpioCtrlRegs.GPAMUX1.bit.GPIO14 = 0;
GpioCtrlRegs.GPAPUD.bit.GPIO14 = 0;
GpioDataRegs.GPADAT.bit.GPIO14 = 1;
GpioCtrlRegs.GPADIR.bit.GPIO14 = 1;
// ECAP5
GpioCtrlRegs.GPBMUX2.bit.GPIO48 = 0;
GpioCtrlRegs.GPBDIR.bit.GPIO48 = 0;
GpioCtrlRegs.GPBPUD.bit.GPIO48 = 0;
// ECAP6
GpioCtrlRegs.GPBMUX2.bit.GPIO49 = 0;
GpioCtrlRegs.GPBDIR.bit.GPIO49 = 0;
GpioCtrlRegs.GPBPUD.bit.GPIO49 = 0;
// EQEP1A
GpioCtrlRegs.GPBMUX2.bit.GPIO50 = 0;
GpioCtrlRegs.GPBDIR.bit.GPIO50 = 0;
GpioCtrlRegs.GPBPUD.bit.GPIO50 = 0;
EDIS;
}
/*
* mode=0表示按下按键后立即返回键值
* mode=1表示按下按键并抬起后返回键值
* mode=2表示按下按键后持续返回键值
*/
char Key_Scan(char mode)
{
static char FLAG1=1; //第一行按键可以有效按下的标志位,按下变为0,不按为1
static char FLAG2=1; //第二行按键可以有效按下的标志位,按下变为0,不按为1
static char FLAG3=1; //第三行按键可以有效按下的标志位,按下变为0,不按为1
static char Key_VALUE; //mode=1时使用,用于存储键值
char tempt; //tempt作为消抖的中间变量
//第一行扫描
Key_H1_SetL;Key_H2_SetH;Key_H3_SetH;
//mode=0时的代码
if((mode==0) && (FLAG1==1) && (Key_L1==0 || Key_L2 ==0 || Key_L3 ==0))
{
DELAY_US(10000);
FLAG1=0; //防止重复返回键值
if(Key_L1==0) return Key1_PRESS;
if(Key_L2==0) return Key2_PRESS;
if(Key_L3==0) return Key3_PRESS;
}
else if((mode==0) && (Key_L1==1 && Key_L2 ==1 && Key_L3 ==1))
{FLAG1=1;}
//mode=1时的代码
if((mode==1) && (FLAG1==1) && (Key_L1==0 || Key_L2 ==0 || Key_L3 ==0))
{
DELAY_US(10000);
FLAG1=0;
if(Key_L1==0) Key_VALUE = Key1_PRESS;
if(Key_L2==0) Key_VALUE = Key2_PRESS;
if(Key_L3==0) Key_VALUE = Key3_PRESS;
}
else if((mode==1) && (FLAG1==0) && (Key_L1==1 && Key_L2 ==1 && Key_L3 ==1))
{
FLAG1=1;
tempt = Key_VALUE;
Key_VALUE = 0;
return tempt;
}
//mode=2时的代码
if((mode==2) && (Key_L1==0 || Key_L2 ==0 || Key_L3 ==0))
{
if(Key_L1==0) return Key1_PRESS;
if(Key_L2==0) return Key2_PRESS;
if(Key_L3==0) return Key3_PRESS;
}
//第二行扫描
Key_H1_SetH;Key_H2_SetL;Key_H3_SetH;
//mode=0时的代码
if((mode==0) && (FLAG2==1) && (Key_L1==0 || Key_L2 ==0 || Key_L3 ==0))
{
DELAY_US(10000);
FLAG2=0; //防止重复返回键值
if(Key_L1==0) return Key4_PRESS;
if(Key_L2==0) return Key5_PRESS;
if(Key_L3==0) return Key6_PRESS;
}
else if((mode==0) && (Key_L1==1 && Key_L2 ==1 && Key_L3 ==1))
{FLAG2=1;}
//mode=1时的代码
if((mode==1) && (FLAG2==1) && (Key_L1==0 || Key_L2 ==0 || Key_L3 ==0))
{
DELAY_US(10000);
FLAG2=0;
if(Key_L1==0) Key_VALUE = Key4_PRESS;
if(Key_L2==0) Key_VALUE = Key5_PRESS;
if(Key_L3==0) Key_VALUE = Key6_PRESS;
}
else if((mode==1) && (FLAG2==0) && (Key_L1==1 && Key_L2 ==1 && Key_L3 ==1))
{
FLAG2=1;
tempt = Key_VALUE;
Key_VALUE = 0;
return tempt;
}
//mode=2时的代码
if((mode==2) && (Key_L1==0 || Key_L2 ==0 || Key_L3 ==0))
{
if(Key_L1==0) return Key4_PRESS;
if(Key_L2==0) return Key5_PRESS;
if(Key_L3==0) return Key6_PRESS;
}
//第三行扫描
Key_H1_SetH;Key_H2_SetH;Key_H3_SetL;
//mode=0时的代码
if((mode==0) && (FLAG3==1) && (Key_L1==0 || Key_L2 ==0 || Key_L3 ==0))
{
DELAY_US(10000);
FLAG3=0; //防止重复返回键值
if(Key_L1==0) return Key7_PRESS;
if(Key_L2==0) return Key8_PRESS;
if(Key_L3==0) return Key9_PRESS;
}
else if((mode==0) && (Key_L1==1 && Key_L2 ==1 && Key_L3 ==1))
{FLAG3=1;}
//mode=1时的代码
if((mode==1) && (FLAG3==1) && (Key_L1==0 || Key_L2 ==0 || Key_L3 ==0))
{
DELAY_US(10000);
FLAG3=0;
if(Key_L1==0) Key_VALUE = Key7_PRESS;
if(Key_L2==0) Key_VALUE = Key8_PRESS;
if(Key_L3==0) Key_VALUE = Key9_PRESS;
}
else if((mode==1) && (FLAG3==0) && (Key_L1==1 && Key_L2 ==1 && Key_L3 ==1))
{
FLAG3=1;
tempt = Key_VALUE;
Key_VALUE = 0;
return tempt;
}
//mode=2时的代码
if((mode==2) && (Key_L1==0 || Key_L2 ==0 || Key_L3 ==0))
{
if(Key_L1==0) return Key7_PRESS;
if(Key_L2==0) return Key8_PRESS;
if(Key_L3==0) return Key9_PRESS;
}
return Key_UNPRESS; //无按键按下时返回0
}
Key.h文件
/*
* Key.h
*/
#ifndef KEY_H_
#define KEY_H_
void Key_Init();
char Key_Scan(char mode);
#define Key_H1_SetL (GpioDataRegs.GPACLEAR.bit.GPIO12=1)
#define Key_H2_SetL (GpioDataRegs.GPACLEAR.bit.GPIO13=1)
#define Key_H3_SetL (GpioDataRegs.GPACLEAR.bit.GPIO14=1)
#define Key_H1_SetH (GpioDataRegs.GPASET.bit.GPIO12=1)
#define Key_H2_SetH (GpioDataRegs.GPASET.bit.GPIO13=1)
#define Key_H3_SetH (GpioDataRegs.GPASET.bit.GPIO14=1)
#define Key_L1 (GpioDataRegs.GPBDAT.bit.GPIO48)
#define Key_L2 (GpioDataRegs.GPBDAT.bit.GPIO49)
#define Key_L3 (GpioDataRegs.GPBDAT.bit.GPIO50)
#define Key1_PRESS 1
#define Key2_PRESS 2
#define Key3_PRESS 3
#define Key4_PRESS 4
#define Key5_PRESS 5
#define Key6_PRESS 6
#define Key7_PRESS 7
#define Key8_PRESS 8
#define Key9_PRESS 9
#define Key_UNPRESS 0
#endif /* KEY_H_ */
main.c文件
/*
* main.c
*/
#include "DSP2833x_Device.h"
#include "DSP2833x_Examples.h"
#include "Key.h"
#include "LED_Set.h"
void main()
{
char KeyNum;
InitSysCtrl();
LED_Init();
Key_Init();
while(1)
{
KeyNum = Key_Scan(1);
switch(KeyNum)
{
case 1:{LED1_TOGGLE;break;}
case 2:{LED2_TOGGLE;break;}
case 3:{LED3_TOGGLE;break;}
case 4:{LED4_TOGGLE;break;}
case 5:{LED5_TOGGLE;break;}
case 6:{LED6_TOGGLE;break;}
case 7:{LED7_TOGGLE;break;}
case 8:{LED1_TOGGLE;break;}
case 9:{LED2_TOGGLE;break;}
}
}
}
对于Key.c文件中的Key_Scan(char mode)函数有几点要注意:
- 当mode=0时,按键按下的消抖延时函数(DELAY_US(10000))也是按键抬起时的消抖延时函数,也就是说这个DELAY_US(10000)同时实现了按下消抖和抬起消抖,因此不需要考虑抬起消抖部分;
- 当mode=1时,按键按下的消抖仍然用延时函数,但是此时的延时函数不能再用于按键抬起消抖,因此用temp作为中间变量来实现消抖;
- 当mode=2时,此时不设置消抖处理,因为对于连续按的情况如果设置延时消抖可能会影响器件的正常工作。例如,对于无源蜂鸣器来说,当振荡源振荡频率越高时,其声音将会越大,而振荡频率高的实质是振荡周期短;如果设置了延时10ms,那么蜂鸣器接收到有效键值时将延迟10ms,此时振荡源的振荡周期将非常大,蜂鸣器的声音将非常低。
2. 利用GPIO输入量化实现按键消抖
GPIO的输入量化指:当GPIO作为通用输入输出口并配置为输入模式时,它是可以通过GPxCTRL(x=A,B)来设置采样周期,通过GPxQSEL1/2(x=A,B)来设置采样次数。当在采样时间(采样周期×采样次数)内GPIO端口发生电平的波动时,GPxDAT寄存器中的值仍然是采样之前的值,直至整个采样时间(采样周期×采样次数)内GPIO端口电平保持稳定,GPxDAT寄存器中的值才会发生改变。
可见,可以利用GPIO的输入量化来实现按键的消抖。
2.1 理论分析
查阅数据手册中GPxCTRL、GPxQSEL1/2(x=A,B)两个寄存器的说明,可以发现:最大采样周期为510×SYSCLKOUT=510×6.67ns=3.4us,假设采样周期为6(最大),则采样时间T为6×3.4us=20.4us。也就是说GPIO最大量化延时20.4us得到正确的电平,这远远小于10ms,故实现不了消抖!
其次,GPIO设置了输入量化后,其读取端口电平一定具有延时性,而程序的运行速度是很快的,因此这种延时性很可能使得程序连最基本的键值判断函数都进不去!
综上分析,利用GPIO的输入量化来实现按键消抖并不是一件值得推崇的工作!
2.1 代码分析
以矩阵键盘的第一行为例来说明输入量化实现按键消抖出现的问题。
代码如下(以mode=1为例):
void Key_Init()
{
EALLOW;
// TZ1
GpioCtrlRegs.GPAMUX1.bit.GPIO12 = 0;
GpioCtrlRegs.GPAPUD.bit.GPIO12 = 0;
GpioCtrlRegs.GPADAT.bit.GPIO12 = 1;
GpioCtrlRegs.GPADIR.bit.GPIO12 = 1;
// ECAP5
GpioCtrlRegs.GPBMUX2.bit.GPIO48 = 0;
GpioCtrlRegs.GPBDIR.bit.GPIO48 = 0;
GpioCtrlRegs.GPBPUD.bit.GPIO48 = 0;
// ECAP6
GpioCtrlRegs.GPBMUX2.bit.GPIO49 = 0;
GpioCtrlRegs.GPBDIR.bit.GPIO49 = 0;
GpioCtrlRegs.GPBPUD.bit.GPIO49 = 0;
// EQEP1A
GpioCtrlRegs.GPBMUX2.bit.GPIO50 = 0;
GpioCtrlRegs.GPBDIR.bit.GPIO50 = 0;
GpioCtrlRegs.GPBPUD.bit.GPIO50 = 0;
// 设置GPIO48、GPIO49和GPIO50的采样周期为最大510×SYSCLKOUT
GpioCtrlRegs.GPBCTRL.bit.QUALPRD2 = 0xFF;
// 设置GPIO48、GPIO49和GPIO50的采样次数为6
GpioCtrlRegs.GPBQSEL2.bit.GPIO48 = 2;
GpioCtrlRegs.GPBQSEL2.bit.GPIO49 = 2;
GpioCtrlRegs.GPBQSEL2.bit.GPIO50 = 2;
EDIS;
}
/*
* mode=0表示单次按下(按下即返回键值)
*/
char Key_Scan(char mode)
{
static char FLAG1=1; //第一行有按键按下的标志位,按下变为0,不按为1
//第一行扫描
Key_H1_SetL;Key_H2_SetH;Key_H3_SetH;
//mode=0时的代码
if((mode==0) && (FLAG1==1) && (Key_L1==0 || Key_L2 ==0 || Key_L3 ==0))
{
FLAG1=0; //防止重复返回键值
if(Key_L1==0) return Key1_PRESS;
if(Key_L2==0) return Key2_PRESS;
if(Key_L3==0) return Key3_PRESS;
}
else if((mode==0) && (Key_L1==1 && Key_L2 ==1 && Key_L3 ==1))
{FLAG1=1;}
return Key_UNPRESS; //无按键按下时返回0
}
实际的情况是:无论按第一行的哪一个按键,LED都不会亮,这是为什么呢?
由于设置了输入量化,所以GPIO48、GPIO49和GPIO50在获取端口电平时都具有20.4us的延时,假设在“ Key_H1_SetL;Key_H2_SetH;Key_H3_SetH;”语句之前按键按下,当执行完“ Key_H1_SetL;Key_H2_SetH;Key_H3_SetH;”语句之后执行“if((mode == 0) && (FLAG1 == 1) && (Key_L1 == 0 || Key_L2 == 0 || Key_L3 == 0))”语句时,由于输入量化的延时,GPIO48、GPIO49和GPIO50的端口电平永远是1,这就使得键值返回部分的程序永远无法执行,这也就解释了为什么LED不会亮的原因。
解决上述问题有两种方法:
一是减小采样周期和采样次数以减小量化时间,从而能有效地判断“if((mode == 0) && (FLAG1 == 1) && (Key_L1 == 0 || Key_L2 == 0 || Key_L3 == 0))”语句。量化时间的缩小虽然可以使得键值返回部分的程序得到执行,但是并不会消除按键抖动带来的影响;
二是在“Key_H1_SetL;Key_H2_SetH;Key_H3_SetH;”语句之后设置一个延时,即:
……
Key_H1_SetL;Key_H2_SetH;Key_H3_SetH;
DELAY_US(10000);
//mode=0时的代码
if((mode==0) && (FLAG1==1) && (Key_L1==0 || Key_L2 ==0 || Key_L3 ==0))
……
以保证输入量化地延时被过滤掉。这种方法的实质就是延时消抖。
综上,利用GPIO的输入量化来实现消抖是不可取的!