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坐标,那么就可以确定在坐标轴上面一个点的位置。
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;
}
评论
查看更多