學習目標: 1.復習 STM32 的硬件 SPI 2.學習觸摸屏的原理 觸摸屏實驗
16.1 觸摸屏的簡介 現在的液晶屏大部分都帶觸摸了,一般我們使用比較多的是電阻式觸摸屏(多點觸摸屬 于電容式觸摸屏,比如幾乎所有智能機都支持多點觸摸,它們所用的屏就是電容式的觸摸屏) 我們彩屏上面帶的也是電阻式的觸摸屏。 電阻觸摸屏的主要部分是一塊與顯示器表面非常配合的電阻薄膜屏,它是一種多層的復 合薄膜,它以一層玻璃或硬塑料平板作為基層,表面涂有一層透明氧化金屬(透明的導電電 阻)導電層,上面再蓋有一層外表面硬化處理、光滑防擦的塑料層、它的內表面也涂有一層 涂層、在他們之間有許多細小的(小于1/1000 英寸)的透明隔離點把兩層導電層隔開絕緣。
1.001.jpg (9.76 KB, 下載次數: 123)
下載附件
2017-8-19 00:07 上傳
當手指觸摸屏幕時,兩層導電層在觸摸點位置就有了接觸,電阻發生變化,在 X 和 Y 兩個方向上的電壓發生變化,產生信號,然后控制器讀取信號,并計算出手指觸摸的位置, 這就是電阻式觸摸屏的原理。
1.002.jpg (6.88 KB, 下載次數: 119)
下載附件
2017-8-19 00:07 上傳
16.2觸摸屏的控制 XPT2046芯片簡介 從上面的簡介,我們知道觸摸屏都需要一個 AD 轉換器,也就是要將電壓變化讀取出 來,供主機求出觸摸的位置。而我們彩屏上面使用的觸摸芯片是XPT2046。 XPT2046 的特點主要有 1) 一款 4 導線制觸摸屏控制器,采用 SPI 模式進行通信。 2) 內含 12 位分辨率 125KHz 轉換速率逐步逼近型 A/D 轉換器。 3) 支持從 1.5V 到 5.25V 的低電壓 I/O 接口。 XPT2046 應該有 16 個引腳,如圖:
1.003.jpg (14.58 KB, 下載次數: 141)
下載附件
2017-8-19 00:07 上傳
其引腳說明如下:
1.004.jpg (50.08 KB, 下載次數: 123)
下載附件
2017-8-19 00:07 上傳
從上面的引腳圖,我們知道,XPT2046 跟單片機的主要引腳主要有:BUSY、DIN(單 片機 SPI 輸出端)、CS、DCLK(單片機 SPI 時鐘端)、PEN(筆觸中斷)、DOUT(單片機 SPI 輸入端) 16.3PZ6808L觸摸屏的原理圖
1.005.jpg (13.49 KB, 下載次數: 121)
下載附件
2017-8-19 00:07 上傳
1.006.jpg (11.88 KB, 下載次數: 114)
下載附件
2017-8-19 00:07 上傳
16.4XPT2046的操作 1. XPT2046 的初始化 XPT2046 說起來其實就是一個 AD 轉換器,所以它適合不需要什么初始化設置的, 而具體的初始化其實也就是單片機 IO 的初始化和 SPI 的初始化。 這次 STM32 是使用 SPI1 來進行操作,SPI 的設置其實在前幾節課已經講過了,這 里就不重復講了,初始化的具體代碼如下: - /**********************************************************************
- * Function Name : TOUCH_Init
- * Description : 初始化觸摸屏
- * Input : None
- * Output : None
- * Return : None
- **********************************************************************/
- void TOUCH_Init(void)
- {
- GPIO_InitTypeDef GPIO_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 的
初始化程序,校正原理我們在后面在講述。 - /**********************************************************************
- * Function Name : SPI1_Config
- * Description : 初始化 SPI2
- * Input : None
- * Output : None
- * Return : None
- *********************************************************************/
- void SPI1_Config(void)
- {
- GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_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 位 SPI SPI_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 坐標, 那么就可以確定在坐標軸上面一個點的位置。
1.007.jpg (22.52 KB, 下載次數: 123)
下載附件
2017-8-19 00:07 上傳
如何讀取 XPT2046 的數據呢?接下來我們來看一個時序圖:
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 軸數據,所以我們這里需要兩個控 制命令。一個完整的控制命令的結構為:
1.008.jpg (24.98 KB, 下載次數: 105)
下載附件
2017-8-19 00:07 上傳
1.009.jpg (28.48 KB, 下載次數: 123)
下載附件
2017-8-19 00:07 上傳
從上圖,我們可以得到兩個命令,讀取 X 軸的命令為:0xD0。而讀取 Y 軸的命令 為:0x90。 程序實現為: - /**************************************************************************
- * Function Name : TOUCH_ReadData
- * Description : 采樣物理坐標值
- * Input : cmd:選擇要讀取是 X 軸還是 Y 軸的命令
- * Output : None
- * Return : 讀取到的物理坐標值
- **************************************************************************/
- static uint16_t TOUCH_ReadData(uint8_t cmd)
- {
- uint8_t i, j;
- uint16_t readValue[TOUCH_READ_TIMES], value;
- uint32_t totalValue;
-
-
- /* 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);
-
-
- return value;
- }
復制代碼
在這個讀取函數的程序中,為了獲取數據值的準確性,進行多次讀取,然后除去最大最 小值,求出平均值。這個就是所謂的程序濾波,接下來,再詳細講述程序濾波。
3. 物理坐標值的數據處理 在讀取 X 軸和 Y 軸的物理坐標值,也就是 AD 值的時候,需要進行一些必要的數據處 理,這也是為了獲取更準確的數據值,否則就會出現飛點等誤差。 比較常用的程序濾波的方法為平均值法。也就是多次讀取結果,然后去掉它們的最大值 和最小值,最后求取它們的平均值。這種方法讀取的次數越多,得到的數據就更準確。而上 面我們讀取數據程序里面使用的濾波方法也是這種方法。 不過為了更好的濾波,還使用了另外一種方式進行濾波。也就是當讀取到兩次數據之后, 然后檢查兩個數據之間的差值,如果超過理想的誤差,那么丟棄數據。這種方法也是很多的 處理飛點的程序方法。 我們來看一下我例程中的程序數據處理: - /**************************************************************************
- * Function Name : TOUCH_ReadXY
- * Description : 讀取觸摸屏的 X 軸 Y 軸的物理坐標值
- * Input : *xValue:保存讀取到 X 軸物理坐標值的地址
- * * *yValue:保存讀取到 Y 軸物理坐標值的地址
- * Output : None
- * Return : 0:讀取成功;0xFF:讀取失敗
- **************************************************************************/
-
-
- static uint8_t TOUCH_ReadXY(uint16_t *xValue, uint16_t *yValue)
- {
- uint16_t xValue1, 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))
- {
- return 0xFF;
- }
-
-
- /* 求平均值 */
- *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))
- {
- return 0xFF;
- }
-
-
- return 0;
- }
復制代碼該程序就如上述所說,調用上一小節的 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 這兩個數的過程就是校正程序應該做的工作了。現在我們理解了 屏幕校正的原因和原理,并懂得了物理坐標轉換成像素坐標之后,我們來看一下我們例程的 校正程序和觸摸屏讀取像素坐標程序。 - /**************************************************************************
- * Function Name : TOUCH_Adjust
- * Description : 檢測屏幕是否校正,沒有的話進行校正,將校正值放置到 FLASH 中
- * Input : None
- * Output : None
- * Return : None
- **************************************************************************/
-
-
- static void TOUCH_Adjust(void)
- {
- uint16_t px[2], py[2], xPot[4], yPot[4];
- float xFactor, 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));
- }
- /*************************************************************************
- * Function Name : TOUCH_Scan
- * Description : 掃描是否有觸摸按下
- * Input : None
- * Output : TouchData:讀取到的物理坐標值和對應的彩屏坐標值
- * Return : 0:讀取成功;0xFF:沒有觸摸
- **************************************************************************/
-
-
- uint8_t TOUCH_Scan(void)
- {
-
-
- if(TOUCH_PEN == 0) //查看是否有觸摸
- {
- if(TOUCH_ReadXY(&TouchData.x, &TouchData.y)) //沒有觸摸
- {
- return 0xFF;
- }
- /* 根據物理坐標值,計算出彩屏坐標值 */
- 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;
- }
- return 0;
- }
- return 0xFF;
- }
復制代碼
231455pdd2r7qzg71dwq2q.png (50.75 KB, 下載次數: 132)
下載附件
2017-8-19 00:07 上傳
完整源碼+資料等內容下載地址:
http://www.zg4o1577.cn/bbs/dpj-92931-1.html
|