矩阵键盘GPIO驱动—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
板载键会了。今天换矩阵键盘:报的是标准按键事件,和那几颗用户键不是一组脚。
按 4×4 键盘,板载 KEY3 那套 event 上没有任何码,keypad 这个 input 设备也不在 /proc/bus/input/devices 里。dmesg 搜 matrix 是空的。不是键盘坏了,是设备树里的 compatible 写了短名 matrix-keypad,主线驱动认的是 gpio-matrix-keypad。
驱动文件是 drivers/input/keyboard/matrix_keypad.c,of_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-keys | gpio-matrix-keypad |
| 脚 | VIN0 / VIN1 / GPIO0_B0 | GPIO1 为主 |
| input 名 | adc-keys-3 / gpio-keys | keypad |
| 键码 | MENU / BACK / HOME | KEY_0..9、A..F |
测矩阵时按 KEY5,桌面回主页,矩阵 event 没反应——正常,你按错键了。
2. 脚在 GPIO1,不要写成超声波那组
Linux 编号:
编号 = bank × 32 + 组内偏移 + 位号
A=0 B=8 C=16 D=24
| 角色 | 原理图名 | 计算 | linux |
|---|---|---|---|
| ROW0 | GPIO1_D4 | 1×32+24+4 | 60 |
| ROW1 | GPIO1_C7 | 1×32+16+7 | 55 |
| ROW2 | GPIO1_D2 | 1×32+24+2 | 58 |
| ROW3 | GPIO1_D0 | 1×32+24+0 | 56 |
| COL0 | GPIO3_A1 | 3×32+0+1 | 97 |
| COL1 | GPIO1_D3 | 1×32+24+3 | 59 |
| COL2 | GPIO1_D1 | 1×32+24+1 | 57 |
| COL3 | GPIO3_A2 | 3×32+0+2 | 98 |
八根里六根在 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.h,MATRIX_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」,期望:
| type | code | value |
|---|---|---|
| 0001 EV_KEY | 0003 KEY_2 | 0001 按下 |
| 0000 EV_SYN | 0000 | 0000 |
| 0001 | 0003 | 0000 松开 |
按「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 里没有 keypad | compatible 写成 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_MATRIXKMAP | defconfig 补上 |
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
更多推荐



所有评论(0)