[经验] 关于矩阵键盘,使用电子表格辅助编程

qiushenghua   2015-5-9 20:53 楼主
最近在某个项目中需要用到按键检测,常规想法是矩阵键盘,于是就这么做下去了。 后来在编程的时候发现,实现按键检测并不是一件容易的事情,特别是有多个按键同时按下的情况下。 在这个过程中逻辑太容易乱了,于是借助了电子表格工具(WPS或者Excel),试图节省工作量。 后来发现效果确实不错,于是在这里分享一下。 绘制PCB使用的是Altium Designer 10,但是家中未安装该软件,于是使用DesignSparkPCB将原理图重新绘制了一遍,大家将就着看。 1.png 我们的目的是实现2*3的矩阵键盘的检测,思路大概是这样的: 1.将Row1置低,其他管脚设置为带有上拉的输入状态,将其状态记录下来。 2.将Row2置低,其他管脚设置为带有上拉的输入状态,记录输入结果并计算按键状态。 或许大家觉得这个过程很容易,一看Col哪个被拉低就知道是哪个按键被按下了。 其实却不是这样,有可能存在多个按键共同作用导致某个Col被拉低的情况。 为什么不用Col来扫描?——因为扫描Row只需要扫描2回,扫描Col需要扫描3回。 为了解决这个问题,尽可能多的检测出多个按键按下的不同状况,我使用了大名鼎鼎的电子表格(俗称Excel),来实现这个检测。 作为6个按键,有2^6=64种不同的状态,所以我们可以通过电子表格的自动填充功能实现这个工作。 我们将A列定义为64种不同状态(0-63) 然后在B2单元格内输入“=dec2bin(A2,6)”,将A列的数据转换为二进制数 C列表头为SW1,C2单元格“=LEFT(B2,1)”,即二进制数左端第1个数 D列表头为SW2,D2单元格“=RIGHT(LEFT(B2,2),1)”,即二进制数左端取2个,再从这两个里取右端的一个,也就是左起第二个数 同理: E列表头SW3,E2单元格“=RIGHT(LEFT(B2,3),1)” F列表头SW4,F2单元格“=RIGHT(LEFT(B2,4),1)” G列表头SW5,G2单元格“=RIGHT(LEFT(B2,5),1)” H列表头SW6,H2单元格“=RIGHT(B2,1)” 完成上述步骤之后,我们空出一列来(I列),接下来计算按键按下之后产生的逻辑关系,这也是最让人头疼的问题。 为了节省列宽(屏幕大小有限,可以显示更多内容),我们将J1单元格输入R2(Row_2),K1单元格输入C1(Col_1),L1=C2,M1=C3 为什么没有R1?——这里计算的是R1设定为低的情况下其他信号线的输入状态,R1一定为0。 接下来就是信号怎么判断了。 由于上拉是弱上拉,在按键按下的时候,如果对应行是低电平,那么列会被拉低。 在这里我们约定,如果SW1=1,表示SW1按下,其余按键以此类推。 在R1=0时,要让R2也等于0,那么需要SW1和SW4同时按下,或者SW2和SW5同时按下,又或者SW3和SW6同时按下。 于是,J2单元格(Row2列)的逻辑就是“=IF(C2*F2+D2*G2+E2*H2,0,1)”(组合逻辑不要忘记了) 接下来计算Col1列,这个时候刚刚计算得到的Row2列的结果可以用到。 我们知道,如果Row1为低,且SW1按下,那么Col为低,如果Row2为低,且SW4为低,那么Col也为低。 故K2单元格:“=IF(C2+F2*(1-J2),0,1)” L2:“=IF(D2+G2*(1-J2),0,1)” M2:“=IF(E2+H2*(1-J2),0,1)” 继续空一列(N列),有了之前的经验,很容易填写下面的单元格: O2:“=IF(C2*F2+D2*G2+E2*H2,0,1)” P2:“=IF(F2+C2*(1-O2),0,1)” Q2:“=IF(G2+D2*(1-O2),0,1)” R2:“=IF(H2+E2*(1-O2),0,1)” 然后选中这一行的所有数据,向下填充到65行(表头占一行,数据有64行) 效果如下图所示: 2.png 有人要问了,不就是计算个逻辑吗,何必搞的那么复杂呢?又是公式又是填充的。 请稍等,先来段广告,精彩稍后继续。。。 刚刚说了那么久,还没提到电气连接是怎么样的。 这个项目原本我是用STM32实现的,但是,这里是MSP430板块嘛,那么我就移植一下咯,用最常见的MSP430G2553来做。 R1:P1.4 R2:P1.3 C1:P1.2 C2:P1.1 C3:P1.0 初始化的代码就不写了,定义两个宏:
  1. #define R1OUT() do{\
  2. P1DIR &= ~BIT3;\
  3. P1OUT |= BIT3;\
  4. P1REN |= BIT3;\
  5. P1REN &= ~BIT4;\
  6. P1OUT &= ~BIT4;\
  7. P1DIR |= BIT4;\
  8. }while(0)
  9. #define R2OUT() do{\
  10. P1DIR &= ~BIT4;\
  11. P1OUT |= BIT4;\
  12. P1REN |= BIT4;\
  13. P1REN &= ~BIT3;\
  14. P1OUT &= ~BIT3;\
  15. P1DIR |= BIT3;\
  16. }while(0)
那么我们可以分两轮扫描,定期执行。 第一轮扫描R2OUT()运行时的P1IN,读取完毕执行R1OUT()。 第二轮扫描R1OUT()运行时的P1IN,读取完毕执行R2OUT()。 那么我们可以知道,第一轮扫描的时候,R2是输出,我们需要读取P1.4 P1.2 P1.1 P1.0的键值。 第二轮扫描的时候,R1输出,我们需要读取P1.3 P1.2 P1.1 P1.0的键值。 由于使用了连续的寄存器,所以我们能够很方便的使用一个寄存器Key_Buffer来储存键码。 于是,我们只需要在第一轮扫描的时候将寄存器清除,再将键值搬运到寄存器的高4位, 在第二轮扫描的时候直接将寄存器加上P1IN的低4位即可。
  1. void Key_Scan()
  2. {
  3. static unsigned char count=1;
  4. static unsigned char Key_Buffer=0;
  5. static unsigned char Key_States=Key_Old_States=Key_Press=Key_Bottom_up=0;
  6. if(count&0x01)//奇数轮
  7. {
  8. unsigned char temp=0;
  9. temp=P1IN;
  10. Key_Buffer=((temp&0x07)<<4)+((temp&0x10)<<3);
  11. R1OUT();
  12. }
  13. else//偶数轮
  14. {
  15. Key_Buffer+=(P1IN&0x0f);
  16. R1OUT();
  17. }
  18. count++;
  19. }
眼尖的可能见到了,我还定义了Key_States,Key_Old_States,Key_Press和Key_Bottom_up一共4个变量,它们是用来干嘛的呢? 我们接着看。 这个扫描结果对应的是表格里J-M,O-R列的数值,而我们需要的是C到H列里各个按键的状态,如果有办法将其转换一下就好了。 好吧,我们回到电子表格里继续计算。 老规矩,空出一列(S列)作为分隔,我们的Key_Buffer在经过奇偶两轮的运算之后,是J-M,O-R列数据拼接而成的数值。 于是我们在T2单元格模仿这个运算,填入“=J2&K2&L2&M2&O2&P2&Q2&R2”,在电子表格里,&既不是与运算也不是按位与运算,而是字符串拼接运算。 继续往后看,U2单元格“=bin2dec(T2)”将这个结果向下填充,得到了最终版本的Sheet1。 (后续运算如果在这个工作表基础上继续下去,将破坏本表格的完整性,所以将Sheet中所有数据复制到Sheet2中继续进行。) 现在表格长这个样子了: 3.png 刚刚说了,将Sheet1复制了一份在Sheet2表,我们接着算。 我们留意到,在U2列里出现了好多相同的数据,这说明扫描得到的结果是一样的,但是键码却不一样。于是我们要找出这些无法判断键码的键值来。 在V2单元格填入“=COUNTIF(U:U,U2)”,向下填充。 于是看到了好多次数超过一次的。选中A-V列,自定义排序,选择有标题行,按照V列降序,U列升序,A列升序的次序排列。 我们截选了一段键值一样的扫描结果: 4.png 从这段表格可以看出,在SW1,SW2,SW4同时按下的时候,无法判断SW5是否按下。 结合原理图,我们发现,在在SW1,SW2,SW4同时按下的时候, Row1,Row2,Col1,Col2均被短路,按下或者不按下SW5,均无法改变这个结果。 换句话说就是在这样的情况下从电路上就没办法检测出SW5了。 对于这样的检测结果,最好的处理办法就是认为它就是上一次的键码。 于是,我们先在表格里删除这些扫描结果重复了的行(一共有34行,超过了一半)。 回到IDE里面,Key_Buffer这个变量是在偶数轮扫描完成之后得到结果的,也就是我们的扫描键值。 那么最简单的处理方式就是:
  1. Key_Old_States=Key_States;
  2. switch(Key_Buffer)
  3. {
  4. case 51:Key_States=36;break;
  5. case ……
  6. }
虽然就已经删除了三十多行,可还有三十行呢,一个个数敲进去简直痛苦死了。那么我们继续: 我们发现,关键就在于“case 51:Key_States=36;break;”,如果能够自动生成这样的语句,那么就简单多了。 于是在W2单元格内敲入:
  1. ="case "&U2&":Key_States="&A2&";break;"
向下填充: 5.png 开开心心将其复制进IDE里,代码就完成了。 最后别忘记补充一句“default:break;” 毕竟我们删除了那么多的重复键值,直接保持Key_State的结果不变就好了。 然后,Key_Press和Key_Bottom_up怎么办呢? 很简单,检查到Key_States==Key_Old_States,那就可以认为没发生按键动作。
  1. Key_Press=((Key_States^Key_Old_States)&Key_States);
  2. Key_Bottom_up=((Key_States^Key_Old_States)&Key_Old_States);
怎么根据按键来实现具体功能就不提了,大家都会的。等会沙发楼上完整代码。 本帖最后由 qiushenghua 于 2015-5-10 01:19 编辑

回复评论 (13)

  1. void Key_Scan()
  2. {
  3. static unsigned char count=1;
  4. static unsigned char Key_Buffer=0;
  5. static unsigned char Key_States=0;
  6. static unsigned char Key_Old_States=0;
  7. if(count&0x01)//奇数轮
  8. {
  9. unsigned char temp=0;
  10. temp=P1IN;
  11. Key_Buffer=((temp&0x07)<<4)+((temp&0x10)<<3);
  12. R1OUT();
  13. }
  14. else//偶数轮
  15. {
  16. Key_Buffer+=(P1IN&0x0f);
  17. R1OUT();
  18. Key_Old_States=Key_States;
  19. switch(Key_Buffer)
  20. {
  21. case 51:Key_States=36;break;
  22. case 85:Key_States=18;break;
  23. case 102:Key_States=9;break;
  24. case 143:Key_States=56;break;
  25. case 158:Key_States=49;break;
  26. case 159:Key_States=48;break;
  27. case 173:Key_States=42;break;
  28. case 175:Key_States=40;break;
  29. case 188:Key_States=35;break;
  30. case 189:Key_States=34;break;
  31. case 190:Key_States=33;break;
  32. case 191:Key_States=32;break;
  33. case 203:Key_States=28;break;
  34. case 207:Key_States=24;break;
  35. case 218:Key_States=21;break;
  36. case 219:Key_States=20;break;
  37. case 222:Key_States=17;break;
  38. case 223:Key_States=16;break;
  39. case 233:Key_States=14;break;
  40. case 235:Key_States=12;break;
  41. case 237:Key_States=10;break;
  42. case 239:Key_States=8;break;
  43. case 248:Key_States=7;break;
  44. case 249:Key_States=6;break;
  45. case 250:Key_States=5;break;
  46. case 251:Key_States=4;break;
  47. case 252:Key_States=3;break;
  48. case 253:Key_States=2;break;
  49. case 254:Key_States=1;break;
  50. case 255:Key_States=0;break;
  51. default:break;
  52. }
  53. Key_Press=((Key_States^Key_Old_States)&Key_States);
  54. Key_Bottom_up=((Key_States^Key_Old_States)&Key_Old_States);
  55. }
  56. count++;
  57. }
本帖最后由 qiushenghua 于 2015-5-9 21:01 编辑
点赞  2015-5-9 20:54
多谢分享!!学习了
点赞  2015-5-9 23:08
真不错
顶一个,过段时间也许会用得上
So TM what......?
点赞  2015-5-9 23:38
赞一个,他山之石,可以攻玉
点赞  2015-5-10 07:24
不错,很棒的方法
点赞  2015-5-10 10:38
厉害啊
点赞  2015-5-10 13:41
Mark,用电脑的时候看
    懒得很
点赞  2015-5-10 21:52
不错不错,学习到了
点赞  2015-5-11 08:55
好复杂的说。。。。。。。
点赞  2015-5-11 09:02
学习了,顺便收藏
点赞  2015-5-11 10:57
好资源,真心好楼主,学习学习,真心希望电子工程世界这个论坛越来越好,希望大家都能来支持楼主
点赞  2015-6-13 16:42
6个按键有这么多组合啊!!!
我们一般用不上啊...
我这里有个项目也是6个按键...有长按,短按,组合按...程序我没有做得这么复杂...
点赞  2016-7-7 14:36
这个工程实际测试效果怎么样
点赞  2018-1-27 22:49
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复