完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第54章 STM32H7的LTDC應用之LCD電阻觸摸和電容觸摸
本章教程為大家講解LTDC應用之LCD電阻觸摸芯片STMPE811的4點和2點觸摸校准和電容觸摸芯片FT5X06、GT911和GT811的使用。
54.1 初學者重要提示
54.2 電阻觸摸和電容觸摸相關知識
54.3 電阻屏硬件設計
54.4 電容屏硬件設計
54.5 電阻觸摸驅動設計
54.6 電容觸摸驅動設計
54.7 不同觸摸IC的識別
54.8 LCD觸摸移植和使用
54.9 實驗例程設計框架
54.10 實驗例程說明(MDK)
54.11 實驗例程說明(IAR)
54.12 總結
54.1 初學者重要提示
- 學習本章節前,務必優先學習第50章,需要對LTDC的基礎知識和HAL庫的幾個常用API有個認識。
- LTDC驅動設計和相關問題在第51章有詳細說明。
- 電阻觸摸支持2點和4點校准,而電容屏無需校准。
- 電阻觸摸校准解決的是觸摸板的線性度問題,而飛點要另外處理,當前程序已經做了支持。總的來說,V7配套的電阻觸摸方案已經比較成熟,可以放心用於項目。
54.2 電阻觸摸和電容觸摸相關知識
這部分知識點在第52章的2.2小節有詳細說明,必看。
54.3 電阻屏硬件設計
電阻觸摸STMPE811的原理圖如下:
通過STMPE811的原理圖要了解以下幾點:
- I2C的兩根通信線I2C_SCL和I2C_SDA的上拉電阻在V7的主板上。
- 原理圖右側的GPIO-0到GPIO-7可以作為擴展IO使用,支持輸入和輸出。其中GPIO-4到GPIO-7用於電阻觸摸校准(使用那個IO是可以配置的)。
- 對於X-,X+,Y-和Y+,只要不是X和Y進行組合,其它組合方式可以隨意接,配套的觸摸校准算法都可以正常識別。
54.4 電容屏硬件設計
電容觸摸主要有三種:FT5X06,GT911和GT811,其中GT811已經停產。下面是FT5X06和GT911觸摸板效果(觸摸板和觸摸芯片是一體的):
觸摸芯片已經集成到柔性PCB上,且已經校准好。用戶使用的話,直接通過I2C方式讀取數據即可。下面是電容觸摸板引出的引腳:
注意I2C_SDK和I2C_SCL的上拉電阻在V7主板上。
54.5 電阻觸摸驅動設計
下面將電阻觸摸程序設計中的相關問題逐一為大家做個說明。
54.5.1 STMPE811的驅動實現
電阻觸摸要比電容觸摸麻煩很多,因為電阻觸摸要做校准,還要做濾波,否則采集回來的觸摸值會抖動或者出現飛點,出現這種情況的主要原因是電阻觸摸板的線性度不夠好。開發板電阻屏使用的觸摸芯片是STMPE811,這個芯片其實就是12位分辨率的ADC,用來采集電阻觸摸板的X軸ADC值和Y軸ADC值,然后按照一定的線性關系將ADC值轉換為實際的坐標值。其中這個線性關系是通過觸摸校准建立起來的,每次采集的X軸和Y軸ADC就可以代入這個線性關系,從而獲得實際的坐標值。
總的來說,STMPE811的驅動不難實現,可以結合STMPE811的數據手冊:http://bbs.armfly.com/read.php?tid=23306 研究開發板提供的驅動配置。配置好后僅需要提供讀取的X軸,Y軸的ADC值以及觸摸按下狀態(判斷STMPE811的中斷輸出引腳就可以了,如果有觸摸,這個引腳輸出低電平,反之,輸出高電平。通過判斷這個引腳就可以選擇是否讀取X軸,Y軸的ADC值,避免不必要的操作)。這些函數在bsp_ts_stmpe811.c文件實現。而觸摸值濾波,觸摸掃描和觸摸校准是在bsp_touch.c文件里面實現。
下面是清除觸摸中斷標志函數和X軸,Y軸的ADC值讀取函數,這些函數被bsp_touch.c文件所調用,而函數TOUCH_PenInt是在bsp_touch.c文件,這里也貼出來。
1. /* 2. ****************************************************************************************************** 3. * 函 數 名: TOUCH_PenInt 4. * 功能說明: 判斷觸摸按下 5. * 形 參: 無 6. * 返 回 值: 0表示無觸筆按下,1表示有觸筆按下 7. ****************************************************************************************************** 8. */ 9. uint8_t TOUCH_PenInt(void) 10. { 11. if ((TP_INT_GPIO_PORT->IDR & TP_INT_PIN) == 0) 12. { 13. return 1; 14. } 15. return 0; 16. } 17. 18. /* 19. ****************************************************************************************************** 20. * 函 數 名: STMPE811_ClearInt 21. * 功能說明: 清楚觸筆中斷 22. * 形 參: 無 23. * 返 回 值: 無 24. ****************************************************************************************************** 25. */ 26. void STMPE811_ClearInt(void) 27. { 28. STMPE811_WriteReg1(REG811_INT_STA, 0xFF); 29. } 30. 31. /* 32. ****************************************************************************************************** 33. * 函 數 名: STMPE811_ReadX 34. * 功能說明: 讀取X坐標adc 35. * 形 參: 無 36. * 返 回 值: X坐標值adc 37. ****************************************************************************************************** 38. */ 39. uint16_t STMPE811_ReadX(void) 40. { 41. /* 按照 XY 讀取模式,連續讀取3字節數據,然后分解出X,Y 42. | byte0 | byte1 | byte2 | 43. | X[11:4], | X[3:0],Y[11:8] | Y[7:0] | 44. */ 45. uint8_t buf[3]; 46. 47. #if 0 48. STMPE811_ReadBytes(buf, REG811_TSC_DATA1, 3); 49. 50. s_AdcX = ((uint16_t)buf[0] << 4) | (buf[1] >> 4); 51. s_AdcY = ((uint16_t)(buf[1] & 0xF) << 8) | buf[2]; 52. #else 53. if (STMPE811_ReadReg1(REG811_TSC_CTRL) & 0x80) 54. { 55. STMPE811_ReadBytes(buf, REG811_TSC_DATA1, 3); 56. 57. s_AdcX = ((uint16_t)buf[0] << 4) | (buf[1] >> 4); 58. s_AdcY = ((uint16_t)(buf[1] & 0xF) << 8) | buf[2]; 59. 60. #if 0 61. /* for debug */ 62. { 63. static int32_t s_t1 = 0; 64. int32_t tt; 65. 66. tt = bsp_GetRunTime(); 67. if (tt - s_t1 > 1000) 68. { 69. printf("\r\n"); 70. s_t1 = tt; 71. } 72. printf("(%7d) %5d %5d\r\n", tt, s_AdcX, s_AdcY); 73. } 74. #endif 75. } 76. else 77. { 78. s_AdcX = 0; 79. s_AdcY = 0; 80. } 81. #endif 82. 83. return s_AdcX; 84. } 85. 86. /* 87. ****************************************************************************************************** 88. * 函 數 名: STMPE811_ReadX 89. * 功能說明: 讀取Y坐標adc 90. * 形 參: 無 91. * 返 回 值: Y坐標值adc 92. ****************************************************************************************************** 93. */ 94. uint16_t STMPE811_ReadY(void) 95. { 96. return s_AdcY; 97. }
下面將程序設計中的關鍵地方做個闡釋:
- 第9-16行,通過判斷STMPE811的中斷輸出引腳的高低電平來判斷觸摸板是否被按下,如果有觸摸,這個引腳輸出低電平,反之,輸出高電平。通過判斷這個引腳就可以選擇是否讀取X軸,Y軸的ADC值,避免不必要的操作。
- 第26-29行,清除觸摸中斷標志,檢測到觸摸屏未被按下時,要做清除。
- 第39-84行,讀取X軸ADC數值。
- 第94-97行,讀取Y軸ADC數值。
54.5.2 電阻觸摸掃描函數TOUCH_Scan
接下來再來看bsp_touch.c文件中STMPE811觸摸掃描函數TOUCH_Scan的實現:
1. /* 2. ****************************************************************************************************** 3. * 函 數 名: TOUCH_Scan 4. * 功能說明: 觸摸板事件檢測程序。該函數被周期性調用,每ms調用1次. 見 bsp_Timer.c 5. * 形 參: 無 6. * 返 回 值: 無 7. ****************************************************************************************************** 8. */ 9. void TOUCH_Scan(void) 10. { 11. uint16_t usAdcX; 12. uint16_t usAdcY; 13. static uint16_t s_usXBuf[SAMPLE_COUNT]; 14. static uint16_t s_usYBuf[SAMPLE_COUNT]; 15. static uint8_t s_ucPos = 0; 16. static uint8_t s_count = 0; 17. static uint8_t s_down = 0; 18. static uint16_t s_usSaveAdcX, s_usSaveAdcY; /* 用於觸筆抬起事件,保存按下和移動的最后采樣值 */ 19. static uint8_t s_ms = 0; 20. 21. if (g_GT811.Enable == 1) 22. { 23. GT811_Timer1ms(); /* 電容觸摸屏程序計數器 */ 24. return; 25. } 26. 27. if (g_GT911.Enable == 1) 28. { 29. GT911_Timer1ms(); /* 電容觸摸屏程序計數器 */ 30. return; 31. } 32. 33. if (g_tFT5X06.Enable == 1) 34. { 35. FT5X06_Timer1ms(); /* 電容觸摸屏程序計數器 */ 36. return; 37. } 38. 39. /* 下面用於電阻觸摸 */ 40. 41. if (g_tTP.Enable == 0) 42. { 43. return; 44. } 45. 46. if (++s_ms >= 2) 47. { 48. return; 49. } 50. 51. /* 2ms進入一次 */ 52. s_ms = 0; 53. 54. /* 觸筆中斷發生 */ 55. if (TOUCH_PenInt()) 56. { 57. /* 獲得原始的ADC值,未濾波 */ 58. usAdcX = STMPE811_ReadX(); 59. usAdcY = STMPE811_ReadY(); 60. 61. if (TOUCH_PressValid(usAdcX, usAdcY)) 62. { 63. /* 按壓30ms之后才開始采集數據 */ 64. if (s_count >= DOWN_VALID / 2) 65. { 66. s_usXBuf[s_ucPos] = usAdcX; 67. s_usYBuf[s_ucPos] = usAdcY; 68. 69. /* 采集20ms數據進行濾波 */ 70. if (++s_ucPos >= SAMPLE_COUNT / 2) 71. { 72. s_ucPos = 0; 73. 74. /* 對ADC采樣值進行軟件濾波 */ 75. g_tTP.usAdcNowX = TOUCH_DataFilter(s_usXBuf, SAMPLE_COUNT / 2); 76. g_tTP.usAdcNowY = TOUCH_DataFilter(s_usYBuf, SAMPLE_COUNT / 2); 77. 78. if (s_down == 0) 79. { 80. s_down = 1; 81. /* 觸摸按下事件 */ 82. TOUCH_PutKey(TOUCH_DOWN, g_tTP.usAdcNowX, g_tTP.usAdcNowY); 83. 84. s_usSaveAdcX = g_tTP.usAdcNowX; 85. s_usSaveAdcY = g_tTP.usAdcNowY; 86. } 87. else 88. { 89. if (TOUCH_MoveValid(s_usSaveAdcX, s_usSaveAdcY, g_tTP.usAdcNowX, g_tTP.usAdcNowY)) 90. { 91. /* 觸摸移動事件 */ 92. TOUCH_PutKey(TOUCH_MOVE, g_tTP.usAdcNowX, g_tTP.usAdcNowY); 93. 94. s_usSaveAdcX = g_tTP.usAdcNowX; 95. s_usSaveAdcY = g_tTP.usAdcNowY; 96. } 97. else 98. { 99. g_tTP.usAdcNowX = 0; /* for debug stop */ 100. } 101. } 102. } 103. } 104. else 105. { 106. s_count++; 107. } 108. } 109. else 110. { 111. if (s_count > 0) 112. { 113. if (--s_count == 0) 114. { 115. /* 觸摸釋放事件 */ 116. //TOUCH_PutKey(TOUCH_RELEASE, g_tTP.usAdcNowX, g_tTP.usAdcNowY); 117. TOUCH_PutKey(TOUCH_RELEASE, s_usSaveAdcX, s_usSaveAdcY); 118. 119. g_tTP.usAdcNowX = 0; 120. g_tTP.usAdcNowY = 0; 121. 122. s_count = 0; 123. s_down = 0; 124. 125. STMPE811_ClearInt(); /* 清觸筆中斷標志 */ 126. } 127. } 128. s_ucPos = 0; 129. } 130. } 131. }
下面將程序設計中的關鍵地方做個闡釋:
- 第9行,此函數要每1ms被調用一次。
- 第21-37行,用於GT811,GT911和FT5X06的程序計數器。
- 第46-52行,設置每2ms進行一次STMPE811檢測。
- 第55行,這個就是本章前面小節說的利用STMPE811的中斷輸出引腳的高低電平來判斷觸摸板是否被按下。
- 第58-59行,讀取X軸ADC數值和Y軸ADC數值。
- 第61行,通過函數TOUCH_PressValid檢測剛剛讀取的X軸,Y軸數值是否在有效的范圍內。
函數TOUCH_PressValid的具體實現如下,其中全局變量g_tTP.usMaxAdc = 4095,因為電阻觸摸芯片STMPE811是12位ADC,最大觸摸值就是2^12 – 1 = 4095。
/* 有效ADC值的判斷門限. 太接近ADC臨界值的坐標認為無效 */ #define ADC_VALID_OFFSET 2 /* ********************************************************************************************************* * 函 數 名: TOUCH_PressValid * 功能說明: 判斷按壓是否有效,根據X, Y的ADC值進行大致判斷 * 形 參: 無 * 返 回 值: 1 表示有效; 0 表示無效 ********************************************************************************************************* */ static uint8_t TOUCH_PressValid(uint16_t _usX, uint16_t _usY) { if ((_usX <= ADC_VALID_OFFSET) || (_usY <= ADC_VALID_OFFSET) || (_usX >= g_tTP.usMaxAdc - ADC_VALID_OFFSET) || (_usY >= g_tTP.usMaxAdc - ADC_VALID_OFFSET)) { return 0; } else { return 1; } }
- 第64行,DOWN_VALID的宏定義是
#define DOWN_VALID 30
由於是每2ms進行一次檢測,這里就表示延遲30ms后進行觸摸數據采集。延遲30ms是為了消除觸摸抖動。
- 第70行,SAMPLE_COUNT 的宏定義是
#define SAMPLE_COUNT 20
由於是每2ms進行一次檢測,這里就表示采集夠10組數據,即20ms后進行下一步操作。
- 第75-76行,對X軸和Y軸的ADC數值都進行軟件濾波。軟件濾波函數TOUCH_DataFilter的實現方法是對10組數值由小到大進行排序,對第3個,第4個和第5個數值求和,然后求平均,將平均值作為最終的ADC數值。
- 第78-86行,變量標識s_down = 0表示觸摸之前是未按下狀態,在此條件里面設置s_down = 1表示觸摸已經按下,並通過函數TOUCH_TransX(這個函數比較關鍵,是通過觸摸校准函數得到的一個線性關系)將當前的X軸和Y軸ADC數值轉換成實際的坐標值,然后調用函數TOUCH_PutKey將當前的坐標信息存儲到FIFO里面。
- 第89-100行設置變量標識s_down = 1后會進入此條件里面,在這個條件里面通過函數TOUCH_MoveValid判斷當前是否是有效的移動,如果是,就繼續調用函數TOUCH_PutKey將當前的坐標信息存儲到FIFO里面,如果不是,就設置全局變量g_tTP.usAdcNowX = 0。
- 第111-128行,如果通過STMPE811的中斷輸出引腳檢測到觸摸未按下,然后判斷變量s_count是否大於0,如果大於0的話,做減減運算,算是做了一個松手延遲,防止抖動。減到0的時候,將觸摸未按下或者說觸摸釋放消息通過函數TOUCH_PutKey存儲到FIFO里面。
54.5.3 電阻屏觸摸校准原理(2點)
由於不同電阻觸摸板的線性度參差不齊,不能直接采用比例關系將電阻觸摸芯片STMPE811的返回
值轉換成實際的坐標。比如我們操作的顯示屏分辨率是800*480,電阻觸摸芯片采用STMPE811(12位ADC,觸摸值范圍0-4095),獲得當前的觸摸值是(1024, 2048),按照比例關系轉換成坐標值就是(1024*800/4096,2048*800/4096),即(200,400)。采用這種方法效果不好,容易出現點擊不准確的問題。
鑒於此原因,需要通過觸摸校准在ADC數值和顯示屏分辨率之間建立一個新的線性關系,簡單的說就是由比例關系y = ax升級為y = ax + b。如果有了新的觸摸ADC數值,代入這個線性關系里面就可以得到當前實際的坐標值,觸摸校准的作用就在這里了。
具體實現原理圖如下:
在左上角和右下角分別設置兩個坐標點(LcdX0, LcdY0)和(LcdX1, LcdY1),然后讓用戶去點擊,會得到兩組ADC數值(AdcX0,AdcY0)和(AdcX1, AdcY1)。
根據這四個坐標點,可以建立兩組方程,一個X軸的,一個Y軸。
- 將數值(AdcX0, LcdX0)和(AdcX1, LcdX1)代入方程y = ax + b得到X軸方程 :
y = (x - AdcX0)*(LcdX1 - LcdX0)/(AdcX1 - AdcX0) + LcdX0
- 將數值(AdcY0, LcdY0)和(AdcY1, LcdY1)代入方程y = ax + b得到Y軸方程
y = (x - AdcY0)*(LcdY1 - LcdY0)/(AdcY1 - AdcX0) + LcdY0
后面采集到的ADC數值直接代入上面公式就可以得到校准后的物理坐標值(實際的分辨率坐標)。
54.5.4 電阻屏觸摸校准原理(4點)
4點觸摸校准實現,略復雜,實現原理如下(如果理解起來麻煩的話,會用就行,一般情況下2點校准就行):
在LCD的左上角,右上角,左下角和右下角分別標坐標點(LcdX1, LcdY1),(LcdX4, LcdY4),(LcdX3, LcdY3)和(LcdX2, LcdY2)。然后讓用戶去點擊,會得到四組ADC數值(AdcX1, AdcY1),(AdcX4, AdcY4),(AdcX3, AdcY3)和(AdcX2, AdcY2)。
計算X軸:
- 將數值(AdcX1,AdcY1)和(AdcX2, AdcY2)代入方程y = ax + b得到一組方程
y = (x - AdcX1)*(AdcY2- AdcY1)/(AdcX2- AdcX1) + AdcY1
- 這里將AdcX2用AdcX3替換,那么坐標方程就變為
y = (x - AdcX1)*(AdcY2- AdcY1)/(AdcX3- AdcX1) + AdcY1。
- 同理,將AdcX1用AdcX4替換,那么坐標方程就變為
y = (x - AdcX4)*(AdcY2- AdcY1)/(AdcX3- AdcX4) + AdcY1
那么將采集的X值代入上面兩個方程會得到兩個數值,假設數值是x1和x2。
- 再將(x1, LcdX1))和(x2, LcdX2)代入方程y = ax + b得到一組方程
y = (x - x1)*(LcdX2- LcdX1)/(x2- x1) + LcdX1
將采集的X軸ADC數值再次代入這個方程就得到了最終的物理坐標(實際的分辨率坐標)。
計算Y軸:
- 將數值(AdcX1, AdcY1)和(AdcX2, AdcY2)代入方程y = ax + b得到一組方程
y = (x - AdcX1)*(AdcY2- AdcY1)/(AdcX2- AdcX1) + AdcY1
- 這里將AdcY2用AdcY4替換,那么坐標方程就變為
y = (x - AdcX1)*(AdcY4- AdcY1)/(AdcX2- AdcX1) + AdcY1
- 同理,將AdcX1用AdcX3替換,那么坐標方程就變為
y = (x - AdcX3)*(AdcY2- AdcY1)/(AdcX2- AdcX3) + AdcY1
那么將采集的X值代入上面兩個方程會得到兩個數值,假設數值是x1和x2。
- 再將(x1, LcdY1))和(x2, LcdY2)代入方程y = ax + b得到一組方程
y = (x - x1)*(LcdY2- LcdY1)/(x2- x1) + LcdY1
將采集的Y軸ADC數值再次代入這個方程就得到了最終的物理坐標(實際的分辨率坐標)。
54.5.5 電阻屏觸摸校准的實現
對2點和4點觸摸校准原理有所了解后,再看代碼部分就比較好理解了:
1. /* 2. ****************************************************************************************************** 3. * 函 數 名: TOUCH_Calibration 4. * 功能說明: 觸摸屏校准 5. * 形 參: _PointCount : 校准點數,2 或 4. 6. * 返 回 值: 無 7. ****************************************************************************************************** 8. */ 9. void TOUCH_Calibration(uint8_t _PointCount) 10. { 11. uint16_t usAdcX; 12. uint16_t usAdcY; 13. uint8_t usCount; 14. uint8_t I; 15. uint32_t n; 16. 17. /* 校准點數,2點或4點 */ 18. if (_PointCount == 4) 19. { 20. g_tTPParam.CalibPointCount = 4; 21. } 22. else 23. { 24. g_tTPParam.CalibPointCount = 2; 25. } 26. 27. TOUCH_CelarFIFO(); /* 清除無效的觸摸事件 */ 28. 29. for (I = 0; I < g_tTPParam.CalibPointCount; i++) 30. { 31. TOUCH_DispPoint(i); /* 顯示校准點 */ 32. 33. TOUCH_WaitRelease(); /* 等待觸筆釋放 */ 34. 35. usCount = 0; 36. for (n = 0; n < 500; n++) 37. { 38. usAdcX = TOUCH_ReadAdcX(); 39. usAdcY = TOUCH_ReadAdcY(); 40. 41. if (TOUCH_PressValid(usAdcX, usAdcY)) 42. { 43. if (++usCount > 5) 44. { 45. /* 按壓有效, 保存校准點ADC采樣值 */ 46. if (I == 0) 47. { 48. g_tTPParam.usAdcX1 = usAdcX; 49. g_tTPParam.usAdcY1 = usAdcY; 50. } 51. else if (I == 1) 52. { 53. g_tTPParam.usAdcX2 = usAdcX; 54. g_tTPParam.usAdcY2 = usAdcY; 55. } 56. else if (I == 2) 57. { 58. g_tTPParam.usAdcX3 = usAdcX; 59. g_tTPParam.usAdcY3 = usAdcY; 60. } 61. else 62. { 63. g_tTPParam.usAdcX4 = usAdcX; 64. g_tTPParam.usAdcY4 = usAdcY; 65. } 66. break; 67. } 68. } 69. else 70. { 71. usCount = 0; 72. } 73. bsp_DelayMS(10); 74. } 75. if (n == 500) 76. { 77. return; 78. } 79. } 80. 81. TOUCH_WaitRelease(); /* 等待觸筆釋放 */ 82. 83. /* 識別觸摸的 X, Y 和 顯示面板的 X,Y 是否需要交換 */ 84. g_tTPParam.XYChange = 0; /* 1表示X Y需要交換 */ 85. if (LCD_GetHeight() < LCD_GetWidth()) 86. { 87. if (TOUCH_Abs(g_tTPParam.usAdcX1 – g_tTPParam.usAdcX2) < 88. TOUCH_Abs(g_tTPParam.usAdcY1 – g_tTPParam.usAdcY2)) 89. { 90. g_tTPParam.XYChange = 1; 91. } 92. } 93. else 94. { 95. if (TOUCH_Abs(g_tTPParam.usAdcX1 – g_tTPParam.usAdcX2) > 96. TOUCH_Abs(g_tTPParam.usAdcY1 – g_tTPParam.usAdcY2)) 97. { 98. g_tTPParam.XYChange = 1; 99. } 100. } 101. 102. g_tTPParam.usLcdX1 = TP_X1; 103. g_tTPParam.usLcdY1 = TP_Y1; 104. g_tTPParam.usLcdX2 = TP_X2; 105. g_tTPParam.usLcdY2 = TP_Y2; 106. g_tTPParam.usLcdX3 = TP_X3; 107. g_tTPParam.usLcdY3 = TP_Y3; 108. g_tTPParam.usLcdX4 = TP_X4; 109. g_tTPParam.usLcdY4 = TP_Y4; 110. 111. /* 在最后一步,將校准參數保存入Flash 或者EEPROM */ 112. TOUCH_SaveParam(); 113. }
下面將程序設置中的關鍵地方做個闡釋:
- 第18-25行,用於標記是4點觸摸校准還是2點觸摸校准。
- 第31行,顯示觸摸校准點,2點觸摸校准的話,顯示左上角和右下角的校准點位置。4點觸摸校准的話,顯示左上角,右上角,左下角和右下角的校准點位置。
- 第33行,用於等待觸摸筆釋放,當校准完畢1個點后,等待釋放時使用。
- 第35-79行,讀取500次X軸和Y軸的ADC數值,每10ms讀取1次,每個觸摸點的最大讀取時間就是5秒。如果5秒內還沒有觸摸就會進入到第77行直接退出觸摸校准。
第41行檢測到有按下的話,會連續讀取5次,確保已經按下了,然后標記本次按下得到的ADC數值。根據執行的是4點觸摸校准還是2點觸摸校准,這個操作會執行4次或者2次。
- 第84-100行,這里涉及到一個知識點,即X軸鏡像,Y軸鏡像和XY交換的處理辦法,詳情在此貼進行了說明:http://www.armbbs.cn/forum.php?mod=viewthread&tid=93300 。
- 第112行,將校准后的參數存儲到EEPROM里面,下次開發板上電可以直接從EEPROM里面讀取校准參數。
54.5.6 電阻屏觸摸ADC值轉物理坐標
電阻屏觸摸ADC值轉物理坐標的公式就是由前面5.4和5.6小節而來。
1. /* 2. ****************************************************************************************************** 3. * 函 數 名: CalTwoPoint 4. * 功能說明: 根據2點直線方程,計算Y值 5. * 形 參: 2個點的坐標和x輸入量 6. * 返 回 值: x對應的y值 7. ****************************************************************************************************** 8. */ 9. static int32_t CalTwoPoint(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x) 10. { 11. return y1 + ((int32_t)(y2 - y1) * (x - x1)) / (x2 - x1); 12. } 13. 14. /* 15. ****************************************************************************************************** 16. * 函 數 名: TOUCH_TransX 17. * 功能說明: 將觸摸ADC值轉換為像素坐標 18. * 形 參: 無 19. * 返 回 值: X 坐標值,允許負值 20. ****************************************************************************************************** 21. */ 22. static int16_t TOUCH_TransX(uint16_t _usAdcX, uint16_t _usAdcY) 23. { 24. if (g_tTPParam.CalibPointCount == 2) 25. { 26. uint16_t x; 27. int32_t y; 28. 29. if (g_tTPParam.XYChange == 0) 30. { 31. x = _usAdcX; 32. if (x == 0) 33. { 34. y = 0; 35. } 36. else 37. { 38. //y = CalTwoPoint(g_tTPParam.usAdcX1, TP_X1, g_tTPParam.usAdcX2, TP_X2, x); 39. y = CalTwoPoint(g_tTPParam.usAdcX1, g_tTPParam.usLcdX1, g_tTPParam.usAdcX2, 40. g_tTPParam.usLcdX2, x); 41. } 42. } 43. else 44. { 45. x = _usAdcY; 46. if (x == 0) 47. { 48. y = 0; 49. } 50. else 51. { 52. //y = CalTwoPoint(g_tTPParam.usAdcY1, TP_X1, g_tTPParam.usAdcY2, TP_X2, x); 53. y = CalTwoPoint(g_tTPParam.usAdcY1, g_tTPParam.usLcdX1, g_tTPParam.usAdcY2, 54. g_tTPParam.usLcdX2, x); 55. } 56. } 57. return y; 58. } 59. else /* 4點校准 */ 60. { 61. uint16_t x, x1, x2; 62. int32_t y; 63. 64. if (g_tTPParam.XYChange == 0) /* X Y 坐標不交換 */ 65. { 66. x = _usAdcX; 67. 68. /* 根據 Y ADC 實時計算直線方程的參考點x1, x2 69. if _usAdcY = usAdcY1 then 取點 = (AdcX1, TP_X1, AdcX4, TP_X4, _usAdcY) 70. if _usAdcY = usAdcY2 then 取點 = (AdcX3, TP_X3, AdcX2, TP_X2, _usAdcY) 71. 72. 其中 TP_X1 = TP_X3; TP_X4 = TP_X1 , 這是程序設定的校准位置的像素坐標, 是固定的。 73. 我們僅需要動態計算對第1個和第3個參數。同樣采用2點直線方程計算。 74. */ 75. x1 = CalTwoPoint(g_tTPParam.usAdcY1, g_tTPParam.usAdcX1, g_tTPParam.usAdcY2, 76. g_tTPParam.usAdcX3, _usAdcY); 77. x2 = CalTwoPoint(g_tTPParam.usAdcY1, g_tTPParam.usAdcX4, g_tTPParam.usAdcY2, 78. g_tTPParam.usAdcX2, _usAdcY); 79. } 80. else /* X Y 坐標交換 */ 81. { 82. x = _usAdcY; 83. 84. /* 根據 X ADC 實時計算直線方程的參考點x1, x2 85. if _usAdcX = usAdcX1 then 取點 = (AdcY1, TP_X1, AdcY4, TP_X4, _usAdcX) 86. if _usAdcX = usAdcX2 then 取點 = (AdcY3, TP_X3, AdcY2, TP_X2, _usAdcX) 87. 88. 其中 TP_X1 = TP_X3; TP_X4 = TP_X1 , 這是程序設定的校准位置的像素坐標, 是固定的。 89. 我們僅需要動態計算對第1個和第3個參數。同樣采用2點直線方程計算。 90. */ 91. x1 = CalTwoPoint(g_tTPParam.usAdcX1, g_tTPParam.usAdcY1, g_tTPParam.usAdcX2, 92. g_tTPParam.usAdcY3, _usAdcX); 93. x2 = CalTwoPoint(g_tTPParam.usAdcX1, g_tTPParam.usAdcY4, g_tTPParam.usAdcX2, 94. g_tTPParam.usAdcY2, _usAdcX); 95. } 96. 97. if (x == 0) 98. { 99. y = 0; 100. } 101. else 102. { 103. /* 根據2點直線方程,計算坐標 */ 104. //y = CalTwoPoint(x1, TP_X1, x2, TP_X2, x); 105. y = CalTwoPoint(x1, g_tTPParam.usLcdX1, x2, g_tTPParam.usLcdX2, x); 106. } 107. return y; 108. } 109. } 110. 111. /* 112. ****************************************************************************************************** 113. * 函 數 名: TOUCH_TransY 114. * 功能說明: 將觸摸ADC值轉換為像素坐標 115. * 形 參: 無 116. * 返 回 值: Y 坐標值,允許負值 117. ****************************************************************************************************** 118. */ 119. static int16_t TOUCH_TransY(uint16_t _usAdcX, uint16_t _usAdcY) 120. { 121. if (g_tTPParam.CalibPointCount == 2) /* 2點校准 */ 122. { 123. /* 類似函數TOUCH_TransX,省略未貼出 */ 124. } 125. else /* 4點校准 */ 126. { 127. /* 類似函數TOUCH_TransX,省略未貼出 */ 128. } 129. }
下面將程序設計中幾個關鍵地方做個闡釋:
- 第9-12行,y =ax+b類型的直線方程,根據前四個參數輸入的兩個坐標點可以確定一條直線,然后輸入第5個參數x,可以得到此坐標點對應的y值。
- 第22行,函數TOUCH_TransX的作用是將X軸采集的ADC值轉換為物理坐標值。
- 第24-58行,用於處理2點觸摸校准,對應的公式就是本章5.4小節的內容。
- 第59-108行,用於處理4點觸摸校准,對應的公式就是本章5.5小節的內容。
- 第119-129行,函數TOUCH_TransY的作用是將Y軸采集的ADC值轉換為物理坐標值。
這里注意g_tTPParam.XYChange = 1情況的處理,之所以會有這種情況,詳情看此貼:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=93300 。
54.5.7 電阻觸摸的使用方法
電阻觸摸的使用主要分為三步:
- 第1步,使能觸摸,在bsp.c文件的函數bsp_Init里面實現。
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* 省略未寫 */ bsp_InitI2C(); /* 初始化I2C總線 */ TOUCH_InitHard(); /* 初始化觸摸芯片,LCD面板型號的檢查也在此函數,所以要在函數LCD_InitHard前調用 */ LCD_InitHard(); /* 初始化LCD */ }
- 第2步,每毫秒調用1次觸摸掃描。
/* ********************************************************************************************************* * 函 數 名: bsp_RunPer1ms * 功能說明: 該函數每隔1ms被Systick中斷調用1次。詳見 bsp_timer.c的定時中斷服務程序。一些需要周期性處理 * 的事務可以放在此函數。比如:觸摸坐標掃描。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_RunPer1ms(void) { TOUCH_Scan(); /* 觸摸屏 */ }
- 第3步,用戶程序里面采用下面的框架進行觸摸消息處理。
TOUCH_DOWN表示按下消息。
TOUCH_MOVE表示觸摸移動消息。
TOUCH_RELEASE表示觸摸釋放消息。
根據這幾個消息,用戶可以在程序里面判斷當前獲取的物理坐標值是否在設置的區域內來執行觸摸操作。
int16_t tpX, tpY; uint8_t ucTouch; /* 觸摸事件 */ ucTouch = TOUCH_GetKey(&tpX, &tpY); /* 讀取觸摸事件 */ if (ucTouch != TOUCH_NONE) { switch (ucTouch) { case TOUCH_DOWN: /* 觸筆按下事件 */ /* 在觸筆所在位置顯示一個小圈 */ if ((tpX > 0) && (tpY > 0)) { } break; case TOUCH_MOVE: /* 觸筆移動事件 */ /* 實時刷新觸摸ADC采樣值和轉換后的坐標 */ { /* 在觸筆所在位置顯示一個小圈 */ if ((tpX > 0) && (tpY > 0)) { } } break; case TOUCH_RELEASE: /* 觸筆釋放事件 */ /* 在觸筆所在位置顯示一個小圈 */ if ((tpX > 0) && (tpY > 0)) { } break; } }
54.6 電容觸摸驅動設計
電容觸摸相比電阻觸摸就要簡單很多了,因為電容觸摸不需要做觸摸校准,而且用的是觸摸板和觸摸芯片一體的,也不需要做寄存器初始化配置,上電后直接讀取參數即可。
由於GT811已經停產,這里重點把GT911和FT5X06做個說明。
54.6.1 電容屏觸摸IC---FT5X06
電容觸摸IC是支持多點觸摸的,FT5X06支持多達10點觸摸同時按下,並提供了I2C和SPI兩種
通信接口方式,開發板使用的是I2C通信接口。更多相關知識學習可以在這里下載FT5X06數據手冊和應用手冊:http://bbs.armfly.com/read.php?tid=16461 。
注意,這個芯片返回的就是實際的坐標值,比如顯示屏的分辨率是800*480,那么返回的就是在這個分辨率范圍內的實際坐標,然后通過函數TOUCH_PutKey將FT5X06讀出的實際坐標值存儲到FIFO中即可,具體實現代碼如下:
1. /* 2. ****************************************************************************************************** 3. * 函 數 名: FT5X06_Scan 4. * 功能說明: 讀取觸摸數據。讀取全部的數據。放在主程序 bsp_Idle()中執行 5. * 形 參: 無 6. * 返 回 值: 無 7. ****************************************************************************************************** 8. */ 9. void FT5X06_Scan(void) 10. { 11. uint8_t buf[CFG_POINT_READ_BUF]; 12. uint8_t i; 13. static uint8_t s_tp_down = 0; 14. uint16_t x, y; 15. static uint16_t x_save, y_save; 16. static uint8_t s_count = 0; 17. 18. if (g_tFT5X06.Enable == 0) 19. { 20. return; 21. } 22. 23. /* 10ms 執行一次 */ 24. if (g_tFT5X06.TimerCount < 10) 25. { 26. return; 27. } 28. 29. g_tFT5X06.TimerCount = 0; 30. 31. #if 1 /* 方案1: 檢測INT引腳電平. */ 32. if (TOUCH_PenInt() == 0) 33. { 34. #else /* 方案2:不用INT引腳,讀狀態寄存器 */ 35. FT5X06_ReadReg(2, buf, 1); 36. if ((buf[0] & 0x07) == 0) 37. { 38. #endif 39. /* 持續按下時,INT電平是脈沖信號。每隔18ms出現1個寬度4ms的高電平。 */ 40. if (s_tp_down == 1) 41. { 42. if (++s_count > 2) 43. { 44. s_count = 0; 45. s_tp_down = 0; 46. TOUCH_PutKey(TOUCH_RELEASE, x_save, y_save); 47. } 48. } 49. return; 50. } 51. s_count = 0; 52. 53. /* 有觸摸,讀取完整的數據 */ 54. FT5X06_ReadReg(0, buf, CFG_POINT_READ_BUF); 55. 56. g_tFT5X06.Count = buf[2] & 0x07; 57. if (g_tFT5X06.Count > FT5X06_TOUCH_POINTS) 58. { 59. g_tFT5X06.Count = FT5X06_TOUCH_POINTS; 60. } 61. 62. g_tFT5X06.Count = 0; 63. for (i = 0; i < FT5X06_TOUCH_POINTS; i++) 64. { 65. uint8_t pointid; 66. 67. pointid = (buf[5 + 6*i]) >> 4; 68. if (pointid >= 0x0f) 69. { 70. break; 71. } 72. else 73. { 74. g_tFT5X06.Count++; 75. g_tFT5X06.X[i] = (int16_t)(buf[3 + 6*i] & 0x0F)<<8 | (int16_t)buf[4 + 6*i]; 76. g_tFT5X06.Y[i] = (int16_t)(buf[5 + 6*i] & 0x0F)<<8 | (int16_t)buf[6 + 6*i]; 77. g_tFT5X06.Event[i] = buf[0x3 + 6*i] >> 6; 78. g_tFT5X06.id[i] = (buf[5 + 6*i])>>4; 79. } 80. } 81. 82. /* 檢測按下 */ 83. { 84. if (g_tFT5X06.ChipID == 0x55) /* 4.3寸 480 * 272 */ 85. { 86. x = g_tFT5X06.Y[0]; 87. y = g_tFT5X06.X[0]; 88. 89. /* 判斷值域 */ 90. if (x > 479) 91. { 92. x = 479; 93. } 94. 95. if (y > 271) 96. { 97. y = 271; 98. } 99. } 100. else if (g_tFT5X06.ChipID == 0x0A) /* 5.0寸 800 * 480 */ 101. { 102. x = g_tFT5X06.X[0]; 103. y = g_tFT5X06.Y[0]; 104. 105. /* 判斷值域 */ 106. if (x > 799) 107. { 108. x = 799; 109. } 110. if (y > 479) 111. { 112. y = 479; 113. } 114. } 115. else /* id == 0x06 表示7寸電容屏(FT芯片) */ 116. { 117. x = g_tFT5X06.X[0]; 118. y = g_tFT5X06.Y[0]; 119. 120. /* 判斷值域 */ 121. if (x > 799) 122. { 123. x = 799; 124. } 125. if (y > 479) 126. { 127. y = 479; 128. } 129. } 130. } 131. 132. if (s_tp_down == 0) 133. { 134. s_tp_down = 1; 135. 136. TOUCH_PutKey(TOUCH_DOWN, x, y); 137. } 138. else 139. { 140. TOUCH_PutKey(TOUCH_MOVE, x, y); 141. } 142. x_save = x; /* 保存坐標,用於釋放事件 */ 143. y_save = y; 144. 145. #if 0 146. for (i = 0; i < CFG_POINT_READ_BUF; i++) 147. { 148. printf("%02X ", buf[i]); 149. } 150. printf("\r\n"); 151. #endif 152. 153. #if 0 /* 打印5個坐標點數據 */ 154. printf("(%5d,%5d,%3d,%3d) ", g_tFT5X06.X[0], g_tFT5X06.Y[0], g_tFT5X06.Event[0], 155. g_tFT5X06.id[0]); 156. printf("(%5d,%5d,%3d,%3d) ", g_tFT5X06.X[1], g_tFT5X06.Y[1], g_tFT5X06.Event[1], 157. g_tFT5X06.id[1]); 158. printf("(%5d,%5d,%3d,%3d) ", g_tFT5X06.X[2], g_tFT5X06.Y[2], g_tFT5X06.Event[2], 159. g_tFT5X06.id[2]); 160. printf("(%5d,%5d,%3d,%3d) ", g_tFT5X06.X[3], g_tFT5X06.Y[3], g_tFT5X06.Event[3], 161. g_tFT5X06.id[3]); 162. printf("(%5d,%5d,%3d,%3d) ", g_tFT5X06.X[4], g_tFT5X06.Y[4], g_tFT5X06.Event[4], 163. g_tFT5X06.id[4]); 164. printf("\r\n"); 165. #endif 166. }
下面將程序設計中幾個關鍵地方做個闡釋:
- 第32行,通過判斷FT5X06的中斷輸出引腳的高低電平來判斷觸摸板是否被按下,如果有觸摸,這個引腳輸出低電平,反之,輸出高電平。通過判斷這個引腳可以避免不必要的操作。
- 第35-36行,從寄存器2讀取一個數據,判斷是否有觸摸數據,這種方式就沒有直接判斷中斷引腳方便。
- 第40-50行,如果沒有觸摸數據,而且變量標志s_tp_down = 1(此變量等於1表示之前處於觸摸按下或者移動狀態,如果等於0表示之前處於未被觸摸狀態),那么此時要通過函數TOUCH_PutKey存儲松手消息,這一步比較重要,切不可省略。如果變量標志s_tp_down = 0,直接退出即可。另外,特別注意一點,這里是通過s_count變量連續記錄到兩次松手消息才執行。
- 第50-80行,如果有觸摸,將所有觸摸值全部記錄下來(由於支持多點觸摸,會有多個觸摸值)。
- 第83-130行,根據不同的分辨率的顯示屏,做值域范圍處理,防止超出范圍。
- 第132-137行,如果變量s_tp_down = 0表示之前處於未被觸摸狀態,這里設置此變量為1,並通過函數TOUCH_PutKey存儲按下消息和當前的坐標值。
- 第138-141行,如果變量不等於0(其實這里也就是1)表示之前處於按下狀態,此時觸摸處於移動狀態,這里不用重復設置此變量了,但通過函數TOUCH_PutKey存儲按下消息和當前的坐標值。
- 第153-165行,這里預留的條件編譯主要是方便調試階段使用。
54.6.2 電容屏觸摸IC---GT911
GT911支持多達5點觸摸同時按下,並提供了I2C通信接口方式,更多相關知識學習可以在這里下載GT911數據手冊:http://www.armbbs.cn/forum.php?mod=viewthread&tid=87205 。
GT911的使用基本跟FT5X06是一樣(注意,芯片GT911返回的就是實際的坐標值,比如顯示屏的分辨率是800*480,那么返回的就是在這個分辨率范圍內的實際坐標,跟FT5X06也是一樣的),具體實現代碼如下(這里沒有使用多點觸摸功能,僅存了一個觸摸值,一般情況下已經夠用):
1. /* 2. ****************************************************************************************************** 3. * 函 數 名: GT911_Scan 4. * 功能說明: 讀取GT911觸摸數據。讀取全部的數據,需要 720us左右。放在 bsp_Idle()中執行 5. * 形 參: 無 6. * 返 回 值: 無 7. ****************************************************************************************************** 8. */ 9. void GT911_Scan(void) 10. { 11. uint8_t buf[48]; 12. static uint8_t s_tp_down = 0; 13. uint16_t x, y; 14. static uint16_t x_save, y_save; 15. uint8_t clear_flag = 0; 16. static uint8_t s_count = 0; 17. 18. if (g_GT911.Enable == 0) 19. { 20. return; 21. } 22. 23. /* 10ms 執行一次 */ 24. if (g_GT911.TimerCount < 10) 25. { 26. return; 27. } 28. 29. g_GT911.TimerCount = 0; 30. 31. #if 1 /* 方案1: 檢測INT引腳電平. */ 32. if (TOUCH_PenInt() == 0) 33. { 34. #else /* 方案2:不用INT引腳,讀狀態寄存器 */ 35. GT911_ReadReg(GT911_READ_XY_REG, buf, 1); 36. if (buf[0] == 0) 37. { 38. #endif 39. if (s_tp_down == 1) 40. { 41. if (++s_count > 1) 42. { 43. s_count = 0; 44. s_tp_down = 0; 45. TOUCH_PutKey(TOUCH_RELEASE, x_save, y_save); 46. } 47. } 48. return; 49. } 50. s_count = 0; 51. 52. #if 1 /* 一般應用只讀1點 */ 53. GT911_ReadReg(GT911_READ_XY_REG, buf, 8); 54. #else /* 讀5個觸摸點 */ 55. GT911_ReadReg(GT911_READ_XY_REG, buf, 40); 56. #endif 57. 58. GT911_WriteReg(GT911_READ_XY_REG, &clear_flag, 1); /* 讀完坐標后必須寫0清除 */ 59. 60. /* 61. 0x814E R/W Bufferstatus Large_Detect number of touch points 62. Bit7: Buffer status,1表示坐標(或按鍵)已經准備好,主控可以讀取;0表示未就緒,數據無效。 63. 當主控讀取完坐標后,必須通過I2C將此標志(或整個字節)寫為0。 64. Bit4: HaveKey, 1表示有按鍵,0表示無按鍵(已經松鍵)。 65. Bit3~0: Number of touch points, 屏上的坐標點個數 66. 67. 0x814F R Point1 track id 68. 0x8150 R Point1Xl 觸摸點 1,X 坐標低 8 位 69. 0x8151 R Point1Xh 觸摸點 1,X 坐標高 8 位 70. 0x8152 R Point1Yl 觸摸點 1,Y 坐標低 8 位 71. 0x8153 R Point1Yh 觸摸點 1,Y 坐標高 8 位 72. 0x8154 R Point1 觸摸點 1,觸摸面積低 8 位 73. 0x8155 R Point1 觸摸點 1,觸摸面積高 8 位 74. 0x8156 ---- 75. 76. 0x8157 R Point2 track id 77. 0x8158 R Point2Xl 觸摸點 2,X 坐標低 8 位 78. 0x8159 R Point2Xh 觸摸點 2,X 坐標高 8 位 79. 0x815A R Point2Yl 觸摸點 2,Y 坐標低 8 位 80. 0x815B R Point2Yh 觸摸點 2,Y 坐標高 8 位 81. 0x815C R Point2 觸摸點 2,觸摸面積低 8 位 82. 0x815D R Point2 觸摸點 2,觸摸面積高 8 位 83. 0x815E ---- 84. 85. 0x815F R Point3 track id 86. 0x8160 R Point3Xl 觸摸點 3,X 坐標低 8 位 87. 0x8161 R Point3Xh 觸摸點 3,X 坐標高 8 位 88. 0x8162 R Point3Yl 觸摸點 3,Y 坐標低 8 位 89. 0x8163 R Point3Yh 觸摸點 3,Y 坐標高 8 位 90. 0x8164 R Point3 觸摸點 3,觸摸面積低 8 位 91. 0x8165 R Point3 觸摸點 3,觸摸面積高 8 位 92. 0x8166 ---- 93. 94. 0x8167 R Point4 track id 95. 0x8168 R Point4Xl 觸摸點 4,X 坐標低 8 位 96. 0x8169 R Point4Xh 觸摸點 4,X 坐標高 8 位 97. 0x816A R Point4Yl 觸摸點 4,Y 坐標低 8 位 98. 0x816B R Point4Yh 觸摸點 4,Y 坐標高 8 位 99. 0x816C R Point4 觸摸點 4,觸摸面積低 8 位 100. 0x816D R Point4 觸摸點 4,觸摸面積高 8 位 101. 0x816E ---- 102. 103. 0x816F R Point5 track id 104. 0x8170 R Point5Xl 觸摸點 5,X 坐標低 8 位 105. 0x8171 R Point5Xh 觸摸點 5,X 坐標高 8 位 106. 0x8172 R Point5Yl 觸摸點 5,Y 坐標低 8 位 107. 0x8173 R Point5Yh 觸摸點 5,Y 坐標高 8 位 108. 0x8174 R Point5 觸摸點 5,觸摸面積低 8 位 109. 0x8175 R Point5 觸摸點 5,觸摸面積高 8 位 110. 0x8176 -- 111. 112. */ 113. g_GT911.TouchpointFlag = buf[0]; 114. g_GT911.Touchkeystate = buf[1]; 115. 116. g_GT911.X0 = ((uint16_t)buf[3] << 8) + buf[2]; 117. g_GT911.Y0 = ((uint16_t)buf[5] << 8) + buf[4]; 118. g_GT911.P0 = ((uint16_t)buf[7] << 8) + buf[6]; 119. 120. #if 0 /* 其余4點一般不用 */ 121. g_GT911.X1 = ((uint16_t)buf[9] << 8) + buf[10]; 122. g_GT911.Y1 = ((uint16_t)buf[11] << 8) + buf[12]; 123. g_GT911.P1 = ((uint16_t)buf[13] << 8) + buf[14]; 124. 125. g_GT911.X2 = ((uint16_t)buf[17] << 8) + buf[16]; 126. g_GT911.Y2 = ((uint16_t)buf[19] << 8) + buf[18]; 127. g_GT911.P2 = ((uint16_t)buf[21] << 8) + buf[20]; 128. 129. g_GT911.X3 = ((uint16_t)buf[24] << 8) + buf[23]; 130. g_GT911.Y3 = ((uint16_t)buf[26] << 8) + buf[25]; 131. g_GT911.P3 = ((uint16_t)buf[28] << 8) + buf[27]; 132. 133. g_GT911.X4 = ((uint16_t)buf[31] << 8) + buf[30]; 134. g_GT911.Y4 = ((uint16_t)buf[33] << 8) + buf[32]; 135. g_GT911.P4 = ((uint16_t)buf[35] << 8) + buf[34]; 136. #endif 137. 138. /* 檢測按下 */ 139. { 140. /* 坐標轉換 : 141. 電容觸摸板左下角是 (0,0); 右上角是 (479,799) 142. 需要轉到LCD的像素坐標 (左上角是 (0,0), 右下角是 (799,479) 143. */ 144. { 145. x = g_GT911.X0; 146. y = g_GT911.Y0; 147. 148. if (x > 799) 149. { 150. x = 799; 151. } 152. 153. if (y > 479) 154. { 155. y = 479; 156. } 157. } 158. } 159. 160. if (s_tp_down == 0) 161. { 162. s_tp_down = 1; 163. 164. TOUCH_PutKey(TOUCH_DOWN, x, y); 165. } 166. else 167. { 168. TOUCH_PutKey(TOUCH_MOVE, x, y); 169. } 170. x_save = x; /* 保存坐標,用於釋放事件 */ 171. y_save = y; 172. 173. #if 0 174. { 175. uint8_t i; 176. 177. for (i = 0; i < 34; i++) 178. { 179. printf("%02X ", buf[i]); 180. } 181. printf("\r\n"); 182. 183. printf("(%5d,%5d,%3d) ", g_GT911.X0, g_GT911.Y0, g_GT911.P0); 184. printf("(%5d,%5d,%3d) ", g_GT911.X1, g_GT911.Y1, g_GT911.P1); 185. printf("(%5d,%5d,%3d) ", g_GT911.X2, g_GT911.Y2, g_GT911.P2); 186. printf("(%5d,%5d,%3d) ", g_GT911.X3, g_GT911.Y3, g_GT911.P3); 187. printf("(%5d,%5d,%3d) ", x, y, g_GT911.P4); 188. printf("\r\n"); 189. } 190. #endif 191. }
下面將程序設計中幾個關鍵地方做個闡釋:
- 第18-21行,使能標志,在文件bsp_touch.c文件里面有一個函數bsp_DetectLcdType專門檢測不同的觸摸IC,如果是GT911的話,變量g_tFT5X06.Enable會被置1。
- 第24-27行,表示函數GT911_Scan每10ms執行1次。
- 第32行,通過判斷GT911的中斷輸出引腳的高低電平來判斷觸摸板是否被按下,如果有觸摸,這個引腳輸出低電平,反之,輸出高電平。通過判斷這個引腳可以避免不必要的操作。
- 第35-36行,從寄存器2讀取一個數據,判斷是否有觸摸數據,這種方式就沒有直接判斷中斷引腳方便。
- 第39-49行,如果沒有觸摸數據,而且變量標志s_tp_down = 1(此變量等於1表示之前處於觸摸按下或者移動狀態,如果等於0表示之前處於未被觸摸狀態),那么此時要通過函數TOUCH_PutKey存儲松手消息,這一步比較重要,切不可省略。如果變量標志s_tp_down = 0,直接退出即可。另外,特別注意一點,這里是通過s_count變量連續記錄到兩次松手消息才執行。
- 第52-136行,僅讀取一組。這里沒有使用多點觸摸功能,僅存了一個觸摸值,一般情況下已經夠用。
- 第138-158行,對讀取的值域范圍處理,防止超出范圍。
- 第160-165行,如果變量s_tp_down = 0表示之前處於未被觸摸狀態,這里設置此變量為1,並通過函數TOUCH_PutKey存儲按下消息和當前的坐標值。
- 第166-169行,如果變量不等於0(其實這里也就是1)表示之前處於按下狀態,此時觸摸處於移動狀態,這里不用重復設置此變量了,但通過函數TOUCH_PutKey存儲按下消息和當前的坐標值。
- 第173-190行,這里預留的條件編譯主要是方便調試階段使用。
54.6.3 電容觸摸的使用方法
電容觸摸的使用主要分為三步:
- 第1步,使能觸摸,在bsp.c文件的函數bsp_Init里面實現。
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* 省略未寫 */ bsp_InitI2C(); /* 初始化I2C總線 */ TOUCH_InitHard(); /* 初始化觸摸芯片,LCD面板型號的檢查也在此函數,所以要在函數LCD_InitHard前調用 */ LCD_InitHard(); /* 初始化LCD */ }
- 第2步,每毫秒調用1次觸摸掃描。對於電容屏來說,這個函數主要提供了一個計數器功能。並且需要用戶在主任務里面一直調用函數bsp_Idle,這個函數主要實現電容觸摸屏掃描。
/* ********************************************************************************************************* * 函 數 名: bsp_RunPer1ms * 功能說明: 該函數每隔1ms被Systick中斷調用1次。詳見 bsp_timer.c的定時中斷服務程序。一些需要周期性處理 * 的事務可以放在此函數。比如:觸摸坐標掃描。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_RunPer1ms(void) { TOUCH_Scan(); /* 觸摸屏 */ } /* ********************************************************************************************************* * 函 數 名: bsp_Idle * 功能說明: 空閑時執行的函數。一般主程序在for和while循環程序體中需要插入 CPU_IDLE() 宏來調用本函數。 * 本函數缺省為空操作。用戶可以添加喂狗、設置CPU進入休眠模式的功能。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Idle(void) { /* --- 喂狗 */ /* --- 讓CPU進入休眠,由Systick定時中斷喚醒或者其他中斷喚醒 */ /* 例如 emWin 圖形庫,可以插入圖形庫需要的輪詢函數 */ //GUI_Exec(); /* 例如 uIP 協議,可以插入uip輪詢函數 */ TOUCH_CapScan(); }
- 第3步,用戶程序里面采用下面的框架進行觸摸消息處理。
TOUCH_DOWN表示按下消息。
TOUCH_MOVE表示觸摸移動消息。
TOUCH_RELEASE表示觸摸釋放消息。
根據這幾個消息,用戶可以在程序里面判斷當前獲取的物理坐標值是否在設置的區域內來執行觸摸操作。
int16_t tpX, tpY; uint8_t ucTouch; /* 觸摸事件 */ ucTouch = TOUCH_GetKey(&tpX, &tpY); /* 讀取觸摸事件 */ if (ucTouch != TOUCH_NONE) { switch (ucTouch) { case TOUCH_DOWN: /* 觸筆按下事件 */ /* 在觸筆所在位置顯示一個小圈 */ if ((tpX > 0) && (tpY > 0)) { } break; case TOUCH_MOVE: /* 觸筆移動事件 */ /* 實時刷新觸摸ADC采樣值和轉換后的坐標 */ { /* 在觸筆所在位置顯示一個小圈 */ if ((tpX > 0) && (tpY > 0)) { } } break; case TOUCH_RELEASE: /* 觸筆釋放事件 */ /* 在觸筆所在位置顯示一個小圈 */ if ((tpX > 0) && (tpY > 0)) { } break; } }
54.7 不同觸摸IC的識別
由於V7開發板要做不同顯示屏的自適應,所以關聯了多個文件。自適應方法也比較簡單,因為觸摸芯片GT911,GT811,FT5X06和STMPE811都是I2C接口,通過I2C地址就區分開了,具體代碼如下:
1. /* 2. ****************************************************************************************************** 3. * 函 數 名: bsp_DetectLcdType 4. * 功能說明: 通過I2C觸摸芯片,識別LCD模組類型。結果存放在全局變量 g_LcdType 和 g_TouchType 5. * 形 參: 無 6. * 返 回 值: 無 7. ****************************************************************************************************** 8. */ 9. void bsp_DetectLcdType(void) 10. { 11. uint8_t i; 12. 13. g_TouchType = 0xFF; 14. g_LcdType = 0xFF; 15. 16. bsp_DelayUS(5000); 17. 18. touch_printf("正在識別觸摸屏型號\r\n"); 19. 20. /* 50ms,等待GT811復位就緒,才能探測GT811芯片 ID */ 21. for (i = 0; i < 5; i++) 22. { 23. /* 24. GT811電容觸摸板和GT911的I2C地址相同 25. 一般就 0x28 、 0xBA 兩種。 26. 通過讀取觸摸IC的芯片ID來識別。 27. */ 28. if (i2c_CheckDevice(0x28) == 0) 29. { 30. uint32_t id; 31. 32. bsp_DelayUS(500); 33. g_GT911.i2c_addr = 0x28; 34. id = GT911_ReadID(); 35. if (id == 0x00313139) 36. { 37. g_GT911.i2c_addr = 0x28; 38. g_TouchType = CT_GT911; 39. g_LcdType = LCD_70_800X480; 40. touch_printf("檢測到7.0寸電容觸摸屏GT911(0x28) 800x480\r\n"); 41. } 42. else /* GT811 */ 43. { 44. g_GT811.i2c_addr = 0x28; 45. g_TouchType = CT_GT811; 46. g_LcdType = LCD_70_800X480; 47. touch_printf("檢測到7.0寸電容觸摸屏GT811(0x28) 800x480\r\n"); 48. } 49. break; 50. } 51. 52. if (i2c_CheckDevice(0xBA) == 0) 53. { 54. uint32_t id; 55. 56. bsp_DelayUS(500); 57. g_GT911.i2c_addr = 0xBA; 58. id = GT911_ReadID(); 59. if (id == 0x00313139) 60. { 61. g_GT911.i2c_addr = 0xBA; 62. g_TouchType = CT_GT911; 63. g_LcdType = LCD_70_800X480; 64. touch_printf("檢測到7.0寸電容觸摸屏GT911(0xBA) 800x480\r\n"); 65. } 66. else /* GT811 */ 67. { 68. g_GT811.i2c_addr = 0xBA; 69. g_TouchType = CT_GT811; 70. g_LcdType = LCD_70_800X480; 71. touch_printf("檢測到7.0寸電容觸摸屏GT811(0xBA) 800x480\r\n"); 72. } 73. break; 74. } 75. 76. /* FT系列電容觸摸: 4.3寸id = 0x55 5.0寸id = 0x0A 7.0寸id = 0x06 */ 77. if (i2c_CheckDevice(FT5X06_I2C_ADDR) == 0) 78. { 79. uint8_t id; 80. 81. bsp_DelayUS(50000); /* 延遲50ms */ 82. id = FT5X06_ReadID(); 83. if (id == 0x55) 84. { 85. g_TouchType = CT_FT5X06; 86. g_LcdType = LCD_43_480X272; 87. touch_printf("檢測到4.3寸電容觸摸屏\r\n"); 88. } 89. else if (id == 0x0A) 90. { 91. g_TouchType = CT_FT5X06; 92. g_LcdType = LCD_50_800X480; 93. touch_printf("檢測到5.0寸電容觸摸屏\r\n"); 94. } 95. else /* id == 0x06 表示7寸電容屏(FT芯片) */ 96. { 97. g_TouchType = CT_FT5X06; 98. g_LcdType = LCD_70_800X480; 99. touch_printf("檢測到7.0寸電容觸摸屏FT\r\n"); 100. } 101. break; 102. } 103. 104. /* 電阻觸摸板 */ 105. if (i2c_CheckDevice(STMPE811_I2C_ADDRESS) == 0) 106. { 107. /* 108. 0 = 4.3寸屏(480X272) 109. 1 = 5.0寸屏(480X272) 110. 2 = 5.0寸屏(800X480) 111. 3 = 7.0寸屏(800X480) 112. 4 = 7.0寸屏(1024X600) 113. 5 = 3.5寸屏(480X320) 114. */ 115. uint8_t id; 116. 117. g_TouchType = CT_STMPE811; /* 觸摸類型 */ 118. 119. STMPE811_InitHard(); /* 必須先配置才能讀取ID */ 120. 121. id = STMPE811_ReadIO(); /* 識別LCD硬件類型 */ 122. 123. touch_printf("檢測到電阻觸摸屏, id = %d\r\n", id); 124. switch (id) 125. { 126. case 0: 127. g_LcdType = LCD_43_480X272; 128. break; 129. 130. case 1: 131. g_LcdType = LCD_50_480X272; 132. break; 133. 134. case 2: 135. g_LcdType = LCD_50_800X480; 136. break; 137. 138. case 3: 139. g_LcdType = LCD_70_800X480; 140. break; 141. 142. case 4: 143. g_LcdType = LCD_70_1024X600; 144. break; 145. 146. case 5: 147. g_LcdType = LCD_35_480X320; 148. break; 149. 150. default: 151. g_LcdType = LCD_35_480X320; 152. break; 153. } 154. break; 155. } 156. 157. bsp_DelayMS(10); 158. } 159. 160. if (i == 5) 161. { 162. touch_printf("未識別出顯示模塊\r\n"); 163. } 164. }
下面將程序設計中幾個關鍵地方做個闡釋:
- 第21行,上電后,這幾款傳感器一定要延遲一段時間才可以正常讀取ID,所以這里做了一個for循環做檢測。
- 第28-74行,GT811電容觸摸板和GT911的I2C地址相同,一般就0x28和0xBA兩種。程序里面對這兩個地址都做了檢測,然后通過ID區分是GT811還是GT911。
- 第77-102行,FT5X06電容觸摸板檢測,通過ID區分不同分辨率。
- 第105-158行,STMPE811電阻觸摸屏檢測,通過函數STMPE811_ReadIO返回的IO狀態可以識別3.5寸480*320分辨率顯示屏,4.3寸480*272分辨率顯示屏,5寸480*272和800*480分辨率顯示屏以及7寸800*480和1024*600分辨率顯示屏。
- 第160-163行,用於調試。
54.8 LCD觸摸移植和使用
LCD驅動的移植與第51章51.7小節相同,這里重點說下觸摸部分的移植。
通過前面的講解,移植觸摸驅動到自己的板子上,最簡單的辦法是將開發板上與觸摸相關的文件全部移植過來,然后在這些文件的基礎上進行修改。下面分兩種情況進行說明:
- 電容屏觸摸的移植比較簡單,如果用戶用的觸摸IC跟開發板一樣,直接拿來用即可,如果不一樣,需要先將觸摸IC的驅動實現,然后按照開發板提供的GT811、GT911和FT5X06的觸摸掃描函數,照葫蘆畫瓢實現一個即可。
- 電阻屏的移植稍麻煩些,如果用戶用的觸摸IC跟開發板一樣,直接拿來用即可,如果不一樣,需要先將觸摸IC的驅動實現,然后仿照TOUCH_STMPE811.c文件提供觸摸按下狀態函數,X軸,Y軸的ADC數值讀取函數和清除觸摸中斷標志函數。最后用重新實現的這幾個函數替換bsp_touch.c文件中的原函數即可。另外要注意一點,這種方式實現后,雖然觸摸校准依然可以使用,但是開發板的觸摸校准參數保存在EEPROM中,用戶可以根據自己的實際情況選擇存儲介質。
當然,如果大家不想用開發板實現的方案,想自己重新實現一個,也是沒問題的。
54.9 實驗例程設計框架
通過程序設計框架,讓大家先對配套例程有一個全面的認識,然后再理解細節,本次實驗例程的設計框架如下:
第1階段,上電啟動階段:
- 這部分在第14章進行了詳細說明。
第2階段,進入main函數:
- 第1步,硬件初始化,主要是MPU,Cache,HAL庫,系統時鍾,滴答定時器,LED,串口,LCD,SDRAM等。
- 第2步,LCD應用程序設計部分。通過按鍵執行功能,同時LCD界面做了一個簡單的畫板功能。
54.10 實驗例程說明(MDK)
配套例子:
V7-034_LCD的電阻觸摸和電容觸摸(電阻觸摸支持2點和4點校准)
實驗目的:
- 學習LCD的電阻觸摸和電容觸摸。
實驗內容:
- 電容屏無需校准,電阻屏需要校准。
- LCD界面實現了一個簡單的畫板功能,方便檢測觸摸是否准確。
- 啟動1個200ms的自動重裝定時器,讓LED2每200ms翻轉一次。
LCD界面顯示效果如下:
上電后串口打印的信息:
波特率 115200,數據位 8,奇偶校驗位無,停止位 1
程序設計:
系統棧大小分配:
RAM空間用的DTCM:
硬件外設初始化
硬件外設的初始化是在 bsp.c 文件實現:
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* 配置MPU */ MPU_Config(); /* 使能L1 Cache */ CPU_CACHE_Enable(); /* STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鍾: - 調用函數HAL_InitTick,初始化滴答時鍾中斷1ms。 - 設置NVIV優先級分組為4。 */ HAL_Init(); /* 配置系統時鍾到400MHz - 切換使用HSE。 - 此函數會更新全局變量SystemCoreClock,並重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用於代碼執行時間測量,MDK5.25及其以上版本才支持,IAR不支持。 - 默認不開啟,如果要使能此選項,務必看V7開發板用戶手冊第8章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder並開啟 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitKey(); /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */ bsp_InitTimer(); /* 初始化滴答定時器 */ bsp_InitUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化FMC總線74HC574擴展IO. 必須在 bsp_InitLed()前執行 */ bsp_InitLed(); /* 初始化LED */ bsp_InitI2C(); /* 初始化I2C總線 */ TOUCH_InitHard(); /* 初始化觸摸芯片,LCD面板型號的檢查也在此函數,所以要在函數LCD_InitHard前調用 */ LCD_InitHard(); /* 初始化LCD */ }
MPU配置和Cache配置:
數據Cache和指令Cache都開啟。配置了AXI SRAM區(本例子未用到AXI SRAM),FMC的擴展IO區和SDRAM。由於SDRAM要用於LCD的顯存,方便起見,直接將其配置為WT模式。
/* ********************************************************************************************************* * 函 數 名: MPU_Config * 功能說明: 配置MPU * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void MPU_Config( void ) { MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */ HAL_MPU_Disable(); /* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x24000000; MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置FMC擴展IO的MPU屬性為Device或者Strongly Ordered */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x60000000; MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER1; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置SDRAM的MPU屬性為Write through, read allocate,no write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0xC0000000; MPU_InitStruct.Size = MPU_REGION_SIZE_32MB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER2; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } /* ********************************************************************************************************* * 函 數 名: CPU_CACHE_Enable * 功能說明: 使能L1 Cache * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void CPU_CACHE_Enable(void) { /* 使能 I-Cache */ SCB_EnableICache(); /* 使能 D-Cache */ SCB_EnableDCache(); }
主功能:
主程序實現如下操作:
- LCD界面實現了一個簡單的畫板功能,方便檢測觸摸是否准確。
- 啟動1個200ms的自動重裝定時器,讓LED2每200ms翻轉一次。
- 搖桿上鍵,增加LCD背景光亮度。
- 搖桿下鍵,降低LCD背景光亮度。
- 搖桿左鍵,電阻觸摸屏校准。
- 搖桿OK鍵,清屏。
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程序入口 * 形 參: 無 * 返 回 值: 錯誤代碼(無需處理) ********************************************************************************************************* */ int main(void) { uint16_t ucBright; /* 背光亮度(0-255) */ uint8_t ucKeyCode; /* 按鍵代碼 */ uint8_t fRefresh; /* 刷屏請求標志,1表示需要刷新 */ FONT_T tFont; /* 定義一個字體結構體變量,用於設置字體參數 */ char buf[64]; uint16_t usAdcX, usAdcY; int16_t tpX, tpY; uint8_t ucTouch; /* 觸摸事件 */ /* 設置字體參數 */ { tFont.FontCode = FC_ST_16; /* 字體代碼 16點陣 */ tFont.FrontColor = CL_WHITE; /* 字體顏色 */ tFont.BackColor = CL_BLUE; /* 文字背景顏色 */ tFont.Space = 0; /* 文字間距,單位 = 像素 */ } bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名稱和版本等信息 */ PrintfHelp(); /* 打印操作提示 */ /* 延遲200ms再點亮背光,避免瞬間高亮 */ bsp_DelayMS(200); DispFirstPage(); /* 顯示第1頁 */ /* 界面整體顯示完畢后,再打開背光,設置為缺省亮度 */ bsp_DelayMS(100); ucBright = BRIGHT_DEFAULT; LCD_SetBackLight(ucBright); bsp_StartAutoTimer(0, 200); /* 啟動1個200ms的自動重裝的定時器,軟件定時器0 */ /* 進入主程序循環體 */ fRefresh = 1; while (1) { bsp_Idle(); /* 判斷軟件定時器0是否超時 */ if(bsp_CheckTimer(0)) { /* 每隔200ms 進來一次 */ bsp_LedToggle(2); } if (fRefresh) { fRefresh = 0; /* 實時刷新觸摸ADC采樣值和轉換后的坐標 */ { /* 讀取並顯示當前X軸和Y軸的ADC采樣值 */ usAdcX = TOUCH_ReadAdcX(); usAdcY = TOUCH_ReadAdcY(); sprintf(buf, "觸摸ADC值 X = %4d, Y = %4d ", usAdcX, usAdcY); LCD_DispStr(5, 40, buf, &tFont); /* 讀取並顯示當前觸摸坐標 */ tpX = TOUCH_GetX(); tpY = TOUCH_GetY(); sprintf(buf, "觸摸坐標 X = %4d, Y = %4d ", tpX, tpY); LCD_DispStr(5, 60, buf, &tFont); /* 在觸筆所在位置顯示一個小圈 */ if ((tpX > 0) && (tpY > 0)) { LCD_DrawCircle(tpX, tpY, 2, CL_YELLOW); } } /* 在屏幕邊沿繪制2個矩形框(用於檢測面板邊緣像素是否正常) */ LCD_DrawRect(0, 0, LCD_GetHeight(), LCD_GetWidth(), CL_WHITE); LCD_DrawRect(2, 2, LCD_GetHeight() - 4, LCD_GetWidth() - 4, CL_YELLOW); /* 顯示背光值 */ sprintf(buf, "當前背光亮度: %3d", ucBright); LCD_DispStr(5, 80, buf, &tFont); } ucTouch = TOUCH_GetKey(&tpX, &tpY); /* 讀取觸摸事件 */ if (ucTouch != TOUCH_NONE) { switch (ucTouch) { case TOUCH_DOWN: /* 觸筆按下事件 */ /* 在觸筆所在位置顯示一個小圈 */ if ((tpX > 0) && (tpY > 0)) { LCD_DrawCircle(tpX, tpY, 3, CL_RED); } break; case TOUCH_MOVE: /* 觸筆移動事件 */ /* 實時刷新觸摸ADC采樣值和轉換后的坐標 */ { /* 讀取並顯示當前X軸和Y軸的ADC采樣值 */ usAdcX = TOUCH_ReadAdcX(); usAdcY = TOUCH_ReadAdcY(); sprintf(buf, "觸摸ADC值 X = %4d, Y = %4d ", usAdcX, usAdcY); LCD_DispStr(5, 40, buf, &tFont); /* 讀取並顯示當前觸摸坐標 */ //tpX = TOUCH_GetX(); //tpY = TOUCH_GetY(); sprintf(buf, "觸摸坐標 X = %4d, Y = %4d ", tpX, tpY); LCD_DispStr(5, 60, buf, &tFont); /* 在觸筆所在位置顯示一個小圈 */ if ((tpX > 0) && (tpY > 0)) { LCD_DrawCircle(tpX, tpY, 2, CL_YELLOW); } } break; case TOUCH_RELEASE: /* 觸筆釋放事件 */ /* 在觸筆所在位置顯示一個小圈 */ if ((tpX > 0) && (tpY > 0)) { LCD_DrawCircle(tpX, tpY, 4, CL_WHITE); } break; } } ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { /* 有鍵按下 */ switch (ucKeyCode) { case JOY_DOWN_L: /* 搖桿LEFT鍵按下, 2點觸摸校准 */ TOUCH_Calibration(2); DispFirstPage(); fRefresh = 1; /* 請求刷新LCD */ break; case JOY_DOWN_OK: /* 搖桿OK鍵,清屏 */ DispFirstPage(); fRefresh = 1; /* 請求刷新LCD */ break; case JOY_DOWN_U: /* 搖桿UP鍵按下 */ ucBright += BRIGHT_STEP; if (ucBright > BRIGHT_MAX) { ucBright = BRIGHT_MAX; } LCD_SetBackLight(ucBright); fRefresh = 1; /* 請求刷新LCD */ break; case JOY_DOWN_D: /* 搖桿DOWN鍵按下 */ if (ucBright < BRIGHT_STEP) { ucBright = 0; } else { ucBright -= BRIGHT_STEP; } LCD_SetBackLight(ucBright); fRefresh = 1; /* 請求刷新LCD */ break; default: break; } } } }
LCD主界面顯示內容如下:
/* ********************************************************************************************************* * 函 數 名: DispFirstPage * 功能說明: 顯示操作提示 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ static void DispFirstPage(void) { FONT_T tFont; /* 定義一個字體結構體變量,用於設置字體參數 */ uint16_t y; /* Y坐標 */ uint16_t usLineCap; /* 行高 */ char buf[50]; LCD_ClrScr(CL_BLUE); /* 清屏,背景藍色 */ /* 設置字體屬性 */ tFont.FontCode = FC_ST_16; /* 字體選擇宋體16點陣,高16x寬15) */ tFont.FrontColor = CL_WHITE; /* 字體顏色設置為白色 */ tFont.BackColor = CL_MASK; /* 文字背景顏色,透明 */ tFont.Space = 0; /* 字符水平間距, 單位 = 像素 */ y = 4; usLineCap = 18; /* 行間距 */ LCD_DispStr(5, y, "安富萊STM32-V7開發板 www.armfly.com", &tFont); y += usLineCap; LCD_DispStr(5, y, "電阻屏和電容屏實驗,電容屏無需校准,電阻需要校准", &tFont); y += 5 * usLineCap; LCD_DispStr(30, y, "操作提示:", &tFont); y += usLineCap; LCD_DispStr(50, y, "搖桿上鍵 = 增加背光亮度", &tFont); y += usLineCap; LCD_DispStr(50, y, "搖桿下鍵 = 降低背光亮度", &tFont); y += usLineCap; LCD_DispStr(50, y, "搖桿左鍵 = 電阻屏觸摸校准", &tFont); y += usLineCap; LCD_DispStr(50, y, "搖桿OK鍵 = 清屏", &tFont); y += 2 * usLineCap; /* 顯示TFT控制器型號和屏幕分辨率 */ //LCD_GetChipDescribe(buf); /* 讀取TFT驅動芯片型號 */ if (g_TouchType == CT_GT811) { strcpy(buf, "STM32H743 + GT811"); } else if (g_TouchType == CT_GT911) { strcpy(buf, "STM32H743 + GT911"); } else if (g_TouchType == CT_FT5X06) { strcpy(buf, "STM32H743 + FT5X06"); } else if (g_TouchType == CT_STMPE811) { strcpy(buf, "STM32H743 + STMPE811"); } else { strcpy(buf, "STM32H743 + NoTouch"); } sprintf(&buf[strlen(buf)], " %d x %d", LCD_GetWidth(), LCD_GetHeight()); LCD_DispStr(5, y, (char *)buf, &tFont); }
54.11 實驗例程說明(IAR)
配套例子:
V7-034_LCD的電阻觸摸和電容觸摸(電阻觸摸支持2點和4點校准)
實驗目的:
- 學習LCD的電阻觸摸和電容觸摸。
實驗內容:
- 電容屏無需校准,電阻屏需要校准。
- LCD界面實現了一個簡單的畫板功能,方便檢測觸摸是否准確。
- 啟動1個200ms的自動重裝定時器,讓LED2每200ms翻轉一次。
LCD界面顯示效果如下:
上電后串口打印的信息:
波特率 115200,數據位 8,奇偶校驗位無,停止位 1
程序設計:
系統棧大小分配:
RAM空間用的DTCM:
硬件外設初始化
硬件外設的初始化是在 bsp.c 文件實現:
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* 配置MPU */ MPU_Config(); /* 使能L1 Cache */ CPU_CACHE_Enable(); /* STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鍾: - 調用函數HAL_InitTick,初始化滴答時鍾中斷1ms。 - 設置NVIV優先級分組為4。 */ HAL_Init(); /* 配置系統時鍾到400MHz - 切換使用HSE。 - 此函數會更新全局變量SystemCoreClock,並重新配置HAL_InitTick。 */ SystemClock_Config(); /* Event Recorder: - 可用於代碼執行時間測量,MDK5.25及其以上版本才支持,IAR不支持。 - 默認不開啟,如果要使能此選項,務必看V7開發板用戶手冊第8章 */ #if Enable_EventRecorder == 1 /* 初始化EventRecorder並開啟 */ EventRecorderInitialize(EventRecordAll, 1U); EventRecorderStart(); #endif bsp_InitKey(); /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */ bsp_InitTimer(); /* 初始化滴答定時器 */ bsp_InitUart(); /* 初始化串口 */ bsp_InitExtIO(); /* 初始化FMC總線74HC574擴展IO. 必須在 bsp_InitLed()前執行 */ bsp_InitLed(); /* 初始化LED */ bsp_InitI2C(); /* 初始化I2C總線 */ TOUCH_InitHard(); /* 初始化觸摸芯片,LCD面板型號的檢查也在此函數,所以要在函數LCD_InitHard前調用 */ LCD_InitHard(); /* 初始化LCD */ }
MPU配置和Cache配置:
數據Cache和指令Cache都開啟。配置了AXI SRAM區(本例子未用到AXI SRAM),FMC的擴展IO區和SDRAM。由於SDRAM要用於LCD的顯存,方便起見,直接將其配置為WT模式。
/* ********************************************************************************************************* * 函 數 名: MPU_Config * 功能說明: 配置MPU * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void MPU_Config( void ) { MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */ HAL_MPU_Disable(); /* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x24000000; MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置FMC擴展IO的MPU屬性為Device或者Strongly Ordered */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x60000000; MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER1; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置SDRAM的MPU屬性為Write through, read allocate,no write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0xC0000000; MPU_InitStruct.Size = MPU_REGION_SIZE_32MB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER2; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } /* ********************************************************************************************************* * 函 數 名: CPU_CACHE_Enable * 功能說明: 使能L1 Cache * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void CPU_CACHE_Enable(void) { /* 使能 I-Cache */ SCB_EnableICache(); /* 使能 D-Cache */ SCB_EnableDCache(); }
主功能:
主程序實現如下操作:
- LCD界面實現了一個簡單的畫板功能,方便檢測觸摸是否准確。
- 啟動1個200ms的自動重裝定時器,讓LED2每200ms翻轉一次。
- 搖桿上鍵,增加LCD背景光亮度。
- 搖桿下鍵,降低LCD背景光亮度。
- 搖桿左鍵,電阻觸摸屏校准。
- 搖桿OK鍵,清屏。
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: c程序入口 * 形 參: 無 * 返 回 值: 錯誤代碼(無需處理) ********************************************************************************************************* */ int main(void) { uint16_t ucBright; /* 背光亮度(0-255) */ uint8_t ucKeyCode; /* 按鍵代碼 */ uint8_t fRefresh; /* 刷屏請求標志,1表示需要刷新 */ FONT_T tFont; /* 定義一個字體結構體變量,用於設置字體參數 */ char buf[64]; uint16_t usAdcX, usAdcY; int16_t tpX, tpY; uint8_t ucTouch; /* 觸摸事件 */ /* 設置字體參數 */ { tFont.FontCode = FC_ST_16; /* 字體代碼 16點陣 */ tFont.FrontColor = CL_WHITE; /* 字體顏色 */ tFont.BackColor = CL_BLUE; /* 文字背景顏色 */ tFont.Space = 0; /* 文字間距,單位 = 像素 */ } bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名稱和版本等信息 */ PrintfHelp(); /* 打印操作提示 */ /* 延遲200ms再點亮背光,避免瞬間高亮 */ bsp_DelayMS(200); DispFirstPage(); /* 顯示第1頁 */ /* 界面整體顯示完畢后,再打開背光,設置為缺省亮度 */ bsp_DelayMS(100); ucBright = BRIGHT_DEFAULT; LCD_SetBackLight(ucBright); bsp_StartAutoTimer(0, 200); /* 啟動1個200ms的自動重裝的定時器,軟件定時器0 */ /* 進入主程序循環體 */ fRefresh = 1; while (1) { bsp_Idle(); /* 判斷軟件定時器0是否超時 */ if(bsp_CheckTimer(0)) { /* 每隔200ms 進來一次 */ bsp_LedToggle(2); } if (fRefresh) { fRefresh = 0; /* 實時刷新觸摸ADC采樣值和轉換后的坐標 */ { /* 讀取並顯示當前X軸和Y軸的ADC采樣值 */ usAdcX = TOUCH_ReadAdcX(); usAdcY = TOUCH_ReadAdcY(); sprintf(buf, "觸摸ADC值 X = %4d, Y = %4d ", usAdcX, usAdcY); LCD_DispStr(5, 40, buf, &tFont); /* 讀取並顯示當前觸摸坐標 */ tpX = TOUCH_GetX(); tpY = TOUCH_GetY(); sprintf(buf, "觸摸坐標 X = %4d, Y = %4d ", tpX, tpY); LCD_DispStr(5, 60, buf, &tFont); /* 在觸筆所在位置顯示一個小圈 */ if ((tpX > 0) && (tpY > 0)) { LCD_DrawCircle(tpX, tpY, 2, CL_YELLOW); } } /* 在屏幕邊沿繪制2個矩形框(用於檢測面板邊緣像素是否正常) */ LCD_DrawRect(0, 0, LCD_GetHeight(), LCD_GetWidth(), CL_WHITE); LCD_DrawRect(2, 2, LCD_GetHeight() - 4, LCD_GetWidth() - 4, CL_YELLOW); /* 顯示背光值 */ sprintf(buf, "當前背光亮度: %3d", ucBright); LCD_DispStr(5, 80, buf, &tFont); } ucTouch = TOUCH_GetKey(&tpX, &tpY); /* 讀取觸摸事件 */ if (ucTouch != TOUCH_NONE) { switch (ucTouch) { case TOUCH_DOWN: /* 觸筆按下事件 */ /* 在觸筆所在位置顯示一個小圈 */ if ((tpX > 0) && (tpY > 0)) { LCD_DrawCircle(tpX, tpY, 3, CL_RED); } break; case TOUCH_MOVE: /* 觸筆移動事件 */ /* 實時刷新觸摸ADC采樣值和轉換后的坐標 */ { /* 讀取並顯示當前X軸和Y軸的ADC采樣值 */ usAdcX = TOUCH_ReadAdcX(); usAdcY = TOUCH_ReadAdcY(); sprintf(buf, "觸摸ADC值 X = %4d, Y = %4d ", usAdcX, usAdcY); LCD_DispStr(5, 40, buf, &tFont); /* 讀取並顯示當前觸摸坐標 */ //tpX = TOUCH_GetX(); //tpY = TOUCH_GetY(); sprintf(buf, "觸摸坐標 X = %4d, Y = %4d ", tpX, tpY); LCD_DispStr(5, 60, buf, &tFont); /* 在觸筆所在位置顯示一個小圈 */ if ((tpX > 0) && (tpY > 0)) { LCD_DrawCircle(tpX, tpY, 2, CL_YELLOW); } } break; case TOUCH_RELEASE: /* 觸筆釋放事件 */ /* 在觸筆所在位置顯示一個小圈 */ if ((tpX > 0) && (tpY > 0)) { LCD_DrawCircle(tpX, tpY, 4, CL_WHITE); } break; } } ucKeyCode = bsp_GetKey(); /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */ if (ucKeyCode != KEY_NONE) { /* 有鍵按下 */ switch (ucKeyCode) { case JOY_DOWN_L: /* 搖桿LEFT鍵按下, 2點觸摸校准 */ TOUCH_Calibration(2); DispFirstPage(); fRefresh = 1; /* 請求刷新LCD */ break; case JOY_DOWN_OK: /* 搖桿OK鍵,清屏 */ DispFirstPage(); fRefresh = 1; /* 請求刷新LCD */ break; case JOY_DOWN_U: /* 搖桿UP鍵按下 */ ucBright += BRIGHT_STEP; if (ucBright > BRIGHT_MAX) { ucBright = BRIGHT_MAX; } LCD_SetBackLight(ucBright); fRefresh = 1; /* 請求刷新LCD */ break; case JOY_DOWN_D: /* 搖桿DOWN鍵按下 */ if (ucBright < BRIGHT_STEP) { ucBright = 0; } else { ucBright -= BRIGHT_STEP; } LCD_SetBackLight(ucBright); fRefresh = 1; /* 請求刷新LCD */ break; default: break; } } } }
LCD主界面顯示內容如下:
/* ********************************************************************************************************* * 函 數 名: DispFirstPage * 功能說明: 顯示操作提示 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ static void DispFirstPage(void) { FONT_T tFont; /* 定義一個字體結構體變量,用於設置字體參數 */ uint16_t y; /* Y坐標 */ uint16_t usLineCap; /* 行高 */ char buf[50]; LCD_ClrScr(CL_BLUE); /* 清屏,背景藍色 */ /* 設置字體屬性 */ tFont.FontCode = FC_ST_16; /* 字體選擇宋體16點陣,高16x寬15) */ tFont.FrontColor = CL_WHITE; /* 字體顏色設置為白色 */ tFont.BackColor = CL_MASK; /* 文字背景顏色,透明 */ tFont.Space = 0; /* 字符水平間距, 單位 = 像素 */ y = 4; usLineCap = 18; /* 行間距 */ LCD_DispStr(5, y, "安富萊STM32-V7開發板 www.armfly.com", &tFont); y += usLineCap; LCD_DispStr(5, y, "電阻屏和電容屏實驗,電容屏無需校准,電阻需要校准", &tFont); y += 5 * usLineCap; LCD_DispStr(30, y, "操作提示:", &tFont); y += usLineCap; LCD_DispStr(50, y, "搖桿上鍵 = 增加背光亮度", &tFont); y += usLineCap; LCD_DispStr(50, y, "搖桿下鍵 = 降低背光亮度", &tFont); y += usLineCap; LCD_DispStr(50, y, "搖桿左鍵 = 電阻屏觸摸校准", &tFont); y += usLineCap; LCD_DispStr(50, y, "搖桿OK鍵 = 清屏", &tFont); y += 2 * usLineCap; /* 顯示TFT控制器型號和屏幕分辨率 */ //LCD_GetChipDescribe(buf); /* 讀取TFT驅動芯片型號 */ if (g_TouchType == CT_GT811) { strcpy(buf, "STM32H743 + GT811"); } else if (g_TouchType == CT_GT911) { strcpy(buf, "STM32H743 + GT911"); } else if (g_TouchType == CT_FT5X06) { strcpy(buf, "STM32H743 + FT5X06"); } else if (g_TouchType == CT_STMPE811) { strcpy(buf, "STM32H743 + STMPE811"); } else { strcpy(buf, "STM32H743 + NoTouch"); } sprintf(&buf[strlen(buf)], " %d x %d", LCD_GetWidth(), LCD_GetHeight()); LCD_DispStr(5, y, (char *)buf, &tFont); }
54.12 總結
本章節為大家講解的電阻觸摸方案和電容觸摸方案都是經過實戰總結的,實際項目中比較實用。