板载键会了。今天换矩阵键盘:报的是标准按键事件,和那几颗用户键不是一组脚。

按 4×4 键盘,板载 KEY3 那套 event 上没有任何码,keypad 这个 input 设备也不在 /proc/bus/input/devices 里。dmesg 搜 matrix 是空的。不是键盘坏了,是设备树里的 compatible 写了短名 matrix-keypad,主线驱动认的是 gpio-matrix-keypad

驱动文件是 drivers/input/keyboard/matrix_keypad.cof_device_id 只有这一条:

static const struct of_device_id matrix_keypad_dt_match[] = {
    { .compatible = "gpio-matrix-keypad" },
    { }
};

平台驱动的 .name 也叫 matrix-keypad,那是 sysfs 驱动名,不是 DT 匹配串。某些板级 BSP 自己写过一份 compatible = "matrix-keypad" 的私有驱动。主线这份对不上那个短名,probe 直接不发生,行列 GPIO 也不会被申请。你在 dts 里写得再完整,内核当没看见。

行列在 GPIO1 组(外加两根 GPIO3_A 的列线)。超声波、避障在 GPIO3 的 C/D。两套脚不重叠,可以同一份镜像同时 okay。旧稿曾经把超声波写成 GPIO1_D0/D1/D2,那正是键盘的行,两边抢 pinctrl,看起来像「键盘和超声波互斥」。脚号订正之后,这个互斥是假的。

产品 rk3568_evb,板级:

device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dts

1. 矩阵怎么扫,事件从哪来

4 行 × 4 列,16 个交叉点各一颗键。驱动轮流把某一列拉成有效电平,读四根行。哪一行被拉低,就是这一列和那一行的交叉键按下。

        COL0      COL1      COL2      COL3
         │         │         │         │
ROW0 ───●─────────●─────────●─────────●     KEY_1 KEY_2 KEY_3 KEY_A
         │         │         │         │
ROW1 ───●─────────●─────────●─────────●     KEY_4 KEY_5 KEY_6 KEY_B
         │         │         │         │
ROW2 ───●─────────●─────────●─────────●     KEY_7 KEY_8 KEY_9 KEY_C
         │         │         │         │
ROW3 ───●─────────●─────────●─────────●     KEY_0 KEY_F KEY_E KEY_D

每颗键是一个 EV_KEY。上报走 input 子系统,用户态读 /dev/input/eventN。没有 /dev/keypad 这种字符设备。找节点只认:

hdc shell "cat /proc/bus/input/devices"

名字是 keypad 或 dts 节点名,Handlers 里带 eventN。不要去 od 板载 KEY3 那个 adc-keys 的 event,也不要去 od 触摸。

和板载三键的差别:

板载 KEY3/4/5矩阵
物理3 颗独立键16 颗交叉
驱动adc-keys + gpio-keysgpio-matrix-keypad
VIN0 / VIN1 / GPIO0_B0GPIO1 为主
input 名adc-keys-3 / gpio-keyskeypad
键码MENU / BACK / HOMEKEY_0..9、A..F

测矩阵时按 KEY5,桌面回主页,矩阵 event 没反应——正常,你按错键了。


2. 脚在 GPIO1,不要写成超声波那组

Linux 编号:

编号 = bank × 32 + 组内偏移 + 位号
A=0  B=8  C=16  D=24
角色原理图名计算linux
ROW0GPIO1_D41×32+24+460
ROW1GPIO1_C71×32+16+755
ROW2GPIO1_D21×32+24+258
ROW3GPIO1_D01×32+24+056
COL0GPIO3_A13×32+0+197
COL1GPIO1_D31×32+24+359
COL2GPIO1_D11×32+24+157
COL3GPIO3_A23×32+0+298

八根里六根在 GPIO1。COL0/COL3 落到 GPIO3_A1/A2,那是 A 组的 97、98,不是 超声波用的 GPIO3_C6=118、GPIO3_D0=120、GPIO3_D2=122。

hdc shell "cat /sys/kernel/debug/gpio" | grep -E "gpio-(55|56|57|58|59|60|97|98|118|120|122)"

键盘 probe 成功后,55/56/57/58/59/60/97/98 的 consumer 是 matrix-keypad 或行/列标签。118/120/122 若被 hcsr04 / ir-avoidance 占着,两边可以共存。若你看见 56/58 被写成 echo/trig,那是超声波脚号还停在旧稿,键盘会申请失败。

echo 56 > /sys/class/gpio/export 返回 busy:键盘已经占了。这是好现象,说明矩阵在干活。不要为了「手动读 GPIO」去把键盘节点 disabled。


3. 设备树:短名不要写

keypad {
    compatible = "gpio-matrix-keypad";
    pinctrl-names = "default";
    pinctrl-0 = <&GPIO3_A1_pin
                 &GPIO1_D3_pin
                 &GPIO1_D4_pin
                 &GPIO1_C7_pin
                 &GPIO1_D2_pin
                 &GPIO1_D1_pin
                 &GPIO1_D0_pin
                 &GPIO3_A2_pin>;
    debounce-delay-ms = <20>;
    col-scan-delay-us = <400>;

    row-gpios = <&gpio1 RK_PD4 GPIO_ACTIVE_LOW
                 &gpio1 RK_PC7 GPIO_ACTIVE_LOW
                 &gpio1 RK_PD2 GPIO_ACTIVE_LOW
                 &gpio1 RK_PD0 GPIO_ACTIVE_LOW>;

    col-gpios = <&gpio3 RK_PA1 GPIO_ACTIVE_LOW
                 &gpio1 RK_PD3 GPIO_ACTIVE_LOW
                 &gpio1 RK_PD1 GPIO_ACTIVE_LOW
                 &gpio3 RK_PA2 GPIO_ACTIVE_LOW>;

    linux,keymap = <
        MATRIX_KEY(0, 0, KEY_1)
        MATRIX_KEY(0, 1, KEY_2)
        MATRIX_KEY(0, 2, KEY_3)
        MATRIX_KEY(0, 3, KEY_A)
        MATRIX_KEY(1, 0, KEY_4)
        MATRIX_KEY(1, 1, KEY_5)
        MATRIX_KEY(1, 2, KEY_6)
        MATRIX_KEY(1, 3, KEY_B)
        MATRIX_KEY(2, 0, KEY_7)
        MATRIX_KEY(2, 1, KEY_8)
        MATRIX_KEY(2, 2, KEY_9)
        MATRIX_KEY(2, 3, KEY_C)
        MATRIX_KEY(3, 0, KEY_0)
        MATRIX_KEY(3, 1, KEY_F)
        MATRIX_KEY(3, 2, KEY_E)
        MATRIX_KEY(3, 3, KEY_D)
    >;
    gpio-activelow;
    status = "okay";
};

如果预处理器没引 dt-bindings/input/input.hMATRIX_KEY 展不开。可以直接写打包值,格式是:

((row & 0xff) << 24) | ((col & 0xff) << 16) | (keycode & 0xffff)

对应表:

0x00000002  /* r0c0 KEY_1=2 */
0x00010003  /* r0c1 KEY_2=3 */
0x00020004  /* r0c2 KEY_3=4 */
0x0003001E  /* r0c3 KEY_A=30 */
0x01000005  /* r1c0 KEY_4 */
0x01010006
0x01020007
0x01030030  /* KEY_B=48 */
0x02000008
0x02010009
0x0202000A
0x0203002E  /* KEY_C=46 */
0x0300000B  /* KEY_0=11 */
0x03010021  /* KEY_F=33 */
0x03020012  /* KEY_E=18 */
0x03030020  /* KEY_D=32 */

键值与丝印对不上时,先 od 看实际 code,再改 keymap 或改应用显示名。不要先改驱动扫描逻辑。行列接反的症状是:按「1」出来的是另一颗,整行整列错位,用一张纸画交叉核对比改驱动快。

debounce-delay-ms = <20> 滤抖动。col-scan-delay-us = <400> 是列驱动之后等电平稳定。矩阵走 GPIO,没有中断逐键,是扫描。手按住不放,会重复上报,这是 input 的长按,不是故障。

pinctrl 必须把这八根脚强制成 GPIO 功能。只写 row-gpios / col-gpios、脚还停在别的复用上,扫描全是 1 或全是 0。

内核:

CONFIG_KEYBOARD_MATRIX=y
CONFIG_INPUT_MATRIXKMAP=y

缺 INPUT_MATRIXKMAP,keymap 解析失败,probe 中途返回,devices 里仍然没有 keypad。


4. 现场:先证伪短名,再对 event

hdc shell "dmesg | grep -iE 'matrix|keypad'"
hdc shell "cat /proc/bus/input/devices"

短名写错时,dmesg 没有 input: keypad as /devices/...。dts 改成 gpio-matrix-keypad 并刷 resource + boot_linux 之后,会出现类似:

input: keypad as /devices/platform/keypad/input/input0

/proc/bus/input/devices

I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="keypad"
P: Phys=keypad/input0
H: Handlers=kbd event0

event 号以现场为准,可能是 event0,也可能被触摸、adc-keys 挤到 event6。每次开机以 devices 为准,不要写死 event0。

hdc shell "od -An -tx2 -w16 /dev/input/event0"

按「2」,期望:

typecodevalue
0001 EV_KEY0003 KEY_20001 按下
0000 EV_SYN00000000
000100030000 松开

按「B」code 是 0x0030。按板载 KEY3 这个 event 不应动;KEY3 在 adc-keys 那个 handler 上,code 0x8b

一行全亮、邻键误触发:导电膜脏了或 debounce 太小。先加到 50 ms 看还乱不乱,再查硬件。


5. 完整 C

/* keypad_probe.c
 * armv7-unknown-linux-ohos-clang --target=arm-linux-ohos \
 *   --sysroot=${OHOS_NATIVE}/sysroot \
 *   -o keypad_probe keypad_probe.c \
 *   -Wl,--dynamic-linker=/system/lib/ld-musl-arm.so.1
 *
 * hdc file send keypad_probe /data/keypad_probe
 * hdc shell "chmod 755 /data/keypad_probe; /data/keypad_probe"
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <linux/input.h>

static int find_event_by_name(const char *want, char *out, int len)
{
    FILE *f = fopen("/proc/bus/input/devices", "r");
    char line[256], name[128] = {0}, handler[128] = {0};
    if (!f) {
        return -1;
    }
    while (fgets(line, sizeof(line), f)) {
        if (strncmp(line, "N: Name=", 8) == 0) {
            snprintf(name, sizeof(name), "%s", line + 8);
        } else if (strncmp(line, "H: Handlers=", 12) == 0) {
            snprintf(handler, sizeof(handler), "%s", line + 12);
            if (strstr(name, want)) {
                char *p = strstr(handler, "event");
                if (p) {
                    int n = 0;
                    sscanf(p, "event%d", &n);
                    snprintf(out, len, "/dev/input/event%d", n);
                    fclose(f);
                    return 0;
                }
            }
        }
    }
    fclose(f);
    return -1;
}

static const char *key_label(int code)
{
    switch (code) {
    case KEY_1: return "1";
    case KEY_2: return "2";
    case KEY_3: return "3";
    case KEY_4: return "4";
    case KEY_5: return "5";
    case KEY_6: return "6";
    case KEY_7: return "7";
    case KEY_8: return "8";
    case KEY_9: return "9";
    case KEY_0: return "0";
    case KEY_A: return "A";
    case KEY_B: return "B";
    case KEY_C: return "C";
    case KEY_D: return "D";
    case KEY_E: return "E";
    case KEY_F: return "F";
    case KEY_MENU: return "MENU(板载KEY3? 读错event)";
    case KEY_HOME: return "HOME(板载KEY5? 读错event)";
    default: return "?";
    }
}

int main(void)
{
    char path[64];
    if (find_event_by_name("keypad", path, sizeof(path)) < 0) {
        fprintf(stderr, "没有 Name=keypad。查 compatible=gpio-matrix-keypad "
                        "和 CONFIG_KEYBOARD_MATRIX\n");
        return 1;
    }
    printf("open %s\n", path);
    int fd = open(path, O_RDONLY);
    if (fd < 0) {
        perror(path);
        return 2;
    }
    struct input_event ev;
    while (read(fd, &ev, sizeof(ev)) == (ssize_t)sizeof(ev)) {
        if (ev.type != EV_KEY) {
            continue;
        }
        printf("code=0x%x %s value=%d\n",
               ev.code, key_label(ev.code), ev.value);
    }
    close(fd);
    return 0;
}

按名字找 event,不要写死 /dev/input/event0。触摸屏常常先注册,event0 是触摸不是键盘。


6. ArkTS

import evbio from 'libevbio.so';

@Entry
@Component
struct MatrixKeys {
  @State last: string = '按矩阵上的键,不是 KEY3';
  @State grid: string[] = [
    '1', '2', '3', 'A',
    '4', '5', '6', 'B',
    '7', '8', '9', 'C',
    '0', 'F', 'E', 'D'
  ];
  @State hit: string = '';
  private timer: number = -1;
  private map: Record<number, string> = {
    2: '1', 3: '2', 4: '3', 5: '4', 6: '5', 7: '6',
    8: '7', 9: '8', 10: '9', 11: '0',
    30: 'A', 48: 'B', 46: 'C', 32: 'D', 18: 'E', 33: 'F'
  };

  build() {
    Column() {
      Text('4×4 矩阵').fontSize(22).fontWeight(FontWeight.Bold)
      Flex({ wrap: FlexWrap.Wrap }) {
        ForEach(this.grid, (k: string) => {
          Text(k)
            .width('22%')
            .height(48)
            .textAlign(TextAlign.Center)
            .margin(4)
            .backgroundColor(this.hit === k ? '#C62828' : '#EEEEEE')
            .fontSize(20)
        })
      }
      Text(this.last).margin({ top: 12 }).fontSize(14)
    }.padding(16)
  }

  aboutToAppear() {
    let path: string = evbio.findInput('keypad');
    this.last = 'dev=' + path;
    evbio.openKeys(path);
    this.timer = setInterval(() => {
      let arr = evbio.pollKey() as Array<Record<string, number>>;
      if (!arr || arr.length === 0) {
        return;
      }
      let code: number = arr[0]['code'];
      let val: number = arr[0]['value'];
      let lab: string = this.map[code] || ('0x' + code.toString(16));
      if (val) {
        this.hit = lab;
        this.last = '按下 ' + lab + ' code=0x' + code.toString(16);
      } else {
        this.hit = '';
      }
    }, 40);
  }

  aboutToDisappear() {
    if (this.timer >= 0) {
      clearInterval(this.timer);
    }
  }
}

宫格必须给宽度,不要把 Grid 扔进 Scroll 还不设 rowsTemplate,命中区域高度会变成 0,点不动。Flex wrap + 22% 宽足够。

权限:

chmod 0666 /dev/input/event*

7. 刷哪、和超声波的关系

keymap、compatible、GPIO 都在设备树。生效 DTB 在 resource 分区。

hdc shell "cat /proc/partitions"
hdc shell "dd if=/data/resource.img of=/dev/block/mmcblk1p4 bs=4M conv=fsync"
hdc shell "dd if=/data/boot_linux.img of=/dev/block/mmcblk1p5 bs=4M conv=fsync"

of= 必须 /dev/block/...

超声波那组是 GPIO3_C6/D0/D2,和键盘的 GPIO1 + GPIO3_A1/A2 不冲突。同一份镜像可以同时有 /dev/hcsr04 和 Name=keypad。电机类模块才和超声波共 GPIO3_C/D,那是另一对互斥,跟键盘无关。

旧结论「键盘和超声波二选一」来自超声波脚号写成了 GPIO1_D0/D1/D2。那三根是键盘的行。订正超声波之后,把 keypad 的 status 改回 okay。


8. 现象对照

现象原因处理
devices 里没有 keypadcompatible 写成 matrix-keypad改 `gpio-matrix-keypad`
有节点但按键无事件od 错了 event,或脚还在复用按 Name=keypad 找;补 pinctrl
按 1 出来 4 / 整列错位行列顺序或 keymap 行列反了纸上画交叉,改 keymap
按 KEY3 有码、按矩阵没有读的是 adc-keys换 keypad 的 event
export GPIO56 busy键盘已经占着正常,不要 disabled 键盘
和超声波抢脚超声波仍写在 GPIO1_D0/D1/D2超声波改 GPIO3_C/D
probe 失败,keymap 解析错没开 INPUT_MATRIXKMAPdefconfig 补上
flowchart TD
  A[按矩阵无事件] --> B{devices 有 Name=keypad?}
  B -->|无| C{dmesg 有 matrix?}
  C -->|无| D[compatible 改 gpio-matrix-keypad]
  C -->|有但失败| E[INPUT_MATRIXKMAP / GPIO 被占]
  B -->|有| F{od 的 event 是它?}
  F -->|否| G[换 Handlers 里的号]
  F -->|是, 码是 0x8b| H[那是 KEY3, 不是矩阵]
  F -->|是, 码错位| I[改 keymap]

主线只认 gpio-matrix-keypad。行列在 GPIO1(加两根 GPIO3_A),超声波在 GPIO3_C/D,不是同一组。事件只从 Name=keypad 那个 /dev/input/event* 出来。


系列第 25 篇 · 芯片:瑞芯微 RK3568 · OpenHarmony 4.1(API 11) · Linux 5.10

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐