【按键扫描】矩阵按键之多按键扫描

前言

上一章我们介绍了经典矩阵键盘的实现方法,但是示例程序中仅实现了单按键检测功能。虽说单按键已经基本可以覆盖矩阵键盘的常见需求,但在一些特殊应用场合,我们仍然需要多按键识别操作,或者一些类似电脑组合按键的功能支持。

先从示例程序上进行改善,支持多按键检测。

多按键扫描示例程序

经典矩阵键盘是由行列线组成,行线与列线构成一个二维模型,从代码的角度看,类似二维数组。

使用二维数组的每一个值,记录矩阵键盘行列中对应按键的状态,按键按下记录为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 中进行验证,但在实际仿真时,发现程序并没有按照预期的方式在运行。主要有如下两点:

  1. 无二极管时,矩阵键盘并没有按照预期出现"鬼影"效果,实际表现为相关按键不受控制
  2. 添加二极管后,单片机无法正常检测到低电平,按键均无法识别。

排查原因未果,而实际又没有相关硬件验证,如上为搜集到的一些资料及推理整理。希望有条件人事可以加以验证。


  • 9
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
当使用STM32微控制器来实现矩阵按键扫描时,可以通过以下步骤来完成: 1. 配置GPIO引脚:首先,需要将矩阵按键的行和列连接到STM32的GPIO引脚上。使用CubeMX或手动配置GPIO引脚的输入/输出模式和上拉/下拉电阻。 2. 设置行扫描:将矩阵按键的行设置为输出模式,并将其输出为低电平。这样可以逐行扫描按。 3. 设置列检测:将矩阵按键的列设置为输入模式,并启用上拉电阻。这样可以检测按是否被按下。 4. 扫描按状态:在一个循环中,逐行设置行为低电平,然后检测每一列的状态。如果某个按按下,相应的行和列会形成一个连接,从而检测到按按下。 5. 处理按事件:根据检测到的按状态,可以执行相应的操作或触发事件。例如,可以在按按下时发送一个信号或执行特定的功能。 下面是一个简单的示例代码,用于演示STM32矩阵按键扫描: ```c #include "stm32f4xx.h" #define ROW_NUM 4 #define COL_NUM 4 GPIO_TypeDef* row_ports[ROW_NUM] = {GPIOA, GPIOA, GPIOA, GPIOA}; uint16_t row_pins[ROW_NUM] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3}; GPIO_TypeDef* col_ports[COL_NUM] = {GPIOB, GPIOB, GPIOB, GPIOB}; uint16_t col_pins[COL_NUM] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3}; void matrix_keypad_init() { // 配置行引脚为输出模式 for (int i = 0; i < ROW_NUM; i++) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = row_pins[i]; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(row_ports[i], &GPIO_InitStruct); } // 配置列引脚为输入模式 for (int i = 0; i < COL_NUM; i++) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = col_pins[i]; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(col_ports[i], &GPIO_InitStruct); } } uint8_t matrix_keypad_scan() { uint8_t key = 0; // 逐行扫描按状态 for (int i = 0; i < ROW_NUM; i++) { // 设置当前行为低电平 HAL_GPIO_WritePin(row_ports[i], row_pins[i], GPIO_PIN_RESET); // 检测每一列的状态 for (int j = 0; j < COL_NUM; j++) { if (HAL_GPIO_ReadPin(col_ports[j], col_pins[j]) == GPIO_PIN_RESET) { // 按按下 key = i * COL_NUM + j + 1; // 计算按编号 break; } } // 恢复当前行为高电平 HAL_GPIO_WritePin(row_ports[i], row_pins[i], GPIO_PIN_SET); if (key != 0) { break; } } return key; } int main(void) { // 初始化矩阵按键 matrix_keypad_init(); while (1) { // 扫描按状态 uint8_t key = matrix_keypad_scan(); if (key != 0) { // 处理按事件 // TODO: 根据按编号执行相应的操作 } } } ``` 请注意,以上代码仅为示例,具体的实现可能会根据具体的硬件和需求进行调整。在实际应用中,您可能需要根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值