经典按键扫描程序
按键扫描程序(4)

else if(key == D_key)
........//点亮B_LED,关闭A_LED和C_LED
else if(key == S_key)
key_time_1 = 0; // 第1次单击,不返回,到下个状态判断后面是否出现双击
key_m = key_state_1;
}
else
特别操作情况定义:
1。短按操作和长按操作间隔<0.5s,以及,长按操作和短按操作间隔<0.5s,均不产生双击事件
2。连续n次(n为奇数)短按操作,且间隔均<0.5s,产生(n-1)/2次双击事件+1次单击事件
3。连续n次(n为偶数)短按操作,且间隔均<0.5s,产生n/2次双击事件
题目:多功能按键设计。利用一个I/O口,接一个按键,实现3功能操作:单击 + 双击 + 长按。
============================================================================
用户基本操作定义:
1。短按操作:按键按下,按下时间<1s,属于一次短按操作
if (!key_press)
{
key_time = 0; //
key_state = key_state_2; // 按键仍然处于按下,消抖完成,状态转换到按下键时间的计时状态,但返回的还是无键事件
}
else
key_state = key_state_0; // 按键已抬起,转换到按键初始态。此处完成和实现软件消抖,其实按键的按下和释放都在此消抖的。
51单片机简易密码锁

51单片机简易密码锁学号:201114040215HEBEI UNITED UNIVERSITY单片机课程设计说明书设计题目:简易电子密码锁学生姓名:李红辉专业班级:测控技术及仪器2学院:电气工程学院指导教师:曹晓华2014年06月05日成绩评定表AbstractIn daily life and work, the department of housing and security, unit documents, financial statements and some personal information to save more in order to lock the ways to solve. If use the traditional mechanical key to open the lock, people often need to carry multiple keys, use very convenient, and the key missing after security is compromised. With the continuous development of science and technology, people in daily life the demand is higher and higher safety insurance device. To meet the requirements of people on the use of the lock, increase its safety, use the password instead of the key combination lock arises at the historic moment. Combination lock with high safety, low cost, low power consumption, easy operation, etc.In the field of security technology to guard against, with electronic combination lock anti-theft alarm function gradually replace the traditional mechanical combination lock, overcoming the mechanical combination lock password quantity is little, the shortcomings of poor safety performance, make the combination lock both in technology and step in performance are improved greatly. With the development of large scale integrated circuit technology, especially the single chip microcomputer, appeared with the intelligent combination of the microprocessor, it besides has the function of electronic combination lock, also introduced the intelligent management, expert analysis system, and other functions, so that the combination lock of high security, reliability, and increasingly widely used.The course design of electronic combination lock based on MCU is introduced, the design of the hardware is mainly composed of AT89C52 minimum system, matrix circuit, 1602 LCD display circuit, power circuit and alarm circuit and so on several parts. Software is mainly composed of C language programming. The combination lock by the single chip microcomputer technology, through the matrix circuit setting, change passwords, passwordprotection, and by the LCD display password input, so as to realize the password Settings, protection.Key words: single chip microcomputer, trick lock, the 1602, the smallest system, matrix keyboard目录摘要 ............................................................................................ 错误!未定义书签。
按键消抖实验

基于verilog按键消抖设计Aaron malone关于键盘的基础知识,我就以下面的一点资料带过,因为这个实在是再基础不过的东西了。
然后我引两篇我自己的博文,都是关于按键消抖的,代码也正是同目录下project里的。
这两篇博文都是ednchina的博客精华,并且在其blog 首页置顶多日,我想对大家会很有帮助的。
键盘的分类键盘分编码键盘和非编码键盘。
键盘上闭合键的识别由专用的硬件编码器实现,并产生键编码号或键值的称为编码键盘,如计算机键盘。
而靠软件编程来识别的称为非编码键盘。
在单片机组成的各种系统中,用的最多的是非编码键盘。
也有用到编码键盘的。
非编码键盘有分为:独立键盘和行列式(又称为矩阵式)键盘。
按键在闭合和断开时,触点会存在抖动现象:从上面的图形我们知道,在按键按下或者是释放的时候都会出现一个不稳定的抖动时间的,那么如果不处理好这个抖动时间,我们就无法处理好按键编码,所以如何才能有效的消除按键抖动呢?让下面的两篇博文日志给你答案吧。
经典的verilog键盘扫描程序从最基础的分频程序开始,但看到这个键盘扫描程序后,直呼经典,有相见恨晚的感觉,还想说一句:威百仕( VibesIC ),我很看好你!WHY?待我慢慢道来,这个程序的综合后是0error,0warning。
想想自己编码的时候那个warning是满天飞,现在才明白HDL设计有那么讲究了,代码所设计的不仅仅是简单的逻辑以及时序的关系,更重要的是你要在代码中不仅要表现出每一个寄存器,甚至每一个走线。
想想我写过的代码,只注意到了前者,从没有注意过后者,还洋洋自得以为自己也算是个高手了,现在想来,实在惭愧啊!学习学习在学习,这也重新激发了我对HDL设计的激情,威百仕给了我一个方向,那我可要开始努力喽!废话说了一大堆,看程序吧:(本代码经过ise7.1i综合并下载到SP306板上验证通过)//当三个独立按键的某一个被按下后,相应的LED被点亮;再次按下后,LED 熄灭,按键控制LED亮灭经过一次20ms的采样后判定为键盘按下。
键盘码表:单键扫描码,ascii,组合键码

键盘码表:单键扫描码,ascii,组合键码字母和空格按键的编码表按键单键SHIFT CTRL ALT扫描码ASCII码扫描码ASCII码扫描码ASCII码扫描码ASCII码a 1E 61 1E 41 1E 01 1E 00b 30 62 30 42 30 02 30 00c 2E 63 2E 43 2E 03 2E 00d 20 64 20 44 20 04 20 00e 12 65 12 45 12 05 12 00f 21 66 21 46 21 06 21 00g 22 67 22 47 22 07 22 00h 23 68 23 48 23 08 23 00i 17 69 17 49 17 09 17 00 j 24 6A 24 4A 24 0A 24 00 k 25 6B25 4B 25 0B 25 00 l 26 6C 26 4C 26 0C 26 00 m 32 6D 32 4D 32 0D 32 00 n 31 6E 31 4E 31 0E 31 00 o 18 6F 18 4F 18 0F 18 00 p 19 70 19 50 19 10 19 00 q 10 71 10 51 10 11 10 00 r 13 72 13 52 13 12 13 00 s 1F 73 1F 53 1F 13 1F 00 t 14 74 14 54 14 14 14 00 u 16 75 16 55 16 15 16 00 v 2F 76 2F 56 2F 16 2F 00 w 11 77 11 57 11 17 11 00 x 2D 78 2D 58 2D 18 2D 00 y 15 79 15 59 15 19 15 00 z 2C 7A 2C 5A 2C 1A 2C 00 SpaceBar 39 20 39 20 39 20 39 20功能键和数字键盘的编码表·内容正文按键单键SHIFT CTRL ALT扫描码ASCII码扫描码ASCII码扫描码ASCII码扫描码ASCII码F1 3B 00 54 00 5E 00 68 00 F2 3C 00 55 00 5F 00 69 00 F3 3D 00 56 00 60 00 6A 00 F4 3E 00 57 00 61 00 6B 00 F5 3F 00 58 00 62 00 6C 00 F6 40 00 59 00 63 00 6D 00 F7 41 00 5A 00 64 00 6E 00 F8 42 00 5B 00 65 00 6F 00 F9 43 00 5C 00 66 00 70 00 F10 44 00 5D 00 67 00 71 00 F11 85 00 87 00 89 00 8B 00 F12 86 00 8800 8A 00 8C 00 键盘码表:单键扫描码,ascii,组合键码数字键盘的编码表按键单键SHIFT CTRL ALT扫描码ASCII码扫描码ASCII码扫描码ASCII码扫描码ASCII码Ins&0 52 00 52 30 92 00 00 End&1 4F 00 4F 31 75 00 00 01 ↓&2 50 00 50 32 91 00 00 02 PgDn&3 51 00 51 33 76 00 00 03 ←&4 4B 00 4B 34 73 00 00 04 5 4C 00 4C 35 8F 00 00 05 →&6 4D 00 4D 36 74 00 00 06 Home&7 47 00 47 37 77 00 00 07 ↑&8 48 00 48 38 8D 00 00 08 PgUp&9 49 00 49 39 84 00 00 09 + 4E 2B 4E 2B 90 00 4E 00 - 4A 2D 4A 2D 8E 00 4A 00 Del&'.' 53 00 53 2E 93 00 00 * 37 2A 37 2A 96 00 37 00第一排数字键盘的编码表按键单键SHIFT CTRL ALT扫描码ASCII码扫描码ASCII码扫描码ASCII码扫描码ASCII码`@~ 29 60 29 7E 00 29 00 1@! 02 31 02 21 00 78 00 2@@ 03 32 03 40 03 00 79 00 3@# 04 33 04 23 00 7A 00 4@$ 05 34 05 24 00 7B 00 5@% 06 35 06 25 00 7C 00 6@^ 07 36 07 5E 07 1E 7D 00 7@& 08 37 08 26 00 7E 00 8@* 09 38 09 2A 00 7F 00 9@( 0A 39 0A 38 00 80 00 0@) 0B 30 0B 29 00 81 00 -@_0C 2D 0C 5F 0C 1F 82 00 =@+0D 3D 0D 2B 00 83 00操作、标号和附加按键等的编码表·内容正文按键单键SHIFT CTRL ALT扫描码ASCII码扫描码ASCII码扫描码ASCII码扫描码ASCII码Esc 01 1B 01 1B 01 1B 01 00 Backspace 0E 08 0E 08 0E 7F 0E 00 Tab 0F 09 0F 00 94 00 A5 00 Enter 1C 0D 1C 0D 1C 0A 1C 00标号按键的编码表按键单键SHIFT CTRL ALT扫描码ASCII码扫描码ASCII码扫描码ASCII码扫描码ASCII码[@{ 1A 5B 1A 7B 1A 1B 1A 00]@} 1B 5D 1B 7D 1B 1D 1B 00 ;@: 27 3B 27 3A 00 27 00 @"28 27 28 22 00 28 00 \@| 2b 5C 2b 7C 2B 1C 2b 00 , @ < 33 2C 33 3C 00 33 00 . @> 34 2E 34 3E 00 34 00 / @ ? 35 2F 35 3F 00 35 00、附加按键的编码表按键单键SHIFT CTRL ALT扫描码ASCII码扫描码ASCII码扫描码ASCII码扫描码ASCII码Flash(/) E0 2F E0 2F 95 00 A4 00 Enter E0 0D E0 0D E0 0A A6 00 Home 47 E0 47 E0 77 E0 97 00 End 4F E0 4F E0 75 E0 9F 00 PageUp 49 E0 49 E0 84 E0 99 00 PageDown 51 E0 51 E0 76 E0 A1 00 DnArrow 50 E0 50 E0 91 E0 A0 00 LeftArrow 4B E0 4B E0 73 E0 9B 00 RightArrow 4D E0 4D E0 74 E0 9D 00 UpArrow 48 E0 48 E0 8D E0 98 00 Ins 52 E0 52 E0 92 E0 A2 00 Del 53 E0 53 E0 93 E0 A3 00其它按键的扫描码当这些键被按下时,BIOS并没有把它们的扫描码输入键盘缓冲区。
键盘扫描三种方法

第一种------传统法uchar scanf(){P3=0xfe;temp=P3&0xf0;if(temp!=0xf0) //判断是否有键按下{delay(5);//给一个延时temp=P3&0xf0;if(temp!=0xf0) //再次判断是否有键按下{temp=P3;switch(temp){case 0xee:num=1;break;case 0xde:num=2;break;case 0xbe:num=3;break;case 0x7e:num=4;break;}}while(temp!=0xf0)//键起,推出程序{temp=P3&0xf0;}}P3=0xfd;temp=P3&0xf0;if(temp!=0xf0){delay(5);temp=P3&0xf0;if(temp!=0xf0){temp=P3;switch(temp){case 0xed:num=5;break;case 0xdd:num=6;break;case 0xbd:num=7;break;case 0x7d:num=8;break;}}while(temp!=0xf0){temp=P3&0xf0;}}P3=0xfb;temp=P3&0xf0;if(temp!=0xf0){delay(5);temp=P3&0xf0;if(temp!=0xf0){temp=P3;switch(temp){case 0xeb:num=9;break;case 0xdb:num=10;break;case 0xbb:num=11;break;case 0x7b:num=12;break;}}while(temp!=0xf0){temp=P3&0xf0;}}P3=0xf7;temp=P3&0xf0;if(temp!=0xf0){delay(5);temp=P3&0xf0;if(temp!=0xf0){temp=P3;switch(temp){case 0xe7:num=13;break;case 0xd7:num=14;break;case 0xb7:num=15;break;case 0x77:num=16;break;}}while(temp!=0xf0){temp=P3&0xf0;}}Return(num);}第二种——简单法uchar keyscan(void){uchar scancode,tmpcode;P3 = 0xf0; // 发全0行扫描码if ((P3&0xf0)!=0xf0) // 若有键按下{delay(5); // 延时去抖动if ((P3&0xf0)!=0xf0)// 延时后再判断一次,去除抖动影响{scancode = 0xfe;//相当于从第一行开始扫描1111 1110while((scancode&0x10)!=0) // 控制行我的理解while((P3&0xf0)!=0xf0)(原来的程序转了一个大弯)(原程序,首先进入,使其扫描全行,扫描一次退出;我的:按键复原后,才退出程序;我的程序要扫描多次,但前提是一定扫描得到{P3 = scancode; // 输出行扫描码其实P3变为了1101 1110(假设有键按下)其中因为有键按下,写入的1马上又变为0(只有当行和列都对应时,才会继续下面的运算)即:如果开始按的是1101 1011,那么P3显示的就是1111 1110列中就不会出现0if ((P3&0xf0)!=0xf0) // 本行有键按下(确定行){tmpcode = (P3&0xf0)|0x0f; //确定列return((~scancode)+(~tmpcode)); /* 返回特征字节码,为1的位即对应于行和列*/}elsescancode = (scancode<<1)|0x01; // 行扫描码左移一位,换另一行扫面}}}return(0); // 无键按下,返回值为0}第三种———先行扫描,再列扫描uchar keyscan(void){uchar tag1,tag2;tag1=0xff;tag2=0xff;P3 = 0xf0; // 发全0行扫描码if ((P3&0xf0)!=0xf0) // 若有键按下{delay(5); // 延时去抖动if ((P3&0xf0)!=0xf0) // 延时后再判断一次,去除抖动影响{tag1=P3;P3=0x0f;tag2=P3&0x0f;}}return(~(tag1|tag2)); }。
键盘扫描码

键盘上的每一个键都有两个唯一的数值进行标志。
为什么要用两个数值而不是一个数值呢?这是因为一个键可以被按下,也可以被释放。
当一个键按下时,它们产生一个唯一的数值,当一个键被释放时,它也会产生一个唯一的数值,我们把这些数值都保存在一张表里面,到时候通过查表就可以知道是哪一个键被敲击,并且可以知道是它是被按下还是被释放了。
这些数值在系统中被称为键盘扫描码2扫描码大全扫描码键0x011b ESC0x3b00 F10x3c00 F20x3d00 F30x3e00 F40x3f00 F50x4000 F60x4100 F70x4200 F80x4300 F90x4400 F10主键盘区:0x2960 ~0x0231 10x0332 20x0433 30x0534 40x0635 50x0736 60x0837 70x0938 80x0a39 90x0b30 00x0c2d -0x0d3d =0x2b5c \0x0e08 退格键0x0f09 Tab0x1071 q0x1177 w0x1265 e0x1372 r0x1474 t0x1579 y0x1769 i0x186f o0x1970 p0x1a5b [0x1b5d ]0x1e61 a0x1f73 s0x2064 d0x2166 f0x2267 g0x2368 h0x246a j0x256b k0x266c l0x273b ;0x2827 '0x1c0d 回车0x2c7a z0x2d78 x0x2e63 c0x2f76 v0x3062 b0x316e n0x326d m0x332c ,0x342e .0x352f /0x3920 空格键0xe05b 左Win0xe05c 右Win0xe05d Menu右边数字键盘:0x5200 Insert0x4700 Home0x4900 Page UP 0x5300 Delete0x4f00 End0x5100 PageDown 0x4800 上箭头0x4b00 左箭头0x5000 下箭头0x4d00 右箭头0x352f /0x4a2d - (注意,这是数字键盘的)0x4737 70x4838 80x4939 90x4b34 40x4c35 50x4d36 60x4e2b +0x4f31 10x5032 20x5133 30x5230 00x532e Del通过PC机键盘输入汉字时,其中经过多次的代码转换:用户---汉字输入码---键盘---键盘扫描码---BIOS键盘驱动程序----ASCII码----汉字输入软件----汉字内码。
经典按键程序

switch(Key)
{
case _KEY_F2:
if(Times < 200) //长按 2s
{
return _REENTER; //2s内允许长按
}
BYTE KeyDownCallBack2(WORD Key, WORD Times);
//按键处理数据结构
static struct_KeyInfo g_KeyInfo1 = {0, 0, 0, 0, KeyDownCallBack};
static struct_KeyInfo g_KeyInfo2 = {0, 0, 0, 0, KeyDownCallBack2};
#include "Key.h"
//定时消抖的按键处理函数, 通常在定时中断中调用,
void DitherlessKey(struct_KeyInfo* pInfo)
{
switch(pInfo->KeyState)
{
case _HAS_NO_KEY:
WORD CurKey; //当前检测到的键, 用于处理长按的情况
BYTE (*KeyDownCallBack)(WORD, WORD); //键确认按下的回调函数指针
void (*KeyUpCallBack)(WORD); //键抬起的回调函数指针
WORD PreDownKey; //上次检测到的键
BYTE KeyState; //状态
WORD SameKeyCntr; //同一键检测到按下的次数
g_KeyInfo2.CurKey = temp & 0xFF00; //同一个消抖函数处理不同的按键
经典的矩阵键盘扫描程序1

取键值以启动相应的功能程序。
/***************************************************File Name :LED Debug*Autor :HR*Version :V1.0*Data :*Descrption :none*********************************************************************************************************/#include "include.h"JOB Job_Data; ----------1SYS_TimeHandler SYS_Time; ---------2MSG Msg_Key; /* 按键任务使用的结构体消息*/ ---------3uint16 test_a; ---------4 uint16 TX_Buff_Byte[25]; ---------4uint8 TX_Buff_ASIC[25]; ---------4/************************************************主程序************************************************/int main(){BSP_Init(); ---------5SYSTEM_LogShow (); ---------6for(;;) {SYS_100US_handler(); ---------7SYS_10MS_handler(); ---------8SYS_100MS_handler(); ---------9SYS_200MS_handler(); ---------10 }}JOB Job_Data;typedef struct WELD /* 任务用的焊接数据包(结构体数据类型) */{// 临时标记uint8 FgWeldRunEn; // 焊接输出标记uint8 FgGasRunEn; // 焊接气体输出标记uint8 FgPowerRunEn; // 系统工作标记uint8 FgMotorRunEn; // 送丝机系统工作标记uint8 FgLcmFlickerEn; // 显示闪烁工作标记uint8 FgRoughMachiningEn; // 粗调节有效uint8 FgCURRENTIndicateLedEn; // 输出正常电流指示LEDuint8 FgMotorCheckEn; // 送丝机系统检测标记uint8 FgEepromSaveEn; // 数据记忆标记uint8 FgLcmHoldEn; // 冻结显示内容uint8 FgVRDReset; // VRD复位辅助标记uint8 LockState; // 4T焊接辅助指令uint8 Fg_RXFinish; // 串口接收数据完成标记uint8 Fg_ShowLog; // 开机LOG辅助标记uint8 Fg_UsartConnect; // USART连接标记uint8 FgACV oltageState; // 网压状态标记uint8 FgSystemFault; // 系统故障标记uint8 FgFactoryTest; // 工厂调试标记uint8 FgDisChannel; // 公英制切换uint8 RunV olatgeTest; // 关机检测状态// 临时数据处理uint16 FaultOrderSet; /* 设备故障指令数据缓存*/uint16 SystemOrderSet; /* 传递系统指令数据缓存*/uint16 LcmOrderSet; /* 传递LCM显示指令数据缓存*/uint16 DriveOrderSet; /* 传递驱动组件指令数据缓存*/uint16 WorkOrderSet; /* 焊接工作指令数据缓存*/uint16 MainMenu; /* 主目录数据缓存*/uint16 CheckOrderSet;uint16 LcmV oltageData; /* 焊接显示电压数据缓存*/uint16 LcmCurrentData; /* 焊接显示电流数据缓存*/uint16 LcmV oltageDataDis; /* 焊接显示电压数据缓存*/uint16 LcmCurrentDataDis; /* 焊接显示电流数据缓存*/uint16 LcmMotorData; /* 焊接显示电机数据缓存*/uint16 LcmTempData; /* 焊接显示温度数据缓存*/uint16 LcmACSupplyState; /* 网压显示数据缓存*/uint16 LCMDataBuff_1; /* LCM1显示数据缓存*/uint16 LCMDataBuff_2; /* LCM2显示数据缓存*/uint16 SystemV oltageData; /* 焊接实际电压数据缓存*/uint16 SystemCurrentData; /* 焊接实际电流数据缓存*/uint16 SystemCurrentBuff;// 设备数据处理(需要记忆) //uint16 WeldWorkSetMode; // 焊接类型选择:MMA,TIG,MIG uint16 WeldRunSetVRD; // VRD setuint16 WeldRunSetMode; // 焊接方式选择:2T,4T// MMA //uint16 MMASetCurrent; // MMA手工焊电流设置// TIG //uint16 TIGSetCurrent; // TIG焊接电流// MIG //uint16 MIGSetV oltage; // MIG焊接电压uint16 MIGSetMotorSpeed; // MIG送丝速度uint16 MIGSetInductance; // MIG电子电抗uint16 MIGSetV oltageFine; // MIG焊接电压微调uint8 MIGSetV oltageCorrect; // 系统给定焊接电压修正uint8 MIGDisV oltageCorrect; // 系统显示焊接电压修正uint8 MIGSetCurrentCorrect; // 系统给定焊接电流修正uint8 MIGDisCurrentCorrect; // 系统显示焊接电流修正//外置MIG //uint16 Ext_MIGSetVoltage; // MIG焊接电压// 已更改uint16 Ext_MIGSetMotorSpeed; // MIG送丝速度// 已更改uint16 Ext_MIGSetInductance; // MIG电子电抗// 已更改uint16 Ext_MIGSetVoltageFine; // MIG焊接电压微调// 已更改uint8 Ext_MIGSetV oltageCorrect; // 系统给定焊接电压修正// 已更改uint8 Ext_MIGDisVoltageCorrect; // 系统显示焊接电压修正// 已更改uint8 Ext_MIGSetCurrentCorrect; // 系统给定焊接电流修正// 已更改uint8 Ext_MIGDisCurrentCorrect; // 系统显示焊接电流修正// 已更改uint8 CheckW_VRD_RunI_2T4T;int16 V_Set_Value_08;} JOB;{unsigned char row,col,tmp1,tmp2;tmp1 = 0x10; //tmp1用来设置P1口的输出,取反后使P1.4~P1.7中有一个为0for(row=0;row<4;row++) // 行检测{P1 = 0x0f; // 先将p1.4~P1.7置高P1 =~tmp1; // 使P1.4~p1.7中有一个为0tmp1*=2; // tmp1左移一位if ((P1 & 0x0f) < 0x0f) // 检测P1.0~P1.3中是否有一位为0,只要有,则说明此行有键按下,进入列检测{tmp2 = 0x01; // tmp2用于检测出哪一列为0for(col =0;col<4;col++) // 列检测{if((P1 & tmp2)==0x00) // 该列如果为低电平则可以判定为该列{key_val =key_Map[ row*4 +col ]; // 获取键值,识别按键;key_Map为按键的定义表return; // 退出循环}tmp2*=2; // tmp2左移一位}}}}。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
以下假设你懂C语言,因为纯粹的C语言描述,所以和处理器平台无关,你可以在MCS-51,AVR,PIC,甚至是ARM平台上面测试这个程序性能。
以下以AVR的MEGA8作为平台讲解,没有其它原因,因为我手头上只有AVR的板子而已没有51的。
用51也可以,只是芯片初始化部分不同,还有寄存器名字不同而已。
核心算法:unsigned char Trg;unsigned char Cont;void KeyRead( void ){unsigned char ReadData = PINB^0xff; // 1Trg = ReadData & (ReadData ^ Cont); // 2Cont = ReadData; // 3}下面是程序解释:Trg(triger)代表的是触发,Cont(continue)代表的是连续按下。
1:读PORTB的端口数据,取反,然后送到ReadData 临时变量里面保存起来。
(端口值与0XFF 按位异或,有按键按下为0,异或后相应的位就为1,相当于将读取的端口值取反)2:算法1,用来计算触发变量的。
一个位与操作,一个异或操作,我想学过C语言都应该懂吧?Trg为全局变量,其它程序可以直接引用。
3:算法2,用来计算连续变量。
看到这里,有种“知其然,不知其所以然”的感觉吧?代码很简单,但是它到底是怎么样实现我们的目的的呢?好,下面就让我们绕开云雾看青天吧。
我们最常用的按键接法如下:AVR是有内部上拉功能的,但是为了说明问题,我是特意用外部上拉电阻。
(STM32可以将端口设置为输入上拉模式)那么,按键没有按下的时候,读端口数据为1,如果按键按下,那么端口读到0。
下面就看看具体几种情况之下,这算法是怎么一回事。
(1)没有按键的时候端口为0xff,ReadData读端口并且取反,很显然,就是 0x00 了。
(0XFF^0XFF=0X00)Trg = ReadData & (ReadData ^ Cont); (初始状态下,Cont也是为0的)很简单的数学计算,因为ReadData为0,则它和任何数“相与”,结果也是为0的。
Cont = ReadData; 保存Cont 其实就是等于ReadData,为0;结果就是:ReadData = 0;Trg = 0;Cont = 0;(2)第一次PB0按下的情况端口数据为0xfe,ReadData读端口并且取反,很显然,就是 0x01 了。
(0XFE^0XFF=0X01)Trg = ReadData & (ReadData ^ Cont); 因为这是第一次按下,所以Cont是上次的值,应为为0。
那么这个式子的值也不难算,也就是 Trg = 0x01 & (0x01^0x00) = 0x01Cont = ReadData = 0x01;结果就是:ReadData = 0x01;Trg = 0x01;Trg只会在这个时候对应位的值为1,其它时候都为0Cont = 0x01;(3)PB0按着不松(长按键)的情况端口数据为0xfe,ReadData读端口并且取反是 0x01 了。
Trg = ReadData & (ReadData ^ Cont); 因为这是连续按下,所以Cont是上次的值,应为为0x01。
那么这个式子就变成了 Trg = 0x01 & (0x01^0x01) = 0x00Cont = ReadData = 0x01;结果就是:ReadData = 0x01;Trg = 0x00;Cont = 0x01;因为现在按键是长按着,所以MCU会每个一定时间(20ms左右)不断的执行这个函数,那么下次执行的时候情况会是怎么样的呢?ReadData = 0x01;这个不会变,因为按键没有松开Trg = ReadData & (ReadData ^ Cont) = 0x01 & (0x01 ^ 0x01) = 0 ,只要按键没有松开,这个Trg 值永远为 0 !!!Cont = 0x01;只要按键没有松开,这个值永远是0x01!!(4)按键松开的情况端口数据为0xff,ReadData读端口并且取反是 0x00 了。
Trg = ReadData & (ReadData ^ Cont) = 0x00 & (0x00^0x01) = 0x00Cont = ReadData = 0x00;结果就是:ReadData = 0x00;Trg = 0x00;Cont = 0x00;很显然,这个回到了初始状态,也就是没有按键按下的状态。
总结一下,不知道想懂了没有?其实很简单,答案如下:Trg 表示的就是触发的意思,也就是跳变,只要有按键按下(电平从1到0的跳变),那么Trg在对应按键的位上面会置一,我们用了PB0则Trg的值为0x01,类似,如果我们PB7按下的话,Trg 的值就应该为 0x80 ,这个很好理解,还有,最关键的地方,Trg 的值每次按下只会出现一次,然后立刻被清除,完全不需要人工去干预。
所以按键功能处理程序不会重复执行,省下了一大堆的条件判断,这个可是精粹哦!!Cont代表的是长按键,如果PB0按着不放,那么Cont的值就为 0x01,相对应,PB7按着不放,那么Cont的值应该为0x80,同样很好理解。
因为有了这个支持,那么按键处理就变得很爽了,下面看应用:应用一:一次触发的按键处理假设PB0为蜂鸣器按键,按一下,蜂鸣器beep的响一声。
这个很简单,但是大家以前是怎么做的呢?对比一下看谁的方便?#define KEY_BEEP 0x01void KeyProc(void){if (Trg & KEY_BEEP) // 如果按下的是KEY_BEEP{Beep(); // 执行蜂鸣器处理函数}}当你按下按键的话,Trg & KEY_BEEP 为“真”的情况只会出现一次,所以处理起来非常的方便,蜂鸣器也不会没事乱叫,hoho~~~应用2:长按键的处理项目中经常会遇到一些要求,例如:一个按键如果短按一下执行功能A,如果长按2秒不放的话会执行功能B,又或者是要求3秒按着不放,计数连加什么什么的功能,很实际。
这里具个简单例子,为了只是说明原理,PB0是模式按键,短按则切换模式,PB1就是加,如果长按的话则连加(玩过电子表吧?没错,就是那个!)#define KEY_MODE 0x01 // 模式按键#define KEY_PLUS 0x02 // 加void KeyProc(void){if (Trg & KEY_MODE) // 如果按下的是KEY_MODE,而且你常按这按键也没有用,{ //它是不会执行第二次的哦,必须先松开再按下Mode++; // 模式寄存器加1,当然,这里只是演示,你可以执行你想// 执行的任何代码}if (Cont & KEY_PLUS) // 如果“加”按键被按着不放{cnt_plus++; // 计时if (cnt_plus > 100) // 20ms*100 = 2S 如果时间到{Func(); // 你需要的执行的程序}}}应用3:点触型按键和开关型按键的混合使用点触形按键估计用的最多,特别是单片机。
开关型其实也很常见,例如家里的电灯,那些按下就不松开,除非关。
这是两种按键形式的处理原理也没啥特别,但是你有没有想过,如果一个系统里面这两种按键是怎么处理的?我想起了我以前的处理,分开两个非常类似的处理程序,现在看起来真的是笨的不行了,但是也没有办法啊,结构决定了程序。
原理么?可能你也会想到,对于点触开关,按照上面的办法处理一次按下和长按,对于开关型,我们只需要处理Cont就OK了,为什么?很简单嘛,把它当成是一个长按键,这样就找到了共同点,屏蔽了所有的细节。
程序就不给了,完全就是应用2的内容,在这里提为了就是说明原理~~好了,这个好用的按键处理算是说完了。
可能会有朋友会问,为什么不说延时消抖问题?哈哈,被看穿了。
果然不能偷懒。
下面谈谈这个问题,顺便也就非常简单的谈谈我自己用时间片轮办法,以及是如何消抖的。
延时消抖的办法是非常传统,也就是第一次判断有按键,延时一定的时间(一般习惯是20ms)再读端口,如果两次读到的数据一样,说明了是真正的按键,而不是抖动,则进入按键处理程序。
当然,不要跟我说你delay(20)那样去死循环去,真是那样的话,我衷心的建议你先放下手上所有的东西,好好的去了解一下操作系统的分时工作原理,大概知道思想就可以,不需要详细看原理,否则你永远逃不出“菜鸟”这个圈子。
我的主程序架构是这样的:volatile unsigned char Intrcnt;void InterruptHandle() // 中断服务程序{Intrcnt++; // 1ms 中断1次,可变}void main(void){SysInit();while(1) // 每20ms 执行一次大循环{KeyRead(); // 将每个子程序都扫描一遍KeyProc();Func1();Funt2();……while(1){if (Intrcnt>20) // 一直在等,直到20ms时间到{Intrcnt="0";break; // 返回主循环}}}}貌似扯远了,回到我们刚才的问题,也就是怎么做按键消抖处理。
我们将读按键的程序放在了主循环,也就是说,每20ms我们会执行一次KeyRead()函数来得到新的Trg 和 Cont 值。
好了,下面是我的消抖部分:很简单(第二个while(1)部分)当然,和这个配合,每个子程序必须执行时间不长,更加不能死循环,一般采用有限状态机的办法来实现。
懂得基本原理之后,至于怎么用就大家慢慢思考了,我想也难不到聪明的工程师们。
例如还有一些处理,怎么判断按键释放?很简单,Trg 和Cont都为0 则肯定已经释放了。