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;
}
評論