Cont = 0x01;
自己实验总结:在main() 的while(1)内调用KeyRead()后,即可判断Trg的状态,然后按其状态对应处理。如:
KeyRead();
if(Trg = 0x80)
{
// 处理程序
}
此时,Trg保持当前值,只有当while内后续操作完成后,重新循环回KeyRead()函数后,Trg会更新。
(3) PB0按着不松(长按键)的情况怎么判断按键释放?很简单,Trg 和Cont都为0 则肯定已经释放了。
这个是4*4矩阵,是从吧里转来的,感觉也是这种算法。顺便也贴到这里
把文本格式奉上 直接复制到你的程序里
volatile unsigned char CF[4]; //按键触发标志(表示4列,每一列同一行的
//值是一样的但列标不一样来区分不同列的键)
volatile unsigned char KeyVal;//键值
code unsigned char KeyOut[4] = {0xef,0xdf,0xbf,0x7f}; //4X4按输出端控制
#define KEY P2 //P2口作为4*4矩阵键盘输入
/*
**描述:新型4X4按键扫描程序 放在1ms-10ms中断内使用(十分稳定不需要再写消抖程序)
**备注:按键弹起时 keyVal = 0 单键按下 keyVal 有16个值,你自己程序可以针对不同值
**进行不同程序操作 keyVal单键值分别为
**0x11,0x12,0x14,0x18,
**0x21,0x22,0x24,0x28,
**0x31,0x32,0x34,0x38,
**0x41,0x42,0x44,0x48,
*/
void Key_Head()
{
unsigned char ReadData[4];
static unsigned char i;
if(++i>=4)i=0;
KEY = KeyOut[i]|0x0f; //忽略低4位
ReadData[i] = (KEY|0xf0)^0xff; //忽略高4位 取反
CF[i] = ReadData[i] & (ReadData[i] ^ Cont[i]);
Cont[i] = ReadData[i];
//输出键值
switch(CF[i])//第i列
{
case 0x08: KeyVal = (i<<4+8);break;
case 0x04: KeyVal = (i<<4+4);break;
case 0x02: KeyVal = (i<<4+2);break;
case 0x01: KeyVal = (i<<4+1);break;
default:KeyVal = 0;break;
}
}
原贴“给大家分享一个超级好用 不占CPU的4*4矩阵键盘扫描程序”:http://tieba.baidu.com/p/2730390494
自己使用总结:
上述程序实例使用需注意一下几点:
1. i定义为static型变量,每执行if(++i>=4)后i会自增;
2. Cont[]忘记定义:unsigned char Cont[4];
3. KeyVal值错误,因为if(++i>=4) i=0; 所以KeyVal单键值应为:
0x11,0x12,0x14,0x18,
0x21,0x22,0x24,0x28,
0x31,0x32,0x34,0x38,
0x01,0x02,0x04,0x08,
4. 在使用中端口要注意,程序中P2端口的高4位为输出端口,低4位为输入端口,且4个输入端口要默认上拉。
KeyOut[4] = {0xef,0xdf,0xbf,0x7f};的定义决定高四位为输出端口,低4位为输入端口。
扫描原理可参考:http://blog.csdn.net/phenixyf/article/details/76173409
KEY = KeyOut[i]|0x0f; //忽略低4位
这一步是在配置输出
ReadData[i] = (KEY|0xf0)^0xff; //忽略高4位 取反
这一步是在读取矩阵按键
这两句要根据具体使用自己修改,如使用STM8S003,将两个端口拼成一个8位扫描端口,程序如下:
volatile unsigned char Cont[4];
//unsigned char KeyOut[4] = {0xef,0xdf,0xbf,0x7f}; //4X4按输出端控制
unsigned char KeyOut[4] = {0x3f,0x5f,0x6f,0x77}; //两个端口组合组成4x4端口
unsigned char KeyVal; //键值
unsigned char cIn0,cIn1,cIn2,cIn3;
void Key_Head()
{
unsigned char ReadData[4];
static unsigned char i;
/*
PortCom BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0
PD6 PD5 PD4 PD3 PD2 PA3 PA2 PA1
*/
cIn0 = 0;
cIn1 = 0;
cIn2 = 0;
cIn3 = 0;
if(++i>=4)i=0;
// PortCom = KeyOut[i]|0x0f; //忽略低4位
//输出扫描
PD_ODR = KeyOut[i];
//输入侦测
cIn0 = PA_IDR_bit.IDR1;
cIn1 = PA_IDR_bit.IDR2;
cIn2 = PA_IDR_bit.IDR3;
cIn3 = PD_IDR_bit.IDR2;
PortCom = (cIn3<<3) | (cIn2<<2) | (cIn1<<1) | cIn0;
ReadData[i] = (PortCom|0xf0)^0xff; //忽略高4位 取反
CF[i] = ReadData[i] & (ReadData[i] ^ Cont[i]);
Cont[i] = ReadData[i];
//输出键值
switch(CF[i])//第i列
{
case 0x08: KeyVal = ((i<<4)+8);break;
case 0x04: KeyVal = ((i<<4)+4);break;
case 0x02: KeyVal = ((i<<4)+2);break;
case 0x01: KeyVal = ((i<<4)+1);break;
default:KeyVal = 0;break;
delay(50);
}
5. 特别注意一点:在原Key_Head()函数中,键值KeyVal只会保持按键侦测到当时一下,在下次扫描中马上会被清零(下次扫描i增加,CF[i]也随之变化,此时switch语句进入default分支,将KeyVal清零)。
这个是算法的精髓,当按键按下后,键值只出现一次,在Key_Head()后紧跟对应键值处理程序,完成对应操作,然后下次循环Key_Head()会自动将KeyVal清零,保证不会重复执行键值对应处理程序。
对于消抖,可以在KeyVal后加入适当延迟,如上程序最后一行
int main(void)
{
CLK_CKDIVR = 0x00; //内部时钟为1分频
ALL_LED_Init(); //调用LED1初始化函数
ALLKeyInit(); //调用按钮初始化函数
while(1)
{
Key_Head();
switch(KeyVal)
{
case 0x11:
{
PC_ODR ^= 0x80; //异或取反LED2使其亮灭
break;
}
case 0x12:
{
PC_ODR ^= 0x40; //异或取反LED3使其亮灭
break;
}
case 0x14:
{
PC_ODR ^= 0x08; //异或取反LED4使其亮灭
break;
}
case 0x18:
{
PC_ODR ^= 0x80; //异或取反LED2使其亮灭
break;
}
default:
{
break;
}
}
}
}
转自:http://tieba.baidu.com/p/2751359634
因可能会用不同端口的pin脚,拼接成扫描的端口,这时要注意以下两点:
1. 输出扫描的值KeyOut[4] = {0xbf,0xef,0xdf,0x7f}; 要做对应的修改;
2. 获取输入pin数据时,输入pin的端口拼接方法要根据使用具体情况来定。
简单举例:获取pin数据是按bit获取,获取后拼接输入端口
//输入侦测
cIn0 = PA_IDR_bit.IDR1;
cIn1 = PA_IDR_bit.IDR2;
cIn2 = PA_IDR_bit.IDR3;
cIn3 = PD_IDR_bit.IDR2;
PortCom = (cIn3<<3) | (cIn2<<2) | (cIn1<<1) | cIn0;
当获取pin数据是按byte获取,获取后拼接输入端口
cIn[0] = GPIOE->IDR & 0x20;
cIn[1] = GPIOC->IDR & 0x02;
cIn[2] = GPIOC->IDR & 0x04;
cIn[3] = GPIOC->IDR & 0x08;
PortCom = cIn[3] | cIn[2] | cIn[1] | (cIn[0]>>5);
在上述前提下,swith(CF[i])中的case值不用变,KeyValue值也是在
{0x11,0x12,0x14,0x18,
0x21,0x22,0x24,0x28,
0x31,0x32,0x34,0x38,
0x01,0x02,0x04,0x08,}范围内。
4X4 KeyScan() debug方法:
如下图所示设置断点:
进入第一个断点后,按下某个按键,向下执行,因要扫描4列,所以KeyScan函数要进出4次,其中必有一次可以扫到按键;
扫到按键后PortCom不为0x0F,CF[i]和kevalue也有对应值出现,如下图:
扫到某个键后,如果按住不放,等再次扫到时,CF和keyvalue会清零,但Cont会有值,表示长按
KeyScan()侦测同时按下的多个值的方法:
switch(CF[i])//第i列
{
case 0x08: KeyVal = ((i<<4)+8);break;
case 0x04: KeyVal = ((i<<4)+4);break;
case 0x02: KeyVal = ((i<<4)+2);break;
case 0x01: KeyVal = ((i<<4)+1);break;
default:KeyVal = 0;break;
delay(50);
}
在状态判断中,如上判断只是判断一个按键是否被按下,如0x08代表SW1被按下,0x12代表SW11被按下;
如果要判断多个按键是否同时被按下,可以更改case后的值,如0x09代表SW1和SW2同时被按下,0x0B代表SW1/SW2/SW3同时被按下;
但如果要判断不同输出下的两个或多个键同时被按下,需定义一个变量,让keyScan循环4次后,将所得值或起来判断
for(int i=0; i<4; i++)
{
keyScan();
KeyMul |= KeyVal;
}然后判断KeyMul值。
但这个会有误判,即不同按键组合可能会得到相同的KeyMul值。如SW1/SW10同时按下和SW3/SW5同时按下,得到的值都是0x2A。这需要另外区分判断。
另外,对应同一输入下的多个按键同时按下,如SW1/SW5,可能无法得到正确值,这需要根据硬件看上拉或下拉的能力哪个强,要量测实际值来判断。如SW1/SW5同时按下,当PC6输出0时,keyValue值是否为0xA7或0xE7或0xEF,要根据实际量测为准。
KeyScan()独立表示各按键状态,让按键状态与键值呈组合逻辑状态:
在车窗镜控制板项目中,各按键值要根据按键状态实时反应,所以要对KeyScan做修改,如上图按键组合:
bool KeyPos[20];
void KeyScan(void)
{
unsigned char ReadData[4];
static unsigned char i;
cIn[0] = 0;
cIn[1] = 0;
cIn[2] = 0;
cIn[3] = 0;
if(++i>=4)i=0;
T1 = KeyOut[i];
GPIOC->ODR = KeyOut[i];
cIn[0] = GPIOE->IDR & 0x20;
cIn[1] = GPIOC->IDR & 0x02;
cIn[2] = GPIOC->IDR & 0x04;
cIn[3] = GPIOC->IDR & 0x08;
PortCom = cIn[3] | cIn[2] | cIn[1] | (cIn[0]>>5);
ReadData[i] = (PortCom|0xf0)^0xff;
CF[i] = ReadData[i] & (ReadData[i] ^ Cont[i]);
Cont[i] = ReadData[i];
KeyPos[i*4] = (((CF[i] |Cont[i]) & 0x08)==0x08)?true:false;
KeyPos[i*4+1] = (((CF[i] |Cont[i]) & 0x04)==0x04)?true:false;
KeyPos[i*4+2] = (((CF[i] |Cont[i]) & 0x02)==0x02)?true:false;
KeyPos[i*4+3] = (((CF[i] |Cont[i]) & 0x01)==0x01)?true:false;
Delay(3);
}
KeyPos[4]对应SW7; KeyPos[5]对应SW16; KeyPos[6]对应SW11; KeyPos[7]对应SW20;
KeyPos[8]对应SW6; KeyPos[9]对应SW13; KeyPos[10]对应SW10; KeyPos[11]对应SW17;
KeyPos[12]对应SW22; KeyPos[13]对应SW23; KeyPos[14]对应SW21; KeyPos[15]对应SWX;
按此方式修改,即KeyPos[i]实时反应对应的按键是按下还是松开,不会因KeyScan循环下次后,键值发生改变。
如SW1被按下后,当KeyOut输出0xEF时,KeyPos[0]=1,当KeyOut分别输出0xBF,0xDF,0x7F时,KeyPos[0]也维持为1,不会如之前的KeyValue被清零或变成其它状态。
此方法就是将每个按键与一个键值状态,独立联系起来。之前是用一个状态KeyValue将16个按键统一表示起来。