Ghost Key 鬼键处理技术

一、矩阵键盘按键扫描原理:

在单片机中,我们如果要获取一个按键的状态,可以将按键连接到 MCU 的一个引脚上,通过 GPIO 实时监控这个按键的状态变化,但是,假设我有 100 个按键需要获取状态,那么是不是要配置 100 个 GPIO 呢?当然不是。按照常规做法会非常浪费 IO 资源,所以我们要引进矩阵键盘技术。矩阵实际上是一种接口技术,可以用于连接输入,例如机械键盘,也可用于矩阵式的 RGB 输出控制。根据矩阵分布,我们可以将 GPIO 分为两部分,行和列,类比于 EXCEL 工作表。

一个 4*4 的矩阵布置如下图:



图一

红线为行,蓝线为列,行列相交,图中行列互不相交,此时我在每个行列交叉位置再添加一个按键,如下图:



图二

这时候,行列交叉处都由一个按键进行连接,产生一个结节。按键按下时,对应行列将会相交。而每个按键的位置则对应了行列的值,这样的话,如果要获取这 16 个按键值,只需要 8 个 GPIO 即可做到,能够大大节省 IO 资源。我们可以设置列为 GPIO 输出,行为输入,通过不停地顺序设置列的 IO 状态为高,然后检测行的 IO 状态,即 MCU 会有规律地依次从 ABCD 上输出高电平,输出高电平地同时读取 1234 引脚的状态。每一列都可看为单独的 4 个按键,若按下 A1 按键,则行 1 会检测到高电平变化,说明按键按下了。通过这种方式,我们可以实时获取到这 16 个按键的状态。以上这种以行列排布的方式,就是矩阵键盘。

在一些单按键操作中,这种 4*4 的矩阵按键已经足够使用,也不需要组合按键。此时矩阵按键的缺陷并没有暴露出来,但在实际应用中,机械键盘或者薄膜键盘中,不管是办公还是游戏场景下,都会有很多情况下需要用到组合按键。在矩阵键盘中多个按键同时按下后,这就是组合按键了。

二、鬼键产生原因

通过上面的讲解,我们可以基本了解矩阵键盘的原理,现在我们继续熟悉矩阵键盘容易出现的缺陷之处。假设上图的矩阵键盘中,我们同时按下 A1-A4 按键,MCU 端能够实时监控到按键 A1-A4 按下了,这是一次正常的扫键过程。假设如下图操作,我们同时按下 B2,B3,C2 按键时,当 C 端口输出高电平,C 线上的电会通过 C2 按键传输至 B 线,由于 B3 也按下,此时 2,3 端口都会读取到高电平,MCU 则会认为按键 C2、C3 已经被按下了,然而此时 C3 按键实际上并没有按下,这种现象可称为“重影现象”。



图三

在电脑游戏中有很多组合按键,如果对这部分按键不加以处理,就会导致按键识别错误。换一种情况,这时候,我们同时按下 B2,B3,C2,C3 按键,2,3 端口都会检测到高电平。



图四

此时我们松开 C2 按键,C 端口高电平时,会发现 3 端口电平依旧为高,仍然没有任何变化,MCU 无法检测到按键 C2 释放,这种现象也称为“遮罩现象”。



图五

以上,没有触发的按键,MCU 识别到按键触发,触发的按键,MCU 无法识别到,这种不按照实际情况识别按键的情况,称之为“鬼键”,鬼键会引起一系列错误,

使得使用者非常苦恼。

三、鬼键消除技术

那么我们可以通过什么方法去规避这些鬼键的触发呢?我们可以从硬件和软件方面进行规避,先从硬件方向讲解,如下图,我们可以在每个按键旁边添加一个二极管,可以防止在同时按下 B2,C2 按键时,当 C 端口输出高电平,电被传导至 B 端口的情况。添加二极管可以防止电流倒灌,但会增加成本,通常在机械键盘中,我们会添加二极管进行防鬼键。在一些特定情况下,例如轮询速度比较快的时候,我们需要选择合适的二极管,通常可用廉价的 LN4148 通用二极管,假如需要更快的采样,更多的按键,我们可以采用肖特基二极管。



图六

当然,在产品中,我们还可以合理地设计按键的位置,也能够有效地避免实际应用中触发鬼键,在某些少按键的产品应用中,例如 61 键 、87 键等等,所需按键不多的情况下,我们可以进行合理地留空,去避免一些鬼键的产生。通常在微软系统中,组合键为 Control Key,我们可以对这些按键进行特别的摆放,组合按键不放在同一行,也能够进行避免。

我们继续从软件方向进行防止鬼键在,在软件中,需要及时发现鬼键,并且丢弃,防止出错。假设我们现在采用的是 8*16 的矩阵,C0-C15 为列,R0-R7 为行,8 个行 GPIO 用做电平输入,16 个列 GPIO 用作电平输出,依次在 C0-C15 上输出高电平,同时记录 R0-R7 的电平状态,由此类推,可以记录出 16 组 R0-R7 的状态,即最大可识别 128 个按键的状态变化。

我们设定 Keyboard_old_state[x] 记录上一组的扫键 bit 记录值,keyboard_new_state[x] 记录当前组的扫键 bit 记录值,每一个记录值记录当前组 R0-R7 的状态,然后我们将这两个 state 进行异或运算处理,再用一个变量去记录这个异或运算值,如果这个变量 bit 计算超过 2 个 bit 是置为 1 的,说明此时有鬼键触发。扫描按键流程为:首先通过for 循环从左至右,找出有按键触发的第一列,然后再从有按键的下一列开始继续向右查询有按键的一列,记录下这两列的 state 值,每列对应 8 bit 值,再使用变量记录下当前两个state  值异或运算后的值,对该变量进行判断处理,假设同 bit 位的值为 2 个以上,则说明有鬼键触发,需要进行软件丢键处理。比如 C1,C2 均有按键按下,当 C1 对应值为 0x10001000,C2 对应值为 0x10001000,如上判断即成立,认为有鬼键触发。


具体参考代码如下:

  • 判断按键中是否有鬼键触发:
  /*==================ghost handle=================*/
u32CurScanCode = (C0 | (C1<<1U) | (C2<<2U) | (C3<<3U) | (C4<<4U) | (C5<<5U)| (C6<<6U)| (C7<<7U));
uint32_t ScanCode;
ScanCode = u32CurScanCode & ~(0xffffff00);
ghost_bitmap=ghost_history & ScanCode;
if(ghost_bitmap != 0 && ghost_bitmap != 0x01)
{
while( !(ghost_bitmap & 0x01) )
{
ghost_bitmap >>= 1;
}
if(ghost_bitmap != 0x01)
{
Ghost_key=true;
ghost_key_cnt = 10;
PRINTF("catch ghostkey!\r\n");
}
}
GPIO_ClearPinsOutput(GPIOA,1<<BOARD_SW9_GPIO_PIN);
ghost_history |= ScanCode; // update ghost history
/*==================ghost handle=================*/
  • 鬼键处理:
  if (ghost_key_cnt > 0)
{
ghost_key_cnt--;
if (ghost_key_cnt == 0)
{
Ghost_key = false;
Key_data_change = true;
}
}
  • 鬼键丢弃,上报空包数据
  if(!Ghost_key)
{
for(uint8_t i=2;i<=8;i++)
{
if(KeyboardBuffer_Send[i]>=0xE8)
{
KeyboardBuffer_Send[i]=0;
}
}
Send_KeyboardReport(KeyboardBuffer_Send); //sent normal key data keyboard report
}


四、总结

以上则为本篇所讲的鬼键处理技术,后续会推出更多关于 MCU 的知识,欢迎大家关注!

五、参考资料

【1】http://pcbheaven.com/wikipages/How_Key_Matrices_Works/

【2】https://github.com/coreboot/chrome-ec/blob/main/common/keyboard_scan.c

★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论

shuszhao

shuszhao

2022年7月8日
在每个按键加一个Diode ,可以完美解决Ghost key 问题