前言

前面的文章我们已经学习了触摸屏的基本检测原理,并且还专门深入了解了触摸控制器GT5688芯片的检测过程,本文我们将基于之前的内容讲解如何驱动电容触摸屏,并利用触摸屏制作一个简易的触摸画板应用。


一、硬件设计

在这里插入图片描述
触摸面板和液晶面板是两个独立的部件,生产厂商把这两个部件帖在了一起,形成整体

如图是我绘制的大概触摸面板,液晶面板和主板之间的简化连接。

触摸面板:与触摸控制器GT5688直接进行连接,触摸控制器的主要作用是把触摸信号采集并简化成信号通过IIC或SPI给PCB板子上的MCU
液晶面板:液晶面板内部通过RGB接口与液晶控制器LI9806G进行相连,液晶控制器内部有显存,通过8080接口与PCB主板进行相连
PCB主板:如果想要在屏幕上显示数据,主板通过8080接口连接到显存,把想要发送的数据存放在显存中,显存再通过RGB接口不断把数据刷新显示在屏幕上。

在这里插入图片描述
在这里插入图片描述
以上是我们 STM32F407 实验板使用的 4.5 寸屏原理图,它通过屏幕上的排针接入到实验板的液
晶排母接口,与 STM32 芯片的引脚相连,连接见图屏幕与实验板的引脚连接 。
在这里插入图片描述
图屏幕与实验板的引脚连接 中 23~27 号引脚即电容触摸屏相关的控制引脚。在本硬件设计中,没
有使用 STM32 的硬件 I2C 引脚,只使用普通的 GPIO 与触摸芯片的 SCL、 SDA 连接,在程序上
使用软件模拟 I2C 与触摸芯片进行通讯。

二、软件设计

本工程中的 GT5688 芯片驱动主要是从官方提供的 Linux 驱动修改过来的,我们把这部分文件存
储到“gt5xx.c”及“gt5xx.h”文件中,而这些驱动的底层 I2C 通讯接口我们存储到了“bsp_i2c_touch.c”
及“bsp_i2c_touch.h”文件中

编程要点

  • 分析官方的 gt5xx 驱动,了解需要提供哪些底层接口;
  • 编写底层驱动接口;
  • 利用 gt5xx 驱动,获取触摸坐标;
  • 编写测试程序检验驱动。

程序分析

触摸屏硬件相关宏定义:
这段代码是GT5688 电容触摸芯片的硬件引脚与通信参数定义,核心是通过软件模拟 I2C实现 STM32 与触摸芯片的通信,同时定义了复位、中断引脚的硬件映射,是触摸功能初始化的基础。


/*设定使用的电容屏IIC设备地址*/
#define GTP_ADDRESS            0xBA

#define I2CT_FLAG_TIMEOUT         ((uint32_t)0x1000)
#define I2CT_LONG_TIMEOUT         ((uint32_t)(10 * I2CT_FLAG_TIMEOUT))

/*I2C引脚*/
#define GTP_I2C_SCL_PIN                  GPIO_Pin_7                 
#define GTP_I2C_SCL_GPIO_PORT            GPIOD                       
#define GTP_I2C_SCL_GPIO_CLK             RCC_AHB1Periph_GPIOD
#define GTP_I2C_SCL_SOURCE               GPIO_PinSource7

#define GTP_I2C_SDA_PIN                  GPIO_Pin_3                 
#define GTP_I2C_SDA_GPIO_PORT            GPIOD                     
#define GTP_I2C_SDA_GPIO_CLK             RCC_AHB1Periph_GPIOD
#define GTP_I2C_SDA_SOURCE               GPIO_PinSource3

/*复位引脚*/
#define GTP_RST_GPIO_PORT                GPIOD
#define GTP_RST_GPIO_CLK                 RCC_AHB1Periph_GPIOD
#define GTP_RST_GPIO_PIN                 GPIO_Pin_6
/*中断引脚*/
#define GTP_INT_GPIO_PORT                GPIOG
#define GTP_INT_GPIO_CLK                 RCC_AHB1Periph_GPIOG
#define GTP_INT_GPIO_PIN                 GPIO_Pin_8
#define GTP_INT_EXTI_PORTSOURCE          EXTI_PortSourceGPIOG
#define GTP_INT_EXTI_PINSOURCE           EXTI_PinSource8
#define GTP_INT_EXTI_LINE                EXTI_Line8
#define GTP_INT_EXTI_IRQ                 EXTI9_5_IRQn
/*中断服务函数*/
#define GTP_IRQHandler                   EXTI9_5_IRQHandler

初始化触摸屏控制引脚:
GT5688 触摸芯片的GPIO 初始化核心函数,它完成了软件 I2C 引脚、复位引脚、中断引脚的模式配置,是触摸功能能正常工作的基础


/**
  * @brief  触摸屏 I/O配置
  * @param  无
  * @retval 无
  */
static void I2C_GPIO_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;  
  
  /*使能触摸屏使用的引脚的时钟*/
  RCC_AHB1PeriphClockCmd(GTP_I2C_SCL_GPIO_CLK|
                       	 GTP_I2C_SDA_GPIO_CLK|
	                       GTP_RST_GPIO_CLK|GTP_INT_GPIO_CLK, 
	                       ENABLE);

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
    
    /*配置SCL引脚 */   
    GPIO_InitStructure.GPIO_Pin = GTP_I2C_SCL_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
    GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;
    GPIO_Init(GTP_I2C_SCL_GPIO_PORT, &GPIO_InitStructure);

    /*配置SDA引脚 */
    GPIO_InitStructure.GPIO_Pin = GTP_I2C_SDA_PIN;
    GPIO_Init(GTP_I2C_SDA_GPIO_PORT, &GPIO_InitStructure);

 
  /*配置RST引脚,下拉推挽输出 */   
  GPIO_InitStructure.GPIO_Pin = GTP_RST_GPIO_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_DOWN;
  GPIO_Init(GTP_RST_GPIO_PORT, &GPIO_InitStructure);
  
  /*配置 INT引脚,下拉推挽输出,方便初始化 */   
  GPIO_InitStructure.GPIO_Pin = GTP_INT_GPIO_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	//设置为下拉,方便初始化
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_DOWN;
  GPIO_Init(GTP_INT_GPIO_PORT, &GPIO_InitStructure);
}

配置 I2C 的模式:
代码是软件模拟 I2C 的核心时序函数,完整实现了 I2C 协议的启动、停止、发送字节、读取字节四大基础操作,是 STM32 与 GT5688 触摸芯片通信的 “底层骨架”。

//软件IIC使用的宏
#define I2C_SCL_1()  GPIO_SetBits(GTP_I2C_SCL_GPIO_PORT, GTP_I2C_SCL_PIN)		/* SCL = 1 */
#define I2C_SCL_0()  GPIO_ResetBits(GTP_I2C_SCL_GPIO_PORT, GTP_I2C_SCL_PIN)		/* SCL = 0 */

#define I2C_SDA_1()  GPIO_SetBits(GTP_I2C_SDA_GPIO_PORT, GTP_I2C_SDA_PIN)		/* SDA = 1 */
#define I2C_SDA_0()  GPIO_ResetBits(GTP_I2C_SDA_GPIO_PORT, GTP_I2C_SDA_PIN)		/* SDA = 0 */

#define I2C_SDA_READ()  GPIO_ReadInputDataBit(GTP_I2C_SDA_GPIO_PORT, GTP_I2C_SDA_PIN)	/* 读SDA口线状态 */
/*
*********************************************************************************************************
*	函 数 名: i2c_Start
*	功能说明: CPU发起I2C总线启动信号
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_Start(void)
{
	/* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
	I2C_SDA_1();
	I2C_SCL_1();
	i2c_Delay();
	I2C_SDA_0();
	i2c_Delay();
	I2C_SCL_0();
	i2c_Delay();
}


/*
*********************************************************************************************************
*	函 数 名: i2c_Stop
*	功能说明: CPU发起I2C总线停止信号
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_Stop(void)
{
	/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
	I2C_SDA_0();
	I2C_SCL_1();
	i2c_Delay();
	I2C_SDA_1();
}

/*
*********************************************************************************************************
*	函 数 名: i2c_SendByte
*	功能说明: CPU向I2C总线设备发送8bit数据
*	形    参:_ucByte : 等待发送的字节
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_SendByte(uint8_t _ucByte)
{
	uint8_t i;

	/* 先发送字节的高位bit7 */
	for (i = 0; i < 8; i++)
	{		
		if (_ucByte & 0x80)
		{
			I2C_SDA_1();
		}
		else
		{
			I2C_SDA_0();
		}
		i2c_Delay();
		I2C_SCL_1();
		i2c_Delay();	
		I2C_SCL_0();
		if (i == 7)
		{
			 I2C_SDA_1(); // 释放总线
		}
		_ucByte <<= 1;	/* 左移一个bit */
		i2c_Delay();
	}
}

/*
*********************************************************************************************************
*	函 数 名: i2c_ReadByte
*	功能说明: CPU从I2C总线设备读取8bit数据
*	形    参:无
*	返 回 值: 读到的数据
*********************************************************************************************************
*/
uint8_t i2c_ReadByte(void)
{
	uint8_t i;
	uint8_t value;

	/* 读到第1个bit为数据的bit7 */
	value = 0;
	for (i = 0; i < 8; i++)
	{
		value <<= 1;
		I2C_SCL_1();
		i2c_Delay();
		if (I2C_SDA_READ())
		{
			value++;
		}
		I2C_SCL_0();
		i2c_Delay();
	}
	return value;
}


使用上电时序设置触摸屏的 I2C 地址:
GT5688(兼容 GT91xx)触摸芯片的复位与初始化配置函数,核心是通过控制复位引脚(RST)的电平完成芯片复位,并调整中断引脚(INT)的模式,最终将触摸芯片的 I2C 设备地址配置为 0xBA(与之前定义的GTP_ADDRESS匹配)


/**
  * @brief  对GT91xx芯片进行复位
  * @param  无
  * @retval 无
  */
void I2C_ResetChip(void)
{
	  GPIO_InitTypeDef GPIO_InitStructure;

  /*配置 INT引脚,下拉推挽输出,方便初始化 */   
	  GPIO_InitStructure.GPIO_Pin = GTP_INT_GPIO_PIN;
	  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_DOWN;       //设置为下拉,方便初始化
	  GPIO_Init(GTP_INT_GPIO_PORT, &GPIO_InitStructure);

	  /*初始化GT5688,rst为高电平,int为低电平,则gt5688的设备地址被配置为0xBA*/

	  /*复位为低电平,为初始化做准备*/
	  GPIO_ResetBits (GTP_RST_GPIO_PORT,GTP_RST_GPIO_PIN);
	  Delay(0x0FFFFF);

	  /*拉高一段时间,进行初始化*/
	  GPIO_SetBits (GTP_RST_GPIO_PORT,GTP_RST_GPIO_PIN);
	  Delay(0x0FFFFF);

	  /*把INT引脚设置为浮空输入模式,以便接收触摸中断信号*/
	  GPIO_InitStructure.GPIO_Pin = GTP_INT_GPIO_PIN;
	  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
	  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;
	  GPIO_Init(GTP_INT_GPIO_PORT, &GPIO_InitStructure);
}

使能 INT 中断:
I2C_GTP_IRQEnable函数是GT5688 触摸芯片中断功能的完整配置函数,核心是把触摸中断引脚(PG8)配置为 EXTI 外部中断,并设置中断优先级、使能中断通道,最终实现 “触摸屏幕时触发 STM32 中断” 的功能。


/**
  * @brief  使能触摸屏中断
  * @param  无
  * @retval 无
  */
void I2C_GTP_IRQEnable(void)
{
  EXTI_InitTypeDef EXTI_InitStructure;  
  NVIC_InitTypeDef NVIC_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;  
  /*配置 INT 为浮空输入 */   
  GPIO_InitStructure.GPIO_Pin = GTP_INT_GPIO_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;
  GPIO_Init(GTP_INT_GPIO_PORT, &GPIO_InitStructure);  
  
  /* 连接 EXTI 中断源 到INT 引脚 */
  SYSCFG_EXTILineConfig(GTP_INT_EXTI_PORTSOURCE, GTP_INT_EXTI_PINSOURCE);

  /* 选择 EXTI 中断源 */
  EXTI_InitStructure.EXTI_Line = GTP_INT_EXTI_LINE;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;  
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);  
  
  /* 配置中断优先级 */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  
	/*使能中断*/
  NVIC_InitStructure.NVIC_IRQChannel = GTP_INT_EXTI_IRQ;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);

}

初始化封装:
I2C_Touch_Init函数是GT5688 电容触摸芯片的一站式初始化入口函数,它按 “GPIO 配置→芯片复位→中断使能” 的核心流程,完成触摸功能的全部初始化工作,是整个触摸驱动的 “总开关”。


/**
  * @brief  I2C 外设(GT5xx)初始化
  * @param  无
  * @retval 无
  */
void I2C_Touch_Init(void)
{
  I2C_GPIO_Config(); 
 
  I2C_ResetChip();

  I2C_GTP_IRQEnable();
}

I2C 基本读写函数:
I2C_ReadBytes和I2C_WriteBytes是基于软件 I2C 时序封装的通用读写函数,适配 GT5688 等 I2C 设备的通信逻辑,核心解决了 “多字节连续读写 + 应答处理 + 超时容错” 的问题,是触摸芯片数据交互的核心上层函数。


#define I2C_DIR_WR	0		/* 写控制bit */
#define I2C_DIR_RD	1		/* 读控制bit */

/**
  * @brief   使用IIC读取数据
  * @param   
  * 	@arg ClientAddr:从设备地址
  *		@arg pBuffer:存放由从机读取的数据的缓冲区指针
  *		@arg NumByteToRead:读取的数据长度
  * @retval  无
  */
uint32_t I2C_ReadBytes(uint8_t ClientAddr,uint8_t* pBuffer, uint16_t NumByteToRead)
{
	
	/* 第1步:发起I2C总线启动信号 */
	i2c_Start();
	
	/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	i2c_SendByte(ClientAddr | I2C_DIR_RD);	/* 此处是读指令 */
	
	/* 第3步:等待ACK */
	if (i2c_WaitAck() != 0)
	{
		goto cmd_fail;	/* 器件无应答 */
	}

	while(NumByteToRead) 
  {
   if(NumByteToRead == 1)
    {
			i2c_NAck();	/* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
      
      /* 发送I2C总线停止信号 */
      i2c_Stop();
    }
    
   *pBuffer = i2c_ReadByte();
    
    /* 读指针自增 */
    pBuffer++; 
      
    /*计数器自减 */
    NumByteToRead--;
    
    i2c_Ack();	/* 中间字节读完后,CPU产生ACK信号(驱动SDA = 0) */  
  }

	/* 发送I2C总线停止信号 */
	i2c_Stop();
	return 0;	/* 执行成功 */

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	i2c_Stop();
	return 1;
}

/**
  * @brief   使用IIC写入数据
  * @param   
  * 	@arg ClientAddr:从设备地址
  *		@arg pBuffer:缓冲区指针
  *     @arg NumByteToWrite:写的字节数
  * @retval  无
  */
uint32_t I2C_WriteBytes(uint8_t ClientAddr,uint8_t* pBuffer,  uint8_t NumByteToWrite)
{
	uint16_t m;	

  /* 第0步:发停止信号,启动内部写操作 */
  i2c_Stop();
  
  /* 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms 			
    CLK频率为200KHz时,查询次数为30次左右
  */
  for (m = 0; m < 1000; m++)
  {				
    /* 第1步:发起I2C总线启动信号 */
    i2c_Start();
    
    /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
    i2c_SendByte(ClientAddr | I2C_DIR_WR);	/* 此处是写指令 */
    
    /* 第3步:发送一个时钟,判断器件是否正确应答 */
    if (i2c_WaitAck() == 0)
    {
      break;
    }
  }
  if (m  == 1000)
  {
    goto cmd_fail;	/* EEPROM器件写超时 */
  }	
	
  while(NumByteToWrite--)
  {
  /* 第4步:开始写入数据 */
  i2c_SendByte(*pBuffer);

  /* 第5步:检查ACK */
  if (i2c_WaitAck() != 0)
  {
    goto cmd_fail;	/* 器件无应答 */
  }
  
      pBuffer++;	/* 地址增1 */		
  }
	
	/* 命令执行成功,发送I2C总线停止信号 */
	i2c_Stop();
	return 0;

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	i2c_Stop();
	return 1;
}

Linux 的 I2C 驱动接口:
使用前面的基本读写函数,主要是为了对接原“gt5xx.c”驱动里使用的 Linux I2C 接口函数I2C_Transfer,实现了这个函数后,移植时就可以减少“gt5xx.c”文件的修改量。
I2C_Transfer函数是对之前I2C_ReadBytes/I2C_WriteBytes的更高层封装,借鉴了 Linux 内核 I2C 子系统的设计思路,通过i2c_msg结构体和批量传输机制,实现了 “多段 I2C 通信的一站式处理”(比如 “写寄存器地址 + 读数据” 可封装为两个i2c_msg,一次调用完成),大幅简化了复杂 I2C 交互的代码逻辑。

/* 表示读数据 */ 
#define I2C_M_RD		0x0001	
 /*
 * 存储I2C通讯的信息
 * @addr:  从设备的I2C设备地址	
 * @flags: 控制标志
 * @len:  读写数据的长度
 * @buf:  存储读写数据的指针
 **/
struct i2c_msg {
	uint8_t addr;		/*从设备的I2C设备地址 */
	uint16_t flags;	/*控制标志*/
	uint16_t len;		/*读写数据的长度			*/
	uint8_t *buf;		/*存储读写数据的指针	*/
};

/**
  * @brief   使用IIC进行数据传输
  * @param
  *		@arg i2c_msg:数据传输结构体
  *		@arg num:数据传输结构体的个数
  * @retval  正常完成的传输结构个数,若不正常,返回0xff
  */
static int I2C_Transfer( struct i2c_msg *msgs,int num)
{
	int im = 0;
	int ret = 0;

	GTP_DEBUG_FUNC();

	for (im = 0; ret == 0 && im != num; im++)
	{
		if ((msgs[im].flags&I2C_M_RD))																//根据flag判断是读数据还是写数据
		{
			ret = I2C_ReadBytes(msgs[im].addr, msgs[im].buf, msgs[im].len);		//IIC读取数据
		} else
		{
			ret = I2C_WriteBytes(msgs[im].addr,  msgs[im].buf, msgs[im].len);	//IIC写入数据
		}
	}

	if(ret)
		return ret;

	return im;   													//正常完成的传输结构个数
}

I2C 复合读写函数:
GTP_I2C_Read和GTP_I2C_Write是GT5688 触摸芯片的最终版专用读写函数,完整覆盖了芯片 “2 字节寄存器地址 + 多字节数据” 的读写场景,集成了 5 次重试容错机制,是触摸驱动中最核心、最实用的业务层接口。

#define GTP_ADDR_LENGTH       2
/**
  * @brief   从IIC设备中读取数据
  * @param
  *		@arg client_addr:设备地址
  *		@arg  buf[0~1]: 读取数据寄存器的起始地址
  *		@arg buf[2~len-1]: 存储读出来数据的缓冲buffer
  *		@arg len:    GTP_ADDR_LENGTH + read bytes count(寄存器地址长度+读取的数据字节数)
  * @retval  i2c_msgs传输结构体的个数,2为成功,其它为失败
  */
static int32_t GTP_I2C_Read(uint8_t client_addr, uint8_t *buf, int32_t len)
{
    struct i2c_msg msgs[2];
    int32_t ret=-1;
    int32_t retries = 0;

    GTP_DEBUG_FUNC();
    /*一个读数据的过程可以分为两个传输过程:
     * 1. IIC  写入 要读取的寄存器地址
     * 2. IIC  读取  数据
     * */

    msgs[0].flags = !I2C_M_RD;					//写入
    msgs[0].addr  = client_addr;					//IIC设备地址
    msgs[0].len   = GTP_ADDR_LENGTH;	//寄存器地址为2字节(即写入两字节的数据)
    msgs[0].buf   = &buf[0];						//buf[0~1]存储的是要读取的寄存器地址
    
    msgs[1].flags = I2C_M_RD;					//读取
    msgs[1].addr  = client_addr;					//IIC设备地址
    msgs[1].len   = len - GTP_ADDR_LENGTH;	//要读取的数据长度
    msgs[1].buf   = &buf[GTP_ADDR_LENGTH];	//buf[GTP_ADDR_LENGTH]之后的缓冲区存储读出的数据

    while(retries < 5)
    {
        ret = I2C_Transfer( msgs, 2);					//调用IIC数据传输过程函数,有2个传输过程
        if(ret == 2)break;
        retries++;
    }
    if((retries >= 5))
    {
        GTP_ERROR("I2C Read: 0x%04X, %d bytes failed, errcode: %d! Process reset.", (((uint16_t)(buf[0] << 8)) | buf[1]), len-2, ret);
    }
    return ret;
}



/**
  * @brief   向IIC设备写入数据
  * @param
  *		@arg client_addr:设备地址
  *		@arg  buf[0~1]: 要写入的数据寄存器的起始地址
  *		@arg buf[2~len-1]: 要写入的数据
  *		@arg len:    GTP_ADDR_LENGTH + write bytes count(寄存器地址长度+写入的数据字节数)
  * @retval  i2c_msgs传输结构体的个数,1为成功,其它为失败
  */
static int32_t GTP_I2C_Write(uint8_t client_addr,uint8_t *buf,int32_t len)
{
    struct i2c_msg msg;
    int32_t ret = -1;
    int32_t retries = 0;

    GTP_DEBUG_FUNC();
    /*一个写数据的过程只需要一个传输过程:
     * 1. IIC连续 写入 数据寄存器地址及数据
     * */
    msg.flags = !I2C_M_RD;			//写入
    msg.addr  = client_addr;			//从设备地址
    msg.len   = len;							//长度直接等于(寄存器地址长度+写入的数据字节数)
    msg.buf   = buf;						//直接连续写入缓冲区中的数据(包括了寄存器地址)

    while(retries < 5)
    {
        ret = I2C_Transfer(&msg, 1);	//调用IIC数据传输过程函数,1个传输过程
        if (ret == 1)break;
        retries++;
    }
    if((retries >= 5))
    {

        GTP_ERROR("I2C Write: 0x%04X, %d bytes failed, errcode: %d! Process reset.", (((uint16_t)(buf[0] << 8)) | buf[1]), len-2, ret);

    }
    return ret;
}

在这里插入图片描述

读取触控芯片的产品 ID 及版本号:
GTP_Read_Version函数是GT5688/GT911/GT9157 等触摸芯片的版本识别与型号匹配函数,核心是通过读取芯片的版本寄存器(GTP_REG_VERSION),解析返回数据中的字符特征来识别具体芯片型号,并将结果存入touchIC全局变量,为后续差异化适配不同芯片的驱动逻辑提供依据。


/*******************************************************
Function:
    Read chip version.
Input:
    client:  i2c device
    version: buffer to keep ic firmware version
Output:
    read operation return.
        2: succeed, otherwise: failed
*******************************************************/
int32_t GTP_Read_Version(void)
{
    int32_t ret = -1;
    uint8_t buf[8] = {GTP_REG_VERSION >> 8, GTP_REG_VERSION & 0xff};    //寄存器地址

    GTP_DEBUG_FUNC();

    ret = GTP_I2C_Read(GTP_ADDRESS, buf, sizeof(buf));
    if (ret < 0)
    {
        GTP_ERROR("GTP read version failed");
        return ret;
    }
    if (buf[2] == '5')
    {
        GTP_INFO("IC1 Version: %c%c%c%c_%02x%02x", buf[2], buf[3], buf[4], buf[5], buf[7], buf[6]);
				
				//GT5688芯片
				if(buf[2] == '5' && buf[3] == '6' && buf[4] == '8'&& buf[5] == '8')
					touchIC = GT5688;
    }        
    else if (buf[5] == 0x00)
    {
        GTP_INFO("IC2 Version: %c%c%c_%02x%02x", buf[2], buf[3], buf[4], buf[7], buf[6]);
				
				//GT911芯片
				if(buf[2] == '9' && buf[3] == '1' && buf[4] == '1')
					touchIC = GT911;
    }
    else
    {
        GTP_INFO("IC3 Version: %c%c%c%c_%02x%02x", buf[2], buf[3], buf[4], buf[5], buf[7], buf[6]);
				
				//GT9157芯片
				if(buf[2] == '9' && buf[3] == '1' && buf[4] == '5' && buf[5] == '7')
					touchIC = GT9157;
		}
    return ret;
}

向触控芯片写入配置参数:
GTP_Init_Panel函数是GT5688 触摸芯片的完整配置初始化函数,核心是根据芯片型号加载对应的硬件配置参数表(如CTP_CFG_GT5688),动态调整分辨率、屏幕扫描方向等关键参数,计算校验和后写入配置寄存器,最终完成触摸芯片的个性化硬件适配,是触摸驱动能匹配具体 4.5 寸屏的核心函数。


// 4.5寸屏GT5688驱动配置(2020年6月20日)
const uint8_t CTP_CFG_GT5688[] =  {
  0x97,0x56,0x03,0xE0,0x01,0x05,0x3D,0x00,0x00,0x41,
  0x00,0x0A,0x50,0x3C,0x53,0x11,0x00,0x00,0x00,0x00,
  0x14,0x18,0x1A,0x1E,0x16,0x04,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x64,0x1E,0x28,0x89,0x29,0x0A,0x2D,
  0x2F,0xEB,0x06,0x20,0x33,0x62,0x13,0x02,0x24,0x00,
  0x00,0x20,0x3C,0xC0,0x14,0x02,0x00,0x00,0x53,0xB0,
  0x24,0x9F,0x29,0x8D,0x2D,0x80,0x32,0x76,0x37,0x6D,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0x50,0x3C,
  0xFF,0xFF,0x07,0x00,0x00,0x00,0x00,0x14,0x14,0x03,
  0x04,0x00,0x21,0x64,0x0A,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x32,0x20,0x50,0x3C,0x3C,0x00,0x00,0x00,0x00,0x00,
  0x0B,0x04,0x0A,0x03,0x09,0x02,0x08,0x01,0x07,0x00,
  0x05,0x0C,0x06,0x0D,0x08,0x07,0x06,0x05,0x04,0x03,
  0x02,0x01,0x00,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
  0x10,0x11,0x12,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0x00,0x00,0x00,0x3C,0x00,0x05,0x1E,0x00,0x02,
  0x2A,0x1E,0x19,0x14,0x02,0x00,0x03,0x0A,0x05,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xFF,0xFF,0x86,
  0x22,0x03,0x00,0x00,0x33,0x00,0x0F,0x00,0x00,0x00,
  0x50,0x3C,0x50,0x00,0x00,0x00,0xCC,0xEF,0x00
};


/*******************************************************
Function:
    Initialize gtp.
Input:
    ts: goodix private data
Output:
    Executive outcomes.
        0: succeed, otherwise: failed
*******************************************************/
 int32_t GTP_Init_Panel(void)
{
    int32_t ret = -1;

    int32_t i = 0;
    uint16_t check_sum = 0;
    int32_t retry = 0;

    const uint8_t* cfg_info;
    uint8_t cfg_info_len  ;
		uint8_t* config;

    uint8_t cfg_num =0 ;		//需要配置的寄存器个数

    GTP_DEBUG_FUNC();
	
//uint8_t config[GTP_CONFIG_MAX_LENGTH + GTP_ADDR_LENGTH]
//                = {GTP_REG_CONFIG_DATA >> 8, GTP_REG_CONFIG_DATA & 0xff};

		config = (uint8_t *)malloc (GTP_CONFIG_MAX_LENGTH + GTP_ADDR_LENGTH);

		config[0] = GTP_REG_CONFIG_DATA >> 8;
		config[1] =  GTP_REG_CONFIG_DATA & 0xff;
	
    I2C_Touch_Init();

    ret = GTP_I2C_Test();
    if (ret < 0)
    {
        GTP_ERROR("I2C communication ERROR!");
				return ret;
    } 
		
		//获取触摸IC的型号
    GTP_Read_Version(); 
		
#if UPDATE_CONFIG
		//根据IC的型号指向不同的配置
    if(touchIC == GT5688)
		{
			cfg_info =  CTP_CFG_GT5688; //指向寄存器配置
			cfg_info_len = CFG_GROUP_LEN(CTP_CFG_GT5688);//计算配置表的大小
		}
		
    memset(&config[GTP_ADDR_LENGTH], 0, GTP_CONFIG_MAX_LENGTH);
    memcpy(&config[GTP_ADDR_LENGTH], cfg_info, cfg_info_len);
		

		cfg_num = cfg_info_len;
		
		GTP_DEBUG("cfg_info_len = %d ",cfg_info_len);
		GTP_DEBUG("cfg_num = %d ",cfg_num);
		GTP_DEBUG_ARRAY(config,6);
		
		/*根据LCD的扫描方向设置分辨率*/
		config[GTP_ADDR_LENGTH+1] = LCD_X_LENGTH & 0xFF;
		config[GTP_ADDR_LENGTH+2] = LCD_X_LENGTH >> 8;
		config[GTP_ADDR_LENGTH+3] = LCD_Y_LENGTH & 0xFF;
		config[GTP_ADDR_LENGTH+4] = LCD_Y_LENGTH >> 8;
		
		/*根据扫描模式设置X2Y交换*/
		switch(LCD_SCAN_MODE)
		{
			case 0:case 2:case 4: case 6:
				config[GTP_ADDR_LENGTH+6] &= ~(X2Y_LOC);
				break;
			
			case 1:case 3:case 5: case 7:
				config[GTP_ADDR_LENGTH+6] |= (X2Y_LOC);
				break;		
		}

    //计算要写入checksum寄存器的值
    check_sum = 0;
		
		for (i = GTP_ADDR_LENGTH; i < (cfg_num+GTP_ADDR_LENGTH -3); i += 2) 
		{
			check_sum += (config[i] << 8) + config[i + 1];
		}
		
		check_sum = 0 - check_sum;
		GTP_DEBUG("Config checksum: 0x%04X", check_sum);
		//更新checksum
		config[(cfg_num+GTP_ADDR_LENGTH -3)] = (check_sum >> 8) & 0xFF;
		config[(cfg_num+GTP_ADDR_LENGTH -2)] = check_sum & 0xFF;
		config[(cfg_num+GTP_ADDR_LENGTH -1)] = 0x01;
		

    //写入配置信息
    for (retry = 0; retry < 5; retry++)
    {
        ret = GTP_I2C_Write(GTP_ADDRESS, config , cfg_num + GTP_ADDR_LENGTH+2);
        if (ret > 0)
        {
            break;
        }
    }
    Delay(0xfffff);				//延迟等待芯片更新
		

		
#if 0	//读出写入的数据,检查是否正常写入
    //检验读出的数据与写入的是否相同
	{
    uint16_t i;
    uint8_t buf[300];
     buf[0] = config[0];
     buf[1] =config[1];    //寄存器地址

    GTP_DEBUG_FUNC();

    ret = GTP_I2C_Read(GTP_ADDRESS, buf, sizeof(buf));
   
    GTP_DEBUG("read ");

    GTP_DEBUG_ARRAY(buf+2,cfg_num+2);

    GTP_DEBUG("write ");

    GTP_DEBUG_ARRAY(config,cfg_num);

    //不对比版本号
    for(i=1;i<cfg_num+GTP_ADDR_LENGTH-3;i++)
    {

      if(config[i] != buf[i])
      {
        GTP_ERROR("Config fail ! i = %d ",i);
        free(config);
        return -1;
      }
    }
    if(i==cfg_num+GTP_ADDR_LENGTH-3)
    GTP_DEBUG("Config success ! i = %d ",i);
	}
#endif
  free(config);
  #endif
  /*使能中断,这样才能检测触摸数据*/
  I2C_GTP_IRQEnable();

  GTP_Get_Info();

  return 0;
}

INT 中断服务函数:
当触摸时, INT 引脚会产生触摸中断,会进入中断服务函数 GTP_IRQHandler,中断服务函数只是简单地调用了 GTP_TouchProcess 函数,它是读取触摸坐标的主体,
而它又直接调用了函数 Goodix_TS_Work_Func。

void GTP_IRQHandler(void)
{
	if(EXTI_GetITStatus(GTP_INT_EXTI_LINE) != RESET) //确保是否产生了EXTI Line中断
	{
		LED2_TOGGLE;
     GTP_TouchProcess();    
		EXTI_ClearITPendingBit(GTP_INT_EXTI_LINE);     //清除中断标志位
	}  
}

读取坐标数据:
Goodix_TS_Work_Func函数是GT5688 触摸芯片的核心数据处理函数,负责读取触摸坐标数据、解析触摸点数 / ID / 坐标 / 尺寸,适配屏幕扫描模式修正坐标,处理 “按下 / 抬起” 事件,并在最后发送结束指令清空芯片缓冲区,既可以在中断中调用(实时性高),也可轮询调用,是触摸事件从 “硬件数据” 到 “业务事件” 的核心转换层。


/**
  * @brief   触屏处理函数,轮询或者在触摸中断调用
  * @param 无
  * @retval 无
  */
static void Goodix_TS_Work_Func(void)
{
    uint8_t  end_cmd[3] = {GTP_READ_COOR_ADDR >> 8, GTP_READ_COOR_ADDR & 0xFF, 0};
    uint8_t  point_data[2 + 1 + 8 * GTP_MAX_TOUCH + 1]={GTP_READ_COOR_ADDR >> 8, GTP_READ_COOR_ADDR & 0xFF};
    uint8_t  touch_num = 0;
    uint8_t  finger = 0;
    static uint16_t pre_touch = 0;
    static uint8_t pre_id[GTP_MAX_TOUCH] = {0};

    uint8_t client_addr=GTP_ADDRESS;
    uint8_t* coor_data = NULL;
    int32_t input_x = 0;
    int32_t input_y = 0;
    int32_t input_w = 0;
    uint8_t id = 0;
 
    int32_t i  = 0;
    int32_t ret = -1;

    GTP_DEBUG_FUNC();

    ret = GTP_I2C_Read(client_addr, point_data, 12);//10字节寄存器加2字节地址
    if (ret < 0)
    {
        GTP_ERROR("I2C transfer error. errno:%d\n ", ret);

        return;
    }
    
    finger = point_data[GTP_ADDR_LENGTH];//状态寄存器数据

    if (finger == 0x00)		//没有数据,退出
    {
        return;
    }

    if((finger & 0x80) == 0)//判断buffer status位
    {
        goto exit_work_func;//坐标未就绪,数据无效
    }

    touch_num = finger & 0x0f;//坐标点数
    if (touch_num > GTP_MAX_TOUCH)
    {
        goto exit_work_func;//大于最大支持点数,错误退出
    }

    if (touch_num > 1)//不止一个点
    {
        uint8_t buf[8 * GTP_MAX_TOUCH] = {(GTP_READ_COOR_ADDR + 10) >> 8, (GTP_READ_COOR_ADDR + 10) & 0xff};

        ret = GTP_I2C_Read(client_addr, buf, 2 + 8 * (touch_num - 1));
        memcpy(&point_data[12], &buf[2], 8 * (touch_num - 1));			//复制其余点数的数据到point_data
    }

    
    
    if (pre_touch>touch_num)				//pre_touch>touch_num,表示有的点释放了
    {
        for (i = 0; i < pre_touch; i++)						//一个点一个点处理
         {
            uint8_t j;
           for(j=0; j<touch_num; j++)
           {
               coor_data = &point_data[j * 8 + 3];
               id = coor_data[0] & 0x0F;									//track id
              if(pre_id[i] == id)
                break;

              if(j >= touch_num-1)											//遍历当前所有id都找不到pre_id[i],表示已释放
              {
                 GTP_Touch_Up( pre_id[i]);
              }
           }
       }
    }


    if (touch_num)
    {
        for (i = 0; i < touch_num; i++)						//一个点一个点处理
        {
            coor_data = &point_data[i * 8 + 3];

            id = coor_data[0] & 0x0F;									//track id
            pre_id[i] = id;

            input_x  = coor_data[1] | (coor_data[2] << 8);	//x坐标
            input_y  = coor_data[3] | (coor_data[4] << 8);	//y坐标
            input_w  = coor_data[5] | (coor_data[6] << 8);	//size
        
            {
							
									/*根据扫描模式更正X/Y起始方向*/
								switch(LCD_SCAN_MODE)
								{
									case 0:case 7:
										input_y  = LCD_Y_LENGTH - input_y;
										break;
									
									case 2:case 3: 
										input_x  = LCD_X_LENGTH - input_x;
										input_y  = LCD_Y_LENGTH - input_y;
										break;
									
									case 1:case 6:
										input_x  = LCD_X_LENGTH - input_x;
										break;	
									
									default:
									break;
								}
								
                GTP_Touch_Down( id, input_x, input_y, input_w);//数据处理
            }
        }
    }
    else if (pre_touch)		//touch_ num=0 且pre_touch!=0
    {
      for(i=0;i<pre_touch;i++)
      {
          GTP_Touch_Up(pre_id[i]);
      }
    }


    pre_touch = touch_num;


exit_work_func:
    {
        ret = GTP_I2C_Write(client_addr, end_cmd, 3);
        if (ret < 0)
        {
            GTP_INFO("I2C write end_cmd error!");
        }
    }

}

触点释放和触点按下的坐标接口:
GTP_Touch_Down和GTP_Touch_Up是GT5688 触摸驱动的业务层事件回调函数,核心是将底层解析的触摸坐标 / ID 转换为 “按下 / 抬起” 业务事件,集成了触摸画板的基础逻辑(按钮处理、轨迹绘制),并预留了自定义业务的扩展入口,是触摸驱动从 “硬件数据” 到 “用户交互” 的最终落地层。


/**
  * @brief   用于处理或报告触屏检测到按下
  * @param
  *    @arg     id: 触摸顺序trackID
  *    @arg     x:  触摸的 x 坐标
  *    @arg     y:  触摸的 y 坐标
  *    @arg     w:  触摸的 大小
  * @retval 无
  */
/*用于记录连续触摸时(长按)的上一次触摸位置,负数值表示上一次无触摸按下*/
static int16_t pre_x[GTP_MAX_TOUCH] ={-1,-1,-1,-1,-1};
static int16_t pre_y[GTP_MAX_TOUCH] ={-1,-1,-1,-1,-1};

static void GTP_Touch_Down(int32_t id,int32_t x,int32_t y,int32_t w)
{
  
	GTP_DEBUG_FUNC();

	/*取x、y初始值大于屏幕像素值*/
    GTP_DEBUG("ID:%d, X:%d, Y:%d, W:%d", id, x, y, w);

	
    /* 处理触摸按钮,用于触摸画板 */
    Touch_Button_Down(x,y); 
	

    /*处理描绘轨迹,用于触摸画板 */
    Draw_Trail(pre_x[id],pre_y[id],x,y,&brush);
	
		/************************************/
		/*在此处添加自己的触摸点按下时处理过程即可*/
		/* (x,y) 即为最新的触摸点 *************/
		/************************************/
	
		/*prex,prey数组存储上一次触摸的位置,id为轨迹编号(多点触控时有多轨迹)*/
    pre_x[id] = x; pre_y[id] =y;
	
}


/**
  * @brief   用于处理或报告触屏释放
  * @param 释放点的id号
  * @retval 无
  */
static void GTP_Touch_Up( int32_t id)
{
	

    /*处理触摸释放,用于触摸画板*/
    Touch_Button_Up(pre_x[id],pre_y[id]);

		/*****************************************/
		/*在此处添加自己的触摸点释放时的处理过程即可*/
		/* pre_x[id],pre_y[id] 即为最新的释放点 ****/
		/*******************************************/	
		/***id为轨迹编号(多点触控时有多轨迹)********/
	
	
    /*触笔释放,把pre xy 重置为负*/
	  pre_x[id] = -1;
	  pre_y[id] = -1;		
  
    GTP_DEBUG("Touch id[%2d] release!", id);

}

main 函数:
main函数是GT5688 触摸画板项目的总入口,核心是完成 “硬件初始化(LED/LCD/ 串口)→屏幕扫描方向配置→触摸芯片初始化→画板界面绘制” 的全流程初始化,最终进入空主循环(触摸事件依赖中断驱动处理),是整个触摸画板项目的启动核心。


/**
  * @brief  主函数
  * @param  无  
  * @retval 无
  */
int main ( void )
{
	LED_GPIO_Config();
	ILI9806G_Init ();         //LCD 初始化

	Debug_USART_Config();	

	
	//其中0、3、5、6 模式适合从左至右显示文字,
	//不推荐使用其它模式显示文字	其它模式显示文字会有镜像效果			
	//其中 6 模式为大部分液晶例程的默认显示方向  
  ILI9806G_GramScan ( 3 );	

	/* 设定好液晶扫描方向后,再初始化触摸屏,触摸屏会根据液晶的扫描方向输出匹配的触摸坐标 */
	/* 每次修改液晶扫描方向后,应重新调用一次GTP_Init_Panel函数更新触摸配置 */
	GTP_Init_Panel(); 
    
	printf("\r\n ********** 触摸画板程序 *********** \r\n"); 
	printf("\r\n 若汉字显示不正常,请阅读工程中的readme.txt文件说明,根据要求给FLASH重刷字模数据\r\n"); 
 
	
	//绘制触摸画板界面
	Palette_Init(LCD_SCAN_MODE);
    
	while ( 1 )
	{
	}
	
	
}
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐