37.2多点电容触摸屏实验(详细代码)_csdn
根据前面知识类博客总结可知,咱们这一内容涉及:
1、I2C驱动框架;
2、中断框架;
3、input 子系统框架;
4、linux 的 MT 协议;
以下内容知识如果涉及以前知识,不会详细讲解。
触摸屏原理图一共涉及到 4 个引脚,如下所示:

一共有4个引脚:
分别是:I2C2_SCL(PH4)、I2C2_SDA(PH5)、CT_RST(PH15)、CT_INT(PI1)。
我们进行实验驱动的是FT5426 设备;
1、修改镜像和设备树
1.1、 添加 FT5426 设备节点
1、添加引脚节点
FT5426 用到了 I2C2 接口以及 PI1 和 PH15 这两个 IO;
首先关于I2C2本文涉及的接口PH4和PH5:
在stm32mp15- pinctrl.dtsi 文件,添加如下引脚配置信息:
i2c2_pins_a: i2c2-0 {pins {pinmux = <STM32_PINMUX('H', 4, AF4)>, /* I2C2_SCL */<STM32_PINMUX('H', 5, AF4)>; /* I2C2_SDA */bias-disable;drive-open-drain;slew-rate = <0>;};};i2c2_pins_sleep_a: i2c2-1 {pins {pinmux = <STM32_PINMUX('H', 4, ANALOG)>, /* I2C2_SCL */<STM32_PINMUX('H', 5, ANALOG)>; /* I2C2_SDA */};};i2c2_pins_b1: i2c2-2 {pins {pinmux = <STM32_PINMUX('H', 5, AF4)>; /* I2C2_SDA */bias-disable;drive-open-drain;slew-rate = <0>;};};i2c2_pins_sleep_b1: i2c2-3 {pins {pinmux = <STM32_PINMUX('H', 5, ANALOG)>; /* I2C2_SDA */};};
其次另外两个本文涉及的接口PI1和PH15:
在stm32mp15- pinctrl.dtsi 文件,添加如下引脚配置信息:
ft5426int_reset_pins_a: ft5426int_reset_pins-0 {pins1 {pinmux = <STM32_PINMUX('I', 1, GPIO)>, /* ft5426 INT */<STM32_PINMUX('H', 15, GPIO)>; /* ft5426 RESET */bias-pull-up;slew-rate = <0>;};};
编译:
make uImage LOADADDR=0XC2000040 -j8 //编译内核
复制给开发板:
sudo cp arch/arm/boot/uImage /home/chensir/linux/tfboot -f
2、 FT5426 节点配置

在stm32mp157d- atk.dts 文件,追加I2C2关于FT5426信息;
&i2c2 {
pinctrl-names = "default", "sleep";pinctrl-0 = <&i2c2_pins_a>;
pinctrl-1 = <&i2c2_pins_sleep_a>;
status = "okay";ft5426: ft5426@38 {compatible = "edt,edt-ft5426";pinctrl-0 = <&ft5426int_reset_pins_a>;reg = <0x38>;irq-gpios = <&gpioi 1 GPIO_ACTIVE_LOW>;reset-gpios = <&gpioh 15 GPIO_ACTIVE_LOW>;status = "okay";};
};
注意:本人在这里栽了一个礼拜以上!
我买的正点原子MP157触摸屏是7寸的,网上和文档大部分说7寸是ft系列,但是我把触摸屏拆下来发现这个是goodixGt911系列!!!
然后用原子给的gt9147的代码可以驱动,但是写进内核没法,大概需要认真写时序,需要把module注册换成late_initcall形式才可以。
module_i2c_driver 宏,这个宏默认会让驱动以 module_init 优先级 注册,但编译为内置(obj-y)时,module_init 会被内核自动转换为 subsys_initcall 优先级(启动早期),导致驱动注册太早,I2C 总线还没就绪
后面我只能使用系统自带的goodix,gt9147了!!!
编译:
make dtbs
复制到开发板中:
sudo cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/chensir/linux/tftpboot/ -f
2、编写驱动程序
之前的博客也是跟大家按照肌肉记忆来编写程序!一步一步按照思路来编写!
总代码会放在最后。
为了让大家更能明白,可以先对着总代码,进行对我的写代码流程更加详细得当!
2.1、头文件
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/input/mt.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
可以看出头文件用到了input/interrupt,说明这个例程代码用到了input子系统和中断。
2.2、驱动注册和注销

注册和注销一体化:这个意思是init和exit不用开发人员写了!
可以看以下举例代码:

2.2.1、编写module_i2c_driver驱动结构体

其中流程是:
10行代码:在本模块执行;
11行代码:name是驱动的名称,字符串"edt_ft5426"会显示在/sys/bus/i2c/devices/0-0038/name中;会在driver目录下生成edt_ft5426。这个是驱动开发者自己编写的,和下面的匹配无关!
12行代码:设备树中的compatible值会与edt_ft5426_of_match下的compatible相匹配。
如下图edt_ft5426_of_match的代码:

其中MODULE_DEVICE_TABLE是声明一下而已!
compatible值一旦匹配成功,就会执行probe和remove。(这些其实之前的驱动程序讲解已经讲过很多遍了)
2.2.2、编写probe和remove函数

2.2.3、注册和注销字符驱动设备
我们这里用到的INPUT子系统和I2C框架,所以可以回顾。
input子系统主要是注册和注销字符设备。在/sys/class 目录下有一个 input 子目录,input子系统的所有设备的主设备号是13,不用开发者自己创建了。
1、定义设备结构体

struct i2c_client 结构体由内核创建和管理,每个连接到 I2C 总线上的设备(比如你的 FT5426 触摸屏)都会对应一个 i2c_client 实例。它包含了设备的关键信息:
- 设备地址(如 FT5426 的 0x38);
- 所属的 I2C 总线(如你系统中的
i2c-0); - 设备名称、设备树节点指针等元数据;
- 用于通信的函数接口(如读取 / 写入设备寄存器)。
2、配置probe和remove函数
这里我们先编译测试一下;

这里需要把i2c_set_clientdata(client, ft5426);放在probe函数最后,只有当所有资源初始化完成且验证无误后,才能将 ft5426 与 client 绑定,一:确保绑定的数据是 “可用的完整状态”,二:避免其他函数访问未初始化的资源。

发现并没有问题!
接下来继续完善probe和remove函数:
2.2.4、注册和注销input子系统
1、定义设备结构体

可以看到client和input在设备结构体当中是没有实例的,所以都要进行实例化;
2、配置probe和remove函数
probe函数:


1- 使用devm_input_allocate_device分配输入设备结构体,这是内核提供的带设备管理的分配函数- 优势:当设备被卸载时,内核会自动释放该内存,无需手动管理。
2- 将分配的输入设备关联到驱动私有数据结构ft5426中
- 设置设备名称,会显示在
/proc/bus/input/devices等系统信息中 - 指明总线类型为 I2C(
BUS_I2C),用于系统识别设备连接方式
3-input_set_abs_params用于配置绝对坐标事件的参数 - 参数说明:
ABS_MT_POSITION_X/Y:多点触摸协议中的 X/Y 坐标事件类型0, 1024和0, 600:分别定义 X 轴 (0-1024) 和 Y 轴 (0-600) 的坐标范围- 后两个
0:分别表示坐标最小变化量和分辨率(此处均设为 0)
4-input_mt_init_slots初始化多点触摸的 “槽位” 系统
- 参数说明:
MAX_SUPPORT_POINTS:设备支持的最大触摸点数(需在驱动中定义)INPUT_MT_DIRECT:表示这是直接触摸设备(区别于间接触摸如触摸板)
5-调用input_register_device(input)进行注册输入设备。
remove函数:

- 从系统中移除之前通过
input_register_device()注册的输入设备 - 通知内核输入子系统该设备已不再可用
2.2.5、复位FT5426触摸芯片
1、定义设备结构体

2、配置probe函数


2.2.6、初始化FT5426
1、配置probe函数

这个都是前面I2C协议学过的!

2.2.7、申请、注册中断服务函数
1、定义设备结构体

2、配置probe函数

其中要配置edt_ft5426_ts_isr函数:


其中需要配置edt_ft5426_ts_read函数:

其中涉及的宏定义需要声明:

最终完善中断配置:

其实不难发现我们这里并没有设涉及字符操作集、cdev、class、device等内容;
解释:
驱动是输入子系统的 “客户端”,子系统已经封装了字符设备的cdev/file_operations/class/device等通用组件,你只需关注 “如何和 FT5426 硬件交互、如何上报触摸事件”,无需重复造轮子。
输入设备(键盘、鼠标、触摸屏等)的核心需求是 “上报事件”(如触摸坐标、按键按下),而非复杂的read/write数据交互。为此,内核输入子系统提前做好了通用封装:
- 替你实现
file_operations:子系统内置了统一的input_fops操作集,包含open/read/poll等函数(用户空间工具如evtest,就是通过这些接口读取触摸事件),你无需自己定义。 - 替你管理
cdev:当你调用input_register_device注册input_dev时,子系统会自动创建cdev结构体,并将其与input_fops绑定,完成字符设备的内核注册。 - 替你创建
class和device:输入子系统已预定义input_class(对应/sys/class/input/目录),注册input_dev后,子系统会自动在该目录下创建设备节点,并触发 udev 生成/dev/input/eventX(你的触摸屏对应的设备文件),无需手动调用class_create或device_create
3、实验现象

4、总代码
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/input/mt.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/interrupt.h>/* FT5426寄存器相关宏定义 */
#define FT5426_DEVIDE_MODE_REG 0x00 // 模式寄存器
#define FT5426_TD_STATUS_REG 0x02 // 状态寄存器
#define FT5426_TOUCH_DATA_REG 0x03 // 触摸数据读取的起始寄存器
#define FT5426_ID_G_MODE_REG 0xA4 // 中断模式寄存器// 定义触摸屏支持的最大触摸点数,根据你的硬件实际情况修改
#define MAX_SUPPORT_POINTS 5#define TOUCH_EVENT_DOWN 0x00 // 按下
#define TOUCH_EVENT_UP 0x01 // 抬起
#define TOUCH_EVENT_ON 0x02 // 接触
#define TOUCH_EVENT_RESERVED 0x03 // 保留struct edt_ft5426_dev {struct i2c_client *client;struct input_dev *input;int reset_gpio;int irq_gpio;
};static int edt_ft5426_ts_reset(struct edt_ft5426_dev *ft5426)
{struct i2c_client *client = ft5426->client;int ret;/* 从设备树中获取复位管脚 */ft5426->reset_gpio = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);if (!gpio_is_valid(ft5426->reset_gpio)) {dev_err(&client->dev, "Failed to get ts reset gpio\n");return ft5426->reset_gpio;}/* 申请使用管脚 */ret = devm_gpio_request_one(&client->dev, ft5426->reset_gpio,GPIOF_OUT_INIT_HIGH, "ft5426 reset");if (ret < 0)return ret;msleep(20);gpio_set_value_cansleep(ft5426->reset_gpio, 0); // 拉低复位引脚msleep(5);gpio_set_value_cansleep(ft5426->reset_gpio, 1); // 拉高复位引脚,结束复位return 0;
}static int edt_ft5426_ts_write(struct edt_ft5426_dev *ft5426,u8 addr, u8 *buf, u16 len)
{struct i2c_client *client = ft5426->client;struct i2c_msg msg;u8 send_buf[6] = {0};int ret;send_buf[0] = addr;memcpy(&send_buf[1], buf, len);msg.flags = 0; //i2c写msg.addr = client->addr;msg.buf = send_buf;msg.len = len + 1;ret = i2c_transfer(client->adapter, &msg, 1);if (1 == ret)return 0;else {dev_err(&client->dev, "%s: write error, addr=0x%x len=%d.\n",__func__, addr, len);return -1;}
}static int edt_ft5426_ts_read(struct edt_ft5426_dev *ft5426,u8 addr, u8 *buf, u16 len)
{struct i2c_client *client = ft5426->client;struct i2c_msg msg[2];int ret;msg[0].flags = 0; // i2c写msg[0].addr = client->addr;msg[0].buf = &addr;msg[0].len = 1; // 1个字节msg[1].flags = I2C_M_RD; //i2c读msg[1].addr = client->addr;msg[1].buf = buf;msg[1].len = len;ret = i2c_transfer(client->adapter, msg, 2);if (2 == ret)return 0;else {dev_err(&client->dev, "%s: read error, addr=0x%x len=%d.\n",__func__, addr, len);return -1;}
}static irqreturn_t edt_ft5426_ts_isr(int irq, void *dev_id)
{struct edt_ft5426_dev *ft5426 = dev_id;u8 rdbuf[30] = {0};int i, type, x, y, id;bool down;int ret;/* 读取FT5426触摸点坐标从0x02寄存器开始,连续读取29个寄存器 */ret = edt_ft5426_ts_read(ft5426, FT5426_TD_STATUS_REG, rdbuf, 29);if (ret)goto out;for (i = 0; i < MAX_SUPPORT_POINTS; i++) {u8 *buf = &rdbuf[i * 6 + 1];/* 以第一个触摸点为例,寄存器TOUCH1_XH(地址0x03),各bit位描述如下:* bit7:6 Event flag 0:按下 1:释放 2:接触 3:没有事件* bit5:4 保留* bit3:0 X轴触摸点的11~8位*/type = buf[0] >> 6; // 获取触摸点的Event Flagif (type == TOUCH_EVENT_RESERVED)continue;/* 我们所使用的触摸屏和FT5426是反过来的 */x = ((buf[2] << 8) | buf[3]) & 0x0fff;y = ((buf[0] << 8) | buf[1]) & 0x0fff;/* 以第一个触摸点为例,寄存器TOUCH1_YH(地址0x05),各bit位描述如下:* bit7:4 Touch ID 触摸ID,表示是哪个触摸点* bit3:0 Y轴触摸点的11~8位。*/id = (buf[2] >> 4) & 0x0f;down = type != TOUCH_EVENT_UP;input_mt_slot(ft5426->input, id);input_mt_report_slot_state(ft5426->input, MT_TOOL_FINGER, down);if (!down)continue;input_report_abs(ft5426->input, ABS_MT_POSITION_X, x);input_report_abs(ft5426->input, ABS_MT_POSITION_Y, y);}input_mt_report_pointer_emulation(ft5426->input, true);input_sync(ft5426->input);out:return IRQ_HANDLED;
}
static int edt_ft5426_ts_irq(struct edt_ft5426_dev *ft5426)
{struct i2c_client *client = ft5426->client;int ret;/* 从设备树中获取中断管脚 */ft5426->irq_gpio = of_get_named_gpio(client->dev.of_node, "irq-gpios", 0);if (!gpio_is_valid(ft5426->irq_gpio)) {dev_err(&client->dev, "Failed to get ts interrupt gpio\n");return ft5426->irq_gpio;}/* 申请使用管脚 */ret = devm_gpio_request_one(&client->dev, ft5426->irq_gpio,GPIOF_IN, "ft5426 interrupt");if (ret < 0)return ret;/* 注册中断服务函数 */ret = devm_request_threaded_irq(&client->dev, gpio_to_irq(ft5426->irq_gpio),NULL, edt_ft5426_ts_isr, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,client->name, ft5426);if (ret) {dev_err(&client->dev, "Failed to request touchscreen IRQ.\n");return ret;}return 0;
}
static int edt_ft5426_ts_probe(struct i2c_client *client,const struct i2c_device_id *id)
{int ret;struct edt_ft5426_dev *ft5426;struct input_dev *input;u8 data;/* 实例化一个struct edt_ft5426_dev对象 */ft5426 = devm_kzalloc(&client->dev, sizeof(struct edt_ft5426_dev), GFP_KERNEL);if (!ft5426) {dev_err(&client->dev, "Failed to allocate ft5426 driver data.\n");return -ENOMEM;}else{dev_err(&client->dev, "Sucessfully!.\n");}ft5426->client = client;/* 复位FT5426触摸芯片 */ret = edt_ft5426_ts_reset(ft5426);if (ret)return ret;msleep(5);/* 初始化FT5426 */data = 0;edt_ft5426_ts_write(ft5426, FT5426_DEVIDE_MODE_REG, &data, 1);data = 1;edt_ft5426_ts_write(ft5426, FT5426_ID_G_MODE_REG, &data, 1);/* 申请、注册中断服务函数 */ret = edt_ft5426_ts_irq(ft5426);if (ret)return ret;/* 注册input设备 */input = devm_input_allocate_device(&client->dev);if (!input) {dev_err(&client->dev, "Failed to allocate input device.\n");return -ENOMEM;}ft5426->input = input;input->name = "FocalTech FT5426 TouchScreen";input->id.bustype = BUS_I2C;input_set_abs_params(input, ABS_MT_POSITION_X,0, 1024, 0, 0);input_set_abs_params(input, ABS_MT_POSITION_Y,0, 600, 0, 0);ret = input_mt_init_slots(input, MAX_SUPPORT_POINTS, INPUT_MT_DIRECT);if (ret) {dev_err(&client->dev, "Failed to init MT slots.\n");return ret;}ret = input_register_device(input);if (ret)return ret;i2c_set_clientdata(client, ft5426);return 0;
}static int edt_ft5426_ts_remove(struct i2c_client *client)
{struct edt_ft5426_dev *ft5426 = i2c_get_clientdata(client);input_unregister_device(ft5426->input);return 0;
}static const struct of_device_id edt_ft5426_of_match[] = {{ .compatible = "edt,edt-ft5426", },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, edt_ft5426_of_match);static struct i2c_driver edt_ft5426_ts_driver = {.driver = {.owner = THIS_MODULE,.name = "edt_ft5426",.of_match_table = of_match_ptr(edt_ft5426_of_match),},.probe = edt_ft5426_ts_probe,.remove = edt_ft5426_ts_remove,
};module_i2c_driver(edt_ft5426_ts_driver);MODULE_LICENSE("GPL");
MODULE_AUTHOR("chensir");
MODULE_INFO(intree, "Y");{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, edt_ft5426_of_match);static struct i2c_driver edt_ft5426_ts_driver = {.driver = {.owner = THIS_MODULE,.name = "edt_ft5426",.of_match_table = of_match_ptr(edt_ft5426_of_match),},.probe = edt_ft5426_ts_probe,.remove = edt_ft5426_ts_remove,
};module_i2c_driver(edt_ft5426_ts_driver);MODULE_LICENSE("GPL");
MODULE_AUTHOR("chensir");
MODULE_INFO(intree, "Y");
