目录
1. 触摸屏原理
2. 电阻式触摸屏检测原理
3. 电容式触摸屏检测原理
4. 硬件配置
4.1 XPT2046驱动芯片
4.2 硬件设计
5. 代码详解
5.1 main.c
5.2 AT24C02.c
5.3 AT24C02.h
5.4 C_Touch_I2C.c
5.5 C_Touch_I2C.h
5.6 Touch.c
5.7 Touch.h
5.8 FT5206.c
5.9 FT5206.h
5.10 GT9147.c
5.11 GT9147.h
5.12 OTT2001A.c
5.13 OTT2001A.h
1. 触摸屏原理
触摸屏又称为触控面板。它是一种把触摸位置转化成坐标数据的输入设备,根据触摸屏的检测原理,主要可以分为电阻式触摸屏和电容式触摸屏。
相对而言,电阻式触摸屏造价便宜,能够适应较恶劣的环境,但只是支持单点触控(一次只能检测面板上一个触控的位置),触摸时需要一定的压力(通常指甲触摸的屏幕,需要给一定的压力),使用久了容易造成表面磨损,影响寿命。
而电容触摸屏支持多点触控,检测精度高,电容屏通过和导电物体产生的电容效应来检测触摸动作(电容屏可以用手指触摸,因为手指是导电的),只能感应导电物体的触摸,湿度较大或屏幕表面有水珠时会影响电容屏的检测效果。
电阻触摸屏和电容触摸屏两种屏幕,从外观上并没有明显的区别,区分电阻屏和电容屏最直接的方法就是使用绝缘物体点击屏幕。因为电阻式触摸屏是根据压力来检测触摸动作的,绝缘物体点击屏幕会产生压力;而电容式触摸屏是根据触摸物体是否导电来感应物体触摸的,所以绝缘物体触摸屏幕是不会由触摸动作的。
目前的电容式触摸屏被大部分应用在智能手机、平板电脑等电子设备中,而电阻式触摸屏在汽车导航、工控机等设备中仍占主流。
2. 电阻式触摸屏检测原理
电阻式触摸屏的结构如下图所示:
电阻式触摸屏主要由表面硬涂层、两个ITO层、间隔点以及玻璃底层构成,这些结构层都是透明的,整个触摸屏覆盖在液晶面板上,通过触摸屏可以看到液晶面板。首先,表面硬涂层的作用是:保护最外层的触摸屏;最深层的玻璃底层起到承载的作用;两个ITO层是触摸屏的关键结构----是涂有铟锡金属氧化物的导电层。两个ITO层之间通过间隔点使得两层分开,保持一定的间隔。当触摸屏表面受到压力时,表面弯曲使得上层ITO与下层ITO接触,在触点处接通电路。
注:以上的结构层都是透明的,这些结构只是为了保证触摸作用,在这些结构层下是液晶板,透过这些结构层是可以看到液晶板的,液晶屏上显示的汉字,图片等是在液晶板上显示的。
ITO涂层检测原理:
通过观察我们手中的开发板可以发现:LCD触摸屏都是通过四根线引出的触摸屏。仔细观察开发板可以发现,这四根线其实就是两个ITO涂层的两端分别引出的四个电极。
简单理解就是:可以将每个ITO层看做一个矩形,两个ITO层就是两个矩形,每个矩形都有2个长,2个宽。一个矩阵的两个长连接一个电极,两个宽连接一个电极。这样一来,两个ITO层就会引出四个电极。
两个ITO层的两端分别引出X-、X+、Y-、Y+四个电极,这是电阻屏最常见的四线结构,通过这些电极,外部电路向这两个涂层可以施加匀强电场或检测电压。
在上图的基础之上,如果我让X-接GND,X+接3.3V,那么在X方向上就会产生匀强电场;同理,如果我给Y-接GND,Y+接3.3V,那么在Y方向上就会产生匀强电场。明白了这一原理,我们接着往下看:
根据以上的原理,可以计算出触点处的X、Y坐标值。
触点处X、Y的坐标值:
计算X坐标时,在X+电极施加驱动电压,X-极接地,所以在X+与X-处形成了匀强电场,此时触点处的电压可以通过Y+电极采集得到,由于ITO层均匀导电,那么触点电压与之比就等于x坐标比上屏宽度。
/=X / Width X= * Width
同理,计算Y坐标时,在Y+电极施加驱动电压,Y-接地,所以会在Y+与Y-之间形成匀强电场,此时触点处的电压值可以通过X+电极采集得到,由于ITO层均匀导电,那么触电电压与之比就等于y坐标与屏长度之比。
Y= * Height
由此就可以得到触点处的坐标值(X,Y)。
电阻式触摸屏的原理就是如此:通过ADC不断的在X+、Y+上采集电压值,通过坐标与屏尺寸的比例,计算出X、Y坐标值。进而实现触摸屏触摸。
但是这样不断地进行采集、计算,未免费时费力。
为了方便检测触摸的坐标,为此, 一些芯片厂商制作了电阻屏专用的控制芯片,控制上述采集过程、采集电压、外部微控制器直接与触摸控制芯片通讯直接获得触点的电压或坐标。
其中XPT2046芯片就是一种典型的触摸控制芯片,控制4线电阻触摸屏,该芯片通过与STM32进行通讯,获得采集的电压值,然后转换成坐标。
STM32F4的2.8寸和3.5寸的LCD屏是电阻式触摸屏,电阻式触摸屏的驱动芯片是ADS7843、ADS7846、TSC2046、XPT2046 和 AK4182 等:
u8 CMD_RDX=0XD0; //读取X轴坐标命令
u8 CMD_RDY=0X90; //读取Y轴坐标命令
3. 电容式触摸屏检测原理
与电阻式触摸屏不同,电容式触摸屏不需要根据压力使接触点变形,再通过触点处电压值来检验坐标。
电容式触摸屏的基本原理和电容按键类似,都是利用电容的充放电原理,利用充电时间检测电容大小,从而通过检测出电容值的变化来获知触摸信号。
电容屏的最上层是玻璃,所以不会向电阻屏那样通过按压就产生明显的形变。核心层部分也是由ITO材料构成的,这些导电材料在屏幕里构成了人眼看不见的静态网,静态网由多行X轴电极和多列Y轴电极构成。两个电极之间会形成电容。
当触摸屏工作时,X轴电极会发出AC交流信号,而交流信号是能够穿过电容的,也可以说通过Y轴是可以感应出该信号的。当交流电穿越时电容会有充放电过程,检测该充电时间是可以获知电容量的。当手指触摸屏幕时,会影响接触点两端的电极发生耦合,明显改变两个电极之间的电容量,倘若检测到某电容的电容量发生了改变,即可获知该电容处有触摸动作。(所以可以称为电容式触摸屏,并且使用绝缘体触碰时不会产生电容变化)
电容屏的检测原理:
在电容屏的表面之下有X轴电极和Y轴电极,X轴电极发出AC交流信号后,交流信号通过电容会有充放电过程,通过Y轴电极检测这个充放电时间即可获得相应的电容值。当手指触摸屏幕时,触摸点的电容值会发生变化,通过下图具体了解可知,哪里的电容发生变化,即表示哪里发生了触摸现象。
实际上:检测Y轴电极检测的是充电时间,t=RC;其中R是整个电容屏的电阻,C是电容,手指触摸时,C电容会发生变化,进而使得充电时间 t 发生变化。
如上图,电极实际上是由多个菱形导体组成的,生产时使用蚀刻工艺在ITO层生成这样的结构。电极实际上是一连串菱形连在一起的。XY轴电极合并时,如右上图所示:
其扫描规则是这样的:
X轴电极依次扫描,Y轴电极同时扫描;比方说,X轴第一行发出AC交流信号后,由于耦合,会在第一行附近产生相应的电容,Y轴同时扫描,当扫描到哪一列的电容充电时间发生变化时,就意味着这一点产生了触摸信号。在X轴不断循环的过程中,依次扫描,在Y轴检测其触摸信号。右下图是单个XY电极产生的电容单元。
这也解释了为什么电容触摸屏可以同时检测多个信号?
意思就说:5个手指同时触摸电容屏是可以检测到的,而电阻屏不行。
因为X轴是依次循环的,第一根手指触摸读到第一个X;然后就会跳过这个X,读下一个X,用Y检测充电时间;上一个触摸点不会影响下一个触摸点。
STM32F4的4.3寸和7寸的LCD屏是电容式触摸屏,常见的两种电容触摸屏驱动IC:
①:GT9147(采用17*10的驱动结构)
GT9147的IIC地址,可以是0X14或者0X5D,当复位结束后的5ms内,如果INT是高电平,则使用0X14作为地址,否则使用0X5D作为地址。
控制命令寄存器:(0X8040)
该寄存器可以写入不同的值,实现不同的控制,我们一般使用0和2这两个值,写入2,即可软复位GT9147,在硬复位之后,一般要往该寄存器写2,实现软复位。然后,写入0,即可正确的读取坐标数据。
配置寄存器组:(0X8047~0X8100)
共186个寄存器,用于配置GT9147的各个参数。
产品ID寄存器:(0X8140~0X8143)
这总共有四个寄存器组成,用于保存产品ID,对于GT9147,这四个寄存器读出来的就是:9,1,4,7四个字符(ASCLL码值)。因此,可以通过该寄存器判断驱动IC的型号,究竟是GT9147还是OTT2001A。
状态寄存器:(0X814E)
这里关心最高位和低4位:最高位用于表示Buffer的状态,只要有数据存储(坐标/按键),那么Buffer就是1;最低4位用于表示有效触点的个数,范围是:0~5,0,表示没有触点,5表示有5点触摸。(这也是电容触摸屏多点触摸的显示)
坐标数据寄存器(共30个):
坐标数据寄存器共分成5组(5个点),每组6个寄存器储存数据,以下是第一组的寄存器:
对于第一组触点X Y的坐标,只需要读取0X8150~0X8153的值组合即可得到触点的X Y坐标。
GT9147支持寄存器地址自增,我们只需要发送寄存器组的首地址,然后读取即可,GT9147会自动地址自增,从而提高读取速度。
②:OTT2001A(采用13*8的驱动结构)
它们与MCU连接通过4根线,SDA、SCL、RST、INT,所以显然它们是通过IIC通讯方式与MCU进行通讯的。
4. 硬件配置
我手中拿着的是STM32F4,LCD是3.5寸的电阻式触摸屏。
4.1 XPT2046驱动芯片
XPT2046是一款4导线制触摸屏控制器,内含12位分辨率125KHz转换速率逐步逼近型A/D转换器。XPT2046支持从1.5V到5.25V的低电压I/O接口。XPT2046能通过执行两次A/D转换查出被按的屏幕位置,除此之外,还可以测量加在触摸屏上的压力。内部自带2.5V参考电压可以用作辅助输入、温度测量和电池监测模式之用。XPT2046片内部集成了温度传感器。
X+:X+位置输入端
Y+:Y+位置输入端
X-:X-位置输入端
Y-:Y-位置输入端
:参考电压输入/输出
DOUT:串行数据输出端
BUSY:忙时信号线。CS为高电平时为高阻状态
DIN:串行数据输入端
CS:片选信号
CLK:时钟
PEN:PEN引脚会在触摸屏被按下的时候产生一个低电平,平时没有触摸的时候是高电平,手指一直按着,会一直是低电平,直到手指松开。
因此可以通过中断来检测PEN引脚是否是低电平,那么则让XPT2046芯片进行模拟和数字的转换。通过中断来检测下降沿。
XPT2046的命令字:
XPT2046内部集成了A/D转换模块,且内部是有2.5V电压的。
之所以电阻屏需要XPT2046芯片驱动是因为:
XPT2046内部是集成A/D转换模块的,电阻屏是根据两个ITO面接触放电时间来进行判断触摸点的,t=RC,C电容变化引起放电时间t的变化,所以需要ADC通过X+、Y+采集获得数字量,XPT2046是通过SPI通讯的方式和STM32进行通过,XPT2046发送下述命令字控制ADC检测X+,Y+的电压值。
位7表示开始位,表示有新的字节到来;位6~4表示表示检测哪个通道的模拟量,从而转换成数字量(因为电阻式触摸屏是给x方向匀强电场,从Y+检测电压值;给y方向上匀强电场,从X+检测电压值);位3表示转换的分辨率是8位还是12位;位2表示输入方式是单端输入方式还是差分输入方式;其中A2-A0是通过下表进行配置的;
电阻式触摸屏的实质:
就是利用STM32的SPI协议和XPT2046芯片进行通讯,通过XPT2046自载的A/D转换模块获得转换的数字量,从而获得触摸点。
4.2 硬件设计
本章的实验功能(程序书写思路):
开机的时候初始化LCD,读取LCD ID,那么需要在配置LCD ID函数,根据ID判断是电容式触摸屏还是电阻式触摸屏。
如果是电阻式触摸屏,则先读取EEPROM(断电不丢失)存储器芯片AT24C02的数据判断触摸屏是否已经校准,写AT24C02判断函数校准函数,AT24C02使用IIC协议进行通讯,如果没有校准,执行校准程序。需要判断校准,写校准程序。校准过后进入电阻式触摸屏测试程序,如果已经校准,则直接进去测试程序。因此需要写电阻屏测试程序。
如果是4.3寸电容式触摸屏,先读芯片ID判断是不是GT9147,需要写判断芯片ID函数,如果是执行GT9147的初始化代码,如果不是,则执行OTT2001A的初始化代码。(注意:GT9147和OTT2001A都是电容式触摸屏的驱动芯片,只不过现在普遍使用的是GT9147)
如果是7寸电容触摸屏,则执行FT5206的初始化代码,在初始电容触摸屏完成后,进入电容触摸屏测试程序。(注意:FT5206是目前7寸电容触摸屏普遍使用的驱动芯片)
注:电阻式触摸屏一般在使用之前是需要校验的,而电容式触摸屏无需校验!!!
电阻触摸屏测试程序和电容触摸屏测试程序基本一样,只是电容触摸屏支持最多5个点同时触摸,电阻触摸屏只支持1点触摸。测试的界面右上角会有一个清空的操作区RST,点击这地方全部清除,回复白板状态。电阻触摸屏测试实验中,可以使用KEY0按键强制进去校验。
T_MOSI、T_MISO、T_SCK、T_CS 和 T_PEN 分别连接在 STM32F4 的:PF11、 PB2、PB0、PC13 和 PB1 上。
5. 代码详解
本章的实验功能(程序书写思路):
开机的时候初始化LCD,读取LCD ID,那么需要在配置LCD ID函数,根据ID判断是电容式触摸屏还是电阻式触摸屏。
如果是电阻式触摸屏,则先读取EEPROM(断电不丢失)存储器芯片AT24C02的数据判断触摸屏是否已经校准,写AT24C02判断函数校准函数,AT24C02使用IIC协议进行通讯,如果没有校准,执行校准程序。需要判断校准,写校准程序。校准过后进入电阻式触摸屏测试程序,如果已经校准,则直接进去测试程序。因此需要写电阻屏测试程序。
如果是4.3寸电容式触摸屏,先读芯片ID判断是不是GT9147,需要写判断芯片ID函数,如果是执行GT9147的初始化代码,如果不是,则执行OTT2001A的初始化代码。(注意:GT9147和OTT2001A都是电容式触摸屏的驱动芯片,只不过现在普遍使用的是GT9147)
如果是7寸电容触摸屏,则执行FT5206的初始化代码,在初始电容触摸屏完成后,进入电容触摸屏测试程序。(注意:FT5206是目前7寸电容触摸屏普遍使用的驱动芯片)
注:电阻式触摸屏一般在使用之前是需要校验的,而电容式触摸屏无需校验!!!
5.1 main.c
#include "stm32f4xx.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "lcd.h"
#include "Key.h"
#include "usmart.h"
#include "Touch.h"
//LCD状态设置函数
void led_set(u8 sta)//只要工程目录下有usmart调试函数,主函数就必须调用这两个函数
{
LED1=sta;
}
//函数参数调用测试函数
void test_fun(void(*ledset)(u8),u8 sta)
{
led_set(sta);
}
//清空屏幕并在右上角显示"RST"
void Load_Drow_Dialog(void)
{
LCD_Clear(WHITE);//清屏
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(lcddev.width-24,0,200,16,16,"RST");//显示清屏区域
POINT_COLOR=RED;//设置画笔蓝色
}
//电容触摸屏专有部分
//画水平线
//x0,y0:坐标
//len:线长度
//color:颜色
void gui_draw_hline(u16 x0,u16 y0,u16 len,u16 color)
{
if(len==0)
return;
LCD_Fill(x0,y0,x0+len-1,y0,color);
}
//画实心圆
//x0,y0:坐标
//r:半径
//color:颜色
void gui_fill_circle(u16 x0,u16 y0,u16 r,u16 color)
{
u32 i;
u32 imax = ((u32)r*707)/1000+1;
u32 sqmax = (u32)r*(u32)r+(u32)r/2;
u32 x=r;
gui_draw_hline(x0-r,y0,2*r,color);
for (i=1;i<=imax;i++)
{
if ((i*i+x*x)>sqmax)// draw lines from outside
{
if (x>imax)
{
gui_draw_hline (x0-i+1,y0+x,2*(i-1),color);
gui_draw_hline (x0-i+1,y0-x,2*(i-1),color);
}
x--;
}
// draw lines from inside (center)
gui_draw_hline(x0-x,y0+i,2*x,color);
gui_draw_hline(x0-x,y0-i,2*x,color);
}
}
//两个数之差的绝对值
//x1,x2:需取差值的两个数
//返回值:|x1-x2|
u16 my_abs(u16 x1,u16 x2)
{
if(x1>x2)return x1-x2;
else return x2-x1;
}
//画一条粗线
//(x1,y1),(x2,y2):线条的起始坐标
//size:线条的粗细程度
//color:线条的颜色
void lcd_draw_bline(u16 x1, u16 y1, u16 x2, u16 y2,u8 size,u16 color)
{
u16 t;
int xerr=0,yerr=0,delta_x,delta_y,distance;
int incx,incy,uRow,uCol;
if(x1<size|| x2<size||y1<size|| y2<size)
return;
delta_x=x2-x1; //计算坐标增量
delta_y=y2-y1;
uRow=x1;
uCol=y1;
if(delta_x>0)
incx=1; //设置单步方向
else if(delta_x==0)
incx=0;//垂直线
else
{
incx=-1;
delta_x=-delta_x;
}
if(delta_y>0)
incy=1;
else if(delta_y==0)
incy=0;//水平线
else{incy=-1;delta_y=-delta_y;}
if( delta_x>delta_y)
distance=delta_x; //选取基本增量坐标轴
else
distance=delta_y;
for(t=0;t<=distance+1;t++ )//画线输出
{
gui_fill_circle(uRow,uCol,size,color);//画点
xerr+=delta_x ;
yerr+=delta_y ;
if(xerr>distance)
{
xerr-=distance;
uRow+=incx;
}
if(yerr>distance)
{
yerr-=distance;
uCol+=incy;
}
}
}
//5个触控点的颜色(电容触摸屏用)
const u16 POINT_COLOR_TBL[OTT_MAX_TOUCH]={RED,GREEN,BLUE,BROWN,GRED};
//电阻触摸屏测试函数
void rtp_test(void)
{
u8 key;
u8 i=0;
while(1)
{
key=KEY_Scan(0);
tp_dev.scan(0);
if(tp_dev.sta&TP_PRES_DOWN) //触摸屏被按下
{
if(tp_dev.x[0]<lcddev.width&&tp_dev.y[0]<lcddev.height)
{
if(tp_dev.x[0]>(lcddev.width-24)&&tp_dev.y[0]<16)
Load_Drow_Dialog();//清除
else
TP_Draw_Big_Point(tp_dev.x[0],tp_dev.y[0],RED); //画图
}
}
else
delay_ms(10); //没有按键按下的时候
if(key==1) //KEY0按下,则执行校准程序
{
LCD_Clear(WHITE); //清屏
TP_Adjust(); //屏幕校准
TP_Save_Adjdata();
Load_Drow_Dialog();
}
i++;
if(i%20==0)
LED0=!LED0;
}
}
//电容触摸屏测试函数
void ctp_test(void)
{
u8 t=0;
u8 i=0;
u16 lastpos[5][2]; //最后一次的数据
while(1)
{
tp_dev.scan(0);
for(t=0;t<5;t++)
{
if((tp_dev.sta)&(1<<t))
{
if(tp_dev.x[t]<lcddev.width&&tp_dev.y[t]<lcddev.height)
{
if(lastpos[t][0]==0XFFFF)
{
lastpos[t][0] = tp_dev.x[t];
lastpos[t][1] = tp_dev.y[t];
}
lcd_draw_bline(lastpos[t][0],lastpos[t][1],tp_dev.x[t],tp_dev.y[t],2,POINT_COLOR_TBL[t]);//画线
lastpos[t][0]=tp_dev.x[t];
lastpos[t][1]=tp_dev.y[t];
if(tp_dev.x[t]>(lcddev.width-24)&&tp_dev.y[t]<20)
{
Load_Drow_Dialog();//清除
}
}
}else lastpos[t][0]=0XFFFF;
}
delay_ms(5);i++;
if(i%20==0)LED0=!LED0;
}
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为115200
LED_Init(); //初始化LED
LCD_Init(); //LCD初始化
Key_Init(); //按键初始化
tp_dev.init(); //触摸屏初始化
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");
LCD_ShowString(30,70,200,16,16,"TOUCH TEST");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2023/06/21");
if(tp_dev.touchtype!=0XFF)
LCD_ShowString(30,130,200,16,16,"Press KEY0 to Adjust");//电阻屏才显示
delay_ms(1500);
Load_Drow_Dialog();
if(tp_dev.touchtype&0X80)
ctp_test();//电容屏测试
else
rtp_test(); //电阻屏测试
}
5.2 AT24C02.c
#include "stm32f4xx.h"
#include "AT24C02.h"
#include "MyI2C.h"
#include "delay.h"
//初始化IIC接口
void AT24C02_Init(void)
{
IIC_Init();
}
//在AT24C02指定的地址写入一个数据
//WriteAddress:写入数据的目的地址
//WriteData:要写入的数据
void AT24C02_WriteByte(u16 WriteAddress,u8 WriteData)
{
IIC_Start();
if(EE_TYPE>AT24C16)
{
IIC_Send_Byte(0xA0); //发送写命令AT24C02的写命令是0xA0,也可以在头文件中进行宏定义,方便后续修改
IIC_Wait_Ack();
IIC_Send_Byte(WriteAddress>>8); //发送高地址
}
else
IIC_Send_Byte(0xA0+((WriteAddress/256)<<1)); //发送器件地址,写数据
IIC_Wait_Ack();
IIC_Send_Byte(WriteAddress%256); //发送低地址
IIC_Wait_Ack();
IIC_Send_Byte(WriteData); //发送字节
IIC_Wait_Ack();
IIC_Stop();
delay_ms(10);
}
//在AT24C02指定的地址读出一个数据
//ReadAddress:开始读出的地址
//返回值:读到的数据
u8 AT24C02_ReadByte(u16 ReadAddress)
{
u8 Data;
IIC_Start();
if(EE_TYPE>AT24C16)
{
IIC_Send_Byte(0xA0); //发送写命令AT24C02的写命令是0xA0,也可以在头文件中进行宏定义,方便后续修改
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddress>>8); //发送高地址
}
else
IIC_Send_Byte(0xA0+((ReadAddress/256)<<1)); //发送器件地址,写数据
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddress%256); //发送低地址
IIC_Wait_Ack();
//从AT24C02中读数据是需要经历两次起始位的,第一次起始位之前和写数据是一模一样的
IIC_Start();
IIC_Send_Byte(0xA0|0x01); //0XA0是写数据的地址,0xA1就是读数据的地址
IIC_Wait_Ack();
Data=IIC_Read_Byte(0); //根据IIC文件下IIC_Read_Byte的设置,如果设置参数为0,那么意味着只读一次,发送非应答,如果需要接着读,那么就需要发送应答,也就是参数设置为1
IIC_Stop();
return Data;
}
//在AT24C02里面的指定地址开始写入长度为Len的数据
//该函数用于写入16bit或者32bit的数据
//WriteAddress:开始写入的地址
//WriteData:数据元素首地址
//Len:要写入数据的长度2,4
void AT24C02_WriteLenByte(u16 WriteAddress,u32 WriteData,u8 Len)
{
u8 t;
for(t=0;t<Len;t++)
{
AT24C02_WriteByte(WriteAddress+t,(WriteData>>(8*t))&0xFF);
}
}
//在AT24CXX里面的指定地址开始读出长度为Len的数据
//该函数用于读出16bit或者32bit的数据.
//ReadAddress: 开始读出的地址
//返回值 :数据
//Len :要读出数据的长度2,4
u32 AT24C02_ReadLenByte(u16 ReadAddress,u8 Len)
{
u8 t;
u32 Data;
for(t=0;t<Len;t++)
{
Data<<=8;
Data+=AT24C02_ReadByte(ReadAddress+Len-t-1);
}
return Data;
}
//检查AT24C02是否正常
//这里运用一个AT24C02最后一个地址(255)来存储标记字,如果运用AT24Cxx的其他系列,那么这个地址就需要进行更改
//返回1:检测失败
//返回0:检测成功
u8 AT24C02_Check(void)
{
u8 ID;
ID=AT24C02_ReadByte(255); //解除睡眠,避免每次开机都写AT24C02
if(ID==0x55)
return 0;//AT24C02在地址为255上读出的为0x55,如果是就表示检测成功
else//排除第一次初始化的影响
{
AT24C02_WriteByte(255,0x55);//主动在255地址上写0x55
ID=AT24C02_ReadByte(255);//自己主动在地址上写的0x55,如果还是读不出来0x55,只能表明之前写的读写函数有问题了
if(ID==0x55)
return 0;
}
return 1;
}
//在AT24C02里面的指定地址开始写入指定个数的数据
//WriteAddress:开始写入的地址
//pBuffer:数据数组首地址
//Num:要写入数据的个数
void AT24C02_Write(u16 WriteAddress,u8 *pBuffer,u16 Num)
{
while(Num--)
{
AT24C02_WriteByte(WriteAddress,*pBuffer);
WriteAddress++;
pBuffer++;
}
}
//在AT24C02里面的指定地址开始读出指定个数的数据
//WriteAddress:开始读出的地址
//pBuffer:数据数组首地址
//Num:要读出数据的个数
void AT24C02_Read(u16 WriteAddress,u8 *pBuffer,u16 Num)
{
while(Num)
{
*pBuffer++=AT24C02_ReadByte(WriteAddress++);
Num--;
}
}
5.3 AT24C02.h
#ifndef _AT24C02__H_
#define _AT24C02__H_
#define AT24C01 127
#define AT24C02 255
#define AT24C04 511
#define AT24C08 1023
#define AT24C16 2047
#define AT24C32 4095
#define AT24C64 8191
#define AT24C128 16383
#define AT24C256 32767
//Mini STM32开发板使用的是24c02,所以定义EE_TYPE为AT24C02
#define EE_TYPE AT24C02
void AT24C02_Init(void);
void AT24C02_WriteByte(u16 WriteAddress,u8 WriteData);
u8 AT24C02_ReadByte(u16 ReadAddress);
void AT24C02_WriteLenByte(u16 WriteAddress,u32 WriteData,u8 Len);
u32 AT24C02_ReadLenByte(u16 ReadAddress,u8 Len);
u8 AT24C02_Check(void);
void AT24C02_Write(u16 WriteAddress,u8 *pBuffer,u16 Num);
void AT24C02_Read(u16 WriteAddress,u8 *pBuffer,u16 Num);
#endif
5.4 C_Touch_I2C.c
#include "stm32f4xx.h"
#include "C_TOUCH_I2C.h"
#include "delay.h"
//
//本程序实现电容触摸屏通过IIC协议和STM32F4进行通讯,从而使STM32接收到电容屏充放电的电容电压变化
//进而判断出触摸点的坐标
//
//控制IIC速度延时
void Touch_Delay(void)
{
delay_us(2);
}
//电容触摸芯片IIC接口初始化
//因为电容驱动芯片OTT2001A、GT9147、FT5206和STM32通讯的方式是IIC,所以使用对应芯片时需要首次初始化IIC
void Touch_IIC_Init(void)
{
//I2C只需要两个串行总线,SCK和SDA;所以使能两个引脚的时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOF,ENABLE); //使能GPIOB和GPIOF时钟
//初始化GPIOB,PB0引脚接SCK
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT; //输出模式
GPIO_InitStructure.GPIO_OType=GPIO_OType_OD; //开漏输出
//开漏输出区别于推挽输出的地方在于开漏输出需要上接上拉电阻外接电源
//推挽输出是不需要的
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0; //PB0引脚 串行时钟引脚
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOB,&GPIO_InitStructure);
//初始化PF11,PF11接MOSI,主输出从输入引脚,对应于IIC通讯中的串行数据总线
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11; //PF11
GPIO_Init(GPIOF,&GPIO_InitStructure);
}
//产生IIC起始信号
void Touch_IIC_Start(void)
{
//串行时钟总线高电平期间,串行数据总线由高变低表示起始信号,期间需要加上延迟函数,等待信号结束
Touch_IIC_SDA=1;
Touch_IIC_SCL=1;
Touch_Delay();
Touch_IIC_SDA=0;
Touch_Delay();
Touch_IIC_SCL=0; //起始信号已经来临了,所以将串行时钟总线置为低电平,准备发送和接收数据
Touch_Delay();
}
//产生IIC停止信号
void Touch_IIC_Stop(void)
{
//串行时钟总线高电平期间,数据总线由低电平变为高电平表示结束信号
Touch_IIC_SDA=0;
Touch_Delay();
Touch_IIC_SCL=1;
Touch_Delay();
Touch_IIC_SDA=1;
Touch_Delay();
}
//等待应答信号的到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 Touch_IIC_Wait_Ack(void)
{
u8 ucErrTime=0; //等待应答也是有时间的,不能无限时间的等待应答,用该变量来记录等待的时间
u8 rack=0; //该变量用来记录返回值0或者1;0表示接收成功,1表示接收失败;
Touch_IIC_SDA=1;
Touch_Delay();
Touch_IIC_SCL=1;
Touch_Delay();
while(Touch_READ_SDA) //只要有数据输入,就进入循环等待应答
{
ucErrTime++; //等待期间该值++
if(ucErrTime>250)//最多等待的时间,超过该时间就报错,启动停止函数,返回值为1,表示接收应答失败
{
Touch_IIC_Stop();
rack=1;
break;
}
Touch_Delay();
}
Touch_IIC_SCL=0; //时钟总线设置为0,表示发送应答
Touch_Delay();
return rack;
}
//产生Ack应答
void Touch_IIC_Ack(void)
{
Touch_IIC_SDA=0;
Touch_Delay();
Touch_IIC_SCL=1;
Touch_Delay();
Touch_IIC_SCL=0;
Touch_Delay();
Touch_IIC_SDA=1;
Touch_Delay();
}
//不产生应答
void Touch_IIC_NAck(void)
{
Touch_IIC_SDA=1;
Touch_Delay();
Touch_IIC_SCL=1;
Touch_Delay();
Touch_IIC_SCL=0;
Touch_Delay();
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void Touch_IIC_Send_Byte(u8 Type)
{
u8 i=0;
for(i=0;i<8;i++)
{
Touch_IIC_SDA=(Type&0x80)>>7; //发送之前首先把数据准备好,因为是高位在前,一位一位的发送,所以使用&运算符,左移7位,分别从高位开始,依次拿到每一位
Touch_Delay();
Touch_IIC_SCL=1;//
Touch_Delay();
Touch_IIC_SCL=0;
Type=Type<<1; //每循环一次都要保证Type&0x80得到的一个接着一个的最高位,也就是第一拿到的是Type的第7位,第二次拿到的是第6位
}
Touch_IIC_SDA=1;
}
//读一个字节,Ack=1时,发送ACK;Ack=0时,发送NAck;
u8 Touch_IIC_Read_Byte(unsigned char Ack)
{
u8 i=0;
u8 Receive=0;
for(i=0;i<8;i++)
{
Receive=Receive<<1; //因为发送数据是高位在前一位一位发送的,所以接收的时候也是一位一位接收的,所以每循环接收一次,就要左移一位
Touch_IIC_SCL=1;//接收数据在串行时钟总线高电平期间接收数据位,时钟一周期接收一位
Touch_Delay();
if(Touch_READ_SDA)//判断是否有数据发送过来,只要从机有数据发送,接收Receive++
Receive++;
Touch_IIC_SCL=0; //结束接收
Touch_Delay();
}
if(!Ack)
Touch_IIC_NAck(); //是否发送应答
else
Touch_IIC_Ack();
return Receive; //返回接收到的8位数据
}
5.5 C_Touch_I2C.h
#ifndef _C_TOUCH_I2C__H_
#define _C_TOUCH_I2C__H_
//IO操作函数
#define Touch_IIC_SCL PBout(0) //SCL
#define Touch_IIC_SDA PFout(11) //输出SDA
#define Touch_READ_SDA PFin(11) //输入SDA
//IO方向设置
//该宏定义方式是通过位段的方式来定义的,通过调用GPIO的模式寄存器来设置相关位,其中00:输入模式 01:输出模式
//位段操作分两步:第一步将所要操作位清空,使用与&操作符完成;第二步将清空的位设置为相应的值,按设置的值输出相应的模式
//GPIO状态寄存器是32位寄存器,每两个位控制一个GPIO引脚,所以总共控制15个引脚
//本次我们操作的对象是PF11引脚,也就对应于寄存器的22位、23位
#define Touch_SDA_IN() {GPIOF->MODER&=~(3<<(2*11));GPIOF->MODER|=0<<2*11;} //PF11输入模式
//将22/23位清空,使用&操作符完成;2*11表示每两个位控制一个引脚,先找到PF11所在的两个位
//3的32位二进制是: 0000 0000 0000 0000 0000 0000 0000 0011
//左移22位得到的是: 0000 0000 0011 0000 0000 0000 0000 0000
//取反得到的是: 1111 1111 1100 1111 1111 1111 1111 1111
//和最初的32位二进制按位与: 0000 0000 0000 0000 0000 0000 0000 0011 此时就设置了22 23位00,表示将这两位清空
//0的32位二进制是: 0000 0000 0000 0000 0000 0000 0000 0000
//0左移22位,|或操作符: 0000 0000 0000 0000 0000 0000 0000 0000 此时就将22位、23位设置成了00,表示输入模式
#define Touch_SDA_OUT() {GPIOF->MODER&=~(3<<(2*11));GPIOF->MODER|=1<<2*11;} //PF11输出模式
//同理设置PF11输出模式,将第22 23位设置为01即可。
void Touch_Delay(void);
void Touch_IIC_Init(void);
void Touch_IIC_Start(void);
void Touch_IIC_Stop(void);
u8 Touch_IIC_Wait_Ack(void);
void Touch_IIC_Ack(void);
void Touch_IIC_NAck(void);
void Touch_IIC_Send_Byte(u8 Type);
u8 Touch_IIC_Read_Byte(unsigned char Ack);
#endif
5.6 Touch.c
#include "stm32f4xx.h"
#include "Touch.h"
#include "delay.h"
#include "lcd.h"
#include "stdlib.h"
#include "math.h"
#include "AT24C02.h"
_m_tp_dev tp_dev={TP_Init,TP_Scan,TP_Adjust,0,0,0,0,0,0,0,0}; //初始化头文件中定义的结构体,设置结构体成员
//默认为touchtype=0的数据
u8 CMD_RDX=0XD0; //该变量的设置是通过XPT2046芯片的命令字来设置的
//该命令的位7:开始位,设置为1;位6~位4:A2-A0,选择从哪个通道去读取X+,Y+的电压值;位3:Mode,选择转换分辨率,这里设置为0,12位分辨率
//位2:设置单端输入模式还是差分输入模式,这里选择0差分输入模式,位1~位0:默认设置为00;
//因为0XD0表示1101 0000 X方向施加电压,产生匀强电场,从YP进行AD采集测量
u8 CMD_RDY=0X90;
//0X90表示1001 0000 Y方向上施加电压,产生匀强电场,从XP进行AD采集测量
//SPI写数据
//向触摸屏IC写入1字节的数据
//Byte:要写入的数据
void TP_Write_Byte(u8 Byte)
{
//整个SPI写数据的软件思路就是,一位一位的写,所以循环8次;时钟上升沿有效,每上升沿发送一位数据,发送之前,需要将数据准备好,这是很重要的
u8 i=0;
for(i=0;i<8;i++)
{
if(Byte&0x80) //拿到数据的最高位进行判断
{
TDIN=1; //T_MOSI 置1,主输出从接收位
}
else
TDIN=0;
Byte=Byte<<1; //数据移位,保证每个字节的数据通过Byte&0x80能依次被读取
TCLK=0; //先将时钟置低电平0
delay_us(1);
TCLK=1; //上升沿有效
}
}
//SPI读数据
//从触摸屏IC读取ADC的值
//CMD:指令
//返回值:读到的数据
u16 TP_Read_AD(u8 CMD)
{
u8 i=0;
u16 Num=0;
TCLK=0; //首先拉低时钟线
TDIN=0; //拉低数据线
TCS=0; //SPI通过片选信号线CS选中从机,即实现MCU和XPT2046之间的通讯
TP_Write_Byte(CMD);//通过调用写字节函数发送指令
delay_us(6); //ADS7846的转换时间最长为6us,也就是说XPT2046需要等待6us才会向MCU传递数据
TCLK=0;
delay_us(1);
TCLK=1; //给1个时钟,清除BUSY,BUSY是SPI时序中的一环
delay_us(1);
TCLK=0;//时钟线低电平时接收数据
for(i=0;i<16;i++) //读出16位数据,只有高12位有效,之所以高12位有效,是因为在设置XPT2046的命令字时,位3Mode,选择的是12位分辨率
{
//首先先忽略时钟下降沿读一位有效,单纯来看下述程序为什么能读到写入的数据
//Num=Num<<1; if(DOUT) Num++;
//首先Num初始化是0,16位,0000 0000 0000 0000;每次左移一位,右边补0;对于0来说,左移并没有影响
//但是if(DOUT) 一旦判断XPT2046像MCU发送一位数据,注意上面发送数据时,只有拿出数据最高位为1时,TDIN=1;所以并不是每接收一个数据Num都会++的
//Num++; Num++表示原本的0000 0000 0000 0000左移一次,右边补0,所以只要if语句成立,最右边位补上去的0就会变成1,再补上的基础上再次左移,这样每当发送一位,右边补1,最终下来实现读数据
//核心就是SPI是全双工,一边发送,一边接收的。所以发送函数发送,接收函数就会立刻接收。
Num=Num<<1;
TCLK=0; //下降沿有效
delay_us(1);
TCLK=1;
if(DOUT)
Num++;
}
Num=Num>>4; //只有高12位有效,所以后移4位,左4位移到高4位变为0
TCS=1;//取消片选
return(Num);
}
//读取一个坐标值(x或者y其中之一) 注意这里不是读整个坐标,而只是x和y其中一个
//连续读取READ_TIMES次数据,对这些数据升序排序
//然后去掉最低和最高LOST_VAL个数,取平均值
//xy(CMD_RDX/CMD_RDY)
//返回值:读到的数据
#define READ_TIMES 5 //读取次数
#define LOST_VAL 1 //丢弃值,这里注意定义的LOST_VAL只是表示最小值和最大值分别丢弃的个数,那么一个数组中必然有一个最大值和一个最小值,所以丢弃值最小就是2
u16 TP_Read_XOY(u8 xy)
{
u16 i,j;
u16 Buf[READ_TIMES]; //定义一个正好的数组来接收读取到的数据
u16 sum=0; //取平均的时候必然要用和除以个数,所以定义sum来接收读到的数据和
u16 temp; //中间变量用来做中间过渡用
for(i=0;i<READ_TIMES;i++)
{
Buf[i]=TP_Read_AD(xy);//读了多少次,就循环多少次,将读到的数据依次存储到我们定义好的接收Buf中
}
for(i=0;i<READ_TIMES-1;i++)//冒泡排序思想进行排序
{
for(j=i+1;j<READ_TIMES;j++)//从第二个值开始依次进行比较,只要有不符合升序的,就进行换位
{
if(Buf[i]>Buf[j])//因为是升序,所以判断是否前面的比后面的大,如果大,则利用过度变量temp,三者之间循环换位置
{
temp=Buf[i];
Buf[i]=Buf[j];
Buf[j]=temp;
}
}
}
sum=0;
for(i=LOST_VAL;i<READ_TIMES-LOST_VAL;i++)//因为宏定义LOST_VAL等于1,i=1作为初始条件,总和自然是不计算最大最小值
{
sum=sum+Buf[i];//取总和
}
temp=sum/(READ_TIMES-2*LOST_VAL); //取平均
return temp;
}
//读取x,y坐标
//可以附加坐标最小值不能小于100这个条件
//x,y:读到的坐标值
//返回值:0失败;1成功
u8 TP_Read_XY(u16 *x,u16 *y)
{
u16 xtemp,ytemp;
xtemp=TP_Read_XOY(CMD_RDX); //调用读x或y坐标函数进行读取,坐标命令通过宏定义定义的X为0xD0,0xD0是XPT2046的命令字设置的
ytemp=TP_Read_XOY(CMD_RDY);
//if(xtemp<100||ytemp<100) //坐标小于100,读取失败
// return 0;
*x=xtemp;
*y=ytemp;
return 1;
}
//连续两次读取触摸屏IC,取平均值大大提高准确率,并且这两次的偏差不能超过ERROR_RANGE,则认为满足条件,否则认为读数错误
//x,y:读到的坐标值
//返回值:0失败;1成功
#define ERROR_RANGE 50 //允许的误差范围
u8 TP_Read_XY2(u16 *x,u16 *y)
{
u16 x1,y1;
u16 x2,y2;
u8 flag; //定义一个变量来判断取到的坐标是否为0坐标
flag=TP_Read_XY(&x1,&y1);//用读坐标函数读到两个坐标来判断
if(flag==0)
return(0);
flag=TP_Read_XY(&x2,&y2);
if(flag==0)
return(0);
if(((x1<=x2&&x2<x1+ERROR_RANGE)||(x2<=x1&&x1<x2+ERROR_RANGE))&&((y2<=y1&&y1<y2+ERROR_RANGE)||(y1<=y2&&y2<y1+ERROR_RANGE)))
{//误差判断,允许误差的范围是定义的
*x=(x1+x2)/2;
*y=(y1+y2)/2;
return 1;
}
else
return 0;
}
//在LCD上画一个触摸点
//用来校准用的
//x,y:坐标
//color:颜色
void TP_Drow_Touch_Point(u16 x,u16 y,u16 color)
{
POINT_COLOR=color;
LCD_DrawLine(x-12,y,x+13,y);//横线
LCD_DrawLine(x,y-12,x,y+13);//竖线
LCD_DrawPoint(x+1,y+1);
LCD_DrawPoint(x-1,y+1);
LCD_DrawPoint(x+1,y-1);
LCD_DrawPoint(x-1,y-1);
LCD_Draw_Circle(x,y,6);//画中心圈
}
//画一个大点(2*2的点)
//x,y:坐标
//color:颜色
void TP_Draw_Big_Point(u16 x,u16 y,u16 color)
{
POINT_COLOR=color;
LCD_DrawPoint(x,y);//中心点
LCD_DrawPoint(x+1,y);
LCD_DrawPoint(x,y+1);
LCD_DrawPoint(x+1,y+1);
}
//
//触摸按键扫描
//tp:0,屏幕坐标;1,物理坐标(校准等特殊场合用)
//返回值:当前触屏状态.
//0,触屏无触摸;1,触屏有触摸
u8 TP_Scan(u8 tp)
{
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;//返回当前的触屏状态
}
//
//保存在EEPROM里面的地址区间基址,占用13个字节(RANGE:SAVE_ADDR_BASE~SAVE_ADDR_BASE+12)
#define SAVE_ADDR_BASE 40
//保存校准参数
void TP_Save_Adjdata(void)
{
s32 temp;
//保存校正结果!
temp=tp_dev.xfac*100000000;//保存x校正因素
AT24C02_WriteLenByte(SAVE_ADDR_BASE,temp,4);
temp=tp_dev.yfac*100000000;//保存y校正因素
AT24C02_WriteLenByte(SAVE_ADDR_BASE+4,temp,4);
//保存x偏移量
AT24C02_WriteLenByte(SAVE_ADDR_BASE+8,tp_dev.xoff,2);
//保存y偏移量
AT24C02_WriteLenByte(SAVE_ADDR_BASE+10,tp_dev.yoff,2);
//保存触屏类型
AT24C02_WriteByte(SAVE_ADDR_BASE+12,tp_dev.touchtype);
temp=0X0A;//标记校准过了
AT24C02_WriteByte(SAVE_ADDR_BASE+13,temp);
}
//得到保存在EEPROM里面的校准值
//返回值:1,成功获取数据
// 0,获取失败,要重新校准
u8 TP_Get_Adjdata(void)
{
s32 tempfac;
tempfac=AT24C02_ReadByte(SAVE_ADDR_BASE+13);//读取标记字,看是否校准过!
if(tempfac==0X0A)//触摸屏已经校准过了
{
tempfac=AT24C02_ReadLenByte(SAVE_ADDR_BASE,4);
tp_dev.xfac=(float)tempfac/100000000;//得到x校准参数
tempfac=AT24C02_ReadLenByte(SAVE_ADDR_BASE+4,4);
tp_dev.yfac=(float)tempfac/100000000;//得到y校准参数
//得到x偏移量
tp_dev.xoff=AT24C02_ReadLenByte(SAVE_ADDR_BASE+8,2);
//得到y偏移量
tp_dev.yoff=AT24C02_ReadLenByte(SAVE_ADDR_BASE+10,2);
tp_dev.touchtype=AT24C02_ReadByte(SAVE_ADDR_BASE+12);//读取触屏类型标记
if(tp_dev.touchtype)//X,Y方向与屏幕相反
{
CMD_RDX=0X90;
CMD_RDY=0XD0;
}else //X,Y方向与屏幕相同
{
CMD_RDX=0XD0;
CMD_RDY=0X90;
}
return 1;
}
return 0;
}
//提示字符串
u8* const TP_REMIND_MSG_TBL="Please use the stylus click the cross on the screen.The cross will always move until the screen adjustment is completed.";
//提示校准结果(各个参数)
void TP_Adj_Info_Show(u16 x0,u16 y0,u16 x1,u16 y1,u16 x2,u16 y2,u16 x3,u16 y3,u16 fac)
{
POINT_COLOR=RED;
LCD_ShowString(40,160,lcddev.width,lcddev.height,16,"x1:");
LCD_ShowString(40+80,160,lcddev.width,lcddev.height,16,"y1:");
LCD_ShowString(40,180,lcddev.width,lcddev.height,16,"x2:");
LCD_ShowString(40+80,180,lcddev.width,lcddev.height,16,"y2:");
LCD_ShowString(40,200,lcddev.width,lcddev.height,16,"x3:");
LCD_ShowString(40+80,200,lcddev.width,lcddev.height,16,"y3:");
LCD_ShowString(40,220,lcddev.width,lcddev.height,16,"x4:");
LCD_ShowString(40+80,220,lcddev.width,lcddev.height,16,"y4:");
LCD_ShowString(40,240,lcddev.width,lcddev.height,16,"fac is:");
LCD_ShowNum(40+24,160,x0,4,16); //显示数值
LCD_ShowNum(40+24+80,160,y0,4,16); //显示数值
LCD_ShowNum(40+24,180,x1,4,16); //显示数值
LCD_ShowNum(40+24+80,180,y1,4,16); //显示数值
LCD_ShowNum(40+24,200,x2,4,16); //显示数值
LCD_ShowNum(40+24+80,200,y2,4,16); //显示数值
LCD_ShowNum(40+24,220,x3,4,16); //显示数值
LCD_ShowNum(40+24+80,220,y3,4,16); //显示数值
LCD_ShowNum(40+56,240,fac,3,16); //显示数值,该数值必须在95~105范围之内.
}
//触摸屏校准代码
//得到四个校准参数
void TP_Adjust(void)
{
u16 pos_temp[4][2];//坐标缓存值
u8 cnt=0;
u16 d1,d2;
u32 tem1,tem2;
double fac;
u16 outtime=0;
cnt=0;
POINT_COLOR=BLUE;
BACK_COLOR =WHITE;
LCD_Clear(WHITE);//清屏
POINT_COLOR=RED;//红色
LCD_Clear(WHITE);//清屏
POINT_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(lcddev.width-20,20,RED); //画点2
break;
case 2:
TP_Drow_Touch_Point(lcddev.width-20,20,WHITE); //清除点2
TP_Drow_Touch_Point(20,lcddev.height-20,RED); //画点3
break;
case 3:
TP_Drow_Touch_Point(20,lcddev.height-20,WHITE); //清除点3
TP_Drow_Touch_Point(lcddev.width-20,lcddev.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(lcddev.width-20,lcddev.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(lcddev.width-20,lcddev.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(lcddev.width-20,lcddev.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)(lcddev.width-40)/(pos_temp[1][0]-pos_temp[0][0]);//得到xfac
tp_dev.xoff=(lcddev.width-tp_dev.xfac*(pos_temp[1][0]+pos_temp[0][0]))/2;//得到xoff
tp_dev.yfac=(float)(lcddev.height-40)/(pos_temp[2][1]-pos_temp[0][1]);//得到yfac
tp_dev.yoff=(lcddev.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(lcddev.width-20,lcddev.height-20,WHITE); //清除点4
TP_Drow_Touch_Point(20,20,RED); //画点1
LCD_ShowString(40,26,lcddev.width,lcddev.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;
}
POINT_COLOR=BLUE;
LCD_Clear(WHITE);//清屏
LCD_ShowString(35,110,lcddev.width,lcddev.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;
}
}
}
//触摸屏初始化
//返回值:0,没有进行校准
// 1,进行过校准
u8 TP_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
if(lcddev.id==0X5510) //电容触摸屏
{
if(GT9147_Init()==0) //是GT9147
{
tp_dev.scan=GT9147_Scan; //扫描函数指向GT9147触摸屏扫描
}else
{
OTT2001A_Init();
tp_dev.scan=OTT2001A_Scan; //扫描函数指向OTT2001A触摸屏扫描
}
tp_dev.touchtype|=0X80; //电容屏
tp_dev.touchtype|=lcddev.dir&0X01;//横屏还是竖屏
return 0;
}else if(lcddev.id==0X1963)
{
FT5206_Init();
tp_dev.scan=FT5206_Scan; //扫描函数指向GT9147触摸屏扫描
tp_dev.touchtype|=0X80; //电容屏
tp_dev.touchtype|=lcddev.dir&0X01;//横屏还是竖屏
return 0;
}else
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOF, ENABLE);//使能GPIOB,C,F时钟
//GPIOB1,2初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;//PB1/PB2 设置为上拉输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//输入模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//PB0设置为推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出模式
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;//PC13设置为推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出模式
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;//PF11设置推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出模式
GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化
TP_Read_XY(&tp_dev.x[0],&tp_dev.y[0]);//第一次读取初始化
AT24C02_Init(); //初始化24CXX
if(TP_Get_Adjdata())return 0;//已经校准
else //未校准?
{
LCD_Clear(WHITE);//清屏
TP_Adjust(); //屏幕校准
TP_Save_Adjdata();
}
TP_Get_Adjdata();
}
return 1;
}
5.7 Touch.h
#ifndef _TOUCH__H_
#define _TOUCH__H_
#include "OTT2001A.h"
#include "GT9147.h"
#include "FT5206.h"
#define TP_PRES_DOWN 0X80 //触屏被按下
#define TP_CATH_PRES 0X40 //有按键按下了
#define CT_MAX_TOUCH 5 //电容屏支持的点数,固定为5点
//触摸屏控制器
typedef struct
{
u8 (*init)(void); //初始化触摸屏控制器
//_m_tp_dev是一个结构体,init、scan是它的结构体成员,成员的类型是指针,也就是该指针指向的是一个函数
//使用该指针的同时就相当于调用了函数
//比如说在Touch.c里面初始化了结构体,初始化为TP_Init,那么调用_m_tp_dev.init就等于调用了TP_Init函数,该函数的参数类型是void型
u8 (*scan)(u8); //扫描触摸屏。0,屏幕扫描;1,物理坐标
void (*adjust)(void); //触摸屏校准
u16 x[CT_MAX_TOUCH]; //当前坐标
u16 y[CT_MAX_TOUCH]; //电容屏有最多5组坐标,电阻屏则用x[0],y[0]代表:此次扫描时,触屏的坐标,
//用x[4],y[4]存储第一次按下时的坐标
u8 sta; //笔的状态
//b7:按下1/松开0
//b6:0没有按键按下;1有按键按下
//b5:保留
//b4~b0:电容触摸屏按下的点数(0表示未按下,1表示按下)
//触摸屏校准参数
float xfac;
float yfac;
short xoff;
short yoff;
//b7:0电阻屏;1电容屏
//b1~b6:保留
//b0:0竖屏;1横屏
u8 touchtype;
}_m_tp_dev;
extern _m_tp_dev tp_dev; //触屏控制器在touch.c里面的定义
//电阻屏芯片引脚
#define PEN PBin(1) //T_PEN
#define DOUT PBin(2) //T_MISO
#define TDIN PFout(11) //T_MOSI
#define TCLK PBout(0) //T_SCK
#define TCS PCout(13) //T_CS
//电阻屏函数
void TP_Write_Byte(u8 num); //向控制芯片写入一个数据
u16 TP_Read_AD(u8 CMD); //读取AD转换值
u16 TP_Read_XOY(u8 xy); //带滤波的坐标读取(X/Y)
u8 TP_Read_XY(u16 *x,u16 *y); //双方向读取(X+Y)
u8 TP_Read_XY2(u16 *x,u16 *y); //带加强滤波的双方向坐标读取
void TP_Drow_Touch_Point(u16 x,u16 y,u16 color);//画一个坐标校准点
void TP_Draw_Big_Point(u16 x,u16 y,u16 color); //画一个大点
void TP_Save_Adjdata(void); //保存校准参数
u8 TP_Get_Adjdata(void); //读取校准参数
void TP_Adjust(void); //触摸屏校准
void TP_Adj_Info_Show(u16 x0,u16 y0,u16 x1,u16 y1,u16 x2,u16 y2,u16 x3,u16 y3,u16 fac);//显示校准信息
//电阻屏/电容屏 共用函数
u8 TP_Scan(u8 tp); //扫描
u8 TP_Init(void); //初始化
#endif
5.8 FT5206.c
#include "stm32f4xx.h"
#include "GT9147.h"
#include "Touch.h"
#include "C_Touch_I2C.h"
#include "usart.h"
#include "delay.h"
#include "LCD.h"
#include "string.h"
//GT9147配置参数表
//第一个字节为版本号(0X60),必须保证新的版本号大于等于GT9147内部
//flash原有版本号,才会更新配置.
const u8 GT9147_CFG_TBL[]=
{
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配置参数
//mode:0,参数不保存到flash
// 1,参数保存到flash
u8 GT9147_Send_Cfg(u8 mode)
{
u8 buf[2];
u8 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,(u8*)GT9147_CFG_TBL,sizeof(GT9147_CFG_TBL));//发送寄存器配置
GT9147_WR_Reg(GT_CHECK_REG,buf,2);//写入校验和,和配置更新标记
return 0;
}
//向GT9147写入一次数据
//reg:起始寄存器地址
//buf:数据缓缓存区
//len:写数据长度
//返回值:0,成功;1,失败.
u8 GT9147_WR_Reg(u16 reg,u8 *buf,u8 len)
{
u8 i;
u8 ret=0;
Touch_IIC_Start();
Touch_IIC_Send_Byte(GT_CMD_WR); //发送写命令
Touch_IIC_Wait_Ack();
Touch_IIC_Send_Byte(reg>>8); //发送高8位地址
Touch_IIC_Wait_Ack();
Touch_IIC_Send_Byte(reg&0XFF); //发送低8位地址
Touch_IIC_Wait_Ack();
for(i=0;i<len;i++)
{
Touch_IIC_Send_Byte(buf[i]); //发数据
ret=Touch_IIC_Wait_Ack();
if(ret)break;
}
Touch_IIC_Stop(); //产生一个停止条件
return ret;
}
//从GT9147读出一次数据
//reg:起始寄存器地址
//buf:数据缓缓存区
//len:读数据长度
void GT9147_RD_Reg(u16 reg,u8 *buf,u8 len)
{
u8 i;
Touch_IIC_Start();
Touch_IIC_Send_Byte(GT_CMD_WR); //发送写命令
Touch_IIC_Wait_Ack();
Touch_IIC_Send_Byte(reg>>8); //发送高8位地址
Touch_IIC_Wait_Ack();
Touch_IIC_Send_Byte(reg&0XFF); //发送低8位地址
Touch_IIC_Wait_Ack();
Touch_IIC_Start();
Touch_IIC_Send_Byte(GT_CMD_RD); //发送读命令
Touch_IIC_Wait_Ack();
for(i=0;i<len;i++)
{
buf[i]=Touch_IIC_Read_Byte(i==(len-1)?0:1); //发数据
}
Touch_IIC_Stop();//产生一个停止条件
}
//初始化GT9147触摸屏
//返回值:0,初始化成功;1,初始化失败
u8 GT9147_Init(void)
{
u8 temp[5];
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC, ENABLE);//使能GPIOB,C时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 ;//PB1设置为上拉输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//输入模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;//PC13设置为推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出模式
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化
Touch_IIC_Init(); //初始化电容屏的I2C总线
GT_RST=0; //复位
delay_ms(10);
GT_RST=1; //释放复位
delay_ms(10);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;//PB1设置为浮空输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出模式
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
delay_ms(100);
GT9147_RD_Reg(GT_PID_REG,temp,4);//读取产品ID
temp[4]=0;
printf("CTP ID:%s\r\n",temp); //打印ID
if(strcmp((char*)temp,"9147")==0)//ID==9147
{
temp[0]=0X02;
GT9147_WR_Reg(GT_CTRL_REG,temp,1);//软复位GT9147
GT9147_RD_Reg(GT_CFGS_REG,temp,1);//读取GT_CFGS_REG寄存器
if(temp[0]<0X60)//默认版本比较低,需要更新flash配置
{
printf("Default Ver:%d\r\n",temp[0]);
GT9147_Send_Cfg(1);//更新并保存配置
}
delay_ms(10);
temp[0]=0X00;
GT9147_WR_Reg(GT_CTRL_REG,temp,1);//结束复位
return 0;
}
return 0;
}
const u16 GT9147_TPX_TBL[5]={GT_TP1_REG,GT_TP2_REG,GT_TP3_REG,GT_TP4_REG,GT_TP5_REG};
//扫描触摸屏(采用查询方式)
//mode:0,正常扫描.
//返回值:当前触屏状态.
//0,触屏无触摸;1,触屏有触摸
u8 GT9147_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使用率
{
GT9147_RD_Reg(GT_GSTID_REG,&mode,1); //读取触摸点的状态
temp=0;
GT9147_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)) //触摸有效?
{
GT9147_RD_Reg(GT9147_TPX_TBL[i],buf,4); //读取XY坐标值
if(tp_dev.touchtype&0X01)//横屏
{
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("x[%d]:%d,y[%d]:%d\r\n",i,tp_dev.x[i],i,tp_dev.y[i]);
}
}
res=1;
if(tp_dev.x[0]>lcddev.width||tp_dev.y[0]>lcddev.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&=~(1<<7); //标记按键松开
}
tp_dev.x[0]=0xffff;
tp_dev.y[0]=0xffff;
tp_dev.sta&=0XE0; //清除点有效标记
}
if(t>240)t=10;//重新从10开始计数
return res;
}
5.9 FT5206.h
#ifndef _FT5206__H_
#define _FT5206__H_
#include "sys.h"
//与电容触摸屏连接的芯片引脚(未包含IIC引脚)
//IO操作函数
#define FT_RST PCout(13) //FT5206复位引脚
#define FT_INT PBin(1) //FT5206中断引脚
//I2C读写命令
#define FT_CMD_WR 0X70 //写命令
#define FT_CMD_RD 0X71 //读命令
//FT5206 部分寄存器定义
#define FT_DEVIDE_MODE 0x00 //FT5206模式控制寄存器
#define FT_REG_NUM_FINGER 0x02 //触摸状态寄存器
#define FT_TP1_REG 0X03 //第一个触摸点数据地址
#define FT_TP2_REG 0X09 //第二个触摸点数据地址
#define FT_TP3_REG 0X0F //第三个触摸点数据地址
#define FT_TP4_REG 0X15 //第四个触摸点数据地址
#define FT_TP5_REG 0X1B //第五个触摸点数据地址
#define FT_ID_G_LIB_VERSION 0xA1 //版本
#define FT_ID_G_MODE 0xA4 //FT5206中断模式控制寄存器
#define FT_ID_G_THGROUP 0x80 //触摸有效值设置寄存器
#define FT_ID_G_PERIODACTIVE 0x88 //激活状态周期设置寄存器
u8 FT5206_WR_Reg(u16 reg,u8 *buf,u8 len);
void FT5206_RD_Reg(u16 reg,u8 *buf,u8 len);
u8 FT5206_Init(void);
u8 FT5206_Scan(u8 mode);
#endif
5.10 GT9147.c
#include "stm32f4xx.h"
#include "GT9147.h"
#include "Touch.h"
#include "C_Touch_I2C.h"
#include "usart.h"
#include "delay.h"
#include "LCD.h"
#include "string.h"
//GT9147配置参数表
//第一个字节为版本号(0X60),必须保证新的版本号大于等于GT9147内部
//flash原有版本号,才会更新配置.
const u8 GT9147_CFG_TBL[]=
{
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配置参数
//mode:0,参数不保存到flash
// 1,参数保存到flash
u8 GT9147_Send_Cfg(u8 mode)
{
u8 buf[2];
u8 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,(u8*)GT9147_CFG_TBL,sizeof(GT9147_CFG_TBL));//发送寄存器配置
GT9147_WR_Reg(GT_CHECK_REG,buf,2);//写入校验和,和配置更新标记
return 0;
}
//向GT9147写入一次数据
//reg:起始寄存器地址
//buf:数据缓缓存区
//len:写数据长度
//返回值:0,成功;1,失败.
u8 GT9147_WR_Reg(u16 reg,u8 *buf,u8 len)
{
u8 i;
u8 ret=0;
Touch_IIC_Start();
Touch_IIC_Send_Byte(GT_CMD_WR); //发送写命令
Touch_IIC_Wait_Ack();
Touch_IIC_Send_Byte(reg>>8); //发送高8位地址
Touch_IIC_Wait_Ack();
Touch_IIC_Send_Byte(reg&0XFF); //发送低8位地址
Touch_IIC_Wait_Ack();
for(i=0;i<len;i++)
{
Touch_IIC_Send_Byte(buf[i]); //发数据
ret=Touch_IIC_Wait_Ack();
if(ret)break;
}
Touch_IIC_Stop(); //产生一个停止条件
return ret;
}
//从GT9147读出一次数据
//reg:起始寄存器地址
//buf:数据缓缓存区
//len:读数据长度
void GT9147_RD_Reg(u16 reg,u8 *buf,u8 len)
{
u8 i;
Touch_IIC_Start();
Touch_IIC_Send_Byte(GT_CMD_WR); //发送写命令
Touch_IIC_Wait_Ack();
Touch_IIC_Send_Byte(reg>>8); //发送高8位地址
Touch_IIC_Wait_Ack();
Touch_IIC_Send_Byte(reg&0XFF); //发送低8位地址
Touch_IIC_Wait_Ack();
Touch_IIC_Start();
Touch_IIC_Send_Byte(GT_CMD_RD); //发送读命令
Touch_IIC_Wait_Ack();
for(i=0;i<len;i++)
{
buf[i]=Touch_IIC_Read_Byte(i==(len-1)?0:1); //发数据
}
Touch_IIC_Stop();//产生一个停止条件
}
//初始化GT9147触摸屏
//返回值:0,初始化成功;1,初始化失败
u8 GT9147_Init(void)
{
u8 temp[5];
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC, ENABLE);//使能GPIOB,C时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 ;//PB1设置为上拉输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//输入模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;//PC13设置为推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出模式
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化
Touch_IIC_Init(); //初始化电容屏的I2C总线
GT_RST=0; //复位
delay_ms(10);
GT_RST=1; //释放复位
delay_ms(10);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;//PB1设置为浮空输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出模式
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
delay_ms(100);
GT9147_RD_Reg(GT_PID_REG,temp,4);//读取产品ID
temp[4]=0;
printf("CTP ID:%s\r\n",temp); //打印ID
if(strcmp((char*)temp,"9147")==0)//ID==9147
{
temp[0]=0X02;
GT9147_WR_Reg(GT_CTRL_REG,temp,1);//软复位GT9147
GT9147_RD_Reg(GT_CFGS_REG,temp,1);//读取GT_CFGS_REG寄存器
if(temp[0]<0X60)//默认版本比较低,需要更新flash配置
{
printf("Default Ver:%d\r\n",temp[0]);
GT9147_Send_Cfg(1);//更新并保存配置
}
delay_ms(10);
temp[0]=0X00;
GT9147_WR_Reg(GT_CTRL_REG,temp,1);//结束复位
return 0;
}
return 0;
}
const u16 GT9147_TPX_TBL[5]={GT_TP1_REG,GT_TP2_REG,GT_TP3_REG,GT_TP4_REG,GT_TP5_REG};
//扫描触摸屏(采用查询方式)
//mode:0,正常扫描.
//返回值:当前触屏状态.
//0,触屏无触摸;1,触屏有触摸
u8 GT9147_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使用率
{
GT9147_RD_Reg(GT_GSTID_REG,&mode,1); //读取触摸点的状态
temp=0;
GT9147_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)) //触摸有效?
{
GT9147_RD_Reg(GT9147_TPX_TBL[i],buf,4); //读取XY坐标值
if(tp_dev.touchtype&0X01)//横屏
{
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("x[%d]:%d,y[%d]:%d\r\n",i,tp_dev.x[i],i,tp_dev.y[i]);
}
}
res=1;
if(tp_dev.x[0]>lcddev.width||tp_dev.y[0]>lcddev.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&=~(1<<7); //标记按键松开
}
tp_dev.x[0]=0xffff;
tp_dev.y[0]=0xffff;
tp_dev.sta&=0XE0; //清除点有效标记
}
if(t>240)t=10;//重新从10开始计数
return res;
}
5.11 GT9147.h
#ifndef _GT9147__H_
#define _GT9147__H_
#include "sys.h"
//IO操作函数
#define GT_RST PCout(13) //GT9147复位引脚
#define GT_INT PBin(1) //GT9147中断引脚
//I2C读写命令
#define GT_CMD_WR 0X28 //写命令
#define GT_CMD_RD 0X29 //读命令
//GT9147 部分寄存器定义
#define GT_CTRL_REG 0X8040 //GT9147控制寄存器
#define GT_CFGS_REG 0X8047 //GT9147配置起始地址寄存器
#define GT_CHECK_REG 0X80FF //GT9147校验和寄存器
#define GT_PID_REG 0X8140 //GT9147产品ID寄存器
#define GT_GSTID_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 //第五个触摸点数据地址
u8 GT9147_Send_Cfg(u8 mode);
u8 GT9147_WR_Reg(u16 reg,u8 *buf,u8 len);
void GT9147_RD_Reg(u16 reg,u8 *buf,u8 len);
u8 GT9147_Init(void);
u8 GT9147_Scan(u8 mode);
#endif
5.12 OTT2001A.c
#include "stm32f4xx.h"
#include "OTT2001A.h"
#include "Touch.h"
#include "C_Touch_I2C.h"
#include "usart.h"
#include "delay.h"
//向OTT2001A写入一次数据
//reg:起始寄存器地址
//buf:数据缓缓存区
//len:写数据长度
//返回值:0,成功;1,失败.
u8 OTT2001A_WR_Reg(u16 reg,u8 *buf,u8 len)
{
u8 i;
u8 ret=0;
Touch_IIC_Start();
Touch_IIC_Send_Byte(OTT_CMD_WR); //发送写命令
Touch_IIC_Wait_Ack();
Touch_IIC_Send_Byte(reg>>8); //发送高8位地址
Touch_IIC_Wait_Ack();
Touch_IIC_Send_Byte(reg&0XFF); //发送低8位地址
Touch_IIC_Wait_Ack();
for(i=0;i<len;i++)
{
Touch_IIC_Send_Byte(buf[i]); //发数据
ret=Touch_IIC_Wait_Ack();
if(ret)break;
}
Touch_IIC_Stop(); //产生一个停止条件
return ret;
}
//从OTT2001A读出一次数据
//reg:起始寄存器地址
//buf:数据缓缓存区
//len:读数据长度
void OTT2001A_RD_Reg(u16 reg,u8 *buf,u8 len)
{
u8 i;
Touch_IIC_Start();
Touch_IIC_Send_Byte(OTT_CMD_WR); //发送写命令
Touch_IIC_Wait_Ack();
Touch_IIC_Send_Byte(reg>>8); //发送高8位地址
Touch_IIC_Wait_Ack();
Touch_IIC_Send_Byte(reg&0XFF); //发送低8位地址
Touch_IIC_Wait_Ack();
Touch_IIC_Start();
Touch_IIC_Send_Byte(OTT_CMD_RD); //发送读命令
Touch_IIC_Wait_Ack();
for(i=0;i<len;i++)
{
buf[i]=Touch_IIC_Read_Byte(i==(len-1)?0:1); //发数据
}
Touch_IIC_Stop();//产生一个停止条件
}
//传感器打开/关闭操作
//cmd:1,打开传感器;0,关闭传感器
void OTT2001A_SensorControl(u8 cmd)
{
u8 regval=0X00;
if(cmd)regval=0X80;
OTT2001A_WR_Reg(OTT_CTRL_REG,®val,1);
}
//初始化触摸屏
//返回值:0,初始化成功;1,初始化失败
u8 OTT2001A_Init(void)
{
u8 regval=0;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC, ENABLE);//使能GPIOB,C时钟
//GPIOB1初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;//PB1设置为上拉输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//输入模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;//PC13设置为推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出模式
GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化
Touch_IIC_Init(); //初始化电容屏的I2C总线
OTT_RST=0; //复位
delay_ms(100);
OTT_RST=1; //释放复位
delay_ms(100);
OTT2001A_SensorControl(1); //打开传感器
OTT2001A_RD_Reg(OTT_CTRL_REG,®val,1);//读取传感器运行寄存器的值来判断I2C通信是否正常
printf("CTP ID:%x\r\n",regval);
if(regval==0x80)return 0;
return 1;
}
const u16 OTT_TPX_TBL[5]={OTT_TP1_REG,OTT_TP2_REG,OTT_TP3_REG,OTT_TP4_REG,OTT_TP5_REG};
//扫描触摸屏(采用查询方式)
//mode:0,正常扫描.
//返回值:当前触屏状态.
//0,触屏无触摸;1,触屏有触摸
u8 OTT2001A_Scan(u8 mode)
{
u8 buf[4];
u8 i=0;
u8 res=0;
static u8 t=0;//控制查询间隔,从而降低CPU占用率
t++;
if((t%10)==0||t<10)//空闲时,每进入10次CTP_Scan函数才检测1次,从而节省CPU使用率
{
OTT2001A_RD_Reg(OTT_GSTID_REG,&mode,1);//读取触摸点的状态
if(mode&0X1F)
{
tp_dev.sta=(mode&0X1F)|TP_PRES_DOWN|TP_CATH_PRES;
for(i=0;i<5;i++)
{
if(tp_dev.sta&(1<<i)) //触摸有效?
{
OTT2001A_RD_Reg(OTT_TPX_TBL[i],buf,4); //读取XY坐标值
if(tp_dev.touchtype&0X01)//横屏
{
tp_dev.y[i]=(((u16)buf[2]<<8)+buf[3])*OTT_SCAL_Y;
tp_dev.x[i]=800-((((u16)buf[0]<<8)+buf[1])*OTT_SCAL_X);
}else
{
tp_dev.x[i]=(((u16)buf[2]<<8)+buf[3])*OTT_SCAL_Y;
tp_dev.y[i]=(((u16)buf[0]<<8)+buf[1])*OTT_SCAL_X;
}
//printf("x[%d]:%d,y[%d]:%d\r\n",i,tp_dev.x[i],i,tp_dev.y[i]);
}
}
res=1;
if(tp_dev.x[0]==0 && tp_dev.y[0]==0)mode=0; //读到的数据都是0,则忽略此次数据
t=0; //触发一次,则会最少连续监测10次,从而提高命中率
}
}
if((mode&0X1F)==0)//无触摸点按下
{
if(tp_dev.sta&TP_PRES_DOWN) //之前是被按下的
{
tp_dev.sta&=~(1<<7); //标记按键松开
}else //之前就没有被按下
{
tp_dev.x[0]=0xffff;
tp_dev.y[0]=0xffff;
tp_dev.sta&=0XE0; //清除点有效标记
}
}
if(t>240)t=10;//重新从10开始计数
return res;
}
5.13 OTT2001A.h
#ifndef _OTT2001A__H_
#define _OTT2001A__H_
#include "sys.h"
//IO操作函数
#define OTT_RST PCout(13) //OTT2001A复位引脚
#define OTT_INT PBin(1) //OTT2001A中断引脚
//通过OTT_SET_REG指令,可以查询到这个信息
//注意,这里的X,Y和屏幕的坐标系刚好是反的.
#define OTT_MAX_X 2700 //TP X方向的最大值(竖方向)
#define OTT_MAX_Y 1500 //TP Y方向的最大值(横方向)
//缩放因子
#define OTT_SCAL_X 0.2963 //屏幕的 纵坐标/OTT_MAX_X
#define OTT_SCAL_Y 0.32 //屏幕的 横坐标/OTT_MAX_Y
//I2C读写命令
#define OTT_CMD_WR 0XB2 //写命令
#define OTT_CMD_RD 0XB3 //读命令
//寄存器地址
#define OTT_GSTID_REG 0X0000 //OTT2001A当前检测到的触摸情况
#define OTT_TP1_REG 0X0100 //第一个触摸点数据地址
#define OTT_TP2_REG 0X0500 //第二个触摸点数据地址
#define OTT_TP3_REG 0X1000 //第三个触摸点数据地址
#define OTT_TP4_REG 0X1400 //第四个触摸点数据地址
#define OTT_TP5_REG 0X1800 //第五个触摸点数据地址
#define OTT_SET_REG 0X0900 //分辨率设置寄存器地址
#define OTT_CTRL_REG 0X0D00 //传感器控制(开/关)
#define OTT_MAX_TOUCH 5 //电容屏支持的点数,固定为5点
u8 OTT2001A_WR_Reg(u16 reg,u8 *buf,u8 len); //写寄存器(实际无用)
void OTT2001A_RD_Reg(u16 reg,u8 *buf,u8 len); //读寄存器
void OTT2001A_SensorControl(u8 cmd);//传感器打开/关闭操作
u8 OTT2001A_Init(void); //4.3电容触摸屏始化函数
u8 OTT2001A_Scan(u8 mode); //电容触摸屏扫描函数
#endif