目录
一 电阻驱动与电容驱动原理
1. 电阻屏等效电路如下所示, 当产生按压时:
X-接地,X+接电源,Y+接ADC输入.通过读取Y+的电压以及电阻分压原理,可以得出触点的x坐标
Y-接地,Y+接电源,X+接ADC输入.通过读取X+的电压以及电阻分压原理,可以得出触点的y坐标
2. 电容屏等效图如下所示, 检测原理类似矩阵按键, 当产生按压时:
X轴产生从上至下依次产生激励信号(AC交流信号), Y轴电极同时接受信号 (该激励信号是交流电,可以通过电容穿到Y轴).
假设有如下两个按压点(分别位于(1,1)与(2,2)的位置),检测过程如下:
当X1产生激励信号时,Y1接受到的交流信号产生了变化, 所以可以确认(1,1)处有触摸.
接着当当X2产生激励信号时,Y2接受到的交流信号产生了变化, 所以可以确认(2,2)处也有有触摸.
二 软件模拟IIC
由于stm32的硬件iic有各种各样的问题,所以采用软件模拟IIC的方式,主要实现IIC起始信号函数、IIC停止信号函数、等待ACK函数、产生ACK应答函数、不产生ACK应答函数、发送字节函数、读字节函数
1. 起始信号与停止信号
下图是IIC起始信号与停止信号时序图,通过观察该图编写起始信号与停止信号函数。
- 起始信号:当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号。
/***************************************************************************************
* @brief 产生IIC起始信号,当SCL为高期间,SDA由高到低的跳变
***************************************************************************************/
void CT_IIC_Start(void)
{
CT_SDA_OUT(); //sda线输出
CT_IIC_SCL(1);
CT_IIC_SDA(1);
CT_Delay();
CT_IIC_SDA(0);
}
- 停止信号:当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。
/***************************************************************************************
* @brief 产生IIC停止信号,当SCL为高期间,SDA由低到高的跳变
***************************************************************************************/
void CT_IIC_Stop(void)
{
CT_SDA_OUT();//sda线输出
CT_IIC_SCL(1);
CT_IIC_SDA(0);//STOP:when CLK is high DATA change form low to high
CT_Delay();
CT_IIC_SDA(1);
}
在GT9147数据手册中可以发现如下截图所示内容,SCL在低电平期间至少保持1.3us,其他电平最小保持0.6
所以将IIC延时函数定时为2us可以满足所有时序。(一般支持最高速度400k的IIC设备,基本都可以使用2us延时。)
//控制I2C速度的延时
void CT_Delay(void)
{
delay_us(2);
}
2. 数据传输 与 ACK时序
下图是ACK时序与 传输数据0的时序图,这两种时序图是一样的,区别在于ACK是在8个数据时序后产生,且SDA电平是由接受数据端拉低的。
- 产生ACK时序:在SCL高电平期间,SDA为低电平状态。
/***************************************************************************************
* @brief 产生ACK应答,
***************************************************************************************/
void CT_IIC_Ack(void)
{
CT_IIC_SCL(0);
CT_Delay();
CT_SDA_OUT();
CT_IIC_SDA(0);
CT_IIC_SCL(1);
CT_Delay();
CT_IIC_SCL(0);
}
- 等待ACK时序:拉高SCL与SDA后, 然后设置SDA为输入检测状态,等待接受数据端拉低SDA
/***************************************************************************************
* @brief 等待应答信号到来,等待接受数据端拉低SDA
* @input
* @return 1,接收应答失败 ; 0,接收应答成功
***************************************************************************************/
uint8_t CT_IIC_Wait_Ack(void)
{
uint8_t ucErrTime=0;
CT_IIC_SDA(1);
CT_IIC_SCL(1);
CT_SDA_IN(); //SDA设置为输入
while(CT_READ_SDA)
{
ucErrTime++;
if(ucErrTime>250){
CT_IIC_Stop();
return 1;
}
CT_Delay();
}
CT_IIC_SCL(0);
return 0;
}
下图是nACK时序与 传输数据1的时序图,这两种时序图是一样的,区别在于ACK是在8个数据时序后产生,且SDA电平是由接受数据端拉低的。
- 产生ACK时序:在SCL高电平期间,SDA为高电平状态。
/***************************************************************************************
* @brief 不产生ACK应答
***************************************************************************************/
void CT_IIC_NAck(void)
{
CT_IIC_SCL(0);
CT_Delay();
CT_SDA_OUT();
CT_IIC_SDA(1);
CT_IIC_SCL(1);
CT_Delay();
CT_IIC_SCL(0);
}
- 发送一个字节函数,优先发送高位,即从bit7开始发送。
/***************************************************************************************
* @brief IIC发送一个字节,
* @input
* @return
***************************************************************************************/
void CT_IIC_Send_Byte(uint8_t txd)
{
uint8_t t;
CT_SDA_OUT();
CT_IIC_SCL(0);//拉低时钟开始数据传输
CT_Delay();
for(t=0;t<8;t++)
{
CT_IIC_SDA((txd&0x80)>>7);//发送bit7位
txd<<=1; //将txd的次高位左移到最高位
CT_IIC_SCL(1);
CT_Delay();
CT_IIC_SCL(0);
CT_Delay();
}
}
- 读字一个字节函数,接受到1bit数据后,将该bit放在bit0位置(通过receive++实现),如果还要继续接受数据,则将之前接受到的数据左移一位(通过receive<<=1实现),留出bit0位置接受新的数据。
/***************************************************************************************
* @brief 读1个字节
* @input ack=1时,发送ACK,ack=0,发送nACK
* @return
***************************************************************************************/
uint8_t CT_IIC_Read_Byte(unsigned char ack)
{
uint8_t i,receive=0;
CT_SDA_IN();//SDA设置为输入
CT_Delay();
for(i=0;i<8;i++ )
{
CT_IIC_SCL(0);
CT_Delay();
CT_IIC_SCL(1);
receive<<=1;
if(CT_READ_SDA)receive++;
}
if (!ack)
CT_IIC_NAck();//发送nACK
else
CT_IIC_Ack(); //发送ACK
return receive;
}
三 GT9147电容触摸屏控制芯片驱动
1. GT914内部结构框图如下图所示
引脚 | 说明 |
AVDD、AVDD18、DVDD12、VDDDIO、GND | 电源和地 |
Driving channels | 激励信号输出的引脚,一共有 0-17 个引脚,它连接到电容屏引出的各个激励信号轴 |
Sensing channels | 信号检测引脚,一共有 0-9 个引脚,它连接到电容屏 引出的各个电容量检测信号轴 |
I2C | I2C 通信信号线,包含 SCL与 SDA,外部控制器通过它与 GT9147 芯片通讯 |
INT | 中断信号,GB9147 芯片通过它告诉外部控制器有新的触摸事件 |
/RSTB | 复位引脚,用于复位 GT9157 芯片;在上电时还与 INT 引脚配合设置 IIC 通讯的设备地址 |
2. 上电流程
GT9147上电初始化流程如下所示:
- 配置RST输出低电平,INT输出低(或者高)电平后,位置100us以上,此阶段配置IIC地址为 0xBA/0xBB(或者0x28/0x29)
- 接着配置RST输出高电平并维持5ms以上后,可以配置INT引脚为悬浮输入模式。
- 等待50ms以上后,可以发送配置信息。
代码参考原子例程编写,贴上代码:
uint8_t GT9147_Init(void)
{
uint8_t temp[5];
GPIO_InitTypeDef GPIO_Initure;
GPIO_Initure.Pin = GPIO_PIN_7; //PH7
GPIO_Initure.Mode = GPIO_MODE_INPUT; //输出
GPIO_Initure.Pull = GPIO_PULLUP; //上拉
GPIO_Initure.Speed = GPIO_SPEED_HIGH; //高速
HAL_GPIO_Init(GPIOH, &GPIO_Initure); //初始化
GPIO_Initure.Pin = GPIO_PIN_8; //PI8
GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出
HAL_GPIO_Init(GPIOI, &GPIO_Initure); //初始化
CT_IIC_Init(); //初始化电容屏的I2C总线
GT_RST(0); //复位
delay_ms(1); //INT引脚电平维持100us以上
GT_RST(1); //释放复位
delay_ms(10); //释放复位后维持5ms以上,设置INT为悬浮输入
GPIO_Initure.Pin = GPIO_PIN_7;
GPIO_Initure.Mode = GPIO_MODE_IT_RISING;
GPIO_Initure.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOH, &GPIO_Initure); //初始化
delay_ms(60);
GT9147_RD_Reg(GT_PID_REG, temp, 4); //读取产品ID
temp[4] = 0;
printf("CTP ID:0x%s\r\n", temp); //打印ID
GT9147_Send_Cfg(1);//更新并保存配置
return 1;
}
3. 读取坐标流程
使用中断方式读取坐标,需要注意的是,如果不及时读取坐标信息会一直产生中断。GT9147的中断脚与STM32的LINE7中断线相连,中断回调函数如下所示。当按下触摸屏时,touch_x与touch_y分别保存触摸时的xy坐标;当松开触摸屏时,touch_x,touch_y赋值0xffff
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
uint8_t buf[4];
uint8_t i = 0, mode = 0, temp = 0;
uint16_t x[5], y[5];
if(GPIO_Pin == GPIO_PIN_7) { //读取坐标, 否则会一直有INT脉冲
GT9147_RD_Reg(GT_GSTID_REG, &mode, 1); //读取触摸点的状态
if(mode & 0X80 && ((mode & 0XF) < 6)) //有坐标可读取
{
temp = 0;
GT9147_WR_Reg(GT_GSTID_REG, &temp, 1);//清标志
}
if( (mode & 0xF) && ((mode & 0xF) < 6)) //判断触摸点个数
{
for(i = 0; i < (mode & 0xF); i++)
{
GT9147_RD_Reg(GT9147_TPX_TBL[i], buf, 4); //读取XY坐标值
x[i] = (((uint16_t)buf[1] << 8) + buf[0]);
y[i] = (((uint16_t)buf[3] << 8) + buf[2]);
}
touch_x = x[0];
touch_y = y[0];
}
if((mode&0x8F)==0x80)//无触摸点按下,xy赋值为0xffff
{
touch_x = 0xffff;
touch_y = 0xffff;
}
}
}
4.主机 对 GT9147 进行读操作时序
根据该时序图编写读寄存器函数如下:
/***************************************************************************************
* @brief 从GT9147读出一次数据
* @input reg:起始寄存器地址
buf:数据缓缓存区
len:读数据长度
* @return
***************************************************************************************/
void GT9147_RD_Reg(uint16_t reg,uint8_t *buf,uint8_t len)
{
uint8_t i;
CT_IIC_Start();
CT_IIC_Send_Byte(GT_CMD_WR); //发送写命令
CT_IIC_Wait_Ack();
CT_IIC_Send_Byte(reg>>8); //发送高8位地址
CT_IIC_Wait_Ack();
CT_IIC_Send_Byte(reg&0XFF); //发送低8位地址
CT_IIC_Wait_Ack();
CT_IIC_Start();
CT_IIC_Send_Byte(GT_CMD_RD); //发送读命令
CT_IIC_Wait_Ack();
for(i=0;i<len;i++)
{
buf[i]=CT_IIC_Read_Byte(i==(len-1)?0:1); //发数据
}
CT_IIC_Stop();//产生一个停止条件
}
5.主机对 GT9147 进行写操作时序
通过该时序图,编写写寄存器函数如下
/***************************************************************************************
* @brief 向GT9147写入一次数据
* @input reg:起始寄存器地址;
buf:数据缓缓存区
len:写数据长度
* @return 0,成功; 1,失败.
***************************************************************************************/
uint8_t GT9147_WR_Reg(uint16_t reg,uint8_t *buf,uint8_t len)
{
uint8_t i;
uint8_t ret=0;
CT_IIC_Start();
CT_IIC_Send_Byte(GT_CMD_WR); //发送写命令
CT_IIC_Wait_Ack();
CT_IIC_Send_Byte(reg>>8); //发送高8位地址
CT_IIC_Wait_Ack();
CT_IIC_Send_Byte(reg&0XFF); //发送低8位地址
CT_IIC_Wait_Ack();
for(i=0;i<len;i++)
{
CT_IIC_Send_Byte(buf[i]); //发数据
ret = CT_IIC_Wait_Ack();
if(ret)break;
}
CT_IIC_Stop(); //产生一个停止条件
return ret;
}
6. GT9147配置函数如下,GT9147_CFG_TBL针对分辨率480×272的触摸屏。
配置寄存器时是要注意以下几点:
- 新的版本号大于等于GT9147内部flash原有版本号,才会更新配置.
- 写完GT9147_CFG_TBL中的配置后,还需要往0x80FF寄存器中写入校验,0x8100寄存器中写1。
- 关于配置更详细的信息可以参考GT9147编程参考手册
const uint8_t GT9147_CFG_TBL[]=
{
0x60,0xe0,0x01,0x10,0x01,0x05,0x0C,0x00,0x01,0x08,
0x28,0x05,0x50,0x32,0x03,0x05,0x00,0x00,0xff,0xff,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x89,0x28,0x0a,
0x17,0x15,0x31,0x0d,0x00,0x00,0x02,0x9b,0x03,0x25,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x32,0x00,0x00,
0x00,0x0f,0x94,0x94,0xc5,0x02,0x07,0x00,0x00,0x04,
0x8d,0x13,0x00,0x5c,0x1e,0x00,0x3c,0x30,0x00,0x29,
0x4c,0x00,0x1e,0x78,0x00,0x1e,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,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x08,0x0a,0x0c,0x0e,0x10,0x12,0x14,0x16,
0x18,0x1a,0x00,0x00,0x00,0x00,0x1f,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0x00,0x02,0x04,0x05,0x06,0x08,0x0a,0x0c,
0x0e,0x1d,0x1e,0x1f,0x20,0x22,0x24,0x28,0x29,0xff,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,
};
uint8_t GT9147_Send_Cfg(uint8_t mode)
{
uint8_t buf[2];
uint8_t i=0;
buf[0]=0;
buf[1]=mode; //是否写入到GT9147 FLASH? 即是否掉电保存
for(i=0;i<sizeof(GT9147_CFG_TBL);i++)
buf[0]+=GT9147_CFG_TBL[i];//计算校验和
buf[0]=(~buf[0])+1;
GT9147_WR_Reg(GT_CFGS_REG,(uint8_t*)GT9147_CFG_TBL,sizeof(GT9147_CFG_TBL));//发送寄存器配置
GT9147_WR_Reg(GT_CHECK_REG,buf,2);//写入校验和,和配置更新标记
return 0;
}
四 移植触摸屏驱动到STemWin
这里假设已经成功移植了STemWin到STM32F7工程。
1. 在GUIConf打开STemWin触摸屏驱动宏
#define GUI_SUPPORT_TOUCH (1) // Support touchscreen
2. 新建GUI_X_Touch_Analog.c文件实现以下四个函数,在第三章第3小节我们知道touch_x,touch_y表示当前触摸的xy坐标。整个文件的内容如下所示。
void GUI_TOUCH_X_ActivateX(void)
{
}
void GUI_TOUCH_X_ActivateY(void)
{
}
/*获取x坐标*/
int GUI_TOUCH_X_MeasureX (void)
{
return touch_x;
}
/*获取y坐标*/
int GUI_TOUCH_X_MeasureY (void)
{
return touch_y;
}
3. 使用GUI_TOUCH_Calibrate函数校准x与y值,然后才能调用GUI_TOUCH_Exec()函数处理触摸事件。
void StartTouchTask(void const * argument)
{
/* USER CODE BEGIN StartTouchTask */
GUI_CURSOR_Show();//显示鼠标指针
GUI_TOUCH_Calibrate(GUI_COORD_X,0,LCD_WIDTH,0,LCD_WIDTH);
GUI_TOUCH_Calibrate(GUI_COORD_Y,0,LCD_HEIGHT,0,LCD_HEIGHT);
/* Infinite loop */
for(;;)
{
GUI_TOUCH_Exec();
osDelay(5);
}
/* USER CODE END StartTouchTask */
}