前言
上一章我们介绍了经典矩阵键盘的实现方法,但是示例程序中仅实现了单按键检测功能。虽说单按键已经基本可以覆盖矩阵键盘的常见需求,但在一些特殊应用场合,我们仍然需要多按键识别操作,或者一些类似电脑组合按键的功能支持。
先从示例程序上进行改善,支持多按键检测。
多按键扫描示例程序
经典矩阵键盘是由行列线组成,行线与列线构成一个二维模型,从代码的角度看,类似二维数组。
使用二维数组的每一个值,记录矩阵键盘行列中对应按键的状态,按键按下记录为1,按键抬起记录为0,每次检测后保存当前矩阵状态。
前后两次检测,判断对应矩阵有没有变化,即可确认是否有按键状态更新, 也可以判断当前有哪些按键被按下。
实际使用时,由于按键状态只有 0/1 状态变化,使用 1bit 即可表示。
所以二维数据可简化为一维数组,列不超过8时,可使用 uint8_t 8位数据的每一位去代表每一列的按键状态。
// 检查每一行对应的列值
void check_cols_on_row(uint8_t row, uint8_t matrix[])
{
uint8_t col;
uint8_t col_shifter = 1;
uint8_t row_value = 0;
// 逐行选中
set_gpio_low(matrix_rows[row]);
for (col = 0; col < MATRIX_COL_MAX; col++, col_shifter <<= 1)
{
// 逐位赋值
// GPIO 为高时,抬起状态,记录为0,
// GPIO 为低时,按下状态,记录为1
row_value |= get_gpio(matrix_cols[col]) ? KEY_UNPRESSED : col_shifter;
}
matrix[row] = row_value;
// 清除选中行
set_gpio_height(matrix_rows[row]);
}
// 矩阵键盘扫描
void matrix_scan(uint8_t matrix[])
{
uint8_t row;
memset(matrix, 0x0, sizeof(matrix));
for (row = 0; row < MATRIX_ROW_MAX; row++)
{
// 判断对应行中的列状态,确认按键状态
check_cols_on_row(row, matrix);
}
}
按键消抖,此处实现了一种简单的消抖方式"矩阵消抖",第一次检测矩阵状态,延时10us,再次检测矩阵状态,如果矩阵前后两次状态相同,才认为是有效数据,否则认为是按键抖动。
消抖方式可自行实现,在时间和空间复杂度上进一步优化。
// 按键扫描
uint8_t key_scan()
{
uint8_t changed = 0;
uint8_t bounce = 0;
// 扫描矩阵状态
matrix_scan(scan_matrix);
// 和上一次有效值比较,如果存在差异,初步检测状态变化
changed = memcmp(scan_matrix, active_matrix, sizeof(scan_matrix)) != 0;
if (changed)
{
// 延时消抖
delay_10us(1);
// 再次扫描状态,如果矩阵状态相同,才认为状态更新
matrix_scan(debounce_matrix);
bounce = memcmp(scan_matrix, debounce_matrix, sizeof(scan_matrix)) != 0;
if (!bounce)
{
memcpy(active_matrix, scan_matrix, sizeof(scan_matrix));
return MATRIX_CHANGED;
}
}
return MATRIX_UNCHANGED;
}
矩阵检测后,需要根据按键的功能需求,确认对应的实现方式,DEMO 仅做打印处理。
// 处理键盘扫描结果
void key_process()
{
static uint8_t matrix_prev[MATRIX_ROW_MAX];
uint8_t row, col;
uint8_t matrix_row;
uint8_t matrix_change;
uint8_t col_mask = 1;
matrix_key_t key;
for (row = 0; row < MATRIX_ROW_MAX; row++)
{
matrix_row = matrix_get_row(row);
// 当前行值与上次的行值进行比较,看是否有变化
matrix_change = matrix_row ^ matrix_prev[row];
if (matrix_change)
{
for (col = 0; col < MATRIX_COL_MAX; col++, col_mask <<= 1)
{
// 当有变化时,逐位判断,哪一列的值有变化
if (matrix_change & col_mask)
{
key.keymap = keymaps[row][col];
key.row = row;
key.col = col;
// 确定按键是按下还是抬起状态
if (matrix_row & col_mask)
key.status = KEY_PRESSED;
else
key.status = KEY_UNPRESSED;
matrix_print(active_matrix);
matrix_key_print(&key);
// 记录当前状态变化
matrix_prev[row] ^= col_mask;
}
}
}
}
return;
}
经典矩阵键盘的"鬼影"
经典矩阵键盘,在多按键同时按下时,有时会存在误识别的现象。
具体的表现场景:当矩阵中的一组按键构成四边形时,处于四边形4个顶点的对应按键有三个按键被按下时,则第四个按键也会被检测为导通状态。如图 SW1, SW2, SW3 被按下时,SW4 也会被识别为按下状态。
矩阵键盘在检测 SW4 时,电流会产生如下导通回路,这种现象被称为按键鬼影(ghosting)。按键数量较多时,鬼影现象会变得更明显。
鬼影主要是多按键按下时的异常通路导致,为了避免这种情况产生,我们可以利用二极管的单向导通特性,阻断通路,使矩阵键盘中的按键都可以被独立检测。
在按键侧增加列对行导通方向的二极管,即可阻断异常通路,且不影响正常的按键检测。
二极管会产生压降,从而会降低电路的稳定性,普通二极管为0.7v,肖特基二极管为0.2v左右,在实际应用中,可优先考虑压降小的肖特基二极管。
结论
矩阵键盘在实际使用中,如需要多按键支持,需要使用二极管避免鬼影现象,程序上也需要对多按键进行兼容处理,如此才可以实现一个"全键无冲"版本的矩阵键盘。
其他
多按键扫描相关的程序和原理,本来是想在 proteus 中进行验证,但在实际仿真时,发现程序并没有按照预期的方式在运行。主要有如下两点:
- 无二极管时,矩阵键盘并没有按照预期出现"鬼影"效果,实际表现为相关按键不受控制
- 添加二极管后,单片机无法正常检测到低电平,按键均无法识别。
排查原因未果,而实际又没有相关硬件验证,如上为搜集到的一些资料及推理整理。希望有条件人事可以加以验证。