提示:IIC通信-数据上报案例知识点分享
文章目录
- 前言
- 一、涉及到知识点
- 二、相关资料参考
- [三、 驱动案例](#三、 驱动案例)
- 四、知识点总结分享
-
- 驱动代码初步分析
-
- 1、私有设备结构体,存储所有硬件资源
- 2、中断-延时工作队列(防抖)
- [3、i2c_transfer 保证传输可达-重试机制](#3、i2c_transfer 保证传输可达-重试机制)
- 4、输入子系统上报
- 读懂数据手册
- [数据换算(特别重要-特别重要)- 移位运算](#数据换算(特别重要-特别重要)- 移位运算)
- 总结
前言
前面了解了一些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 基本知识点
二、相关资料参考
输入子系统相关:
中断相关:
IIC 知识点:
三、 驱动案例
驱动程序
具体驱动程序完整代码如下
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 = ®_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、中断-延时工作队列(防抖)
触摸程序用到了延时的中断队列 ,相关知识点 强烈建议参考:
创建一个工作项
如上在结构体中封装了
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 = ®_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坐标)
总结
- 这个案例把知识点给串联起来了非常不错
- 对于基础比较弱的需要反复看、分析,很有意义
- 对于位移运算,要想通
- 这其实是一个非常简单的案例,但是要自己去思考、总结。