正点原子 I.MX6ULL MINI 开发板学习笔记:GT9147 电容触摸屏驱动

这篇笔记继续正点原子 I.MX6ULL MINI 开发板裸机学习,主题是GT9147 电容触摸屏驱动。代码来自实际工程 /home/zhao/code/2026/touch_screen,通过 I2C2 + 中断读取触摸坐标。
1. 完成什么实验?
驱动 GT9147 电容触摸芯片,通过 I2C2 读取触摸点坐标,并用 GPIO1_IO09 中断触发读取。
2. 电容屏和电阻屏有什么区别?
电容屏靠人体感应电荷工作,支持多点触控,表面覆盖玻璃,寿命长、手感好;电阻屏靠压力使两层导电膜接触,通常只支持单点,表面为软屏,容易磨损。
3. GT9147 使用什么接口?
GT9147 使用 I2C 通信,同时需要 INT 中断引脚和 RST 复位引脚。代码中 INT 接 GPIO1_IO09,RST 接 GPIO5_IO09。
4. GT9147 的 I2C 地址是多少?
代码中使用的设备地址是 0x14。GT9147 在复位时序中采样 INT 引脚电平来选择地址:本代码的复位时序是 INT 与 RST 同时拉低,10ms 后释放 RST,INT 继续保持低电平约 100ms——复位释放期间 INT 保持低电平,选中的就是 0x14;如果复位释放后把 INT 拉高,则选中 0x5D。所以代码的时序和所用地址 0x14 是一致的。
5. 怎么读取坐标?
先读 0x814E 获取触摸点数并清标志位,再从 0x8150、0x8158、0x8160 等寄存器读取每个触摸点的 4 字节坐标(X 低字节、X 高字节、Y 低字节、Y 高字节)。

6. 中断方式还是轮询方式?
代码使用 GPIO1_IO09 上升沿中断(ICR1[19:18]=10,I.MX6ULL 的 ICR 编码为 00=低电平/01=高电平/10=上升沿/11=下降沿)。触摸发生时 GT9147 拉高 INT,触发中断服务函数 read_points()。
7. 真实可用的 GT9147 代码
下面是工程中验证过的 gt9147.c 核心代码。
struct GT9147_Device
{
int num;
int available;
unsigned short x[5];
unsigned short y[5];
};
struct GT9147_Device gt9147_dev;
const unsigned short GT_TP[5] = {0x8150, 0x8158, 0x8160, 0x8168, 0x8170};
void read_gt9147(unsigned short reg_addr, unsigned char *data, unsigned int len)
{
struct I2C_Msg msg =
{
.dev_addr = 0x14,
.reg_addr = reg_addr,
.reg_len = 2,
.data = data,
.direction = I2C_Read,
.len = len
};
transfer(I2C2, &msg);
}
void write_gt9147(unsigned short reg_addr, unsigned char *data, unsigned int len)
{
struct I2C_Msg msg =
{
.dev_addr = 0x14,
.reg_addr = reg_addr,
.reg_len = 2,
.data = data,
.direction = I2C_Write,
.len = len
};
transfer(I2C2, &msg);
}
void read_points(void)
{
unsigned char buffer[24];
read_gt9147(0x814E, buffer, 1);
int num = buffer[0] & 0x0F;
buffer[0] = 0;
write_gt9147(0x814E, buffer, 1);
if(num != 0)
{
gt9147_dev.available = 1;
gt9147_dev.num = num;
}
int i;
for(i = 0;i < num;++i)
{
read_gt9147(GT_TP[i], buffer, 4);
gt9147_dev.x[i] = buffer[1] << 8 | buffer[0];
gt9147_dev.y[i] = buffer[3] << 8 | buffer[2];
}
}
void touch_screen_interrupt_handler(void)
{
if((GPIO1->ISR & (1 << 9)) != 0)
{
read_points();
GPIO1->ISR |= (1 << 9);
}
}
void init_gt9147(void)
{
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO09_GPIO1_IO09, 0); // INT
IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER9_GPIO5_IO09, 0); // RST
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO09_GPIO1_IO09, 0x10B0);
IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER9_GPIO5_IO09, 0x10B0);
gpio_configt_t t =
{
.pin_direction = gpio_direction_output,
.default_vaule = 0
};
init_gpio(GPIO1, 9, &t);
init_gpio(GPIO5, 9, &t);
write_gpio(GPIO1, 9, 0);
write_gpio(GPIO5, 9, 0);
delay_ms(10);
write_gpio(GPIO5, 9, 1);
delay_ms(100);
unsigned char buffer[24] = {0};
read_gt9147(0x8140, buffer, 4);
printf("product ID = %s\n", (char *)buffer);
read_gt9147(0x804d, buffer, 1);
printf("interrupt way 0 = %d\r\n", buffer[0] & 0x03);
read_gt9147(0x8056, buffer, 1);
printf("interrupt way 1 = %d\r\n", buffer[0] & 0x03);
t.pin_direction = gpio_direction_input;
init_gpio(GPIO1, 9, &t);
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO09_GPIO1_IO09, 0x80);
GPIO1->ICR1 &= ~(3 << 18);
GPIO1->ICR1 |= (2 << 18);
GPIO1->IMR |= (1 << 9);
system_interrupt_register(touch_screen_interrupt_handler, GPIO1_Combined_0_15_IRQn);
GIC_EnableIRQ(GPIO1_Combined_0_15_IRQn);
buffer[0] = 0;
write_gt9147(0x8040, buffer, 1);
delay_ms(200);
}
#include "gt9147.h"
#include "lcd.h"
int main(void)
{
init_clock();
system_interrupt_init();
init_uart1();
init_lcd();
init_gt9147();
while (1)
{
if (gt9147_dev.available)
{
gt9147_dev.available = 0;
for (int i = 0; i < gt9147_dev.num; i++)
{
printf("point %d: x=%d, y=%d\n", i,
gt9147_dev.x[i], gt9147_dev.y[i]);
}
}
delay_ms(50);
}
return 0;
}

8. 关键点说明
- 接口:I2C2 通信 + GPIO1_IO09 中断 + GPIO5_IO09 复位。
- I2C 地址:代码中使用 0x14(复位释放期间 INT 保持低电平选中 0x14;INT 拉高则选中 0x5D)。
- 寄存器:0x814E 为状态/点数寄存器;0x8150/58/60... 为各点坐标寄存器;0x804D 低位与 INT 触发方式相关;0x8056 实为刷新率寄存器 Refresh_Rate(代码里打印标签 interrupt way 1 是笔误)。
- 坐标格式:每个点 4 字节,低高字节组合成 X 和 Y。
- 中断触发:GPIO1_IO09 配置为上升沿中断,触摸时自动触发读取。
- 清标志:读 0x814E 后必须写 0 清标志,否则不会再次触发中断。
- 点数钳位:0x814E 低 4 位给出的点数最大可到 15,而 GT_TP 数组只有 5 项,严谨写法应在 read_points() 里加 if(num > 5) num = 5; 钳位,防止数组越界。
9. 实验现象
程序运行后,用手指触摸屏幕,串口会打印当前触摸点坐标,例如 "point 0: x=312, y=198"。单点和多点触摸都能检测到最多 5 个点的坐标。
总结
| 问题 | 答案 |
|---|---|
| 完成什么实验? | 驱动 GT9147,I2C2 读取触摸坐标,GPIO1_IO09 中断触发 |
| 电容屏 vs 电阻屏? | 电容屏靠电荷感应,支持多点,寿命长 |
| GT9147 接口? | I2C + INT 中断 + RST 复位 |
| I2C 地址? | 代码中用 0x14(也可能为 0x5D) |
| 怎么读坐标? | 读 0x814E 获取点数,清标志,再读 0x8150/58/60... |
| 中断还是轮询? | GPIO1_IO09 上升沿中断 |
GT9147 触摸驱动把 I2C、GPIO 中断和 LCD 显示串在了一起,是人机交互的重要一环。