Linux驱动-IIC通信-FT5X06驱动-数据上报案例

提示:IIC通信-数据上报案例知识点分享

文章目录

前言

前面了解了一些IIC知识点,在上篇笔记里面引入了 Linux 驱动-I2C输入子系统相关知识点

也就是说 IIC驱动程序中我们引入了 输入子系统,可以看到输入子系统设备,但是它并没有上报上来呀。

所以:目标,借助输入子系统,让触摸数据上报上来。 上报什么,触摸动作、坐标 这些最最基本的数据上报上来。

一、涉及到知识点

整个IIC程序里面,我们发现都是以前学过的知识点的串联应用,这里面真正涉及到IIC 的就是

  • 添加IIC设备 int ret = i2c_add_driver(&ft5x06_driver);
  • 读写数据的时候,调用IIC关联的API 进行数据传输: i2c_transfer

那么涉及到哪些知识点呢?

  • 中断:触摸屏触摸就死中断信号,在中断里面去处理数据上报
  • 如何在中断里面处理数据上报呢? 这里就用到了中断队列,不然卡顿、掉帧等
  • gpio 处理、pinctrl:设备树硬件的描述中少不了gpio , 就是IIC涉及到的pin脚和外设的gpio IIC 通讯用的
  • 输入子系统:中断触发的操作反馈、数据反馈 这里是通过 输入子系统上报上来的。
  • IIC 基本知识点

二、相关资料参考

输入子系统相关:

Linux驱动Input子系统开篇-输入设备和节点

Linux驱动-Input子系统-基本数据结构

驱动-Input子系统-最简单驱动程序

Linux驱动-Input输入子系统-分析匹配规则

中断相关:

RK3568-中断-触摸屏中断

RK3568中断-tasklet

Linux驱动-中断-共享队列

Linux驱动-中断-自定义工作队列

Linux驱动-中断-工作队列传参

中断线程化

IIC 知识点:

驱动-I2C-客户端代码编写-编写设备树

Linux驱动-i2c 驱动框架编写

Linux驱动-IIC完善FT5X06设备节点和驱动

Linux驱动-I2C通信-FT5X06驱动程序部分编写

Linux驱动-通过逻辑分析仪-认识-分析IIC波形

Linux 驱动-I2C输入子系统相关知识点

三、 驱动案例

驱动程序

具体驱动程序完整代码如下

java 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of_device.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/workqueue.h>
#include <linux/err.h>

// 私有设备结构体,存储所有硬件资源
struct ft5x06_dev {
    struct i2c_client *client;
    struct input_dev *input_dev;
    struct gpio_desc *reset_gpio;
    struct delayed_work dwork;  // 延时工作队列 防抖
    int irq;
};

// 前置声明
static void ft5x06_func(struct work_struct *work);
static irqreturn_t ft5x06_handler(int irq, void *args);

/**
 * I2C 单字节读寄存器,失败自动重试2次
 */
static int ft5x06_read_reg(struct ft5x06_dev *priv, u8 reg_addr)
{
    u8 data;
    int retry = 2;
    struct i2c_msg msgs[2] = {
        {
            .addr = priv->client->addr,
            .flags = 0,
            .len = sizeof(reg_addr),
            .buf = &reg_addr,
        },
        {
            .addr = priv->client->addr,
            .flags = I2C_M_RD,
            .len = sizeof(data),
            .buf = &data,
        },
    };

    while (retry--) {
        if (i2c_transfer(priv->client->adapter, msgs, ARRAY_SIZE(msgs)) == ARRAY_SIZE(msgs)) {
            return data;
        }
        msleep(1);
    }
    dev_err(&priv->client->dev, "read reg 0x%02x retry all failed\n", reg_addr);
    return -EIO;
}

/**
 * I2C 多字节写寄存器
 */
static int ft5x06_write_reg(struct ft5x06_dev *priv, u8 reg_addr, u8 data, u16 len)
{
    u8 buff[256];
    struct i2c_msg msgs[] = {
        {
            .addr = priv->client->addr,
            .flags = 0,
            .len = len + 1,
            .buf = buff,
        },
    };
    buff[0] = reg_addr;
    memcpy(&buff[1], &data, len);

    if (i2c_transfer(priv->client->adapter, msgs, ARRAY_SIZE(msgs)) != ARRAY_SIZE(msgs)) {
        dev_err(&priv->client->dev, "write reg 0x%02x I2C transfer failed\n", reg_addr);
        return -EIO;
    }
    return 0;
}

/**
 * 延时工作队列处理函数:读取坐标+上报input事件
 */
static void ft5x06_func(struct work_struct *work)
{
    struct ft5x06_dev *priv = container_of(to_delayed_work(work), struct ft5x06_dev, dwork);
    int TD_STATUS, ret;
    int x = 0, y = 0;

    dev_dbg(&priv->client->dev, "===== touch work queue run =====\n");

    // 1. 优先读取触摸点数寄存器 0x02
    ret = ft5x06_read_reg(priv, 0x02);
    if (ret < 0) {
        dev_err(&priv->client->dev, "read TD_STATUS failed\n");
        return;
    }
    TD_STATUS = ret & 0x0F;
    dev_dbg(&priv->client->dev, "touch point num = %d\n", TD_STATUS);

    // 2. 有触摸才读取XY坐标
    if (TD_STATUS > 0) {
        int xh, xl, yh, yl;
        xh = ft5x06_read_reg(priv, 0x03);
        xl = ft5x06_read_reg(priv, 0x04);
        yh = ft5x06_read_reg(priv, 0x05);
        yl = ft5x06_read_reg(priv, 0x06);

        if (xh >= 0 && xl >= 0 && yh >= 0 && yl >= 0) {
            x = ((xh << 8) | xl) & 0xFFF;
            y = ((yh << 8) | yl) & 0xFFF;
            dev_dbg(&priv->client->dev, "touch down x=%d y=%d\n", x, y);
        }
    }

    // 3. 上报输入事件
    if (TD_STATUS == 0) {
        // 手指抬起:清空坐标,上报松开
        input_report_key(priv->input_dev, BTN_TOUCH, 0);
        input_report_abs(priv->input_dev, ABS_X, 0);
        input_report_abs(priv->input_dev, ABS_Y, 0);
        dev_dbg(&priv->client->dev, "touch up\n");
    } else {
        // 手指按下/移动:上报坐标+按下
        input_report_key(priv->input_dev, BTN_TOUCH, 1);
        input_report_abs(priv->input_dev, ABS_X, x);
        input_report_abs(priv->input_dev, ABS_Y, y);
    }
    input_sync(priv->input_dev);
}

/**
 * 中断服务函数:防抖延时10ms再读取,合并连续中断
 */
static irqreturn_t ft5x06_handler(int irq, void *args)
{
    struct ft5x06_dev *priv = args;
    dev_dbg(&priv->client->dev, "==========ft5x06_handler enter==========\n");
    // 取消未执行任务,重新延迟10ms执行,防抖
    mod_delayed_work(system_wq, &priv->dwork, msecs_to_jiffies(10));
    return IRQ_HANDLED;
}

static int ft5x06_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret;
    struct ft5x06_dev *priv;

    dev_info(&client->dev, "###### This is ft5x06 probe ######\n");

    // 分配私有设备内存
    priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;
    priv->client = client;
    i2c_set_clientdata(client, priv);

    // 初始化延时工作队列
    INIT_DELAYED_WORK(&priv->dwork, ft5x06_func);

    // 获取复位GPIO(devm自动释放,无需手动put)
    priv->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_LOW);
    if (IS_ERR(priv->reset_gpio)) {
        ret = PTR_ERR(priv->reset_gpio);
        dev_err(&client->dev, "get reset-gpios failed, ret=%d\n", ret);
        goto err_priv_free;
    }

    // 硬件复位时序:加长延时,保证芯片校准完成
    gpiod_set_value(priv->reset_gpio, 0);
    msleep(200);    // 复位低电平200ms
    gpiod_set_value(priv->reset_gpio, 1);
    msleep(300);    // 等待芯片上电+自校准300ms

    // ========== 关键寄存器初始化(解决触摸迟钝核心配置)==========
    // 1. 开启INT脉冲中断模式
    ret = ft5x06_write_reg(priv, 0xA4, 0x01, 1);
    if (ret < 0)
        dev_warn(&client->dev, "set int mode reg 0xA4 failed\n");

    // 2. 强制全速Active模式,禁止Monitor低速休眠(解决触摸不灵敏)
    ret = ft5x06_write_reg(priv, 0xA5, 0x00, 1);
    if (ret < 0)
        dev_warn(&client->dev, "set active power mode reg 0xA5 failed\n");

    // 3. 降低触摸灵敏度阈值 0x20,轻触即可识别(原0x4B偏硬)
    ret = ft5x06_write_reg(priv, 0x80, 0x20, 1);
    if (ret < 0)
        dev_warn(&client->dev, "set touch threshold reg 0x80 failed\n");
    // ==========================================================

    // 申请外部中断:下降沿触发
    priv->irq = client->irq;
    ret = request_irq(priv->irq, ft5x06_handler, IRQF_TRIGGER_FALLING, "ft5x06_irq", priv);
    if (ret < 0) {
        dev_err(&client->dev, "request irq %d failed ret=%d\n", priv->irq, ret);
        goto err_priv_free;
    }

    // 分配input设备
    priv->input_dev = input_allocate_device();
    if (!priv->input_dev) {
        ret = -ENOMEM;
        dev_err(&client->dev, "input allocate device fail\n");
        goto err_free_irq;
    }
    priv->input_dev->name = "ft5x06_dev";
    priv->input_dev->dev.parent = &client->dev;

    // 配置支持的事件类型
    set_bit(EV_KEY, priv->input_dev->evbit);
    set_bit(BTN_TOUCH, priv->input_dev->keybit);
    set_bit(EV_ABS, priv->input_dev->evbit);
    set_bit(ABS_X, priv->input_dev->absbit);
    set_bit(ABS_Y, priv->input_dev->absbit);

    // 屏幕坐标范围(根据你的屏幕分辨率修改)
    input_set_abs_params(priv->input_dev, ABS_X, 0, 800, 0, 0);
    input_set_abs_params(priv->input_dev, ABS_Y, 0, 1280, 0, 0);

    // 注册input设备
    ret = input_register_device(priv->input_dev);
    if (ret < 0) {
        dev_err(&client->dev, "input register device fail ret=%d\n", ret);
        goto err_input_free;
    }

    dev_info(&client->dev, "ft5x06 probe success, touch full speed mode\n");
    return 0;

// 错误回收分支
err_input_free:
    input_free_device(priv->input_dev);
err_free_irq:
    free_irq(priv->irq, priv);
err_priv_free:
    devm_kfree(&client->dev, priv);
    return ret;
}

static int ft5x06_remove(struct i2c_client *client)
{
    struct ft5x06_dev *priv = i2c_get_clientdata(client);
    dev_info(&client->dev, "###### This is ft5x06 remove ######\n");

    // 1. 等待延时工作队列完全执行完毕,防止野指针访问
    flush_delayed_work(&priv->dwork);
    // 2. 释放中断
    free_irq(priv->irq, priv);
    // 3. 注销输入设备
    input_unregister_device(priv->input_dev);
    return 0;
}

static const struct of_device_id ft5x06_id[] = {
    { .compatible = "my-ft5x06" },
    { /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, ft5x06_id);

static struct i2c_driver ft5x06_driver = {
    .driver = {
        .owner = THIS_MODULE,
        .name = "my-ft5x06",
        .of_match_table = ft5x06_id,
    },
    .probe = ft5x06_probe,
    .remove = ft5x06_remove,
};

static int __init ft5x06_driver_init(void)
{
    int ret = i2c_add_driver(&ft5x06_driver);
    if (ret < 0) {
        pr_err("i2c_add_driver failed ret=%d\n", ret);
        return ret;
    }
    pr_info("###### ft5x06 driver register success ######\n");
    return 0;
}

static void __exit ft5x06_driver_exit(void)
{
    i2c_del_driver(&ft5x06_driver);
    pr_info("###### ft5x06 driver unregister ######\n");
}

module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("FT5x06 Capacitive Touch Driver RK3568 Fix Lag/Slow Touch");
MODULE_AUTHOR("Dev");

实验验证

加载驱动验证结果

命令:insmod ft5x06_driver.ko

打印了日志信息,加载成功,IIC 驱动注册成功,生成了输入子系统 input: ft5x06_dev as /devices/platform/fe5a0000.i2c/i2c-1/1-0038/input/input4

格式化查看触摸上报事件(直观看到 XY 实时变化)- evtest /dev/input/event4

命令:evtest /dev/input/event4

原始二进制打印-hexdump /dev/input/event4

命令:hexdump /dev/input/event4,这个是原始二进制输出打印的上报数据,强烈建议经过翻译的民乐: evtest /dev/input/event4

四、知识点总结分享

驱动代码初步分析

1、私有设备结构体,存储所有硬件资源

就是把硬件资源放在一个结构体里面去

java 复制代码
struct ft5x06_dev {
    struct i2c_client *client;
    struct input_dev *input_dev;
    struct gpio_desc *reset_gpio;
    struct delayed_work dwork;  // 延时工作队列 防抖
    int irq;
};

所以,所有的硬件资源调用都是通过它来间接调用的,如下:

私有结构体的用处

封装的目的无外乎就是为了解耦,规避大量的全局变量,传参更方便。

分配私有地址
中断传参

中断中需要用,使用中断传参: ret = request_irq(priv->irq, ft5x06_handler, IRQF_TRIGGER_FALLING, "ft5x06_irq", priv);

container_of 获取结构体指针

源码中:

java 复制代码
/**
 * 延时工作队列处理函数:读取坐标+上报input事件
 */
static void ft5x06_func(struct work_struct *work)
{
    struct ft5x06_dev *priv = container_of(to_delayed_work(work), struct ft5x06_dev, dwork);

分析:container_of 方法

java 复制代码
#define container_of(ptr, type, member) ({                      \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);        \
    (type *)( (char *)__mptr - offsetof(type,member) );})

三个入参含义

  • ptr:结构体内部某个成员的指针(你现在就是 &priv->dwork 的地址)
  • type:外层完整大结构体类型(struct ft5x06_dev)
  • member:该成员在大结构体里的变量名(dwork)

所以这个方法不就拿到了结构体ft5x06_dev 的指针吗

to_delayed_work 这个api 是做什么的,本身就是拿到成员内部变量 struct delayed_work dwork 的指针

2、中断-延时工作队列(防抖)

触摸程序用到了延时的中断队列 ,相关知识点 强烈建议参考:

Linux驱动-中断-共享队列

Linux驱动-中断-自定义工作队列

创建一个工作项

如上在结构体中封装了

java 复制代码
struct ft5x06_dev {
    struct i2c_client *client;
    struct input_dev *input_dev;
    struct gpio_desc *reset_gpio;
    struct delayed_work dwork;  // 延时工作队列 防抖
    int irq;
};
初始化工作队列

在probe 函数中初始化工作队列,并制定了工作队列的回调函数 ft5x06_func

java 复制代码
 INIT_DELAYED_WORK(&priv->dwork, ft5x06_func);
声明中断回调函数
java 复制代码
// 前置声明
static void ft5x06_func(struct work_struct *work);
java 复制代码
/**
 * 延时工作队列处理函数:读取坐标+上报input事件
 */
static void ft5x06_func(struct work_struct *work)
{
    struct ft5x06_dev *priv = container_of(to_delayed_work(work), struct ft5x06_dev, dwork);
    int TD_STATUS, ret;
    int x = 0, y = 0;

    dev_dbg(&priv->client->dev, "===== touch work queue run =====\n");

    // 1. 优先读取触摸点数寄存器 0x02
    ret = ft5x06_read_reg(priv, 0x02);
    if (ret < 0) {
        dev_err(&priv->client->dev, "read TD_STATUS failed\n");
        return;
    }
    TD_STATUS = ret & 0x0F;
    dev_dbg(&priv->client->dev, "touch point num = %d\n", TD_STATUS);

    // 2. 有触摸才读取XY坐标
    if (TD_STATUS > 0) {
        int xh, xl, yh, yl;
        xh = ft5x06_read_reg(priv, 0x03);
        xl = ft5x06_read_reg(priv, 0x04);
        yh = ft5x06_read_reg(priv, 0x05);
        yl = ft5x06_read_reg(priv, 0x06);

        if (xh >= 0 && xl >= 0 && yh >= 0 && yl >= 0) {
            x = ((xh << 8) | xl) & 0xFFF;
            y = ((yh << 8) | yl) & 0xFFF;
            dev_dbg(&priv->client->dev, "touch down x=%d y=%d\n", x, y);
        }
    }

    // 3. 上报输入事件
    if (TD_STATUS == 0) {
        // 手指抬起:清空坐标,上报松开
        input_report_key(priv->input_dev, BTN_TOUCH, 0);
        input_report_abs(priv->input_dev, ABS_X, 0);
        input_report_abs(priv->input_dev, ABS_Y, 0);
        dev_dbg(&priv->client->dev, "touch up\n");
    } else {
        // 手指按下/移动:上报坐标+按下
        input_report_key(priv->input_dev, BTN_TOUCH, 1);
        input_report_abs(priv->input_dev, ABS_X, x);
        input_report_abs(priv->input_dev, ABS_Y, y);
    }
    input_sync(priv->input_dev);
}
开始中断任务 mod_delayed_work -(自带 "取消旧任务 + 重置延时" 能力,防抖场景专用)

在中断中使用:

java 复制代码
/**
 * 中断服务函数:防抖延时10ms再读取,合并连续中断
 */
static irqreturn_t ft5x06_handler(int irq, void *args)
{
    struct ft5x06_dev *priv = args;
    dev_dbg(&priv->client->dev, "==========ft5x06_handler enter==========\n");
    // 取消未执行任务,重新延迟10ms执行,防抖
    mod_delayed_work(system_wq, &priv->dwork, msecs_to_jiffies(10));
    return IRQ_HANDLED;
}

3、i2c_transfer 保证传输可达-重试机制

命令i2c_transfer 不一定保证每次OK,传输过去了,要有个重试机制。

java 复制代码
/**
 * I2C 单字节读寄存器,失败自动重试2次
 */
static int ft5x06_read_reg(struct ft5x06_dev *priv, u8 reg_addr)
{
    u8 data;
    int retry = 2;
    struct i2c_msg msgs[2] = {
        {
            .addr = priv->client->addr,
            .flags = 0,
            .len = sizeof(reg_addr),
            .buf = &reg_addr,
        },
        {
            .addr = priv->client->addr,
            .flags = I2C_M_RD,
            .len = sizeof(data),
            .buf = &data,
        },
    };

    while (retry--) {
        if (i2c_transfer(priv->client->adapter, msgs, ARRAY_SIZE(msgs)) == ARRAY_SIZE(msgs)) {
            return data;
        }
        msleep(1);
    }
    dev_err(&priv->client->dev, "read reg 0x%02x retry all failed\n", reg_addr);
    return -EIO;
}

/**
 * I2C 多字节写寄存器
 */
static int ft5x06_write_reg(struct ft5x06_dev *priv, u8 reg_addr, u8 data, u16 len)
{
    u8 buff[256];
    struct i2c_msg msgs[] = {
        {
            .addr = priv->client->addr,
            .flags = 0,
            .len = len + 1,
            .buf = buff,
        },
    };
    buff[0] = reg_addr;
    memcpy(&buff[1], &data, len);

    if (i2c_transfer(priv->client->adapter, msgs, ARRAY_SIZE(msgs)) != ARRAY_SIZE(msgs)) {
        dev_err(&priv->client->dev, "write reg 0x%02x I2C transfer failed\n", reg_addr);
        return -EIO;
    }
    return 0;
}

看看实战项目中也是类似的保证机制:

4、输入子系统上报

其实很简单,完整一帧执行流程拆解(触摸按下举例),具体API 自行研究。

java 复制代码
// 1. 上报有手指按下
input_report_key(..., BTN_TOUCH, 1);
// 2. 上报当前屏幕坐标X
input_report_abs(..., ABS_X, 497);
// 3. 上报当前屏幕坐标Y
input_report_abs(..., 396);
// 4. 同步提交,内核输出一整组事件到event4
input_sync(priv->input_dev);

读懂数据手册

我们看一下 我们进行IIC通讯 读写寄存器时候与寄存器进行了哪些通讯:

java 复制代码
    // 1. 优先读取触摸点数寄存器 0x02
    ret = ft5x06_read_reg(priv, 0x02);

// 1. 开启INT脉冲中断模式
    ret = ft5x06_write_reg(priv, 0xA4, 0x01, 1);
    if (ret < 0)
        dev_warn(&client->dev, "set int mode reg 0xA4 failed\n");
    // 2. 强制全速Active模式,禁止Monitor低速休眠(解决触摸不灵敏)
    ret = ft5x06_write_reg(priv, 0xA5, 0x00, 1);
    if (ret < 0)
        dev_warn(&client->dev, "set active power mode reg 0xA5 failed\n");
    // 3. 降低触摸灵敏度阈值 0x20,轻触即可识别(原0x4B偏硬)
    ret = ft5x06_write_reg(priv, 0x80, 0x20, 1);



 xh = ft5x06_read_reg(priv, 0x03);
 xl = ft5x06_read_reg(priv, 0x04);
 yh = ft5x06_read_reg(priv, 0x05);
 yl = ft5x06_read_reg(priv, 0x06);

直接去看使用手册:

寄存器0x02

基础信息

  • 地址:0x02
  • 名称:TD_STATUS 触摸状态寄存器
  • 作用:存放当前屏幕检测到的触摸点数,是你驱动里最核心的读取寄存器!

寄存器0xA4

  • 地址:0xA4
  • 名称:ID_G_MODE
  • 功能:配置 FT5x06 芯片中断 INT 引脚工作模式,用来通知主控(RK3568)有没有触摸事件
    两个模式:
  • 0x00 → Polling mode(轮询模式)
    INT 引脚不会主动产生电平变化;驱动必须不断循环 I2C 读取触摸状态寄存器 (0x02),轮询查询是否有触摸。
    缺点:持续占用 I2C 总线、CPU 空转,功耗高,性能差。
  • 0x01 → Trigger mode(触发/脉冲中断模式)
    有触摸按下、滑动、抬起时,芯片自动拉低 INT 引脚产生中断信号;
    没有触摸时,INT 保持空闲电平。
    主控平时休眠,只有发生触摸才被中断唤醒,也就是你驱动里使用的中断方案。

寄存器A5

基础信息

  • 地址:0xA5
  • 名称:ID_G_PMODE
  • 功能:配置触摸屏芯片扫描功耗模式,决定触控面板的扫描帧率、响应速度、功耗
    也就是解决你之前「触摸迟钝、要摸好几次才有反应」最核心的寄存器!

三种模式定义(手册原文)

数值 模式名称 工作特点 触摸响应表现
0x00 Active 全速活跃模式 芯片持续高速扫描触控电极,刷新率最高(典型~100Hz)
0x01 Monitor 监控低速模式 一段时间无触摸后,自动降速低频扫描,降低功耗 ⚠️ 扫描变慢,触摸反应迟钝,经常要点好几下才能唤醒识别(你之前遇到的现象)
0x03 Hibernate 深度休眠 几乎停止扫描,功耗极低;需要强烈触摸唤醒 ❌ 几乎无法正常使用,只适合长期闲置场景

寄存器 0x80

基础信息

  • 地址:0x80
  • 名称:ID_G_THGROUP
  • 功能:有效触摸检测阈值(灵敏度门槛)
  • 电容触摸芯片通过检测电极电容变化判断是否有触摸;这个寄存器设定「电容变化达到多大才算一次有效触摸」。

举两个代码里的例子:

  • 原先代码:0x4B

    真实阈值 = 0x4B ×4 = 75 ×4 = 300

    比默认 280 门槛更高,需要更大力按压,轻触容易识别不到。

  • 修改后的配置:0x20

    真实阈值 = 0x20 ×4 =32 ×4 = 128

    阈值大幅降低,手指轻轻碰到屏幕就能识别,提升灵敏度。

寄存器 0x03-0x04-0x05-0x06 触摸坐标

横竖坐标就是通过两组数据来的,一个高8位中的3:0 位数+低8位组成

数据换算(特别重要-特别重要)- 移位运算

我们这里将2个知识点,状态和坐标问题

读取当前触摸状态

多指触摸先不管,就看单触摸场景

那就是前四位也就是低四位是有效位,那么就是要把高四位去掉,所以获取得到数据后与0x0F 运算不就行了嘛。

java 复制代码
 TD_STATUS = ret & 0x0F;

举例:

如果读到寄存器数值 0x12(二进制 0001 0010)

0x12 & 0x0F = 0x02,正确拿到触摸点数 2。

记住: 有效位是低四位,那么把高四位去掉就可以了,怎么去 直接 & 0x0F

0x0F = 00001111

& 0x0F 的作用:只保留低 4 位,屏蔽高 4 位无效数据,正好对应寄存器 0x02 Bit 3:0 触摸点数。

获取当前坐标

java 复制代码
 if (TD_STATUS > 0) {
        int xh, xl, yh, yl;
        xh = ft5x06_read_reg(priv, 0x03);
        xl = ft5x06_read_reg(priv, 0x04);
        yh = ft5x06_read_reg(priv, 0x05);
        yl = ft5x06_read_reg(priv, 0x06);

        if (xh >= 0 && xl >= 0 && yh >= 0 && yl >= 0) {
            x = ((xh << 8) | xl) & 0xFFF;
            y = ((yh << 8) | yl) & 0xFFF;
            dev_dbg(&priv->client->dev, "touch down x=%d y=%d\n", x, y);
        }
    }

X/Y 坐标都是高位低四位+低位8位数据,那就需要拼接数据了。

所以基于上面我们分析:x = ((xh << 8) | xl) & 0xFFF; 就是高位左移8位 或 上 低8位组成十六位数的二进制,然后再与上0xFFF

具体拼接过程距离:

java 复制代码
步骤1:TOUCH1_XH << 8
TOUCH1_XH = 0100 1010
左移8位后 = 0100 1010 0000 0000
              ^--XH内容--^ ^--空位--^

步骤2:| TOUCH1_XL(按位或)
0100 1010 0000 0000
              | 0000 0000 1100 1100  (把XL放到低8位)
              = 0100 1010 1100 1100

步骤3:& 0x0FFF(清除高4位)
0100 1010 1100 1100
& 0000 1111 1111 1111  (0x0FFF)
= 0000 1010 1100 1100
   ^^ 高4位清零了
= 0x0ACC(正确的12位X坐标)

总结

  • 这个案例把知识点给串联起来了非常不错
  • 对于基础比较弱的需要反复看、分析,很有意义
  • 对于位移运算,要想通
  • 这其实是一个非常简单的案例,但是要自己去思考、总结。