正点原子 Linux 驱动开发:多点电容触摸屏实验,gt9147 触摸芯片

正点原子 imx6ull 开发板,4.3寸屏

&i2c2 { clock_frequency = <100000>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c2>; status = "okay"; /* 实际是 4.3寸 触摸屏 */ gt9147: gt9147@14 { compatible = "goodix,gt9147","goodix,gt9"; reg = <0X14>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_tsc &pinctrl_tsc_reset >; interrupt-parent = <&gpio1>; interrupts = <9 0>; reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; status = "okay"; };

pinctrl_tsc_reset: tsc_rest { fsl,pins = < MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0 /* 复位引脚 */ >; };
pinctrl_tsc: tscgrp { fsl,pins = < MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x79 /* 中断引脚 */ >; };
#include <linux/module.h> #include <linux/i2c.h> #include <linux/regmap.h> #include <linux/gpio/consumer.h> #include <linux/of_irq.h> #include <linux/interrupt.h> #include <linux/input.h> #include <linux/input/mt.h> #include <linux/debugfs.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/input/mt.h> #include <linux/input/touchscreen.h> #include <linux/i2c.h> #include <asm/unaligned.h> #define GT_CTRL_REG 0X8040 /* GT9147控制寄存器 */ #define GT_MODSW_REG 0X804D /* GT9147模式切换寄存器 */ #define GT_9xx_CFGS_REG 0X8047 /* GT9147配置起始地址寄存器 */ #define GT_1xx_CFGS_REG 0X8050 /* GT1151配置起始地址寄存器 */ #define GT_CHECK_REG 0X80FF /* GT9147校验和寄存器 */ #define GT_PID_REG 0X8140 /* GT9147产品ID寄存器 */ #define GT_GSTID_REG 0X814E /* GT9147当前检测到的触摸情况 */ #define GT_TP1_REG 0X814F /* 第一个触摸点数据地址 */ #define GT_TP2_REG 0X8157 /* 第二个触摸点数据地址 */ #define GT_TP3_REG 0X815F /* 第三个触摸点数据地址 */ #define GT_TP4_REG 0X8167 /* 第四个触摸点数据地址 */ #define GT_TP5_REG 0X816F /* 第五个触摸点数据地址 */ #define MAX_SUPPORT_POINTS 5 /* 最多5点电容触摸 */ struct gt9147_dev { int irq_pin,reset_pin; /* 中断和复位IO */ int irqnum; /* 中断号 */ int irqtype; /* 中断类型 */ int max_x; /* 最大横坐标 */ int max_y; /* 最大纵坐标 */ void *private_data; /* 私有数据 */ struct input_dev *input; /* input结构体 */ struct i2c_client *client; /* I2C客户端 */ }; struct gt9147_dev gt9147; const u8 irq_table[] = {IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING, IRQ_TYPE_LEVEL_LOW, IRQ_TYPE_LEVEL_HIGH}; /* 触发方式 */ /* * @description : 复位GT9147 * @param - client : 要操作的i2c * @param - multidev: 自定义的multitouch设备 * @return : 0,成功;其他负值,失败 */ static int gt9147_ts_reset(struct i2c_client *client, struct gt9147_dev *dev) { int ret = 0; /* 申请复位IO*/ if (gpio_is_valid(dev->reset_pin)) { /* 申请复位IO,并且默认输出高电平 */ ret = devm_gpio_request_one(&client->dev, dev->reset_pin, GPIOF_OUT_INIT_HIGH, "gt9147 reset"); if (ret) { return ret; } } /* 申请中断IO*/ if (gpio_is_valid(dev->irq_pin)) { /* 申请复位IO,并且默认输出高电平 */ ret = devm_gpio_request_one(&client->dev, dev->irq_pin, GPIOF_OUT_INIT_HIGH, "gt9147 int"); if (ret) { return ret; } } /* 4、初始化GT9147,要严格按照GT9147时序要求 */ gpio_set_value(dev->reset_pin, 0); /* 复位GT9147 */ msleep(10); gpio_set_value(dev->reset_pin, 1); /* 停止复位GT9147 */ msleep(10); gpio_set_value(dev->irq_pin, 0); /* 拉低INT引脚 */ msleep(50); gpio_direction_input(dev->irq_pin); /* INT引脚设置为输入 */ return 0; } /* * @description : 从GT9147读取多个寄存器数据 * @param - dev: GT9147设备 * @param - reg: 要读取的寄存器首地址 * @param - buf: 读取到的数据 * @param - len: 要读取的数据长度 * @return : 操作结果 */ static int gt9147_read_regs(struct gt9147_dev *dev, u16 reg, u8 *buf, int len) { int ret; u8 regdata[2]; struct i2c_msg msg[2]; struct i2c_client *client = (struct i2c_client *)dev->client; /* GT9147寄存器长度为2个字节 */ regdata[0] = reg >> 8; regdata[1] = reg & 0xFF; /* msg[0]为发送要读取的首地址 */ msg[0].addr = client->addr; /* ft5x06地址 */ msg[0].flags = !I2C_M_RD; /* 标记为发送数据 */ msg[0].buf = &regdata[0]; /* 读取的首地址 */ msg[0].len = 2; /* reg长度*/ /* msg[1]读取数据 */ msg[1].addr = client->addr; /* ft5x06地址 */ msg[1].flags = I2C_M_RD; /* 标记为读取数据*/ msg[1].buf = buf; /* 读取数据缓冲区 */ msg[1].len = len; /* 要读取的数据长度*/ ret = i2c_transfer(client->adapter, msg, 2); if(ret == 2) { ret = 0; } else { ret = -EREMOTEIO; } return ret; } /* * @description : 向GT9147多个寄存器写入数据 * @param - dev: GT9147设备 * @param - reg: 要写入的寄存器首地址 * @param - val: 要写入的数据缓冲区 * @param - len: 要写入的数据长度 * @return : 操作结果 */ static s32 gt9147_write_regs(struct gt9147_dev *dev, u16 reg, u8 *buf, u8 len) { u8 b[256]; struct i2c_msg msg; struct i2c_client *client = (struct i2c_client *)dev->client; b[0] = reg >> 8; /* 寄存器首地址低8位 */ b[1] = reg & 0XFF; /* 寄存器首地址高8位 */ memcpy(&b[2],buf,len); /* 将要写入的数据拷贝到数组b里面 */ msg.addr = client->addr; /* gt9147地址 */ msg.flags = 0; /* 标记为写数据 */ msg.buf = b; /* 要写入的数据缓冲区 */ msg.len = len + 2; /* 要写入的数据长度 */ return i2c_transfer(client->adapter, &msg, 1); } #if 1 static irqreturn_t gt9147_irq_handler(int irq, void *dev_id) { struct gt9147_dev *dev = dev_id; u8 data, clear_val = 0; u8 temp_buf[5]; // 每个触摸点的原始数据缓存 int i, ret, touch_num, x, y, id; static u8 last_touch_mask = 0; // 用于记录上一次触摸的手指(槽位)状态 u8 current_touch_mask = 0; // 本次触摸状态 static int last_points = -1; // 调试 /* 读取触摸状态寄存器 , 最高位 0x80 表示触摸数据有效 */ ret = gt9147_read_regs(dev, GT_GSTID_REG, &data, 1); if (ret < 0 ) return IRQ_HANDLED; if(!(data & 0x80)) goto exit_clear; // 清除中断 touch_num = data & 0x0f; // 获取触摸点数量 /* 遍历每个触摸点 */ for (i = 0; i < touch_num; i++) { gt9147_read_regs(dev, GT_TP1_REG + i * 8, temp_buf, 5); id = temp_buf[0] & 0x0F; // 手指 ID(0~9),用于 MT 槽位 x = temp_buf[1] | (temp_buf[2] << 8); // 触摸坐标(低字节 + 高字节) y = temp_buf[3] | (temp_buf[4] << 8); /* 报告触摸事件 */ if (id < MAX_SUPPORT_POINTS) { input_mt_slot(dev->input, id); // 选择对应槽位, 使用硬件 id 作为槽位 input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, true);// 报告手指按下状态,标记按下 input_report_abs(dev->input, ABS_MT_POSITION_X, x); // 上报坐标 input_report_abs(dev->input, ABS_MT_POSITION_Y, y); current_touch_mask |= (1 << id); if (i == 0) { // 兼容单点 , 老驱动或单点应用使用 input_report_abs(dev->input, ABS_X, x); input_report_abs(dev->input, ABS_Y, y); input_report_key(dev->input, BTN_TOUCH, 1); } } } // 遍历所有槽位 , 释放已经松开的槽位 for (i = 0; i < MAX_SUPPORT_POINTS; i++) { /* 如果上一次触摸有,而本次没有,标记松开 */ if ((last_touch_mask & (1 << i)) && !(current_touch_mask & (1 << i))) { input_mt_slot(dev->input, i); input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false); // 标记松开 } } last_touch_mask = current_touch_mask; // 更新 /* 单点触摸松开处理: 如果没有触摸点,释放单点触摸按钮 */ if (touch_num == 0) input_report_key(dev->input, BTN_TOUCH, 0); /* 完成多点报告和同步 */ input_mt_report_pointer_emulation(dev->input, true); // 兼容老单点应用 input_sync(dev->input); // 通知内核所有输入事件已就绪 /* 用于调试,避免频繁打印 */ if (touch_num != last_points) { // printk("gt9147 touch: points = %d\n", touch_num); last_points = touch_num; } exit_clear: clear_val = 0; gt9147_write_regs(dev, GT_GSTID_REG, &clear_val, 1); return IRQ_HANDLED; // 写 0 到状态寄存器,清除中断标志 ,通知内核 中断处理完成 } #else static irqreturn_t gt9147_irq_handler(int irq, void *dev_id) { struct gt9147_dev *dev = dev_id; u8 data; u8 clear_val = 0; int i, ret; int touch_num; u8 temp_buf[5]; int id, x, y; /* 1. 读取状态寄存器 */ ret = gt9147_read_regs(dev, GT_GSTID_REG, &data, 1); if (ret < 0) return IRQ_HANDLED; /* 判断数据是否有效(Bit7) */ if (!(data & 0x80)) goto exit_clear; touch_num = data & 0x0f; /* 2. 遍历所有 slot(固定 0~4) */ for (i = 0; i < MAX_SUPPORT_POINTS; i++) { if (i < touch_num) { /* 读取触摸点数据 */ gt9147_read_regs(dev, GT_TP1_REG + i * 8, temp_buf, 5); id = temp_buf[0] & 0x0F; x = temp_buf[1] | (temp_buf[2] << 8); y = temp_buf[3] | (temp_buf[4] << 8); /* ⭐ slot 用 i,不是 id */ input_mt_slot(dev->input, i); input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, true); input_report_abs(dev->input, ABS_MT_POSITION_X, x); input_report_abs(dev->input, ABS_MT_POSITION_Y, y); input_report_abs(dev->input, ABS_MT_TRACKING_ID, id); /* 单点兼容 */ if (i == 0) { input_report_abs(dev->input, ABS_X, x); input_report_abs(dev->input, ABS_Y, y); input_report_key(dev->input, BTN_TOUCH, 1); } } else { /* 释放 slot */ input_mt_slot(dev->input, i); input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false); if (i == 0) input_report_key(dev->input, BTN_TOUCH, 0); } } /* 同步 */ input_mt_report_pointer_emulation(dev->input, true); input_sync(dev->input); printk("gt9147 touch: points = %d\n", touch_num); exit_clear: /* 清中断 */ clear_val = 0; gt9147_write_regs(dev, GT_GSTID_REG, &clear_val, 1); return IRQ_HANDLED; } #endif /* * @description : GT9147中断初始化 * @param - client : 要操作的i2c * @param - multidev: 自定义的multitouch设备 * @return : 0,成功;其他负值,失败 */ static int gt9147_ts_irq(struct i2c_client *client, struct gt9147_dev *dev) { int ret = 0; /* 2,申请中断,client->irq就是IO中断, IRQF_ONESHOT -> 线程没执行完之前,禁止再次触发中断 */ ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, gt9147_irq_handler, irq_table[dev->irqtype] | IRQF_ONESHOT, client->name, &gt9147); // &gt9147 传给 handler if (ret) { dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); return ret; } return 0; } /* * @description : GT9147读取固件 * @param - client : 要操作的i2c * @param - multidev: 自定义的multitouch设备 * @return : 0,成功;其他负值,失败 */ static int gt9147_read_firmware(struct i2c_client *client, struct gt9147_dev *dev) { int ret = 0, version = 0; u16 id = 0; u8 data[7]={0}; /* data[0~3] → PID(ASCII), data[4~5] → VERSION(小端 u16) */ char id_str[5]; ret = gt9147_read_regs(dev, GT_PID_REG, data, 6); if (ret) { dev_err(&client->dev, "Unable to read PID.\n"); return ret; } memcpy(id_str, data, 4); id_str[4] = 0; if (kstrtou16(id_str, 10, &id)) // 字符串形式的数字 转换成 u16 id = 0x1001; version = get_unaligned_le16(&data[4]); // 从字节流读小端16位: version = data[4] | (data[5] << 8) dev_info(&client->dev, "ID %d, version: %04x\n", id, version); switch (id) { /* 由于不同的芯片配置寄存器地址不一样需要判断一下 */ case 1151: case 1158: case 5663: case 5688: /* 读取固件里面的配置信息 */ printk("gt9147 read GT_1xx_CFGS_REG\r\n"); ret = gt9147_read_regs(dev, GT_1xx_CFGS_REG, data, 7); break; default: printk("gt9147 read GT_9xx_CFGS_REG\r\n"); ret = gt9147_read_regs(dev, GT_9xx_CFGS_REG, data, 7); break; } if (ret) { dev_err(&client->dev, "Unable to read Firmware.\n"); return ret; } dev->max_x = (data[2] << 8) + data[1]; dev->max_y = (data[4] << 8) + data[3]; dev->irqtype = data[6] & 0x3; printk("X_MAX: %d, Y_MAX: %d, TRIGGER: 0x%02x", dev->max_x, dev->max_y, dev->irqtype); return 0; } int gt9147_probe(struct i2c_client *client, const struct i2c_device_id *id) { u8 data; int ret; gt9147.client = client; /* 1,获取设备树中的中断和复位引脚 */ gt9147.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0); gt9147.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0); /* 2,复位GT9147 */ ret = gt9147_ts_reset(client, &gt9147); if(ret < 0) { goto fail; } /* 3,初始化GT9147 */ data = 0x02; gt9147_write_regs(&gt9147, GT_CTRL_REG, &data, 1); /* 软复位 */ mdelay(100); data = 0x0; gt9147_write_regs(&gt9147, GT_CTRL_REG, &data, 1); /* 停止软复位 */ mdelay(100); /* 4,初始化GT9147,读取固件 */ ret = gt9147_read_firmware(client, &gt9147); if(ret != 0) { printk("Fail !!! gt9147 check !!\r\n"); goto fail; } /* 5. Input 设备注册 */ gt9147.input = devm_input_allocate_device(&client->dev); if (!gt9147.input) return -ENOMEM; /* 设备基本信息初始化 */ gt9147.input->name = client->name; gt9147.input->id.bustype = BUS_I2C; gt9147.input->dev.parent = &client->dev; /* 声明支持的“事件类型” */ __set_bit(EV_ABS, gt9147.input->evbit); // 绝对坐标(触摸屏必须) __set_bit(EV_KEY, gt9147.input->evbit); // 按键事件(触摸按下/抬起) __set_bit(BTN_TOUCH, gt9147.input->keybit); // 这个设备有一个“触摸按键” __set_bit(INPUT_PROP_DIRECT, gt9147.input->propbit); // 设备属性:直接触摸设备(屏幕) /* 声明支持哪些“坐标事件” */ __set_bit(ABS_X, gt9147.input->absbit); // X 坐标 __set_bit(ABS_Y, gt9147.input->absbit); // Y 坐标 /* 多点(MT协议) */ __set_bit(ABS_MT_POSITION_X, gt9147.input->absbit); // 多点X __set_bit(ABS_MT_POSITION_Y, gt9147.input->absbit); // 多点Y /* 单点(兼容用),设置坐标范围 */ input_set_abs_params(gt9147.input, ABS_X, 0, gt9147.max_x, 0, 0); // X: 0 ~ max_x input_set_abs_params(gt9147.input, ABS_Y, 0, gt9147.max_y, 0, 0); // Y: 0 ~ max_y /* 设置 多点坐标范围 0 ~ max */ input_set_abs_params(gt9147.input, ABS_MT_POSITION_X, 0, gt9147.max_x, 0, 0); input_set_abs_params(gt9147.input, ABS_MT_POSITION_Y, 0, gt9147.max_y, 0, 0); /* 标识“这是第几个手指” , 系统用这个来跟踪:手指移动 手指抬起 */ input_set_abs_params(gt9147.input, ABS_MT_TRACKING_ID, 0, 255, 0, 0); /* 每个手指一个坑位 Slot0 → 手指1 , Slot1 → 手指2 */ /* 初始化多点槽(MT Slot), MAX_SUPPORT_POINTS -> 最多支持5个手指 , INPUT_MT_DIRECT 触摸屏 */ ret = input_mt_init_slots(gt9147.input, MAX_SUPPORT_POINTS, INPUT_MT_DIRECT); if (ret) { dev_err(&client->dev, "MT init failed: %d\n", ret); return ret; } /* 注册 input 设备 */ ret = input_register_device(gt9147.input); if (ret) return ret; /* 6,最后初始化中断 */ ret = gt9147_ts_irq(client, &gt9147); if(ret < 0) { goto fail; } return 0; fail: return ret; } /* * @description : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行 * @param - client : i2c设备 * @return : 0,成功;其他负值,失败 */ int gt9147_remove(struct i2c_client *client) { /* 使用 dev_info 会自动处理换行,且信息更完整 */ dev_info(&client->dev, "gt9147 driver removed\n"); input_unregister_device(gt9147.input); return 0; } /* * 传统驱动匹配表 */ const struct i2c_device_id gt9147_id_table[] = { { "goodix,gt9147", 0}, { /* sentinel */ } }; /* * 设备树匹配表 */ const struct of_device_id gt9147_of_match_table[] = { {.compatible = "goodix,gt9147" }, { /* sentinel */ } }; /* i2c驱动结构体 */ struct i2c_driver gt9147_i2c_driver = { .driver = { .name = "gt9147", .owner = THIS_MODULE, .of_match_table = gt9147_of_match_table, }, .id_table = gt9147_id_table, .probe = gt9147_probe, .remove = gt9147_remove, }; module_i2c_driver(gt9147_i2c_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai"); 

Read more

前端微前端架构:大项目的救命稻草还是自找麻烦?

前端微前端架构:大项目的救命稻草还是自找麻烦? 毒舌时刻 微前端?听起来就像是一群前端工程师为了显得自己很高级,特意发明的复杂术语。不就是把一个大应用拆成几个小应用嘛,至于搞得这么玄乎吗? 你以为拆成微前端就能解决所有问题?别做梦了!到时候你会发现,调试变得更麻烦了,部署变得更复杂了,甚至连样式都可能互相冲突。 为什么你需要这个 1. 大型应用的可维护性:当你的应用变得越来越大,单靠一个团队已经无法高效维护时,微前端可以让不同团队独立开发和部署各自的模块。 2. 技术栈的灵活性:不同的微前端可以使用不同的技术栈,比如一个模块用React,另一个模块用Vue,这样可以根据团队的专长选择最合适的技术。 3. 独立部署:微前端可以独立部署,不需要整个应用一起发布,这样可以减少发布风险,加快发布速度。 4. 团队协作:不同团队可以独立开发各自的微前端,减少代码冲突和沟通成本。 反面教材 // 这是一个典型的单体应用结构 import React from 'react'; import ReactDOM from 'react-dom'

AI大模型驱动的软件开发革命:从代码生成到自愈系统的全流程重构

AI大模型驱动的软件开发革命:从代码生成到自愈系统的全流程重构

目录 * 引言:软件开发范式转移的临界点 * 技术演进:从辅助工具到开发中枢 * 需求分析阶段:智能需求工程师 * 设计阶段:AI架构师登场 * 编码阶段:从Copilot到AutoCode * 测试阶段:智能测试工程师 * 部署与运维:自愈式系统 * 行业应用场景深度解析 * 医疗领域:智能陪诊系统 * 金融领域:智能合规助手 * 技术挑战与解决方案 * 数据隐私保护 * 模型可解释性 * 未来趋势:AI原生开发范式 * 开发工具链重构 * 开发者角色转型 * 产业链影响 * 总结与展望 引言:软件开发范式转移的临界点 在GitHub Copilot用户突破1.5亿的2025年,AI大模型已渗透到软件开发的每个环节。根据微软Build大会披露的数据,某金融企业通过AI开发平台将新功能上线周期从6个月压缩至6周,人力成本降低40%。这场变革不仅体现在效率提升上,更重塑了软件开发的底层逻辑。本文将结合2025年最新实践案例,深度解析AI大模型如何重构软件开发全生命周期。 技术演进:从辅助工具到

基于30年教学沉淀的清华大学AI通识经典:《人工智能的底层逻辑》

基于30年教学沉淀的清华大学AI通识经典:《人工智能的底层逻辑》

📚 引言:为什么你需要这本书? 在人工智能技术席卷全球的今天,你是否曾好奇: * 机器是如何"看见"世界的? * 算法是如何"理解"人类语言的? * 智能系统背后的基本原理是什么? 《人工智能的底层逻辑》正是为解答这些疑问而生!这本书由清华大学张长水教授基于30年教学与科研经验精心撰写,以通俗易懂的方式揭开AI技术的神秘面纱。 你对AI的好奇 《人工智能的底层逻辑》 理解AI基本原理 应用AI思维解决问题 参与AI技术讨论 基于30年教学沉淀的清华大学AI通识经典:《人工智能的底层逻辑》 * 📚 引言:为什么你需要这本书? * 🏛️ 书籍结构与内容亮点 * 📖 系统化的知识架构 * 🧩 独特的"四维解析"框架 * 🌟 特色教学方式 * 🎯 适合哪些读者? * 📊 为什么这本书与众不同? * ✨ 三大核心优势 * 🆚 同类书籍对比 * 🚀 实际应用案例 * 案例1:智能客服系统 * 案例2:医疗影像分析 * 📖 如何高效阅读本书? * 🔍 阅读路线建议 * 💡 学习

5分钟切换不同AI引擎:Codex多模型支持实战指南

5分钟切换不同AI引擎:Codex多模型支持实战指南 【免费下载链接】codex为开发者打造的聊天驱动开发工具,能运行代码、操作文件并迭代。 项目地址: https://gitcode.com/GitHub_Trending/codex31/codex 还在为频繁切换AI模型烦恼?本文将带你掌握Codex的多模型支持功能,轻松切换不同AI引擎,提升开发效率。读完本文,你将学会如何配置、切换和优化不同的AI模型,满足多样化的开发需求。 为什么需要多模型支持? 在开发过程中,不同的任务可能需要不同的AI模型。例如,代码生成可能需要GPT-5的强大能力,而简单的文本处理使用Ollama本地模型更高效。Codex的多模型支持让你可以根据任务需求灵活切换,无需更换工具。 Codex的模型切换功能基于model_family.rs和model_provider_info.rs实现,支持多种主流AI模型和自定义模型配置。 支持的AI模型和提供商 Codex支持多种AI模型和提供商,包括但不限于: 模型系列提供商特点GPT-5系列OpenAI强大的代码生成和理解能力o3/o4-