ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

使用电容触摸屏做画板——软件IIC(二)

2022-02-10 23:05:17  阅读:350  来源: 互联网

标签:画板 GTP 触摸屏 InitStructure IIC GPIO I2C buf


使用电容触摸屏时发现硬件IIC会使电容触摸屏卡死,经过调试发现软件IIC更加好用,那么下面就了解一下软件IIC叭。

IIC协议:

//先定义引脚的高低电平
#define IIC_SDA_1   GPIO_SetBits(EEPROM_I2C_SDA_GPIO_PORT,EEPROM_I2C_SDA_PIN)
#define IIC_SDA_0   GPIO_ResetBits(EEPROM_I2C_SDA_GPIO_PORT,EEPROM_I2C_SDA_PIN)

#define IIC_SCL_1   GPIO_SetBits(EEPROM_I2C_SCL_GPIO_PORT,EEPROM_I2C_SCL_PIN)
#define IIC_SCL_0   GPIO_ResetBits(EEPROM_I2C_SCL_GPIO_PORT,EEPROM_I2C_SCL_PIN)

//由于是在IIC读写EEPROM那边改的,所以引脚的宏并没有改

那么就模拟起始信号写 一个引脚电平的变换:

static void  IIC_Start(void)
{
    IIC_SDA_1;		//保持高电平
    IIC_SCL_1;
  
    IIC_Delay();	//延迟
  
    IIC_SDA_0;		//SDA低电平,起始信号
    IIC_Delay();
    
    IIC_SCL_0;		//SCL低电平,开始工作
    IIC_Delay();

}

其中的延时函数:

void IIC_Delay(void)
{
  uint8_t i ;  
  for(i=0;i<50;i++);  
}

(2)模拟停止信号:SCL为高电平,SDA由低电平变为高电平。

static void  IIC_Stop(void)
{
    IIC_SDA_0;
    IIC_SCL_1;
  
    IIC_Delay();
  
    IIC_SDA_1;
    IIC_Delay();
    
    IIC_SCL_1;
    IIC_Delay();

}

(3)数据有效时读取数据:SCL高电平、读取SDA数据

每次采样一个字节一共八位,当SCL高电平时,SDA为高电平时数据有效。

//定义一个读取引脚的宏
#define IIC_READ_SDA()  GPIO_ReadInputDataBit(EEPROM_I2C_SDA_GPIO_PORT,EEPROM_I2C_SDA_PIN)

 读取数据函数:

static uint8_t IIC_ReadByte(void)
{
  uint8_t i;  
  uint8_t value = 0;
  
  for(i=0;i<8;i++)
  {
    //要将高位的数据往左移
    value <<= 1 ;    
    
    IIC_SCL_1;
    IIC_Delay();

    if(IIC_READ_SDA())  //SDA为高电平时数据有效
    {
      value++;
    }
    else
    {
    
    }        
	
    //读取一个字节后要变为低电平
    IIC_SCL_0;      
    IIC_Delay(); 
    
  }
  
  return value;
}

(4)发送一个数据:收发的电平信号与读取一样。

static void IIC_SendByte(uint8_t data)
{
  uint8_t i;  
  
  for(i=0;i<8;i++)
  {   
    //每一次先发高位数据
    if(data &0x80)
    {
       IIC_SDA_1;
    }
    else
    {
      IIC_SDA_0;
    }   
    IIC_Delay();     
    
    //拉高一段时间
    IIC_SCL_1;
    IIC_Delay();     
    //再拉低   
    IIC_SCL_0;      
    IIC_Delay(); 
    //将数据左移使其置为高位
    data <<= 1 ;    
    
    //释放总线
    if(i==7)
    {
      //相当于产生一个停止信号
      IIC_SDA_1;
    }
    
  }  

}

(5)发送非应答/应答信号:当SCL处于一个高电平的时钟里面,等待响应,当SDA表现为高电平时,表现为非应答信号。

static void IIC_NACK(void)
{
  IIC_SDA_1;
  IIC_Delay();     
  
  IIC_SCL_1;
  IIC_Delay();  
  IIC_SCL_0;
  
  IIC_Delay(); 

}

当SCL处于一个高电平的时钟里面,等待响应,当SDA表现为低电平时,表现为应答信号。

static void IIC_ACK(void)
{
  IIC_SDA_0;
  IIC_Delay();     
  
  IIC_SCL_1;
  IIC_Delay();  
  IIC_SCL_0;
  
  IIC_Delay(); 
  
  //释放总线
  IIC_SDA_1;
}

 (6)等待应答信号

static uint8_t IIC_Wait_ACK(void)
{
  uint8_t ack_value;
  //释放控制权
  IIC_SDA_1;
  IIC_Delay(); 
  //拉高进行读取
  IIC_SCL_1;  
  IIC_Delay(); 
  //判断应答还是非应答
  if(IIC_READ_SDA())
    ack_value = 1;
  else
    ack_value = 0;
  
  IIC_SCL_0;      
  IIC_Delay();  
  
  return ack_value;
}

接下来使用软件IIC控制液晶屏:

一、初始化引脚

 

 对所有要使用的引脚进行初始化:

/*设定使用的电容屏 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 I2C2
#define GTP_I2C_CLK RCC_APB1Periph_I2C2
#define GTP_I2C_CLK_INIT RCC_APB1PeriphClockCmd

#define GTP_I2C_SCL_PIN GPIO_Pin_4
#define GTP_I2C_SCL_GPIO_PORT GPIOH
#define GTP_I2C_SCL_GPIO_CLK RCC_AHB1Periph_GPIOH
#define GTP_I2C_SCL_SOURCE GPIO_PinSource4
#define GTP_I2C_SCL_AF GPIO_AF_I2C2

#define GTP_I2C_SDA_PIN GPIO_Pin_5
#define GTP_I2C_SDA_GPIO_PORT GPIOH
#define GTP_I2C_SDA_GPIO_CLK RCC_AHB1Periph_GPIOH
#define GTP_I2C_SDA_SOURCE GPIO_PinSource5
#define GTP_I2C_SDA_AF GPIO_AF_I2C2

/*复位引脚*/
#define GTP_RST_GPIO_PORT GPIOI
#define GTP_RST_GPIO_CLK RCC_AHB1Periph_GPIOI
#define GTP_RST_GPIO_PIN GPIO_Pin_8
/*中断引脚*/
#define GTP_INT_GPIO_PORT GPIOD
#define GTP_INT_GPIO_CLK RCC_AHB1Periph_GPIOD
#define GTP_INT_GPIO_PIN GPIO_Pin_13
#define GTP_INT_EXTI_PORTSOURCE EXTI_PortSourceGPIOD
#define GTP_INT_EXTI_PINSOURCE EXTI_PinSource13
#define GTP_INT_EXTI_LINE EXTI_Line13
#define GTP_INT_EXTI_IRQ EXTI15_10_IRQn
/*中断服务函数*/
#define GTP_IRQHandler EXTI15_10_IRQHandler

//初始化触摸屏使用的I2C信号线,并且把RET与INT引脚也初始化为下拉推挽输出模式,以便刚上电的时候输出上电时序,设置触摸屏的I2C设备地址
static void I2C_GPIO_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;  
   
  /*使能IIC时钟 */
  RCC_APB1PeriphClockCmd(GTP_I2C_CLK, ENABLE);
  
  /*使能触摸屏使用的引脚的时钟 */
  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);
    
      /* 配置I2C_SCL源*/
    GPIO_PinAFConfig(GTP_I2C_SCL_GPIO_PORT, 
                     GTP_I2C_SCL_SOURCE, GTP_I2C_SCL_AF);
    /* 配置I2C_SDA 源*/
    GPIO_PinAFConfig(GTP_I2C_SDA_GPIO_PORT, 
                     GTP_I2C_SDA_SOURCE, GTP_I2C_SDA_AF);  
    
      /*配置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);
    
      /*!< Configure 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);
  
  /*!< Configure 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);
   

通过RST引脚与INT引脚确定设备地址以及配置IIC模式:最开始这INT引脚要配置为输出模式,后面要输入,实现对液晶屏的上电时序控制。

void I2C_ResetChip(void)
{
	  GPIO_InitTypeDef GPIO_InitStructure;

	  /*!< Configure 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);

	  /*初始化GT9157,rst为高电平,int为低电平,则gt9157的设备地址被配置为0xBA*/
	  //这段函数中控制RST引脚由低电平改变至高电平,且期间按INT引脚一直为低电平这样的上电时序使控制芯片的I2C写地址为0xBA,读地址为0xBB,可以写为(0xBA|0x01)
	  /*复位为低电平,为初始化做准备*/
	  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引脚设置为浮空输入模式*/
	  /*!< Configure 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引脚配置为上升沿触发
void I2C_GTP_IRQEnable(void)
{
        EXTI_InitTypeDef EXTI_InitStructure;
        NVIC_InitTypeDef NVIC_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);
}
void SysTick_Handler(void)
{

	static uint8_t timecount=0;
	if(timecount>=10)
	{
		timecount=0;
		GTP_TouchProcess();
	}
	TimingDelay_Decrement();
	timecount++;
}

这个中断用于触摸处理。具体的函数是在gt9xx.c文件中,是触摸屏基于Linux给的一个驱动文件,函数作用用于判断现在触摸点于哪个位置。

配置I2C模式,FT9157使用的是标准7位地址模式的I2C通讯:

/* STM32 I2C 快速模式 */
#define I2C_Speed                        400000

/* 这个地址只要与STM32外挂的I2C器件地址不一样即可 */
#define I2C_OWN_ADDRESS7                 0x0A
static void I2C_Mode_Config(void)
{
  I2C_InitTypeDef  I2C_InitStructure; 

  /* I2C 配置 */
  I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;	
  I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;		                     /* 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比 */
  I2C_InitStructure.I2C_OwnAddress1 =I2C_OWN_ADDRESS7;
  I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;	
  I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;	
  /* I2C的寻址模式 */
  I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;	                            
  /* 通信速率 */
  I2C_Init(GTP_I2C, &I2C_InitStructure);	                                     /* I2C1 初始化 */
  I2C_Cmd(GTP_I2C, ENABLE);  	                                                 /* 使能 I2C1 */

  I2C_AcknowledgeConfig(GTP_I2C, ENABLE);
  
}

 最后封装一下初始化所需要的函数:

void I2C_Touch_Init(void)
{
        I2C_GPIO_Config();

        I2C_Mode_Config();

        I2C_ResetChip();

        I2C_GTP_IRQEnable();
}

下面的函数都是移植了Linux(gt9xx.c)中的驱动函数:

在函数中有一个结构体数组,在IIC中有读写复合的方式进行通讯,上面的函数正表示了这一点,在Linux驱动中通过将写入与读取封装在一个结构体数组中,进行了这种复合方式。

//寄存器地址的长度
#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;
}
//需要注意的是,其中buf的前两个字节表示寄存器地址,且len的长度为buf的整体长度

 复合读过程的步骤:

复合写过程的步骤:

 其中提到的通讯结构体:

/* 表示读数据 */ 
#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;		/*存储读写数据的指针	*/
};
  • addr:从机的IIC设备地址,通讯时无论是读方向还是写方向,给这个成员赋值为写地址即可(0xBA)。

  • flags:存储了控制标志,用于指示i2c_msg结构体要求以什么方式来传输(读or写)。在原来的Linux驱动中有很多种控制方式,在这里被赋值为I2C_M_RD表示读。

  • len:数据长度。

  • buf:存储了指向读写数据缓冲区的指针。

在读取数据时,使用以下的函数,判断结构体数组中是读取的数据包还是要接收的数据包,这样就使读取与写入都可以封装在一个函数中,牛:I2C_Transfer的主要输入参数是i2c_msg结构体的指针以及要传输多少个这样的结构体,属于Linux内部的驱动层,对外提供接口。

 

/**
  * @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;   							//正常完成的传输结构个数
}

后面的读取数据与接收数据都使用软件IIC的方式进行:

/**
  * @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;
}

上面的两个读写函数都是很纯粹的IIC读写过程,没有包含寄存器的地址,这两个函数都通过调用数据包进行传输设备地址、缓冲区指针以及数据量。

接下来是读取触控芯片的产品ID及其版本号

由前面介绍的,寄存器是存放于0x8140这个寄存器中。

//设定使用的电容屏的IIC设备地址
#define GTP_ADDRESS 	0XBA
//芯片版本号地址
#define GTP_REG_VERSION	0X8140

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[4] == '1')
    {				
				//GT911芯片
				if(buf[2] == '9' && buf[3] == '1' && buf[4] == '1')
        {
          GTP_INFO("IC1 Version: %c%c%c_%02x%02x", buf[2], buf[3], buf[4], buf[7], buf[6]);

					touchIC = GT911;
          /* 设置当前的液晶屏类型 */
          cur_lcd = INCH_7;
        }
        //GT9157芯片
        else
           GTP_INFO("Unknown IC Version: %c%c%c%c_%02x%02x", buf[2], buf[3], buf[4], buf[5], buf[7], buf[6]);
		}
		else if (buf[4] == '5')
		{
			if( buf[2] == '9' && buf[3] == '1' && buf[4] == '5' && buf[5] == '7')
        {
          GTP_INFO("IC2 Version: %c%c%c%c_%02x%02x", buf[2], buf[3], buf[4], buf[5], buf[7], buf[6]);

					touchIC = GT9157;
          /* 设置当前的液晶屏类型 */
          cur_lcd = INCH_5;
        }
        else
           GTP_INFO("Unknown IC Version: %c%c%c%c_%02x%02x", buf[2], buf[3], buf[4], buf[5], buf[7], buf[6]);

    }    
    else if (buf[4] == '8')
    {	
				//GT5688芯片
				if(buf[2] == '5' && buf[3] == '6' && buf[4] == '8' && buf[5] == '8')
        {
          GTP_INFO("IC3 Version: %c%c%c%c_%02x%02x", buf[2], buf[3], buf[4], buf[5], buf[7], buf[6]);

					touchIC = GT5688;
          /* 设置当前的液晶屏类型 */
          cur_lcd = INCH_4_3;
        }
        else
           GTP_INFO("Unknown IC Version: %c%c%c%c_%02x%02x", buf[2], buf[3], buf[4], buf[5], buf[7], buf[6]);

    }
		else if(buf[4] == '7')
    {
			  //GT917S芯片
         GTP_INFO("IC2 Version: %c%c%c%c_%02x%02x", buf[2], buf[3], buf[4], buf[5], buf[7], buf[6]);
				
				if(buf[2] == '9' && buf[3] == '1' && buf[4] == '7' && buf[5] == 'S')
				{	
					touchIC = GT917S; 
					/* 设置当前的液晶屏类型 */
          cur_lcd = INCH_5;	
				}					
    }
    else 
       GTP_INFO("Unknown IC Version: %c%c%c%c_%02x%02x", buf[2], buf[3], buf[4], buf[5], buf[7], buf[6]);

    return ret;
}

 上面那个函数定义了一个8字节的buf数组,并且向他的第0个和第1个元素写入产品ID寄存器的地址,然后调用复合函数读取数据,之后就可以读取寄存器的信息,利用下面这个宏:

#define GTP_INFO(fmt,arg...)           printf("<<-GTP-INFO->> "fmt"\n",##arg)

之后向触控芯片写入参数

我们识别出是哪一种产品id后,我们就可以往显示屏里面写入配置参数到寄存器中:

 

 在函数中定义了一个枚举,存放了所有的产品型号:

 之后在下面这个函数进行了初始化,也调用了I2C_Touch_Init初始化了STM32的IIC外设,设定触控芯片的IIC设备地址,然后调用了上面的获取触控芯片的版本号。之后将配置参数表写入到触控芯片的配置寄存器中,在传输中包含由checksum寄存器的值,需要利用其来校验数据。

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();
	
    I2C_Touch_Init();

    ret = GTP_I2C_Test();
    if (ret < 0)
    {
        GTP_ERROR("I2C communication ERROR!");
				return ret;
    } 
		
		//获取触摸IC的型号
    GTP_Read_Version(); 
    
#if UPDATE_CONFIG

		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;
		
		//根据IC的型号指向不同的配置
		if(touchIC == GT9157)
		{
			cfg_info =  CTP_CFG_GT9157; //指向寄存器配置
			cfg_info_len = CFG_GROUP_LEN(CTP_CFG_GT9157);//计算配置表的大小
		}
		else if(touchIC == GT911)
		{
			cfg_info =  CTP_CFG_GT911;//指向寄存器配置
			cfg_info_len = CFG_GROUP_LEN(CTP_CFG_GT911) ;//计算配置表的大小
		}
		else if(touchIC == GT5688)			
		{
			cfg_info =  CTP_CFG_GT5688; //指向寄存器配置
			cfg_info_len = CFG_GROUP_LEN(CTP_CFG_GT5688);//计算配置表的大小
		}
		else if(touchIC == GT917S)
		{
			cfg_info =  CTP_CFG_GT917S; //指向寄存器配置
			cfg_info_len = CFG_GROUP_LEN(CTP_CFG_GT917S);//计算配置表的大小
		}
		
    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_PIXEL_WIDTH & 0xFF;
		config[GTP_ADDR_LENGTH+2] = LCD_PIXEL_WIDTH >> 8;
		config[GTP_ADDR_LENGTH+3] = LCD_PIXEL_HEIGHT & 0xFF;
		config[GTP_ADDR_LENGTH+4] = LCD_PIXEL_HEIGHT >> 8;
		
		/*根据模式设置X2Y交换*/

    //不交换
//		config[GTP_ADDR_LENGTH+6] &= ~(X2Y_LOC);

    //交换
//    config[GTP_ADDR_LENGTH+6] |= (X2Y_LOC);

    //计算要写入checksum寄存器的值
    check_sum = 0;

    /* 计算check sum校验值 */
    if(touchIC == GT911 || touchIC == GT9157)
    {
        for (i = GTP_ADDR_LENGTH; i < cfg_num+GTP_ADDR_LENGTH; i++)
        {
            check_sum += (config[i] & 0xFF);
        }
        config[ cfg_num+GTP_ADDR_LENGTH] = (~(check_sum & 0xFF)) + 1; 	//checksum
        config[ cfg_num+GTP_ADDR_LENGTH+1] =  1; 						//refresh 配置更新标志
    }
    else if(touchIC == GT5688 || touchIC == GT917S) 
    {
      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 1	//读出写入的数据,检查是否正常写入
    //检验读出的数据与写入的是否相同
	{
    	    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,cfg_num);
		
			GTP_DEBUG("write ");

			GTP_DEBUG_ARRAY(config,cfg_num);

					//不对比版本号
    	    for(i=3;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
	 /* emXGUI示例中不使能中断 */
		GTP_IRQ_Enable();
	
    	GTP_Get_Info();
		
		

    return 0;
}

#define GTP_INFO(fmt,arg...)           printf("<<-GTP-INFO->> "fmt"\n",##arg)
#define GTP_ERROR(fmt,arg...)          printf("<<-GTP-ERROR->> "fmt"\n",##arg)
#define GTP_DEBUG(fmt,arg...)          do{\
                                         if(GTP_DEBUG_ON)\
                                         printf("<<-GTP-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
																					}while(0)

#define GTP_DEBUG_ARRAY(array, num)    do{\
                                         int32_t i;\
                                         uint8_t* a = array;\
                                         if(GTP_DEBUG_ARRAY_ON)\
                                         {\
                                            printf("<<-GTP-DEBUG-ARRAY->>\n");\
                                            for (i = 0; i < (num); i++)\
                                            {\
                                                printf("%02x   ", (a)[i]);\
                                                if ((i + 1 ) %10 == 0)\
                                                {\
                                                    printf("\n");\
                                                }\
                                            }\
                                            printf("\n");\
                                        }\
                                       }while(0)

#define GTP_DEBUG_FUNC()               do{\
                                         if(GTP_DEBUG_FUNC_ON)\
                                         printf("<<-GTP-FUNC->> Func:%s@Line:%d\n",__func__,__LINE__);\
                                       }while(0)

																			 
																			 
#define GTP_SWAP(x, y)                 do{\
                                         typeof(x) z = x;\
                                         x = y;\
                                         y = z;\
                                       }while (0)

INT中断服务函数

void SysTick_Handler(void)
{

	static uint8_t timecount=0;
	if(timecount>=10)
	{
		timecount=0;
        //此函数的作用是读取触摸坐标
		GTP_TouchProcess();
	}
	TimingDelay_Decrement();
	timecount++;
}

void GTP_TouchProcess(void)
{
  GTP_DEBUG_FUNC();
  Goodix_TS_Work_Func();

}

核心在于下面这个函数:

//状态寄存器地址
#define GTP_READ_COOR_ADDR 0X814E

/**
  * @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
        
            {
                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!");
        }
    }

}

这个函数内容在于,首先读取了状态寄存器,获取当前有多少个触电,然后根据触点数去读取各个点的数据,其中还有包含pre_touch的处理,保存了上一点的触点数据,利用这些数据和触电的track id号,可以确认同一条笔迹。读取后,对状态寄存器的buffer status位写0,结束读取。在这个函数中提供了两个坐标获取接口,只要在这两个接口中修改即可简单地得到了坐标信息。

触点释放和触点按下的坐标接口

/**
  * @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);

}

这两个坐标接口函数都还是在服务函数里面调用的,在实际应用中可以先把这些坐标信息存储起来,等待到系统空闲的时候再处理,就可以减轻中断服务程序的负担。

最后的主函数:

 

int main(void)
{
	/* LED 端口初始化 */
	LED_GPIO_Config();	
	
  Debug_USART_Config();    
  printf("\r\n野火STM3F429 触摸画板测试例程\r\n");


  /* 初始化触摸屏 */
  GTP_Init_Panel(); 
  SysTick_Init();
 /*初始化液晶屏*/
  LCD_Init();
	

  LCD_LayerInit();
  LTDC_Cmd(ENABLE);
	
  /*把背景层刷黑色*/
  LCD_SetLayer(LCD_BACKGROUND_LAYER);  
  LCD_Clear(LCD_COLOR_BLACK);
	
  /*初始化后默认使用前景层*/
  LCD_SetLayer(LCD_FOREGROUND_LAYER); 
  /*默认设置不透明	,该函数参数为不透明度,范围 0-0xff ,0为全透明,0xff为不透明*/
  LCD_SetTransparency(0xFF);
  LCD_Clear(LCD_COLOR_BLACK);


  Delay(0xfff);
   

  while(1);
   

}

最后关于应用层,放在另外一章吧。

标签:画板,GTP,触摸屏,InitStructure,IIC,GPIO,I2C,buf
来源: https://blog.csdn.net/Alkaid2000/article/details/122870885

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有