嵌入式驱动开发详解15(电容触摸屏gt9147)

文章目录

  • 前言
  • 电容触摸屏特点
  • MT触摸消息
    • 电容触摸屏协议
    • 电容屏触摸时序
      • Type A 触摸点信息上报时序
      • Type B 触摸点信息上报时序
    • 多点触摸所使用到的API函数
  • 驱动部分
    • 驱动框图
    • 设备树节点修改
      • 设备树引脚配置
      • 设备节点配置
    • 具体驱动开发
      • I2C驱动框架
      • I2C框架内部实现
  • 参考文献

前言

随着智能手机的发展,电容触摸屏也得到了飞速的发展。相比电阻触摸屏,电容触摸屏有 很多的优势,比如支持多点触控、不需要按压,只需要轻轻触摸就有反应。

电容触摸屏特点

  1. 电容触摸屏是 IIC 接口的,需要触摸IC,以正点原子的 ATK7016 为例,其所使用的触摸屏控制IC为 FT5426,因此所谓的电容触摸驱动就是 IIC 设备驱动。
  2. 触摸 IC 提供了中断信号引脚(INT),可以通过中断来获取触摸信息。
  3. 电容触摸屏得到的是触摸位置绝对信息以及触摸屏是否有按下。
  4. 电容触摸屏不需要校准,当然了,这只是理论上的,如果电容触摸屏质量比较差,或者触摸玻璃和 TFT 之间没有完全对齐,那么也是需要校准的。

因此对应到驱动开发上,我们可以得出电容触摸屏驱动其实就是以下几种linux驱动框架的组合:

  • IIC 设备驱动,因为电容触摸 IC 基本都是 IIC 接口的,因此大框架就是 IIC 设备驱动。
  • 通过中断引脚(INT)向 linux 内核上报触摸信息,因此需要用到 linux 中断驱动框架。坐 标的上报在中断服务函数中完成。
  • 触摸屏的坐标信息、屏幕按下和抬起信息都属于 linux 的 input 子系统,因此向 linux 内 核上报触摸屏坐标信息就得使用 input 子系统。只是,我们得按照 linux 内核规定的规则来上报 坐标信息。

MT触摸消息

电容触摸屏协议

老版本的 linux 内核是不支持多点电容触摸的(Multi-touch,简称 MT),MT 协议是后面加入 的,因此如果使用 2.x 版本 linux 内核的话可能找不到 MT 协议。MT 协议被分为两种类型,Type A 和 TypeB,这两种类型的区别如下:
Type A:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据(此类型在实际使 用中非常少!)。
Type B:适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过 slot 更新某一个 触摸点的信息,FT5426 就属于此类型,一般的多点电容触摸屏 IC 都有此能力。

触摸点的信息通过一系列的 ABS_MT 事件(有的资料也叫消息)上报给 linux 内核,只有 ABS_MT 事件是用于多点触摸的,ABS_MT 事件定义在文件 include/uapi/linux/input.h 中

#define ABS_MT_SLOT		0x2f	/* MT slot being modified */
#define ABS_MT_TOUCH_MAJOR	0x30	/* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR	0x31	/* Minor axis (omit if circular) */
#define ABS_MT_WIDTH_MAJOR	0x32	/* Major axis of approaching ellipse */
#define ABS_MT_WIDTH_MINOR	0x33	/* Minor axis (omit if circular) */
#define ABS_MT_ORIENTATION	0x34	/* Ellipse orientation */
#define ABS_MT_POSITION_X	0x35	/* Center X touch position */
#define ABS_MT_POSITION_Y	0x36	/* Center Y touch position */
#define ABS_MT_TOOL_TYPE	0x37	/* Type of touching device */
#define ABS_MT_BLOB_ID		0x38	/* Group a set of packets as a blob */
#define ABS_MT_TRACKING_ID	0x39	/* Unique ID of initiated contact */
#define ABS_MT_PRESSURE		0x3a	/* Pressure on contact area */
#define ABS_MT_DISTANCE		0x3b	/* Contact hover distance */
#define ABS_MT_TOOL_X		0x3c	/* Center X tool position */
#define ABS_MT_TOOL_Y		0x3d	/* Center Y tool position */

其 中 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 用 来 上 报 触 摸 点 的 (X,Y) 坐 标 信 息 ,ABS_MT_SLOT 用来上报触摸点 ID , 对 于 Type B 类型的设备,需要用到 ABS_MT_TRACKING_ID 事件来区分触摸点。
如果设备支持的话,还可以使用 ABS_MT_TOUCH_MAJOR 和 ABS_MT_WIDTH_MAJOR 这两个消息上报触摸面积信息,关于 其他 ABS_MT 事件的具体含义大家可以查看 Linux 内核中的 multi-touch-protocol.txt 文档。

电容屏触摸时序

Type A 触摸点信息上报时序

下面这个是发送两个触摸点的例子:

ABS_MT_POSITION_X x[0] 
ABS_MT_POSITION_Y y[0] 
SYN_MT_REPORT 
ABS_MT_POSITION_X x[1] 
ABS_MT_POSITION_Y y[1] 
SYN_MT_REPORT
SYN_REPORT

Linux 内核里面也有 Type A 类型的多点触摸驱动,找到 st2332.c 这个驱动文件,具体实例如下:

static irqreturn_t st1232_ts_irq_handler(int irq, void *dev_id)
{
	struct st1232_ts_data *ts = dev_id;
	struct st1232_ts_finger *finger = ts->finger;
	struct input_dev *input_dev = ts->input_dev;
	int count = 0;
	int i, ret;
	ret = st1232_ts_read_data(ts);
	if (ret < 0)
		goto end;
	/* multi touch protocol */
	for (i = 0; i < MAX_FINGERS; i++) {
		if (!finger[i].is_valid)
			continue;
		input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, finger[i].t);
		input_report_abs(input_dev, ABS_MT_POSITION_X, finger[i].x);
		input_report_abs(input_dev, ABS_MT_POSITION_Y, finger[i].y);
		input_mt_sync(input_dev);
		count++;
	}
	/* SYN_MT_REPORT only if no contact */
	if (!count) {
		input_mt_sync(input_dev);
		if (ts->low_latency_req.dev) {
			dev_pm_qos_remove_request(&ts->low_latency_req);
			ts->low_latency_req.dev = NULL;
		}
	} else if (!ts->low_latency_req.dev) {
		/* First contact, request 100 us latency. */
		dev_pm_qos_add_ancestor_request(&ts->client->dev,
						&ts->low_latency_req,
						DEV_PM_QOS_RESUME_LATENCY, 100);
	}
	/* SYN_REPORT */
	input_sync(input_dev);
end:
	return IRQ_HANDLED;
}

其中input_mt_sync和input_sync对应着SYN_MT_REPORT和SYN_REPORT,每 上报完一个触摸点坐标,都要调用 input_mt_sync 函数上报一个 SYN_MT_REPORT 信息。 每上报完一轮触摸点信息就调用一次 input_sync 函数,也就是发送一个 SYN_REPORT 事件。

Type B 触摸点信息上报时序

下面这个是发送两个触摸点的例子:

ABS_MT_SLOT 0 
ABS_MT_TRACKING_ID 45
ABS_MT_POSITION_X x[0] 
ABS_MT_POSITION_Y y[0] 
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID 46 
ABS_MT_POSITION_X x[1] 
ABS_MT_POSITION_Y y[1] 
SYN_REPORT

这里就以 ili210x 这个触摸驱动 IC 为例,看看是 Type B 类型 是 如 何 上 报 触 摸 点 坐 标 信 息 的 。 找 到 ili210x.c 这 个 驱 动 文 件,具体实力2机器人

static void ili210x_report_events(struct input_dev *input,
				  const struct touchdata *touchdata)
{
	int i;
	bool touch;
	unsigned int x, y;
	const struct finger *finger;

	for (i = 0; i < MAX_TOUCHES; i++) {
		input_mt_slot(input, i);

		finger = &touchdata->finger[i];

		touch = touchdata->status & (1 << i);
		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
		if (touch) {
			x = finger->x_low | (finger->x_high << 8);
			y = finger->y_low | (finger->y_high << 8);

			input_report_abs(input, ABS_MT_POSITION_X, x);
			input_report_abs(input, ABS_MT_POSITION_Y, y);
		}
	}

	input_mt_report_pointer_emulation(input, false);
	input_sync(input);
}

调用 input_mt_slot 函数 上报 ABS_MT_SLOT 事 件 。 调用 input_mt_report_slot_state 函 数 上 报 ABS_MT_TRACKING_ID 事件,也就是给 SLOT 关联一个 ABS_MT_TRACKING_ID。使用 input_report_abs 函数上报触摸点对应的(X,Y)坐标值。 使用 input_sync 函数上报 SYN_REPORT 事件。

多点触摸所使用到的API函数

  • input_mt_init_slots:用于初始化 MT 的输入 slots,编写 MT 驱动的时候必须先调用此函 数初始化 slots
  • input_mt_slot:用于产生 ABS_MT_SLOT 事件,告诉内核当前上报的是哪个触摸点的坐标数据
  • input_mt_report_slot_state:用于 Type B 类型,用于产生 ABS_MT_TRACKING_ID 和 ABS_MT_TOOL_TYPE 事 件 , ABS_MT_TRACKING_ID 事 件 给 slot 关 联 一 个 ABS_MT_TRACKING_ID , ABS_MT_TOOL_TYPE 事件指定触摸类型(是笔还是手指等)。
  • input_report_abs:ype A 和 Type B 类型都使用此函数上报触摸点坐标信息,通过 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 事件实现 X 和 Y 轴坐标信息上报
  • input_mt_report_pointer_emulation:如果追踪到的触摸点数量多于当前上报的数量,驱动程序使用 BTN_TOOL_TAP 事件来通 知用户空间当前追踪到的触摸点总数量,然后调用 input_mt_report_pointer_emulation 函数将 use_count 参数设置为 false。否则的话将 use_count 参数设置为 true,表示当前的触摸点数量

驱动部分

驱动框图

编写驱动的框架如下:
①、多点电容触摸芯片的接口,一般都为 I2C 接口,因此驱动主框架肯定是 I2C。
②、linux 里面一般都是通过中断来上报触摸点坐标信息,因此需要用到中断框架。
③、多点电容触摸属于 input 子系统,因此还要用到 input 子系统框架。
④、在中断处理程序中按照 linux 的 MT 协议上报坐标信息。

具体见下图:
在这里插入图片描述
注意:中断没有使用 request_irq 函数申请中断,而是采用了 devm_request_threaded_irq 这个函数,并不是因为前者不能用,而是因为后者有更多的特点,比较适用于此场景。devm_request_threaded_irq具有如下特点:

  1. 用于申请中断,作用和 request_irq 函数类似。
  2. threaded的作用是中断线程化,硬件中断具有最高优先级,只要硬件中断发生,那么内核都会终止当前正在执行的操作,转而去执行中断处理程序。中断线程化以后任务的优先级可能比中断线程的优先级高,这样做的目的就是保证高优先级的任务能被优先处理。大家可能会疑问,不是说可以将比较耗时的中断放到下半部(bottom half) 处理吗?虽然下半部可以被延迟处理,但是依旧先于线程执行,中断线程化可以让这些比较耗时的下半部与进程进行公平竞争
  3. 使用devm_前缀的函数申请到的资源可以由系统自动释放,不需要我们手动处理。

设备树节点修改

设备树节点的修改主要分为两个部分,一个是引脚的配置,一个是设备节点的配置

设备树引脚配置

在这里插入图片描述
如上图所示,该电容屏,主要用到四个引脚,其中两个是I2C的信号引脚,一个是触摸屏的中断引脚,还有一个是电容屏的复位引脚。
复位引脚:复位引脚用的是SNVS_TAMPER9,因此我们需要在&iomuxc_snvs下写入相关信息,具体如下:

&iomuxc_snvs {
	pinctrl-names = "default_snvs";
        pinctrl-0 = <&pinctrl_hog_2>;
        imx6ul-evk {
		pinctrl_tsc_reset: tsc_reset {
						fsl,pins = <
								MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09		0x10B0
						>;
				};
        };
};

中断引脚:此处使用的是GPIO1_IO09,因此我们需要在节点&iomuxc下写入相关信息,具体如下:

&iomuxc {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_hog_1>;
	imx6ul-evk {
		pinctrl_tsc: tscgrp {
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO09__GPIO1_IO09	0x79
			>;
		};
	};
};

I2C引脚:此处使用的是I2C2,UART5_TX和UART5_RX,因此需要同中断引脚一样,在节点&iomuxc下写入相关信息,具体如下:

&iomuxc {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_hog_1>;
	imx6ul-evk {
		pinctrl_i2c2: i2c2grp {
			fsl,pins = <
				MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
				MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
			>;
		};
	};
};

这里需要注意一个很容易犯的错误,我们在配置对应的引脚时一定要先看看这个引脚有没有被配置成其他的功能,如有有的话需要把其他的功能给屏蔽掉,不然会发生冲突。

设备节点配置

引脚配置好之后,需要我们配置相应的设备树节点信息,因为这个设备是挂载在I2C2上的,因此需要把对应的节点信息放在&i2c2上面,对应的注释我都写入代码里了,如下所示:

&i2c2 {
	clock_frequency = <100000>; //时钟频率
	pinctrl-names = "default";  
	pinctrl-0 = <&pinctrl_i2c2>;  //I2C引脚
	status = "okay";  //打开I2C2

	gt9147:gt9147@14 {  //名称@设备地址
		compatible = "hbb,gt9147";  //熟悉值,用于匹配驱动
		reg = <0x14>;		//寄存器地址
		pinctrl-names = "default"; 
		pinctrl-0 = <&pinctrl_tsc  //另外两个引脚
					&pinctrl_tsc_reset>;
		interrupt-parent = <&gpio1>;  //配置中断
		interrupts = <9 0>;  //配置中断IO序号,以及中断触发形式
		reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;  //配置复位引脚,便于驱动直接从节点读取
		interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;  //配置复位引脚,便于驱动直接从节点读取
		status = "okay";
	};
};

这里跟上面引脚定义一样,我们也需要避免出现冲突,防止同一个引脚被不同的驱动拿去使用,我们需要查看pinctrl-0、reset-gpios、interrupt-gpios也就是对应的四个引脚有没有被其他地方使用。

具体驱动开发

具体驱动开发本质上就是I2C,中断,input子系统的综合例程,=接下来我将分析如何搭建出一个框架
在驱动开发之前,需要注意以下两点

  • I2C框架中i2c_add_driver函数创建的驱动节点里面就包含了设备的相关信息,并且能实现I2C通信
  • input子系统是在I2C总线上面开发的,之前开发ap3216设备时,我们直接用dev、class、device三步走的方式创建设备节点,而这次是需要开发一个input子系统的设备节点,因此我们需要借助input_register_device这个函数创建对应的设备节点。

I2C驱动框架

I2C驱动框架如下:

static const struct of_device_id gt9147_of_match[] = {
    {.compatible = "hbb,gt9147"},
    {       }
};   

static const struct i2c_device_id gt9147_id[] = {
    {"gt9147",0},/* 我们用的是of_device_id,i2c_device_id可以写的不对,但是必须要有*/
};

static struct i2c_driver gt9147_driver = {
    .probe = gt9147_probe,
    .remove = gt9147_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "gt9147",
        .of_match_table = gt9147_of_match,
    },
    .id_table = gt9147_id,
};

static int __init gt9147_init(void)
{
    return i2c_add_driver(&gt9147_driver);
}

static void __exit gt9147_exit(void)
{
    i2c_del_driver(&gt9147_driver);
}

I2C框架内部实现

在I2C设备驱动节点创建好之后就可以进行I2C通信了,但是我们还需要input子系统和中断的配合,因此我们需要丰富一下.probe函数,下面是probe函数的具体内容,紧接着按照probe函数的顺序逐一讲解:

static int gt9147_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret;
    u8 data;
    printk("gt9147_probe!!!\r\n");
    gt9147dev.client = client;
 	// 1,获取设备树中的中断和复位引脚
	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);
	// 2,复位GT9147
	ret = gt9147_ts_reset(client, &gt9147dev);
	if(ret < 0) {
		goto fail;
    }
    // 3,初始化GT9147
    data = 0x02;
    gt9147_write_regs(&gt9147dev, GT_CTRL_REG, &data, 1); //软复位
    mdelay(100);
    data = 0x0;
    gt9147_write_regs(&gt9147dev, GT_CTRL_REG, &data, 1); //停止软复位
    mdelay(100);
    // 4,初始化GT9147,读取固件
	ret = gt9147_read_firmware(client, &gt9147dev);
	if(ret != 0) {
		printk("Fail !!! check !!\r\n");
		goto fail;
    }
    // 5,input设备注册 
	gt9147dev.input = devm_input_allocate_device(&client->dev);
	if (!gt9147dev.input) {
		ret = -ENOMEM;
		goto fail;
	}
	gt9147dev.input->name = client->name;
	gt9147dev.input->id.bustype = BUS_I2C;
	gt9147dev.input->dev.parent = &client->dev;
	__set_bit(EV_KEY, gt9147dev.input->evbit);
	__set_bit(EV_ABS, gt9147dev.input->evbit);
	__set_bit(BTN_TOUCH, gt9147dev.input->keybit);
	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);	     
	ret = input_mt_init_slots(gt9147dev.input, MAX_SUPPORT_POINTS, 0);
	if (ret) {
		goto fail;
	}
	ret = input_register_device(gt9147dev.input);
	if (ret)
		goto fail;
    // 6,最后初始化中断 
	ret = gt9147_ts_irq(client, &gt9147dev);
	if(ret < 0) {
		goto fail;
	}
    return 0;
fail:
	return ret;
}
  1. 由于设备不是通电就能使用,还需要进对复位引脚进行读写操作才行,因此需要先获取设备对应的IO引脚号后执行函数gt9147_ts_reset来实现复位GT9147电容屏幕

  2. 接着利用i2c通信来初始化GT9147,这里用到了gt9147_write_regs 函数,后续读取数据会用到对应的gt9147_read_regs函数,重点注意这个地方的寄存器地址是16位的,因此需要把地址写成两个字节分两次传输。利用函数gt9147_read_firmware来读取固件信息,配置对应的像素点等信息。

  3. devm_input_allocate_device是input设备注册,方便我们上传电容屏数据给用户端,同时也需要配置好与input有关的初始化配置,具体见probe函数如何配置的。

  4. 配置引脚中断,利用函数gt9147_ts_irq配置引脚中断,这里我们用的是devm_request_threaded_irq函数,为啥不用request_irq前面已经讲解了,最后只需要在中断中上报input的MT数据即可

上面所有的子函数以及中断处理函数等直接参考最后的参考文献链接地址即可,代码都放入了对应的github仓库里面,这里不在一一展示。

需要特别说明一下的是正点原子提供的驱动只能实现单点触摸,本实验在网上找到了多点触摸的实现方法,通过touch_index和id配合实现,具体多点触摸代码如下:

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[BUFFER_SIZE];
    u16 touch_index = 0;
    int pos = 0;
    int report_num = 0;
    int i;
    static u16 last_index = 0;
    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;
    }
    if(touch_num) {         //有触摸按下
        //读取具体的触摸寄存器
        gt9147_read_regs(dev, GT_TP1_REG, touch_data, BUFFER_SIZE);
        id = touch_data[0];
        touch_index |= (0x01<<id);
        for(i = 0;i < 5; i++){
            if(touch_index |= (0x01 << i)){
                input_x  = touch_data[pos + 1] | (touch_data[pos + 2] << 8);
                input_y  = touch_data[pos + 3] | (touch_data[pos + 4] << 8);

                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);  // 上报触摸点坐标信息
                report_num++;
                if(report_num < touch_num){
                    pos += 8;
                    id = touch_data[pos];
                    touch_index |= (0x01<<id);
                }
            }
            else{
                input_mt_slot(dev->input, i);
                input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false);   // 关闭手指触摸 
            }
        }
    } 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
    gt9147_write_regs(dev, GT_GSTID_REG, &data, 1);

fail:
	return IRQ_HANDLED;
}

下面这个是单点触摸实验代码,可以对比学习一下:

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;
    }
    // 由于GT9147没有硬件检测每个触摸点按下和抬起,因此每个触摸点的抬起和按
    // 下不好处理,尝试过一些方法,但是效果都不好,因此这里暂时使用单点触摸 
    if(touch_num) {         //单点触摸按下
        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);

            input_mt_slot(dev->input, id);
		    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);
        }
    } else if(touch_num == 0){                // 单点触摸释放
        input_mt_slot(dev->input, id);
        input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false);
    }
	input_mt_report_pointer_emulation(dev->input, true);
    input_sync(dev->input);

    data = 0x00;                //向0X814E寄存器写0
    gt9147_write_regs(dev, GT_GSTID_REG, &data, 1);

fail:
	return IRQ_HANDLED;
}

参考文献

  1. 个人专栏系列文章
  2. 正点原子嵌入式驱动开发指南
  3. 对代码有兴趣的同学可以查看链接https://github.com/NUAATRY/imx6ull_dev

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/936915.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

antdv-<a-button>中属性的使用

UI组件库&#xff08;User Interface Component Library&#xff09;是一种预先构建好的、可重用的用户界面元素集合&#xff0c;旨在帮助开发者更快速、更简便地构建用户界面。这些组件通常包括按钮、表单、导航栏、模态框等&#xff0c;能够提供一致的外观和交互风格&#xf…

win服务器的架设、windows server 2012 R2 系统的下载与安装使用

文章目录 windows server 2012 R2 系统的下载与安装使用1 windows server 2012 的下载2 打开 VMware 虚拟机软件&#xff08;1&#xff09;新建虚拟机&#xff08;2&#xff09;设置虚拟机&#xff08;3&#xff09;打开虚拟机 windows server 2012&#xff08;4&#xff09;进…

【ArcGIS微课1000例】0135:自动生成标识码(长度不变,前面自动加0)

文章目录 一、加载实验数据二、BSM计算方法一、加载实验数据 加载专栏《ArcGIS微课实验1000例(附数据)》配套数据中0135.rar中的建筑物数据,如下图所示: 打开属性表,BSM为数据库中要求的字段:以TD_T 1066-2021《不动产登记数据库标准》为例: 计算出来的BSM如下图: 二、B…

康谋方案 | 多源相机数据采集与算法集成测试方案

目录 一、相机组成 二、多源相机采集与测试方案 三、应用案例分享 四、结语 在智能化技术快速发展当下&#xff0c;图像数据的采集与处理逐渐成为自动驾驶、工业等领域的一项关键技术。高质量的图像数据采集与算法集成测试都是确保系统性能和可靠性的关键。随着技术的不断进…

陪玩系统小程序源码/游戏陪玩APP系统用户端有哪些功能?游戏陪玩小程序APP源码开发

多客陪玩系统-游戏陪玩线下预约上门服务等陪玩圈子陪玩社区系统源码 陪玩系统源码&#xff0c;高质量的陪玩系统源码&#xff0c;游戏陪玩APP源码开发&#xff0c;语音陪玩源码搭建: 线上陪玩活动组局与线下家政服务系统的部署需要综合考虑技术选型、开发流程、部署流程、功能实…

运维实战:K8s 上的 Doris 高可用集群最佳实践

今天我们将深入探讨&#xff1a;&#xff1a;如何在 K8s 集群上部署 Compute storage coupled&#xff08;存算耦合&#xff09; 模式的 Doris 高可用集群&#xff1f; 本文&#xff0c;我将为您提供一份全面的实战指南&#xff0c;逐步引导您完成以下关键任务&#xff1a; 配…

从零用java实现 小红书 springboot vue uniapp (2)主页优化

前言 移动端演示 http://8.146.211.120:8081/#/ 前面的文章我们基本完成了主页的布局 今天我们具体的去进行实现 并且分享我开发时遇到的问题 首先先看效果 java仿小红书主页 实现效果为 1.顶端全屏切换 2.上划加载更多 3.下拉当前页整体刷新 顶端全屏切换我们选择 gui-switch…

动手学深度学习-线性神经网络-7softmax回归的简洁实现

目录 初始化模型参数 重新审视Softmax的实现 优化算法 训练 小结 在 线性回归的实现中&#xff0c; 我们发现通过深度学习框架的高级API能够使实现 线性回归变得更加容易。 同样&#xff0c;通过深度学习框架的高级API也能更方便地实现softmax回归模型。 本节如在上一节…

人工智能原理实验四:智能算法与机器学习

一、实验目的 本实验课程是计算机、智能、物联网等专业学生的一门专业课程&#xff0c;通过实验&#xff0c;帮助学生更好地掌握人工智能相关概念、技术、原理、应用等&#xff1b;通过实验提高学生编写实验报告、总结实验结果的能力&#xff1b;使学生对智能程序、智能算法等…

【新界面】基于卷积神经网络的垃圾分类(Matlab)

基于CNN的垃圾识别与分类GUI【新界面】 有需要可直接联系我&#xff0c;基本都在在线&#xff0c;能秒回&#xff01;可加我看演示视频&#xff0c;不懂可以远程教学 1.此项目设计包括两份完整的源代码&#xff0c;有GUI界面的代码和无GUI界面系统的代码。 &#xff08;以下部…

网站访问的基础-HTTP超文本传输协议

BS架构 浏览器Browser⬅➡服务器Server 浏览器和服务器之间通过 IP 地址进行通信&#xff0c;实现数据的请求和传输。 例如&#xff0c;当用户在浏览器中访问一个网站时&#xff0c;浏览器会根据用户输入的网址&#xff08;通过 DNS 解析得到服务器 IP 地址&#xff09;向服…

【C++】递归填充矩阵的理论解析与实现

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;问题描述&#x1f4af;递归实现&#x1f4af;参数解析函数参数详解填充顺序分析递归终止条件 &#x1f4af;示例解析第一层递归第二层递归第三层递归最终输出 &#x1f4af…

Git 仓库托管教程

git远程仓库 常用的远程仓库-->托管服务&#xff1a;github、码云、gitlab等 github需要魔法上网&#xff0c;速度较慢因为在国外且仅仅支持Git&#xff0c;如果不是Git项目是不支持的&#xff1b;码云--gitee国内的代码托管平台&#xff0c;服务器在国内速度快一些&#…

[创业之路-190]:《华为战略管理法-DSTE实战体系》-2-华为DSTE战略管理体系概要

目录 一、DSTE战略管理体系与BLM的关系 1、DSTE战略管理体系概述 2、BLM模型概述 3、DSTE与BLM的关系 二、重新认识流程 1. 流程就是业务本身&#xff0c;流程是业务过程的可视化&#xff1a; 2. 流程是业务最佳路径的经验教训总结&#xff1a; 3. 流程是战略知识资产、…

多智能体架构 Insight-V:针对长链视觉推理瓶颈

多智能体架构 Insight-V&#xff1a;针对长链视觉推理瓶颈 https://arxiv.org/abs/2411.14432 推理智能体与总结智能体协作完成任务&#xff0c;实现复杂视觉任务中的高效推理与总结。其中写了一小段&#xff0c;用迭代 DPO 算法&#xff0c;在每一轮训练中&#xff0c;模型会…

ASP.NET |日常开发中连接Oracle数据库详解

ASP.NET &#xff5c;日常开发中连接Oracle数据库详解 前言一、安装和配置 Oracle 数据访问组件1.1 安装ODP.NET&#xff08;Oracle Data Provider for.NET&#xff09;&#xff1a;1.2 引用相关程序集&#xff1a; 二、配置连接字符串2.1 连接字符串的基本组成部分&#xff1a…

生成树协议STP工作步骤

第一步&#xff1a;选择根桥 优先级比较&#xff1a;首先比较优先级&#xff0c;优先级值越小的是根桥MAC地址比较&#xff1a;如果优先级相同&#xff0c;则比较MAC地址。MAC地址小的是根桥。 MAC地址比较的时候从左往右&#xff0c;一位一位去比 第二步&#xff1a;所有非根…

Redis是什么?Redis和MongoDB的区别在那里?

Redis介绍 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的、基于内存的数据结构存储系统&#xff0c;它可以用作数据库、缓存和消息中间件。以下是关于Redis的详细介绍&#xff1a; 一、数据结构支持 字符串&#xff08;String&#xff09; 这是Redis最…

minio 分布式文件管理

一、minio 是什么&#xff1f; MinIO构建分布式文件系统&#xff0c;MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合使用&#xff0c;它兼容亚马逊 S3 云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频、日志文件、备份数…

【射频IC学习笔记】4 D类功率放大器PA电路设计/loadpull仿真/输出功率及效率PAE计算

一、功率放大器设计指标及电路结构 1. 设计指标 功率放大器的指标要求如下图所示采用D类的开关类型功率放大器&#xff0c;理论上开关类型的PA能够做到100%的效率&#xff0c;但实际上会有一些偏差。像D类功放并不适合高功率射频信号的输出&#xff0c;因为其在射频功率上面的…