# python-keyboard 按键扫描优化
从 2020 年开始,主力键盘从宁芝静电容换成了使用 python-keyboard (粉笔套 + 佳达隆白轴按键)。前两年使用下来没有遇到什么问题,直到最近两个月开始,有几个常用按键开始出现误触的情况。猜测可能是机械按键老化性能下降导致抖动加剧,进而影响了键值识别。
打开控制台,跟踪了一下日志,发现单次按键按下被识别成了两次按下。
key 07
7 7 \ 0x24 latency 7 | 759
key 87
7 7 / 0x24 latency 9 | 25
key 07
7 7 \ 0x24 latency 7 | 21
key 87
7 7 / 0x24 latency 9 | 25
key 0D
跟踪代码发现跟这个函数的实现有关:
https://github.com/makerdiary/python-keyboard/blob/main/keyboard/matrix.py#L50
def scan(self):
"""
Scan keyboard matrix and save key event into the queue.
:return: length of the key event queue.
"""
t = time.monotonic_ns()
# use local variables to speed up
pressed = self.pressed
last_mask = self.mask
cols = self.cols
mask = 0
count = 0
key_index = -1
for row in self.rows:
row.value = pressed # select row
for col in cols:
key_index += 1
if col.value == pressed:
key_mask = 1 << key_index
if not (last_mask & key_mask):
if t - self.t1[key_index] < self._debounce_time:
print("debonce")
continue
self.t0[key_index] = t
self.put(key_index)
mask |= key_mask
count += 1
elif last_mask and (last_mask & (1 << key_index)):
if t - self.t0[key_index] < self._debounce_time:
print("debonce")
mask |= 1 << key_index
continue
self.t1[key_index] = t
self.put(0x80 | key_index)
row.value = not pressed
self.mask = mask
self.count = count
return self.length
上面函数消抖部分处理有问题,按下消抖确认使用释放时的时间戳(t1)不合理。扫描部分的逻辑耦合较为严重。新的思路是把矩阵键盘上的每个按键进行独立检测(硬件上已经支持),并利用状态机重构scan函数。
class MatrixKey:
def __init__(self) -> None:
self.sta = 0
self.ts = 0
self.ts_pressed = 0
self.ts_released = 0
# return value: 1 pressed, 0 released, -1 wait/idle
def scan(self, ts, pressed, duration) -> int:
if self.sta not in [0, 2] and ts - self.ts < duration:
return -1
self.ts = ts
if self.sta == 0:
if pressed:
# press detected, to debounce
self.sta = 1
elif self.sta == 1:
if not pressed:
# too short, ignore
self.sta = 0
else:
# press confirm
self.sta = 2
self.ts_pressed = ts
return 1
elif self.sta == 2:
if not pressed:
# release detected, to debounce
self.sta = 3
elif self.sta == 3:
if pressed:
# too short, ignore
self.sta = 0
else:
# release confirm
self.sta = 0
self.ts_released = ts
return 0
return -1
class Matrix:
def __init__(self):
...
self._keys = []
for _ in range(self.keynum):
self._keys.append(MatrixKey())
def _scan(self):
ts = time.monotonic_ns()
pressed = self.pressed
cols = self.cols
rows = self.rows
key_index = -1
for row in rows:
row.value = pressed # select row
for col in cols:
key_index += 1
k = self._keys[key_index].scan(ts, col.value == pressed, self._debounce_time)
if k == 1:
self.put(key_index)
elif k == 0:
self.put(0x80 | key_index)
row.value = not pressed
在嵌入式这个领域,很多确实是硬件问题的问题,其实可能是可以使用软件来解决的。这也确实很难,需要设计人员有未卜先知的能力。