电容触摸屏

一、Linux电容触摸屏驱动框架

电容触摸驱动的基本原理可参考Linux裸机开发|电容触摸屏实验一文。电容触摸 IC 基本都是 I2C 接口的,因此大框架就是 I2C 设备驱动;通过中断引脚(INT)向内核上报触摸信息,因此需要用到中断驱动框架;触摸屏的坐标信息、屏幕按下和抬起信息都属于 input 子系统,因此需要使用 input 子系统

1.1 多点触摸协议

I2C 驱动、中断驱动、 input 子系统在之前的文章中已经介绍过了,下面主要介绍 input 子系统下的多点电容触摸协议(Multi-touch,简称 MT)

触摸点的信息通过 ABS_MT 事件上报给内核,ABS_MT 事件定义在文件 include/uapi/linux/input.h 中,

#define ABS_MT_SLOT      0x2f    /* 最常用:用于上报触摸点ID */
#define ABS_MT_TOUCH_MAJOR  0x30    /* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR  0x31    /* Minor axis (omit if circular) */
#define ABS_MT_WIDTH_MAJOR  0x32    /* Major axis of approaching ellipse */
#define ABS_MT_WIDTH_MINOR  0x33    /* Minor axis (omit if circular) */
#define ABS_MT_ORIENTATION  0x34    /* Ellipse orientation */
#define ABS_MT_POSITION_X   0x35    /* 用于上报触摸点的X坐标信息 */
#define ABS_MT_POSITION_Y   0x36    /* 用于上报触摸点的Y坐标信息 */
#define ABS_MT_TOOL_TYPE    0x37    /* Type of touching device */
#define ABS_MT_BLOB_ID      0x38    /* Group a set of packets as a blob */
#define ABS_MT_TRACKING_ID  0x39    /* 用于区分触摸点 */
#define ABS_MT_PRESSURE     0x3a    /* Pressure on contact area */
#define ABS_MT_DISTANCE     0x3b    /* Contact hover distance */
#define ABS_MT_TOOL_X       0x3c    /* Center X tool position */
#define ABS_MT_TOOL_Y       0x3d    /* Center Y tool position */

MT 协议被分为两种类型:Type A 和 Type B

  • Type A:适用于触摸点不能被区分或者追踪,此类型设备上报原始数据(较少用)

通过 input_mt_sync(struct input_dev *dev)函数来隔离不同的触摸点数据信息,该函数会触发 SYN_MT_REPORT 事件,此事件会通知接收者获取当前触摸数据,并且准备接收下一个触摸点数据

input_mt_sync(struct input_dev *dev)
//input_dev:用于指定具体的 input_dev 设备

Type A 触摸点信息上报时序:编写Type A类型的多点触摸驱动时,需要按照下面的时序上报坐标信息

//以两点触摸为例
ABS_MT_POSITION_X x[0]  //上报第一个触摸点的X坐标信息,通过input_report_abs函数实现
ABS_MT_POSITION_Y y[0]  //上报第一个触摸点的Y坐标信息,通过input_report_abs函数实现
SYN_MT_REPORT           //上报SYN_MT_REPORT事件,通过调用input_mt_sync函数实现
ABS_MT_POSITION_X x[1]  //上报第二个触摸点的X坐标信息,通过input_report_abs函数实现
ABS_MT_POSITION_Y y[1]  //上报第二个触摸点的Y坐标信息,通过input_report_abs函数实现
SYN_MT_REPORT           //上报SYN_MT_REPORT事件,通过调用input_mt_sync函数实现
SYN_REPORT              //上报SYN_REPORT事件,通过调用input_sync函数实现

Type A 类型触摸点信息上报实例

static irqreturn_t st1232_ts_irq_handler(int irq, void *dev_id) {......ret = st1232_ts_read_data(ts);   //获取所有触摸点信息if (ret < 0)goto end;/* 按照Type A类型轮流上报所有的触摸点信息 */for (i = 0; i < MAX_FINGERS; i++) {if (!finger[i].is_valid)continue;input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, finger[i].t);input_report_abs(input_dev, ABS_MT_POSITION_X, finger[i].x);  input_report_abs(input_dev, ABS_MT_POSITION_Y, finger[i].y);//每上报完一个触摸点坐标,都要调用以下函数上报一个 SYN_MT_REPORinput_mt_sync(input_dev); count++;}....../* 每上报完一轮触摸点信息就调用一次input_sync函数,发送SYN_REPORT事件 */input_sync(input_dev);
end:return IRQ_HANDLED;
}
  • Type B:适用于有硬件追踪并能区分触摸点的触摸设备,通过 slot 更新某个触摸点的信息

通过 input_mt_slot(struct input_dev *dev, int slot)函数区分是哪一个触摸点,函数会触发 ABS_MT_SLOT 事件,此事件会告诉接收者当前正在更新的是哪个触摸点(slot)的数据

void input_mt_slot(struct input_dev *dev, int slot)
//input_dev:用于指定具体的 input_dev 设备
//slot:用于指定当前上报的是哪个触摸点信息

Type B 触摸点信息上报时序:编写Type B类型的多点触摸驱动时,需要按照下面的时序上报坐标信息

//以两点触摸为例
ABS_MT_SLOT 0           //上报触摸点对应的SLOT(即触摸点ID),通过input_mt_slot函数实现
ABS_MT_TRACKING_ID 45   //每个触摸点关联一个ID,修改该ID可进行触摸点的添加、替换或删除,通过input_mt_report_slot_state函数实现
ABS_MT_POSITION_X x[0]  //上报第一个触摸点的X坐标信息,通过input_report_abs函数实现
ABS_MT_POSITION_Y y[0]  //上报第一个触摸点的Y坐标信息,通过input_report_abs函数实现
ABS_MT_SLOT 1           //上报触摸点对应的SLOT(即触摸点ID),通过input_mt_slot函数实现
ABS_MT_TRACKING_ID 46   //每个触摸点关联一个ID,修改该ID可进行触摸点的添加、替换或删除,通过input_mt_report_slot_state函数实现
ABS_MT_POSITION_X x[1]  //上报第二个触摸点的X坐标信息,通过input_report_abs函数实现
ABS_MT_POSITION_Y y[1]  //上报第二个触摸点的Y坐标信息,通过input_report_abs函数实现
SYN_REPORT              //上报SYN_REPORT事件,通过调用input_sync函数实现

Type B 类型触摸点信息上报实例

static void ili210x_report_events(struct input_dev *input, const struct touchdata *touchdata) {int i;bool touch;unsigned int x, y;const struct finger *finger;for (i = 0; i < MAX_TOUCHES; i++) {input_mt_slot(input, i);  //上报 ABS_MT_SLOT 事件finger = &touchdata->finger[i];touch = touchdata->status & (1 << i);//上报ABS_MT_TRACKING_ID事件,即给SLOT关联一个ABS_MT_TRACKING_IDinput_mt_report_slot_state(input, MT_TOOL_FINGER, touch);if (touch) {x = finger->x_low | (finger->x_high << 8);y = finger->y_low | (finger->y_high << 8);input_report_abs(input, ABS_MT_POSITION_X, x);input_report_abs(input, ABS_MT_POSITION_Y, y);}}input_mt_report_pointer_emulation(input, false);/* 每上报完一轮触摸点信息就调用一次input_sync函数,发送SYN_REPORT事件 */input_sync(input);
}
1.2 多点触摸相关API函数

Linux 下的多点触摸协议其实就是通过不同的事件来上报触摸点坐标信息,这些事件都是通过内核提供的对应 API 函数实现的,下面介绍一些常见的 API 函数

  • input_mt_init_slots 函数:用于初始化 MT 的输入 slots,编写 MT 驱动时须先调用此函数初始化 slots
int input_mt_init_slots(struct input_dev *dev,unsigned int num_slots,unsigned int flags)
//dev: MT 设备对应的 input_dev,因为 MT 设备隶属于 input_dev
//num_slots:设备要使用的 SLOT 数量,也就是触摸点的数量
//flags: 其他一些 flags 信息,可设置的 flags 如下所示:
#define INPUT_MT_POINTER 0x0001     /* pointer device, e.g. trackpad */
#define INPUT_MT_DIRECT 0x0002      /* direct device, e.g. touchscreen */
#define INPUT_MT_DROP_UNUSED0x0004  /* drop contacts not seen in frame */
#define INPUT_MT_TRACK 0x0008       /* use in-kernel tracking */
#define INPUT_MT_SEMI_MT 0x0010     /* semi-mt device, finger count handled manually */
//返回值: 0,成功;负值,失败
  • input_mt_slot 函数:用于 Type B 类型,此函数用于产生 ABS_MT_SLOT 事件,告诉内核当前上报的是哪个触摸点的坐标数据
void input_mt_slot(struct input_dev *dev, int slot)
//dev:MT设备对应的input_dev
//slot:当前发送的是哪个slot的坐标信息,也就是哪个触摸点
  • input_mt_report_slot_state 函数:用于 Type B 类型,用于产生ABS_MT_TRACKING_ID 和 ABS_MT_TOOL_TYPE事 件 , ABS_MT_TRACKING_ID 事 件 给 slot 关 联 一 个 ABS_MT_TRACKING_ID,
    ABS_MT_TOOL_TYPE 事 件 指 定 触 摸 类 型 ( 是 笔 还 是 手 指 等 )
void input_mt_report_slot_state(struct input_dev *dev,unsigned int tool_type,bool active)
//dev:MT设备对应的input_dev
//tool_type:触摸类型,可选择MT_TOOL_FINGER、MT_TOOL_PEN或MT_TOOL_PALM(手掌),多点电容触摸屏一般都是手指
//active: true表示连续触摸,input子系统内核会自动分配一个ABS_MT_TRACKING_ID给slot
//         false表示触摸点抬起,即某个触摸点无效了,input子系统内核会分配一个-1给slot
  • input_report_abs 函数:Type A 和 Type B 类型都使用此函数上报触摸点坐标信息
void input_report_abs(struct input_dev *dev,unsigned int code,int value)
//dev:MT设备对应的input_dev
//code:要上报的是什么数据,如设置为ABS_MT_POSITION_X,就表示上报X轴坐标数据
//value:具体的X轴或Y轴坐标数据值
  • input_mt_report_pointer_emulation 函数:若追踪到的触摸点数量多于当前上报的数量,驱动程序使用 BTN_TOOL_TAP 事件来通知用户空间当前追踪到的触摸点总数量,然后调用此函数将use_count 参数设置为 false;否则的话将 use_count 参数设置为 true,表示当前的触摸点数量
void input_mt_report_pointer_emulation( struct input_dev *dev,bool use_count)
//dev:MT设备对应的input_dev
//use_count:true,有效的触摸点数量;false,追踪到的触摸点数量多于当前上报的数量
1.3 多点触摸驱动框架

多点电容触摸驱动编写框架以及步骤如下:

  • 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,
};
/* 驱动入口函数 */
static int __init xxx_init(void) {int ret = 0;ret = i2c_add_driver(&xxx_ts_driver);return ret;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void) {i2c_del_driver(&ft5x06_ts_driver);
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
  • 初始化触摸 IC、中断和 input 子系统:在xxx_ts_probe 函数中完成
static int xxx_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) {struct input_dev *input;/* 1、初始化 I2C */....../* 2,申请中断, */devm_request_threaded_irq(&client->dev, client->irq, NULL,xxx_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, client->name, &xxx);....../* 3, input 设备申请与初始化 */input = devm_input_allocate_device(&client->dev);input->name = client->name;input->id.bustype = BUS_I2C;input->dev.parent = &client->dev;....../* 4,初始化 input 和 MT */__set_bit(EV_ABS, input->evbit);__set_bit(BTN_TOUCH, input->keybit);input_set_abs_params(input, ABS_X, 0, width, 0, 0);input_set_abs_params(input, ABS_Y, 0, height, 0, 0);input_set_abs_params(input, ABS_MT_POSITION_X,0, width, 0, 0);input_set_abs_params(input, ABS_MT_POSITION_Y,0, height, 0, 0);input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0);....../* 5,注册 input_dev */input_register_device(input);......
}

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

  • 上报坐标信息:在中断服务程序中上报读取到的坐标信息
static irqreturn_t xxx_handler(int irq, void *dev_id) {int num;          /* 触摸点数量 */int x[n], y[n];  /* 保存坐标值 *//* 1、从触摸芯片获取各个触摸点坐标值 */....../* 2、上报每一个触摸点坐标 */for (i = 0; i < num; i++) {input_mt_slot(input, id);input_mt_report_slot_state(input, MT_TOOL_FINGER, true);input_report_abs(input, ABS_MT_POSITION_X, x[i]);input_report_abs(input, ABS_MT_POSITION_Y, y[i]);}......input_sync(input);......return IRQ_HANDLED;
}

二、电容触摸屏实验

本实验以ATK7016(7 寸 1024*600 分辨率)屏幕所使用的 FT5426 触摸芯片为例,讲解如何编写多点电容触摸驱动,硬件原理图参考Linux裸机开发|电容触摸屏实验一文

2.1 修改设备树
  • 修改或添加pinctrl节点:FT5426 触摸芯片用到了复位 IO、中断 IO、I2C2 的 SCL 和 SDA共4个 IO

如有默认的 pinctrl_tsc 子节点,则直接添加如下中断引脚信息;若没有则在iomuxc节点下自行创建

pinctrl_tsc: tscgrp {fsl,pins = <MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0xF080 /* TSC_INT */>;
};

复位引脚是SNVS_TAMPER9,因此在iomuxc_snvs节点下添加复位引脚信息

pinctrl_tsc_reset: tsc_reset {fsl,pins = <MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0>;
};

I2C2 的 SCL 和 SDA 属于 I2C2,一般默认已经添加,无需修改

pinctrl_i2c2: i2c2grp {fsl,pins = <MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0>;
};
  • 添加子节点:FT5426 触摸 IC 挂载 I2C2 下,因此需要向 I2C2 节点下添加一个子节点,用于描述 FT5426
&i2c2 {clock_frequency = <100000>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_i2c2>;status = "okay";/****************************//* 省略掉其他的设备节点 *//****************************/ft5426: ft5426@38 {     //器件地址为0x38compatible = "edt,edt-ft5426";reg = <0x38>;    //描述器件地址为0x38pinctrl-names = "default";pinctrl-0 = < &pinctrl_tsc&pinctrl_tsc_reset >;interrupt-parent = <&gpio1>;interrupts = <9 0>;reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;};
};
  • 检查PIN是否冲突:检查pinctrl中设置以及设备节点中指定的引脚有没有被别的外设使用

保存修改后,在kernel主目录下使用“make dtbs”命令编译设备树,使用新的设备树文件启动Llinux系统

2.2 驱动程序编写

新建 ft5x06.c 驱动文件

#define MAX_SUPPORT_POINTS       5           /* 5点触摸     */
#define TOUCH_EVENT_DOWN        0x00        /* 按下   */
#define TOUCH_EVENT_UP          0x01        /* 抬起   */
#define TOUCH_EVENT_ON          0x02        /* 接触   */
#define TOUCH_EVENT_RESERVED    0x03        /* 保留   */
/* FT5X06寄存器相关宏定义 */
#define FT5X06_TD_STATUS_REG    0X02        /*  状态寄存器地址     */
#define FT5x06_DEVICE_MODE_REG  0X00        /* 模式寄存器        */
#define FT5426_IDG_MODE_REG     0XA4        /* 中断模式         */
#define FT5X06_READLEN          29          /* 要读取的寄存器个数 */struct ft5x06_dev {struct device_node    *nd;                /* 设备节点         */int irq_pin,reset_pin;                    /* 中断和复位IO  */int irqnum;                               /* 中断号      */void *private_data;                       /* 私有数据         */struct input_dev *input;              /* input结构体     */struct i2c_client *client;                /* I2C客户端   */
};static struct ft5x06_dev ft5x06;
/* 复位FT5X06 */
static int ft5x06_ts_reset(struct i2c_client *client, struct ft5x06_dev *dev){int ret = 0;if (gpio_is_valid(dev->reset_pin)) {          /* 检查IO是否有效 *//* 申请复位IO,并且默认输出低电平 */ret = devm_gpio_request_one(&client->dev,    dev->reset_pin, GPIOF_OUT_INIT_LOW,"edt-ft5x06 reset");if (ret) {return ret;}msleep(5);gpio_set_value(dev->reset_pin, 1);   /* 输出高电平,停止复位 */msleep(300);}return 0;
}
/* 从FT5X06读取多个寄存器数据 */
static int ft5x06_read_regs(struct ft5x06_dev *dev, u8 reg, void *val, int len){int ret;struct i2c_msg msg[2];struct i2c_client *client = (struct i2c_client *)dev->client;/* msg[0]为发送要读取的首地址 */msg[0].addr = client->addr;            /* ft5x06地址 */msg[0].flags = 0;                    /* 标记为发送数据 */msg[0].buf = &reg;                    /* 读取的首地址 */msg[0].len = 1;                        /* reg长度*//* msg[1]读取数据 */msg[1].addr = client->addr;           /* ft5x06地址 */msg[1].flags = I2C_M_RD;         /* 标记为读取数据*/msg[1].buf = val;                  /* 读取数据缓冲区 */msg[1].len = len;                 /* 要读取的数据长度*/ret = i2c_transfer(client->adapter, msg, 2);if(ret == 2) {ret = 0;} else {ret = -EREMOTEIO;}return ret;
}
/* 向ft5x06多个寄存器写入数据 */
static s32 ft5x06_write_regs(struct ft5x06_dev *dev, u8 reg, u8 *buf, u8 len){u8 b[256];struct i2c_msg msg;struct i2c_client *client = (struct i2c_client *)dev->client;b[0] = reg;                    /* 寄存器首地址 */memcpy(&b[1],buf,len);      /* 将要写入的数据拷贝到数组b里面 */msg.addr = client->addr;   /* ft5x06地址 */msg.flags = 0;               /* 标记为写数据 */msg.buf = b;               /* 要写入的数据缓冲区 */msg.len = len + 1;         /* 要写入的数据长度 */return i2c_transfer(client->adapter, &msg, 1);
}
/* 向ft5x06指定寄存器写入指定的值,写一个寄存器 */
static void ft5x06_write_reg(struct ft5x06_dev *dev, u8 reg, u8 data){u8 buf = 0;buf = data;ft5x06_write_regs(dev, reg, &buf, 1);
}
/* FT5X06中断服务函数 */
static irqreturn_t ft5x06_handler(int irq, void *dev_id){struct ft5x06_dev *multidata = dev_id;u8 rdbuf[29];int i, type, x, y, id;int offset, tplen;int ret;bool down;offset = 1;     /* 偏移1,也就是0X02+1=0x03,从0X03开始是触摸值 */tplen = 6;        /* 一个触摸点有6个寄存器来保存触摸值 */memset(rdbuf, 0, sizeof(rdbuf));     /* 清除 *//* 读取FT5X06触摸点坐标从0X02寄存器开始,连续读取29个寄存器 */ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG, rdbuf, FT5X06_READLEN);if (ret) {goto fail;}/* 上报每一个触摸点坐标 */for (i = 0; i < MAX_SUPPORT_POINTS; i++) {u8 *buf = &rdbuf[i * tplen + offset];/* 以第一个触摸点为例,寄存器TOUCH1_XH(地址0X03),各位描述如下:* bit7:6  Event flag  0:按下 1:释放 2:接触 3:没有事件* bit5:4  保留* bit3:0  X轴触摸点的11~8位。*/type = buf[0] >> 6;     /* 获取触摸类型 */if (type == TOUCH_EVENT_RESERVED)continue;/* 我们所使用的触摸屏和FT5X06是反过来的 */x = ((buf[2] << 8) | buf[3]) & 0x0fff;y = ((buf[0] << 8) | buf[1]) & 0x0fff;/* 以第一个触摸点为例,寄存器TOUCH1_YH(地址0X05),各位描述如下:* bit7:4  Touch ID  触摸ID,表示是哪个触摸点* bit3:0  Y轴触摸点的11~8位。*/id = (buf[2] >> 4) & 0x0f;down = type != TOUCH_EVENT_UP;input_mt_slot(multidata->input, id);input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, down);if (!down)continue;input_report_abs(multidata->input, ABS_MT_POSITION_X, x);input_report_abs(multidata->input, ABS_MT_POSITION_Y, y);}input_mt_report_pointer_emulation(multidata->input, true);input_sync(multidata->input);fail:return IRQ_HANDLED;}
/* FT5x06中断初始化 */
static int ft5x06_ts_irq(struct i2c_client *client, struct ft5x06_dev *dev){int ret = 0;/* 1,申请中断GPIO */if (gpio_is_valid(dev->irq_pin)) {ret = devm_gpio_request_one(&client->dev, dev->irq_pin,GPIOF_IN, "edt-ft5x06 irq");if (ret) {dev_err(&client->dev,"Failed to request GPIO %d, error %d\n",dev->irq_pin, ret);return ret;}}/* 2,申请中断,client->irq就是IO中断, */ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,client->name, &ft5x06);if (ret) {dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");return ret;}return 0;
}
/* i2c驱动的probe函数,当驱动与设备匹配以后此函数就会执行 */
static int ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id){int ret = 0;ft5x06.client = client;/* 1,获取设备树中的中断和复位引脚 */ft5x06.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);ft5x06.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);/* 2,复位FT5x06 */ret = ft5x06_ts_reset(client, &ft5x06);if(ret < 0) {goto fail;}/* 3,初始化中断 */ret = ft5x06_ts_irq(client, &ft5x06);if(ret < 0) {goto fail;}/* 4,初始化FT5X06 */ft5x06_write_reg(&ft5x06, FT5x06_DEVICE_MODE_REG, 0);  /* 进入正常模式   */ft5x06_write_reg(&ft5x06, FT5426_IDG_MODE_REG, 1);        /* FT5426中断模式   *//* 5,input设备注册 */ft5x06.input = devm_input_allocate_device(&client->dev);if (!ft5x06.input) {ret = -ENOMEM;goto fail;}ft5x06.input->name = client->name;ft5x06.input->id.bustype = BUS_I2C;ft5x06.input->dev.parent = &client->dev;__set_bit(EV_KEY, ft5x06.input->evbit);__set_bit(EV_ABS, ft5x06.input->evbit);__set_bit(BTN_TOUCH, ft5x06.input->keybit);input_set_abs_params(ft5x06.input, ABS_X, 0, 1024, 0, 0);input_set_abs_params(ft5x06.input, ABS_Y, 0, 600, 0, 0);input_set_abs_params(ft5x06.input, ABS_MT_POSITION_X,0, 1024, 0, 0);input_set_abs_params(ft5x06.input, ABS_MT_POSITION_Y,0, 600, 0, 0);        ret = input_mt_init_slots(ft5x06.input, MAX_SUPPORT_POINTS, 0);if (ret) {goto fail;}ret = input_register_device(ft5x06.input);if (ret)goto fail;return 0;fail:return ret;
}
/* i2c驱动的remove函数,移除i2c驱动的时候此函数会执行 */
static int ft5x06_ts_remove(struct i2c_client *client){ /* 释放input_dev */input_unregister_device(ft5x06.input);return 0;
}
/* 传统驱动匹配表 */
static const struct i2c_device_id ft5x06_ts_id[] = {{ "edt-ft5206", 0, },{ "edt-ft5426", 0, },{ /* sentinel */ }
};
/* 设备树匹配表 */
static const struct of_device_id ft5x06_of_match[] = {{ .compatible = "edt,edt-ft5206", },{ .compatible = "edt,edt-ft5426", },{ /* sentinel */ }
};
/* i2c驱动结构体 */
static struct i2c_driver ft5x06_ts_driver = {.driver = {.owner = THIS_MODULE,.name = "edt_ft5x06",.of_match_table = of_match_ptr(ft5x06_of_match),},.id_table = ft5x06_ts_id,.probe    = ft5x06_ts_probe,.remove   = ft5x06_ts_remove,
};
/* 驱动入口函数 */
static int __init ft5x06_init(void){int ret = 0;ret = i2c_add_driver(&ft5x06_ts_driver);return ret;
}
/* 驱动出口函数 */
static void __exit ft5x06_exit(void){i2c_del_driver(&ft5x06_ts_driver);
}module_init(ft5x06_init);
module_exit(ft5x06_exit);
MODULE_LICENSE("GPL");
2.3 运行测试

多点电容触摸屏测试不需要编写专门的 APP

  • 修改Makefile编译目标变量
obj-m := ft5x06.o
  • 使用“make -j32”编译出驱动模块文件
make -j32
  • 将驱动文件拷贝至“rootfs/lib/modules/4.1.15”中

  • 使用“modprobe”命令加载驱动,加载成功后总线就会进行匹配

depmod  #第一次加载驱动时,需使用“depmod”命令
modprobe ft5x06.ko
  • 当驱动模块加载成功以后会有下图所示信息输入

  • 驱动加载成功以后就会生成/dev/input/eventX(X=1,2,3…)

  • 使用hexdump /dev/input/event2命令,查看多点电容触摸屏上报的原始数据:

多点电容触摸屏上报的原始数据表示的含义如下

/* 编号 */ /* tv_sec */ /* tv_usec */ /* type */ /* code */ /* value */
0000000    02bb 0000    9459 0007     0003       002f       0000 0000
//type为0x3,说明是EV_ABS事件
//code为0x2f,为ABS_MT_SLOT,此行是input_mt_slot函数上报的 ABS_MT_SLOT 事件
//value=0,说明接下来上报的是第一个触摸点坐标
0000010    02bb 0000    9459 0007     0003       0039       0005 0000
//type为0x3,说明是EV_ABS事件
//code为0x39,为ABS_MT_TRACKING_ID
//说明此行是input_mt_report_slot_state函数上报的ABS_MT_TRACKING_ID事件
//value=5,说明给 SLOT0 分配的 ID 为 5
0000020    02bb 0000    9459 0007     0003       0035       03ec 0000
//type为0x3,说明是EV_ABS事件
//code为0x35,为ABS_MT_POSITION_X
//说明此行是input_report_abs函数上报的ABS_MT_POSITION_X事件
//value=0x03ec=1004,说明触摸点X轴坐标为1004,属于屏幕右上角区域
0000030    02bb 0000    9459 0007     0003       0036       0017 0000
//type为0x3,说明是EV_ABS事件
//code为0x36,为ABS_MT_POSITION_Y
//说明此行是input_report_abs函数上报的ABS_MT_POSITION_Y事件
//value=0x17=23,说明Y轴坐标为23,由此可以看出本次触摸的坐标为(1004,23)
0000040    02bb 0000    9459 0007     0001       014a       0001 0000
//type为0x1,说明是EV_KEY事件,code=0x14a为 BTN_TOUCH,value=0x1触摸屏被按下
0000050    02bb 0000    9459 0007     0003       0000       03ec 0000
//type为0x3,说明是EV_ABS事件
//code为0x0,为ABS_X,用于单点触摸的时候上报X轴坐标
//说明此行是input_mt_report_pointer_emulation函数上报的ABS_X事件
//value=0x03ec=1004,说明触摸点X轴坐标为1004,属于屏幕右上角区域
0000060    02bb 0000    9459 0007     0003       0001       0017 0000
//type为0x3,说明是EV_ABS事件
//code为0x1,为ABS_Y,用于单点触摸的时候上报y轴坐标
//说明此行是input_mt_report_pointer_emulation函数上报的ABS_Y事件
//value=0x17=23,说明Y轴坐标为23,由此可以看出本次触摸的坐标为(1004,23)
0000070    02bb 0000    9459 0007     0000       0000       0000 0000
//type为0x0,说明是EV_SYN事件,由input_sync函数上报
0000080    02bb 0000    e5f8 0008     0003       0039       ffff ffff
//type为0x3,说明是EV_ABS事件,code为0x39,即ABS_MT_TRACKING_ID
//value=0xffffffff=-1,说明触摸点离开了屏幕
0000090    02bb 0000    e5f8 0008     0001       014a       0000 0000
//type为0x1,说明是EV_KEY事件,code=0x14a为BTN_TOUCH,value=0x0手指离开触摸屏
00000a0    02bb 0000    e5f8 0008     0000       0000       0000 0000
type为0x0,说明是EV_SYN事件,由input_sync函数上报
2.4 将驱动添加到内核中运行

将触摸驱动编译为模块,每次系统启动以后手动加载驱动模块的方式不是很方便。所以当我们把驱动调试成功以后一般都会将其编译到内核中,这样内核启动以后就会自动加载驱动,不需要再手动 modprobe了。下面将介绍如何将驱动添加到内核中,步骤如下:

  • 将驱动文件放到合适的位置:ft5x06.c是个触摸屏驱动,因此放到内核的drivers/input/touchscreen目录下
  • 修改对应的 Makefile:修改drivers/input/touchscreen目录下的Makefile,在末尾添加如下代码
obj-y += ft5x06.o
  • 重新编译内核,然后用新的 zImage启动开发板。若驱动添加成功,系统启动的时候就会输出如下图所示的信息

触摸屏驱动启动后,会自动生成 /dev/input/evenvtX,使用hexdump /dev/input/eventX命令,查看触摸屏上报的数据,如坐标数据上报正常,说明驱动工作没问题

另外,还可以通过 tslib 来直观的测试多点电容触摸屏驱动,比观看 eventX 原始数据方便的多,具体请参考触摸屏调试库tslib的移植与使用一文

Linux驱动开发|电容触摸屏相关推荐

  1. ⑧tiny4412 Linux驱动开发之触摸屏(TouchScreen)驱动程序

    本节主要是说一下触摸屏驱动的编写. 触摸屏输入输入设备,所以我们本次通过输入子系统的方式来实现,输入子系统的框架图如下: 然后,我们看一线电路图的触摸屏部分: 可以看到触摸屏是通过I2C接口进行数据和 ...

  2. linux驱动开发:触摸屏的认识(FT5x06 IC的分析)

    我们板子上的触摸屏是电容屏,当然也有电阻屏.不过目前绝大多数是电容屏. 原理:按下某个位置,得到某个位置的特定电阻/电容值,电阻屏(可能通过好几个adc去捕获这些电阻值),因为平面上一个点至少需要a( ...

  3. Linux驱动开发学习笔记-电容触摸屏驱动

    <电容触摸屏驱动框架> 电容触摸屏驱动其实是以下几种 linux 驱动框架的组合: ① IIC 设备驱动,因为电容触摸 IC 基本都是 IIC 接口的,因此大框架就是 IIC 设备驱动. ...

  4. 嵌入式linux驱动开发实战教程,嵌入式Linux驱动开发实战视频教程

    嵌入式Linux驱动开发实战教程(内核驱动.看门狗技术.触摸屏.视频采集系统) 适合人群:高级 课时数量:109课时 用到技术:嵌入式 Linux 涉及项目:驱动开发.看门狗技术.触摸屏.视频采集 咨 ...

  5. 华清远见嵌入式Linux驱动开发培训班

    课程背景 开放的 Linux 受到广泛的欢迎,得到越来越多公司的支持,但是阻碍 Linux 在各个领域广泛应用的主要因素就是内核/驱动高端人才极度缺乏,Linux源代码中85%是设备驱动,嵌入式系统中 ...

  6. 最全Linux驱动开发全流程详细解析(持续更新)

    Linux驱动开发详细解析 一.驱动概念 驱动与底层硬件直接打交道,充当了硬件与应用软件中间的桥梁. 具体任务 读写设备寄存器(实现控制的方式) 完成设备的轮询.中断处理.DMA通信(CPU与外设通信 ...

  7. Linux驱动开发(十)---树莓派输入子系统学习(红外接收)

    前文回顾 <Linux驱动开发(一)-环境搭建与hello world> <Linux驱动开发(二)-驱动与设备的分离设计> <Linux驱动开发(三)-设备树> ...

  8. 嵌入式linux驱动开发之点亮led(驱动编程思想之初体验)

    这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...

  9. Linux驱动开发环境配置(内核源码树构造)

    来源:季义钦BLOG 作者:季义钦 初次接触Linux驱动程序开发,买了一本<Linux设备驱动程序>,第一件事当然就是构建开发环境了!!! 它上面有一个Hello World的列子: / ...

  10. linux驱动开发的经典书籍

    Linux驱动学习的最大困惑在于书籍的缺乏,市面上最常见的书为<linux_device_driver 3rd Edition>,这是一本很经典的书,无奈Linux的东东还是过于庞大,这本 ...

最新文章

  1. linux 中关于网络的配置方法
  2. Flutter开发者必备手册 Flutter Go
  3. Py之cv2:cv2库(OpenCV,opencv-python)的简介、安装、使用方法(常见函数、方法等)最强详细攻略
  4. hdu 4529(状态dp)
  5. mysql复制的配置
  6. cocos2dx基础篇(2)——Win32移植到Android
  7. 聊聊Elasticsearch RestClient的RequestLogger
  8. virtual 关键字以及虚函数的介绍
  9. SNMP 简单网络管理协议
  10. 19. 各种提权姿势总结
  11. 分页的时候pageSize没有效果
  12. C++ 多线程(一)入门
  13. 【剑桥摄影协会】色彩管理之色彩空间
  14. 信创操作系统--统信UOS桌面版(使用终端:bash、tty、基本shell操作)
  15. OGRE粒子系统脚本详解
  16. Openshift入门:基本概念解析
  17. 华为 ServiceComb框架
  18. windows下配置IIS以及优化配置
  19. SLAM及深度学习环境配置总教程
  20. Android二手书交易app设计(2)启动图Activity

热门文章

  1. web前端面试之基础面试题(一)(含答案)
  2. 使用C语言创建顺序表
  3. 32怎么通过一个按键实现不同工作模式_罗技G604鼠标为设计师而生,15个可编程按键玩转Adobe软件!...
  4. Javassist框架研究
  5. python selenium下载图片_用selenium自动加载浏览器下载图片
  6. 软件测试微信钱包,面试官:请你说说微信发红包,有哪些测试点
  7. 统一建模语言(UML)介绍和使用
  8. 通过GPS测试跑步速度可行性验证
  9. JDK动态代理和CGLIB动态代理介绍
  10. 虚拟服务器软件哪个好,虚拟机软件哪个好?热门虚拟机软件推荐