本文主要是介绍矩阵键盘的按下、长按、松手检测【以51单片机为例】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
矩阵键盘的按下、长按、松手检测
- 51单片机矩阵键盘电路原理图
- 矩阵键盘的原理
- 处理扫描得到的数据
- 按键的结构体
- 初始化
- 处理过程(函数)
- 三个状态判别的基本条件
- 处理过程
- 键值功能函数的处理
- 三种状态的事件处理
- 扫描函数
- 主循环中的处理和执行、定时器中断的扫描处理
- 定时器处理
- 主循环处理
- 功能简介
- 总结
51单片机矩阵键盘电路原理图
上图为51单片机矩阵的连接图,为4*4的矩阵键盘,只需要8个引脚就可以控制16个按键。
矩阵键盘的原理
在这里简单说明一下吧,例如我的矩阵键盘是接在了P1口,内部有上拉电阻,所以在上电的情况下,默认输出的是高电平。
扫描第一行:把P17引脚拉低,P16、P15、P14三行全部拉高,四列也就是P13、P12、P11、P10四个引脚全部设置为高电平。
检测按下状态:当我们按下第一行某个按键,P17引脚会是低电平,对应的列引脚也会被拉低,我们回读P1引脚,如果发现扫描的值与回读的值不一样,就确定第一行某个按键被按下,回读的值低四位哪一位为0就确定是哪一列被按下。
以下为检测键盘的伪代码,正式代码我会放在最后。
unsigned char keyValue;
P1 = 0x7f;//P17引脚拉低,其他七个引脚全高
if((P1&0x7f) != 0x7f)
{ //回读P1的值发现与给定的不一样,有按键被按下keyValue = P1;
}
//得到keyValue的值
行数有四行,我们放到for循环中依次执行,同时给一个四个长度数组保存下来每一行的P1值
uint8_t key_row[4] = {0x7f, 0xbf, 0xdf, 0xef};
uint8_t keyValue = 0x00;
uint8_t i=0;
for(i=0;i<4;i++)
{P1 = key_row[i];if(P1 & key_row[i] != key_row[i]){keyValue = key_row[i];//得到键值break;}
}
return keyValue;//如果在函数里,可以直接加上返回退出扫描函数了
好了,51的矩阵键盘原理就先说到这里,重点是扫描到的数据如何进行处理,判别是长按和松手
处理扫描得到的数据
这里用到了一种方法,处理器扫描速度很快,在我们按下的一瞬间扫描函数不知道记录多少次相同的返回结果了,所以我们根据这个处理速度来判别我们的键盘是哪种状态,在我们的扫描函数放在中断里去获取键值,键值的处理和键值对应的事件我们全放在主循环中执行。
我们开发单片机程序最好将事件多的处理交给主循环。
按键的结构体
typedef enum
{Key_Short_Press,//短按Key_Lift,//抬起Key_Long_Press,//长按Key_Compound_Press,//复合按键Key_None,//none
}key_state_e;typedef struct keyBoard
{uint8_t key_value[KEY_VALUE_MAX];//键值uint8_t key_Number;//按键数量bool key_Action_Flag;//执行标志void (*key_func)(struct keyBoard *);//执行函数uint16_t key_Last_value;//上次键值key_state_e key_status;//当前状态uint16_t Key_Long_Count;//长按扫描计数bool key_long_press_action_flag;//长按操作的执行标志uint16_t key_long_scan_Count;//长按
}Key_Info_t;
初始化
void ScanKeyInit(Key_Info_t *Key)
{Key->key_Number = 0;Key->key_Action_Flag = false;Key->key_func = keyFuncProcess;//为键值事件的处理函数Key->key_Last_value = 0x00;Key->key_value[0] = 0x00;Key->key_value[1] = 0x00;Key->key_status = Key_None;Key->Key_Long_Count = 0;Key->key_long_scan_Count = 0;
}
处理过程(函数)
先创建一个按键的结构体Key_Info_t Key_Board;
三个状态判别的基本条件
1.判断当前是不是按下状态
bool KeyIsDown(uint8_t keyvalue)
{if (keyvalue != 0x00){return true;}else{return false;}
}
2.判断当前是不是刚刚被按下
bool KeyIsClick(uint8_t keyvalue)
{if (KeyIsDown(keyvalue)){if (Key_Board.key_value[0] == 0X00) /*不能判断last值,必须判断收录的值*/return true;elsereturn false;}elsereturn false;
}
3.判断当前是不是刚刚被抬起
bool KeyIsUp(uint8_t keyvalue)
{if (KeyIsDown(keyvalue))return false;else{if (Key_Board.key_Last_value != 0x00)return true;elsereturn false;}
}
处理过程
void KeyValueProcess(uint8_t key, Key_Info_t *keyboard)
{if (key != 0x00){if (KeyIsClick(key)){/* 刚按下的状态 */keyboard->key_value[keyboard->key_Number++] = key;keyboard->key_Last_value = 0x00;keyboard->key_Action_Flag = true;keyboard->key_status = Key_Short_Press;keyboard->Key_Long_Count = systicks;}else if (KeyIsDown(key) && keyboard->key_value[0] == key && keyboard->key_Number == 1){/* 处理长按 */if (systicks - keyboard->Key_Long_Count > 1000) /* 2s */{keyboard->key_Last_value = keyboard->key_value[0];keyboard->key_status = Key_Long_Press;keyboard->key_Action_Flag = true;}else{keyboard->key_Last_value = keyboard->key_value[0];keyboard->key_Action_Flag = false;}}else if (KeyIsDown(key) && keyboard->key_value[0] != key && keyboard->key_Number == 1){/* 复合按键 */keyboard->key_Last_value = keyboard->key_value[0];keyboard->key_value[1] = key;keyboard->key_Action_Flag = true;keyboard->key_status = Key_Compound_Press;keyboard->key_Number = 2;}/* 按下状态 + 按键与之前两个按键某一个相同 + 按键记录数已经到2 */else if(KeyIsDown(key) && keyboard->key_Number == 2 && (key==keyboard->key_value[0] || key==keyboard->key_value[1])){if (keyComNumber>10) /* 长时间检测到只有一个按键,确保另一个按键已被抬起,先处理抬起的按键 */{if(key == keyboard->key_value[0]) /* 第一个按键没松手 */{keyboard->key_value[1] = 0x00;keyboard->key_Number = 1;keyboard->key_status = Key_Lift;keyboard->key_Action_Flag = true;}else if(key == keyboard->key_value[1]) /* 第二个按键没松手 */{keyboard->key_value[0] = 0x00;keyboard->key_Number = 1;keyboard->key_status = Key_Lift;keyboard->key_Action_Flag = true;}else{return;}}}}else if (KeyIsUp(key)){/* 按键抬起 */keyboard->key_Last_value = key;keyboard->key_status = Key_Lift;keyboard->key_value[keyboard->key_Number--] = 0x00;keyboard->key_Action_Flag = true;}else{/* none */keyboard->key_Action_Flag = false;keyboard->key_value[0] = 0x00;keyboard->key_value[1] = 0x00;keyboard->key_status = Key_None;keyboard->key_Last_value = 0x00;}
}
以上为处理获得键值的过程,唯一bug就是复合按键按下之后,必须两个按键都抬起才能全部释放,不然抬起一个去按另一个无法处理。这个bug留着以后我开发真要碰到的话再解决。
键值功能函数的处理
void keyFuncProcess(Key_Info_t *key)
{if (key->key_Action_Flag == false)return;if (key->key_status == Key_Short_Press){KeyShortAction(key);}else if (key->key_status == Key_Long_Press){if ((systicks - key->key_long_scan_Count) > 1000){key->key_long_press_action_flag = true;}if (key->key_long_press_action_flag == true) /* 长按不松手,每2s执行一次程序 */{KeyLongAction(key);key->key_long_press_action_flag = false;key->key_long_scan_Count = systicks;}}else if (key->key_status == Key_Lift){KeyLiftAction(key);}else if (key->key_status == Key_Compound_Press){KeyCompoundAction(key);}
}
三种状态的事件处理
1.检测到按下
void KeyShortAction(Key_Info_t *key)
{if (key->key_status != Key_Short_Press)return;switch (key->key_value[0]){case S1:sendString("S1 is down\r\n");break;case S2:sendString("S2 is down\r\n");break;case S3:sendString("S3 is down\r\n");break;case S4:sendString("S4 is down\r\n");break;case S5:sendString("S5 is down\r\n");break;case S6:sendString("S6 is down\r\n");break;case S7:sendString("S7 is down\r\n");break;case S8:sendString("S8 is down\r\n");break;case S9:sendString("S9 is down\r\n");break;case S10:sendString("S10 is down\r\n");break;case S11:sendString("S11 is down\r\n");break;case S12:sendString("S12 is down\r\n");break;case S13:sendString("S13 is down\r\n");break;case S14:sendString("S14 is down\r\n");break;case S15:sendString("S15 is down\r\n");break;case S16:sendString("S16 is down\r\n");break;}key->key_status = Key_None;
}
2.检测是长按
void KeyLongAction(Key_Info_t *key)
{if (key->key_status != Key_Long_Press)return;switch (key->key_value[0]){case S1:sendString("S1 is LongPress\r\n");break;case S2:sendString("S2 is LongPress\r\n");break;case S3:sendString("S3 is LongPress\r\n");break;case S4:sendString("S4 is LongPress\r\n");break;case S5:sendString("S5 is LongPress\r\n");break;case S6:sendString("S6 is LongPress\r\n");break;case S7:sendString("S7 is LongPress\r\n");break;case S8:sendString("S8 is LongPress\r\n");break;case S9:sendString("S9 is LongPress\r\n");break;case S10:sendString("S10 is LongPress\r\n");break;case S11:sendString("S11 is LongPress\r\n");break;case S12:sendString("S12 is LongPress\r\n");break;case S13:sendString("S13 is LongPress\r\n");break;case S14:sendString("S14 is LongPress\r\n");break;case S15:sendString("S15 is LongPress\r\n");break;case S16:sendString("S16 is LongPress\r\n");break;}key->key_status = Key_None;
}
3.检测按键被抬起
void KeyLiftAction(Key_Info_t *key)
{uint8_t keyvalue = 0x00;if (key->key_status != Key_Lift)return;if (key->key_value[1] == 0x00 && key->key_value[0] != 0x00)keyvalue = key->key_value[0];else if (key->key_value[1] != 0x00)keyvalue = key->key_value[1];switch (keyvalue){case S1:sendString("S1 is Lift\r\n");break;case S2:sendString("S2 is Lift\r\n");break;case S3:sendString("S3 is Lift\r\n");break;case S4:sendString("S4 is Lift\r\n");break;case S5:sendString("S5 is Lift\r\n");break;case S6:sendString("S6 is Lift\r\n");break;case S7:sendString("S7 is Lift\r\n");break;case S8:sendString("S8 is Lift\r\n");break;case S9:sendString("S9 is Lift\r\n");break;case S10:sendString("S10 is Lift\r\n");break;case S11:sendString("S11 is Lift\r\n");break;case S12:sendString("S12 is Lift\r\n");break;case S13:sendString("S13 is Lift\r\n");break;case S14:sendString("S14 is Lift\r\n");break;case S15:sendString("S15 is Lift\r\n");break;case S16:sendString("S16 is Lift\r\n");break;}key->key_status = Key_None;
}
4.复合按键(有需要的可以用)
void KeyCompoundAction(Key_Info_t *key)
{if (key->key_status != Key_Compound_Press)return;if ((key->key_value[0] == S1 && key->key_value[1] == S6) || (key->key_value[1] == S1 && key->key_value[0] == S6)){sendString("S1 And S2 Compound Press\r\n");}key->key_status = Key_None;
}
复合按键这里只处理了S1 和 S2 一种,毕竟16个按键不可能全部都有匹配的开发。
扫描函数
这里我把扫描函数分为两个正反轮询扫描,目的是检测多按键的情况,如果有需要的可以两个都使用,如果只是单按键,那两个中选一个就可以
uint8_t getKeyValue(void)
{uint8_t i = 0;uint8_t keyValue = 0x00;for (i = 0; i < 4; i++){P1 = key_row[i];if ((P1 & key_row[i]) != key_row[i]){keyValue = P1;goto k1;}}
k1:return keyValue;
}uint8_t getKeyValue2(void)
{char i = 0;uint8_t keyValue = 0x00;for (i = 3; i > -1; i--){P1 = key_row[i];if ((P1 & key_row[i]) != key_row[i]){keyValue = P1;goto k2;}}
k2:return keyValue;
}
主循环中的处理和执行、定时器中断的扫描处理
定时器我是用的TIMER0,时间为1ms执行一次,正反扫描如果你只需要一个按键检测和处理,去掉其中的if(!isLeftModeFlag)保留一个执行就可以
定时器处理
void interrupt_timer1(void) interrupt 1
{TH0 = (65536-1000)/256; // 中断后,赋初值;TL0 = (65536-1000)%256; if(!isLeftModeFlag){keyvalue = getKeyValue(); //正扫描}else{keyvalue = getKeyValue2(); //反扫描}if(keyvalue != 0x00)//上次有按键就切换扫描顺序isLeftModeFlag = ~isLeftModeFlag;if(lastKeyValue==keyvalue){keyComNumber++; //长按的}else{keyComNumber = 0;}sysCount++; // 每次中断,计数 累加 1;systicks++; //tick计数,1ms一次,用来判断长按的检测很不错if(sysCount>=1000){runAppLamp(); //运行灯sysCount = 0; }
}
主循环处理
ScanKeyInit(&Key_Board);while (1){KeyValueProcess(keyvalue,&Key_Board);Key_Board.key_func(&Key_Board);}
我没有加定时器和串口的初始化,这个各位使用的时候加上就可以。
功能简介
能够检测矩阵键盘的按下、长按、松手三种状态,同时添加了多按键,但是只能检测多按键按下,没有抬起和松手的处理。
长按2s执行长按事件,不松手的话每隔2s执行一次事件直到松手。
总结
可以看到除了主循环其他地方全是if进行判别的,可以避免某个地方出错卡死导致整个产品不能启动,这种方式还是挺不错。
在这里只是简单表明了我开发的一个逻辑思路,完整代码可以去我的git工程查看完整的51单片机矩阵键盘的项目。
zilboe/MatrixScanKey.git
这篇关于矩阵键盘的按下、长按、松手检测【以51单片机为例】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!