上篇文章介绍了LCD屏幕的使用,这个屏幕还有触摸功能,本篇就来介绍LCD的触摸功能的使用。
关于触摸的内容有点多,分为上下两篇进行讲解,本篇先介绍触摸驱动的编写以及将触摸点坐标实时打印出来进行测试,先有一个整体的使用感受,下篇文章再介绍具体的触摸上报协议以及图形化的测试方法。
LCD的触摸功能,本质就是显示屏上再叠加一层透明的触摸屏,实现触摸的方式与LCD进行交互。
触摸屏分为电阻触摸屏和电容触摸屏。
本篇使用的是野火的7寸电容触摸屏,分辨率和屏幕一样,800x480。触摸驱动芯片我GT911,是IIC接口的芯片。
触摸芯片有四个引脚:
对应板子原理图的触摸接口如下:
对应屏幕原理图的触摸接口如下:
触摸芯片用到IIC通信,还要用到复位引脚和中断引脚,因此需要先在设备树中对引脚信息进行配置。
修改imx6ull_myboard.dts文件。
在设备树中把触摸要用到的引脚追加到 iomuxc即可。
引脚 | 功能 |
---|---|
UART4_RX_DATA | 复用为 I2C1_SDA,用作 IIC1 的 SDA 引脚 |
UART4_TX_DATA | 复用为 I2C1_SCL,用作 IIC1 的 SCL 引脚 |
LCD_RST | 复用为 GPIO3_IO04 用作触摸芯片的复位引脚 |
SNVS_TAMPER9 | 复用为 GPIO5_IO09 用作触摸芯片的 irq 引脚,接收触摸中断 |
需要注意的是,SNVS_TAMPER9 引脚被复用为 GPIO5_IO09,需要追加到 iomuxc_snvs 节点。
触摸芯片用到的是IIC1,这两个引脚在设备树中以及默认添加了,无需修改:
&iomuxc节点中添加:
/*my gt911*/
pinctrl_tsc_reset: tscresetgrp {
fsl,pins = <
/* used for tsc reset */
MX6UL_PAD_LCD_RESET__GPIO3_IO04 0x10b0
>;
};
&iomuxc_snvs节点中添加:
/*my gt911*/
pinctrl_tsc_irq: tsc_irq {
fsl,pins = <
/* used for tsc irq */
MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x4001b8b0
>;
}
GT911触摸驱动作为一个IIC设备挂载在IIC1总线上,找到IIC1节点:
需要在 IIC1 设备节点下追加相应的子节点:
gt911_tsc@5d {
compatible = "goodix,gt911";
reg = <0x5d>;
pinctrl-0 = <&pinctrl_tsc_reset>;
pinctrl-1 = <&pinctrl_tsc_irq>;
reset-gpios = <&gpio3 4 GPIO_ACTIVE_LOW>;
irq-gpios = <&gpio5 9 GPIO_ACTIVE_HIGH>;
interrupt-parent = <&gpio5>;
interrupts = <9 IRQ_TYPE_EDGE_FALLING>;
};
修改后:
reg = <0x5d>是GT911触摸芯片在 IIC1总线上的地址。
查看GT911的数据手册,找到寄存器相关的表格:
主要关注以下这些寄存器,它们是用来读取触摸坐标点的:
Addr | Access | bit7~bit0 |
---|---|---|
0x814E | R/W | buffer status(7) large detect(6) Reserved(5~4) number of touch points(3~0) |
0x814F | R | track id |
0x8150 | R | point 1 x coordinate (low byte) |
0x8151 | R | point 1 x coordinate (high byte) |
0x8152 | R | point 1 y coordinate (low byte) |
0x8153 | R | point 1 y coorte (high byte) |
0x8154 | R | Point 1 size (low byte) |
0x8155 | R | Point 1 size (high byte) |
0x8156 | R | Reserved |
0x8157 | R | track id |
… | … | … |
0x815F | R | track id |
… | … | … |
0x8167 | R | track id |
… | … | … |
0x816F | R | track id |
0x8170 | R | point 5 x coordinate (low byte) |
0x8171 | R | point 5 x coordinate (high byte) |
0x8172 | R | point 5 y coordinate (low byte) |
0x8173 | R | point 5 y coordinate (high byte) |
0x8174 | R | Point 5 size (low byte) |
0x8175 | R | Point 5 size (high byte) |
0x8176 | R | Reserved |
注:GT911支持硬件追踪触摸点,因此为每个触摸点提供了一个track id,举个简单的例子,当5个手指依次触摸到屏幕时,5组坐标寄存器中的track id会依次是0、1、2、3、4,当松开第1个手指时,即track id为0的点没有了,此时5组坐标寄存器,是只有前45组坐标寄存器有数据,track id会依次是1、2、3、4(理解这个很重要,因为我一开始想当然的误认为,移开第1个手指时,是第1组坐标寄存器没数据了)
新建gt911.c文件作为驱动文件
触摸芯片GT911的使用,本质是使用IIC通信,进行数据的读写,因为触摸屏的驱动,实际就是IIC驱动。另外,触摸的数据是通过中断的方式触发的,因此触摸驱动的编写,涉及到中断的处理。在中断时,读取到触摸数据后,要传递到应用层,这里是使用Linux的input子系统(这也是Linux的一种软件分层设计的方式)。
所以,编写触摸驱动,主要涉及3点:
GT911的驱动按照IIC驱动来写,当驱动运行时,会自动运行gt911_probe,在这个函数中会进行各种初始化操作。另外注意匹配列表,这里的**“goodix,gt911”**对应设备树中添加的设备节点,两处的名字要一致。
/* 匹配列表 */
static const struct of_device_id gt911_of_match[] = {
{.compatible = "goodix,gt911"},
{/* Sentinel */}
};
/* i2c驱动结构体 */
struct i2c_driver gt911_i2c_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "gt911", /* 驱动名字 用于和设备匹配 适用于没有设备树的情况*/
.of_match_table =gt911_of_match, /* 设备树匹配列表 */
},
.probe =gt911_probe,
.remove =gt911_remove,
.id_table = gt911_id, /* id配置列表 */
};
gt911_probe函数进行触摸驱动的初始化,基本流程就是从设备树获取触摸节点,然后进行IO的初始化,中断和复位的初始化以及触摸器件的初始化等。
static int gt911_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
u8 ret = 0;
gt911.client = client;
printk("[BSP] gt911 driver and device has match!\r\n");
/* 获取设备树中的中断和复位引脚 */
printk("[BSP] get gpios\r\n");
gt911.irq_pin = of_get_named_gpio(client->dev.of_node, "irq-gpios", 0);
gt911.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);
/* 初始化复位引脚 */
ret = gt911_ts_reset(client, >911);
/* 初始化gt911 */
printk("[BSP] init gt911\r\n");
gt911_write_reg(>911, GT_CTRL_REG, 2); /* 软复位 */
mdelay(100);
gt911_write_reg(>911, GT_CTRL_REG, 0); /* 停止软复位 */
mdelay(100);
/* input 注册设备*/
printk("[BSP] init input device\r\n");
gt911.input = devm_input_allocate_device(&client->dev);
/* 初始化input */
gt911.input->name = client->name;
gt911.input->id.bustype = BUS_I2C;
gt911.input->dev.parent = &client->dev;
/* 设置input设备需要上报哪些事件*/
__set_bit(EV_SYN, gt911.input->evbit);
__set_bit(EV_KEY, gt911.input->evbit); /* 按键事件 */
__set_bit(EV_ABS, gt911.input->evbit); /* 重复事件 */
/* 设置input设备需要上报哪些按键*/
__set_bit(BTN_TOUCH, gt911.input->keybit); /* 触摸值 */
/* 多点触摸 */
input_mt_init_slots(gt911.input, MAX_SUPPORT_POINTS, 0); /*触摸点的数量 */
input_set_abs_params(gt911.input, ABS_MT_POSITION_X,0, 800, 0, 0);
input_set_abs_params(gt911.input, ABS_MT_POSITION_Y,0, 480, 0, 0);
/* 注册input */
ret = input_register_device(gt911.input);
/* 最后初始化中断 */
ret = gt911_ts_irq(client, >911);
printk("[BSP] %s done \r\n",__FUNCTION__);
return 0;
}
复位引脚的初始化主要就是拉低再拉高复位引脚,实现复位,主要内容为:
/* 申请复位IO 并且默认输出高电平 */
devm_gpio_request_one(&client->dev,
dev->reset_pin,
GPIOF_OUT_INIT_HIGH,
"gt911 reset");
gpio_set_value(dev->reset_pin, 0); /* 复位 */
msleep(10);
gpio_set_value(dev->reset_pin, 1); /* 停止复位 */
msleep(300);
中断的初始化,包括IO的申请和中断函数的注册:
/* 申请复位 IO */
devm_gpio_request_one(&client->dev,
dev->irq_pin,
GPIOF_IN,
"gt911 irq");
/* 申请中断 */
devm_request_threaded_irq(&client->dev,
client->irq,
NULL,
gt911_irq_handler, /* 中断处理函数 */
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, /* 触发方式 */
client->name,
>911);
这里仅贴出gt911_irq_handler的主要内容,基本思路是先读取**0x814E(GT_GSTID_REG)**这一个寄存器,判断触摸点的数量,然后再读取对应的坐标点数据寄存器,依次上报数据。
/* -----读取触摸信息寄存器----- */
ret = gt911_read_regs(dev, GT_GSTID_REG, &data, 1);
if(data == 0x00) /* 没有触摸数据*/
{
goto fail;
}
else
{ /* 统计触摸信息 */
status = data >> 7; // bit7:1表示坐标(或按键)已经准备好,主控可以读取 0 表示未就绪,数据无效
large_detect = (data >> 6) & 0x01; // bit6:
touch_num = data & 0x0f; // bit3~0:屏上的坐标点个数
}
if(touch_num) /* 有触摸按下 */
{
/* -----读取具体的触摸点数据寄存器----- */
gt911_read_regs(dev, GT_TP1_REG, buf, BUFFER_SIZE);
id = buf[0]; // 数据中的第一个触摸点的id
touch_index |= (0x01<<id);
/* 上报每一个触摸点坐标 */
for (i = 0; i < 5; i++)
{
if ((touch_index & (0x01<<i)))
{
input_x = (buf[pos + 1] | (buf[pos + 2] << 8)) & 0x0fff; // x坐标
input_y = (buf[pos + 3] | (buf[pos + 4] << 8)) & 0x0fff; // y坐标
input_mt_slot (dev->input, id); // 产生ABS_MT_SLOT 事件 报告是哪个触摸点的坐标
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, true); // 指定手指触摸 连续触摸
input_report_abs(dev->input, ABS_MT_POSITION_X, input_x); // 上报触摸点坐标信息
input_report_abs(dev->input, ABS_MT_POSITION_Y, input_y); // 上报触摸点坐标信息
printk("[%d](%d, %d) ", id, input_x, input_y);
report_num++;
if (report_num < touch_num)
{
pos += 8;
id = buf[pos];
touch_index |= (0x01<<id);
}
}
else
{
input_mt_slot(dev->input, i);
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false); // 关闭手指触摸
}
}
printk("\r\n");
}
else if(last_index)/* 触摸释放 */
{
for (i = 0; i < 5; i++)
{
if ((last_index & (0x01<<i)))
{
input_mt_slot(dev->input, i); /* 上报触摸点 */
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false); // 关闭手指触摸
}
}
}
last_index = touch_index;
input_mt_report_pointer_emulation(dev->input, true);
input_sync(dev->input); /* 同步数据 数据上报完成 */
data = 0x00; /* 向0x814E寄存器写0 不然就会一直进入中断 */
gt911_write_regs(dev, GT_GSTID_REG, &data, 1); //写入
总结一下GT911多点触摸驱动的执行流程:
对于触摸屏的驱动,NXP已经编写好了触摸驱动,加以修改可以在自己的板子上使用。
不过我没有测试成功,以后有时间再搞,所以这一部分内容可以跳过。
我这个7寸屏的驱动型号为GT911,属于 GOODIX 公司生产的触摸芯片,该触摸驱动已默认添加到了Linux内核中,位于:/drivers/input/touchscreen/goodix.c。
使用Linux内核自代的驱动,还需要进行内核配置。在Linux内核源码目录,输入以下指令打开内核的图形化配置:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
到达Linux内核配置界面,然后按下路径找到对应的配置项:
-> Device Drivers
-> Input device support
-> Touchscreens (INPUT_TOUCHSCREEN [=y])
<*> Goodix I2C touchscreen
最终到达这个界面:
按下y勾选上星号,连按多次ESC退出,最后提示保存,按下y保存配置。
然后需要重新编译zImage和设备树,到Linux内核源码目录,执行之前的编写的编译脚本
./build_myboard.sh
编译的时候会弹出Linux图形配置界面, 不需要做任何的配置, 直接按两下ESC键退出图形界面
将编译出zImage(arch/arm/boot目录)和imx6ull-myboard.dtb (arch/arm/boot/dts目录)复制到网络启动位置
cp arch/arm/boot/zImage ~/myTest/tftpboot/nxp/
cp arch/arm/boot/dts/imx6ull-myboard.dtb ~/myTest/tftpboot/nxp/
使用自己编写的触摸驱动程序,进行测试。
首先是编译设备树,验证添加的触摸节点是否工作正常,在Linux内核源码目录执行下面的命令,重新编译设备树并拷贝到网络启动位置。
make imx6ull-myboard.dtb
cp arch/arm/boot/dts/imx6ull-myboard.dtb ~/myTest/tftpboot/nxp/
然后重启开发板,可以先到如下位置,查看设备树的节点是否正常:
然后是编译驱动文件,也就是gt911.c,编译方式和之前一样,在ubuntu中使用Makefile进行交叉编译。
本篇暂未用到对应的触摸应用程序,所有的触摸坐标打印都是在驱动程序中通过printk的方式进行内核打印。
编译完驱动后,将对应的.ko文件复制到板子中。
先加载触摸驱动,串口会打印出为触摸分配的event,我这里是event2。
然后执行下面的指令进行触摸测试:
hexdump /dev/input/event2
将手指放到屏幕上,就可以在LCD屏幕上看到坐标值的打印,比如将手指放到屏幕左下角,对应输出的值大致就是屏幕的最大位置(800,480):
GT911支持多点触摸,驱动程序中也对多点数据进行了获取和打印,将多个手指放到屏幕上,可以看到最多有5个触摸点的坐标打印:
本篇主要介绍了多点触摸芯片GT911的驱动编写与使用,并通过将触摸点实时打印的方式,测试触摸功能。
https://www.bilibili.com/video/BV1sZ4y1Q7da?spm_id_from=333.999.0.0