1.XPT2046的初始化
XPT2046說起來其實就是一個AD轉(zhuǎn)換器,所以它適合不需要什么初始化設(shè)置的,而具體的初始化其實也就是單片機IO的初始化和SPI的初始化。
這次STM32是使用SPI1來進(jìn)行操作,SPI的設(shè)置其實在前幾節(jié)課已經(jīng)講過了,這里就不重復(fù)講了,初始化的具體代碼如下:
/**********************************************************************
*FunctionName:TOUCH_Init
*Description:初始化觸摸屏
*Input:None
*Output:None
*Return:None
**********************************************************************/
voidTOUCH_Init(void)
{
GPIO_InitTypeDefGPIO_InitStructure;
/*SPI的IO口和SPI外設(shè)打開時鐘*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
/*TOUCH-CS的IO口設(shè)置*/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口設(shè)置*/GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_Init(GPIOD,&GPIO_InitStructure);SPI1_Config();
/*要使用FLASH來存儲校正參數(shù),所以注意之前要初始化*/
/*檢測是否有校正參數(shù)*/
FLASH_ReadData(&TouchAdj.posState,TOUCH_ADJ_ADDR,sizeof(TouchAdj));
if(TouchAdj.posState!=TOUCH_ADJ_OK)
{
TOUCH_Adjust();//校正
}
}
在這個函數(shù)中,調(diào)用了SPI1的初始化函數(shù),和觸摸屏的校正程序,下面是SPI1的
初始化程序,校正原理我們在后面在講述。
/**********************************************************************
*FunctionName:SPI1_Config
*Description:初始化SPI2
*Input:None
*Output:None
*Return:None
*********************************************************************/
voidSPI1_Config(void)
{
GPIO_InitTypeDefGPIO_InitStructure;SPI_InitTypeDefSPI_InitStructure;
/*SPI的IO口和SPI外設(shè)打開時鐘*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
/*SPI的IO口設(shè)置*/
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上拉
/********************************************************************/
/*******************設(shè)置SPI的參數(shù)***********************************/
/*********************************************************************/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;//在第二個時鐘采集數(shù)據(jù)SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;//Nss使用軟件控制
/*選擇波特率預(yù)分頻為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值
我們知道,觸摸屏根據(jù)方向,分為X軸和Y軸兩個部分,通過讀取X軸和Y軸的數(shù)據(jù),我們就可以知道觸摸屏觸摸的位置了,就像數(shù)學(xué)上面的,知道了x坐標(biāo)和y坐標(biāo),那么就可以確定在坐標(biāo)軸上面一個點的位置。
8位總線接口,無DCLK時鐘延遲,24時鐘周期轉(zhuǎn)換時序
XPT2046完成一個完整的轉(zhuǎn)換需要24個串行時鐘,也就是需要3個字節(jié)的SPI時鐘。對照上圖,XPT2046前8個串行時鐘,是接收1個字節(jié)的轉(zhuǎn)換命令,接收到轉(zhuǎn)換命令了之后,然后使用1個串行時鐘的時間來完成數(shù)據(jù)轉(zhuǎn)換(當(dāng)然在編寫程序的時候,為了得到精確的數(shù)據(jù),你可以適當(dāng)?shù)难訒r一下),然后返回12個字節(jié)長度(12個字節(jié)長度也計時12個串行時鐘)的轉(zhuǎn)換結(jié)果。然后最后3個串行時鐘返回三個無效數(shù)據(jù)。
所以讀取一個完整轉(zhuǎn)換過程為:
1)發(fā)送1個8字節(jié)的控制命令
2)在這里可以小延時一下,如果你SPI時鐘周期比XPT2046轉(zhuǎn)換周期慢許多,不用延時也可以。
3)讀取2個字節(jié)的返回數(shù)據(jù)。
4)進(jìn)行數(shù)據(jù)處理。也就是丟棄最后讀取到的3位數(shù)據(jù)。我們需要讀取兩個數(shù)據(jù),一個X軸數(shù)據(jù)和一個Y軸數(shù)據(jù),所以我們這里需要兩個控制命令。
/**************************************************************************
*FunctionName:TOUCH_ReadData
*Description:采樣物理坐標(biāo)值
*Input:cmd:選擇要讀取是X軸還是Y軸的命令
*Output:None
*Return:讀取到的物理坐標(biāo)值
**************************************************************************/
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轉(zhuǎn)換需要24個時鐘,8個時鐘輸入命令,之后1
個時鐘去除*/
/*忙信號,接著輸出12位轉(zhuǎn)換結(jié)果,剩下3個時鐘是忽略位*/SPI1_WriteReadData(cmd);//發(fā)送命令,選擇X軸或者Y軸
/*讀取數(shù)據(jù)*/
readValue[i]=SPI1_WriteReadData(0xFF);
readValue[i]《《=8;
readValue[i]|=SPI1_WriteReadData(0xFF);
讀取數(shù)據(jù)部分
/*將數(shù)據(jù)處理,讀取到的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;
}
在這個讀取函數(shù)的程序中,為了獲取數(shù)據(jù)值的準(zhǔn)確性,進(jìn)行多次讀取,然后除去最大最小值,求出平均值。這個就是所謂的程序濾波,接下來,再詳細(xì)講述程序濾波。
3.物理坐標(biāo)值的數(shù)據(jù)處理
在讀取X軸和Y軸的物理坐標(biāo)值,也就是AD值的時候,需要進(jìn)行一些必要的數(shù)據(jù)處理,這也是為了獲取更準(zhǔn)確的數(shù)據(jù)值,否則就會出現(xiàn)飛點等誤差。
比較常用的程序濾波的方法為平均值法。也就是多次讀取結(jié)果,然后去掉它們的最大值和最小值,最后求取它們的平均值。這種方法讀取的次數(shù)越多,得到的數(shù)據(jù)就更準(zhǔn)確。而上面我們讀取數(shù)據(jù)程序里面使用的濾波方法也是這種方法。
不過為了更好的濾波,還使用了另外一種方式進(jìn)行濾波。也就是當(dāng)讀取到兩次數(shù)據(jù)之后,然后檢查兩個數(shù)據(jù)之間的差值,如果超過理想的誤差,那么丟棄數(shù)據(jù)。這種方法也是很多的處理飛點的程序方法。
我們來看一下我例程中的程序數(shù)據(jù)處理:
/**************************************************************************
*FunctionName:TOUCH_ReadXY
*Description:讀取觸摸屏的X軸Y軸的物理坐標(biāo)值
*Input:*xValue:保存讀取到X軸物理坐標(biāo)值的地址
***yValue:保存讀取到Y(jié)軸物理坐標(biāo)值的地址
*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;
/*判斷采樣差值是否在可控范圍內(nèi)*/
if((*xValue》TOUCH_MAX)||(*yValue》TOUCH_MAX))
{
return0xFF;
}
/*求平均值*/
*xValue=(xValue1+xValue2)/2;
*yValue=(yValue1+yValue2)/2;
/*判斷得到的值,是否在取值范圍之內(nèi)*/
if((*xValue》TOUCH_X_MAX)||(*xValue《TOUCH_X_MIN)
||(*yValue》TOUCH_Y_MAX)||(*yValue《TOUCH_Y_MIN))
{
return0xFF;
}
return0;
}
該程序就如上述所說,調(diào)用上一小節(jié)的TOUCH_ReadData()讀兩次X軸和Y軸(如果把XY軸,交叉來讀,效果更好)。然后求取它們差值(求平均值在TOUCH_ReadData()函數(shù)中已經(jīng)使用了),判斷是否超過理想誤差,然后求出它們兩個的平均值,最后查看是否超過X軸和Y軸的數(shù)據(jù)上限和數(shù)據(jù)下限。
4.觸摸物理坐標(biāo)值轉(zhuǎn)換成LCD彩屏坐標(biāo)
我們使用XPT2046讀取到了觸摸屏的觸摸位置之后,想要在LCD屏相對應(yīng)的位置上進(jìn)行操作,我們還要將它轉(zhuǎn)換成LCD屏的坐標(biāo)值。比如說,我們在LCD屏(0,0)坐標(biāo)位置按下,而讀取到的物理坐標(biāo)值(也就是AD值)為(100,200),那么我們想要在LCD屏(0,0)位置進(jìn)行處理,將要將物理坐標(biāo)(100,200)轉(zhuǎn)換成LCD屏坐標(biāo)。
那如何轉(zhuǎn)換呢?我們知道,XPT2046的分辨率為12位,也就是說我們讀取X軸的物理坐標(biāo)值(這里我們假設(shè)為:Px)和Y軸的物理坐標(biāo)值(這里我們假設(shè)為:Py)的值肯定是在0~4096之間。但是我們LCD彩屏X軸和Y軸的像素坐標(biāo)確是240X400。(這個值是PZ6908L開發(fā)板配的3.5寸彩屏像素,不過不管多少,我們明白原理就行,為了更好的表示,在這里我們LCD彩屏X軸像素坐標(biāo)我們假設(shè)為:Lcdx,LCD彩屏Y軸像素坐標(biāo)我們假設(shè)為:Lcdy。)那么我們假設(shè)當(dāng)(Px,Py)=(0,0)時,正好LCD彩屏像素坐標(biāo)的起始坐標(biāo)(0,0),當(dāng)(Px,Py)=(4096,4096)時,正好LCD彩屏像素坐標(biāo)的終止坐標(biāo)(239,399)。難么我們不難看出觸摸屏的物理坐標(biāo)跟LCD彩屏像素坐標(biāo)的對應(yīng)關(guān)系為:
Factorx=Lcdx/Px;
Factory=Lcdy/Py;
那么我們就可以求出Factorx和Factory,然后每次讀取到Px和Py之后就可以講它很輕松的轉(zhuǎn)換為Lcdx和Lcdy。這是一個很簡單的數(shù)學(xué)關(guān)系。
不過呢,事情沒有那么理想化,我們在LCD像素坐標(biāo)為(0,0)讀取的觸摸屏物理坐標(biāo)值不一定是(0,0),在LCD像素坐標(biāo)為最大時,也不一定讀取到的是觸摸屏的物理坐標(biāo)最大值。所以我們要進(jìn)行一些數(shù)據(jù)校正,這也是屏幕校正的原因。
什么意思呢?那我們在來解一個數(shù)學(xué)問題:我們都知道每個觸摸屏物理坐標(biāo)值都能一一對應(yīng)一個LCD彩屏上面的像素坐標(biāo)值,也就是它們是成比例關(guān)系的。現(xiàn)在我們知道LCD彩屏的X軸像素坐標(biāo)最小值為Lcdx1,我們能顯示的LCD彩屏的X軸像素坐標(biāo)最大值為Lcdx2。而我們在LCD彩屏像素坐標(biāo)X軸最小值處讀取的觸摸屏X軸物理坐標(biāo)為Px1,在LCD彩屏X軸像素坐標(biāo)最大值處讀取的觸摸屏X軸的物理坐標(biāo)為Px2。那么現(xiàn)在我們知道有一個觸摸屏物理坐標(biāo)值在Px1到Px2之間的坐標(biāo)值為Px,那么和它對應(yīng)的Lcdx的值是多少呢?
那么我們可以這么解:
Factorx=(Lcdx2–Lcdx1)/(Px2–Px1);Lcdx=(Px–Px1)*Factorx;那么就求得出Lcdx是多少了,對吧?現(xiàn)在我們把它分解出來:
Lcdx=Px*Factorx–Px1*Factorx;
然后將Px1*Factorx替換成一個變量Offsetx。那么我們現(xiàn)在就可以得到Lcdx和Px之間的對應(yīng)關(guān)系式了。而關(guān)于Y軸也是同理,所以它們從物理坐標(biāo)到像素坐標(biāo)的轉(zhuǎn)換關(guān)系式:
Lcdx=Px*Factorx–Offsetx;
Lcdy=Py*Factory–Offsety;
而求出Factor和Offset這兩個數(shù)的過程就是校正程序應(yīng)該做的工作了。現(xiàn)在我們理解了
屏幕校正的原因和原理,并懂得了物理坐標(biāo)轉(zhuǎn)換成像素坐標(biāo)之后,我們來看一下我們例程的校正程序和觸摸屏讀取像素坐標(biāo)程序。
/**************************************************************************
*FunctionName:TOUCH_Adjust
*Description:檢測屏幕是否校正,沒有的話進(jìn)行校正,將校正值放置到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();
/*處理讀取到的四個點的數(shù)據(jù),整合成對角的兩個點*/
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;
/*求出比例因數(shù)*/
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);
/*將比例因數(shù)進(jìn)行數(shù)據(jù)處理,然后保存*/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:讀取到的物理坐標(biāo)值和對應(yīng)的彩屏坐標(biāo)值
*Return:0:讀取成功;0xFF:沒有觸摸
**************************************************************************/
uint8_tTOUCH_Scan(void)
{
if(TOUCH_PEN==0)//查看是否有觸摸
{
if(TOUCH_ReadXY(&TouchData.x,&TouchData.y))//沒有觸摸
{
return0xFF;
}
/*根據(jù)物理坐標(biāo)值,計算出彩屏坐標(biāo)值*/
TouchData.lcdx=TouchData.x*TouchAdj.xFactor+TouchAdj.xOffset;TouchData.lcdy=TouchData.y*TouchAdj.yFactor+TouchAdj.yOffset;
/*查看彩屏坐標(biāo)值是否超過彩屏大小*/
if(TouchData.lcdx》TFT_XMAX)
{
TouchData.lcdx=TFT_XMAX;
}
if(TouchData.lcdy》TFT_YMAX)
{
TouchData.lcdy=TFT_YMAX;
}
return0;
}
return0xFF;
}
評論