XPT2046触摸屏实验过程详解与STM32代码解析

控制/MCU

1877人已加入

描述

1.XPT2046的初始化

XPT2046说起来其实就是一个AD转换器,所以它适合不需要什么初始化设置的,而具体的初始化其实也就是单片机IO的初始化和SPI的初始化。

这次STM32是使用SPI1来进行操作,SPI的设置其实在前几节课已经讲过了,这里就不重复讲了,初始化的具体代码如下:

/**********************************************************************

*FunctionName:TOUCH_Init

*Description:初始化触摸屏

*Input:None

*Output:None

*Return:None

**********************************************************************/

voidTOUCH_Init(void)

{

GPIO_InitTypeDefGPIO_InitStructure;

/*SPI的IO口和SPI外设打开时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);

/*TOUCH-CS的IO口设置*/GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;

GPIO_Init(GPIOD,&GPIO_InitStructure);

/*TOUCH-PEN的IO口设置*/GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;

GPIO_Init(GPIOD,&GPIO_InitStructure);SPI1_Config();

/*要使用FLASH来存储校正参数,所以注意之前要初始化*/

/*检测是否有校正参数*/

FLASH_ReadData(&TouchAdj.posState,TOUCH_ADJ_ADDR,sizeof(TouchAdj));

if(TouchAdj.posState!=TOUCH_ADJ_OK)

{

TOUCH_Adjust();//校正

}

}

在这个函数中,调用了SPI1的初始化函数,和触摸屏的校正程序,下面是SPI1的

初始化程序,校正原理我们在后面在讲述。

/**********************************************************************

*FunctionName:SPI1_Config

*Description:初始化SPI2

*Input:None

*Output:None

*Return:None

*********************************************************************/

voidSPI1_Config(void)

{

GPIO_InitTypeDefGPIO_InitStructure;SPI_InitTypeDefSPI_InitStructure;

/*SPI的IO口和SPI外设打开时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);

/*SPI的IO口设置*/

GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;

GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);//PA5.6.7上拉

/********************************************************************/

/*******************设置SPI的参数***********************************/

/*********************************************************************/SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;//选择全双工SPI模式

SPI_InitStructure.SPI_Mode=SPI_Mode_Master;//主机模式SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;//8位SPISPI_InitStructure.SPI_CPOL=SPI_CPOL_High;//时钟悬空高电平SPI_InitStructure.SPI_CPHA=SPI_CPHA_2Edge;//在第二个时钟采集数据SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;//Nss使用软件控制

/*选择波特率预分频为256*/

SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_256;SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;//从最高位开始传输

SPI_InitStructure.SPI_CRCPolynomial=7;

SPI_Cmd(SPI1,ENABLE);SPI_Init(SPI1,&SPI_InitStructure);

}

2.XPT2046读取X、Y值

我们知道,触摸屏根据方向,分为X轴和Y轴两个部分,通过读取X轴和Y轴的数据,我们就可以知道触摸屏触摸的位置了,就像数学上面的,知道了x坐标和y坐标,那么就可以确定在坐标轴上面一个点的位置。

STM32

8位总线接口,无DCLK时钟延迟,24时钟周期转换时序

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

所以读取一个完整转换过程为

1)发送1个8字节的控制命令

2)在这里可以小延时一下,如果你SPI时钟周期比XPT2046转换周期慢许多,不用延时也可以。

3)读取2个字节的返回数据。

4)进行数据处理。也就是丢弃最后读取到的3位数据。我们需要读取两个数据,一个X轴数据和一个Y轴数据,所以我们这里需要两个控制命令。

/**************************************************************************

*FunctionName:TOUCH_ReadData

*Description:采样物理坐标值

*Input:cmd:选择要读取是X轴还是Y轴的命令

*Output:None

*Return:读取到的物理坐标值

**************************************************************************/

staticuint16_tTOUCH_ReadData(uint8_tcmd)

{

uint8_ti,j;

uint16_treadValue[TOUCH_READ_TIMES],value;

uint32_ttotalValue;

/*SPI的速度不宜过快*/SPI2_SetSpeed(SPI_BaudRatePrescaler_16);

/*读取TOUCH_READ_TIMES次触摸值*/

for(i=0;i《touch_read_times;i++)

{/*打开片选*/TOUCH_CS_CLR;

/*在差分模式下,XPT2046转换需要24个时钟,8个时钟输入命令,之后1

个时钟去除*/

/*忙信号,接着输出12位转换结果,剩下3个时钟是忽略位*/SPI1_WriteReadData(cmd);//发送命令,选择X轴或者Y轴

/*读取数据*/

readValue[i]=SPI1_WriteReadData(0xFF);

readValue[i]《《=8;

readValue[i]|=SPI1_WriteReadData(0xFF);

读取数据部分

/*将数据处理,读取到的AD值的只有12位,最低三位无用*/

readValue[i]》》=3;TOUCH_CS_SET;

}

/*滤波处理*/

/*首先从大到小排序*/

for(i=0;i《(TOUCH_READ_TIMES-1);i++)

{

for(j=i+1;j《touch_read_times;j++)

{

/*采样值从大到小排序排序*/

if(readValue[i]《readValue[j])

{

value=readValue[i];readValue[i]=readValue[j];readValue[j]=value;

}

}

}

/*去掉最大值,去掉最小值,求平均值*/

j=TOUCH_READ_TIMES-1;

totalValue=0;

for(i=1;i《j;i++)=“”求=“”y=“”的全部值

{

totalValue+=readValue[i];

}

value=totalValue/(TOUCH_READ_TIMES-2);

returnvalue;

}

在这个读取函数的程序中,为了获取数据值的准确性,进行多次读取,然后除去最大最小值,求出平均值。这个就是所谓的程序滤波,接下来,再详细讲述程序滤波。

3.物理坐标值的数据处理

在读取X轴和Y轴的物理坐标值,也就是AD值的时候,需要进行一些必要的数据处理,这也是为了获取更准确的数据值,否则就会出现飞点等误差。

比较常用的程序滤波的方法为平均值法。也就是多次读取结果,然后去掉它们的最大值和最小值,最后求取它们的平均值。这种方法读取的次数越多,得到的数据就更准确。而上面我们读取数据程序里面使用的滤波方法也是这种方法。

不过为了更好的滤波,还使用了另外一种方式进行滤波。也就是当读取到两次数据之后,然后检查两个数据之间的差值,如果超过理想的误差,那么丢弃数据。这种方法也是很多的处理飞点的程序方法。

我们来看一下我例程中的程序数据处理:

/**************************************************************************

*FunctionName:TOUCH_ReadXY

*Description:读取触摸屏的X轴Y轴的物理坐标值

*Input:*xValue:保存读取到X轴物理坐标值的地址

***yValue:保存读取到Y轴物理坐标值的地址

*Output:None

*Return:0:读取成功;0xFF:读取失败

**************************************************************************/

staticuint8_tTOUCH_ReadXY(uint16_t*xValue,uint16_t*yValue)

{

uint16_txValue1,yValue1,xValue2,yValue2;

xValue1=TOUCH_ReadData(TOUCH_X_CMD);yValue1=TOUCH_ReadData(TOUCH_Y_CMD);xValue2=TOUCH_ReadData(TOUCH_X_CMD);yValue2=TOUCH_ReadData(TOUCH_Y_CMD);

/*查看两个点之间的只采样值差距*/

if(xValue1》xValue2)

{

}

else

{

}

*xValue=xValue1-xValue2;

*xValue=xValue2-xValue1;

if(yValue1》yValue2)

{

}

else

{

}

*yValue=yValue1-yValue2;

*yValue=yValue2-yValue1;

/*判断采样差值是否在可控范围内*/

if((*xValue》TOUCH_MAX)||(*yValue》TOUCH_MAX))

{

return0xFF;

}

/*求平均值*/

*xValue=(xValue1+xValue2)/2;

*yValue=(yValue1+yValue2)/2;

/*判断得到的值,是否在取值范围之内*/

if((*xValue》TOUCH_X_MAX)||(*xValue《TOUCH_X_MIN)

||(*yValue》TOUCH_Y_MAX)||(*yValue《TOUCH_Y_MIN))

{

return0xFF;

}

return0;

}

该程序就如上述所说,调用上一小节的TOUCH_ReadData()读两次X轴和Y轴(如果把XY轴,交叉来读,效果更好)。然后求取它们差值(求平均值在TOUCH_ReadData()函数中已经使用了),判断是否超过理想误差,然后求出它们两个的平均值,最后查看是否超过X轴和Y轴的数据上限和数据下限。

4.触摸物理坐标值转换成LCD彩屏坐标

我们使用XPT2046读取到了触摸屏的触摸位置之后,想要在LCD屏相对应的位置上进行操作,我们还要将它转换成LCD屏的坐标值。比如说,我们在LCD屏(0,0)坐标位置按下,而读取到的物理坐标值(也就是AD值)为(100,200),那么我们想要在LCD屏(0,0)位置进行处理,将要将物理坐标(100,200)转换成LCD屏坐标。

那如何转换呢?我们知道,XPT2046的分辨率为12位,也就是说我们读取X轴的物理坐标值(这里我们假设为:Px)和Y轴的物理坐标值(这里我们假设为:Py)的值肯定是在0~4096之间。但是我们LCD彩屏X轴和Y轴的像素坐标确是240X400。(这个值是PZ6908L开发板配的3.5寸彩屏像素,不过不管多少,我们明白原理就行,为了更好的表示,在这里我们LCD彩屏X轴像素坐标我们假设为:Lcdx,LCD彩屏Y轴像素坐标我们假设为:Lcdy。)那么我们假设当(Px,Py)=(0,0)时,正好LCD彩屏像素坐标的起始坐标(0,0),当(Px,Py)=(4096,4096)时,正好LCD彩屏像素坐标的终止坐标(239,399)。难么我们不难看出触摸屏的物理坐标跟LCD彩屏像素坐标的对应关系为:

Factorx=Lcdx/Px;

Factory=Lcdy/Py;

那么我们就可以求出Factorx和Factory,然后每次读取到Px和Py之后就可以讲它很轻松的转换为Lcdx和Lcdy。这是一个很简单的数学关系。

不过呢,事情没有那么理想化,我们在LCD像素坐标为(0,0)读取的触摸屏物理坐标值不一定是(0,0),在LCD像素坐标为最大时,也不一定读取到的是触摸屏的物理坐标最大值。所以我们要进行一些数据校正,这也是屏幕校正的原因。

什么意思呢?那我们在来解一个数学问题:我们都知道每个触摸屏物理坐标值都能一一对应一个LCD彩屏上面的像素坐标值,也就是它们是成比例关系的。现在我们知道LCD彩屏的X轴像素坐标最小值为Lcdx1,我们能显示的LCD彩屏的X轴像素坐标最大值为Lcdx2。而我们在LCD彩屏像素坐标X轴最小值处读取的触摸屏X轴物理坐标为Px1,在LCD彩屏X轴像素坐标最大值处读取的触摸屏X轴的物理坐标为Px2。那么现在我们知道有一个触摸屏物理坐标值在Px1到Px2之间的坐标值为Px,那么和它对应的Lcdx的值是多少呢?

那么我们可以这么解:

Factorx=(Lcdx2–Lcdx1)/(Px2–Px1);Lcdx=(Px–Px1)*Factorx;那么就求得出Lcdx是多少了,对吧?现在我们把它分解出来:

Lcdx=Px*Factorx–Px1*Factorx;

然后将Px1*Factorx替换成一个变量Offsetx。那么我们现在就可以得到Lcdx和Px之间的对应关系式了。而关于Y轴也是同理,所以它们从物理坐标到像素坐标的转换关系式:

Lcdx=Px*Factorx–Offsetx;

Lcdy=Py*Factory–Offsety;

而求出Factor和Offset这两个数的过程就是校正程序应该做的工作了。现在我们理解了

屏幕校正的原因和原理,并懂得了物理坐标转换成像素坐标之后,我们来看一下我们例程的校正程序和触摸屏读取像素坐标程序。

/**************************************************************************

*FunctionName:TOUCH_Adjust

*Description:检测屏幕是否校正,没有的话进行校正,将校正值放置到FLASH中

*Input:None

*Output:None

*Return:None

**************************************************************************/

staticvoidTOUCH_Adjust(void)

{

uint16_tpx[2],py[2],xPot[4],yPot[4];

floatxFactor,yFactor;

/*读取第一个点*/

if(TOUCH_ReadAdjust(LCD_ADJX_MIN,LCD_ADJY_MIN,&xPot[0],&yPot[0]))

{

return;

}

TOUCH_AdjDelay500ms();

/*读取第二个点*/

if(TOUCH_ReadAdjust(LCD_ADJX_MIN,LCD_ADJY_MAX,&xPot[1],&yPot[1]))

{

return;

}

TOUCH_AdjDelay500ms();

/*读取第三个点*/

if(TOUCH_ReadAdjust(LCD_ADJX_MAX,LCD_ADJY_MIN,&xPot[2],&yPot[2]))

{

return;

}

TOUCH_AdjDelay500ms();

/*读取第四个点*/

if(TOUCH_ReadAdjust(LCD_ADJX_MAX,LCD_ADJY_MAX,&xPot[3],&yPot[3]))

{

return;

}

TOUCH_AdjDelay500ms();

/*处理读取到的四个点的数据,整合成对角的两个点*/

px[0]=(xPot[0]+xPot[1])/2;py[0]=(yPot[0]+yPot[2])/2;px[1]=(xPot[3]+xPot[2])/2;py[1]=(yPot[3]+yPot[1])/2;

/*求出比例因数*/

xFactor=(float)LCD_ADJ_X/(px[1]-px[0]);

yFactor=(float)LCD_ADJ_Y/(py[1]-py[0]);

/*求出偏移量*/

TouchAdj.xOffset=(int16_t)LCD_ADJX_MAX-((float)px[1]*xFactor);TouchAdj.yOffset=(int16_t)LCD_ADJY_MAX-((float)py[1]*yFactor);

/*将比例因数进行数据处理,然后保存*/TouchAdj.xFactor=xFactor;

TouchAdj.yFactor=yFactor;

TouchAdj.posState=TOUCH_ADJ_OK;

FLASH_WriteData(&TouchAdj.posState,TOUCH_ADJ_ADDR,sizeof(TouchAdj));

}

/*************************************************************************

*FunctionName:TOUCH_Scan

*Description:扫描是否有触摸按下

*Input:None

*Output:TouchData:读取到的物理坐标值和对应的彩屏坐标值

*Return:0:读取成功;0xFF:没有触摸

**************************************************************************/

uint8_tTOUCH_Scan(void)

{

if(TOUCH_PEN==0)//查看是否有触摸

{

if(TOUCH_ReadXY(&TouchData.x,&TouchData.y))//没有触摸

{

return0xFF;

}

/*根据物理坐标值,计算出彩屏坐标值*/

TouchData.lcdx=TouchData.x*TouchAdj.xFactor+TouchAdj.xOffset;TouchData.lcdy=TouchData.y*TouchAdj.yFactor+TouchAdj.yOffset;

/*查看彩屏坐标值是否超过彩屏大小*/

if(TouchData.lcdx》TFT_XMAX)

{

TouchData.lcdx=TFT_XMAX;

}

if(TouchData.lcdy》TFT_YMAX)

{

TouchData.lcdy=TFT_YMAX;

}

return0;

}

return0xFF;

}

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分