第29章 電容觸摸屏—觸摸畫板
全套200集視頻教程和1000頁PDF教程請到秉火論壇下載:www.firebbs.cn
野火視頻教程優酷觀看網址:http://i.youku.com/firege
本章參考資料:《STM32F4xx 中文參考手冊》、《STM32F4xx規格書》、庫幫助文檔《stm32f4xx_dsp_stdperiph_lib_um.chm》。
關於開發板配套的觸摸屏參數可查閱《5.0寸觸摸屏面板說明》,觸摸面板配套的觸摸控制芯片可查閱《電容觸控芯片GT9157 Datasheet》及《gt91x編程指南》配套資料獲知。對於7寸電容屏,請查閱《電容觸摸芯片GT911》相關的數據手冊,7寸電容屏的驅動原理與5寸電容屏的類似,僅寫入觸摸芯片的配置參數有細節差異。
在前面我們學習了如何使用LTDC外設控制液晶屏並用它顯示各種圖形及文字,利用液晶屏,STM32的系統具有了高級信息輸出功能,然而我們還希望有用戶友好的輸入設備,觸摸屏是不二之選,目前大部分電子設備都使用觸摸屏配合液晶顯示器組成人機交互系統。
29.1 觸摸屏簡介
觸摸屏又稱觸控面板,它是一種把觸摸位置轉化成坐標數據的輸入設備,根據觸摸屏的檢測原理,主要分為電阻式觸摸屏和電容式觸摸屏。相對來說,電阻屏造價便宜,能適應較惡劣的環境,但它只支持單點觸控(一次只能檢測面板上的一個觸摸位置),觸摸時需要一定的壓力,使用久了容易造成表面磨損,影響壽命;而電容屏具有支持多點觸控、檢測精度高的特點,電容屏通過與導電物體產生的電容效應來檢測觸摸動作,只能感應導電物體的觸摸,濕度較大或屏幕表面有水珠時會影響電容屏的檢測效果。
圖 291單電阻屏、電阻液晶屏(帶觸摸控制芯片)
圖 292單電容屏、電容液晶屏(帶觸摸控制芯片)
圖 291和圖 292分別是帶電阻觸摸屏及電容觸摸屏的兩種屏幕,從外觀上並沒有明顯的區別,區分電阻屏與電容屏最直接的方法就是使用絕緣物體點擊屏幕,因為電阻屏通過壓力能正常檢測觸摸動作,而該絕緣物體無法影響電容屏所檢測的信號,因而無法檢測到觸摸動作。目前電容式觸摸屏被大部分應用在智能手機、平板電腦等電子設備中,而在汽車導航、工控機等設備中電阻式觸摸屏仍占主流。
29.1.1 電阻式觸摸屏檢測原理
電阻式的觸摸屏結構見圖 293。它主要由表面硬塗層、兩個ITO層、間隔點以及玻璃底層構成,這些結構層都是透明的,整個觸摸屏覆蓋在液晶面板上,透過觸摸屏可看到液晶面板。表面塗層起到保護作用,玻璃底層起承載的作用,而兩個ITO層是觸摸屏的關鍵結構,它們是塗有銦錫金屬氧化物的導電層。兩個ITO層之間使用間隔點使兩層分開,當觸摸屏表面受到壓力時,表面彎曲使得上層ITO與下層ITO接觸,在觸點處連通電路。
圖 293 電阻式觸摸屏結構
兩個ITO塗層的兩端分別引出X-、X+、Y-、Y+四個電極,見圖 294,這是電阻屏最常見的四線結構,通過這些電極,外部電路向這兩個塗層可以施加勻強電場或檢測電壓。
圖 294 XY的ITO層結構
當觸摸屏被按下時,兩個ITO層相互接觸,從觸點處把ITO層分為兩個電阻,且由於ITO層均勻導電,兩個電阻的大小與觸點離兩電極的距離成比例關系,利用這個特性,可通過以下過程來檢測坐標,這也正是電阻觸摸屏名稱的由來,見圖 295。
計算X坐標時,在X+電極施加驅動電壓Vref,X-極接地,所以X+與X-處形成了勻強電場,而觸點處的電壓通過Y+電極采集得到,由於ITO層均勻導電,觸點電壓與Vref之比等於觸點X坐標與屏寬度之比,從而:
計算Y坐標時,在Y+電極施加驅動電壓Vref,Y-極接地,所以Y+與Y-處形成了勻強電場,而觸點處的電壓通過X+電極采集得到,由於ITO層均勻導電,觸點電壓與Vref之比等於觸點Y坐標與屏高度之比,從而:
圖 295 觸摸檢測等效電路
為了方便檢測觸摸的坐標,一些芯片廠商制作了電阻屏專用的控制芯片,控制上述采集過程、采集電壓,外部微控制器直接與觸摸控制芯片通訊直接獲得觸點的電壓或坐標。如圖 291中我們生產的這款3.2寸電阻觸摸屏就是采用XPT2046芯片作為觸摸控制芯片,XPT2046芯片控制4線電阻觸摸屏,STM32與XPT2046采用SPI通訊獲取采集得的電壓,然后轉換成坐標。
29.1.2 電容式觸摸屏檢測原理
與電阻式觸摸屏不同,電容式觸摸屏不需要通過壓力使觸點變形,再通過觸點處電壓值來檢測坐標,它的基本原理和前面定時器章節中介紹的電容按鍵類似,都是利用充電時間檢測電容大小,從而通過檢測出電容值的變化來獲知觸摸信號。見圖 296,電容屏的最上層是玻璃(不會像電阻屏那樣形變),核心層部分也是由ITO材料構成的,這些導電材料在屏幕里構成了人眼看不見的靜電網,靜電網由多行X軸電極和多列Y軸電極構成,兩個電極之間會形成電容。觸摸屏工作時,X軸電極發出AC交流信號,而交流信號能穿過電容,即通過Y軸能感應出該信號,當交流電穿越時電容會有充放電過程,檢測該充電時間可獲知電容量。若手指觸摸屏幕,會影響觸摸點附近兩個電極之間的耦合,從而改變兩個電極之間的電容量,若檢測到某電容的電容量發生了改變,即可獲知該電容處有觸摸動作(這就是為什么它被稱為電容式觸摸屏以及絕緣體觸摸沒有反應的原因)。
圖 296 電容觸摸屏基本原理
電容屏ITO層的結構見圖 297,這是比較常見的形式,電極由多個菱形導體組成,生產時使用蝕刻工藝在ITO層生成這樣的結構。
圖 297 電容觸摸屏的ITO層結構
X軸電極與Y軸電極在交叉處形成電容,即這兩組電極構成了電容的兩極,這樣的結構覆蓋了整個電容屏,每個電容單元在觸摸屏中都有其特定的物理位置,即電容的位置就是它在觸摸屏的XY坐標。檢測觸摸的坐標時,第1條X軸的電極發出激勵信號,而所有Y軸的電極同時接收信號,通過檢測充電時間可檢測出各個Y軸與第1條X軸相交的各個互電容的大小,各個X軸依次發出激勵信號,重復上述步驟,即可得到整個觸摸屏二維平面的所有電容大小。當手指接近時,會導致局部電容改變,根據得到的觸摸屏電容量變化的二維數據表,可以得知每個觸摸點的坐標,因此電容觸摸屏支持多點觸控。
其實電容觸摸屏可看作是多個電容按鍵組合而成,就像機械按鍵中獨立按鍵和矩陣按鍵的關系一樣,甚至電容觸摸屏的坐標掃描方式與矩陣按鍵都是很相似的。
29.2 電容觸摸屏控制芯片
相對來說,電容屏的坐標檢測比電阻屏的要復雜,因而它也有專用芯片用於檢測過程,下面我們以本章重點講述的電容屏使用的觸控芯片GT9157為例進行講解,關於它的詳細說明可從《gt91x編程指南》和《電容觸控芯片GT9157》文檔了解。(7寸屏使用GT911觸控芯片,原理類似)
29.2.1 GT9157芯片的引腳
GT9157芯片的外觀可以圖 292中找到,其內部結構框圖見圖 298。
圖 298 GT9157結構框圖
該芯片對外引出的信號線介紹如下:
表 291 GT9157信號線說明
信號線 |
說明 |
AVDD、AVDD18、DVDD12、VDDDIO、GND |
電源和地 |
Driving channels |
激勵信號輸出的引腳,一共有0-25個引腳,它連接到電容屏ITO層引出的各個激勵信號軸 |
Sensing channels |
信號檢測引腳,一共有0-13個引腳,它連接到電容屏ITO層引出的各個電容量檢測信號軸 |
I2C |
I2C通信信號線,包含SCL與SDA,外部控制器通過它與GT9157芯片通訊,配置GT9157的工作方式或獲取坐標信號 |
INT |
中斷信號,GB9157芯片通過它告訴外部控制器有新的觸摸事件 |
/RSTB |
復位引腳,用於復位GT9157芯片;在上電時還與INT引腳配合設置IIC通訊的設備地址 |
若您把電容觸摸屏與液晶面板分離開來,在觸摸面板的背面,可看到它的邊框有一些電路走線,它們就是觸摸屏ITO層引出的XY軸信號線,這些信號線分別引出到GT9157芯片的Driving channels及Sensing channels引腳中。也正是因為觸摸屏有這些信號線的存在,所以手機廠商追求的屏幕無邊框是比較難做到的。
29.2.2 上電時序與I2C設備地址
GT9157觸控芯片有兩個備選的I2C通訊地址,這是由芯片的上電時序設定的,見圖 299。上電時序有Reset引腳和INT引腳生成,若Reset引腳從低電電平轉變到高電平期間,INT引腳為高電平的時候,觸控芯片使用的I2C設備地址為0x28/0x29(8位寫、讀地址),7位地址為0x14;若Reset引腳從低電電平轉變到高電平期間,INT引腳一直為低電平,則觸控芯片使用的I2C設備地址為0xBA/0xBB(8位寫、讀地址),7位地址為0x5D。
圖 299 GT9157的上電時序及I2C設備地址
29.2.3 寄存器配置
上電復位后,GT9157芯片需要通過外部主控芯片加載寄存器配置,設定它的工作模式,這些配置通過I2C信號線傳輸到GT9157,它的配置寄存器地址都由兩個字節來表示,這些寄存器的地址從0x8047-0x8100,一般來說,我們實際配置的時候會按照GT9157生產廠商給的默認配置來控制芯片,僅修改部分關鍵寄存器,其中部分寄存器說明見圖 2910。
圖 2910 部分寄存器配置說明
這些寄存器介紹如下:
(1) 配置版本寄存器
0x8047配置版本寄存器,它包含有配置文件的版本號,若新寫入的版本號比原版本大,或者版本號相等,但配置不一樣時,才會更新配置文件到寄存器中。其中配置文件是指記錄了寄存器0x8048-0x80FE控制參數的一系列數據。
為了保證每次都更新配置,我們一般把配置版本寄存器設置為"0x00",這樣版本號會默認初始化為'A',這樣每次我們修改其它寄存器配置的時候,都會寫入到GT9157中。
(2) X、Y分辨率
0x8048-0x804B寄存器用於配置觸控芯片輸出的XY坐標的最大值,為了方便使用,我們把它配置得跟液晶面板的分辨率一致,這樣就能使觸控芯片輸出的坐標一一對應到液晶面板的每一個像素點了。
(3) 觸點個數
0x804C觸點個數寄存器用於配置它最多可輸出多少個同時按下的觸點坐標,這個極限值跟觸摸屏面板有關,如我們本章實驗使用的觸摸面板最多支持5點觸控。
(4) 模式切換
0x804D模式切換寄存器中的X2Y位可以用於交換XY坐標軸;而INT觸發方式位可以配置不同的觸發方式,當有觸摸信號時,INT引腳會根據這里的配置給出觸發信號。
(5) 配置校驗
0x80FF配置校驗寄存器用於寫入前面0x8047-0x80FE寄存器控制參數字節之和的補碼,GT9157收到前面的寄存器配置時,會利用這個數據進行校驗,若不匹配,就不會更新寄存器配置。
(6) 配置更新
0x8100配置更新寄存器用於控制GT9157進行更新,傳輸了前面的寄存器配置並校驗通過后,對這個寄存器寫1,GT9157會更新配置。
29.2.4 讀取坐標信息
坐標寄存器
上述寄存器主要是由外部主控芯片給GT9157寫入配置的,而它則使用圖 2911中的寄存器向主控器反饋信息。
圖 2911 坐標信息寄存器
(1) 產品ID及版本
0x8140-0x8143 寄存器存儲的是產品ID,上電后我們可以利用I2C讀取這些寄存器的值來判斷I2C是否正常通訊,這些寄存器中包含有"9157"字樣; 而0x8144-0x8145則保存有固件版本號,不同版本可能不同。
(2) X/Y分辨率
0x8146-0x8149寄存器存儲了控制觸摸屏的分辨率,它們的值與我們前面在配置寄存器寫入的XY控制參數一致。所以我們可以通過讀取這兩個寄存器的值來確認配置參數是否正確寫入。
(3) 狀態寄存器
0x814E地址的是狀態寄存器,它的Buffer status位存儲了坐標狀態,當它為1時,表示新的坐標數據已准備好,可以讀取,0表示未就緒,數據無效,外部控制器讀取完坐標后,須對這個寄存器位寫0 。number of touch points位表示當前有多少個觸點。其余數據位我們不關心。
(4) 坐標數據
從地址0x814F-0x8156的是觸摸點1的坐標數據,從0x8157-0x815E的是觸摸點2的坐標數據,依次還有存儲3-10觸摸點坐標數據的寄存器。讀取這些坐標信息時,我們通過它們的track id來區分筆跡,多次讀取坐標數據時,同一個track id號里的數據屬於同一個連續的筆划軌跡。
讀坐標流程
上電、配置完寄存器后,GT9157就會開監測觸摸屏,若我們前面的配置使INT采用中斷上升沿報告觸摸信號的方式,整個讀取坐標信息的過程如下:
(1) 待機時INT引腳輸出低電平;
(2) 有坐標更新時,INT引腳輸出上升沿;
(3) INT輸出上升沿后,INT 腳會保持高直到下一個周期(該周期可由配置 Refresh_Rate 決定)。外部主控器在檢測到INT的信號后,先讀取狀態寄存器(0x814E)中的number of touch points位獲當前有多少個觸摸點,然后讀取各個點的坐標數據,讀取完后將 buffer status位寫為 0。外部主控器的這些讀取過程要在一周期內完成,該周期由0x8056地址的Refresh_Rate寄存器配置;
(4) 上一步驟中INT輸出上升沿后,若主控未在一個周期內讀走坐標,下次 GT9157 即使檢測到坐標更新會再輸出一個 INT 脈沖但不更新坐標;
(5) 若外部主控一直未讀走坐標,則 GT9 會一直輸出 INT 脈沖。
29.3 電容觸摸屏—觸摸畫板實驗
本小節講解如何驅動電容觸摸屏,並利用觸摸屏制作一個簡易的觸摸畫板應用。
學習本小節內容時,請打開配套的"電容觸摸屏—觸摸畫板"工程配合閱讀。
29.3.1 硬件設計
圖 2912 液晶屏實物圖
本實驗使用的液晶電容屏實物見圖 2719,屏幕背面的PCB電路對應圖 2721、圖 2725中的原理圖,分別是觸摸屏接口及排針接口。
我們這個觸摸屏出廠時就與GT9157芯片通過柔性電路板連接在一起了,柔性電路板從GT9157芯片引出VCC、GND、SCL、SDA、RSTN及INT引腳,再通過FPC座子引出到屏幕的PCB電路板中,PCB電路板加了部分電路,如I2C的上拉電阻,然后把這些引腳引出到屏幕右側的排針處,方便整個屏幕與外部器件相連。
圖 2913 電容屏接口
以上是我們STM32F429實驗板使用的5寸屏原理圖,它通過屏幕上的排針接入到實驗板的液晶排母接口,與STM32芯片的引腳相連,連接見圖 2725。
圖 2914 屏幕與實驗板的引腳連接
圖 2725中35-38號引腳即電容觸摸屏相關的控制引腳。
以上原理圖可查閱《LCD5.0-黑白原理圖》及《秉火F429開發板黑白原理圖》文檔獲知,若您使用的液晶屏或實驗板不一樣,請根據實際連接的引腳修改程序。
29.3.2 軟件設計
本工程中的GT9157芯片驅動主要是從官方提供的Linux驅動修改過來的,我們把這部分文件存儲到"gt9xx.c"及"gt9xx.h"文件中,而這些驅動的底層I2C通訊接口我們存儲到了"bsp_i2c_touch.c"及"bsp_i2c_touch.h"文件中,這些文件也可根據您的喜好命名,它們不屬於STM32標准庫的內容,是由我們自己根據應用需要編寫的。在我們提供的資料《gt9xx_1.8_drivers.zip》壓縮包里有官方的原Linux驅動,感興趣的讀者可以對比這些文件,了解如何移植驅動。
1. 編程要點
(1) 分析官方的gt9xx驅動,了解需要提供哪些底層接口;
(2) 編寫底層驅動接口;
(3) 利用gt9xx驅動,獲取觸摸坐標;
(4) 編寫測試程序檢驗驅動。
2. 代碼分析
觸摸屏硬件相關宏定義
根據觸摸屏與STM32芯片的硬件連接,我們把觸摸屏硬件相關的配置都以宏的形式定義到"bsp_i2c_touch.h"文件中,見代碼清單 242。
代碼清單 291 觸摸屏硬件配置相關的宏(bsp_i2c_touch.h文件)
1 /*設定使用的電容屏IIC設備地址*/
2 #define GTP_ADDRESS 0xBA
3
4 #define I2CT_FLAG_TIMEOUT ((uint32_t)0x1000)
5 #define I2CT_LONG_TIMEOUT ((uint32_t)(10 * I2CT_FLAG_TIMEOUT))
6
7 /*I2C引腳*/
8 #define GTP_I2C I2C2
9 #define GTP_I2C_CLK RCC_APB1Periph_I2C2
10 #define GTP_I2C_CLK_INIT RCC_APB1PeriphClockCmd
11
12 #define GTP_I2C_SCL_PIN GPIO_Pin_4
13 #define GTP_I2C_SCL_GPIO_PORT GPIOH
14 #define GTP_I2C_SCL_GPIO_CLK RCC_AHB1Periph_GPIOH
15 #define GTP_I2C_SCL_SOURCE GPIO_PinSource4
16 #define GTP_I2C_SCL_AF GPIO_AF_I2C2
17
18 #define GTP_I2C_SDA_PIN GPIO_Pin_5
19 #define GTP_I2C_SDA_GPIO_PORT GPIOH
20 #define GTP_I2C_SDA_GPIO_CLK RCC_AHB1Periph_GPIOH
21 #define GTP_I2C_SDA_SOURCE GPIO_PinSource5
22 #define GTP_I2C_SDA_AF GPIO_AF_I2C2
23
24 /*復位引腳*/
25 #define GTP_RST_GPIO_PORT GPIOD
26 #define GTP_RST_GPIO_CLK RCC_AHB1Periph_GPIOD
27 #define GTP_RST_GPIO_PIN GPIO_Pin_11
28 /*中斷引腳*/
29 #define GTP_INT_GPIO_PORT GPIOD
30 #define GTP_INT_GPIO_CLK RCC_AHB1Periph_GPIOD
31 #define GTP_INT_GPIO_PIN GPIO_Pin_13
32 #define GTP_INT_EXTI_PORTSOURCE EXTI_PortSourceGPIOD
33 #define GTP_INT_EXTI_PINSOURCE EXTI_PinSource13
34 #define GTP_INT_EXTI_LINE EXTI_Line13
35 #define GTP_INT_EXTI_IRQ EXTI15_10_IRQn
36 /*中斷服務函數*/
37 #define GTP_IRQHandler EXTI15_10_IRQHandler
以上代碼根據硬件的連接,把與觸摸屏通訊使用的引腳號、引腳源以及復用功能映射都以宏封裝起來。在這里還定義了與GT9157芯片通訊的I2C設備地址,該地址是一個8位的寫地址,它是由我們的上電時序決定的。
初始化觸摸屏控制引腳
利用上面的宏,編寫LTDC的觸摸屏控制引腳的初始化函數,見代碼清單 243。
代碼清單 292 觸摸屏控制引腳的GPIO初始化函數(bsp_i2c_touch.c文件)
1 /**
2 * @brief 觸摸屏 I/O配置
3 * @param 無
4 * @retval 無
5 */
6 static void I2C_GPIO_Config(void)
7 {
8 GPIO_InitTypeDef GPIO_InitStructure;
9
10 /*使能I2C時鍾 */
11 GTP_I2C_CLK_INIT(GTP_I2C_CLK, ENABLE);
12
13 /*使能觸摸屏使用的引腳的時鍾*/
14 RCC_AHB1PeriphClockCmd(GTP_I2C_SCL_GPIO_CLK | GTP_I2C_SDA_GPIO_CLK|
15 GTP_RST_GPIO_CLK|GTP_INT_GPIO_CLK, ENABLE);
16
17 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
18
19 /* 配置I2C_SCL源*/
20GPIO_PinAFConfig(GTP_I2C_SCL_GPIO_PORT, GTP_I2C_SCL_SOURCE, GTP_I2C_SCL_AF);
21 /* 配置I2C_SDA 源*/
22GPIO_PinAFConfig(GTP_I2C_SDA_GPIO_PORT, GTP_I2C_SDA_SOURCE, GTP_I2C_SDA_AF);
23
24 /*配置SCL引腳 */
25 GPIO_InitStructure.GPIO_Pin = GTP_I2C_SCL_PIN;
26 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
27 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
28 GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
29 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
30 GPIO_Init(GTP_I2C_SCL_GPIO_PORT, &GPIO_InitStructure);
31
32 /*配置SDA引腳 */
33 GPIO_InitStructure.GPIO_Pin = GTP_I2C_SDA_PIN;
34 GPIO_Init(GTP_I2C_SDA_GPIO_PORT, &GPIO_InitStructure);
35
36 /*配置RST引腳,下拉推挽輸出 */
37 GPIO_InitStructure.GPIO_Pin = GTP_RST_GPIO_PIN;
38 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
39 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
40 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
41 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
42 GPIO_Init(GTP_RST_GPIO_PORT, &GPIO_InitStructure);
43
44 /*配置 INT引腳,下拉推挽輸出,方便初始化 */
45 GPIO_InitStructure.GPIO_Pin = GTP_INT_GPIO_PIN;
46 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
47 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
48 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
49 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; //設置為下拉,方便初始化
50 GPIO_Init(GTP_INT_GPIO_PORT, &GPIO_InitStructure);
51 }
以上函數初始化了觸摸屏用到的I2C信號線,並且把RST及INT引腳也初始化成了下拉推挽輸出模式,以便剛上電的時候輸出上電時序,設置觸摸屏的I2C設備地址。
配置I2C的模式
接下來需要配置I2C的工作模式,GT9157芯片使用的是標准7位地址模式的I2C通訊,所以I2C這部分的配置跟我們在EEPROM實驗中的是一樣的,不了解這部分內容的請閱讀EEPROM章節,見代碼清單 244。
代碼清單 293 配置I2C工作模式(bsp_i2c_touch.c文件)
1
2 /* STM32 I2C 快速模式 */
3 #define I2C_Speed 400000
4
5 /* 這個地址只要與STM32外掛的I2C器件地址不一樣即可 */
6 #define I2C_OWN_ADDRESS7 0x0A
7
8 /**
9 * @brief I2C 工作模式配置
10 * @param 無
11 * @retval 無
12 */
13 static void I2C_Mode_Config(void)
14 {
15 I2C_InitTypeDef I2C_InitStructure;
16
17 /* I2C 模式配置 */
18 I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
19 I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
20 I2C_InitStructure.I2C_OwnAddress1 =I2C_OWN_ADDRESS7;
21 I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
22 I2C_InitStructure.I2C_AcknowledgedAddress =
23 I2C_AcknowledgedAddress_7bit; /* I2C的尋址模式 */
24 I2C_InitStructure.I2C_ClockSpeed = I2C_Speed; /* 通信速率 */
25 I2C_Init(GTP_I2C, &I2C_InitStructure); /* I2C1 初始化 */
26 I2C_Cmd(GTP_I2C, ENABLE); /* 使能 I2C1 */
27
28 I2C_AcknowledgeConfig(GTP_I2C, ENABLE);
29 }
使用上電時序設置觸摸屏的I2C地址
注:因硬件I2C在實際驅動時存在無法成功發送信號的情況,我們的范例程序中關於I2C的底層驅動已改成使用軟件I2C,其原理類似,硬件I2C的驅動在范例程序中有保留,可使用bsp_i2c_touch.h頭文件中的宏來切換。
在上面配置完成STM32的引腳后,就可以開始控制這些引腳對觸摸屏進行控制了,為了使用I2C通訊,首先要根據GT9157芯片的上電時序給它設置I2C設備地址,見代碼清單 294。
代碼清單 294使用上電時序設置觸摸屏的I2C地址(bsp_i2c_touch.c文件)
1
2 /**
3 * @brief 對GT91xx芯片進行復位
4 * @param 無
5 * @retval 無
6 */
7 void I2C_ResetChip(void)
8 {
9 GPIO_InitTypeDef GPIO_InitStructure;
10
11 /*配置 INT引腳,下拉推挽輸出,方便初始化 */
12 GPIO_InitStructure.GPIO_Pin = GTP_INT_GPIO_PIN;
13 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
14 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
15 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
16 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; //設置為下拉,方便初始化
17 GPIO_Init(GTP_INT_GPIO_PORT, &GPIO_InitStructure);
18
19 /*初始化GT9157,rst為高電平,int為低電平,則gt9157的設備地址被配置為0xBA*/
20
21 /*復位為低電平,為初始化做准備*/
22 GPIO_ResetBits (GTP_RST_GPIO_PORT,GTP_RST_GPIO_PIN);
23 Delay(0x0FFFFF);
24
25 /*拉高一段時間,進行初始化*/
26 GPIO_SetBits (GTP_RST_GPIO_PORT,GTP_RST_GPIO_PIN);
27 Delay(0x0FFFFF);
28
29 /*把INT引腳設置為浮空輸入模式,以便接收觸摸中斷信號*/
30 GPIO_InitStructure.GPIO_Pin = GTP_INT_GPIO_PIN;
31 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
32 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
33 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
34 GPIO_Init(GTP_INT_GPIO_PORT, &GPIO_InitStructure);
35 }
這段函數中控制RST引腳由低電平改變至高電平,且期間INT一直為低電平,這樣的上電時序可以控制觸控芯片的I2C寫地址為0xBA,讀地址為0xBB,即(0xBA|0x01)。輸出完上電時序后,把STM32的INT引腳模式改成浮空輸入模式,使它可以接收觸控芯片輸出的觸摸中斷信號。接下來我們在I2C_GTP_IRQEnable函數中使能INT中斷,見代碼清單 295。
代碼清單 295 使能INT中斷(bsp_i2c_touch.c文件)
1
2 /**
3 * @brief 使能觸摸屏中斷
4 * @param 無
5 * @retval 無
6 */
7 void I2C_GTP_IRQEnable(void)
8 {
9 EXTI_InitTypeDef EXTI_InitStructure;
10 NVIC_InitTypeDef NVIC_InitStructure;
11 GPIO_InitTypeDef GPIO_InitStructure;
12 /*配置 INT 為浮空輸入 */
13 GPIO_InitStructure.GPIO_Pin = GTP_INT_GPIO_PIN;
14 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
15 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
16 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
17 GPIO_Init(GTP_INT_GPIO_PORT, &GPIO_InitStructure);
18
19 /* 連接 EXTI 中斷源到INT 引腳 */
20 SYSCFG_EXTILineConfig(GTP_INT_EXTI_PORTSOURCE, GTP_INT_EXTI_PINSOURCE);
21
22 /* 選擇 EXTI 中斷源 */
23 EXTI_InitStructure.EXTI_Line = GTP_INT_EXTI_LINE;
24 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
25 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
26 EXTI_InitStructure.EXTI_LineCmd = ENABLE;
27 EXTI_Init(&EXTI_InitStructure);
28
29 /* 配置中斷優先級 */
30 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
31
32 /*使能中斷*/
33 NVIC_InitStructure.NVIC_IRQChannel = GTP_INT_EXTI_IRQ;
34 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
35 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
36 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
37 NVIC_Init(&NVIC_InitStructure);
38 }
這個INT引腳我們配置為上升沿觸發,是跟后面寫入到觸控芯片的配置參數一致的。
初始化封裝
利用以上函數,我們把信號引腳及I2C設備地址初始化的過程都封裝到函數I2C_Touch_Init中,見代碼清單 296。
代碼清單 296 封裝引腳初始化及上電時序(bsp_i2c_touch.c文件)
1
2 /**
3 * @brief I2C 外設(GT91xx)初始化
4 * @param 無
5 * @retval 無
6 */
7 void I2C_Touch_Init(void)
8 {
9 I2C_GPIO_Config();
10
11 I2C_Mode_Config();
12
13 I2C_ResetChip();
14
15 I2C_GTP_IRQEnable();
16 }
I2C基本讀寫函數
為了與上層"gt9xx.c"驅動文件中的函數對接,本實驗中的I2C讀寫函數與EEPROM實驗中的有稍微不同,見代碼清單 278。
代碼清單 297 I2C基本讀寫函數(bsp_i2c_touch.c文件)
1
2 __IO uint32_t I2CTimeout = I2CT_LONG_TIMEOUT;
3 /**
4 * @brief IIC等待超時調用本函數輸出調試信息
5 * @param None.
6 * @retval 返回0xff,表示IIC讀取數據失敗
7 */
8 static uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode)
9 {
10 /* Block communication and all processes */
11 GTP_ERROR("I2C 等待超時!errorCode = %d",errorCode);
12 return 0xFF;
13 }
14 /**
15 * @brief 使用IIC讀取數據
16 * @param
17 * @arg ClientAddr:從設備地址
18 * @arg pBuffer:存放由從機讀取的數據的緩沖區指針
19 * @arg NumByteToRead:讀取的數據長度
20 * @retval 無
21 */
22 uint32_t I2C_ReadBytes(uint8_t ClientAddr,uint8_t* pBuffer,
23 uint16_t NumByteToRead)
24 {
25 I2CTimeout = I2CT_LONG_TIMEOUT;
26
27 while (I2C_GetFlagStatus(GTP_I2C, I2C_FLAG_BUSY))
28 {
29 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(0);
30 }
31
32 /* Send STRAT condition */
33 I2C_GenerateSTART(GTP_I2C, ENABLE);
34
35 I2CTimeout = I2CT_FLAG_TIMEOUT;
36
37 /* Test on EV5 and clear it */
38 while (!I2C_CheckEvent(GTP_I2C, I2C_EVENT_MASTER_MODE_SELECT))
39 {
40 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1);
41 }
42 /* Send GT91xx address for read */
43 I2C_Send7bitAddress(GTP_I2C, ClientAddr, I2C_Direction_Receiver);
44
45 I2CTimeout = I2CT_FLAG_TIMEOUT;
46
47 /* Test on EV6 and clear it */
48 while (!I2C_CheckEvent(GTP_I2C, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
49 {
50 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);
51 }
52 /* While there is data to be read */
53 while (NumByteToRead)
54 {
55 if (NumByteToRead == 1)
56 {
57 /* Disable Acknowledgement */
58 I2C_AcknowledgeConfig(GTP_I2C, DISABLE);
59
60 /* Send STOP Condition */
61 I2C_GenerateSTOP(GTP_I2C, ENABLE);
62 }
63 I2CTimeout = I2CT_LONG_TIMEOUT;
64 while (I2C_CheckEvent(GTP_I2C, I2C_EVENT_MASTER_BYTE_RECEIVED)==0)
65 {
66 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
67 }
68 {
69 /* Read a byte from the device */
70 *pBuffer = I2C_ReceiveData(GTP_I2C);
71
72 /* Point to the next location where the byte read will be saved */
73 pBuffer++;
74
75 /* Decrement the read bytes counter */
76 NumByteToRead--;
77 }
78 }
79 /* Enable Acknowledgement to be ready for another reception */
80 I2C_AcknowledgeConfig(GTP_I2C, ENABLE);
81 return 0;
82 }
83
84
85
86
87 /**
88 * @brief 使用IIC寫入數據
89 * @param
90 * @arg ClientAddr:從設備地址
91 * @arg pBuffer:緩沖區指針
92 * @arg NumByteToWrite:寫的字節數
93 * @retval 無
94 */
95 uint32_t I2C_WriteBytes(uint8_t ClientAddr,uint8_t* pBuffer,
96 uint8_t NumByteToWrite)
97 {
98 I2CTimeout = I2CT_LONG_TIMEOUT;
99
100 while (I2C_GetFlagStatus(GTP_I2C, I2C_FLAG_BUSY))
101 {
102 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(4);
103 }
104
105 /* Send START condition */
106 I2C_GenerateSTART(GTP_I2C, ENABLE);
107
108 I2CTimeout = I2CT_FLAG_TIMEOUT;
109
110 /* Test on EV5 and clear it */
111 while (!I2C_CheckEvent(GTP_I2C, I2C_EVENT_MASTER_MODE_SELECT))
112 {
113 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(5);
114 }
115
116 /* Send GT91xx address for write */
117 I2C_Send7bitAddress(GTP_I2C, ClientAddr, I2C_Direction_Transmitter);
118
119 I2CTimeout = I2CT_FLAG_TIMEOUT;
120
121 /* Test on EV6 and clear it */
122 while(!I2C_CheckEvent(GTP_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
123 {
124 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(6);
125 }
126 /* While there is data to be written */
127 while (NumByteToWrite--)
128 {
129 /* Send the current byte */
130 I2C_SendData(GTP_I2C, *pBuffer);
131
132 /* Point to the next byte to be written */
133 pBuffer++;
134
135 I2CTimeout = I2CT_FLAG_TIMEOUT;
136
137 /* Test on EV8 and clear it */
138 while (!I2C_CheckEvent(GTP_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
139 {
140 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(7);
141 }
142 }
143 /* Send STOP condition */
144 I2C_GenerateSTOP(GTP_I2C, ENABLE);
145 return 0;
146 }
這里的讀寫函數都是很純粹的I2C通訊過程,即讀函數只有讀過程,不包含發送寄存器地址的過程,而寫函數也是只有寫過程,沒有包含寄存器的地址,大家可以對比一下它們與前面EEPROM實驗中的差別。這兩個函數都只包含從I2C的設備地址、緩沖區指針以及數據量。
Linux的I2C驅動接口
使用前面的基本讀寫函數,主要是為了對接原"gt9xx.c"驅動里使用的Linux I2C接口函數I2C_Transfer,實現了這個函數后,移植時就可以減少"gt9xx.c"文件的修改量。I2C_Transfer函數見代碼清單 298。
代碼清單 298 Linux的I2C驅動接口(gt9xx.c文件)
1
2 /* 表示讀數據 */
3 #define I2C_M_RD 0x0001
4 /*
5 * 存儲I2C通訊的信息
6 * @addr:從設備的I2C設備地址
7 * @flags: 控制標志
8 * @len:讀寫數據的長度
9 * @buf:存儲讀寫數據的指針
10 **/
11 struct i2c_msg
12 {
13 uint8_t addr; /*從設備的I2C設備地址 */
14 uint16_t flags; /*控制標志*/
15 uint16_t len; /*讀寫數據的長度 */
16 uint8_t *buf; /*存儲讀寫數據的指針 */
17 };
18
19 /**
20 * @brief 使用IIC進行數據傳輸
21 * @param
22 * @arg i2c_msg:數據傳輸結構體
23 * @arg num:數據傳輸結構體的個數
24 * @retval 正常完成的傳輸結構個數,若不正常,返回0xff
25 */
26 static int I2C_Transfer( struct i2c_msg *msgs,int num)
27 {
28 int im = 0;
29 int ret = 0;
30 //輸出調試信息,可忽略
31 GTP_DEBUG_FUNC();
32
33 for (im = 0; ret == 0 && im != num; im++)
34 {
35 //根據flag判斷是讀數據還是寫數據
36 if ((msgs[im].flags&I2C_M_RD))
37 {
38 //IIC讀取數據
39 ret = I2C_ReadBytes(msgs[im].addr, msgs[im].buf, msgs[im].len);
40 }
41 else
42 {
43 //IIC寫入數據
44 ret = I2C_WriteBytes(msgs[im].addr, msgs[im].buf, msgs[im].len);
45 }
46 }
47
48 if (ret)
49 return ret;
50
51 return im; //正常完成的傳輸結構個數
52 }
I2C_Transfer的主要輸入參數是i2c_msg結構體的指針以及要傳輸多少個這樣的結構體。i2c_msg結構體包含以下幾個成員:
(1) addr
這是從機的I2C設備地址,通訊時無論是讀方向還是寫方向,給這個成員賦值為寫地址即可(本實驗中為0xBA)。
(2) flags
這個成員存儲了控制標志,它用於指示本i2c_msg結構體要求以什么方式來傳輸。在原Linux驅動中有很多種控制方式,在我們這個工程中,只支持讀或寫控制標志,flags被賦值為I2C_M_RD宏的時候表示讀方向,其余值表示寫方向。
(3) len
本成員存儲了要讀寫的數據長度。
(4) buf
本成員存儲了指向讀寫數據緩沖區的指針。
利用這個結構體,我們再來看I2C_Transfer函數做了什么工作。
(1) 輸入參數中可能包含有多個要傳輸的i2c_msg結構體,利用for循環把這些結構體一個個地傳輸出去;
(2) 傳輸的時候根據i2c_msg結構體中的flags標志,確定應該調用I2C讀函數還是寫函數,這些函數即前面定義的I2C基本讀寫函數。調用這些函數的時候,以i2c_msg結構體的成員作為參數。
I2C復合讀寫函數
理解了I2C_Transfer函數的代碼,我們發現它還是什么都沒做,只是對I2C基本讀寫函數封裝了比較特別的調用形式而已,而我們知道GT9157觸控芯片都有很多不同的寄存器,如果我們僅用上面的函數,如何向特定寄存器寫入參數或讀取特定寄存器的內容呢?這就需要再利用I2C_Transfer函數編寫具有I2C通訊復合時序的讀寫函數了。Linux驅動進行這樣的封裝是為了讓它的核心層與具體設備獨立開來,對於這個巨型系統,這樣寫代碼是很有必要的,上述的I2C_Transfer函數屬於Linux內部的驅動層,它對外提供接口,而像GT9157、EEPROM等使用I2C的設備,都利用這個接口編寫自己具體的驅動文件,GT9157的這些I2C復合讀寫函數見代碼清單 299。
代碼清單 299 I2C復合讀寫函數(gt9xx.c文件)
1 //寄存器地址的長度
2 #define GTP_ADDR_LENGTH 2
3
4 /**
5 * @brief 從IIC設備中讀取數據
6 * @param
7 * @arg client_addr:設備地址
8 * @arg buf[0~1]: 讀取數據寄存器的起始地址
9 * @arg buf[2~len-1]: 存儲讀出來數據的緩沖buffer
10 * @arg len: GTP_ADDR_LENGTH + read bytes count(
11 寄存器地址長度+讀取的數據字節數)
12 * @retval i2c_msgs傳輸結構體的個數,2為成功,其它為失敗
13 */
14 static int32_t GTP_I2C_Read(uint8_t client_addr, uint8_t *buf,
15 int32_t len)
16 {
17 struct i2c_msg msgs[2];
18 int32_t ret=-1;
19 int32_t retries = 0;
20
21 //輸出調試信息,可忽略
22 GTP_DEBUG_FUNC();
23 /*一個讀數據的過程可以分為兩個傳輸過程:
24 * 1. IIC 寫入要讀取的寄存器地址
25 * 2. IIC 讀取數據
26 * */
27
28 msgs[0].flags = !I2C_M_RD; //寫入
29 msgs[0].addr = client_addr; //IIC設備地址
30 msgs[0].len = GTP_ADDR_LENGTH; //寄存器地址為2字節(即寫入兩字節的數據)
31 msgs[0].buf = &buf[0]; //buf[0~1]存儲的是要讀取的寄存器地址
32
33 msgs[1].flags = I2C_M_RD; //讀取
34 msgs[1].addr = client_addr; //IIC設備地址
35 msgs[1].len = len - GTP_ADDR_LENGTH; //要讀取的數據長度
36 msgs[1].buf = &buf[GTP_ADDR_LENGTH]; //buf[GTP_ADDR_LENGTH]之后的緩沖區存儲讀出的數據
37
38 while (retries < 5) //
39 {
40 ret = I2C_Transfer( msgs, 2); //調用IIC數據傳輸過程函數,有2個傳輸過程
41 if (ret == 2)break;
42 retries++;
43 }
44 if ((retries >= 5))
45 {
46 //發送失敗,輸出調試信息
47 GTP_ERROR("I2C Read Error");
48 }
49 return ret;
50 }
51
52 /**
53 * @brief 向IIC設備寫入數據
54 * @param
55 * @arg client_addr:設備地址
56 * @arg buf[0~1]: 要寫入的數據寄存器的起始地址
57 * @arg buf[2~len-1]: 要寫入的數據
58 * @arg len: GTP_ADDR_LENGTH + write bytes count(
59 寄存器地址長度+寫入的數據字節數)
60 * @retval i2c_msgs傳輸結構體的個數,1為成功,其它為失敗
61 */
62 static int32_t GTP_I2C_Write(uint8_t client_addr,uint8_t *buf,
63 int32_t len)
64 {
65 struct i2c_msg msg;
66 int32_t ret = -1;
67 int32_t retries = 0;
68
69 //輸出調試信息,可忽略
70 GTP_DEBUG_FUNC();
71 /*一個寫數據的過程只需要一個傳輸過程:
72 * 1. IIC連續寫入數據寄存器地址及數據
73 * */
74 msg.flags = !I2C_M_RD; //寫入
75 msg.addr = client_addr; //從設備地址
76 msg.len = len; //長度直接等於(寄存器地址長度+寫入的數據字節數)
77 msg.buf = buf; //直接連續寫入緩沖區中的數據(包括了寄存器地址)
78
79 while (retries < 5)
80 {
81 ret = I2C_Transfer(&msg, 1); //調用IIC數據傳輸過程函數,1個傳輸過程
82 if (ret == 1)break;
83 retries++;
84 }
85 if ((retries >= 5))
86 {
87 //發送失敗,輸出調試信息
88 GTP_ERROR("I2C Write Error");
89 }
90 return ret;
91 }
可以看到,復合讀寫函數都包含有client_addr、buf及len輸入參數,其中client_addr表示I2C的設備地址,buf存儲了要讀寫的寄存器地址及數據,len表示buf的長度。在函數的內部處理中,復合讀寫過程被分解成兩個基本的讀寫過程,輸入參數被轉化存儲到i2c_msg結構體中,每個基本讀寫過程使用一個i2c_msg結構體來表示,見表 292和表 293。
表 292 復合讀過程的步驟分解
復合讀過程的步驟分解 |
說明 |
傳輸寄存器地址 |
這相當於一個I2C的基本寫過程,寫入一個2字節長度的寄存器地址,buf指針的前兩個字節內容被解釋為寄存器地址。 |
從寄存器讀取內容 |
這是一個I2C的基本讀過程,讀取到的數據存儲到buf指針的第3個地址開始的空間中。 |
表 293 復合寫過程的步驟分解
復合寫過程的步驟分解 |
說明 |
傳輸寄存器地址 |
這相當於一個I2C的基本寫過程,寫入一個2字節長度的寄存器地址,buf指針的前兩個字節內容被解釋為寄存器地址。 |
向寄存器寫入內容 |
這也是一個I2C的基本寫過程,寫入的數據為buf指針的第3個地址開始的內容。 |
復合過程的分解主要是針對寄存器地址傳輸和實際數據傳輸來划分的,調用這兩個復合讀寫過程的時候,我們需要注意buf的前兩個字節為寄存器地址,且len的長度為buf的整體長度。
讀取觸控芯片的產品ID及版本號
利用上述復合讀寫函數,我們就可以使用I2C控制觸控芯片了,首先是最簡單的讀取版本函數,見代碼清單 2910。
代碼清單 2910讀取觸控芯片的產品ID及版本號(gt9xx.c文件)
1
2 /*設定使用的電容屏IIC設備地址*/
3 #define GTP_ADDRESS 0xBA
4 //芯片版本號地址
5 #define GTP_REG_VERSION 0x8140
6
7 /*******************************************************
8 Function:
9 Read chip version.
10 Input:
11 client: i2c device
12 version: buffer to keep ic firmware version
13 Output:
14 read operation return.
15 2: succeed, otherwise: failed
16 *******************************************************/
17 int32_t GTP_Read_Version(void)
18 {
19 int32_t ret = -1;
20 //寄存器地址
21 uint8_t buf[8] = {GTP_REG_VERSION >> 8, GTP_REG_VERSION & 0xff};
22 //輸出調試信息,可忽略
23 GTP_DEBUG_FUNC();
24
25 ret = GTP_I2C_Read(GTP_ADDRESS, buf, sizeof(buf));
26 if (ret < 0)
27 {
28 GTP_ERROR("GTP read version failed");
29 return ret;
30 }
31
32 if (buf[5] == 0x00)
33 {
34 GTP_INFO("IC Version: %c%c%c_%02x%02x",
35 buf[2], buf[3], buf[4], buf[7], buf[6]);
36 }
37 else
38 {
39 GTP_INFO("IC Version: %c%c%c%c_%02x%02x",
40 buf[2], buf[3], buf[4], buf[5], buf[7], buf[6]);
41 }
42 return ret;
43 }
這個函數定義了一個8字節的buf數組,並且向它的第0和第1個元素寫入產品ID寄存器的地址,然后調用復合讀取函數,即可從芯片中讀取這些寄存器的信息,結果使用宏GTP_INFO輸出。
向觸控芯片寫入配置參數
萬事俱備,現在我們可以使用I2C向觸摸芯片寫入寄存器配置了,見代碼清單 2911。
代碼清單 2911 初始化並向觸控芯片寫入配置參數(gt9xx.c文件)
1 // 5寸屏GT9157驅動配置
2 uint8_t CTP_CFG_GT9157[] ={
3 0x00,0x20,0x03,0xE0,0x01,0x05,0x3C,0x00,0x01,0x08,
4 0x28,0x0C,0x50,0x32,0x03,0x05,0x00,0x00,0x00,0x00,
5 /*...部分內容省略...*/
6 0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
7 0xFF,0xFF,0xFF,0xFF,0x48,0x01
8 };
9
10 // 7寸屏GT911驅動配置
11 uint8_t CTP_CFG_GT911[] = {
12 0x00,0x20,0x03,0xE0,0x01,0x05,0x3D,0x00,0x01,0x48,
13 0x28,0x0D,0x50,0x32,0x03,0x05,0x00,0x00,0x00,0x00,
14 /*...部分內容省略...*/
15 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
16 0x00,0x00,0x00,0x00,0x11,0x01
17 };
18
19
20 uint8_t config[GTP_CONFIG_MAX_LENGTH + GTP_ADDR_LENGTH]
21 = {GTP_REG_CONFIG_DATA >> 8, GTP_REG_CONFIG_DATA & 0xff};
22
23 TOUCH_IC touchIC;
24
25
26 /*******************************************************
27 Function:
28 Initialize gtp.
29 Input:
30 ts: goodix private data
31 Output:
32 Executive outcomes.
33 0: succeed, otherwise: failed
34 *******************************************************/
35 int32_t GTP_Init_Panel(void)
36 {
37 int32_t ret = -1;
38
39 int32_t i = 0;
40 uint8_t check_sum = 0;
41 int32_t retry = 0;
42
43 uint8_t* cfg_info;
44 uint8_t cfg_info_len ;
45
46 uint8_t cfg_num =0x80FE-0x8047+1 ; //需要配置的寄存器個數
47
48 GTP_DEBUG_FUNC();
49
50 I2C_Touch_Init();
51
52 ret = GTP_I2C_Test();
53 if (ret < 0)
54 {
55 GTP_ERROR("I2C communication ERROR!");
56 return ret;
57 }
58
59 //獲取觸摸IC的型號
60 GTP_Read_Version();
61
62 //根據IC的型號指向不同的配置
63 if(touchIC == GT9157)
64 {
65 cfg_info = CTP_CFG_GT9157; //指向寄存器配置
66 cfg_info_len = CFG_GROUP_LEN(CTP_CFG_GT9157);//計算配置表的大小
67 }
68 else
69 {
70 cfg_info = CTP_CFG_GT911;//指向寄存器配置
71 cfg_info_len = CFG_GROUP_LEN(CTP_CFG_GT911) ;//計算配置表的大小
72 }
73
74 memset(&config[GTP_ADDR_LENGTH], 0, GTP_CONFIG_MAX_LENGTH);
75 memcpy(&config[GTP_ADDR_LENGTH], cfg_info, cfg_info_len);
76
77 //計算要寫入checksum寄存器的值
78 check_sum = 0;
79 for (i = GTP_ADDR_LENGTH; i < cfg_num+GTP_ADDR_LENGTH; i++)
80 {
81 check_sum += config[i];
82 }
83 config[ cfg_num+GTP_ADDR_LENGTH] = (~check_sum) + 1; //checksum
84 config[ cfg_num+GTP_ADDR_LENGTH+1] = 1; //refresh 配置更新標志
85
86 //寫入配置信息
87 for (retry = 0; retry < 5; retry++)
88 {
89 ret = GTP_I2C_Write(GTP_ADDRESS, config , cfg_num + GTP_ADDR_LENGTH+2);
90 if (ret > 0)
91 {
92 break;
93 }
94 }
95 Delay(0xfffff); //延遲等待芯片更新
96
97 /*使能中斷,這樣才能檢測觸摸數據*/
98 I2C_GTP_IRQEnable();
99
100 GTP_Get_Info();
101
102 return 0;
103 }
這段代碼調用I2C_Touch_Init初始化了STM32的I2C外設,設定觸控芯片的I2C設備地址,然后調用了GTP_Read_Version嘗試獲取觸控芯片的版本號。接下來是函數的主體,它使用GTP_I2C_Write函數通過I2C把配置參數表CTP_CFG_GT9157(5寸屏)或CTP_CFG_GT911(7寸屏)寫入到觸控芯片的的配置寄存器中,注意傳輸中包含有checksum寄存器的值。寫入完參數后調用I2C_GTP_IRQEnable以使能INT引腳檢測中斷。
INT中斷服務函數
經過上面的函數初始化后,觸摸屏就可以開始工作了,當觸摸時,INT引腳會產生觸摸中斷,會進入中斷服務函數GTP_IRQHandler,見代碼清單 2912。
代碼清單 2912 觸摸屏的中斷服務函數(stm32f4xx_it.c文件)
1 //觸摸屏中斷服務函數
2 void GTP_IRQHandler(void)
3 {
4 //確保是否產生了EXTI Line中斷
5 if (EXTI_GetITStatus(GTP_INT_EXTI_LINE) != RESET)
6 {
7 GTP_TouchProcess();
8 EXTI_ClearITPendingBit(GTP_INT_EXTI_LINE); //清除中斷標志位
9 }
10 }
中斷服務函數只是簡單地調用了GTP_TouchProcess函數,它是讀取觸摸坐標的主體。
讀取坐標數據
GTP_TouchProcess函數的內容見代碼清單 2913。
代碼清單 2913 GTP_TouchProcess坐標讀取函數
1
2 /*狀態寄存器地址*/
3 #define GTP_READ_COOR_ADDR 0x814E
4
5 /**
6 * @brief 觸屏處理函數,輪詢或者在觸摸中斷調用
7 * @param 無
8 * @retval 無
9 */
10 static void Goodix_TS_Work_Func(void)
11 {
12 uint8_t end_cmd[3] = {GTP_READ_COOR_ADDR >> 8, GTP_READ_COOR_ADDR & 0xFF, 0};
13 uint8_t point_data[2 + 1 + 8 * GTP_MAX_TOUCH + 1]= {GTP_READ_COOR_ADDR >> 8,
14 GTP_READ_COOR_ADDR & 0xFF };
15
16 uint8_t touch_num = 0;
17 uint8_t finger = 0;
18 static uint16_t pre_touch = 0;
19 static uint8_t pre_id[GTP_MAX_TOUCH] = {0};
20
21 uint8_t client_addr=GTP_ADDRESS;
22 uint8_t* coor_data = NULL;
23 int32_t input_x = 0;
24 int32_t input_y = 0;
25 int32_t input_w = 0;
26 uint8_t id = 0;
27
28 int32_t i = 0;
29 int32_t ret = -1;
30
31 GTP_DEBUG_FUNC();
32
33 ret = GTP_I2C_Read(client_addr, point_data, 12);//10字節寄存器加2字節地址
34 if (ret < 0)
35 {
36 GTP_ERROR("I2C transfer error. errno:%d\n ", ret);
37 return;
38 }
39
40 finger = point_data[GTP_ADDR_LENGTH];//狀態寄存器數據
41
42 if (finger == 0x00) //沒有數據,退出
43 {
44 return;
45 }
46
47 if ((finger & 0x80) == 0) //判斷buffer status位
48 {
49 goto exit_work_func;//坐標未就緒,數據無效
50 }
51
52 touch_num = finger & 0x0f;//坐標點數
53 if (touch_num > GTP_MAX_TOUCH)
54 {
55 goto exit_work_func;//大於最大支持點數,錯誤退出
56 }
57
58 if (touch_num > 1)//不止一個點
59 {
60 uint8_t buf[8 * GTP_MAX_TOUCH] = {(GTP_READ_COOR_ADDR + 10) >> 8,
61 (GTP_READ_COOR_ADDR + 10) & 0xff};
62
63
64 ret = GTP_I2C_Read(client_addr, buf, 2 + 8 * (touch_num - 1));
65 //復制其余點數的數據到point_data
memcpy(&point_data[12], &buf[2], 8 * (touch_num - 1));
66 }
67
68 if (pre_touch>touch_num) //pre_touch>touch_num,表示有的點釋放了
69 {
70 for (i = 0; i < pre_touch; i++) //一個點一個點處理
71 {
72 uint8_t j;
73 for (j=0; j<touch_num; j++)
74 {
75 coor_data = &point_data[j * 8 + 3];
76 id = coor_data[0] & 0x0F; //track id
77 if (pre_id[i] == id)
78 break;
79
80 if (j >= touch_num-1)//遍歷當前所有id都找不到pre_id[i],表示已釋放
81 {
82 GTP_Touch_Up( pre_id[i]);
83 }
84 }
85 }
86 }
87
88 if (touch_num)
89 {
90 for (i = 0; i < touch_num; i++) //一個點一個點處理
91 {
92 coor_data = &point_data[i * 8 + 3];
93
94 id = coor_data[0] & 0x0F; //track id
95 pre_id[i] = id;
96
97 input_x = coor_data[1] | (coor_data[2] << 8); //x坐標
98 input_y = coor_data[3] | (coor_data[4] << 8); //y坐標
99 input_w = coor_data[5] | (coor_data[6] << 8); //size
100
101 {
102 GTP_Touch_Down( id, input_x, input_y, input_w);//數據處理
103 }
104 }
105 }
106 else if (pre_touch) //touch_ num=0 且pre_touch!=0
107 {
108 for (i=0; i<pre_touch; i++)
109 {
110 GTP_Touch_Up(pre_id[i]);
111 }
112 }
113
114 pre_touch = touch_num;
115
116 exit_work_func:
117 {
118 ret = GTP_I2C_Write(client_addr, end_cmd, 3);
119 if (ret < 0)
120 {
121 GTP_INFO("I2C write end_cmd error!");
122 }
123 }
124 }
這個函數的內容比較長,它首先是讀取了狀態寄存器,獲當前有多少個觸點,然后根據觸點數去讀取各個點的數據,其中還有包含有pre_touch點的處理,pre_touch保存了上一次的觸點數據,利用這些數據和觸點的track id號,可以確認同一條筆跡。這個讀取過程完畢后,還對狀態寄存器的buffer status位寫0,結束讀取。在實際應用中,我們並不需要掌握這個Goodix_TS_Work_Func函數的所有細節,因為在這個函數中提供了兩個坐標獲取接口,我們只要在這兩個接口中修改即可簡單地得到坐標信息。
觸點釋放和觸點按下的坐標接口
Goodix_TS_Work_Func函數中獲取到新的坐標數據時會調用觸點釋放和觸點按下這兩個函數,我們只要在這兩個函數中添加自己的坐標處理過程即可,見代碼清單 2914。
代碼清單 2914觸點釋放和觸點按下的坐標接口(gt9xx.c文件)
1
2 /**
3 * @brief 用於處理或報告觸屏檢測到按下
4 * @param
5 * @arg id: 觸摸順序trackID
6 * @arg x: 觸摸的 x 坐標
7 * @arg y: 觸摸的 y 坐標
8 * @arg w: 觸摸的大小
9 * @retval 無
10 */
11 /*用於記錄連續觸摸時(長按)的上一次觸摸位置,負數值表示上一次無觸摸按下*/
12 static int16_t pre_x[GTP_MAX_TOUCH] = {-1,-1,-1,-1,-1};
13 static int16_t pre_y[GTP_MAX_TOUCH] = {-1,-1,-1,-1,-1};
14
15 static void GTP_Touch_Down(int32_t id,int32_t x,int32_t y,int32_t w)
16 {
17
18 GTP_DEBUG_FUNC();
19
20 /*取x、y初始值大於屏幕像素值*/
21 GTP_DEBUG("ID:%d, X:%d, Y:%d, W:%d", id, x, y, w);
22
23 /* 處理觸摸按鈕,用於觸摸畫板 */
24 Touch_Button_Down(x,y);
25 /*處理描繪軌跡,用於觸摸畫板 */
26 Draw_Trail(pre_x[id],pre_y[id],x,y,&brush);
27
28 /************************************/
29 /*在此處添加自己的觸摸點按下時處理過程即可*/
30 /* (x,y) 即為最新的觸摸點 *************/
31 /************************************/
32
33 /*prex,prey數組存儲上一次觸摸的位置,id為軌跡編號(多點觸控時有多軌跡)*/
34 pre_x[id] = x;
35 pre_y[id] =y;
36
37 }
38
39
40 /**
41 * @brief 用於處理或報告觸屏釋放
42 * @param 釋放點的id號
43 * @retval 無
44 */
45 static void GTP_Touch_Up( int32_t id)
46 {
47 /*處理觸摸釋放,用於觸摸畫板*/
48 Touch_Button_Up(pre_x[id],pre_y[id]);
49
50 /*****************************************/
51 /*在此處添加自己的觸摸點釋放時的處理過程即可*/
52 /* pre_x[id],pre_y[id] 即為最新的釋放點 ****/
53 /*******************************************/
54 /***id為軌跡編號(多點觸控時有多軌跡)********/
55
56
57 /*觸筆釋放,把pre xy 重置為負*/
58 pre_x[id] = -1;
59 pre_y[id] = -1;
60
61 GTP_DEBUG("Touch id[%2d] release!", id);
62 }
以上是我們工程中對這兩個接口的應用,我們把觸摸畫板的坐標處理過程直接放到接口里了,大家可參考我們的演示,在函數的注釋部分,根據自己的應用編寫坐標處理過程。
注意這兩個坐標接口都還是在中斷服務函數里調用的(中斷服務函數調用Goodix_TS_Work_Func函數,該函數再調用這兩個坐標接口),實際應用中可以先把這些坐標信息存儲起來,等待到系統空閑的時候再處理,就可以減輕中斷服務程序的負擔了。
3. main函數
完成了觸摸屏的驅動,就可以應用了,以下我們來看工程的主體main函數,見代碼清單 2414。
代碼清單 2915 main函數
1
2 /**
3 * @brief 主函數
4 * @param 無
5 * @retval 無
6 */
7 int main(void)
8 {
9 /* LED 端口初始化 */
10 LED_GPIO_Config();
11
12 Debug_USART_Config();
13 printf("\r\n秉火STM3F429 觸摸畫板測試例程\r\n");
14
15 /* 初始化觸摸屏 */
16 GTP_Init_Panel();
17
18 /*初始化液晶屏*/
19 LCD_Init();
20 LCD_LayerInit();
21 LTDC_Cmd(ENABLE);
22
23 /*把背景層刷黑色*/
24 LCD_SetLayer(LCD_BACKGROUND_LAYER);
25 LCD_Clear(LCD_COLOR_BLACK);
26
27 /*初始化后默認使用前景層*/
28 LCD_SetLayer(LCD_FOREGROUND_LAYER);
29 /*默認設置不透明,該函數參數為不透明度,范圍 0-0xff ,0為全透明,0xff為不透明*/
30 LCD_SetTransparency(0xFF);
31 LCD_Clear(LCD_COLOR_BLACK);
32
33 /*調用畫板初始化函數*/
34 Palette_Init();
35
36 Delay(0xfff);
37
38 while (1);
39 }
main函數初始化觸摸屏、液晶屏后,調用了Palette_Init函數初始化了觸摸畫板應用,關於觸摸畫板應用的內容在"palette.c"及"palette.h"文件中,這些都是與STM32無關上層應用,感興趣的讀者可在工程中閱讀,本教程就不講解這些內容了。
下載驗證
編譯程序下載到實驗板,並上電復位,液晶屏會顯示出觸摸畫板的界面,點擊屏幕可以在該界面畫出簡單的圖形。
29.4 每課一問
1、為什么使用電阻式觸摸屏需要校准,而電容式觸摸屏不需要校准。
答:電阻屏是通過檢測觸點處的電壓來確定位置的,電壓受到電阻材料的影響,而生產中不同批次的電阻材料可能會有偏差,因此需要先定位幾個點來確定屏幕的偏移量(也就是校准),以后通過校准得來的偏移量調整坐標輸出,才能准確通過電壓反映坐標。 而電容屏是直接由多個電容組成的矩陣,檢測時可獲知整個電容矩陣中哪些電容發生了改變,而且各個電容在生產時就確認了它在觸摸屏中的坐標,所以只要獲知哪些電容發生了變化,就可直接得出觸點位置,無須校准。