【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第六十六章 电容屏触摸驱动实验

news2024/9/9 0:50:16

i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码,H.264、H.265、VP8、VP9视频硬解码,并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78+Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩,物联网,工业控制,医疗,智能交通等,可用于任何通用工业和物联网应用、

【公众号】迅为电子

【粉丝群】258811263(加群获取驱动文档+例程)


六十六电容屏触摸驱动实验

如今触摸屏的使用越来越广泛,从手机、平板到蜂巢取货等场合,都是用了触摸屏,触摸屏的使用非

常便捷高效。在本章就来学习一下如何在 Linux 下编写电容触摸屏驱动。我们自己写一个触摸芯片ft5x06的驱动,实际上这个驱动也是可以在触摸芯片ft5426上来使用的,这两个触摸芯片是兼容的,通过这个触摸驱动实验,我们可以将以前学习过的知识进行框架的搭建,对以前学习的知识进行复习。

本章内容对应视频讲解链接(在线观看):

FT5X06触摸驱动实验 (一)  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=48

FT5X06触摸驱动实验(二)  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=49

触摸校准实验  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=50

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\022-电容屏触摸驱动实验\42touchscreen”路径下。

66.1 Linux 下电容触摸屏驱动框架

66.1.1 多点触摸协议详解

电容触摸屏驱动其实就是一下几种 linux 驱动框架的组合:

① IIC 设备驱动,因为电容触摸 IC 基本都是 IIC 接口的,因此大框架就是 IIC 设备驱动。

② 通过中断引脚(INT)向 linux 内核上报触摸信息,因此需要用到 linux 中断驱动框架。坐标的上报在中断服务函数中完成。

③ 触摸屏的坐标信息、屏幕按下和抬起信息都属于 linux 的 input 子系统,因此向 linux 内核上报触摸屏坐标信息就得使用 input 子系统。只是,我们得按照 linux 内核规定的规则来上报坐标信息。

在上面的驱动框架组合中我们发现 I2C 驱动、中断驱动、input 子系统都已经学习了解过了,还没有学习过 input 子系统下的多点电容触摸协议,这个就是本章学习的重点,linux 内核中有一份文档详细的讲解了多点电容触摸屏协议,文档路径为:Documentation/input/multitouch-protocol.txt

MT 协议被分为两种类型,TypeA 和 TypeB,这两种类型的区别如下:

TypeA:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据(此类型在实际使用中非常少!)。

Type B:适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过 slot 更新某一个触摸点的信息,FT5426 就属于此类型,一般的多点电容触摸屏 IC 都有此能力。

触摸点的信息通过一系列的 ABS_MT 事件(有的资料也叫消息)上报给 linux 内核,只有 ABS_MT 事件

是用于多点触摸的,ABS_MT 事件定义在文件 linux/input.h 中,相关事件如下所示:

852 #define ABS_MT_SLOT 0x2f /* MT slot being modified */

853 #define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */

854 #define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */

855 #define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */

856 #define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */

857 #define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */

858 #define ABS_MT_POSITION_X 0x35 /* Center X touch position */

859 #define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */

860 #define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */

861 #define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */

862 #define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */

863 #define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */

864 #define ABS_MT_DISTANCE 0x3b /* Contact hover distance */

865 #define ABS_MT_TOOL_X 0x3c /* Center X tool position */

866 #define ABS_MT_TOOL_Y 0x3d /* Center Y tool position */

在上面这些众多的 ABS_MT 事件中,我们最常用的就是 ABS_MT_SLOT 、 ABS_MT_POSITION_X 、ABS_MT_POSITION_Y 和 ABS_MT_TRACKING_ID 。其中 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 用

来上报触摸点的 (X,Y) 坐标信息,ABS_MT_SLOT 用来上报触摸点 ID ,对于 Type B 类型的设备,需要用到

ABS_MT_TRACKING_ID 事件来区分触摸点。

对于 TypeA 类型的设备,通过 input_mt_sync()函数来隔离不同的触摸点数据信息,此函数原型如下所

示:

void input_mt_sync(struct input_dev *dev)

此函数只要一个参数,类型为 input_dev,用于指定具体的 input_dev 设备。input_mt_sync()函数会触

发 SYN_MT_REPORT 事件,此事件会通知接收者获取当前触摸数据,并且准备接收下一个触摸点数据。

对于 Type B 类型的设备,上报触摸点信息的时候需要通过 input_mt_slot()函数区分是哪一个触摸点,

input_mt_slot()函数原型如下所示:

void input_mt_slot(struct input_dev *dev, int slot)

此函数有两个参数,第一个参数是 input_dev 设备,第二个参数 slot 用于指定当前上报的是哪个触摸

点信息。input_mt_slot()函数会触发 ABS_MT_SLOT 事件,此事件会告诉接收者当前正在更新的是哪个触摸

点(slot)的数据。

不管是哪个类型的设备,最终都要调用 input_sync()函数来标识多点触摸信息传输完成,告诉接收者处

理之前累计的所有消息,并且准备好下一次接收。Type B 和 Type A 相比最大的区别就是 Type B 可以区分

出触摸点, 因此可以减少发送到用户空间的数据。Type B 使用 slot 协议区分具体的触摸点,slot 需要用

到 ABS_MT_TRACKING_ID 消息,这个 ID 需要硬件提供,或者通过原始数据计算出来。对于 TypeA 设备,

内核驱动需要一次性将触摸屏上所有的触摸点信息全部上报,每个触摸点的信息在本次上报事件流中的顺

序不重要,因为事件的过滤和手指(触摸点)跟踪是在内核空间处理的。

Type B 设备驱动需要给每个识别出来的触摸点分配一个 slot,后面使用这个 slot 来上报触摸点信息。

可以通过 slot 的 ABS_MT_TRACKING_ID 来新增、替换或删除触摸点。一个非负数的 ID 表示一个有效的触

摸点,-1 这个 ID 表示未使用 slot。一个以前不存在的 ID 表示这是一个新加的触摸点,一个 ID 如果再也

不存在了就表示删除了。

有些设备识别或追踪的触摸点信息要比他上报的多,这些设备驱动应该给硬件上报的每个触摸点分配

一个 Type B 的 slot。一旦检测到某一个 slot 关联的触摸点 ID 发生了变化,驱动就应该改变这个 slot 的

ABS_MT_TRACKING_ID,使这个 slot 失效。如果硬件设备追踪到了比他正在上报的还要多的触摸点,那么

驱动程序应该发送 BTN_TOOL_*TAP 消息,并且调用 input_mt_report_pointer_emulation()函数,将此函数的

第二个参数 use_count 设置为 false。

66.1.2 多点电容触摸驱动框架

本小节我们来梳理一下 linux 下多点电容

触摸驱动的编写框架和步骤。首先确定驱动需要用到哪些知识点,哪些框架?根据前面的分析,我们在编写驱动的时候需要注意以下几点:

① 多点电容触摸芯片的接口,一般都为 I2C 接口,因此驱动主框架肯定是 I2C。

② 当我们手点击屏幕的时候,首先屏幕会发生触摸中断,然后我们就要在中断服务函数里面启动中断下文,因为我们要做的事情非常耗时间,所以我们要在中断下文里面做这些事情 。linux 里面一般都是通过中断来上报触摸点坐标信息,因此需要用到中断框架。那么我们要使用中断下文,我们之前讲了实现中断下文的两种方法,第一种是tasklet,第二种是工作队列,我们这里要使用哪一种呢?因为我们在使用i2c_transfer进行读写的时候可能会发生休眠,所以说我们要用工作队列。

③ 多点电容触摸属于 input 子系统,因此还要用到 input 子系统框架。

④ 在中断下文里面使用i2c读数据并且通过输入子系统上报数据。

如下图所示,可以形象的了解这一过程。

根据上面的分析,多点电容触摸驱动编写框架以及步骤如下:

1 、I2C 驱动框架

驱动总体采用 I2C 框架,参考框架代码如下所示:

/* 设备树匹配表 */
static const struct i2c_device_id xxx_ts_id[] = {
    {
        "xxx",
        0,
    },
    {/* sentinel */}};

/* 设备树匹配表 */
static const struct of_device_id xxx_of_match[] = {
    {
        .compatible = "xxx",
    },
    {/* sentinel */}};

/* i2c 驱动结构体 */
static struct i2c_driver ft5x06_ts_driver = {
    .driver = {
        .owner = THIS_MODULE,
        .name = "edt_ft5x06",
        .of_match_table = of_match_ptr(xxx_of_match),
    },
    .id_table = xxx_ts_id,
    .probe = xxx_ts_probe,
    .remove = xxx_ts_remove,
};

/*
 * @description : 驱动入口函数
 * @param : 无
 * @return : 无
 */
static int __init xxx_init(void)
{
    int ret = 0;
    ret = i2c_add_driver(&xxx_ts_driver);
    return ret;
}

/*
 * @description : 驱动出口函数
 * @param : 无
 * @return : 无
 */
static void __exit xxx_exit(void)
{
    i2c_del_driver(&ft5x06_ts_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");

 当设备树中触摸 IC 的设备节点和驱动匹配以后,第 21 行的 xxx_ts_probe 函数就会执行,我们可以

在此函数中初始化触摸 IC,中断和 input 子系统等。

2初始化触摸 IC和 、中断和 input 子系统

初始化操作都是在 xxx_ts_probe 函数中完成,参考框架如下所示:

1 static int xxx_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
2 {
3 struct input_dev *input;
4
5 /* 1、初始化 I2C */
6 ......
7
8 /* 2,申请中断, */
9       devm_request_threaded_irq(&client->dev, client->irq, NULL,
10      xxx_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
11      client->name, &xxx);
12 ......
13
14 /* 3,input 设备申请与初始化 */
15      input = devm_input_allocate_device(&client->dev);
16
17      input->name = client->name;
18      input->id.bustype = BUS_I2C;
19      input->dev.parent = &client->dev;
20 ......
21
22 /* 4,初始化 input 和 MT */
23      __set_bit(EV_ABS, input->evbit);
24      __set_bit(BTN_TOUCH, input->keybit);
25
26      input_set_abs_params(input, ABS_X, 0, width, 0, 0);
27      input_set_abs_params(input, ABS_Y, 0, height, 0, 0);
28      input_set_abs_params(input, ABS_MT_POSITION_X,0, width, 0, 0);
29      input_set_abs_params(input, ABS_MT_POSITION_Y,0, height, 0, 0);
30      input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0);
31 ......
32
33 /* 5,注册 input_dev */
34      input_register_device(input);
35 ......
36 }
  • 第 5~7 行,首先肯定是初始化触摸芯片,包括芯片的相关 IO,比如复位、中断等 IO 引脚,然后就是

芯片本身的初始化,也就是配置触摸芯片的相关寄存器。

  • 第 9 行,因为一般触摸芯片都是通过中断来向系统上报触摸点坐标信息的,因此我们需要初始化中断。

大家可能会发现第 9 行并没有使用 request_irq 函数申请中断,而是采用了 devm_request_threaded_irq 这

个函数,为什么使用这个函数呢?是不是 request_irq 函数不能使用?答案肯定不是的,这里用 request_irq

函数是绝对没问题的。那为何要用 devm_request_threaded_irq 呢?这里我们就简单的介绍一下这个 API

函数,devm_request_threaded_irq 函数特点如下:

① 用于申请中断,作用和 request_irq 函数类似。

② 此函数的作用是中断线程化,大家如果直接在网上搜索“devm_request_threaded_irq”会发现相关

解释很少。但是大家去搜索 request_threaded_irq 函数就会有很多讲解的博客和帖子,这两个函数在名字上的差别就是前者比后者多了个“devm_”前缀,“devm_”前缀稍后讲解。大家应该注意到了“request_threaded_irq”相比“request_irq”多了个 threaded 函数,也就是线程的意思。那么为什么要中

断线程化呢?我们都是知道硬件中断具有最高优先级,不论什么时候只要硬件中断发生,那么内核都会终

止当前正在执行的操作,转而去执行中断处理程序(不考虑关闭中断和中断优先级的情况),如果中断非常频

繁的话那么内核将会频繁地执行中断处理程序,导致任务得不到及时的处理。中断线程化以后中断将作为

内核线程运行,而且也可以被赋予不同的优先级,任务的优先级可能比中断线程的优先级高,这样做的目

的就是保证高优先级的任务能被优先处理。大家可能会疑问,前面不是说可以将比较耗时的中断放到下半

部(bottom half)处理吗?虽然下半部可以被延迟处理,但是依旧先于线程执行,中断线程化可以让这些比较

耗时的下半部与进程进行公平竞争。要注意,并不是所有的中断都可以被线程化,重要的中断就不能这么操作。对于触摸屏而言只要手指放到屏幕上,它可能就会一直产生中断(视具体芯片而定,FT5426 是这样的),中断处理程序里面需要通过 I2C读取触摸信息并上报给内核,I2C 的速度最大只有 400KHz,算是低速外设。不断的产生中断、读取触摸信息、上报信息会导致处理器在触摸中断上花费大量的时间,但是触摸相对来说不是那么重要的事件,因此可以将触摸中断线程化。如果你觉得触摸中断很重要,那么就可以不将其进行线程化处理。总之,要不要将一个中断进行线程化处理是需要自己根据实际情况去衡量的。

③ 最后来看一下“devm_”前缀,在 linux 内核中有很多的申请资源类的 API 函数都有对应的“devm_”

前缀版本。比如 devm_request_irq 和 request_irq 这两个函数,这两个函数都是申请中断的,我们使用request_irq 函数申请中断的时候,如果驱动初始化失败的话就要调用 free_irq 函数对申请成功的 irq 进行

释放,卸载驱动的时候也需要我们手动调用 free_irq 来释放 irq。假如我们的驱动里面申请了很多资源,比

如:gpio、irq、input_dev,那么就需要添加很多 goto 语句对其做处理,当这样的标签多了以后代码看起来

就不整洁了。“devm_”函数就是为了处理这种情况而诞生的,“devm_”函数最大的作用就是:

使用“devm_”前缀的函数申请到的资源可以由系统自动释放,不需要我们手动处理。 如果我们使用

devm_request_threaded_irq 函数来申请中断,那么就不需要我们再调用 free_irq 函数对其进行释放。大家

可以注意一下,带有“devm_”前缀的都是一些和设备资源管理有关的函数。关于“devm_”函数的实现原

理这里就不做详细的讲解了,我们的重点在于学会如何使用这些 API 函数,感兴趣的可以查阅一些其他文

档或者帖子来看一下“devm_”函数的实现原理。

  • 第 15 行,接下来就是申请 input_dev,因为多点电容触摸属于 input 子系统。这里同样使用devm_input_allocate_device 函数来申请 input_dev,也就是我们前面讲解的 input_allocate_device 函数加“devm_”前缀版本。申请到 input_dev 以后还需要对其进行初始化操作。
  • 第 23~24 行,设置 input_dev 需要上报的事件为 EV_ABS 和 BTN_TOUCH,因为多点电容屏的触摸坐

标为绝对值,因此需要上报 EV_ABS 事件。触摸屏有按下和抬起之分,因此需要上报 BTN_TOUCH 按 键。

  • 第 26~29 行,调用 input_set_abs_params 函数设置 EV_ABS 事件需要上报 ABS_X、ABS_Y、

ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y。单点触摸需要上报 ABS_X 和 ABS_Y,对于多点触摸需   要上报 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y。

  • 第 30 行,调用 input_mt_init_slots 函数初始化多点电容触摸的 slots。
  • 第 34 行,调用 input_register_device 函数系统注册前面申请到的 input_dev。

3 、上报坐标信息

最后就是在中断服务程序中上报读取到的坐标信息,根据所使用的多点电容触摸设备类型选择使用

TypeA 还是 Type B 时序。由于大多数的设备都是 Type B 类型,因此这里就以 Type B 类型为例讲解一下上报过程,参考驱动框架如下所示:

1 static irqreturn_t xxx_handler(int irq, void *dev_id)
2 {
3
4       int num; /* 触摸点数量 */
5       int x[n], y[n]; /* 保存坐标值 */
6
7 /* 1、从触摸芯片获取各个触摸点坐标值 */
8 ......
9
10 /* 2、上报每一个触摸点坐标 */
11 for (i = 0; i < num; i++) {
12      input_mt_slot(input, id);
13      input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
14      input_report_abs(input, ABS_MT_POSITION_X, x[i]);
15      input_report_abs(input, ABS_MT_POSITION_Y, y[i]);
16 }
17 ......
18
19      input_sync(input);
20 ......
21
22      return IRQ_HANDLED;
23 }

进入中断处理程序以后首先肯定是从触摸 IC 里面读取触摸坐标以及触摸点数量,假设触摸点数量保存

到 num 变量,触摸点坐标存放到 x,y 数组里面。

  • 第 11~16 行,循环上报每一个触摸点坐标,一定要按照 Type B 类型的时序进行。
  • 第 19 行,每一轮触摸点坐标上报完毕以后就调用一次 input_sync 函数发送一个 SYN_REPORT 事件。

关于多点电容触摸驱动框架就讲解到这里,接下来我们就实际编写一个多点电容触摸驱动程序。

66.2 电容屏触摸驱动实验

66.2.1 硬件原理图

在本实验中使用迅为的 7 寸LVDS屏为例,使用的是 FT5426 触摸芯片。

从原理图中得知,7 寸屏使用 I2C1,触摸屏复位引脚为LCD_RST_H,中断引脚为 TOUCH_INT_L_3V3。

66.2.2 编写触摸驱动

我们拷贝第39章编写的驱动代码和Makefile,在此基础上进行修改。

因为我们要写的是触摸芯片ft5x06的驱动,先来了解一下ft5x06的模式设置,打开触摸芯片ft5x06的数据手册,如下图所示,我们可以找到对应寄存器的地址。

 

完整的代码如下所示: 

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/workqueue.h>

#define DEVICE_MODE 0x00
#define ID_G_MODE 0xa4
int irq;
static struct device_node *ft5x06_device_node;
static struct input_dev *ft5x06_dev;
static struct i2c_client *ft5x06_client;
static int ft5x06_read_reg(u8 reg_addr);
static void ft5x06_write_reg(u8 reg_addr, u8 data, u8 len);
static void ft5x06_func(struct work_struct *work);
DECLARE_WORK(ft5x06_work, ft5x06_func);
//写寄存器函数
static void ft5x06_write_reg(u8 reg_addr, u8 data, u8 len)
{
    u8 buff[256];

    struct i2c_msg msgs[] = {
        [0] = {
            .addr = ft5x06_client->addr,
            .flags = 0,
            .len = len + 1,
            .buf = buff,
        }

    };
    buff[0] = reg_addr;
    memcpy(&buff[1], &data, len);

    i2c_transfer(ft5x06_client->adapter, msgs, 1);
}
//读寄存器函数
static int ft5x06_read_reg(u8 reg_addr)
{
    u8 data;

    struct i2c_msg msgs[] = {
        [0] = {
            .addr = ft5x06_client->addr,
            .flags = 0,
            .len = sizeof(reg_addr),
            .buf = &reg_addr,
        },
        [1] = {
            .addr = ft5x06_client->addr,
            .flags = 1,
            .len = sizeof(data),
            .buf = &data,
        },
    };

    i2c_transfer(ft5x06_client->adapter, msgs, 2);

    return data;
}

static void ft5x06_func(struct work_struct *work)
{
    int TOUCH1_XH, TOUCH1_XL, x;
    int TOUCH1_YH, TOUCH1_YL, y;
    int TD_STATUS;

    //读取TOUCH1_XH寄存器的值
    TOUCH1_XH = ft5x06_read_reg(0x03);
    //读取TOUCH1_XL寄存器的值
    TOUCH1_XL = ft5x06_read_reg(0x04);
    //获取X的坐标值
    x = ((TOUCH1_XH << 8) | TOUCH1_XL) & 0x0fff;

    //读取TOUCH1_YH寄存器的值
    TOUCH1_YH = ft5x06_read_reg(0x05);
    //读取TOUCH1_YL寄存器的值
    TOUCH1_YL = ft5x06_read_reg(0x06);
    //获取Y的坐标值
    y = ((TOUCH1_YH << 8) | TOUCH1_YL) & 0x0fff;

    //读取寄存器TD_STATUS的值
    TD_STATUS = ft5x06_read_reg(0x02);

    TD_STATUS = TD_STATUS & 0xf; //获取有没有手指在屏幕上

    if (TD_STATUS == 0)
    { 
        //判断有没有手指按上,如果有的话就要上报按下去的事件,没有就要上报抬手事件
        input_report_key(ft5x06_dev, BTN_TOUCH, 0);
        input_sync(ft5x06_dev);
    }
    else
    {
        input_report_key(ft5x06_dev, BTN_TOUCH, 1);
        input_report_abs(ft5x06_dev, ABS_X, x);
        input_report_abs(ft5x06_dev, ABS_Y, y);
        input_sync(ft5x06_dev);
    }
}
//中断处理函数
static irqreturn_t ft5x06_handler(int irq, void *args)
{
    //printk("This is ft5x06_handler\n");
    //调度工作队列
    schedule_work(&ft5x06_work);
    return IRQ_RETVAL(IRQ_HANDLED);
}
/* i2c 驱动的 probe 函数 */
int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret;
    int ft5x05_irq_gpio;
    int ft5x05_reset_gpio;

    printk("This is ft5x06_probe\n");
    //因为我们要在别的函数里面用到这个client,所以我们要把他复制出来。
    ft5x06_client = client; 

    //获得触摸芯片的节点
    ft5x06_device_node = of_find_node_by_path("/i2c@ff110000/ft5x06@38");
    if (ft5x06_device_node == NULL)
    {
        printk("of_find_node_by_path is error\n");
        return -1;
    }
    //打印节点的名字
    printk("ft5x06_device_node is %s\n", ft5x06_device_node->name);

    //获得中断引脚的GPIO标号
    ft5x05_irq_gpio = of_get_named_gpio(ft5x06_device_node, "irq-gpios", 0);
    if (ft5x05_irq_gpio < 0)
    {
        printk("ft5x05_irq_gpio of_get_named_gpio is error\n");
        return -2;
    }
    //获得复位引脚的GPIO标号
    ft5x05_reset_gpio = of_get_named_gpio(ft5x06_device_node, "reset-gpios", 0);
    if (ft5x05_reset_gpio < 0)
    {
        printk("ft5x05_reset_gpio of_get_named_gpio is error\n");
        return -3;
    }
    // 打印中断引脚的GPIO标号
    printk("ft5x05_irq_gpio is %d\n", ft5x05_irq_gpio);
    //打印复位引脚的GPIO标号
    printk("ft5x05_reset_gpio is %d\n", ft5x05_reset_gpio);
    //在申请中断之前将GPIO释放掉
    gpio_free(ft5x05_irq_gpio);
    //申请中断引脚的GPIO
    ret = gpio_request(ft5x05_irq_gpio, "touch-gpio");

    if (ret < 0)
    {
        printk("gpio_request is error\n");
        return -4;
    }

    gpio_free(ft5x05_reset_gpio);
    //申请复位引脚的GPIO
    ret = gpio_request(ft5x05_reset_gpio, "reset_gpio");
    if (ret < 0)
    {
        printk("ft5x05_reset_gpio is error\n");
        return -5;
    }

    //把中断的引脚设置为输入
    gpio_direction_input(ft5x05_irq_gpio);
    //设置复位引脚的方向为输出,然后输出高电平,停止复位
    gpio_direction_output(ft5x05_reset_gpio, 0);
    msleep(5);
    gpio_set_value(ft5x05_reset_gpio, 1);

    //获得中断号
    irq = gpio_to_irq(ft5x05_irq_gpio);
    //申请中断
    ret = request_irq(irq, ft5x06_handler, IRQ_TYPE_EDGE_FALLING | IRQF_ONESHOT, "ft5x06_irq", NULL);
    if (ret < 0)
    {
        printk("request_irq is error\n");
        goto error_request_irq;
    }

    //设置工作模式为正常模式
    ft5x06_write_reg(DEVICE_MODE, 0, 1);
    ft5x06_write_reg(ID_G_MODE, 1, 1);
    //申请一个 input_dev输入设备
    ft5x06_dev = input_allocate_device();
    //设置 input_dev 名字
    ft5x06_dev->name = "ft5x06_input_test";

    __set_bit(EV_KEY, ft5x06_dev->evbit);     //支持按键事件
    __set_bit(EV_ABS, ft5x06_dev->evbit);     //支持绝对坐标事件
    __set_bit(BTN_TOUCH, ft5x06_dev->keybit); //支持按键检测

    __set_bit(ABS_X, ft5x06_dev->absbit); //支持X坐标
    __set_bit(ABS_Y, ft5x06_dev->absbit); //支持Y坐标

    //__set_bit(ABS_PRESSURE,ft5x06_dev-> keybit);//支持压力检测

    input_set_abs_params(ft5x06_dev, ABS_X, 0, 1024, 0, 0); //设置X坐标值的范围
    input_set_abs_params(ft5x06_dev, ABS_Y, 0, 600, 0, 0);  //设置Y坐标值的范围
  //input_set_abs_params(ft5x06_dev, ABS_PRESSURE, 0, 255, 0,0);//设置压力值的范围
  //向 Linux内核注册 input_dev
    ret = input_register_device(ft5x06_dev);
    if (ret < 0)
    {
        printk("input_register_device is error\n");
        goto error_input_register;
    }

    return 0;

error_request_irq:
    free_irq(irq, NULL);

error_input_register:
    free_irq(irq, NULL);
    input_unregister_device(ft5x06_dev);
    input_free_device(ft5x06_dev);
    return ret;
}
/* i2c 驱动的 remove 函数 */
int ft5x06_remove(struct i2c_client *client)
{
    printk("This is ft5x06_remove\n");
    return 0;
}
// 无设备树的时候匹配 ID 表
static const struct i2c_device_id ft5x06_id_ts[] = {
    {
        "xxx",
        0,
    },
    {/* sentinel */}};
//与设备树的 compatible 匹配
static const struct of_device_id ft5x06_id[] = {
    {
        .compatible = "edt,ft5x0x_ts",
        0,
    },
    {
        .compatible = "edt,ft5x0x_ts",
        0,
    },
    {
        .compatible = "edt,ft5x0x_ts",
        0,
    },
    {/* sentinel */}};

//定义一个i2c_driver的结构体
static struct i2c_driver ft5x06_driver = {
    .driver = {
        .owner = THIS_MODULE,
        .name = "ft5x06_test",
        .of_match_table = ft5x06_id,
    },
    .probe = ft5x06_probe,
    .remove = ft5x06_remove,
    .id_table = ft5x06_id_ts};
/* 驱动入口函数 */
static int ft5x06_driver_init(void)
{

    int ret = 0;
    ret = i2c_add_driver(&ft5x06_driver);
    if (ret < 0)
    {
        printk("i2c_add_driver is error\n");
        return ret;
    }
    return 0;
}
/* 驱动出口函数 */
static void ft5x06_driver_exit(void)
{
    printk("This is ft5x06_driver_exit\n");
    free_irq(irq, NULL);
    input_unregister_device(ft5x06_dev);
    input_free_device(ft5x06_dev);
    i2c_del_driver(&ft5x06_driver);
}
module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);
MODULE_LICENSE("GPL");

我们参考第三十七章 Linux内核模块将刚刚编写的驱动代码编译为驱动模块,

我们进入共享目录并且加载驱动模块,共享目录的搭建参考第二十九章节 NFS服务器的搭建和使用,如下图所示:

我们输入以下命令,查看输入设备节点并且获取event3的输出信息,我们按下触摸屏,上报的数据包如下所示:

 ls /dev/input/event3

 hexdump /dev/input/event3

 数据整理:

我们来分析一下数据包的信息

0d8a 50f9 0000 0000 7454 0007 0000 0000 0001 014a 0001 0000

type  0001

code  014a

value 0001

EV_KEY事件  

BTN_TOUCH

按下

0d8a 50f9 0000 0000 7454 0007 0000 0000 0003 0000 031d 0000 

type   0003

code  0000

value 031d 对应的10进制是 797

EV_ABS事件

ABS_X

X坐标值797

x的坐标值的范围是0到800,797是符合的数据(3399上实验用的屏幕为7寸lvds,分辨率为800*1280)

0d8a 50f9 0000 0000 7454 0007 0000 0000 0003 0001 04dc 0000

type   0003

code 0001

value 04dc 对应的10进制是1244

EV_ABS事件  

ABS_Y

y坐标值 1244

x的坐标值的范围是0到1280,1244是符合的数据(3399上实验用的屏幕为7寸lvds,分辨率为800*1280)

0d8a 50f9 0000 0000 7454 0007 0000 0000 0000 0000 0000 0000

type  0000

 code 0000

 value 0000

EV_SYN事件  

0d8a 50f9 0000 0000 7454 0007 0000 0000 0000 0000 0000 0000

0d8a 50f9 0000 0000 b915 0008 0000 0000 0001 014a 0000 0000

type  0001

code  014a

value 0000

EV_KEY事件  

BTN_TOUCH

抬起

0d8a 50f9 0000 0000 b915 0008 0000 0000 0000 0000 0000 0000 

type  0001

code  014a

value 0000

EV_SYN事件  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1958624.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1.5 1.6 操作系统引导 虚拟机

操作系统引导 操作系统引导的概念 操作系统引导是指计算机利用CPU运行特定程序&#xff0c;通过程序识别硬盘&#xff0c;识别硬盘分区&#xff0c;识别硬盘分区上的操作系统&#xff0c;最后通过程序启动操作系统&#xff0c;一环扣一环地完成上述过程 操作系统引导的过程 …

分布式锁 Redis+RedisSon

文章目录 1.什么是分布式锁2.分布式锁应该具备哪些条件3.分布式锁主流的实现方案4.未添加分布式锁存在的问题4.1测试未添加分布式锁的代码通过jmeter发送请求4.2 添加线程同步锁集群部署配置nginx修改jmeter端口号4.3 使用redis的setnx命令实现分布式锁解决办法4.4 使用try、fi…

【2025留学】德国留学真的很难毕业吗?为什么大家不来德国留学?

大家好&#xff01;我是德国Viviane&#xff0c;一句话讲自己的背景&#xff1a;本科211&#xff0c;硕士在德国读的电子信息工程。 之前网上一句热梗&#xff1a;“德国留学三年将是你人生五年中最难忘的七年。”确实&#xff0c;德国大学的宽进严出机制&#xff0c;延毕、休…

【日常设计案例分享】通道对账

今天跟同事们讨论一个通道对账需求的技术设计。鉴于公司业务线有好几个&#xff0c;为避免不久的将来各业务线都重复竖烟囱&#xff0c;因此&#xff0c;我们打算将通道对账做成系统通用服务&#xff0c;以降低各业务线的开发成本。 以下文稿&#xff08;草图&#xff09;&…

正点原子imx6ull-mini-Linux设备树下的LED驱动实验(4)

1&#xff1a;修改设备树文件 在根节点“/”下创建一个名为“alphaled”的子节点&#xff0c;打开 imx6ull-alientek-emmc.dts 文件&#xff0c; 在根节点“/”最后面输入如下所示内容 alphaled {#address-cells <1>;#size-cells <1>;compatible "atkalp…

昇思25天学习打卡营第1天|快速入门实操教程

昇思25天学习打卡营第1天|快速入门实操教程 目录 昇思25天学习打卡营第1天|快速入门实操教程 一、MindSpore内容简介 主要特点&#xff1a; MindSpore的组成部分&#xff1a; 二、入门实操步骤 1. 安装必要的依赖包 2. 下载并处理数据集 3. 构建网络模型 4. 训练模型…

WIN下的文件病毒

文件病毒 一.windows下知识句柄禁用某些警告MAX_PATH_WIN32_FIND_DATAWFindFirstFileW注册到服务代码&#xff08;自启动&#xff09;隐藏窗口 二.客户端代码三.服务端代码 一.windows下知识 句柄 相当于指针&#xff0c;用来表示windows下的一些对象&#xff1b; 禁用某些警…

vue3中使用ant-design-vue

ant-design-vue官网&#xff1a;Ant Design Vue — An enterprise-class UI components based on Ant Design and Vue.jsAn enterprise-class UI components based on Ant Design and Vuehttps://www.antdv.com/components/overview-cn/ 安装 npm i -S ant-design-vue 引入 …

前端实现【 批量任务调度管理器 】demo优化

一、前提介绍 我在前文实现过一个【批量任务调度管理器】的 demo&#xff0c;能实现简单的任务批量并发分组&#xff0c;过滤等操作。但是还有很多优化空间&#xff0c;所以查找一些优化的库&#xff0c; 主要想优化两个方面&#xff0c; 上篇提到的&#xff1a; 针对 3&…

“数说”巴黎奥运会上的“中国智造”成果

引言&#xff1a;随着“中国智造”在欧洲杯上方兴未艾&#xff0c;在巴黎奥运会上&#xff0c;中国智造继续以多种形式和领域展现了其强大的实力和创新能力。以格力公开表示将为巴黎奥运村提供345台格力空调&#xff0c;为中国制造的清凉送至巴黎事件拉开中国制造闪亮巴黎奥运会…

CTF Web SQL注入 10000字详解

这里写目录标题 涉及的数据库知识unionorder bydatabase()information_schemalimit--空格注释replaceinto outfilelikeGROUP BYHAVINGGROUP BY、HAVING、WHERE之间的关系regexp 原理信息收集操作系统数据库判断注入点注入点类型POST注入数字型注入字符型注入搜索型注入Insert/u…

Debian12 安装Docker 用 Docker Compose 部署WordPress

服务器准备&#xff1a; 以root账号登录&#xff0c;如果不是root&#xff0c;后面指令需要加sudo apt update apt install apt-transport-https ca-certificates curl gnupg lsb-release添加GPG密钥&#xff0c;推荐国内源 curl -fsSL https://mirrors.aliyun.com/docker…

ArchLinux部署waydroid

在Arch Linux系统上部署Waydroid运行Android APP 文章目录 在Arch Linux系统上部署Waydroid运行Android APP1. 安装要求2. 本机环境3. 安装 Waydroid4. 网络配置5.注册Google设备6. 运行效果图 Waydroid是Anbox配合Haliun技术开发的LXC Android容器&#xff0c;可在GUN/Linux系…

C语言中的指针基础

文章目录 &#x1f34a;自我介绍&#x1f34a;地址&#x1f34a;C语言中的指针 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以&#xff1a;点赞关注评论收藏&#xff08;一键四连&#xff09;哦~ &#x1f34a;自我介绍 Hello,大家好&#xff0c;我是小珑也要变强&am…

Spring Boot 3 + Resilience4j 简单入门 + Redis Cache 整合

1. 项目结构 2. Maven依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.2</version><relativePath/> <!-- lookup parent from repository --&…

CSRF Token 原理

CSRF 攻击 CSRF 攻击成功的关键是&#xff0c;恶意网站让浏览器自动发起一个请求&#xff0c;这个请求会自动携带 cookie &#xff0c;正常网站拿到 cookie 后会认为这是正常用户&#xff0c;就允许请求。 防范 如果在请求中加一个字段&#xff08;CSRF Token&#xff09;&am…

C++笔记之指针基础

函数重载:(C++特性) 定义: C++允许函数重名,但是参数列表要有区分 在相同的作用域定义同名的函数,但是它们的参数要有所区分,这样的多个函数构成重载关系 objdump -d test.exe >log.txt 将test.exe反汇编并将结果重定向到log.txt 文件中 ,然后在 log.txt中找到定…

学习网络安全 为什么Linux首择Kali Linux? 以及如何正确的使用Kali Linux

1.什么是kali linux&#xff1f; Kali Linux是一款基于Debian的Linux发行版&#xff0c;主要用于网络安全测试和渗透测试。它由全球顶尖的安全专家和黑客社区维护开发&#xff0c;提供了丰富的工具和资源&#xff0c;用于测试安全性、漏洞利用和渗透测试。此外&#xff0c;Kal…

MySQL 性能调优

文章目录 一. MySQL调优金字塔1. 架构调优2. MySQL调优3. 硬件和OS调优4. 小结 二. 查询性能调优三. 慢查询1. 概念2. 优化数据访问3. 请求了不需要数据&#xff0c;怎么做4. 是否在扫描额外的记录5. 慢查询相关配置 & 日志位置6. 小结 四. 查询优化器五. 实现调优手段 一.…

24、Python之面向对象:责任与自由,私有属性真的有必要吗

引言 前面我们进一步介绍了类定义中属性的使用&#xff0c;今天我们对中关于属性私有化的话题稍微展开聊一下&#xff0c;顺便稍微理解一下Python设计的相关理念。 访问级别 在其他编程语言中&#xff0c;比如Java&#xff0c;关于类中的属性和方法通过关键字定义明确的访问级…