设计中在遇到按键较多,但是单片机I/O资源有限时,采用矩阵键盘是一种很好的选择方案。在按键较少时,矩阵键盘没有明显的优势,比如3×3矩阵键盘占用I/O口6个,只支持9个按键;但是,按键较多时,矩阵键盘的优势就凸显出来了,比如5×5矩阵键盘占用I/O口10个,可支持25个按键;
❤矩阵键盘的硬件设计非常简单,如图1以5×5矩阵键盘为例,蓝色行线5条,绿色列线5条(加上拉电阻),用按键代替行列交叉的节点即组成了矩阵键盘,其中二极管处的红色连线可用来检测按键是否按下或作为中断信号的(可以不需要,使用时没有任何影响,本文不涉及)。
❤如图2为单片机STM32F103CBT6的外围电路,带颜色的I/O口为矩阵键盘行线PA1~PA5(蓝色),列线PA6~PA10(绿色)。为方便编程,矩阵键盘最好选择同组的I/O端口。
硬件的设计就是以上部分,接下来讲解程序部分,
矩阵键盘的扫描方法有多种,其中很青睐的是行列反转扫描,不仅简单而且效率高,而且只需要掌握该方法就足够了。
❤行列反转扫描的逻辑:
①行线输出全为0(行线PA1~PA5配置成推挽输出,列线PA6~PA10配置成上拉输入)
②读入列线值
③输出列线值(行线PA1~PA5配置成上拉输入,列线PA6~PA10配置成推挽输出)
④读入行线值
⑤组合行线列线值
对于STM32的单片机,直接操作寄存器会更简单,且无需再对I/O进行另外的配置,全部代码如图3所示,完全可以当做一个模块来使用。
以下代码与图3里面的代码完全一致(为方便浏览和拷贝)
/*---------------------- 5*5矩阵键盘扫描函数,返回扫描键值(行列反转扫描) -----------------------*/ u16 Key_Scan(void) { u16 cord_h,cord_l; u16 key_value = 0xFFFF; GPIOA->CRL = 0x88333334; GPIOA->CRH = 0x44444888; GPIOA->ODR = GPIOA->IDR & 0xFFC1; cord_l = GPIOA->IDR & 0x07C0; if(cord_l != 0x07C0) { delay_ms(10); if(cord_l != 0x07C0) { cord_l = GPIOA->IDR & 0x07C0; GPIOA->CRL = 0x33888884; GPIOA->CRH = 0x44444333; GPIOA->ODR = cord_l | 0xF83F; delay_us(1); cord_h = GPIOA->IDR & 0x003E; cord_h >>= 1; cord_l >>= 1; key_value = (cord_l + cord_h) & 0xFFFF; return(key_value); } }return(0xFFFF); }
❤以下代码为端口配置寄存器的操作,PA组I/O有16个引脚:PA0~PA15;3代表推挽输出,4代表浮空输入,8代表上拉输入;
GPIOA->CRL = 0x88333334; //低8位配置 GPIOA->CRH = 0x44444888; //高8位配置
❤以下代码中,GPIOA->ODR为端口输出数据寄存器,用于写操作;GPIOA->IDR为端口输入数据寄存器,用于读操作;
0xFFC1的二进制:1111 1111 1100 0001,表示要对PA1~PA5置0。
0x07C0的二进制:0000 0111 1100 0000,表示要对PA6~PA10读值。
GPIOA->ODR = GPIOA->IDR & 0xFFC1;//行线输出全为0 cord_l = GPIOA->IDR & 0x07C0; //读入列线值
行列反转后的原理与上面完全一样,这里就不一一阐述了。
❤最后讲一下组合行线列线值,假如S1按键按下去了,得到的二进制为(注意黑体字)
cord_h:0000 0000 0011 1100
cord_l: 0000 0111 1000 0000
cord_h >>= 1 表示右移1位得到:
0000 0000 0001 1110
cord_l >> = 1 表示右移1位得到:
0000 0011 1100 0000
组合码(键值):key_value=0000 0011 1101 1110,即0x03DE;最后函数返回key_value值,该值就作为判断S1的键值。
❤之所以右移1位,是因为PA0没有用于矩阵键盘,所得到的键值也就被左移了1位,事实上,也可以不用右移,只是得到的组合码不同而已,右移是为了方便计算。
矩阵键盘的键值定义如下:
#define S1 0x03DE #define S2 0x03BE #define S3 0x037E #define S4 0x02FE #define S5 0x01FE #define S6 0x03DD #define S7 0x03BD #define S8 0x037D #define S9 0x02FD #define S10 0x01FD #define S11 0x03DB #define S12 0x03BB #define S13 0x037B #define S14 0x02FB #define S15 0x01FB #define S16 0x03D7 #define S17 0x03B7 #define S18 0x0377 #define S19 0x02F7 #define S20 0x01F7 #define S21 0x03CF #define S22 0x03AF #define S23 0x036F #define S24 0x02EF #define S25 0x01EF
❤要点:
①行列反转扫描的思想是行线作为输出时,读出列线的值;然后反过来,列线作为输出时,读出行线的值,最后把两个值组合起来就是该按键的键值;
②参与计算的十六进制数一定要与行线或列线在同组端口的位置匹配,比如0xFFC1的二进制:1111 1111 1100 0001,表示要对PA1~PA5置0。