多点电容触摸的驱动使用到的知识主要有input子系统、gpio硬件中断、iic子系统,tslib测试等知识点,下面将针对多点电容触摸屏的驱动开发的四个知识点进行展开,以及gt911芯片的知识,官方gt9xx驱动的移植。
一、gt911芯片
gt911芯片是可以直接使用的,因为出场的时候已经代码已经烧录进去了,上电之后就可以直接检测到芯片的int引脚产生的中断脉冲,配置对应的引脚为中断,就可以观察到中断的触发。gt911芯片使用需要注意的有,芯片地址的脉冲配置,数据判断寄存器,中断清除。
(1)根据reset复位引脚和int中断引脚的脉冲的配置,设置芯片的iic通讯地址,
但是gt911的iic通讯地址,只有低7位有效,所以需要将0xBA/0xBB的地址右移一位,得到iic的通讯地址为:0x5d。
(2)触摸点的数据缓冲区是否有数据,以及触摸点的个数,是一个重要的寄存器进行储存的。
(3)在中断里将数据读取走之后,需要对“0x814E”寄存器写入0,不然int引脚就会一直产生中断的脉冲。
二、iic子系统
韦老师的开发板是gt911的芯片通讯的iic是使用的imx6ull的i2c,设备树中gt911的设备结点时挂载到i2c的结点下面的。编写i2c的时候可以先测试与设备树的匹配是否成功,当iic设备与设备树中的结点匹配成功之后,就会调用probe函数,这个可以测试是否匹配成功。在匹配成功之后,就可以写iic的发送和接收函数,这时候可以对gt911的其中一个可以读写的寄存器,先对寄存器写入,然后对寄存器进行读取,观察写入和读出的数据是否相同,相同就说明iic读写是成功的。
注意iic通讯使用的通讯频率。
三、gpio硬件中断(中断线程化)
gpio的硬件中断的配置代码前面是已将有了,但是这里并没有使用gpio的硬件中断,因为gt911的工作频率是非常高的,中断上报的频率也是非常频繁的,如果使用硬件中断进行处理的话,由于硬件中断的优先级是最高的,这时候芯片就需要频繁的去处理中断,导致芯片效率变低。就算使用中断的上半部和下半部进行处理的话,中断的下半部使用软件中断也是优先级比普通的任务函数高的,使用任务进行处理,中断的上半部分也是较占资源的,所以这里采用中断线程化来处理这种比较频繁的中断。中断线程化之后,创建的资源是不需要程序员手动释放的,因为普通的任务线程是由系统管理的,在其不使用了之后就会手动释放。
中断线程化设计到两个函数,使用还是比较简单的,下面将对两个函数的使用给出示例:(中断线程化的函数都是带“devm_”前缀的)。
下面是使用中断线程化函数进行gpio的创建:
/* 申请复位IO */if (gpio_is_valid(dev->reset_pin)) /* gpio_is_valid判断gpio的pin是否存在 */{ /* 申请复位IO,并且默认输出高电平 */ret = devm_gpio_request_one(&dev->i2cdrv->dev, //设备dev->reset_pin,//gpioGPIOF_OUT_INIT_HIGH, //默认输出高电平"gt9147 reset"); //gpio1的名字if (ret) {return ret;}}/* 申请中断IO*/if (gpio_is_valid(dev->irq_pin)) { /* 申请复位IO,并且默认输出高电平 */ret = devm_gpio_request_one(&dev->i2cdrv->dev,dev->irq_pin, GPIOF_OUT_INIT_HIGH,"gt9147 int");if (ret) {return ret;}}
下面是对应gpio的中断的申请:
/* 中断的出发需要在硬件初始化之后 */static int gt911_ts_irq(g_ptI2cDev dev){int ret = 0;memset(dev->irq_name, 0, 20);strcpy(dev->irq_name, "gt911_irq");/* 2,申请中断,client->irq就是IO中断, */ret = devm_request_threaded_irq(&dev->i2cdrv->dev,//设备dev->i2cdrv->irq,//中断号NULL, //默认gt911_handler, //终端函数IRQ_TYPE_EDGE_FALLING | IRQF_ONESHOT, //下降沿出发|不允许中断嵌套,本次结束才能处理下一个dev->irq_name, //终端的名字dev);//给中断函数传入的if (ret) {dev_err(&dev->i2cdrv->dev, "Unable to request touchscreen IRQ.\n");return ret;}return 0;}
下面是对应的中断线程化的线程中断函数:
static irqreturn_t gt911_handler(int irq, void *dev_id){return IRQ_HANDLED;}
不需要手动释放。
四、input子系统
多点电容的触摸屏的input上报是这里新的知识点,前面没有学习过的,分为多触点形式的上报和单触点形式的上报,需要有屏幕按下和抬起的两种情况的上报,两种情况缺少一种,在下面tslib的测试过程中就会有问题。下面将从input上报的重要函数,input初始化和上报的流程案例进行讲解。
前面已经有input子系统上报的相关知识,这里只进行新的知识的记录。
1、input上报的重要函数和数据
(1)MT的两种协议
Type A:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据 ( 此类型在实际使 用中非常少! ) 。Type B:适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过 slot 更新某一个 触摸点的信息, FT5426 就属于此类型,一般的多点电容触摸屏 IC 都有此能力。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 */
(2)上传时间的间隔函数
对于 Type A 类型的设备,通过 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) 的数据。 Type B 设备驱动需要给每个识别出来的触摸点分配一个 slot ,后面使用这个 slot 来上报触 摸点信息。可以通过 slot 的 ABS_MT_TRACKING_ID 来新增、替换或删除触摸点。一个非负数 的 ID 表示一个有效的触摸点, -1 这个 ID 表示未使用 slot 。一个以前不存在的 ID 表示这是一个 新加的触摸点,一个 ID 如果再也不存在了就表示删除了。
(3)Type A 上报时序
1 ABS_MT_POSITION_X x[0]2 ABS_MT_POSITION_Y y[0]3 SYN_MT_REPORT4 ABS_MT_POSITION_X x[1]5 ABS_MT_POSITION_Y y[1]6 SYN_MT_REPORT7 SYN_REPORT
第 1 行,通过 ABS_MT_POSITION_X 事件上报第一个触摸点的 X 坐标数据,通过 input_report_abs 函数实现,下面同理。 第 2 行,通过 ABS_MT_POSITION_Y 事件上报第一个触摸点的 Y 坐标数据。 第 3 行,上报 SYN_MT_REPORT 事件,通过调用 input_mt_sync 函数来实现。 第 4 行,通过 ABS_MT_POSITION_X 事件上报第二个触摸点的 X 坐标数据。 第 5 行,通过 ABS_MT_POSITION_Y 事件上报第二个触摸点的 Y 坐标数据。 第 6 行,上报 SYN_MT_REPORT 事件,通过调用 input_mt_sync 函数来实现。 第 7 行,上报 SYN_REPORT 事件,通过调用 input_sync 函数实现。
116 for (i = 0; i < MAX_FINGERS; i++) {117 if (!finger[i].is_valid)118 continue;119120 input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, finger[i].t);121 input_report_abs(input_dev, ABS_MT_POSITION_X, finger[i].x);122 input_report_abs(input_dev, ABS_MT_POSITION_Y, finger[i].y);123 input_mt_sync(input_dev);124 count++;125 }......140141 /* SYN_REPORT */142 input_sync(input_dev);
(4)Type B上报时序
1 ABS_MT_SLOT 02 ABS_MT_TRACKING_ID 453 ABS_MT_POSITION_X x[0]4 ABS_MT_POSITION_Y y[0]5 ABS_MT_SLOT 16 ABS_MT_TRACKING_ID 467 ABS_MT_POSITION_X x[1]8 ABS_MT_POSITION_Y y[1]9 SYN_REPORT
第 1 行,上报 ABS_MT_SLOT 事件,也就是触摸点对应的 SLOT 。每次上报一个触摸点坐 标之前要先使用 input_mt_slot 函数上报当前触摸点 SLOT ,触摸点的 SLOT 其实就是触摸点 ID , 需要由触摸 IC 提供。 第 2 行,根据 Type B 的要求,每个 SLOT 必须关联一个 ABS_MT_TRACKING_ID ,通过 修改 SLOT 关联的 ABS_MT_TRACKING_ID 来完成对触摸点的添加、替换或删除。具体用到 的函数就是 input_mt_report_slot_state ,如果是添加一个新的触摸点,那么此函数的第三个参数 active 要设置为 true , linux 内核会自动分配一个 ABS_MT_TRACKING_ID 值,不需要用户去指 定具体的 ABS_MT_TRACKING_ID 值。 第 3 行,上报触摸点 0 的 X 轴坐标,使用函数 input_report_abs 来完成。 第 4 行,上报触摸点 0 的 Y 轴坐标,使用函数 input_report_abs 来完成。 第 5~8 行,和第 1~4 行类似,只是换成了上报触摸点 0 的 (X,Y) 坐标信息 第 9 行,当所有的触摸点坐标都上传完毕以后就得发送 SYN_REPORT 事件,使用 input_sync 函数来完成。 当一个触摸点移除以后,同样需要通过 SLOT 关联的 ABS_MT_TRACKING_ID 来处理, 时序如下所示:
1 ABS_MT_TRACKING_ID -12 SYN_REPORT
第 1 行,当一个触摸点 (SLOT) 移除以后,需要通过 ABS_MT_TRACKING_ID 事件发送一 个 -1 给内核。方法很简单,同样使用 input_mt_report_slot_state 函数来完成,只需要将此函数的 第三个参数 active 设置为 false 即可,不需要用户手动去设置 -1 。 第 2 行,当所有的触摸点坐标都上传完毕以后就得发送 SYN_REPORT 事件。
86 for (i = 0; i < MAX_TOUCHES; i++) {87 input_mt_slot(input, i);88 89 finger = &touchdata->finger[i];90 91 touch = touchdata->status & (1 << i);92 input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);93 if (touch) {94 x = finger->x_low | (finger->x_high << 8);95 y = finger->y_low | (finger->y_high << 8);96 97 input_report_abs(input, ABS_MT_POSITION_X, x);98 input_report_abs(input, ABS_MT_POSITION_Y, y);99 }100 }101102 input_mt_report_pointer_emulation(input, false);103 input_sync(input);
(5)input数据上报的重要函数
2、input上报的流程
下面是自己在使用中的input上报系统的初始化和上报处理的代码示例:
(1)input的初始化代码
static int Gt922_Init(void){uint8_t reset_reg;/* gt911的初始化 *//* 3,初始化GT911 */reset_reg = 0x02;gt911_write_regs(&I2c, GT_CTRL_REG, &reset_reg, 1); /* 软复位 */gt911_read_regs(&I2c, GT_CTRL_REG, &reset_reg, 1); printk("reset_reg:0x%x == 0x02", reset_reg);mdelay(100);reset_reg = 0x0;gt911_write_regs(&I2c, GT_CTRL_REG, &reset_reg, 1); /* 停止软复位 */mdelay(100);gt911_read_regs(&I2c, GT_CTRL_REG, &reset_reg, 1); printk("reset_reg:0x%x == 0x00", reset_reg);mdelay(1000);return 0;}static int Input_Init(void){int ret;/* input的初始化,使用前缀“devm_”的函数申请input设备,可以不用自己手动释放,系统管理资源,会自己free掉 */I2c.input = devm_input_allocate_device(&I2c.i2cdrv->dev);if (!I2c.input) {printk("devm_input_allocate_device is error\r\n");ret = -ENOMEM;}memset(I2c.input_name, 0, 20);strcpy(I2c.input_name, "gt911_input");/* input子系统参数配置 */I2c.input->name = I2c.input_name;I2c.input->id.bustype = BUS_I2C;I2c.input->dev.parent = &I2c.i2cdrv->dev;__set_bit(EV_KEY, I2c.input->evbit); /* 支持单点触摸 */__set_bit(EV_ABS, I2c.input->evbit); /* 支持多点触摸 */__set_bit(BTN_TOUCH, I2c.input->keybit); /* 对应的事件为触摸按键touch事件 */I2c.max_x = TOUCH_MAP_MAX_X; /* lcd分辨率的x轴最大值 */I2c.max_y = TOUCH_MAP_MAX_Y; /* lcd分辨率的y轴最大值 *//* 单点 *///input_set_abs_params(I2c.input, ABS_X, 0, I2c.max_x, 0, 0);//input_set_abs_params(I2c.input, ABS_Y, 0, I2c.max_y, 0, 0);/* 多点 */input_set_abs_params(I2c.input, ABS_MT_POSITION_X,0, I2c.max_x, 0, 0);input_set_abs_params(I2c.input, ABS_MT_POSITION_Y,0, I2c.max_y, 0, 0);ret = input_mt_init_slots(I2c.input, MAX_SUPPORT_POINTS, 0); /* 支持的最多的触摸点数 */if (ret) {printk(" input_mt_init_slots is error\r\n");return ret;}ret = input_register_device(I2c.input); /* 进行注册 */if (ret){printk("input_register_device is error\r\n");return ret;}return 0;}static void Input_Exit(void){input_unregister_device(I2c.input);/* 设备的注销 */}
(2)input的上报代码:
/* 有触摸点时候进行上报 */input_mt_slot(dev->input, slot_id);input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, true);input_report_abs(dev->input, ABS_MT_TRACKING_ID, slot_id);input_report_abs(dev->input, ABS_MT_POSITION_X, input_x);input_report_abs(dev->input, ABS_MT_POSITION_Y, input_y);input_sync(dev->input);/* 没有触摸点或者触摸点松开时候的上报 */input_mt_slot(dev->input, slot_id);input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, true);input_report_abs(dev->input, ABS_MT_TRACKING_ID, -1);input_sync(dev->input);/* 中断退出之前,要对寄存器0x814E写入0,不然会持续触发中断 */dt911_flage = 0x00;gt911_write_regs(dev, GT_GSTID_REG, &dt911_flage, 1);
五、tslib测试
tslib的测试,前面已经将过,这里就注意触摸的input对应那些事件就好了。在文件“/etc/profile”文件中。
六、官方的驱动使用
由于gt911没有硬件检测触摸的按下和松开,所以需要软件上进行处理,这里我写的驱动不知道如何解决次问题,所以可以采用gt911官方的驱动代码和设备树,韦东山也是用的官方的设备树和驱动,后面会将这部分整理的代码发到自己的gitee,方便后面自己使用。
七、将自己的触摸驱动加入到内核系统中
(1)首先要验证自己的驱动是否可以使用,是否完成了所有的功能,当所有都验证之后,就可以进行下面的操作。
(2)将自己写的驱动c文件拷贝到下面的文件夹:
/home/book/100ask_imx6ull-sdk/Linux-4.9.88/drivers/input
(3)修改当前文件夹下的“Makefile”
将自己的驱动文件添加进去。
(4)修改完成以后重新编译 linux 内核,然后用新的 zImage 启动开发板。如果驱动添加成功的话系统启动的时候就会输出如图 64.4.3.2 所示的信息:
注意:这时候触摸input对应的event可能会变,tslib测试的时候需要注意。