当前位置: 首页 > news >正文

【普中STM32F1xx开发攻略--标准库版】-- 第 43 章 触摸屏实验

(1)实验平台:

普中STM32F103 朱雀、玄武开发板https://item.taobao.com/item.htm?id=620302685024(2)资料下载 :普中科技-各型号产品资料下载链接


在前面章节我们介绍了使用TFTLCD模块显示字符和汉字, 利用TFTLCD模块, STM32 系统就有了高级信息输出的功能, 如果我们还希望有一个友好的用户输入的设备, 触摸屏就是非常好的选择, 现如今大多电子产品是将触摸屏配合液晶显示器组成人机交互系统, 比如手机、 平板等。 因此我们很有必要学习下触摸屏的控制。 这一章我们来学习下如何使用 STM32F1 来驱动触摸屏。 我们 STM32F1 开发板本身并没有触摸屏控制器, 但是它支持触摸屏, 可以通过外接带触摸屏的LCD 模块(比如我们的 TFTLCD 模块) 来实现触摸屏控制。 本章要实现的功能是:通过 TFTLCD 模块上的触摸板(包括电阻触摸和电容触摸) 实现触摸功能, 最终实现一个画板的功能。 学习本章可以参考“\6--芯片资料\彩屏数据手册” 相关芯片数据手册(电阻触摸屏和电容触摸屏) 。 本章分为如下几部分内容:

43.1 触摸屏介绍

43.1.1 电阻式触摸屏介绍

43.1.2 电容式触摸屏介绍

43.2 硬件设计

43.3 软件设计

43.3.1 触摸屏初始化函数

43.3.2 触摸屏校准函数

43.3.3 触摸屏扫描函数

43.3.4 主函数

43.4 实验现象

课后作业


43.1 触摸屏介绍

触摸屏又称触控面板, 它是一种把触摸位置转化成坐标数据的输入设备, 根据触摸屏的检测原理, 主要分为电阻式触摸屏和电容式触摸屏。 下面我们就分别来介绍下这两种触摸屏。

43.1.1 电阻式触摸屏介绍

电阻式触摸屏是一种传感器, 它将矩形区域中触摸点(X,Y)的物理位置转换为代表 X 坐标和 Y 坐标的电压。 很多 LCD 模块都采用了电阻式触摸屏, 比如我们2.0/2.2/2.4/2.6/2.8/3.2/3.5/4.3/7.0 寸的 TFTLCD 模块都是采用电阻式触摸屏。 使用时需要用一定的压力才会能检测到电压, 即触摸。

电阻式触摸屏基本上是薄膜加上玻璃的结构, 薄膜和玻璃相邻的一面上均涂有 ITO(纳米铟锡金属氧化物)涂层, ITO 具有很好的导电性和透明性。 当触摸操作时, 薄膜下层的 ITO 会接触到玻璃上层的 ITO, 经由感应器传出相应的电信号,经过转换电路送到处理器, 通过运算转化为屏幕上的 X、 Y 值, 而完成点选的动作, 并呈现在屏幕上。 电阻式触摸屏结构如下图所示:

电阻触摸屏的工作原理主要是通过压力感应原理来实现对屏幕内容的操作和控制的, 这种触摸屏屏体部分是一块与显示器表面非常配合的多层复合薄膜,其中第一层为玻璃或有机玻璃底层, 第二层为隔层, 第三层为多元树脂表层, 表面还涂有一层透明的导电层, 上面再盖有一层外表面经硬化处理、 光滑防刮的塑料层。 在多元脂表层表面的传导层及玻璃层感应器是被许多微小的隔层所分隔电流通过表层, 轻触表层压下时, 接触到底层, 控制器同时从四个角读出相称的电流及计算手指位置的距离。 这种触摸屏利用两层高透明的导电层组成触摸屏, 两层之间距离仅为 2.5 微米。 当手指触摸屏幕时, 平常相互绝缘的两层导电层就在触摸点位置有了一个接触, 因其中一面导电层接通 Y 轴方向的电源均匀电压场,使得侦测层的电压由零变为非零, 控制器侦测到这个接通后, 进行 A/D 转换, 并将得到的电压值与参考电压相比, 即可得触摸点的 Y 轴坐标, 同理得出 X 轴的坐标, 这就是所有电阻技术触摸屏共同的最基本原理。

电阻触摸屏的优点: 精度高、 价格便宜、 抗干扰能力强、 稳定性好。

电阻触摸屏的缺点: 容易被划伤、 透光性不太好、 不支持多点触摸。

从上面的简介, 我们知道触摸屏都需要一个 AD 转换器, 也就是要将电压变化读取出来, 供主机求出触摸的位置。 我们的 TFTLCD 模块使用的是四线电阻式触摸屏, 这种触摸屏的控制芯片有很多, 包括: ADS7843、 ADS7846、 TSC2046、XPT2046 和 AK4182 等。 这几款芯片的驱动基本上是一样的, 也就是说你只要写出了 XPT2046 的驱动, 这个驱动对其他几个芯片也是有效的。 而且封装也有一样的, 而且管脚也完全兼容。 所以在替换起来非常方便。

我们彩屏上面使用的触摸屏控制芯片是 XPT2046。 XPT2046 的特点主要有:

1) 一款 4 导线制触摸屏控制器, 采用 SPI 模式进行通信。

2) 内含 12 位分辨率 125KHz 转换速率逐步逼近型 A/D 转换器。

3) 支持从 1.5V 到 5.25V 的低电压 I/O 接口。

4) 只需执行两次 A/D 转换即可查出被按的屏幕位置。

5) 可以测量加在触摸屏上的压力

6) 芯片内部自带温度检测、 电池电压(0-6V) 监测等等

XPT2046 SOP 封装有 16 个引脚, 如下图所示:

其引脚说明如下图所示:

XPT2046 芯片详细的介绍可以参考“\6--芯片资料\开发板芯片数据手册”《XPT2046》

43.1.2 电容式触摸屏介绍

现在几乎所有智能手机, 包括平板电脑都是采用电容屏作为触摸屏, 电容屏是利用人体感应进行触点检测控制, 不需要直接接触或只需要轻微接触, 通过检测感应电流来定位触摸坐标。

我们有的 3.5/4.3/4.5/7 寸 TFTLCD 模块上使用的触摸屏是电容式触摸屏,下面简单介绍下电容式触摸屏的原理。

电容式触摸屏主要分为两种:

(1) 表面电容式电容触摸屏。

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

(2) 投射式电容触摸屏。

投射式电容触摸屏却具有多指触控的功能。 这两种电容式触摸屏都具有透光率高、 反应速度快、 寿命长等优点, 缺点是: 随着温度、 湿度的变化, 电容值会发生变化, 导致工作稳定性差, 时常会有漂移现象, 需要经常校对屏幕, 且不可佩戴普通手套进行触摸定位。

投射电容式触摸屏是传感器利用触摸屏电极发射出静电场线。 一般用于投射电容传感技术的电容类型有两种: 自我电容和交互电容。

自我电容又称绝对电容, 是最广为采用的一种方法, 自我电容通常是指扫描电极与地构成的电容。 在玻璃表面有用 ITO 制成的横向与纵向的扫描电极, 这些电极和地之间就构成一个电容的两极。 当用手或触摸笔触摸的时候就会并联一个电容到电路中去, 从而使在该条扫描线上的总体的电容量有所改变。 在扫描的时候, 控制 IC 依次扫描纵向和横向电极, 并根据扫描前后的电容变化来确定触摸点坐标位置。 笔记本电脑触摸输入板就是采用的这种方式, 笔记本电脑的输入板采用 X*Y 的传感电极阵列形成一个传感格子, 当手指靠近触摸输入板时, 在手指和传感电极之间产生一个小量电荷。 采用特定的运算法则处理来自行、 列传感器的信号来确定手指的位置。

交互电容又叫做跨越电容, 它是在玻璃表面的横向和纵向的 ITO 电极的交叉处形成电容。 交互电容的扫描方式就是扫描每个交叉处的电容变化, 来判定触摸点的位置。 当触摸的时候就会影响到相邻电极的耦合, 从而改变交叉处的电容量, 交互电容的扫面方法可以侦测到每个交叉点的电容值和触摸后电容变化, 因而它需要的扫描时间与自我电容的扫描方式相比要长一些, 需要扫描检测 X*Y根电极。 目前智能手机/平板电脑等的触摸屏, 都是采用交互电容技术。

我们使用的电容屏也是采用投射式电容屏(交互电容类型) , 所以后面仅以投射式电容屏作为介绍。

投射式电容触摸屏(交互电容类型) 内部由驱动电极与接收电极组成, 驱动电极发出低电压高频信号投射到接收电极形成稳定的电流, 当人体接触到电容屏时, 由于人体接地, 手指与电容屏就形成一个等效电容, 而高频信号可以通过这一等效电容流入地线, 这样, 接收端所接收的电荷量减小, 而当手指越靠近发射端时, 电荷减小越明显, 最后根据接收端所接收的电流强度来确定所触碰的点。

以上就是电容触摸屏的基本原理, 更多详细的资料大家可以百度了解下。

电容触摸屏通常也需要一个驱动 IC 来检测电容触摸, 且一般是通过 I2C接口输出触摸数据的。 我们 4.3 寸电容触摸屏使用的驱动 IC 是 FT5336, 不清楚自己彩屏驱动芯片及驱动触摸 IC 型号的可以看下彩屏背面的型号。 FT5336 支持最多 5 点触摸, 这里我们以 FT5536 为例进行介绍, 其他的驱动 IC 参考着学习。

FT5336 是台湾敦泰電子股份有限公司生产的一颗电容触摸屏驱动 IC, 最多支持 13*24(314) 个通道。 支持 SPI/IIC 接口, 我们使用的就是 IIC 接口。 IIC接口模式下, 该驱动 IC 与 STM32F1 的连接仅需要 4 根线: SDA、 SCL、 RST 和INT, SDA 和 SCL 是 IIC 通信用的, RST 是复位脚(低电平有效) , INT 是中断输出信号, 关于 IIC 我们就不详细介绍了, 在前面 I2C 实验时已经讲过。 关于 FT5336 或者其他电容触摸驱动 IC 详细内容, 大家可以在光盘“\6--芯片资料\彩屏数据手册” 内查看。 这里不多说, 我们主要还是看代码, 从代码中体会它们的使用。

43.2 硬件设计

本实验使用到硬件资源如下:

(1) DS0 指示灯

(2) KEY_UP 按键

(3) TFTLCD 模块(带电阻或电容式触摸屏)

(4) AT24C02

DS0 指示灯、 KEY_UP 按键、 TFTLCD 模块(彩屏显示部分) 、 AT24C02 电路在前面章节都介绍过, 这里就不多说, 下面我们看下 TFTLCD 模块上的触摸屏(电阻屏和电容屏) 与 STM32F1 的连接图, 如下图所示:

从电路图中可以看到, T_MOSI、 T_MISO、 T_SCK、 T_CS 和 T_PEN 分别连接在 STM32F1 的 PF9、 PB2、 PB1、 PF11 和 PF10 上。 而 FSMC 相关引脚之前我们介绍 TFTLCD 实验的时候就给大家介绍。 如果是电阻触摸屏的话, 它是直接连接在 XPT2046 触摸芯片 SPI 接口及笔中断引脚上的。 如果是电容触摸屏的话, 只需要 4 根线即可, 分别是 T_PEN(INT)、 T_CS(RST)、 T_CLK(SCL) 和 T_MOSI(SDA)。其中: INT、 RST、 SCL 和 SDA 分别是 FT5336 的中断输出信号、 复位信号, IIC 的SCL 和 SDA 信号。 这里我们用查询的方式读取 FT5336 的数据, 没有用到中断信号(INT) , 所以同 STM32F1 的连接, 只需要 3 根线即可, 不过我们还是预留INT 那根, 方面扩展的其他驱动 IC 做 IIC 地址设定, 所以保持 4 根线连接。

DS0 指示灯用来提示系统运行状态, KEY_UP 按键用来强制校准电阻触摸屏(电容触摸屏无需校准) , 如果出现触摸不准, 可以通过此按键强制校准。 AT24C02用来存储电阻触摸屏校准数据, TFTLCD 模块(带电阻或电容触摸屏) 用来显示触摸。

43.3 软件设计

本章所要实现的功能是: 通过 TFTLCD 模块上的触摸板(包括电阻触摸和电容触摸) 实现触摸功能, 最终实现一个画板的功能。 对于电阻触摸屏, 当出现触摸不准, 可使用 KEY_UP 键校准, 校准参数存储在 24C02 内。 程序框架如下:

(1) 初始化触摸屏

(2) 编写触摸(电阻/电容) 扫描函数

(3) 编写电阻触摸校准函数

(4) 编写主函数

电阻触摸屏采用的是 SPI 通信(使用 IO 口模拟 SPI 时序) , 电容触摸屏采用的是 IIC 通信, 这些通信方式在前面都介绍。 下面我们打开“\4--实验程序\1--基础实验\35-触摸屏实验” 工程, 在 APP 工程组中可以看到添加了 touch.c、ctiic.c、 gt5663.c 文件(里面包含了电阻屏/电容屏驱动程序) , 同时还要包含对应的头文件路径。

这里我们分析几个重要函数, 其他部分程序大家可以打开工程查看。

43.3.1 触摸屏初始化函数

要使用触摸屏, 我们必须先对它所使用的 IO 进行配置。 初始化代码如下:

//触摸屏初始化 //返回值:0,进行过校准 // 1,未进行校准 u8 TP_Init(void) { #if defined(TFTLCD_HX8357D)||defined(TFTLCD_ILI9341)||defined(TFTLCD_HX8352C)|| \ defined(TFTLCD_R61509V)||defined(TFTLCD_R61509VN)||defined(TFTLCD_R61509V3)|| \ defined(TFTLCD_ILI9486)||defined(TFTLCD_ST7793)||defined(TFTLCD_ILI9325)|| \ defined(TFTLCD_ILI9327)||defined(TFTLCD_ILI9481)||defined(TFTLCD_SSD1963)|| \ defined(TFTLCD_R61509VE)||defined(TFTLCD_SSD1963N)||defined(TFTLCD_ILI9488) GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOF, ENABLE); //使能PB,PF端口时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; // PB1端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure);//B1推挽输出 GPIO_SetBits(GPIOB,GPIO_Pin_1);//上拉 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; // PB2端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入 GPIO_Init(GPIOB, &GPIO_InitStructure);//B2上拉输入 GPIO_SetBits(GPIOB,GPIO_Pin_2);//上拉 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11|GPIO_Pin_9; // F9,PF11端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOF, &GPIO_InitStructure);//PF9,PF11推挽输出 GPIO_SetBits(GPIOF, GPIO_Pin_11|GPIO_Pin_9);//上拉 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // PF10端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入 GPIO_Init(GPIOF, &GPIO_InitStructure);//PF10上拉输入 GPIO_SetBits(GPIOF,GPIO_Pin_10);//上拉 TP_Read_XY(&tp_dev.x[0],&tp_dev.y[0]);//第一次读取初始化 AT24CXX_Init(); //初始化24CXX if(TP_Get_Adjdata())return 0;//已经校准 else //未校准? { LCD_Clear(WHITE);//清屏 TP_Adjust(); //屏幕校准 TP_Save_Adjdata(); } TP_Get_Adjdata(); #endif #if defined(TFTLCD_NT35510) GT5663_Init(); return 0; #endif #if defined(TFTLCD_ILI9806) CST716_Init(); return 0; #endif return 1; }

在 TP_Init()函数中, 首先通过 TFTLCD 的彩屏型号宏定义标识符来选择编译电阻触摸屏还是电容触摸屏驱动程序。

使能触摸屏 IO 口时钟, 其次配置对应 IO 口模式, 并初始化 GPIO。 因为在初始化触摸屏时, 我们需要判断是否经过校准, 校准的参数保存在 AT24C02 内,所以还需要初始化 24C02, 并调用函数 TP_Get_Adjdata 从 24CO2 的 213 地址内读取校准状态 tempfac。 如果 tempfac 不等于 0X0A, 执行校准函数 TP_Adjust,并保存校准数据到 24C02 中。

tp_dev 结构体用来保存校准因数和状态等, 其具体成员在 touch.h 文件内定义了。

如果是电容触摸屏, 其不需要校准, 直接调用 GT5663_Init 函数实现初始化配置。

43.3.2 触摸屏校准函数

初始化函数比较简单, 下面我们重点看下触摸屏的校准函数(触摸校准只针对电阻屏) 。 代码如下:

//触摸屏校准代码 //得到四个校准参数 void TP_Adjust(void) { #if defined(TFTLCD_HX8357D)||defined(TFTLCD_ILI9341)||defined(TFTLCD_HX8352C)|| \ defined(TFTLCD_R61509V)||defined(TFTLCD_R61509VN)||defined(TFTLCD_R61509V3)|| \ defined(TFTLCD_ILI9486)||defined(TFTLCD_ST7793)||defined(TFTLCD_ILI9325)|| \ defined(TFTLCD_ILI9327)||defined(TFTLCD_ILI9481)||defined(TFTLCD_SSD1963)|| \ defined(TFTLCD_R61509VE)||defined(TFTLCD_SSD1963N)||defined(TFTLCD_ILI9488) u16 pos_temp[4][2];//坐标缓存值 u8 cnt=0; u16 d1,d2; u32 tem1,tem2; double fac; u16 outtime=0; cnt=0; FRONT_COLOR=BLUE; BACK_COLOR =WHITE; LCD_Clear(WHITE);//清屏 FRONT_COLOR=RED;//红色 LCD_Clear(WHITE);//清屏 FRONT_COLOR=BLACK; LCD_ShowString(40,40,160,100,16,(u8*)TP_REMIND_MSG_TBL);//显示提示信息 TP_Drow_Touch_Point(20,20,RED);//画点1 tp_dev.sta=0;//消除触发信号 tp_dev.xfac=0;//xfac用来标记是否校准过,所以校准之前必须清掉!以免错误 while(1)//如果连续10秒钟没有按下,则自动退出 { tp_dev.scan(1);//扫描物理坐标 if((tp_dev.sta&0xc0)==TP_CATH_PRES)//按键按下了一次(此时按键松开了.) { outtime=0; tp_dev.sta&=~(1<<6);//标记按键已经被处理过了. pos_temp[cnt][0]=tp_dev.x[0]; pos_temp[cnt][1]=tp_dev.y[0]; cnt++; switch(cnt) { case 1: TP_Drow_Touch_Point(20,20,WHITE); //清除点1 TP_Drow_Touch_Point(tftlcd_data.width-20,20,RED); //画点2 break; case 2: TP_Drow_Touch_Point(tftlcd_data.width-20,20,WHITE); //清除点2 TP_Drow_Touch_Point(20,tftlcd_data.height-20,RED); //画点3 break; case 3: TP_Drow_Touch_Point(20,tftlcd_data.height-20,WHITE); //清除点3 TP_Drow_Touch_Point(tftlcd_data.width-20,tftlcd_data.height-20,RED); //画点4 break; case 4: //全部四个点已经得到 //对边相等 tem1=abs(pos_temp[0][0]-pos_temp[1][0]);//x1-x2 tem2=abs(pos_temp[0][1]-pos_temp[1][1]);//y1-y2 tem1*=tem1; tem2*=tem2; d1=sqrt(tem1+tem2);//得到1,2的距离 tem1=abs(pos_temp[2][0]-pos_temp[3][0]);//x3-x4 tem2=abs(pos_temp[2][1]-pos_temp[3][1]);//y3-y4 tem1*=tem1; tem2*=tem2; d2=sqrt(tem1+tem2);//得到3,4的距离 fac=(float)d1/d2; if(fac<0.95||fac>1.05||d1==0||d2==0)//不合格 { cnt=0; TP_Drow_Touch_Point(tftlcd_data.width-20,tftlcd_data.height-20,WHITE); //清除点4 TP_Drow_Touch_Point(20,20,RED); //画点1 TP_Adj_Info_Show(pos_temp[0][0],pos_temp[0][1],pos_temp[1][0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3][0],pos_temp[3][1],fac*100);//显示数据 continue; } tem1=abs(pos_temp[0][0]-pos_temp[2][0]);//x1-x3 tem2=abs(pos_temp[0][1]-pos_temp[2][1]);//y1-y3 tem1*=tem1; tem2*=tem2; d1=sqrt(tem1+tem2);//得到1,3的距离 tem1=abs(pos_temp[1][0]-pos_temp[3][0]);//x2-x4 tem2=abs(pos_temp[1][1]-pos_temp[3][1]);//y2-y4 tem1*=tem1; tem2*=tem2; d2=sqrt(tem1+tem2);//得到2,4的距离 fac=(float)d1/d2; if(fac<0.95||fac>1.05)//不合格 { cnt=0; TP_Drow_Touch_Point(tftlcd_data.width-20,tftlcd_data.height-20,WHITE); //清除点4 TP_Drow_Touch_Point(20,20,RED); //画点1 TP_Adj_Info_Show(pos_temp[0][0],pos_temp[0][1],pos_temp[1][0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3][0],pos_temp[3][1],fac*100);//显示数据 continue; }//正确了 //对角线相等 tem1=abs(pos_temp[1][0]-pos_temp[2][0]);//x1-x3 tem2=abs(pos_temp[1][1]-pos_temp[2][1]);//y1-y3 tem1*=tem1; tem2*=tem2; d1=sqrt(tem1+tem2);//得到1,4的距离 tem1=abs(pos_temp[0][0]-pos_temp[3][0]);//x2-x4 tem2=abs(pos_temp[0][1]-pos_temp[3][1]);//y2-y4 tem1*=tem1; tem2*=tem2; d2=sqrt(tem1+tem2);//得到2,3的距离 fac=(float)d1/d2; if(fac<0.95||fac>1.05)//不合格 { cnt=0; TP_Drow_Touch_Point(tftlcd_data.width-20,tftlcd_data.height-20,WHITE); //清除点4 TP_Drow_Touch_Point(20,20,RED); //画点1 TP_Adj_Info_Show(pos_temp[0][0],pos_temp[0][1],pos_temp[1][0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3][0],pos_temp[3][1],fac*100);//显示数据 continue; }//正确了 //计算结果 tp_dev.xfac=(float)(tftlcd_data.width-40)/(pos_temp[1][0]-pos_temp[0][0]);//得到xfac tp_dev.xoff=(tftlcd_data.width-tp_dev.xfac*(pos_temp[1][0]+pos_temp[0][0]))/2;//得到xoff tp_dev.yfac=(float)(tftlcd_data.height-40)/(pos_temp[2][1]-pos_temp[0][1]);//得到yfac tp_dev.yoff=(tftlcd_data.height-tp_dev.yfac*(pos_temp[2][1]+pos_temp[0][1]))/2;//得到yoff if(abs(tp_dev.xfac)>2||abs(tp_dev.yfac)>2)//触屏和预设的相反了. { cnt=0; TP_Drow_Touch_Point(tftlcd_data.width-20,tftlcd_data.height-20,WHITE); //清除点4 TP_Drow_Touch_Point(20,20,RED); //画点1 LCD_ShowString(40,26,tftlcd_data.width,tftlcd_data.height,16,"TP Need readjust!"); tp_dev.touchtype=!tp_dev.touchtype;//修改触屏类型. if(tp_dev.touchtype)//X,Y方向与屏幕相反 { CMD_RDX=0X90; CMD_RDY=0XD0; }else //X,Y方向与屏幕相同 { CMD_RDX=0XD0; CMD_RDY=0X90; } continue; } FRONT_COLOR=BLUE; LCD_Clear(WHITE);//清屏 LCD_ShowString(35,110,tftlcd_data.width,tftlcd_data.height,16,"Touch Screen Adjust OK!");//校正完成 delay_ms(1000); TP_Save_Adjdata(); LCD_Clear(WHITE);//清屏 return;//校正完成 } } delay_ms(10); outtime++; if(outtime>1000) { TP_Get_Adjdata(); break; } } #endif #if defined(TFTLCD_NT35510)||defined(TFTLCD_ILI9806) return; //电容触摸屏不需要校准 #endif }

TP_Adjust 函数是触摸实验最核心的代码, 在这里给大家介绍一下我们这里所使用的触摸屏校正原理: 我们传统的鼠标是一种相对定位系统, 只和前一次鼠标的位置坐标有关。 而触摸屏则是一种绝对坐标系统, 要选哪就直接点哪, 与相对定位系统有着本质的区别。 绝对坐标系统的特点是每一次定位坐标与上一次定位坐标没有关系, 每次触摸的数据通过校准转为屏幕上的坐标, 不管在什么情况下, 触摸屏这套坐标在同一点的输出数据是稳定的。 不过由于技术原理的原因,并不能保证同一点触摸每一次采样数据相同, 不能保证绝对坐标定位, 点不准,这就是触摸屏最怕出现的问题: 漂移。 对于性能质量好的触摸屏来说, 漂移的情况出现并不是很严重。 所以很多应用触摸屏的系统启动后, 进入应用程序前, 先要执行校准程序。 通常应用程序中使用的 LCD 坐标是以像素为单位的。 比如说:左上角的坐标是一组非 0 的数值, 比如(20, 20) , 而右下角的坐标为(220, 300) 。 这些点的坐标都是以像素为单位的, 而从触摸屏中读出的是点的物理坐标, 其坐标轴的方向、 XY 值的比例因子、 偏移量都与 LCD 坐标不同, 所以,需要在程序中把物理坐标首先转换为像素坐标, 然后再赋给 POS 结构, 达到坐标转换的目的。

校正思路: 在了解了校正原理之后, 我们可以得出下面的一个从物理坐标到像素坐标的转换关系式:

LCDx=xfac*Px+xoff;

LCDy=yfac*Py+yoff;

其中(LCDx,LCDy)是在 LCD 上的像素坐标, (Px,Py) 是从触摸屏读到的物理坐标。 xfac, yfac 分别是 X 轴方向和 Y 轴方向的比例因子, 而 xoff 和 yoff则是这两个方向的偏移量。

这样我们只要事先在屏幕上面显示 4 个点(这四个点的坐标是已知的) ,分别按这四个点就可以从触摸屏读到 4 个物理坐标, 这样就可以通过待定系数法求出 xfac、 yfac、 xoff、 yoff 这四个参数。 我们保存好这四个参数, 在以后的使用中, 我们把所有得到的物理坐标都按照这个关系式来计算, 得到的就是准确的屏幕坐标, 达到了触摸屏校准的目的。

该函数内调用了一个比较关键的函数 TP_Scan, 用来读取触摸屏 X\Y 轴的物理坐标, 因为只有获取物理坐标后我们才能转换对应 LCD 的坐标, 这个在前面已介绍。 TP_Scan 函数代码如下:

////////////////////////////////////////////////////////////////////////////////// //触摸按键扫描 //tp:0,屏幕坐标;1,物理坐标(校准等特殊场合用) //返回值:当前触屏状态. //0,触屏无触摸;1,触屏有触摸 u8 TP_Scan(u8 tp) { #if defined(TFTLCD_HX8357D)||defined(TFTLCD_ILI9341)||defined(TFTLCD_HX8352C)|| \ defined(TFTLCD_R61509V)||defined(TFTLCD_R61509VN)||defined(TFTLCD_R61509V3)|| \ defined(TFTLCD_ILI9486)||defined(TFTLCD_ST7793)||defined(TFTLCD_ILI9325)|| \ defined(TFTLCD_ILI9327)||defined(TFTLCD_ILI9481)||defined(TFTLCD_SSD1963)|| \ defined(TFTLCD_R61509VE)||defined(TFTLCD_SSD1963N)||defined(TFTLCD_ILI9488) if(PEN==0)//有按键按下 { if(tp)TP_Read_XY2(&tp_dev.x[0],&tp_dev.y[0]);//读取物理坐标 else if(TP_Read_XY2(&tp_dev.x[0],&tp_dev.y[0]))//读取屏幕坐标 { tp_dev.x[0]=tp_dev.xfac*tp_dev.x[0]+tp_dev.xoff;//将结果转换为屏幕坐标 tp_dev.y[0]=tp_dev.yfac*tp_dev.y[0]+tp_dev.yoff; } if((tp_dev.sta&TP_PRES_DOWN)==0)//之前没有被按下 { tp_dev.sta=TP_PRES_DOWN|TP_CATH_PRES;//按键按下 tp_dev.x[4]=tp_dev.x[0];//记录第一次按下时的坐标 tp_dev.y[4]=tp_dev.y[0]; } }else { if(tp_dev.sta&TP_PRES_DOWN)//之前是被按下的 { tp_dev.sta&=~(1<<7);//标记按键松开 }else//之前就没有被按下 { tp_dev.x[4]=0; tp_dev.y[4]=0; tp_dev.x[0]=0xffff; tp_dev.y[0]=0xffff; } } return tp_dev.sta&TP_PRES_DOWN;//返回当前的触屏状态 #endif #if defined(TFTLCD_NT35510) return (!GT5663_Scan(0)); #endif #if defined(TFTLCD_ILI9806) return (CST716_Scan(0)); #endif }

函数内主要通过 TP_Read_XY2 函数获取物理坐标, 该函数代码如下:

//读取x,y坐标 //最小值不能少于100. //x,y:读取到的坐标值 //返回值:0,失败;1,成功。 u8 TP_Read_XY(u16 *x,u16 *y) { u16 xtemp,ytemp; xtemp=TP_Read_XOY(CMD_RDX); ytemp=TP_Read_XOY(CMD_RDY); //if(xtemp<100||ytemp<100)return 0;//读数失败 *x=xtemp; *y=ytemp; return 1;//读数成功 } //连续2次读取触摸屏IC,且这两次的偏差不能超过 //ERR_RANGE,满足条件,则认为读数正确,否则读数错误. //该函数能大大提高准确度 //x,y:读取到的坐标值 //返回值:0,失败;1,成功。 #define ERR_RANGE 50 //误差范围 u8 TP_Read_XY2(u16 *x,u16 *y) { u16 x1,y1; u16 x2,y2; u8 flag; flag=TP_Read_XY(&x1,&y1); if(flag==0)return(0); flag=TP_Read_XY(&x2,&y2); if(flag==0)return(0); if(((x2<=x1&&x1<x2+ERR_RANGE)||(x1<=x2&&x2<x1+ERR_RANGE))//前后两次采样在+-50内 &&((y2<=y1&&y1<y2+ERR_RANGE)||(y1<=y2&&y2<y1+ERR_RANGE))) { *x=(x1+x2)/2; *y=(y1+y2)/2; return 1; }else return 0; }

该函数首先通过 TP_Read_XOY 函数获取触摸屏物理坐标值, 然后通过相应的程序滤波, 保证数据的准确性, 防止出现飞点等误差。 比较常用的程序滤波的方法其实就是多次数据的读取, 然后把最大最小值除去, 算出平均值。 这种方法读取的次数越多, 得到的数据就越准确。 不过为了更好的滤波, 还使用了另外一种方式进行滤波, 也就是当读取到两次数据之后, 然后检查两个数据之间的差值,如果超过理想的误差, 那么丢弃数据。 这种方法也是处理飞点现象的常用方法。

该函数里面用到了很多的宏, 比如 CMD_RDX、 CMD_RDY 等, 这些都在 touch.c文件内定义了。 CMD_RDX=0xD0 和 CMD_RDY=0X90 是 XPT2046 AD 芯片读取 X 和 Y轴的命令, 可通过《XPT2046》 芯片数据手册查找到, 如下图:

这里要特别说明下, 要读取 XPT2046 的数据, 根据其时序图可知, 时序图如下:

XPT2046 完成一个完整的转换需要 24 个串行时钟, 也就是需要 3 个字节的 SPI 时钟。 对照上图, XPT2046 前 8 个串行时钟, 是接收 1 个字节的转换命令, 接收到转换命令了之后, 然后使用 1 个串行时钟的时间来完成数据转换(当然在编写程序的时候, 为了得到精确的数据, 你可以适当的延时一下) ,然后返回 12 个字节长度(12 个字节长度也计时 12 个串行时钟) 的转换结果。然后最后 4 个串行时钟返回4个无效数据。这一过程在TP_Read_AD函数内实现。代码如下:

//SPI读数据 //从触摸屏IC读取adc值 //CMD:指令 //返回值:读到的数据 u16 TP_Read_AD(u8 CMD) { u8 count=0; u16 Num=0; TCLK=0; //先拉低时钟 TDIN=0; //拉低数据线 TCS=0; //选中触摸屏IC TP_Write_Byte(CMD);//发送命令字 delay_us(6);//ADS7846的转换时间最长为6us TCLK=0; delay_us(1); TCLK=1; //给1个时钟,清除BUSY delay_us(1); TCLK=0; for(count=0;count<16;count++)//读出16位数据,只有高12位有效 { Num<<=1; TCLK=0; //下降沿有效 delay_us(1); TCLK=1; if(DOUT)Num++; } Num>>=4; //只有高12位有效. TCS=1; //释放片选 return(Num); }

43.3.3 触摸屏扫描函数

要检测是否有触摸, 我们可以编写一个触摸扫描函数, 并在该函数中获取触摸屏的物理坐标和 LCD 坐标。 具体代码如下:

////////////////////////////////////////////////////////////////////////////////// //触摸按键扫描 //tp:0,屏幕坐标;1,物理坐标(校准等特殊场合用) //返回值:当前触屏状态. //0,触屏无触摸;1,触屏有触摸 u8 TP_Scan(u8 tp) { #if defined(TFTLCD_HX8357D)||defined(TFTLCD_ILI9341)||defined(TFTLCD_HX8352C)|| \ defined(TFTLCD_R61509V)||defined(TFTLCD_R61509VN)||defined(TFTLCD_R61509V3)|| \ defined(TFTLCD_ILI9486)||defined(TFTLCD_ST7793)||defined(TFTLCD_ILI9325)|| \ defined(TFTLCD_ILI9327)||defined(TFTLCD_ILI9481)||defined(TFTLCD_SSD1963)|| \ defined(TFTLCD_R61509VE)||defined(TFTLCD_SSD1963N)||defined(TFTLCD_ILI9488) if(PEN==0)//有按键按下 { if(tp)TP_Read_XY2(&tp_dev.x[0],&tp_dev.y[0]);//读取物理坐标 else if(TP_Read_XY2(&tp_dev.x[0],&tp_dev.y[0]))//读取屏幕坐标 { tp_dev.x[0]=tp_dev.xfac*tp_dev.x[0]+tp_dev.xoff;//将结果转换为屏幕坐标 tp_dev.y[0]=tp_dev.yfac*tp_dev.y[0]+tp_dev.yoff; } if((tp_dev.sta&TP_PRES_DOWN)==0)//之前没有被按下 { tp_dev.sta=TP_PRES_DOWN|TP_CATH_PRES;//按键按下 tp_dev.x[4]=tp_dev.x[0];//记录第一次按下时的坐标 tp_dev.y[4]=tp_dev.y[0]; } }else { if(tp_dev.sta&TP_PRES_DOWN)//之前是被按下的 { tp_dev.sta&=~(1<<7);//标记按键松开 }else//之前就没有被按下 { tp_dev.x[4]=0; tp_dev.y[4]=0; tp_dev.x[0]=0xffff; tp_dev.y[0]=0xffff; } } return tp_dev.sta&TP_PRES_DOWN;//返回当前的触屏状态 #endif #if defined(TFTLCD_NT35510) return (!GT5663_Scan(0)); #endif #if defined(TFTLCD_ILI9806) return (CST716_Scan(0)); #endif }

该函数功能比较简单, 函数有一个入口参数, 用于选择是获取物理坐标还是LCD 实际坐标; 函数有一个返回值, 用于判定是否有触摸, 如果为 1 表示有触摸按下, 否则无触摸。 里面调用的函数 TP_Read_XY2 在前面都已介绍, 将获取到的物理坐标存储在结构体 tp_dev.x/tp_dev.y 中, 然后根据 LCD 实际坐标转换公式求取 LCD 实际坐标。 涉及到的结构体在 touch.h 文件中已经定义为全局变量。

在该函数中, 根据 LCD 不同型号来判断是电阻触摸还是电容触摸, 如果是电容触摸屏调用了 GT5663_Scan 函数来扫描电容触摸, 该函数代码如下:

const u16 GT5663_TPX_TBL[5]={GT_TP1_REG,GT_TP2_REG,GT_TP3_REG,GT_TP4_REG,GT_TP5_REG}; //扫描触摸屏(采用查询方式) //mode:0,正常扫描. //返回值:当前触屏状态. //1,触屏无触摸;0,触屏有触摸 u8 GT5663_Scan(u8 mode) { u8 buf[4]; u8 i=0; u8 res=0; u8 temp; u8 tempsta; static u8 t=0;//控制查询间隔,从而降低CPU占用率 t++; if((t%10)==0||t<10)//空闲时,每进入10次CTP_Scan函数才检测1次,从而节省CPU使用率 { GT5663_RD_Reg(GT_GSTID_REG,&mode,1); //读取触摸点的状态 // printf("mode=%d\r\n",mode); if(mode&0X80&&((mode&0XF)<6)) { temp=0; GT5663_WR_Reg(GT_GSTID_REG,&temp,1);//清标志 } if(mode&0XF&&((mode&0XF)<6)) { temp=0XFF<<(mode&0XF); //将点的个数转换为1的位数,匹配tp_dev.sta定义 tempsta=tp_dev.sta; //保存当前的tp_dev.sta值 tp_dev.sta=(~temp)|TP_PRES_DOWN|TP_CATH_PRES; tp_dev.x[4]=tp_dev.x[0]; //保存触点0的数据 tp_dev.y[4]=tp_dev.y[0]; for(i=0;i<5;i++) { if(tp_dev.sta&(1<<i)) //触摸有效? { GT5663_RD_Reg(GT5663_TPX_TBL[i],buf,4); //读取XY坐标值 if(tftlcd_data.dir==1)//横屏 { tp_dev.y[i]=((u16)buf[1]<<8)+buf[0]; tp_dev.x[i]=800-(((u16)buf[3]<<8)+buf[2]); }else { tp_dev.x[i]=((u16)buf[1]<<8)+buf[0]; tp_dev.y[i]=((u16)buf[3]<<8)+buf[2]; } // printf("xx[%d]:%d,yy[%d]:%d\r\n",i,tp_dev.x[i],i,tp_dev.y[i]); } } res=1; if(tp_dev.x[0]>tftlcd_data.width||tp_dev.y[0]>tftlcd_data.height)//非法数据(坐标超出了) { if((mode&0XF)>1) //有其他点有数据,则复第二个触点的数据到第一个触点. { tp_dev.x[0]=tp_dev.x[1]; tp_dev.y[0]=tp_dev.y[1]; t=0; //触发一次,则会最少连续监测10次,从而提高命中率 }else //非法数据,则忽略此次数据(还原原来的) { tp_dev.x[0]=tp_dev.x[4]; tp_dev.y[0]=tp_dev.y[4]; mode=0X80; tp_dev.sta=tempsta; //恢复tp_dev.sta } }else t=0; //触发一次,则会最少连续监测10次,从而提高命中率 } } if((mode&0X8F)==0X80)//无触摸点按下 { if(tp_dev.sta&TP_PRES_DOWN) //之前是被按下的 { tp_dev.sta&=~TP_PRES_DOWN; //标记按键松开 }else //之前就没有被按下 { tp_dev.x[0]=0xffff; tp_dev.y[0]=0xffff; tp_dev.sta&=0XE0; //清除点有效标记 } } if(t>240)t=10;//重新从10开始计数 return res; }

该函数用于扫描电容触摸屏是否有按键下, 由我们是采用查询的方式读取数据, 所以这里使了一个静态变量(static)来提高效率当无触摸时候尽减少对 CPU的占用, 当有触摸时候又保证能迅速检测到。 读取数据时, 先读取状态寄存器(GT_GSTID_REG) 的值, 从而判断触摸点的个数(最多 10 个) , 然后依次读取各触摸点的坐标数据, 在读取到数据后, 还需要根据屏幕的分辨率和横竖屏状态进行坐标变换。 另外, 在遇到非法数据的时候, 需要对非法数据进行处理, 以免干扰程序的正常运行。 对于函数内所调用的其他功能函数大家可以参考源码, 这里就不列出来。

43.3.4 主函数

编写好触摸屏初始化、 触摸校准和扫描函数后, 接下来就可以编写主函数了,代码如下:

#include "system.h" #include "SysTick.h" #include "led.h" #include "usart.h" #include "tftlcd.h" #include "key.h" #include "touch.h" void kai_display() //开机显示 { FRONT_COLOR=BLACK; LCD_ShowString(10,10,tftlcd_data.width,tftlcd_data.height,16,"Touch Test"); LCD_ShowString(10,30,tftlcd_data.width,tftlcd_data.height,16,"www.prechin.net"); LCD_ShowString(10,50,tftlcd_data.width,tftlcd_data.height,16,"K_UP:Adjust"); } void display_init() //初始化显示 { FRONT_COLOR=RED; LCD_ShowString(tftlcd_data.width-8*4,0,tftlcd_data.width,tftlcd_data.height,16,"RST"); LCD_Fill(120, tftlcd_data.height - 16, 139, tftlcd_data.height, BLUE); LCD_Fill(140, tftlcd_data.height - 16, 159, tftlcd_data.height, RED); LCD_Fill(160, tftlcd_data.height - 16, 179, tftlcd_data.height, MAGENTA); LCD_Fill(180, tftlcd_data.height - 16, 199, tftlcd_data.height, GREEN); LCD_Fill(200, tftlcd_data.height - 16, 219, tftlcd_data.height, CYAN); LCD_Fill(220, tftlcd_data.height - 16, 239, tftlcd_data.height, YELLOW); } int main() { u8 i=0; u8 key; u16 penColor = BLUE; SysTick_Init(72); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2组 LED_Init(); USART1_Init(115200); TFTLCD_Init(); //LCD初始化 KEY_Init(); TP_Init(); kai_display(); delay_ms(2000); LCD_Clear(WHITE); display_init(); while(1) { key=KEY_Scan(0); if(key==KEY_UP_PRESS) { TP_Adjust(); //校正 display_init(); } if(TP_Scan(0)) { /* 选择画笔的颜色 */ if(tp_dev.y[0] > tftlcd_data.height - 18&&tp_dev.y[0]<tftlcd_data.height) { if(tp_dev.x[0]>220) { penColor = YELLOW; } else if(tp_dev.x[0]>200) { penColor = CYAN; } else if(tp_dev.x[0]>180) { penColor = GREEN; } else if(tp_dev.x[0]>160) { penColor = MAGENTA; } else if(tp_dev.x[0]>140) { penColor = RED; } else if(tp_dev.x[0]>120) { penColor = BLUE; } } else //画点 { LCD_Fill(tp_dev.x[0]-1, tp_dev.y[0]-1, tp_dev.x[0]+2, tp_dev.y[0]+2, penColor); } /* 清屏 */ if ((tp_dev.x[0] > tftlcd_data.width-8*4) && (tp_dev.y[0] < 16)) { LCD_Fill(0, 0, tftlcd_data.width-1,tftlcd_data.height-16-1, BACK_COLOR); LCD_ShowString(tftlcd_data.width-8*4,0,tftlcd_data.width,tftlcd_data.height,16,"RST"); } } i++; if(i%20==0) { LED1=!LED1; } // delay_ms(10); } }

主函数实现的功能很简单, 首先调用之前编写好的硬件初始化函数, 包括SysTick 系统时钟, LED 初始化等。 然后调用我们前面编写的 TP_Init 触摸屏初始化函数, 如果未校准的首先会进行校准, 接着进入 while 循环, 调用 KEY_Scan函数, 不断检测 KEY_UP 键是否按下, 如果按下进行触摸屏校准(电阻屏校准,电容屏无需校准) , 然后调用 TP_Scan 触摸扫描函数判断是否有触摸, 并获取触摸屏 LCD 坐标, 同时触摸后描出对应的点, 最后控制 DS0 指示灯间隔 200ms 闪烁,提示系统正常运行。

43.4 实验现象

将工程程序编译后下载到开发板内, 可以看到 DS0 指示灯不断闪烁, 表示程序正常运行。 触摸屏显示界面如下图所示:

我们可以在电阻屏上画一些内容, 屏幕右上角的 RST 可以用来清屏, 只要触摸下 RST 这个区域即可将屏幕上所画的内容擦除掉。 如果出现触摸不准, 可以按下 KEY_UP 键, 进入触摸校准界面, 按照提示校准下即可。

课后作业

(1) 在触摸屏上设计 2 个触摸按钮, 控制开发板上蜂鸣器的开关。 (温馨提示: 使用 LCD API 函数画 2 个按键, 通过判断这两个按键的位置坐标实现控制)

http://www.jsqmd.com/news/952297/

相关文章:

  • 不只是备份!深度挖掘华为HiSuite备份文件,教你找回已删除的微信聊天记录
  • Android17新规:内存超限直接杀App,没有崩溃日志怎么排查?
  • structlog:Python 结构化日志的标准答案
  • MIT 6.1810: xv6 book Chapter5: Page faults 笔记
  • 告别LabelImg!用ArcGIS Pro给遥感影像打标签,效率提升不止5倍
  • 2026年食堂承包性价比排名,靠谱的食堂承包公司推荐 - mypinpai
  • 别再用API硬连AI工具了!信贷中台智能编排引擎(IPA)上线72小时内完成OCR/NLP/评分卡全链路自治闭环
  • 告别‘炼丹’:用计算图可视化理解逻辑回归的梯度下降
  • 从Redis缓存到RPC调用:深入理解Java序列化在分布式系统里的核心作用
  • 为什么92%的AI转正试点失败?3个被低估的技术断点,及HR与IT联合攻坚SOP
  • 2026 年跨境行业全新变局,亚马逊、tiktok、Shopee、速卖通迎来合规整改。 - Zhou6
  • 期货实盘委托成交持仓对不上:天勤排查顺序与字段对照
  • AI辅助开发新思路:让快马平台生成你想象不到的sweezy cursors炫酷效果
  • 从BP生成到招股书定稿,AI如何压缩IPO周期68%?一线保荐人亲授5个不可逆的提效节点
  • 告别按键!用STM32F4和PAJ7620手势传感器做个隔空切歌播放器(附完整代码)
  • 别再只用KL散度了!用Wasserstein距离(推土机距离)解决GAN训练中的梯度消失问题
  • MATLAB环境下IF脉冲神经元动态仿真包:含可运行代码、脉冲检测模块与实操录像
  • 从电枢电压到转子转角:手把手拆解直流电机数学模型,附Simulink仿真验证
  • 广州黄金回收哪家靠谱推荐,24小时营业的推荐,上门变现速度快的推荐 - 花生花生1
  • 告别PHP 5!CentOS 7下用Remi仓库一键升级PHP 8.2(附Apache/Nginx重启命令)
  • 保姆级教程:用Hugging Face Transformers库快速上手TabTransformer(PyTorch版)
  • 2026世界杯最核心变化晋级规则与淘汰赛结构彻底调整冷门概率大增
  • 从收音机到手机:高频小信号放大器设计避坑指南(基于Multisim仿真分析)
  • 002、Zephyr RTOS核心特性与优势
  • 广州哪家回收黄金严格按照上海黄金交易所金价结算?金小福黄金回收 - 花生花生1
  • 欧盟Chat Control提案与社交机器人隐私风险分析
  • 别再暴力穷举了!用Python+PuLP库5分钟搞定整数规划(附投资组合实战代码)
  • 别再只用PCA了!粗糙集在风控模型特征工程中的实战应用与避坑指南
  • 除了SCI和EI,搞计算机的你还得知道IEEE Xplore和ACM DL怎么用:四大文献库实战检索与论文追踪教程
  • 影刀RPA店群自动化运维实战:Python协同异常聚类与根因定位系统设计