香港腕表价格交流群

干货 | 关于矩阵键盘,使用电子表格辅助编程

2020-11-10 09:14:41

最近在某个项目中需要用到按键检测,常规想法是矩阵键盘,于是就这么做下去了。


后来在编程的时候发现,实现按键检测并不是一件容易的事情,特别是有多个按键同时按下的情况下。


在这个过程中逻辑太容易乱了,于是借助了电子表格工具(WPS或者Excel),试图节省工作量。


后来发现效果确实不错,于是在这里分享一下。


绘制PCB使用的是Altium Designer 10,但是家中未安装该软件,于是使用DesignSparkPCB将原理图重新绘制了一遍,大家将就着看。


 
我们的目的是实现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行)
效果如下图所示:



有人要问了,不就是计算个逻辑吗,何必搞的那么复杂呢?又是公式又是填充的。


刚刚说了那么久,还没提到电气连接是怎么样的。
这个项目原本我是用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.         }

  19.         count++;

  20. }


眼尖的可能见到了,我还定义了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中继续进行。)
现在表格长这个样子了:


 
刚刚说了,将Sheet1复制了一份在Sheet2表,我们接着算。


我们留意到,在U2列里出现了好多相同的数据,这说明扫描得到的结果是一样的,但是键码却不一样。于是我们要找出这些无法判断键码的键值来。


在V2单元格填入“=COUNTIF(U:U,U2)”,向下填充。


于是看到了好多次数超过一次的。选中A-V列,自定义排序,选择有标题行,按照V列降序,U列升序,A列升序的次序排列。

我们截选了一段键值一样的扫描结果:


 
从这段表格可以看出,在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;"

向下填充:


 
开开心心将其复制进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);


怎么根据按键来实现具体功能就不提了,大家都会的。点击阅读原文查看完整代码。

推荐阅读

干货 | DIY定时恒温饭盒

干货 | 雕刻机自制PCB电路板

干货 | 我自用的一种复杂单片机应用的组织构架

干货 | 浅谈单片机应用程序架构

干货 | 送给新手:STM32的时钟树解析

干货 | FreeRTOS学习笔记 ——应用场景

干货 | 枚举变量与宏的应用

干货 | 打造一款精美VFD语音时钟

干货 | 手把手教你制作带 LED 应急灯的 mini 移动电源

干货 | 稳压杂谈

友情链接

Copyright © 2023 All Rights Reserved 版权所有 香港腕表价格交流群