第 13 篇 输入设备驱动(触摸屏 / 按键)开发详解,Linux input 子系统全解析

目录

[开篇先搞懂:input 子系统到底是什么?解决了什么痛点?](#开篇先搞懂:input 子系统到底是什么?解决了什么痛点?)

大白话定义

[为什么要用 input 子系统?对比我们之前写的私有字符设备驱动,核心优势太明显了](#为什么要用 input 子系统?对比我们之前写的私有字符设备驱动,核心优势太明显了)

[一、input 子系统核心原理,小白必懂的知识点](#一、input 子系统核心原理,小白必懂的知识点)

[1. input 子系统的三层架构](#1. input 子系统的三层架构)

[2. 核心概念:输入事件(Event)](#2. 核心概念:输入事件(Event))

[3. 一次完整的按键事件上报流程](#3. 一次完整的按键事件上报流程)

[4. input 子系统核心 API,小白必须掌握](#4. input 子系统核心 API,小白必须掌握)

[二、实战一:基于 input 子系统的标准按键驱动开发](#二、实战一:基于 input 子系统的标准按键驱动开发)

[1. 设备树配置](#1. 设备树配置)

核心属性讲解

[2. 编译烧录验证](#2. 编译烧录验证)

[3. 自定义按键的驱动实现](#3. 自定义按键的驱动实现)

[三、实战二:GT911 触摸屏驱动开发,RK 平台最常用的触控芯片](#三、实战二:GT911 触摸屏驱动开发,RK 平台最常用的触控芯片)

[1. GT911 核心原理](#1. GT911 核心原理)

[2. 设备树配置](#2. 设备树配置)

[3. 内核配置,开启 GT911 驱动](#3. 内核配置,开启 GT911 驱动)

[4. 验证驱动是否正常工作](#4. 验证驱动是否正常工作)

[四、安卓系统 input 事件的处理流程](#四、安卓系统 input 事件的处理流程)

[五、小白 input 驱动必踩的坑,提前规避](#五、小白 input 驱动必踩的坑,提前规避)

结尾说两句


大家好,我是黒漂技术佬。前几篇我们完成了 GPIO、I2C、SPI 等基础外设的驱动开发,后台很多兄弟反馈了一个核心痛点:

"佬,我自己写的按键驱动,只能在串口里用命令测试,安卓系统根本识别不到,没法用安卓原生的按键事件,更别说触摸屏了,这该怎么解决?"

太懂这种无力感了!之前我们写的字符设备驱动,是我们自己定义的私有协议,安卓系统根本不认识,只能我们自己写 App 调用。而Linux input 子系统,就是解决这个问题的标准答案 ------ 它是 Linux 内核里标准的输入设备框架,所有的按键、触摸屏、鼠标、键盘,全都是基于这个框架开发的,安卓系统原生支持,只要你按标准写驱动,安卓就能自动识别,不用额外做复杂的适配。

今天这篇,我就用大白话给你讲透 Linux input 子系统的核心原理,手把手带你完成标准按键输入驱动 + GT911 触摸屏驱动的完整开发,基于 RK3568 平台,实现安卓系统原生识别,学完你就能适配所有的输入设备。


开篇先搞懂:input 子系统到底是什么?解决了什么痛点?

大白话定义

Linux input 子系统,是内核里一套标准化的输入设备驱动框架,它把所有输入设备(按键、触摸屏、鼠标、键盘、游戏手柄等)的共性抽象出来,给驱动开发者提供了一套标准的 API,给上层应用提供了一套统一的事件接口。

简单说,它就像一个统一的「输入设备翻译官」:

  • 驱动开发者:不用再自己写字符设备、自己定义协议,只需要调用 input 子系统的 API,上报对应的事件就行;
  • 上层应用(包括安卓系统):不用关心底层是什么硬件,只需要读取标准的 input 事件,就能处理所有输入设备的操作,不管是按键、触摸屏还是鼠标,接口完全统一。

为什么要用 input 子系统?对比我们之前写的私有字符设备驱动,核心优势太明显了

  1. 安卓系统原生支持:只要你按 input 子系统的标准上报事件,安卓系统会自动识别,比如按键上报 KEY_POWER,安卓就会响应锁屏 / 亮屏;触摸屏上报坐标,安卓系统就会自动处理触摸事件,不用我们自己写 HAL 层、JNI 去适配,省了 90% 的工作;
  2. 标准化、通用性强:input 子系统是 Linux 内核的标准框架,所有 Linux 发行版、安卓系统全兼容,你写的驱动,换个平台也能直接用;
  3. 开发难度极低:内核已经帮我们做好了字符设备、事件缓冲区、锁机制这些底层逻辑,我们只需要初始化 input 设备,然后上报事件就行,不用再处理复杂的字符设备逻辑;
  4. 调试极其方便 :内核自带了全套的调试工具,比如evtestgetevent,能直接看到设备上报的所有事件,出问题一眼就能定位。

一、input 子系统核心原理,小白必懂的知识点

1. input 子系统的三层架构

input 子系统分为三层,从上到下分别是:

  1. 事件处理层(Event Handler) :最上层,给用户空间提供统一的访问接口,会给每个输入设备在/dev/input/目录下生成对应的设备文件eventX,上层应用就是通过读取这个文件,获取输入事件。我们不用管这一层,内核已经实现好了;
  2. 核心层(Input Core):中间层,承上启下,给下层驱动提供注册 / 注销 input 设备的 API,给上层事件处理层提供事件上报的接口,是整个子系统的核心,我们只需要调用它提供的 API 就行;
  3. 设备驱动层:最下层,也就是我们要写的驱动代码,负责和硬件交互,获取硬件的状态(比如按键按下 / 松开、触摸屏坐标),然后调用核心层的 API,把事件上报上去。

2. 核心概念:输入事件(Event)

input 子系统的核心,就是「事件」。输入设备的所有操作,都会被封装成标准的事件上报给上层,每个事件由 3 个核心属性组成:

  • type(事件类型) :告诉上层,这是什么类型的事件,比如按键事件、坐标事件、同步事件,最常用的有这几个:

    表格

    事件类型 宏定义 作用
    同步事件 EV_SYN 同步事件,用来标记一组事件的结束,每次上报完一组事件,必须上报一个同步事件,告诉上层 "这次的事件上报完了,你可以处理了"
    按键事件 EV_KEY 按键 / 开关事件,比如键盘按键、电源键、触摸屏的按下 / 松开,所有的按键操作都用这个类型
    绝对坐标事件 EV_ABS 绝对坐标事件,触摸屏的 X/Y 坐标、压力值,都用这个类型
    相对坐标事件 EV_REL 相对坐标事件,鼠标的移动、滚轮,用这个类型
  • code(事件码):告诉上层,这个事件具体是哪个按键、哪个坐标。比如 EV_KEY 类型里,code=KEY_POWER 代表电源键,code=KEY_VOLUMEUP 代表音量加键;EV_ABS 类型里,code=ABS_X 代表 X 坐标,code=ABS_Y 代表 Y 坐标;

  • value(事件值):事件的具体值。比如按键事件里,value=1 代表按下,value=0 代表松开;坐标事件里,value 就是具体的坐标数值。

3. 一次完整的按键事件上报流程

比如我们按下电源键,再松开,完整的事件上报流程是这样的:

  1. 按键按下,驱动检测到电平变化;
  2. 驱动调用input_report_key()函数,上报 EV_KEY 事件,code=KEY_POWER,value=1(按下);
  3. 驱动调用input_sync()函数,上报 EV_SYN 同步事件,告诉上层这次事件上报完成;
  4. 按键松开,驱动检测到电平变化;
  5. 驱动调用input_report_key()函数,上报 EV_KEY 事件,code=KEY_POWER,value=0(松开);
  6. 驱动再次调用input_sync()函数,上报同步事件,完成一次完整的按键操作。

安卓系统收到这些事件后,就会自动执行对应的亮屏 / 锁屏操作,完全不用我们自己写上层逻辑,这就是标准框架的好处!

4. input 子系统核心 API,小白必须掌握

Linux 内核给我们提供了一套极其简单的 API,不用我们处理底层逻辑,只需要调用这几个函数,就能完成 input 设备的开发:

表格

API 函数 作用
input_allocate_device() 分配一个 input_dev 结构体,也就是一个输入设备对象
set_bit() 设置 input 设备支持的事件类型和事件码,告诉内核这个设备能上报什么事件
input_register_device() 把分配好的 input 设备注册到内核里,注册成功后,会自动在 /dev/input/ 下生成 event 设备文件
input_report_key() 上报按键事件,最常用
input_report_abs() 上报绝对坐标事件,触摸屏用
input_report_rel() 上报相对坐标事件,鼠标用
input_sync() 上报同步事件,每次上报完一组事件,必须调用这个函数
input_unregister_device() 注销 input 设备,驱动卸载的时候调用
input_free_device() 释放 input 设备的内存,和 input_allocate_device 对应

二、实战一:基于 input 子系统的标准按键驱动开发

我们先从最简单的按键驱动入手,基于之前的硬件接线,用 GPIO0_A1 引脚接按键,实现一个标准的 input 按键驱动,让安卓系统原生识别。

1. 设备树配置

和之前的中断驱动一样,我们只需要在设备树里添加按键节点,配置 GPIO 引脚,这里我们用内核标准的gpio-keys驱动框架,不用自己写复杂的中断逻辑,内核已经帮我们做好了!

对,你没看错,Linux 内核已经自带了标准的 GPIO 按键驱动,我们只需要在设备树里配置好对应的引脚和按键码,内核会自动生成 input 设备,不用我们写一行驱动代码!这就是标准框架的魅力。

修改你的板级.dts 文件,添加下面的节点:

dts

复制代码
/ {
    // 标准GPIO按键节点,用内核自带的gpio-keys驱动
    gpio_keys: gpio-keys {
        compatible = "gpio-keys";  // 必须和内核驱动的compatible匹配
        pinctrl-names = "default";
        status = "okay";
        autorepeat;  // 支持长按重复上报

        // 电源按键
        key_power {
            label = "power-key";  // 按键名称
            gpios = <&gpio0 RK_PA1 GPIO_ACTIVE_LOW>;  // 按键GPIO,低电平有效
            linux,code = <KEY_POWER>;  // 按键码,电源键,安卓原生识别
            debounce-interval = <20>;  // 防抖时间,20ms
            wakeup-source;  // 支持按键唤醒系统
        };

        // 音量加按键
        key_volup {
            label = "volume-up-key";
            gpios = <&gpio0 RK_PA2 GPIO_ACTIVE_LOW>;
            linux,code = <KEY_VOLUMEUP>;
            debounce-interval = <20>;
        };

        // 音量减按键
        key_voldown {
            label = "volume-down-key";
            gpios = <&gpio0 RK_PA3 GPIO_ACTIVE_LOW>;
            linux,code = <KEY_VOLUMEDOWN>;
            debounce-interval = <20>;
        };
    };
};
核心属性讲解
  1. compatible = "gpio-keys":这个是核心,内核自带的 gpio-keys 驱动会匹配这个属性,自动加载驱动;
  2. linux,code = <KEY_POWER>:设置按键的标准键码,这些键码都是 Linux 内核和安卓系统约定好的,安卓系统收到 KEY_POWER 就会处理亮屏锁屏,KEY_VOLUMEUP 就会调节音量加,不用我们写任何上层代码;
  3. debounce-interval = <20>:内核自带的防抖功能,不用我们自己在驱动里写防抖逻辑;
  4. wakeup-source:标记这个按键可以唤醒休眠的系统,电源键必备。

2. 编译烧录验证

  1. 编译设备树,打包 boot.img,烧录到开发板,重启;

  2. 重启完成后,进入 ADB shell,验证 input 设备是否生成: bash

    运行

    复制代码
    adb shell
    su
    ls /dev/input/

    能看到event0event1这类设备文件,说明 input 设备已经生成了;

  3. 查看我们的按键设备: bash

    运行

    复制代码
    cat /proc/bus/input/devices

    能看到我们配置的 gpio-keys 设备,里面有我们设置的 KEY_POWER、KEY_VOLUMEUP 等键码,说明设备注册成功了;

  4. 测试按键功能:用安卓自带的getevent工具,实时查看按键事件:

    bash

    运行

    复制代码
    getevent

    按下按键,终端会实时打印对应的事件,松开也会打印,说明驱动工作正常!同时,按下音量加减键,安卓系统会自动调节音量,按下电源键会锁屏 / 亮屏,完全原生支持,不用我们写一行上层代码!

3. 自定义按键的驱动实现

如果你需要自定义按键,安卓系统没有原生对应的键码,我们可以自己写简单的 input 驱动,实现事件上报,核心代码极其简单:

c

运行

复制代码
// 核心代码片段,完整驱动可以参考之前的中断驱动
static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
    int state = gpio_get_value(key_gpio);
    // 上报按键事件:按下=1,松开=0
    input_report_key(input_dev, KEY_USER1, !state);
    // 必须上报同步事件
    input_sync(input_dev);
    return IRQ_HANDLED;
}

static int __init key_input_init(void)
{
    // 1. 分配input设备
    input_dev = input_allocate_device();
    // 2. 设置设备名称
    input_dev->name = "my-custom-key";
    // 3. 设置支持的事件类型:按键事件+同步事件
    set_bit(EV_KEY, input_dev->evbit);
    set_bit(EV_SYN, input_dev->evbit);
    // 4. 设置支持的按键码
    set_bit(KEY_USER1, input_dev->keybit);
    // 5. 注册input设备
    input_register_device(input_dev);
    // 6. 申请GPIO、注册中断,和之前的中断驱动一样
    // ... 省略GPIO和中断注册代码
    return 0;
}

三、实战二:GT911 触摸屏驱动开发,RK 平台最常用的触控芯片

搞定了按键,我们来看更复杂的触摸屏驱动,RK 平台的开发板,90% 用的都是汇顶的 GT911/GT9271 触控芯片,I2C 接口,我们基于 input 子系统,完成它的驱动适配。

1. GT911 核心原理

GT911 是一款 5 点触控芯片,通过 I2C 接口和主机通信,当有手指触摸屏幕的时候,芯片会把触摸点的 X/Y 坐标、触摸点数、压力值,通过 I2C 接口上报给主机,我们的驱动只需要通过 I2C 读取这些数据,然后通过 input 子系统上报给安卓系统就行。

2. 设备树配置

GT911 的驱动,内核已经自带了,我们只需要在设备树里添加对应的节点,配置好 I2C 引脚、中断引脚、复位引脚,就能直接用,不用自己写驱动代码!

修改你的板级.dts 文件,在 I2C1 节点里添加 GT911 设备节点:

dts

复制代码
&i2c1 {
    status = "okay";
    clock-frequency = <400000>;
    pinctrl-names = "default";
    pinctrl-0 = <&i2c1_xfer>;

    // GT911触摸屏节点
    gt911: gt911@5d {
        compatible = "goodix,gt911";  // 和内核驱动匹配
        reg = <0x5d>;  // GT911的I2C地址,一般是0x5d或者0x14
        status = "okay";
        // 中断引脚,触摸屏有触摸的时候,会触发中断
        interrupt-parent = <&gpio0>;
        interrupts = <RK_PB4 IRQ_TYPE_EDGE_FALLING>;
        // 复位引脚
        reset-gpios = <&gpio0 RK_PB5 GPIO_ACTIVE_LOW>;
        // 触摸屏分辨率,根据你的屏幕修改
        touchscreen-size-x = <800>;
        touchscreen-size-y = <1280>;
        // 最大支持的触摸点数
        touchscreen-max-touch = <5>;
    };
};

// 中断引脚的pinctrl配置
&pinctrl {
    gt911 {
        gt911_irq_pin: gt911-irq-pin {
            rockchip,pins = <0 RK_PB4 RK_FUNC_GPIO &pcfg_pull_up>;
        };
    };
};

3. 内核配置,开启 GT911 驱动

默认情况下,RK SDK 的内核已经开启了 GT911 驱动,如果没开启,我们只需要修改内核配置文件,开启对应的配置:

  1. 进入内核目录,执行菜单配置: bash

    运行

    复制代码
    cd kernel
    make ARCH=arm64 menuconfig
  2. 进入以下路径,开启 GT911 驱动: plaintext

    复制代码
    Device Drivers  --->
        Input device support  --->
            Touchscreens  --->
                <*> Goodix I2C touchscreen driver
  3. 保存配置,重新编译内核,打包 boot.img,烧录到开发板。

4. 验证驱动是否正常工作

  1. 开发板重启后,进入 ADB shell,查看 input 设备: bash

    运行

    复制代码
    cat /proc/bus/input/devices

    能看到 Goodix GT911 的 input 设备,说明驱动加载成功了;

  2. 用 getevent 测试触摸事件: bash

    运行

    复制代码
    getevent

    用手指触摸屏幕,终端会实时打印 X/Y 坐标的 ABS 事件,说明驱动正常上报触摸坐标;

  3. 安卓系统验证:重启后,安卓系统会自动识别触摸屏,你可以直接用触摸操作安卓系统,完全不用做任何上层适配!


四、安卓系统 input 事件的处理流程

很多小白会问:为什么我们按标准上报事件,安卓系统就能自动处理?这里给大家简单讲一下安卓的 input 事件处理流程,让你知其然更知其所以然:

  1. 我们的驱动通过 input 子系统,把事件上报到/dev/input/eventX设备文件;
  2. 安卓系统的EventHub服务,会监听所有的 input 设备文件,读取上报的事件;
  3. InputReader服务,会把读取到的原始事件,处理成安卓系统能识别的输入事件;
  4. InputDispatcher服务,把事件分发给当前焦点的 App,或者系统的按键处理服务;
  5. 系统服务收到电源键、音量键的事件后,会自动执行对应的系统操作,App 收到触摸事件后,会更新 UI。

整个流程,安卓系统已经完全实现好了,我们只需要按 input 子系统的标准,正确上报事件就行,不用关心上层的处理逻辑,这就是标准化的好处。


五、小白 input 驱动必踩的坑,提前规避

  1. 坑 1:上报完事件,没有调用 input_sync () 同步 这是新手最常犯的错误,上报完按键 / 坐标事件后,没有上报同步事件,上层应用收不到事件,或者事件错乱。记住:每次上报完一组事件,必须调用input_sync(),告诉内核这次事件上报完成了。
  2. 坑 2:没有设置对应的事件类型和事件码 比如你要上报按键事件,但是没有用set_bit(EV_KEY, input_dev->evbit)设置支持按键事件,或者没有设置对应的按键码,事件上报了,上层也收不到。必须先告诉内核,你的设备支持什么事件,才能上报对应的事件。
  3. 坑 3:按键防抖没做好,多次触发事件 机械按键有抖动,会导致一次按下,多次上报事件。解决方法:要么用 gpio-keys 驱动自带的debounce-interval属性,要么在中断里做防抖处理,20ms 以内的重复中断直接忽略。
  4. 坑 4:触摸屏坐标不对,触摸方向反了 设备树里的touchscreen-size-x/touchscreen-size-y和屏幕实际分辨率不匹配,或者 X/Y 轴搞反了。解决方法:修改设备树里的分辨率,或者在驱动里调整 X/Y 坐标的交换、翻转。
  5. 坑 5:中断触发类型不对,触摸屏没反应GT911 触摸屏的中断是下降沿触发,如果你配置成了上升沿,就会导致有触摸的时候,中断不触发,驱动读不到坐标。解决方法:根据芯片 datasheet,配置正确的中断触发类型。

结尾说两句

这篇文章,我们彻底搞懂了 Linux input 子系统的核心原理,完成了标准按键驱动和 GT911 触摸屏驱动的开发,实现了安卓系统的原生识别和适配。掌握了 input 子系统,你就能适配所有的输入设备,不管是按键、触摸屏、键盘还是鼠标,都是一样的逻辑,一通百通。

下一篇,我们进入显示驱动的内容,手把手带你完成 MIPI/LVDS 屏的适配与调试,详解 DRM 显示框架,解决新手最头疼的 "屏点不亮、花屏、闪屏" 问题,教你怎么在 RK 平台上适配各种屏幕。

我是黒漂技术佬,关注我,带你零基础入门 RK 安卓驱动开发,不踩坑。有任何 input 驱动的问题,评论区留言,我都会一一回复。

相关推荐
ego.iblacat2 小时前
Nginx 性能优化与深度监控
运维·nginx·性能优化
bukeyiwanshui2 小时前
【无标题】
linux·运维·服务器
疯狂吧小飞牛2 小时前
Linux 多网卡同网段配置冲突问题
linux·运维·服务器
学习3人组2 小时前
Workerman实现 WSS 基于客户端 ID 的精准推送
android·java·开发语言
阿拉斯攀登2 小时前
第 11 篇 RK 平台安卓驱动实战 4:I2C 设备驱动开发,以 OLED 屏为例
android·驱动开发·i2c·瑞芯微·嵌入式驱动·rk3576·嵌入式安卓
阿拉斯攀登2 小时前
第 9 篇 RK 平台安卓驱动实战 2:中断驱动开发,按键中断的完整实现
驱动开发·嵌入式硬件·rk3568·中断·瑞芯微·rk3576·rk安卓驱动
-ONLY-¥2 小时前
Nginx性能优化与监控全攻略
linux
物联网全栈工程猪2 小时前
CAN 总线传输策略设计:基于 CAN ID 优先级竞争与本节点低优先级事件让步
运维·服务器·网络
J超会运2 小时前
OpenEuler系统Nginx性能优化全攻略
运维·nginx·性能优化