触摸屏实验

更多精彩内容请关注抖音:81849645041

目的

了解电容触摸屏GT9147驱动IC原理,通过4.3寸LCD显示屏实现触摸画板实验。

原理

        触摸屏(touch screen)又称为“触控屏”、“触控面板”,是一种可接收触头等输入讯号的感应式装置。作为一种新型的电脑输入设备,可以用来取代传统的机械按键等输入设备。它是目前最简单、方便、自然的一种人机交互方式。触摸屏本质上与液晶是分离的。触摸屏负责的是检测触摸点,液晶屏负责的是显示。

        按照触摸屏的工作原理和传输信息的介质,把触摸屏分为四种,它们分别为:

  1. 电阻式:定位准确,单点触摸。
  2. 电容感应式:支持多点触摸,价格偏贵。
  3. 红外线式:价格低廉,但其外框易碎,容易产生光干扰,曲面情况下失真。
  4. 表面声波式:解决各种缺点,但是屏幕表面如果有水滴和尘土会使触摸屏变的迟钝。

        电容型触摸屏分为2类:

  1. 表面电容式触摸屏:表面电容式触摸屏技术是利用ITO(铟锡氧化物,是一种透明的导电材料)导电膜,通过电场感应方式感测屏幕表面的触摸行为进行。但是表面电容式触摸屏有一些局限性,它只能识别一个手指或者一次触摸。
  2. 投射式电容触摸屏:投射电容式触摸屏是传感器利用触摸屏电极发射出静电场线。

        本实验选用电容触摸屏,也是采用的是投射式电容屏。电容式触摸屏是利用充电时间检测电容大小,从而通过检测出电容值的变化来获知触摸信号。如下图:

        电容屏的最上层是玻璃,核心层部分是由ITO材料构成的,这些导电材料在屏幕里构成了人眼看不见的静电网,静电网由多行X轴电极和多列Y轴电极构成,两个电极之间会形成电容。触摸屏工作时,X轴电极发出AC交流信号,而交流信号能穿过电容,即通过Y轴能感应出该信号,当交流电穿越时电容会有充放电过程,检测该充电时间可获知电容量。若手指触摸屏幕,会影响触摸点附近两个电极之间的耦合,从而改变两个电极之间的电容量,若检测到某电容的电容量发生了改变,即可获知该电容处有触摸动作(这就是为什么它被称为电容式触摸屏以及绝缘体触摸没有反应的原因)。

        电容屏ITO层的电极由多个菱形导体组成,生产时使用蚀刻工艺在ITO层生成这样的结构,结构如下图:

        X轴电极与 Y轴电极在交叉处形成电容,即这两组电极构成了电容的两极,这样的结构覆盖了整个电容屏,每个电容单元在触摸屏中都有其特定的物理位置,即电容的位置就是它在触摸屏的 XY坐标。检测触摸的坐标时,第 1 条 X轴的电极发出激励信号,而所有Y轴的电极同时接收信号,通过检测充电时间可检测出各个 Y轴与第 1 条 X轴相交的各个互电容的大小,各个 X 轴依次发出激励信号,重复上述步骤,即可得到整个触摸屏二维平面的所有电容大小。当手指接近时,会导致局部电容改变,根据得到的触摸屏电容量变化的二维数据表,可以得知每个触摸点的坐标,因此电容触摸屏支持多点触控。

        其实电容触摸屏可看作是多个电容按键组合而成,就像机械按键中独立按键和矩阵按键的关系一样,甚至电容触摸屏的坐标扫描方式与矩阵按键都是很相似的。在任何情况下,触摸位置都是通过测量X电极和Y电极之间信号改变量的分配来确定的,随后会使用数学算法处理这些已改变的信号电平,以确定触摸点的XY坐标。

        电容触摸屏驱动原理:GT9147与MCU连接也是通过4根线:SDA、SCL、RST和INT,GP9147的I2C地址,可以是0x14或者0x5D,当复位结束后的5ms内,如果INT是高电平,则使用0x14作为地址,否则使用0x5D作为地址,具体的设置过程,请查看GT9147数据手册,本实验我们使用0x14作为器件地址。接下来,介绍一下GT9147的几个重要的寄存器:

  • 控制命令寄存器(0x8040)

该寄存器可以写入不同值,实现不同的控制,我们一般使用0和2这两个值,写入2,即可软复位GT9147,在硬复位之后,一般要往该寄存器写2,实行软复位。然后写入0,即可正常读取坐标数据(并且会结束软复位)。

  • 配置寄存器组(0x8047-0x8100)

这里共186个寄存器,用于配置GT9147的各个参数,这些配置一般由厂家提供给我们(一个数组),所以我们只需要将厂家给我们的配置,写入到这些寄存器里面,即可完成GT9147的配置。由于GT9147可以保存配置信息(可写入内部FLASH,从而不需要每次上电都更新配置),我们有几点注意的地方提醒大家:1:0x8047寄存器用于指示配置文件版本号,程序写入的版本号,必须大于等于GT9147本地保存的版本号,才可以更新配置;2:0x80FF寄存器用于存储校验和,使得0x8047-0x80FF之间所有的数据之和为0;3:0x8100用于控制是否将配置保存在本地,写0,则不保存配置,写1则保存配置。

  • 产品ID寄存器(0x8140-0x8143)

这里总共由4个寄存器组成,用于保存产品ID,对于GT9147,这4个寄存器读出来就是:9,1,4,7四个字符(ASCII码格式)。因此,我们通过这4个寄存器的值,来判断驱动IC的型号,以便执行不同的初始化。

  • 状态寄存器(0x814E)

        我们仅关心最高位和最低4位,最高位用于表示buffer状态,如果有数据(坐标/按键),buffer就会是1,最低4位用于表示有效触电的个数,范围是:0~5,0,表示没有触摸,5,表示有5点触摸。

  • 坐标数据寄存器(共50个)

         这里共分成5组(5个点),每组6个寄存器存储数据,我们一般只用到触点的x,y坐标,所以只需要读取0x8150~0x8153的数据,组合即可得到触点坐标。同样GT9147也支持寄存器地址自增,我们只需要发送寄存器组的首地址,然后连续读取即可,GT9147会自动地址自增,从而提高读取速度。

        GT9147只需要经过简单的初始化就可以正常使用了,初始化流程:硬复位→延时10ms→结束硬复位→设置I2C地址→延时100ms→软复位→更新配置(需要时)→结束软复位。此时GT9147即可正常使用了。

        然后,我们不停的查询0x814E寄存器,判断是否有有效触点,如果有,则读取坐标数据寄存器,得到触点坐标,特别注意,如果0x814E读到的值最高位为1,就必须对该位写0,否则无法读到下一次坐标数据。

准备

        MDK5 开发环境。

        STM32F4xx HAL库。

        STM32F407 开发板。

        4.3寸TFTLCD显示屏

        GT9147编程指南

        STM32F4xx 参考手册。

        STM32F407 开发板电路原理图。

步骤

  • 电容式触摸屏用到4根引脚线,分别是:T_PEN(CT_INT)、T_CS(CT_RET)、T_CLK(CT_SCL)和T_MOSI(CT_SDA),查看电路原理图,T_MOSI、T_CLK、T_CS 和 T_PEN 分别连接在 STM32F4 的:PF11、PB0、PC13、PB1。其中:CT_INT、CT_RST、CT_SCL 和 CT_SDA 分别是 GT9147的:中断输出信号、复位信号,IIC 的 SCL 和 SDA 信号。
  • 在头文件中定义GT9147的设备地址及寄存器地址,定义结构体COORDINATE,用来获取触摸点坐标信息,最后进行函数声明。
#ifndef _BSP_TOUCH_H_
#define _BSP_TOUCH_H_

#include "stm32f4xx.h"
#include <stdbool.h>
#include "bsp_soft_i2c.h"
#include <string.h>
#include "bsp_tftlcd.h"


#define GT_RST_H HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET) 
#define GT_RST_L HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET)

#define GT_INT_H HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET)
#define GT_INT_L HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET)

#define GT_CMD_W 0x28	// 写命令
#define GT_CMD_R 0x29	// 读命令

#define GT_CTRL_REG 	0x8040 // GT9147控制命令寄存器
#define GT_CFGS_REG 	0x8047 // GT9147配置起始地址
#define GT_CHECK_REG 	0x80FF // GT9147校验和地址
#define GT_ID_REG		0x8140 // GT9147产品ID寄存器		
#define GT_STATE_REG	0x814E // GT9147当前状态寄存器
#define GT_TP1_REG	0x8150 // 第一个触摸点地址
#define GT_TP2_REG	0x8158 // 第二个触摸点地址
#define GT_TP3_REG	0x8160 // 第三个触摸点地址
#define GT_TP4_REG	0x8168 // 第四个触摸点地址
#define GT_TP5_REG	0x8170 // 第五个触摸点地址

typedef struct
{
	uint8_t Point_num; // 触摸点总数
	int16_t x[5]; // 当前x坐标
	int16_t y[5]; // 当前y坐标
	int16_t Last_x[5]; // 上一次x坐标
	int16_t Last_y[5]; // 上一次y坐标
}COORDINATE;

uint8_t GT9147_Init(void);
bool GT9147_Write_REG(uint16_t reg, uint8_t *buf, uint8_t len);
bool GT9147_Read_REG(uint16_t reg, uint8_t *buf, uint8_t len);

void Touch_Init(void);
void Touch_Draw_Point(void);

#endif
  • 在源文件中首先创建GT9147配置参数表,用于初始化GT9147。第一个字节为版本号(0x60),必须保证新的版本号大于等于GT9147内部的FLASH原有版本号,才会更新配置。
const uint8_t GT9147_CFG[]=
{
	0X60,0XE0,0X01,0X20,0X03,0X05,0X35,0X00,0X02,0X08,
	0X1E,0X08,0X50,0X3C,0X0F,0X05,0X00,0X00,0XFF,0X67,
	0X50,0X00,0X00,0X18,0X1A,0X1E,0X14,0X89,0X28,0X0A,
	0X30,0X2E,0XBB,0X0A,0X03,0X00,0X00,0X02,0X33,0X1D,
	0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X32,0X00,0X00,
	0X2A,0X1C,0X5A,0X94,0XC5,0X02,0X07,0X00,0X00,0X00,
	0XB5,0X1F,0X00,0X90,0X28,0X00,0X77,0X32,0X00,0X62,
	0X3F,0X00,0X52,0X50,0X00,0X52,0X00,0X00,0X00,0X00,
	0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
	0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X0F,
	0X0F,0X03,0X06,0X10,0X42,0XF8,0X0F,0X14,0X00,0X00,
	0X00,0X00,0X1A,0X18,0X16,0X14,0X12,0X10,0X0E,0X0C,
	0X0A,0X08,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
	0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
	0X00,0X00,0X29,0X28,0X24,0X22,0X20,0X1F,0X1E,0X1D,
	0X0E,0X0C,0X0A,0X08,0X06,0X05,0X04,0X02,0X00,0XFF,
	0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,0X00,
	0X00,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,
	0XFF,0XFF,0XFF,0XFF,
};
  • 创建GT9147_Write_REG()函数,实现向GT9147指定寄存器中写入数据。函数实现方法与I2C写数据函数有些类似,首先发送I2C开始信号,随后发送GT9147写地址,然后发送写入寄存器的高8位和低8位地址,最后通过循环将需要写入的数据发送出去,发送完成后停止I2C通讯。
// 向GT9147写入数据
bool GT9147_Write_REG(uint16_t reg, uint8_t *buf, uint8_t len)
{
	I2C_Start();
	I2C_Send_Byte(GT_CMD_W); // 发送写命令
	if(!I2C_Wait_Ack()) return false;
	I2C_Send_Byte(reg >> 8); // 发送寄存器高8位地址
	if(!I2C_Wait_Ack()) return false;
	I2C_Send_Byte(reg & 0xFF); // 发送寄存器低8位地址
	if(!I2C_Wait_Ack()) return false;

	for(uint8_t i=0; i<len; i++)
	{
		I2C_Send_Byte(buf[i]);
		if(!I2C_Wait_Ack()) {I2C_Stop(); return false;}
	}
	I2C_Stop();
	return true;
}
  • 创建GT9147_Read_REG()函数,实现从GT9147中读取数据。

        第一步:首先发送I2C开始信号,然后发送GT9147写地址,等待应答。

        第二步:随后发送需要读取寄存器的高、低8位地址,分别等待应答。

        第三步:重新发送开始信号,然后发送GT9147读地址,等待ACK。

        第四步:通过循环连续读取多个字节数据,然后发送停止信号。

// 从GT9147读出数据
bool GT9147_Read_REG(uint16_t reg, uint8_t *buf, uint8_t len)
{
	I2C_Start();
	I2C_Send_Byte(GT_CMD_W); // 发送写命令
	if(!I2C_Wait_Ack()) return false;
	I2C_Send_Byte(reg >> 8); // 发送寄存器高8位地址
	if(!I2C_Wait_Ack()) return false;
	I2C_Send_Byte(reg & 0xFF); // 发送寄存器低8位地址
	if(!I2C_Wait_Ack()) return false;
	I2C_Start();
	I2C_Send_Byte(GT_CMD_R); // 发送读命令
	if(!I2C_Wait_Ack()) return false;
	
	for(uint8_t i=0; i<len; i++)
	{
		buf[i] = I2C_Read_Byte();
		if(i < len-1){I2C_Ack();}
	}
	I2C_NAck();
	I2C_Stop();
	return true;
}
  • 创建GT9147_Send_CFG()函数,用来配置GT9147的参数。函数中首先通过循环计算上面定义的配置参数数组的校验和,然后调用GT9147_Write_REG()函数分别将配置参数和校验和写入到GT9147寄存器中。
// 发送GT9147配置参数
// mode:1,控制配置保存在本地 0,控制配置不保存在本地
void GT9147_Send_CFG(uint8_t mode)
{
	uint8_t buf[2];
	buf[0] = 0;
	buf[1] = mode; // 是否写入到GT9147 Flash 即是否掉电保存
	for(uint8_t i=0; i<sizeof(GT9147_CFG); i++)
	{
		buf[0] += GT9147_CFG[i]; // 计算校验和
	}
	buf[0] = (~buf[0]) + 1;
	GT9147_Write_REG(GT_CFGS_REG, (uint8_t *)GT9147_CFG, sizeof(GT9147_CFG)); // 发送寄存器配置
	GT9147_Write_REG(GT_CHECK_REG, buf, 2); // 写入校验和,和配置更新标记
}
  • 创建GT9147_Init函数,用来初始化GT9147。

        第一步:初始化T_CS和T_PEN引脚为输出模式。

        第二步:初始化屏幕使用的I2C引脚,然后复位T_CS引脚并延时10ms后将INT引脚拉高,将GT9147的地址配置为0x14,配置后释放复位信号线。

        第三步:配置输出信号线引脚为输入模式,然后调用GT9147_Read_REG()函数读取GT9147的ID。

        第四步:判断读取到的ID为9147则表示设备读取正确,然后实现软复位并读取GT9147的配置寄存器的第一个版本号,如果默认版本比较低,则调用GT9147_Send_CFG()函数实现更新,最后结束复位。

// 初始化GT9147
// 返回值:0,初始化成功;1,初始化失败
uint8_t GT9147_Init(void)
{
	GPIO_InitTypeDef GPIO_Handle;
	
	__HAL_RCC_GPIOB_CLK_ENABLE();
	__HAL_RCC_GPIOC_CLK_ENABLE();
	
	GPIO_Handle.Mode = GPIO_MODE_OUTPUT_PP; // 输出推挽模式
	GPIO_Handle.Pin = GPIO_PIN_13; // CT_RET / T_CS
	GPIO_Handle.Pull = GPIO_PULLUP; // 上拉
	GPIO_Handle.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
	HAL_GPIO_Init(GPIOC, &GPIO_Handle); // GPIO初始化
	
	GPIO_Handle.Pin = GPIO_PIN_1; // CT_INT / T_PEN
	GPIO_Handle.Mode = GPIO_MODE_OUTPUT_PP; // 输出推挽模式
	HAL_GPIO_Init(GPIOB, &GPIO_Handle); // GPIO初始化
	
	I2C_Soft_Init(); // 初始化屏幕的I2C引脚
	GT_RST_L; // 复位
	HAL_Delay(10); // 延时10ms
	GT_INT_H; // 使用0x14作为地址
	HAL_Delay(1); // 延时10ms
	GT_RST_H; // 释放复位信号线
	HAL_Delay(10); // 延时10ms
	
	// 配置输出信号线为输入模式
	GPIO_Handle.Pin = GPIO_PIN_1; // CT_INT / T_PEN
	GPIO_Handle.Mode = GPIO_MODE_INPUT; // 输入模式
	GPIO_Handle.Pull = GPIO_NOPULL; // 浮空模式
	HAL_GPIO_Init(GPIOB, &GPIO_Handle); // GPIO初始化	
	
	HAL_Delay(100); // 延时100ms
	uint8_t Temp[5] = {0}; // 定义临时变量
	GT9147_Read_REG(GT_ID_REG, Temp, 4); // 读取产品ID
	Temp[4] = '\0';
	
	if(strcmp((char *)Temp, "9147") == 0) // ID==9147
	{
		Temp[0] = 2;
		GT9147_Write_REG(GT_CTRL_REG, Temp, 1); // 软复位
		GT9147_Read_REG(GT_CFGS_REG, Temp, 1); // 读取寄存器,获取产品版本
		if(Temp[0] < 0x60) // 默认版本比较低,需要更新
		{
			GT9147_Send_CFG(1); // 更新并保持配置
		}
		HAL_Delay(10); // 延时10ms
		Temp[0] = 0x00;
		GT9147_Write_REG(GT_CTRL_REG, Temp, 1); // 结束复位
		return 0;
	}
	return 1;
}
  • 创建函数Touch_Init(),实现触摸初始化。函数仅调用GT9147_Init()进行芯片初始化。
// 触摸初始化
void Touch_Init(void)
{
	GT9147_Init(); // 芯片初始化
}
  • 创建Touch_Draw_Point()函数,实现触摸画板功能。

        第一步:调用GT9147_Read_REG()函数读取状态寄存器的buffer状态。

        第二步:如果获取坐标已经准备好,那么获取触摸点数并清除标志。

        第三步:获取触摸坐标值,判断触摸点是否在0-5之间,如果在此之间,那么分别获取每个点坐标值,并将坐标值根据横竖屏状态分别存储到结构体变量x、y坐标中。

        第四步:通过循环解析读取坐标位置,如果横竖坐标超出屏幕尺寸范围,那么跳出本次循环,获取下一个数据;如果坐标在复位区域(此时设置复位区域为屏幕的右上角),那么对屏幕进行清屏,刷新屏幕为白色;最后如果获取到其它的坐标,那么则根据两点处的坐标实现画线功能(此处添加了一个判断,如果两个坐标之间距离小于40,才实现画线功能)。

COORDINATE coordinate; // 定义触摸点结构体变量
extern _LCD_Dev lcddev; // 声明LCD屏幕结构体变量

// 定义坐标数据寄存器地址
const uint16_t GT9147_TP_REG[5] = {GT_TP1_REG, GT_TP2_REG, GT_TP3_REG, GT_TP4_REG, GT_TP5_REG};

// 触摸画点
void Touch_Draw_Point(void)
{
	uint8_t state = 0; // 定义状态存储变量
	uint8_t buff[4]; // 定义缓冲区
	
	GT9147_Read_REG(GT_STATE_REG, &state, 1); // 读取坐标标志位
	if(state & 0x80) // 如果坐标已经准备好
	{
		coordinate.Point_num = state & 0x0F; // 获取触摸点数
		state = 0;
		GT9147_Write_REG(GT_STATE_REG, &state, 1); // 清除标志
		// 获取触摸坐标值
		if(coordinate.Point_num>0 && coordinate.Point_num<6) // 判断触摸点是否正常
		{
			// 通过循环获取坐标点值
			for(uint8_t i=0; i<coordinate.Point_num; i++)
			{
				GT9147_Read_REG(GT9147_TP_REG[i], buff, 4); // 从触摸点地址读取坐标高、低8位
				if(lcddev.dir == 1) 
				{
					// 如果为横屏
					coordinate.y[i] = ((uint16_t)buff[1] << 8) + buff[0]; // 计算5个点坐标
					coordinate.x[i] = 800 - (((uint16_t)buff[3] << 8) + buff[2]);
				}
				else
				{
					// 如果为竖屏
					coordinate.x[i] = ((uint16_t)buff[1] << 8) + buff[0]; // 计算5个点坐标
					coordinate.y[i] = ((uint16_t)buff[3] << 8) + buff[2];
				}
			}
		}
		// 根据获取到的坐标值画点
		for(uint8_t i=0; i<coordinate.Point_num; i++)
		{
			// 判断横竖坐标是否在屏幕尺寸范围内
			if(coordinate.x[i] > lcddev.width || coordinate.y[i] > lcddev.height)
			{
				continue;
			}			
			// 复位区域
			if(coordinate.x[i] > (lcddev.width - 80) && coordinate.y[i] < 30)
			{
				LCD_Clear(WHITE);
				LCD_ShowString(400, 0, 200, 16, "RESET", RED);
			}				
			// 根据坐标实现画线
			if((coordinate.x[i] - coordinate.Last_x[i]) > -40 &&
			  (coordinate.x[i] - coordinate.Last_x[i]) < 40  &&
			  (coordinate.y[i] - coordinate.Last_y[i]) > -40 &&
			  (coordinate.y[i] - coordinate.Last_y[i]) < 40) // 如果两个点x,y坐标距离小于40
			{
				// 画线
				LCD_DrawLine(coordinate.Last_x[i], coordinate.Last_y[i], coordinate.x[i], 
				coordinate.y[i], BLUE);
			}
			coordinate.Last_x[i] = coordinate.x[i];
			coordinate.Last_y[i] = coordinate.y[i];
		}
	
	}
}
  • 主函数main程序如下:

        第一步:初始化系统时钟。

        第二步:初始化LCD屏幕和触摸。

        第三步:调用LCD_ShowString()函数,在屏幕右上角显示”RESET”字符,提示复位。

        第四步:在while()循环中调用Touch_Draw_Point()函数实现触摸画板功能。

int main(void)
{
	CLOCK_Init(); // 初始化系统时钟
	TFTLCD_Init(); // LCD屏幕初始化
	Touch_Init(); // 触摸初始化
	
	LCD_ShowString(400, 0, 200, 16, "RESET", RED);
	while(1)
	{
		Touch_Draw_Point(); // 触摸画板
	}
}

现象

        将程序下载到开发板中,液晶屏会显示出触摸画板的界面,点击屏幕可以在该界面画出简单的图形,点击右上角“RESET”可以复位清屏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奚海蛟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值