前言
上一章我们介绍了经典矩阵键盘的实现方法,但是示例程序中仅实现了单按键检测功能。虽说单按键已经基本可以覆盖矩阵键盘的常见需求,但在一些特殊应用场合,我们仍然需要多按键识别操作,或者一些类似电脑组合按键的功能支持。
先从示例程序上进行改善,支持多按键检测。
多按键扫描示例程序
经典矩阵键盘是由行列线组成,行线与列线构成一个二维模型,从代码的角度看,类似二维数组。
使用二维数组的每一个值,记录矩阵键盘行列中对应按键的状态,按键按下记录为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 为低时,按下状态,记录为1row_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;elsekey.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 中进行验证,但在实际仿真时,发现程序并没有按照预期的方式在运行。主要有如下两点:
- 无二极管时,矩阵键盘并没有按照预期出现"鬼影"效果,实际表现为相关按键不受控制
- 添加二极管后,单片机无法正常检测到低电平,按键均无法识别。
排查原因未果,而实际又没有相关硬件验证,如上为搜集到的一些资料及推理整理。希望有条件人事可以加以验证。