干货 | 关于矩阵键盘,使用电子表格辅助编程
最近在某个项目中需要用到按键检测,常规想法是矩阵键盘,于是就这么做下去了。
后来在编程的时候发现,实现按键检测并不是一件容易的事情,特别是有多个按键同时按下的情况下。
在这个过程中逻辑太容易乱了,于是借助了电子表格工具(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
初始化的代码就不写了,定义两个宏:
#define R1OUT() do{\
P1DIR &= ~BIT3;\
P1OUT |= BIT3;\
P1REN |= BIT3;\
P1REN &= ~BIT4;\
P1OUT &= ~BIT4;\
P1DIR |= BIT4;\
}while(0)
#define R2OUT() do{\
P1DIR &= ~BIT4;\
P1OUT |= BIT4;\
P1REN |= BIT4;\
P1REN &= ~BIT3;\
P1OUT &= ~BIT3;\
P1DIR |= BIT3;\
}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位即可。
void Key_Scan()
{
static unsigned char count=1;
static unsigned char Key_Buffer=0;
static unsigned char Key_States=Key_Old_States=Key_Press=Key_Bottom_up=0;
if(count&0x01)//奇数轮
{
unsigned char temp=0;
temp=P1IN;
Key_Buffer=((temp&0x07)<<4)+((temp&0x10)<<3);
R1OUT();
}
else//偶数轮
{
Key_Buffer+=(P1IN&0x0f);
R1OUT();
}
count++;
}
眼尖的可能见到了,我还定义了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这个变量是在偶数轮扫描完成之后得到结果的,也就是我们的扫描键值。
那么最简单的处理方式就是:
Key_Old_States=Key_States;
switch(Key_Buffer)
{
case 51:Key_States=36;break;
case ……
}
虽然就已经删除了三十多行,可还有三十行呢,一个个数敲进去简直痛苦死了。那么我们继续:
我们发现,关键就在于“case 51:Key_States=36;break;”,如果能够自动生成这样的语句,那么就简单多了。
于是在W2单元格内敲入:
="case "&U2&":Key_States="&A2&";break;"
向下填充:
开开心心将其复制进IDE里,代码就完成了。
最后别忘记补充一句“default:break;”
毕竟我们删除了那么多的重复键值,直接保持Key_State的结果不变就好了。
然后,Key_Press和Key_Bottom_up怎么办呢?
很简单,检查到Key_States==Key_Old_States,那就可以认为没发生按键动作。
Key_Press=((Key_States^Key_Old_States)&Key_States);
Key_Bottom_up=((Key_States^Key_Old_States)&Key_Old_States);
怎么根据按键来实现具体功能就不提了,大家都会的。点击阅读原文查看完整代码。