当前位置: 首页 > ds >正文

I2C多点触控驱动开发详解

I2C多点触控驱动开发详解

1. 多点触控技术概述

1.1 触控技术发展历程

触控技术作为人机交互的重要方式,经历了从单点触控到多点触控的演进过程。早期的电阻式触控屏只能实现单点触控,限制了用户体验。随着电容式触控技术的发展,多点触控成为可能,极大地丰富了人机交互方式。

在嵌入式Linux系统中,多点触控技术的应用日益广泛,从智能手机、平板电脑到工业控制面板,都离不开多点触控的支持。GT9147作为一款高性能的电容式触控芯片,为嵌入式设备提供了可靠的多点触控解决方案。

1.2 GT9147芯片特性

GT9147是Goodix公司推出的一款电容式触控控制器芯片,具有以下主要特性:

  • 高精度触控检测:支持5点同时触控,提供精确的触控坐标信息
  • 低功耗设计:多种工作模式可选,满足不同功耗需求
  • I2C通信接口:采用标准I2C协议进行数据通信,简化硬件连接
  • 智能手势识别:支持多种手势识别功能,提升用户体验
  • 抗干扰能力强:内置噪声抑制算法,提高触控稳定性

GT9147通过I2C接口与主控芯片通信,将触控面板的原始数据转换为数字信号,再通过I2C总线传输给主控芯片进行处理。

1.3 多点触控系统架构

在Linux系统中,多点触控系统的架构分为硬件层、驱动层和应用层三个层次:

+------------------+
|    应用程序层     |
|  (触摸事件处理)   |
+------------------+
|    输入子系统层   |
|  (事件分发处理)   |
+------------------+
|   GT9147驱动层    |
| (硬件抽象与控制)  |
+------------------+
|     硬件层       |
|  (触控面板与IC)   |
+------------------+

驱动层作为连接硬件和系统的关键部分,负责将硬件的原始数据转换为系统可识别的输入事件,并通过Linux输入子系统向上层提供统一的接口。

2. I2C总线协议基础

2.1 I2C总线工作原理

I2C(Inter-Integrated Circuit)总线是一种串行通信协议,由Philips公司开发,用于连接低速外围设备。I2C总线采用两线制设计,包括:

  • SDA(Serial Data Line):串行数据线,用于传输数据
  • SCL(Serial Clock Line):串行时钟线,用于同步数据传输

I2C总线支持多主多从架构,通过设备地址进行设备寻址。在Linux系统中,I2C总线由I2C适配器(Adapter)管理,每个连接到总线的设备都有唯一的7位或10位地址。

2.2 I2C通信时序

I2C通信遵循严格的时序要求,主要包括以下几个阶段:

  1. 起始条件(Start Condition):SCL保持高电平,SDA从高电平变为低电平
  2. 地址传输:发送7位或10位设备地址和读写位
  3. 应答信号(ACK):接收方在第9个时钟周期拉低SDA线表示应答
  4. 数据传输:按字节传输数据,每个字节后跟随应答信号
  5. 停止条件(Stop Condition):SCL保持高电平,SDA从低电平变为高电平
// I2C消息结构体定义
struct i2c_msg {__u16 addr;     // 设备地址__u16 flags;    // 读写标志__u16 len;      // 数据长度__u8 *buf;      // 数据缓冲区
};

2.3 Linux I2C子系统架构

Linux I2C子系统采用分层架构设计,主要包括:

  • I2C核心层:提供I2C总线操作的通用接口
  • I2C适配器层:实现具体I2C控制器的硬件操作
  • I2C设备驱动层:实现特定I2C设备的驱动逻辑

这种分层设计使得I2C设备驱动可以独立于具体的硬件平台,提高了代码的可移植性和复用性。

3. GT9147驱动框架分析

3.1 驱动模块初始化

GT9147驱动作为Linux内核模块,遵循标准的模块初始化和退出流程。驱动的入口函数gt9147_init和出口函数gt9147_exit分别完成模块的注册和注销。

static int __init gt9147_init(void)
{int ret = 0;ret = i2c_add_driver(&gt9147_driver);if (ret < 0){ret = -EINVAL;goto fail_i2c_add;}return 0;
}static void __exit gt9147_exit(void)
{i2c_del_driver(&gt9147_driver);
}

i2c_add_driver函数将I2C驱动注册到I2C核心,当系统检测到匹配的I2C设备时,会自动调用驱动的probe函数进行设备初始化。

3.2 I2C驱动结构体

I2C驱动的核心是i2c_driver结构体,它定义了驱动的基本属性和操作方法:

struct i2c_driver gt9147_driver = {.probe = gt9147_probe,.remove = gt9147_remove,.id_table = gt9147_i2c_id,.driver = {.name = "gt9147",.owner = THIS_MODULE,.of_match_table = of_match_ptr(gt9147_of_id),},
};

其中:

  • probe:设备探测函数,在设备匹配时调用
  • remove:设备移除函数,在设备卸载时调用
  • id_table:传统设备匹配表
  • of_match_table:设备树匹配表

3.3 设备树匹配机制

现代Linux系统广泛采用设备树(Device Tree)来描述硬件配置。GT9147驱动通过设备树匹配表与硬件设备关联:

gt9147: gt9147@14 {compatible = "goodix,gt9147", "goodix,gt9xx";reg = <0x14>;interrupt-parent = <&gpio1>;interrupts = <9 0>;reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;status = "okay";
};

驱动中的匹配表:

static struct of_device_id gt9147_of_id[] = {{.compatible = "goodix,gt9147",},{/* sentinel */}
};

当设备树中的compatible属性与驱动中的of_match_table匹配时,内核会调用驱动的probe函数初始化设备。

4. GT9147硬件初始化

4.1 引脚资源获取

在设备初始化过程中,首先需要获取设备所需的GPIO资源。GT9147需要两个关键引脚:中断引脚和复位引脚。

gt9147dev.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
gt9147dev.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);

of_get_named_gpio函数从设备树中解析指定名称的GPIO引脚编号,为后续的GPIO操作做准备。

4.2 硬件复位时序

GT9147芯片需要按照特定的时序进行硬件复位,确保芯片处于已知的初始状态:

gpio_set_value(dev->reset_pin, 0); /* 复位GT9147 */
msleep(20);
gpio_set_value(dev->reset_pin, 1); /* 停止复位GT9147 */
msleep(50);
gpio_set_value(dev->irq_pin, 0); /* 拉低INT引脚 */
msleep(50);
gpio_direction_input(dev->irq_pin); /* INT引脚设置为输入 */

这个复位时序严格遵循GT9147芯片手册的要求,确保芯片能够正确初始化。

4.3 软件复位操作

在硬件复位完成后,还需要进行软件复位,通过I2C接口向GT9147的控制寄存器写入特定值:

data = 0x02;
gt9147_write_regs(&gt9147dev, GT_CTRL_REG, &data, 1); /* 软复位 */
mdelay(100);
data = 0x00;
gt9147_write_regs(&gt9147dev, GT_CTRL_REG, &data, 1); /* 停止软复位 */
mdelay(100);

软件复位确保芯片内部的寄存器和状态机都恢复到默认状态,为后续的配置做好准备。

5. I2C通信实现

5.1 I2C读操作实现

GT9147驱动通过gt9147_read_regs函数实现I2C读操作。该函数采用I2C消息传输机制,分两步完成读取操作:

static int gt9147_read_regs(struct gt9147_dev *dev, u16 reg, void *val, int len)
{u8 regdata[2];struct i2c_client *client = dev->client;struct i2c_msg msg[2];regdata[0] = reg >> 8;regdata[1] = reg & 0xFF;msg[0].addr = client->addr;msg[0].flags = 0;msg[0].buf = &regdata[0];msg[0].len = 2;msg[1].addr = client->addr;msg[1].flags = I2C_M_RD;msg[1].buf = val;msg[1].len = len;return i2c_transfer(client->adapter, msg, 2);
}

读操作流程:

  1. 第一个消息:发送要读取的寄存器地址
  2. 第二个消息:读取指定长度的数据

5.2 I2C写操作实现

写操作通过gt9147_write_regs函数实现,将寄存器地址和数据打包发送:

static int gt9147_write_regs(struct gt9147_dev *dev, u16 reg, u8 *buf, int len)
{u8 b[256];struct i2c_msg msg;struct i2c_client *client;client = dev->client;b[0] = reg >> 8;b[1] = reg & 0XFF;memcpy(&b[1], buf, len);msg.addr = client->addr;msg.flags = 0;msg.buf = b;msg.len = len + 2;return i2c_transfer(client->adapter, &msg, 1);
}

写操作将寄存器地址和数据合并为一个连续的数据包,通过单次I2C传输完成。

5.3 通信错误处理

I2C通信可能因各种原因失败,驱动需要实现适当的错误处理机制:

ret = i2c_transfer(client->adapter, msg, 2);
if (ret != 2) {dev_err(&client->dev, "I2C transfer failed: %d\n", ret);return -EREMOTEIO;
}

常见的错误包括:

  • 设备未响应(NACK)
  • 总线忙
  • 通信超时
  • 数据校验错误

6. 输入子系统集成

6.1 输入设备分配

GT9147驱动使用Linux输入子系统向上层提供统一的输入接口。首先需要分配输入设备结构体:

gt9147dev.input = devm_input_allocate_device(&client->dev);
if (gt9147dev.input == NULL)
{goto fail_probe;
}

devm_input_allocate_device函数分配输入设备结构体,并自动管理其生命周期。

6.2 输入事件类型配置

配置输入设备支持的事件类型和按键类型:

__set_bit(EV_SYN, gt9147dev.input->evbit);
__set_bit(EV_KEY, gt9147dev.input->evbit);
__set_bit(EV_ABS, gt9147dev.input->evbit);
__set_bit(BTN_TOUCH, gt9147dev.input->keybit);

这里配置了三种事件类型:

  • EV_SYN:同步事件,用于标记事件包的结束
  • EV_KEY:按键事件,用于报告触摸状态
  • EV_ABS:绝对坐标事件,用于报告触摸坐标

6.3 绝对坐标参数设置

设置触摸屏的坐标范围参数:

gt9147dev.max_x = 480;
gt9147dev.max_y = 272;input_set_abs_params(gt9147dev.input, ABS_X, 0, gt9147dev.max_x, 0, 0);
input_set_abs_params(gt9147dev.input, ABS_Y, 0, gt9147dev.max_y, 0, 0);
input_set_abs_params(gt9147dev.input, ABS_MT_POSITION_X, 0, gt9147dev.max_x, 0, 0);
input_set_abs_params(gt9147dev.input, ABS_MT_POSITION_Y, 0, gt9147dev.max_y, 0, 0);

这些参数定义了触摸屏的物理尺寸和坐标范围,确保触摸坐标能够正确映射到显示区域。

6.4 多点触控槽初始化

初始化多点触控槽,支持最多5个同时触控点:

ret = input_mt_init_slots(gt9147dev.input, MAX_SUPPORT_POINTS, 0);
if (ret)
{goto fail_probe;
}

多点触控槽机制允许系统跟踪多个独立的触控点,每个槽对应一个触控点的状态。

7. 中断处理机制

7.1 中断申请

GT9147通过中断引脚通知主机有新的触摸事件发生。驱动需要申请中断并注册中断处理函数:

ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,gt9147_irq_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,client->name, dev);

这里使用了线程化中断处理机制(threaded IRQ),将中断处理分为两个部分:

  • 顶半部(top half):快速执行,仅做必要的硬件操作
  • 底半部(bottom half):在内核线程上下文中执行,可以进行较长时间的操作

7.2 中断触发方式

中断触发方式配置为下降沿触发(IRQF_TRIGGER_FALLING),因为GT9147的中断引脚在有触摸事件时会从高电平变为低电平。

IRQF_ONESHOT标志表示这是一个不可共享的中断,确保中断处理的独占性。

7.3 中断处理函数

中断处理函数gt9147_irq_handler负责处理触摸事件:

static irqreturn_t gt9147_irq_handler(int irq, void *dev_id)
{int touch_num = 0;int input_x, input_y;int id = 0;int ret = 0;u8 data;u8 touch_data[5];struct gt9147_dev *dev = dev_id;ret = gt9147_read_regs(dev, GT_GSTID_REG, &data, 1);if (data == 0x00){ /* 没有触摸数据,直接返回 */goto fail;}else{ /* 统计触摸点数据 */touch_num = data & 0x0f;}// ... 触摸数据处理
}

中断处理函数首先读取触摸状态寄存器,判断是否有新的触摸事件,然后读取触摸点数据。

8. 触摸数据处理

8.1 触摸状态寄存器

GT9147通过GT_GSTID_REG寄存器报告当前的触摸状态:

ret = gt9147_read_regs(dev, GT_GSTID_REG, &data, 1);
if (data == 0x00)
{ /* 没有触摸数据,直接返回 */goto fail;
}
else
{ /* 统计触摸点数据 */touch_num = data & 0x0f;
}

寄存器的低4位表示当前检测到的触摸点数量,高4位保留。

8.2 触摸点数据读取

根据触摸点数量,读取相应的触摸点数据:

gt9147_read_regs(dev, GT_TP1_REG, touch_data, 5);
id = touch_data[0] & 0x0F;
if (id == 0)
{input_x = touch_data[1] | (touch_data[2] << 8);input_y = touch_data[3] | (touch_data[4] << 8);
}

每个触摸点数据包含5个字节:

  • 字节0:触摸点ID和状态
  • 字节1-2:X坐标(低字节在前)
  • 字节3-4:Y坐标(低字节在前)

8.3 坐标值解析

GT9147返回的坐标值为16位无符号整数,采用小端字节序:

input_x = touch_data[1] | (touch_data[2] << 8);
input_y = touch_data[3] | (touch_data[4] << 8);

通过位操作将两个字节组合成一个16位整数,得到实际的触摸坐标。

9. 多点触控事件报告

9.1 多点触控槽管理

Linux输入子系统使用多点触控槽机制管理多个触控点:

input_mt_slot(dev->input, id);
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, true);

input_mt_slot函数选择当前要操作的触控槽,input_mt_report_slot_state函数报告该槽的触控状态。

9.2 触摸坐标报告

报告触控点的坐标信息:

input_report_abs(dev->input, ABS_MT_POSITION_X, input_x);
input_report_abs(dev->input, ABS_MT_POSITION_Y, input_y);

这些函数将触摸坐标作为绝对值事件报告给输入子系统。

9.3 事件同步

在报告完所有触控点数据后,需要发送同步事件:

input_mt_report_pointer_emulation(dev->input, true);
input_sync(dev->input);

input_sync函数发送EV_SYN事件,标记当前事件包的结束,确保事件的原子性。

10. 驱动注册与注销

10.1 驱动注册

驱动通过module_i2c_driver宏简化注册过程:

module_i2c_driver(gt9147_i2c_driver);

这个宏自动展开为模块初始化和退出函数,简化了驱动代码。

10.2 设备探测函数

probe函数是驱动的核心,负责设备的完整初始化:

static int gt9147_probe(struct i2c_client *client, const struct i2c_device_id *id)
{// 硬件初始化// 输入设备配置// 中断注册// 返回0表示成功
}

如果probe函数执行成功,设备就被认为是正常工作的。

10.3 设备移除函数

remove函数在设备卸载时调用,负责清理资源:

static int gt9147_remove(struct i2c_client *client)
{input_unregister_device(gt9147dev.input);return 0;
}

这里注销了输入设备,释放相关资源。

11. 设备树配置详解

11.1 设备节点定义

设备树中GT9147设备节点的完整定义:

gt9147: gt9147@14 {compatible = "goodix,gt9147", "goodix,gt9xx";reg = <0x14>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_tsc>;interrupt-parent = <&gpio1>;interrupts = <9 0>;reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;status = "okay";
};

11.2 关键属性说明

  • compatible:兼容性字符串,用于驱动匹配
  • reg:I2C设备地址
  • interrupt-parent:中断控制器引用
  • interrupts:中断配置
  • reset-gpios:复位引脚配置
  • interrupt-gpios:中断引脚配置

11.3 引脚控制组

引脚控制组定义了GT9147相关引脚的电气特性:

pinctrl_tsc: tscgrp {fsl,pins = <MX6UL_PAD_SNVS_TAMPER9__GPIO5_IO09	0x10B0MX6UL_PAD_GPIO1_IO09__GPIO1_IO09	0x10B0>;
};

这些配置确保引脚工作在正确的模式和电平。

12. 性能优化与调试

12.1 中断处理优化

中断处理函数应尽量精简,避免长时间操作:

// 顶半部:快速响应
static irqreturn_t gt9147_irq_handler(int irq, void *dev_id)
{// 仅读取状态并唤醒底半部schedule_work(&dev->work);return IRQ_WAKE_THREAD;
}// 底半部:详细处理
static irqreturn_t gt9147_irq_thread(int irq, void *dev_id)
{// 详细的数据读取和事件报告
}

12.2 电源管理

实现电源管理功能,降低系统功耗:

#ifdef CONFIG_PM
static int gt9147_suspend(struct device *dev)
{// 进入低功耗模式
}static int gt9147_resume(struct device *dev)
{// 恢复正常工作模式
}static const struct dev_pm_ops gt9147_pm_ops = {.suspend = gt9147_suspend,.resume = gt9147_resume,
};
#endif

12.3 调试信息输出

添加适当的调试信息,便于问题排查:

dev_dbg(&client->dev, "Touch point: %d, X: %d, Y: %d\n", id, input_x, input_y);

使用不同级别的日志输出,平衡调试信息和系统性能。

13. 错误处理与恢复

13.1 I2C通信错误

处理I2C通信失败的情况:

if (ret < 0) {dev_err(&client->dev, "I2C communication failed: %d\n", ret);// 尝试重新初始化gt9147_ts_reset(dev);return ret;
}

13.2 硬件复位恢复

在通信持续失败时,尝试硬件复位:

static int gt9147_recovery(struct gt9147_dev *dev)
{gt9147_ts_reset(dev);// 重新初始化序列return gt9147_probe(dev->client, NULL);
}

13.3 看门狗机制

实现看门狗机制,防止驱动死锁:

struct timer_list watchdog_timer;static void gt9147_watchdog(unsigned long data)
{struct gt9147_dev *dev = (struct gt9147_dev *)data;if (time_after(jiffies, dev->last_activity + HZ * 5)) {// 5秒无活动,尝试恢复gt9147_recovery(dev);}mod_timer(&dev->watchdog_timer, jiffies + HZ * 5);
}

14. 驱动测试与验证

14.1 基本功能测试

验证驱动的基本功能:

# 检查设备节点
ls /dev/input/# 查看输入设备信息
cat /proc/bus/input/devices# 监听输入事件
getevent

源码仓库位置:https://gitee.com/dream-cometrue/linux_driver_imx6ull

http://www.xdnf.cn/news/19451.html

相关文章:

  • shell脚本第五阶段---shell函数与正则表达式
  • 大模型训练中的 logits 是什么
  • react代码分割
  • 算法题(195):点名
  • WorkManager
  • BGP路由协议(四):工作原理
  • 银河麒麟Kylin系统安装各种板卡(反射内存卡、图像注入卡、串口卡等)步骤及解决方案
  • 微服务-ruoyi-cloud部署
  • 直流无刷电机2
  • 网络编程(4)
  • windows系统中安装zip版本mysql,配置环境
  • React学习教程,从入门到精通, ReactJS - 优点与缺点(5)
  • 线段树相关算法题(5)
  • LangGraph结构化输出详解:让智能体返回格式化数据
  • Midjourney绘画创作入门操作创作(广告创意与设计)
  • XHR 介绍及实践
  • 【Game-Infra】游戏开发的流程,游戏发布的打包与构建(硬件选型,SDK与操作系统,包体管理,弹性构建,构建调优)
  • 基于 GME-Qwen2-VL-7B 实现多模态语义检索方案
  • 人工智能学习:Python相关面试题
  • 零基础学C++,函数篇~
  • Visual Studio内置环境变量有哪些
  • MQTT 连接建立与断开流程详解(一)
  • Redission 实现延迟队列
  • Verilog 硬件描述语言自学——重温数电之典型组合逻辑电路
  • 基于 Spring Boot3 的ZKmall开源商城分层架构实践:打造高效可扩展的 Java 电商系统
  • 大语言模型的“可解释性”探究——李宏毅大模型2025第三讲笔记
  • Linux kernel 多核启动
  • Tomcat 企业级运维实战系列(六):综合项目实战:Java 前后端分离架构部署
  • 〔从零搭建〕数据中枢平台部署指南
  • 汽车加气站操作工证考试的复习重点是什么?