陈丙山,侯志伟,张永平,景江鹏
(兰州石化职业技术大学 电子电气工程学院,甘肃兰州,730060)
按键是嵌入式应用系统中重要的人机交互工具,能够更好地实现用户与系统之间的沟通与交互[1~2]。实现按键交互的主要结构方式有独立按键和矩阵键盘[3~4],其中独立按键的识别效率很高、硬件连接电路简单,但每个按键都要占用一根I/O口引脚,比较适用于少按键的应用场景,对于按键数量较多的情况下,为节省I/O口资源,需考虑使用矩阵键盘结构形式,矩阵键盘由多个行线和多个列线交织组成,广泛应用于数字密码锁、临时存取柜、电梯控制器等多按键场景[5~8]。因此,矩阵键盘作为电子设备和仪器装置的人机交互重要媒介,相应也提高了软件编程的复杂度,本文针对矩阵键盘识别时间长、电路扩展性差、通用性低等问题,详细介绍了以行列扫描法、行列线反法识别5×4(5 行4 列)矩阵键盘的优化方法,并提出了以16 位并行端口I2C 扩展器PCA9555a 为纽带的矩阵键盘识别方法,以满足相关应用场景中对多按键识别的实际应用需求。
矩阵键盘与处理器的硬件连接结构有直连法和扩展法两种方式[9~10],为适用于更多按键的应用场景,通常采用并行端口扩展器的方式。识别矩阵键盘中某个按键按下的位置,主要有扫描法、线反法、中断法等方式,其识别方法包括获取行列位置、键值编码、确定键号以及延时消抖等。
矩阵键盘硬件连接电路如图1 所示, 主控器STM32F103C8 与5 行4 列矩阵键盘的连接关系为:PB7~PB11 分别连接矩阵键盘的行线,PB12~PB15 分别连接矩阵键盘的列线;VIRTUAL TERMINAL 用来调试输出过程信息;PA0 为处理器正常工作指示灯。
图1 扫描法硬件连接电路
根据行线列线的高低电平来识别是否有按键按下,从而获取按键值。行列扫描法的识别思路为将列线GPIO 口PB12~PB15 设置为输出模式,行线GPIO 口PB7~PB11 设置为输入模式,采用“写列线,读行线”的方式逐列扫描,读行判断是否有按键按下,其主要步骤为:
第一步,置第1 列为低电平,其余列为高电平,那么列线PB12~PB15 的colval 值为1110;
第二步,读取行线数据,行线有低电平表示该行有按键按下,例如当按下的按键在第1 行时,第1 行的行线I/O口为低电平,那么行线PB7~PB11 的rowval 值为11110,如表1 所示。将rowval 值按位取反得到行线二进制值为00001(十进制为1)。同理,第2 行的行线二进制值为00010(十进制为2),第3 行的行线二进制值为00100(十进制为8),第4 行的行线二进制值为01000(十进制为16),第5 行的行线二进制值为10000(十进制为32)。
表1 第1列为低电平时读取的行线值
由此可见,按键按下的所在行keyrow与读取行值rowval按位取反的值呈现的数学关系式为:
式(1)中,C 语言math.h 头文件里log 函数为基数为e 的对数;
第三步,计算按下按键的键号keyval,与所在行keyrow的计算同理,按键按下的所在列keycol与设置行值colval按位取反的值呈现的数学关系式为:
那么,按键按下对应的键号keyval为:
第四步,按以上步骤以此类推逐列扫描完所有列。
本文矩阵键盘按键识别优化算法极大避免了键值编码和查表取值的繁琐工作。
采用“写列线,读行线”的方式需要逐列进行扫描,完成全部列的扫描其识别时间长、效率低。因此,逐行或逐列扫描法较为繁琐,在实际的嵌入式应用系统中更常用的是线反法。线反法的识别思路为行线输出,列线输入,行线置0,列线置1,按键按下时,列线被拉低,读取列线值确定低电平在哪一列;同理,行线输入,列线输出,行线置1,列线置0,按键按下时,行线被拉低,读取行线值确定低电平在哪一行;根据按键所在的行线与列线,即可唯一确定按键所在位置,进而获取按键的键值。其主要操作步骤为:
第一步,将行线PB7~PB11 设置为输出模式,列线PB12~PB15 设置为输入模式。
设置方式如下:
GPIOB->CRL &= 0x0FFFFFFF;
GPIOB->CRL |= ((uint32_t)3<<28);
GPIOB->CRH &= 0x00000000;
GPIOB->CRH |= ((uint32_t)3<<0 | (uint32_t)3<<4| (uint32_t)3<<8 | (uint32_t)3<<12 | (uint32_t)8<<16 |(uint32_t)8<<20 | (uint32_t)8<<24 | (uint32_t)8<<28);
第二步,行线输出低电平,列线输出高电平,若有按键按下,则将某条列线被拉低,读取列线PB12~PB15 的值colval 不再全为高电平,说明有按键按下,跳转到第三步,否则跳转至第一步等待有按键按下;
第三步,按式(2)计算出按键所在列的值keycol;
第四步,将行线PB7~PB11 设置为输入模式,列线PB12~PB15 设置为输出模式。
设置方式如下:
GPIOB->CRL &= 0x0FFFFFFF;
GPIOB->CRL |= ((uint32_t)8<<28);
GPIOB->CRH &= 0x00000000;
GPIOB->CRH |= ((uint32_t)8<<0 | (uint32_t)8<<4| (uint32_t)8<<8 | (uint32_t)8<<12 | (uint32_t)3<<16 |(uint32_t)3<<20 | (uint32_t)3<<24 | (uint32_t)3<<28);
第五步,行线输出高电平,列线输出低电平,读取行线PB7~PB11 的值rowval。
第六步,按式(1)计算出按键所在行的值keyrow;
第七步,按式(3)计算按下按键的键号keyval。
具体实现程序如下:
uint8_t keypad_scan1(void)
{
uint16_t rowval;
uint16_t colval;
uint8_t keyval = 0;
GPIOB->CRL &= 0x0FFFFFFF;
GPIOB->CRL |= ((uint32_t)3<<28);
GPIOB->CRH &= 0x00000000;
GPIOB->CRH |= ((uint32_t)3<<0 | (uint32_t)3<<4| (uint32_t)3<<8 | (uint32_t)3<<12 | (uint32_t)8<<16 |(uint32_t)8<<20 | (uint32_t)8<<24 | (uint32_t)8<<28);
oKeypadPort = 0xF000;
if(iKeypadPort!=0xF000)
{
delay_n_ms(10);
if(iKeypadPort!=0xF000)
{
oKeypadPort = 0xF000;
colval = (~(iKeypadPort>>12))&(0x000F);
GPIOB->CRL &= 0x0FFFFFFF;
GPIOB->CRL |= ((uint32_t)8<<28);
GPIOB->CRH &= 0x00000000;
GPIOB->CRH |= ((uint32_t)8<<0 | (uint32_t)8<<4 | (uint32_t)8<<8 | (uint32_t)8<<12 | (uint32_t)3<<16 | (uint32_t)3<<20 | (uint32_t)3<<24 | (uint32_t)3<<28);
oKeypadPort = 0x0F80;
rowval = (~(iKeypadPort>>7))&(0x001F);
keyval = log(rowval)/log(2)*4 + (log(colval)/log(2)) + 1;
do{rowval = (iKeypadPort>>7)&(0x001F);}while(rowval!=0x001F);
}
}
return keyval;
}
为节省GPIO 接口资源,或GPIO 接口较少的情况下,采用并行端口扩展器的方式来实现矩阵键盘的硬件连接,PCA9555 是一种400kHz 快速I2C 总线的16 位I/O 扩展器,工作电压为2.3V~5.5V,由两个8 位配置(输入或输出可选)、输入端口、输出端口和极性反转(高电平有效或低电平有效运行)寄存器组成。主控制器写入I/O 配置位将I/O 启用为输入或输出,每个输入或输出的数据均保存在相应的输入或输出寄存器中,输入端口寄存器的极性可借助极性反转寄存器进行转换。采用PCA9555 扩展器的矩阵键盘硬件连接电路如图2 所示。
图2 扩展法硬件连接电路
PCA9555 的从机地址配置如表2 所示。
表2 第1列为低电平时读取的行线值
表2 中,PCA9555 的从机地址为0x20,那么写数据地址为(0x20<<1)|0,读数据地址为(0x20<<1)|1。
矩阵键盘按键的识别思路与线反法相似,采用I2C 总线配置16 位I/O 扩展器的输入或输出,写输出端口寄存器值和读输入端口寄存器值。
具体实现程序如下:
uint8_t keypad_scan2(void)
{
uint16_t rowval;
uint16_t colval;
uint16_t PortToData;
uint8_t keyval = 0;
PCA9555A_IOConfiguration(0x20, 0xFF00);
PCA9555A_WriteData(0x20, 0xFF00);
PortToData = PCA9555A_ReadData(0x20, 0x01);
rowval = PortToData&0x001F;
if(rowval != 0x001F)
{
delay_n_ms(2);
PortToData = PCA9555A_ReadData(0x20,0x01);
rowval = PortToData&0x001F;
if(rowval != 0x001F)
{
PCA9555A_WriteData(0x20, 0xFF00);
PortToData = PCA9555A_ReadData(0x20, 0x01);
rowval = (~PortToData)&0x001F;
PCA9555A_IOConfiguration(0x20, 0x00FF);
PCA9555A_WriteData(0x20, 0x00FF);
PortToData = PCA9555A_ReadData(0x20, 0x00);
colval = (~PortToData)&0x000F;
keyval = log(rowval)/log(2)*4 + (log(colval)/log(2)) + 1;
do{
PortToData = PCA9555A_ReadData(0x20,0x00);
colval = PortToData&0x000F;
}while(colval!=0x000F);
}
}
return keyval;
}
为了实验的便捷和成本,5×4(5 行4 列)矩阵键盘的硬件设计思路和软件识别算法经过仿真软件Proteus 8.16 SP3(Build 36097) 和 开 发 环 境STM32CubeIDE 1.14.0的联合调试,验证了该设计过程的正确性。其中主控器STM32F103C8 的OSC frequency 设置为72MHz。
仿真运行时Virtual Terminal 输出的结果如图3 所示。
图3 仿真运行结果
本文针对矩阵键盘识别时间长、电路扩展性差、通用性低等问题,提出了一种矩阵键盘行列扫描法、行列线反法识别5×4(5 行4 列)矩阵键盘的优化识别算法,极大地避免了键值编码和查表取值的繁琐工作。行列扫描法将列线设置为输出模式,行线设置为输入模式,采用“写列线,读行线”的方式逐列扫描,读行判断是否有按键按下,采用基数为e的对数函数计算出按键所在行和所在列,从而计算出按键键号。但逐行或逐列扫描法较为繁琐,扫描识别时间长、效率低,进一步改进为线反法:行线输出,列线输入,读取列线值确定低电平在哪一列;行线输入,列线输出,读取行线值确定低电平在哪一行;根据按键所在的行线与列线确定按键所在位置,进而获取按键的键值。为节省GPIO 接口资源,或GPIO 接口较少的情况下,设计了以16 位并行端口I2C扩展器PCA9555a 为纽带的矩阵键盘硬件连接和识别方法。多次实验结果表明,本文线反法优化识别算法稳定可靠、简洁清晰、通用性好以及效率更高,扩展法比较适用于GPIO接口资源稀少情况,具有较好的实用价值。