使用电容触摸屏时发现硬件IIC会使电容触摸屏卡死,经过调试发现软件IIC更加好用,那么下面就了解一下软件IIC叭。
IIC协议:
通讯的起始和停止信号
有效数据
响应信号
具体参考博客:STM32F429入门(二十):IIC通讯协议(硬件)_Alkaid2000的博客-CSDN博客_stm32 硬件iic
对于SCL与SDA两条线,我们可以使用gpio输出的方式进行控制:
(1)模拟起始信号:SCL为高电平、SDA由高电平变为低电平
//先定义引脚的高低电平 #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); }
最后关于应用层,放在另外一章吧。