產品推薦
最新資訊
聯系我們
ALIENTEK 阿波羅 STM32F767 開發板資料連載第十八章 TFTLCD實驗
- 編輯 :
廣東TFT屏幕
時間 : 2021-12-22 15:27 瀏覽量 : 50 -
1)試驗服務平臺:alientek 阿波羅 STM32F767 單片機開發板2)節選自《STM32F7 開發設計指引(HAL 庫版)》關心官微號微信公眾號,獲得大量材料:正點原子
第十八章 TFTLCD(MCU 屏)試驗
在第 16 章大家詳細介紹了 OLED 模塊以及顯示,可是該模塊只有顯示純色/兩色,不可以顯示彩
色,并且規格也較小。此章大家將詳細介紹 ALIENTEK 的 TFT LCD 模塊(MCU 屏),該模塊選用
TFTLCD 控制面板,可以顯示 16 位色的真彩照片。在這章中,大家將應用阿波羅 STM32F767 開發設計
板底版上的 TFTLCD 插口(僅適用 MCU 屏,此章僅詳細介紹 MCU 屏的應用),來照亮 TFTLCD,
并完成 ASCII 標識符和彩色的顯示等作用,并在串口通信打印出 LCD 控制板 ID,與此同時在 LCD 上邊顯示。
此章分成如下所示一些一部分:
18.1 TFTLCD&FMC 介紹
18.2 硬件開發
18.3 軟件開發
18.4 在線下載認證
18.5 STM32CubeMX 配備 FMC(SRAM)
18.1 TFTLCD&FMC 介紹
此章大家將根據 STM32F767的 FMC 插口來操縱TFTLCD 的顯示,因此這節分成2個一部分,
各自詳細介紹 TFTLCD 和 FMC。
18.1.1 TFTLCD 介紹
TFT-LCD 即塑料薄膜晶體三極管液晶顯示器。其英語全稱之為:Thin Film Transistor-Liquid Crystal
Display。TFT-LCD 與微波感應器 TN-LCD、STN-LCD 的簡易引流矩陣不一樣,它在液晶顯示屏的每一個象
素上面設定有一個塑料薄膜晶體三極管(TFT),可合理地擺脫非選通時的串擾,使顯示液晶顯示屏的靜態數據特
性與掃描線數不相干,因而進一步提高了圖象品質。TFT-LCD 也被稱為真彩液晶顯示器。
上一章詳細介紹了 OLED 模塊,此章,大家給各位詳細介紹 ALIENTEK TFTLCD 模塊(MCU 插口),
該模塊有以下特性:
1,2.8’/3.5’/4.3’/7’等 4 種尺寸的顯示屏可選。
2,320×240 的屏幕分辨率(3.5’屏幕分辨率為:320*480,4.3’和 7’屏幕分辨率為:800*480)。
3,16 位真彩顯示。
4,內置觸摸顯示屏,可以拿來做為操縱鍵入。
此章,大家以 2.8 寸(別的 3.5 寸/4.3 寸等 LCD 方式相近,請參照 2.8 的就可以)的 ALIENTEK
TFTLCD 模塊為例子詳細介紹,該模塊適用 65K 色顯示,顯示屏幕分辨率為 320×240,插口為 16 位的 80
并口,內置觸摸顯示屏。
該模塊的外型圖如下圖 18.1.1.1 所顯示
模塊電路原理圖如下圖 18.1.1.2 所顯示:
TFTLCD 模塊選用 2*17 的 2.54 公排針與外界聯接,接口定義如下圖 18.1.1.3 所顯示:
從圖 18.1.1.3 可以看得出,ALIENTEK TFTLCD 模塊選用 16 位的并方法與外界聯接,往往
不選用 8 位的方法,是由于顯示屏的信息量較為大,特別是在在顯示照片的情況下,假如用 8 位手機充電線,
便會比 16 位方法慢一倍以上,大家自然期待速率越是快就越好,因此大家挑選 16 位的插口。圖
18.1.1.3 還列舉了觸摸顯示屏集成ic的插口,有關觸摸顯示屏此章大家很少詳細介紹,后邊的章節目錄會出現詳盡的介
紹。該模塊的 80 并口有以下一些電源線:
CS:TFTLCD 片選數據信號。
WR:向 TFTLCD 載入數據信息。
RD:從 TFTLCD 接收數據。
D[15:0]:16 位雙重手機充電線。
RST:硬校準 TFTLCD。
RS:指令/數據信息標示(0,讀寫能力指令;1,讀寫能力數據信息)。
80 并口在上一節大家早已有完整的講解了,這兒大家就不會再詳細介紹,必須表明的是,TFTLCD
模塊的 RST 電源線是立即收到 STM32F767 的校準腳底,并不由自主APP操縱,那樣可以省出來一
個 IO 口。此外大家還要一個led背光控線來操縱 TFTLCD 的led背光。因此,大家一共必須的 IO
口數額為 21 個。這兒還要留意,大家標明的 DB1~DB8,DB10~DB17,是相比于 LCD 操縱
IC 標明的,事實上大伙兒可以把她們就相當于 D0~D15,那樣解釋起來就相對簡單一點。
ALIENTEK給予 2.8/3.5/4.3/7 寸等 4種不一樣規格和分辯率的TFTLCD 模塊,其推動集成ic為:
ILI9341/NT35310/NT35510/SSD1963 等(實際的型號規格,大伙兒可以根據在線下載此章試驗編碼,根據串
口或是 LCD 顯示查詢),這兒大家僅以 ILI9341 控制板為例子開展詳細介紹,別的的操縱基本上都相近,
大家也不詳盡論述了。
ILI9341 液晶控制板內置獨顯存儲,其獨顯存儲總尺寸為 172800(240*320*18/8),即 18 位方式(26
萬色)下的顯總量。在 16 位方式下,ILI9341 選用 RGB565 文件格式儲存色調數據信息,這時 ILI9341
的 18 位手機充電線與 MCU 的 16 位手機充電線及其 LCD GRAM 的對應關系如下圖 18.1.1.4 所顯示:
從圖內可以看得出,ILI9341 在 16 位方式下邊,手機充電線有效的是:D17~D13 和 D11~D1,D0
和 D12 沒有使用,事實上在大家 LCD 模塊里邊,ILI9341 的 D0 和 D12 根本就沒有引過來,這
樣,ILI9341 的 D17~D13 和 D11~D1 相匹配 MCU 的 D15~D0。
那樣 MCU 的 16 位數據信息,最少 5 位意味著深藍色,正中間 6 位為翠綠色,最大 5 位為鮮紅色。標值越
大,表明該色調越重。此外,需注意 ILI9341 全部的命令全是 8 位的(高 8 位失效),且主要參數
除開讀寫能力 GRAM 的那時候是 16 位,別的實際操作主要參數,全是 8 位的。
下面,大家介紹一下 ILI9341 的一些關鍵指令,由于 ILI9341 的指令許多,大家這兒就
不所有詳細介紹了,有感興趣的各位可以尋找 ILI9341 的 datasheet 看一下。里邊對這種指令有完整的介
紹。大家將詳細介紹:0XD3,0X36,0X2A,0X2B,0X2C,0X2E 等 6 條命令。
最先看來命令:0XD3,這個是讀 ID4 命令,用以載入 LCD 控制板的 ID,該命令如表 18.1.1.1
所顯示:
從以上可以看得出,0XD3 命令后邊跟了 4 個主要參數,最終 2 個主要參數,讀出是 0X93 和 0X41,
恰好是大家控制板 ILI9341 的小數一部分,進而,根據該命令,就可以辨別常用的 LCD 控制器是什
么型號規格,那樣,大家的編碼,就可以依據控制板的型號規格去實行相匹配推動 IC 的復位編碼,進而
兼容不一樣推動 IC 的屏,促使一個編碼適用幾款 LCD。
下面看命令:0X36,這也是儲存瀏覽程序控制,可以操縱 ILI9341 儲存器的讀寫能力方位,簡
單的說,便是在持續寫 GRAM 的情況下,可以操縱 GRAM 表針的提高方位,進而操縱顯示方法
(讀 GRAM 也是一樣)。該命令如表 18.1.1.2 所顯示:
從以上可以看得出,0X36 命令后邊,緊隨一個主要參數,這兒大家關鍵關心:MY、MX、MV
這三個位,根據這三個位的設定,我們可以操縱全部 ILI9341 的所有掃描儀方位,如表 18.1.1.3
所顯示:
那樣,我們在運用 ILI9341 顯示內容的情況下,就會有非常大靈敏性了,例如顯示 BMP 照片,
BMP 編解碼數據信息,就是以照片的左下方逐漸,漸漸地顯示到右上方,假如設定 LCD 掃描儀方位為從
左到右,從下向上,那麼大家只必須設定一次座標,隨后就不斷的往 LCD 填充顏色數據信息就可以,
那樣可以進一步提高顯示速率。
下面看命令:0X2A,這也是列詳細地址設定命令,在從左往右,自上而下的掃描儀方法(默認設置)
下邊,該命令用以設定橫坐標軸(x 座標),該命令如表 18.1.1.4 所顯示:
在默認設置掃描儀方法時,該命令用以設定 x 座標,該命令含有 4 個主要參數,事實上是 2 個平面坐標:
SC 和 EC,即列詳細地址的起始值和完畢值,SC 務必不大于 EC,且 0≤SC/EC≤239。一般在設
置 x 座標的情況下,大家只必須帶 2 個主要參數就可以,也就是設定 SC 就可以,由于假如 EC 沒有轉變,
大家只必須設定一次就可以(在復位 ILI9341 的過程中設定),進而提高速度。
與 0X2A 命令相近,命令:0X2B,是頁詳細地址設定命令,在從左往右,自上而下的掃描儀方法
(默認設置)下邊,該命令用以設定縱軸(y 座標)。該命令如表 18.1.1.5 所顯示:
在默認設置掃描儀方法時,該命令用以設定 y 座標,該命令含有 4 個主要參數,事實上是 2 個平面坐標:
SP 和 EP,即頁詳細地址的起始值和完畢值,SP 務必不大于 EP,且 0≤SP/EP≤319。一般在設定
y 座標的情況下,大家只必須帶 2 個主要參數就可以,也就是設定 SP 就可以,由于假如 EP 沒有轉變,我
們只必須設定一次就可以(在復位 ILI9341 的過程中設定),進而提高速度。
下面看命令:0X2C,該命令是寫 GRAM 命令,在推送該命令以后,大家便可以往 LCD
的 GRAM 里邊載入色調數據信息了,該命令適用持續寫,命令敘述如表 18.1.1.6 所顯示:
從以上得知,在接到命令 0X2C 以后,數據信息合理位寬變成 16 位,我們可以持續載入 LCD
GRAM 值,而 GRAM 的詳細地址將依據 MY/MX/MV 設定的掃描儀方位開展自增。比如:假定設定
的是從左往右,自上而下的掃描儀方法,那麼設定好起止座標(根據 SC,SP 設定)后,每載入
一個顏色值,GRAM 詳細地址可能全自動自增 1(SC ),假如遇到 EC,則返回 SC,與此同時 SP ,一
直到座標:EC,EP 完畢,期間不用再度設定的座標,進而進一步提高載入速率。
最終,一起來看看命令:0X2E,該命令是讀 GRAM 命令,用以載入 ILI9341 的獨顯存儲(GRAM),
該命令在 ILI9341 的數據信息指南上邊的敘述是不正確的,真正的導出狀況如表 18.1.1.7 所顯示:
該命令用以載入 GRAM,如表 18.1.1.7 所顯示,ILI9341 在得到該命令后,第一次導出的是
dummy 數據信息,也就是失效的數據信息,第二次逐漸,載入到的才算是合理的 GRAM 數據信息(從座標:
SC,SP 逐漸),導出規律性為:每一個色調份量占 8 個位數,一次導出 2 個色調份量。例如:第一次
導出是 R1G1,接著的規律性為:B1R2?G2B2?R3G3?B3R4?G4B4?R5G5... 依此類推。假如
大家只必須載入一個點的顏色值,那麼只必須接受到主要參數 3 就可以,假如要持續載入(運用 GRAM
詳細地址自增,方式跟上面一樣),那麼就依照以上規律性去接受色調數據信息。
以上,便是實際操作 ILI9341 常見的好多個命令,根據這好多個命令,大家便可以不錯的操縱 ILI9341
顯示大家所要顯示的內容了。
一般 TFTLCD 模塊的應用步驟如下圖 18.1.1.5:
一切 LCD,應用步驟都能夠簡易的用以上流程表表明。在其中硬校準和復位編碼序列,只必須
實行一次就可以。而畫點步驟便是:設定座標?寫 GRAM 命令?載入色調數據信息,隨后在 LCD 上
面,大家就可以見到相應的點顯示大家載入的色調了。讀點步驟為:設定座標?讀 GRAM 命令
?載入色調數據信息,那樣就可以獲得到對應的點的色調數據信息了。
以上僅僅非常簡單的實際操作,也是最常見的實際操作,擁有這種實際操作,一般就可以正常的應用 TFTLCD
了。下面人們將該模塊用于來顯示標識符和數據,根據以上詳細介紹,我們可以得到 TFTLCD 顯示
必須的有關設定流程如下所示:
1)設定 STM32F767 與 TFTLCD 模塊相連接的 IO。
這一步,先將我們與 TFTLCD 模塊相連的 IO 口進行初始化,以便驅動 LCD。這里我們用
到的是 FMC,FMC 將在 18.1.2 節向大家詳細介紹。
2)初始化 TFTLCD 模塊。
即圖 18.1.1.5 的初始化序列,這里我們沒有硬復位 LCD,因為阿波羅 STM32F767 開發板
的 LCD 接口,將 TFTLCD 的 RST 同 STM32F767 的 RESET 連接在一起了,只要按下開發板的
RESET 鍵,就會對 LCD 進行硬復位。初始化序列,就是向 LCD 控制器寫入一系列的設置值(比
如伽馬校準),這些初始化序列一般 LCD 供應商會提供給客戶,我們直接使用這些序列即可,
不需要深入研究。在初始化之后,LCD 才可以正常使用。
3)通過函數將字符和數字顯示到 TFTLCD 模塊上。
這一步則通過圖 18.1.1.5 左側的流程,即:設置坐標?寫 GRAM 指令?寫 GRAM 來實現,
但是這個步驟,只是一個點的處理,我們要顯示字符/數字,就必須要多次使用這個步驟,從而
達到顯示字符/數字的目的,所以需要設計一個函數來實現數字/字符的顯示,之后調用該函數,
就可以實現數字/字符的顯示了。
STM32F767xx 系列芯片都帶有 FMC 接口,即可變存儲存儲控制器,能夠與同步或異步存
儲器、SDRAM 存儲器和 NAND FLASH 等連接,STM32F767 的 FMC 接口支持包括 SRAM、
SDRAM、NAND FLASH、NOR FLASH 和 PSRAM 等存儲器。FMC 的框圖如圖 18.1.2.1 所示:
從上圖我們可以看出,STM32F767 的 FMC 將外部設備分為 3 類:NOR/PSRAM 設備、NAND
設備和 SDRAM 設備。他們共用地址數據總線等信號,他們具有不同的 CS 以區分不同的設備,
比如本章我們用到的 TFTLCD 就是用的 FMC_NE1 做片選,其實就是將 TFTLCD 當成 SRAM
來控制。
這里我們介紹下為什么可以把 TFTLCD 當成 SRAM 設備用:首先我們了解下外部 SRAM
的連接,外部 SRAM 的控制一般有:地址線(如 A0~A18)、數據線(如 D0~D15)、寫信號(WE)、
讀信號(OE)、片選信號(CS),如果 SRAM 支持字節控制,那么還有 UB/LB 信號。而 TFTLCD
的信號我們在 18.1.1 節有介紹,包括:RS、D0~D15、WR、RD、CS、RST 和 BL 等,其中真
正在操作 LCD 的時候需要用到的就只有:RS、D0~D15、WR、RD 和 CS。其操作時序和 SRAM
的控制完全類似,唯一不同就是 TFTLCD 有 RS 信號,但是沒有地址信號。
TFTLCD 通過 RS 信號來決定傳送的數據是數據還是命令,本質上可以理解為一個地址信
號,比如我們把 RS 接在 A0 上面,那么當 FMC 控制器寫地址 0 的時候,會使得 A0 變為 0,
對 TFTLCD 來說,就是寫命令。而 FMC 寫地址 1 的時候,A0 將會變為 1,對 TFTLCD 來說,
就是寫數據了。這樣,就把數據和命令區分開了,他們其實就是對應 SRAM 操作的兩個連續地
址。當然 RS 也可以接在其他地址線上,阿波羅 STM32F767 開發板是把 RS 連接在 A18 上面的。
STM32F767 的 FMC 支持 8/16/32 位數據寬度,我們這里用到的 LCD 是 16 位寬度的,所
以在設置的時候,選擇 16 位寬就 OK 了。我們再來看看 FMC 的外部設備地址映像,STM32F767
的 FMC 將外部存儲器劃分為 6 個固定大小為 256M 字節的存儲區域,如圖 18.1.2.2 所示:
從上圖可以看出,FMC 總共管理 1.5GB 空間,擁有 6 個存儲塊(Bank),本章,我們用到
的是塊 1,所以在本章我們僅討論塊 1 的相關配置,其他塊的配置,請參考《STM32F7 中文參
考手冊》第 13 章(286 頁)的相關介紹。
STM32F767 的 FMC 存儲塊 1(Bank1)被分為 4 個區,每個區管理 ** M 字節空間,每個
區都有獨立的寄存器對所連接的存儲器進行配置。Bank1 的 256M 字節空間由 28 根地址線
(HADDR[27:0])尋址。
這里 HADDR 是內部AHB地址總線,其中HADDR[25:0]來自外部存儲器地址 FMC_A[25:0],
而 HADDR[26:27]對 4 個區進行尋址。如表 18.1.2.1 所示:
HADDR[25:0]位包含外部存儲器的地址,由于 HADDR 為字節地址,而存儲器按字尋址,
所以,根據存儲器數據寬度的不同,實際上向存儲器發送的地址也有所不同,如表 18.1.2.2 所
示:
因此,FMC 內部 HADDR 與存儲器尋址地址的實際對應關系就是:
當接的是 32 位寬度存儲器的時候:HADDR[25:2]? FMC_A [23:0]。
當接的是 16 位寬度存儲器的時候:HADDR[25:1]? FMC_A [24:0]。
當接的是 8 位寬度存儲器的時候:HADDR[25:0]? FMC_A [25:0]。
不論外部接 8 位/16 位/32 位寬設備,FMC_A[0]永遠接在外部設備地址 A[0]。 這里,
TFTLCD 使用的是 16 位數據寬度,所以 HADDR[0]并沒有用到,只有 HADDR[25:1]是有效的,
對應關系變為:HADDR[25:1]? FMC_A[24:0],相當于右移了一位,這里請大家特別留意。另
外,HADDR[27:26]的設置,是不需要我們干預的,比如:當你選擇使用 Bank1 的第一個區,
即使用 FMC_NE1 來連接外部設備的時候,即對應了 HADDR[27:26]=00,我們要做的就是配置
對應第 1 區的寄存器組,來適應外部設備即可。STM32F767 的 FMC 各 Bank 配置寄存器如表
18.1.2.3 所示:
對于 NOR FLASH 控制器,主要是通過 FMC_BCRx、FMC_BTRx 和 FMC_BWTRx 寄存器
設置(其中 x=1~4,對應 4 個區)。通過這 3 個寄存器,可以設置 FMC 訪問外部存儲器的時序
參數,拓寬了可選用的外部存儲器的速度范圍。FMC 的 NOR FLASH 控制器支持同步和異步突
發兩種訪問方式。選用同步突發訪問方式時,FMC 將 HCLK(系統時鐘)分頻后,發送給外部存
儲器作為同步時鐘信號 FMC_CLK。此時需要的設置的時間參數有 2 個:
1,HCLK 與 FMC_CLK 的分頻系數(CLKDIV),可以為 2~16 分頻;
2,同步突發訪問中獲得第 1 個數據所需要的等待延遲(DATLAT)。
對于異步突發訪問方式,FMC 主要設置 3 個時間參數:地址建立時間(ADDSET)、數據建
立時間(DATAST)和地址保持時間(ADDHLD)。FMC 綜合了 SRAM、PSRAM 和 NOR Flash 產品
的信號特點,定義了 4 種不同的異步時序模型。選用不同的時序模型時,需要設置不同的時序
參數,如表 18.1.2.4 所列:
在實際擴展時,根據選用存儲器的特征確定時序模型,從而確定各時間參數與存儲器讀/
寫周期參數指標之間的計算關系;利用該計算關系和存儲芯片數據手冊中給定的參數指標,可
計算出 FMC 所需要的各時間參數,從而對時間參數寄存器進行合理的配置。
本章,我們使用異步模式 A(ModeA)方式來控制 TFTLCD,模式 A 的讀操作時序如圖
18.1.2.3 所示:
模式 A 支持獨立的讀寫時序控制,這個對我們驅動 TFTLCD 來說非常有用,因為 TFTLCD
在讀的時候,一般比較慢,而在寫的時候可以比較快,如果讀寫用一樣的時序,那么只能以讀
的時序為基準,從而導致寫的速度變慢,或者在讀數據的時候,重新配置 FMC 的延時,在讀
操作完成的時候,再配置回寫的時序,這樣雖然也不會降低寫的速度,但是頻繁配置,比較麻
煩。而如果有獨立的讀寫時序控制,那么我們只要初始化的時候配置好,之后就不用再配置,
既可以滿足速度要求,又不需要頻繁改配置。
模式 A 的寫操作時序如圖 18.1.2.4 所示:
圖 18.1.2.4 模式 A 寫操作時序
圖 18.1.2.3 和圖 18.1.2.4 中的 ADDSET 與 DATAST,是通過不同的寄存器設置的,接下來
我們講解一下 Bank1 的幾個控制寄存器
首先,我們介紹 SRAM/NOR 閃存片選控制寄存器:FMC_BCRx(x=1~4),該寄存器各位
描述如圖 18.1.2.5 所示:
該寄存器我們在本章用到的設置有:EXTMOD、WREN、MWID、MTYP 和 MBKEN 這幾
個設置,我們將逐個介紹。
EXTMOD:擴展模式使能位,也就是是否允許讀寫不同的時序,很明顯,我們本章需要讀
寫不同的時序,故該位需要設置為 1。
WREN:寫使能位。我們需要向 TFTLCD 寫數據,故該位必須設置為 1。
MWID[1:0]:存儲器數據總線寬度。00,表示 8 位數據模式;01 表示 16 位數據模式;10
表示 32 位數據模式;11 保留。我們的 TFTLCD 是 16 位數據線,所以設置 WMID[1:0]=01。
MTYP[1:0]:存儲器類型。00 表示 SRAM;01 表示 PSRAM;10 表示 NOR FLASH/OneNAND
FLASH;11 保留。前面提到,我們把 TFTLCD 當成 SRAM 用,所以需要設置 MTYP[1:0]=00。
MBKEN:存儲塊使能位。這個容易理解,我們需要用到該存儲塊控制 TFTLCD,當然要
使能這個存儲塊了。
接下來,我們看看 SRAM/NOR 閃存片選時序寄存器:FMC_BTRx(x=1~4),該寄存器各
位描述如圖 18.1.2.6 所示:
這個寄存器包含了每個存儲器塊的控制信息,可以用于 SRAM 和 NOR 閃存存儲器等。如
果 FMC_BCRx 寄存器中設置了 EXTMOD 位,則有兩個時序寄存器分別對應讀(本寄存器)和寫
操作(FMC_BWTRx 寄存器)。因為我們要求讀寫分開時序控制,所以 EXTMOD 是使能了的,
也就是本寄存器是讀操作時序寄存器,控制讀操作的相關時序。本章我們要用到的設置有:
ACCMOD、DATAST 和 ADDSET 這三個設置。
ACCMOD[1:0]:訪問模式。00 表示訪問模式 A;01 表示訪問模式 B;10 表示訪問模式 C;
11 表示訪問模式 D,本章我們用到模式 A,故設置為 00。
DATAST[7:0]:數據保持時間。0 為保留設置,其他設置則代表保持時間為: DATAST 個
HCLK 時鐘周期,最大為 255 個 HCLK 周期。對 ILI9341 來說,其實就是 RD 低電平持續時間,
一般為 355ns。而一個 HCLK 時鐘周期為 4.6ns 左右(1/216Mhz),為了兼容其他屏,我們這里
設置 DATAST 為 80,也就是 80 個 HCLK 周期,時間大約是 368ns。
ADDSET[3:0]:地址建立時間。其建立時間為:ADDSET 個 HCLK 周期,最大為 15 個 HCLK
周期。對 ILI9341 來說,這里相當于 RD 高電平持續時間,為 90ns,我們設置 ADDSET 為最大
15,即 15*4.6=69ns(略超)。
最后,我們再來看看 SRAM/NOR 閃寫時序寄存器:FMC_BWTRx(x=1~4),該寄存器各
位描述如圖 18.1.2.7 所示:
該寄存器在本章用作寫操作時序控制寄存器,需要用到的設置同樣是:ACCMOD、DATAST
和 ADDSET 這三個設置。這三個設置的方法同 FMC_BTRx 一模一樣,只是這里對應的是寫操
作的時序,ACCMOD 設置同 FMC_BTRx 一模一樣,同樣是選擇模式 A,另外 DATAST 和
ADDSET 則對應低電平和高電平持續時間,對 ILI9341 來說,這兩個時間只需要 15ns 就夠了,
比讀操作快得多。所以我們這里設置 DATAST 為 4,即 4 個 HCLK 周期,時間約為 18.4ns。然
后 ADDSET 設置為 4,即 4 個 HCLK 周期,時間為 18.4ns。
至此,我們對 STM32F767 的 FMC 介紹就差不多了,關于 FMC 的詳細介紹,請大家參考
《STM32F7 中文參考手冊》第 13 章。通過以上兩個小節的了解,我們可以開始寫 LCD 的驅動
代碼了。不過,這里還要給大家做下科普,在 MDK 的寄存器定義里面,并沒有定義 FMC_BCRx、
FMC_BTRx、FMC_BWTRx 等這個單獨的寄存器,而是將他們進行了一些組合。
FMC_BCRx 和 FMC_BTRx,組合成 BTCR[8]寄存器組,他們的對應關系如下:
BTCR[0]對應 FMC_BCR1,BTCR[1]對應 FMC_BTR1
BTCR[2]對應 FMC_BCR2,BTCR[3]對應 FMC_BTR2
BTCR[4]對應 FMC_BCR3,BTCR[5]對應 FMC_BTR3
BTCR[6]對應 FMC_BCR4,BTCR[7]對應 FMC_BTR4
FMC_BWTRx 則組合成 BWTR[7],他們的對應關系如下:
BWTR[0]對應 FMC_BWTR1,BWTR[2]對應 FMC_BWTR2,
BWTR[4]對應 FMC_BWTR3,BWTR[6]對應 FMC_BWTR4,
BWTR[1]、BWTR[3]和 BWTR[5]保留,沒有用到。
通過上面的講解,通過對 FSC 相關的寄存器的描述,大家對 FMC 的原理有了一個初步的
認識,如果還不熟悉的朋友,請一定要搜索網絡資料理解 FMC 的原理。只有理解了原理,使
用庫函數才可以得心應手。那么在庫函數中是怎么實現 FMC 的配置的呢?FMC_BCRx,
FMC_BTRx 寄存器在庫函數是通過什么函數來配置的呢?下面我們來講解一下使用 FMC 接口
驅動 LCD(SRAM)相關的庫函數操作過程。與 SRAM 和 FMC 相關的庫函數定義和聲明在源
文件 stm32f7xx_hal_fmc.c/stm32f7xx_hal_sram.c 以及頭文件
stm32f7xx_hal_fmc.h/stm32f7xx_hal_sram.h 中。
1) 使能 FMC 和 GPIO 時鐘,初始化 IO 口配置,設置映射關系
這個步驟在前面實驗已多次講解。這里我們主要列出 FMC 時鐘使能方法:
__HAL_RCC_FMC_CLK_ENABLE ();
//使能 FMC 時鐘
對于 IO 配置,調用函數 HAL_GPIO_Init 配置即可,具體 請參考實驗源碼。
2) 初始化 FMC 接口讀寫時序參數,初始化 LCD(SRAM)控制接口
根據前面的講解,我們把 LCD 當 SRAM 使用,連接在 FMC 接口之上,所以我們要初始化
FMC 讀寫時序參數以及 LCD 數據接口,也就是初始化三個寄存器 FMC_BCRx,FMC_BTRx
和 FMC_BWTRx。HAL 庫提供了 SRAM 初始化函數 HAL_SRAM_Init,該函數聲明如下:
HAL_StatusTypeDef HAL_SRAM_Init(SRAM_HandleTypeDef *hsram,
FMC_NORSRAM_TimingTypeDef *Timing,
FMC_NORSRAM_TimingTypeDef *ExtTiming);
該函數有三個入口參數,首先我們來看看第一個入口參數 hsram,它是
SRAM_HandleTypeDef 結構體指針類型,該參數用來初始化當 FMC 接口當 SRAM 使用時的控
制接口參數。結構體 SRAM_HandleTypeDef 定義如下:
typedef struct
{
FMC_NORSRAM_TypeDef
*Instance;
FMC_NORSRAM_EXTENDED_TypeDef
*Extended;
FMC_NORSRAM_InitTypeDef
Init;
HAL_LockTypeDef
Lock;
__IO HAL_SRAM_StateTypeDef
State;
DMA_HandleTypeDef
*hd ** ;
}SRAM_HandleTypeDef;
成員變量 Instance 和成員變量 Extended 實際上是用來在指定的時序模型下,寄存器基地址
和擴展模式寄存器基地址。這個怎么理解呢,本實驗我們使用異步模式 A(ModeA)方式來控
制 TFTLCD,使用的存儲塊是 Bank1,所以寄存器基地址 Instance 我們直接寫 FMC_Bank1 即可,
當然,HAL 庫定義好了宏定義 FMC_NORSRAM_DEVICE,也就是如果是 SRAM 設備,直接
填寫這個宏定義標識符即可。因為我們要配置的讀寫時序是不一樣的,也就是我們前面講解的
FMC_BCRx 寄存器的 EXTMOD 位我們會配置為 1 允許讀寫不同的時序,所以我們這里還要指
定寫操作時序寄存器地址,也就是通過參數 Extended 來指定的,這里我們設置為 FMC_Bank1E
即可,同樣 MDK 定義好了宏定義標識符 FMC_NORSRAM_EXTENDED_DEVICE,所以這里
我們填寫這個宏定義標識符也是一樣的。對于寫時序參數配置,是在函數 HAL_SRAM_Init 的
第三個參數 ExtTiming 來配置的,這個我們后面會講解。
成員變量 Init 是 FMC_NORSRAM_InitTypeDef 結構體指針類型,改變量才是真正用來設置
SRAM 控制接口參數的。我們接下來看看這個結構體定義:
typedef struct
{
uint32_t NSBank;
//存儲區塊號
uint32_t DataAddressMux;
//地址/數據復用使能
uint32_t MemoryType;
//存儲器類型
uint32_t MemoryDataWidth; //存儲器數據寬度
uint32_t BurstAccessMode;
uint32_t WaitSignalPolarity;
uint32_t WaitSignalActive;
uint32_t WriteOperation;
//存儲器寫使能
uint32_t WaitSignal;
uint32_t ExtendedMode;
//是否使能擴展模式
uint32_t AsynchronousWait;
uint32_t WriteBurst;
uint32_t ContinuousClock;
//啟用/禁止 FMC 時鐘輸出到外部存儲設備
uint32_t WriteFifo;
uint32_t PageSize;
}FMC_NORSRAM_InitTypeDef;
NSBank 用來指定使用到的存儲塊區號,前面講過,我們是使用的存儲塊區號 1,所以選擇
值為 FMC_NORSRAM_BANK1。DataAddressMux 用來設置是否使能地址/數據復用,該變量僅對
NOR/PSRAM 有 效 , 所 以 這 里 我 們 選 擇 不 使 能 地 址 / 數據復用值
FMC_DATA_ADDRESS_MUX_DISABLE 即可。MemoryType 用來設置存儲器類型,這里我們
把 LCD 當 SRAM 使用,所以設置為 FMC_MEMORY_TYPE_SRAM 即可。MemoryDataWidth
用來設置存儲器數據總線寬度,可選 8 位還是 16 位,這里我們選擇 16 位數據寬度
FMC_NORSRAM_MEM_BUS_WIDTH_16。WriteOperation 用來設置存儲器寫使能,也就是是
否允許寫入。毫無疑問我們會進行存儲器寫操作,所以這里設置為
FMC_WRITE_OPERATION_ENABLE。ExtendedMode 用來設置是否使能擴展模式,也就是是
否允許讀寫使用不同時序,前面講解過本實驗讀寫采用不同時序,所以設置值為使能值
FMC_EXTENDED_MODE_ENABLE。ContinuousClock 用來設置啟用/禁止 FMC 時鐘輸出到外
部存儲設備 ,這里 僅 當 使 用 FMC_BCR1 寄 存 器 的 時 候 需 要 啟 用 , 啟 用 值 為
FMC_CONTINUOUS_CLOCK_SYNC_ASYNC 。 其 他 參 數 WriteBurst , BurstAccessMode ,
WaitSignalPolarity,WaitSignalActive,WaitSignal,AsynchronousWait 等是用在突發訪問和異步
時序情況下,這里我們不做過多講解。
成員變量 Lock 和 State 是 HAL 庫處理狀態標識變量。這里就不做過多講解。
成員變量 hd ** 在使用 DMA 時候才使用,這里就先不講解了。
函數 HAL_SRAM_Init 的第一個入口參數就給大家講解到這里。
接下來看看后面 2 個參數 Timing 和 ExtTiming,它們都是 FMC_NORSRAM_TimingTypeDef
結構體指針類型,分別用來設置 FMC 接口讀和寫時序,主要涉及地址建立保持時間,數據建
立時間等等配置,對于我們的實驗中,讀寫時序不一樣,讀寫速度要求不一樣,所以對于參數
Timing 和 ExtTiming 設置了不同的值。
FMC_NORSRAM_TimingTypeDef 結構體定義如下:
typedef struct
{
uint32_t AddressSetupTime;
//地址建立時間
uint32_t AddressHoldTime;
//地址保持時間
uint32_t DataSetupTime;
//數據簡歷時間
uint32_t BusTurnAroundDuration; //總線周轉階段的持續時間
uint32_t CLKDivision;
//CLK 時鐘輸出信號的周期
uint32_t DataLatency;
//同步突發 NOR FLASH 的數據延遲
uint32_t AccessMode;
//異步模式配置
}FMC_NORSRAM_TimingTypeDef;
成員變量 AddressSetupTime 用來設置地址建立時間。AddressHoldTime 用來設置地址保持
時間。DataSetupTime 用來設置數據建立時間。BusTurnAroundDuration 用來配置總線周轉階段
的持續時間。CLKDivision 用來配置 CLK 時鐘輸出信號的周期,以 HCLK 周期數表示。
DataLatency 用來設置同步突發 NOR FLASH 的數據延遲。AccessMode 用來設置異步模式,取
值范圍為 FMC_ACCESS_MODE_A,FMC_ACCESS_MODE_B, FMC_ACCESS_MODE_C 和
FMC_ACCESS_MODE_D,這里我們用是異步模式 A,所以取值為 FMC_ACCESS_MODE_A。
HAL_SRAM_Init 函數各個入口參數含義和配置就給大家講解到這里。
和其他外設一樣,HAL 庫也提供了 SRAM 的初始化 MSP 回調函數,函數聲明如下:
void HAL_SRAM_MspInit(SRAM_HandleTypeDef *hsram) ;
關于 MSP 函數的使用方法相信大家已經非常熟悉。該函數內部一般用來使能時鐘以及初
始化 IO 口這些與 MCU 相關的步驟。
前面我們講解過,FMC 接口支持多種存儲器,包括 SDRAM,NOR,NAND 和 PC CARD
等。HAL 庫為每種支持的存儲器類型都定義了一個獨立的 HAL 庫文件,并且在文件中定義了
獨立的初始化函數。這里以 SDRAM 為例,HAL 提供庫支持文件 stm32f7xx_hal_sdram.c 和頭文
件 stm32f7xx_hal_sdram.h,同時還提供了獨立的初始化函數 HAL_SDRAM_Init,這里我們就列
出幾種存儲器的初始化函數:
HAL_SDRAM_Init();//SDRAM 初始化函數,省略入口參數
HAL_NOR_Init();//NOR 初始化函數,省略入口參數
HAL_NAND_Init();//NAND 初始化函數,省略入口參數
3)存儲區使能
實際上,當我們調用了存儲器初始化函數之后,相應的使用到的存儲區就已經被使能。
SRAM 存儲區使能方法為:
__FMC_NORSRAM_ENABLE(FMC_Bank1,FMC_NORSRAM_BANK1);
18.2 硬件設計
本實驗用到的硬件資源有:
1) 指示燈 DS0
2) TFTLCD 模塊
TFTLCD 模塊的電路見圖 18.1.1.2,這里我們介紹 TFTLCD 模塊與 ALIETEK 阿波羅
STM32F767 開發板的連接,阿波羅 STM32F767 開發板底板的 LCD 接口和 ALIENTEK TFTLCD
模塊直接可以對插,連接關系如圖 18.2.1 所示:
圖 18.2.1 中圈出來的部分就是連接 TFTLCD 模塊的接口,液晶模塊直接插上去即可。
在硬件上,TFTLCD 模塊與阿波羅 STM32F767 開發板的 IO 口對應關系如下:
LCD_BL(背光控制)對應 PB5;
LCD_CS 對應 PD7 即 FMC_NE1;
LCD _RS 對應 PD13 即 FMC_A18;
LCD _WR 對應 PD5 即 FMC_NWE;
LCD _RD 對應 PD4 即 FMC_NOE;
LCD _D[15:0]則直接連接在 FMC_D15~FMC_D0;
這些線的連接,阿波羅 STM32F767 開發板的內部已經連接好了,我們只需要將 TFTLCD
模塊插上去就好了。實物連接(4.3 寸 TFTLCD 模塊)如圖 18.2.2 所示:
18.3 軟件設計
打開我們光盤的實驗 13 TFTLCD(MCU 屏)工程可以看到我們添加了兩個文件 lcd.c 和頭
文 件 lcd.h 。 同 時 , FMC 和 SRAM 相 關 的 庫 函 數 和 聲 明 定 義 在 源 文 件
stm32f7xx_hal_fmc.c/stm32f7xx_hal_sdram.c 和頭文件 stm32f7xx_hal_fmc.h
/stm32f7xx_hal_sram.h 中。
在 lcd.c 里面要輸入的代碼比較多,我們這里就不貼出來了,只針對幾個重要的函數進行講
解。完整版的代碼見光盤?4,程序源碼?標準例程-寄存器版本?實驗 13 TFTLCD(MCU 屏)
實驗 的 lcd.c 文件。
本實驗,我們用到 FMC 驅動 LCD,通過前面的介紹,我們知道 TFTLCD 的 RS 接在 FMC
的 A18 上面,CS 接在 FMC_NE1 上,并且是 16 位數據總線。即我們使用的是 FMC 存儲器 1
的第 1 區,我們定義如下 LCD 操作結構體(在 lcd.h 里面定義):
//LCD 地址結構體
typedef struct
{
vu16 LCD_REG;
vu16 LCD_RAM;
} LCD_TypeDef;
//使用 NOR/SRAM 的 Bank1.sector1,地址位 HADDR[27,26]=00 A18 作為數據命令區分線
//注意設置時 STM32 內部會右移一位對其!
#define LCD_BASE ((u32)(0x | 0x0007FFFE))
#define LCD ((LCD_TypeDef *) LCD_BASE)
其中 LCD_BASE,必須根據我們外部電路的連接來確定,我們使用 Bank1.sector1 就是從
地址 0X 開始,而 0x0007FFFE,則是 A18 的偏移量,這里很多朋友不理解這個偏移量
的概念,簡單說明下:以 A18 為例,0x0007FFFE 轉換成二進制就是:0111 1111 1111 1111 1110,
而 16 位數據時,地址右移一位對齊,那么實際對應到地址引腳的時候,就是:A18:A0=011 1111
1111 1111 1111,此時 A18 是 0,但是如果 16 位地址再加 1(注意:對應到 8 位地址是加 2,即
0x0007FFFE +0X02),那么:A18:A0=100 0000 0000 0000 0000,時 A18 就是 1 了,即實現了對
RS 的 0 和 1 的控制。
我們將這個地址強制轉換為 LCD_TypeDef 結構體地址,那么可以得到 LCD->LCD_REG 的
地址就是 0X6007,FFFE,對應 A18 的狀態為 0(即 RS=0),而 LCD->LCD_RAM 的地址就是
0X6008,0000(結構體地址自增),對應 A18 的狀態為 1(即 RS=1)。
所以,有了這個定義,當我們要往 LCD 寫命令/數據的時候,可以這樣寫:
LCD->LCD_REG=CMD; //寫命令
LCD->LCD_RAM=DATA; //寫數據
而讀的時候反過來操作就可以了,如下所示:
CMD= LCD->LCD_REG; //讀 LCD 寄存器
DATA = LCD->LCD_RAM; //讀 LCD 數據
這其中,CS、WR、RD 和 IO 口方向都是由 FMC 硬件自動控制,不需要我們手動設置了。
接下來,我們先介紹一下 lcd.h 里面的另一個重要結構體:
//LCD 重要參數集
typedef struct
{
u16 width;
//LCD 寬度
u16 height;
//LCD 高度
u16 id;
//LCD ID
u8 dir;
//橫屏還是豎屏控制:0,豎屏;1,橫屏。
u16 wramcmd;
//開始寫 gram 指令
u16 setxcmd;
//設置 x 坐標指令
u16 setycmd;
//設置 y 坐標指令
}_lcd_dev;
//LCD 參數
extern _lcd_dev lcddev; //管理 LCD 重要參數
該結構體用于保存一些 LCD 重要參數信息,比如 LCD 的長寬、LCD ID(驅動 IC 型號)、
LCD 橫豎屏狀態等,這個結構體雖然占用了十幾個字節的內存,但是卻可以讓我們的驅動函數
支持不同尺寸的 LCD,同時可以實現 LCD 橫豎屏切換等重要功能,所以還是利大于弊的。有
了以上了解,下面我們開始介紹 lcd.c 里面的一些重要函數。
先看 7 個簡單,但是很重要的函數:
//寫寄存器函數
//regval:寄存器值
void LCD_WR_REG(vu16 regval)
{
regval=regval;
//使用-O2 優化的時候,必須插入的延時
LCD->LCD_REG=regval;//寫入要寫的寄存器序號
}
//寫 LCD 數據
//data:要寫入的值
void LCD_WR_DATA(vu16 data)
{
data=data;
//使用-O2 優化的時候,必須插入的延時
LCD->LCD_RAM=data;
}
//讀 LCD 數據
//返回值:讀到的值
u16 LCD_RD_DATA(void)
{
vu16 ram;
//防止被優化
ram=LCD->LCD_RAM;
return ram;
}
//寫寄存器
//LCD_Reg:寄存器地址
//LCD_RegValue:要寫入的數據
void LCD_WriteReg(u16 LCD_Reg, u16 LCD_RegValue)
{
LCD->LCD_REG = LCD_Reg;
//寫入要寫的寄存器序號
LCD->LCD_RAM = LCD_RegValue; //寫入數據
}
//讀寄存器
//LCD_Reg:寄存器地址
//返回值:讀到的數據
u16 LCD_ReadReg(u16 LCD_Reg)
{
LCD_WR_REG(LCD_Reg);
//寫入要讀的寄存器序號
delay_us(5);
return LCD_RD_DATA();
//返回讀到的值
}
//開始寫 GRAM
void LCD_WriteRAM_Prepare(void)
{
LCD->LCD_REG=lcddev.wramcmd;
}
//LCD 寫 GRAM
//RGB_Code:顏色值
void LCD_WriteRAM(u16 RGB_Code)
{
LCD->LCD_RAM = RGB_Code;//寫十六位 GRAM
}
因為 FMC 自動控制了 WR/RD/CS 等這些信號,所以這 7 個函數實現起來都非常簡單,我
們就不多說,注意,上面有幾個函數,我們添加了一些對 MDK –O2 優化的支持,去掉的話,
在-O2 優化的時候會出問題。這些函數實現功能見函數前面的備注,通過這幾個簡單函數的組
合,我們就可以對 LCD 進行各種操作了。
第七個要介紹的函數是坐標設置函數,該函數代碼如下:
//設置光標位置
//Xpos:橫坐標
//Ypos:縱坐標
void LCD_SetCursor(u16 Xpos, u16 Ypos)
{
if(lcddev.id==0X9341||lcddev.id==0X5310)
{
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA(Xpos>>8);LCD_WR_DATA(Xpos&0XFF);
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA(Ypos>>8);LCD_WR_DATA(Ypos&0XFF);
}else if(lcddev.id==0X1963)
{
if(lcddev.dir==0)//x 坐標需要變換
{
Xpos=lcddev.width-1-Xpos;
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA(0);LCD_WR_DATA(0);
LCD_WR_DATA(Xpos>>8);LCD_WR_DATA(Xpos&0XFF);
}else
{
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA(Xpos>>8);LCD_WR_DATA(Xpos&0XFF);
LCD_WR_DATA((lcddev.width-1)>>8);
LCD_WR_DATA((lcddev.width-1)&0XFF);
}
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA(Ypos>>8);LCD_WR_DATA(Ypos&0XFF);
LCD_WR_DATA((lcddev.height-1)>>8);LCD_WR_DATA((lcddev.height-1)&0XFF);
}else if(lcddev.id==0X5510)
{
LCD_WR_REG(lcddev.setxcmd);LCD_WR_DATA(Xpos>>8);
LCD_WR_REG(lcddev.setxcmd+1);LCD_WR_DATA(Xpos&0XFF);
LCD_WR_REG(lcddev.setycmd);LCD_WR_DATA(Ypos>>8);
LCD_WR_REG(lcddev.setycmd+1);LCD_WR_DATA(Ypos&0XFF);
}
}
該函數實現將 LCD 的當前操作點設置到指定坐標(x,y)。因為 9341/5310/1963/5510 等的設
置有些不太一樣,所以進行了區別對待。
接下來我們介紹第八個函數:畫點函數。該函數實現代碼如下:
//畫點
//x,y:坐標
//POINT_COLOR:此點的顏色
void LCD_DrawPoint(u16 x,u16 y)
{
LCD_SetCursor(x,y);
//設置光標位置
LCD_WriteRAM_Prepare(); //開始寫入 GRAM
LCD->LCD_RAM=POINT_COLOR;
}
該函數實現比較簡單,就是先設置坐標,然后往坐標寫顏色。其中 POINT_COLOR 是我們
定義的一個全局變量,用于存放畫筆顏色,順帶介紹一下另外一個全局變量:BACK_COLOR,
該變量代表 LCD 的背景色。LCD_DrawPoint 函數雖然簡單,但是至關重要,其他幾乎所有上
層函數,都是通過調用這個函數實現的。
有了畫點,當然還需要有讀點的函數,第九個介紹的函數就是讀點函數,用于讀取 LCD
的 GRAM,這里說明一下,為什么 OLED 模塊沒做讀 GRAM 的函數,而這里做了。因為 OLED
模塊是單色的,所需要全部 GRAM 也就 1K 個字節,而 TFTLCD 模塊為彩色的,點數也比 OLED
模塊多很多,以 16 位色計算,一款 320×240 的液晶,需要 320×240×2 個字節來存儲顏色值,
也就是也需要 150K 字節,這對任何一款單片機來說,都不是一個小數目了。而且我們在圖形
疊加的時候,可以先讀回原來的值,然后寫入新的值,在完成疊加后,我們又恢復原來的值。
這樣在做一些簡單菜單的時候,是很有用的。這里我們讀取 TFTLCD 模塊數據的函數為
LCD_ReadPoint,該函數直接返回讀到的 GRAM 值。該函數使用之前要先設置讀取的 GRAM
地址,通過 LCD_SetCursor 函數來實現。LCD_ReadPoint 的代碼如下:
//讀取個某點的顏色值
//x,y:坐標
//返回值:此點的顏色
u16 LCD_ReadPoint(u16 x,u16 y)
{
u16 r=0,g=0,b=0;
if(x>=lcddev.width||y>=lcddev.height)return 0; //超過了范圍,直接返回
LCD_SetCursor(x,y);
if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X1963)
LCD_WR_REG(0X2E);//9341/3510/1963 發送讀 GRAM 指令
else if(lcddev.id==0X5510)LCD_WR_REG(0X2E00);//5510 發送讀 GRAM 指令
r=LCD_RD_DATA();
//dummy Read
if(lcddev.id==0X1963)return r;
//1963 直接讀就可以
opt_delay(2);
r=LCD_RD_DATA();
//實際坐標顏色
//9341/NT35310/NT35510 要分 2 次讀出
opt_delay(2);
b=LCD_RD_DATA();
g=r&0XFF; //對于 9341/5310/5510,第一次讀取的是 RG 的值,R 在前,G 在后,各占 8 位
g<<=8;
return (((r>>11)<<11)|((g>>10)<<5)|(b>>11));
//需要公式轉換一下
}
在 LCD_ReadPoint 函數中,因為我們的代碼不止支持一種 LCD 驅動器,所以,我們根據
不同的 LCD 驅動器((lcddev.id)型號,執行不同的操作,以實現對各個驅動器兼容,提高函數
的通用性。
第十個要介紹的是字符顯示函數 LCD_ShowChar,該函數同前面 OLED 模塊的字符顯示函
數差不多,但是這里的字符顯示函數多了 1 個功能,就是可以以疊加方式顯示,或者以非疊加
方式顯示。疊加方式顯示多用于在顯示的圖片上再顯示字符。非疊加方式一般用于普通的顯示。
該函數實現代碼如下:
//在指定位置顯示一個字符
//x,y:起始坐標
//num:要顯示的字符:" "--->"~"
//size:字體大小 12/16/24/32
//mode:疊加方式(1)還是非疊加方式(0)
void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode)
{
u8 temp,t1,t;
u16 y0=y;
u8 csize=(size/8+((size%8)?1:0))*(size/2);//得到字體一個字符對應點陣集所占的字節數
num=num-' ';//ASCII 字庫是從空格開始取模,所以-' '就是對應字符的字庫
for(t=0;t<csize;t++)
{
if(size==12)temp=asc2_1206[num][t];
//調用 1206 字體
else if(size==16)temp=asc2_1608[num][t]; //調用 1608 字體
else if(size==24)temp=asc2_2412[num][t]; //調用 2412 字體
else if(size==32)temp=asc2_3216[num][t]; //調用 3216 字體
else return;
//沒有的字庫
for(t1=0;t1<8;t1++)
{
if(temp&0x80)LCD_Fast_DrawPoint(x,y,POINT_COLOR);
else if(mode==0)LCD_Fast_DrawPoint(x,y,BACK_COLOR);
temp<<=1;
y++;
if(y>=lcddev.height)return;
//超區域了
if((y-y0)==size)
{
y=y0;
x++;
if(x>=lcddev.width)return; //超區域了
break;
}
}
}
}
在 LCD_ShowChar 函數里面,我們采用快速畫點函數 LCD_Fast_DrawPoint 來畫點顯示字
符,該函數同 LCD_DrawPoint 一樣,只是帶了顏色參數,且減少了函數調用的時間,詳見本例
程源碼。該代碼中我們用到了四個字符集點陣數據數組 asc2_3216、asc2_2412、asc2_1206 和
asc2_1608,這幾個字符集的點陣數據的提取方式,同十六章介紹的提取方法是一模一樣的。詳
細請參考第十六章。
最后,我們再介紹一下 TFTLCD 模塊的初始化函數 LCD_Init,該函數先配置 FMC 控制器,
然后讀取 LCD 控制器的型號,根據控制 IC 的型號執行不同的初始化代碼,其簡化代碼如下:
//初始化 lcd
//該初始化函數可以初始化各種型號的 LCD(詳見本.c 文件最前面的描述)
void LCD_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
FMC_NORSRAM_TimingTypeDef FMC_ReadWriteTim;
FMC_NORSRAM_TimingTypeDef FMC_WriteTim;
__HAL_RCC_GPIOB_CLK_ENABLE();
//開啟 GPIOB 時鐘
GPIO_Initure.Pin=GPIO_PIN_5;
//PB5,背光控制
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽輸出
GPIO_Initure.Pull=GPIO_PULLUP;
//上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH;
//高速
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
LCD_MPU_Config(); //使能 MPU 保護 LCD 區域
SRAM_Handler.Instance= FMC_NORSRAM_DEVICE;
//SRAM BANK1
SRAM_Handler.Extended= FMC_NORSRAM_EXTENDED_DEVICE;
SRAM_Handler.Init.NSBank=FMC_NORSRAM_BANK1;
//使用 NE1
SRAM_Handler.Init.DataAddressMux=FMC_DATA_ADDRESS_MUX_DISABLE;
//地址/數據線不復用
SRAM_Handler.Init.MemoryType=FMC_MEMORY_TYPE_SRAM; //SRAM
SRAM_Handler.Init.MemoryDataWidth=FMC_NORSRAM_MEM_BUS_WIDTH_16;
//16 位數據寬度
SRAM_Handler.Init.BurstAccessMode=FMC_BURST_ACCESS_MODE_DISABLE;
//是否使能突發訪問,僅對同步突發存儲器有效,此處未用到
SRAM_Handler.Init.WaitSignalPolarity=FMC_WAIT_SIGNAL_POLARITY_LOW;
//等待信號的極性,僅在突發模式訪問下有用
SRAM_Handler.Init.WaitSignalActive=FMC_WAIT_TIMING_BEFORE_WS;
//存儲器是在等待周期之前的一個時鐘周期還是等待周期期間使能 NWAIT
SRAM_Handler.Init.WriteOperation=FMC_WRITE_OPERATION_ENABLE;
//存儲器寫使能
SRAM_Handler.Init.WaitSignal=FMC_WAIT_SIGNAL_DISABLE;
//等待使能位,此處未用到
SRAM_Handler.Init.ExtendedMode=FMC_EXTENDED_MODE_ENABLE;
//讀寫使用不同的時序
SRAM_Handler.Init.AsynchronousWait=FMC_ASYNCHRONOUS_WAIT_DISABLE;
//是否使能同步傳輸模式下的等待信號,此處未用到
SRAM_Handler.Init.WriteBurst=FMC_WRITE_BURST_DISABLE;
//禁止突發寫
SRAM_Handler.Init.ContinuousClock=FMC_CONTINUOUS_CLOCK_SYNC_ASYNC;
//FMC 讀時序控制寄存器
FMC_ReadWriteTim.AddressSetupTime=0x011; //地址建立時間為 17 個 HCLK
FMC_ReadWriteTim.AddressHoldTime=0x00;
FMC_ReadWriteTim.DataSetupTime=0x55; //數據保存時間(DATAST)為 85 個 HCLK
FMC_ReadWriteTim.AccessMode=FMC_ACCESS_MODE_A; //模式 A
//FMC 寫時序控制寄存器
FMC_WriteTim.AddressSetupTime=0x15; //地址建立時間(ADDSET)為 21 個 HCLK
FMC_WriteTim.AddressHoldTime=0x00;
FMC_WriteTim.DataSetupTime=0x015; //數據保存時間(DATAST)為 21 個 HCLK
FMC_WriteTim.AccessMode=FMC_ACCESS_MODE_A; //模式 A
HAL_SRAM_Init(&SRAM_Handler,&FMC_ReadWriteTim,&FMC_WriteTim);
delay_ms(50); // delay 50 ms
//嘗試 9341 ID 的讀取
LCD_WR_REG(0XD3);
lcddev.id=LCD_RD_DATA(); //dummy read
lcddev.id=LCD_RD_DATA(); //讀到 0X00
lcddev.id=LCD_RD_DATA();
//讀取 93
lcddev.id<<=8;
lcddev.id|=LCD_RD_DATA();
//讀取 41
if(lcddev.id!=0X9341)
//非 9341,嘗試看看是不是 NT35310
{
LCD_WR_REG(0XD4);
lcddev.id=LCD_RD_DATA();//dummy read
lcddev.id=LCD_RD_DATA();//讀回 0X01
lcddev.id=LCD_RD_DATA();//讀回 0X53
lcddev.id<<=8;
lcddev.id|=LCD_RD_DATA();
//這里讀回 0X10
if(lcddev.id!=0X5310)
//也不是 NT35310,嘗試看看是不是 NT35510
{
LCD_WR_REG(0XDA00);
lcddev.id=LCD_RD_DATA();
//讀回 0X00
LCD_WR_REG(0XDB00);
lcddev.id=LCD_RD_DATA();
//讀回 0X80
lcddev.id<<=8;
LCD_WR_REG(0XDC00);
lcddev.id|=LCD_RD_DATA();
//讀回 0X00
if(lcddev.id==0x8000)lcddev.id=0x5510;
//NT35510 讀回的 ID 是 8000H,為方便區分,我們強制設置為 5510
if(lcddev.id!=0X5510)
//也不是 NT5510,嘗試看看是不是 SSD1963
{
LCD_WR_REG(0XA1);
lcddev.id=LCD_RD_DATA();
lcddev.id=LCD_RD_DATA();
//讀回 0X57
lcddev.id<<=8;
lcddev.id|=LCD_RD_DATA();
//讀回 0X61
if(lcddev.id==0X5761)lcddev.id=0X1963;
//SSD1963 讀回的 ID 是 5761H,為方便區分,我們強制設置為 1963
}
}
}
printf(" LCD ID:%x",lcddev.id); //打印 LCD ID
if(lcddev.id==0X9341)
//9341 初始化
{
……//9341 初始化代碼
}else if(lcddev.id==0xXXXX) //其他 LCD 初始化代碼
{
……//其他 LCD 驅動 IC,初始化代碼
}
//初始化完成以后,提速
if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510||lcddev.id==0X1963)
{
//重新配置寫時序控制寄存器的時序
FMC_Bank1E->BWTR[0]&=~(0XF<<0);
//地址建立時間(ADDSET)清零
FMC_Bank1E->BWTR[0]&=~(0XF<<8);
//數據保存時間清零
FMC_Bank1E->BWTR[0]|=5<<0; //地址建立時間(ADDSET)為 5 個 HCLK =21ns
FMC_Bank1E->BWTR[0]|=5<<8;//數據保存時間(DATAST) 為 21ns
}
LCD_Display_Dir(0);
//默認為豎屏顯示
LCD_LED(1);
//點亮背光
LCD_Clear(WHITE);
}
該函數先對 FMC 相關 IO 進行初始化,然后是 FMC 的初始化,這個我們在前面都有介紹,
最后根據讀到的 LCD ID,對不同的驅動器執行不同的初始化代碼,從上面的代碼可以看出,
這個初始化函數針對多款不同的驅動 IC 執行初始化操作,這樣提高了整個程序的通用性。大家
在以后的學習中應該多使用這樣的方式,以提高程序的通用性、兼容性。
這里還要提醒大家,在 LCD_Init 函數中有如下一行代碼:
LCD_MPU_Config(); //使能 MPU 保護 LCD 區域
這行代碼的作用是調用函數 LCD_MPU_Config 使能 MPU 保護 LCD 區域,而函數
LCD_MPU_Config 定義的內容實際上是我們上一章給大家講解的使能 MPU 保護 LCD 區域。這
里我們之所以直接在 LCD 程序中加入 MPU 保護,是因為方便大家在移植 LCD 相關代碼到自
己的工程中的時候不會因為沒有引入 MPU 相關配置而導致 LCD 無 ** 常工作。
特別注意:本函數使用了 printf 來打印 LCD ID,所以,如果你在主函數里面沒有初始化串
口,那么將導致程序死在 printf 里面!!如果不想用 printf,那么請注釋掉它。
SRAM 初始化 MSP 回調函數 HAL_SRAM_MspInit 內容比較簡單,主要是進行時鐘使能以
及 IO 口映射配置,這里就不做過多講解。
LCD 驅動相關的函數就給大家講解到這里。接下來,我們看看主函數代碼如下:
int ** in(void)
{
u8 x=0;
u8 lcd_id[12];
Cache_Enable(); //打開 L1-Cache
HAL_Init();
//初始化 HAL 庫
Stm32_Clock_Init(432,25,2,9); //設置時鐘,216Mhz
delay_init(216); //延時初始化
uart_init(115200);
//串口初始化
LED_Init(); //初始化 LED
LCD_Init(); //初始化 LCD
POINT_COLOR=RED;
sprintf((char*)lcd_id,"LCD ID:%04X",lcddev.id);//將 LCD ID 打印到 lcd_id 數組。
while(1)
{
switch(x)
{
case 0:LCD_Clear(WHITE);break;
……//此處省略部分代碼
case 11:LCD_Clear(BROWN);break;
}
POINT_COLOR=RED;
LCD_ShowString(10,40,260,32,32,"Apollo STM32F4/F7");
LCD_ShowString(10,80,240,24,24,"TFTLCD TEST");
LCD_ShowString(10,110,240,16,16,"ATOM@ALIENTEK");
LCD_ShowString(10,130,240,16,16,lcd_id);
//顯示 LCD ID
LCD_ShowString(10,150,240,12,12,"2016/7/11");
x++;
if(x==12)x=0;
LED0_Toggle;
delay_ms(1000);
}
}
該部分代碼將顯示一些固定的字符,字體大小包括 32*16、24*12、16*8 和 12*6 等四種,
同時顯示 LCD 驅動 IC 的型號,然后不停的切換背景顏色,每 1s 切換一次。而 LED0 也會不停
的閃爍,指示程序已經在運行了。其中我們用到一個 sprintf 的函數,該函數用法同 printf,只
是 sprintf 把打印內容輸出到指定的內存區間上,sprintf 的詳細用法,請百度學習。
另外特別注意:uart_init 函數,不能去掉,因為在 LCD_Init 函數里面調用了 printf,所以
一旦你去掉這個初始化,就會死機了!實際上,只要你的代碼有用到 printf,就必須初始化串口,
否則都會死機,即停在 usart.c 里面的 fputc 函數,出不來。
在編譯通過之后,我們開始下載驗證代碼。
18.4 下載驗證
將程序下載到阿波羅 STM32 后,可以看到 DS0 不停的閃爍,提示程序已經在運行了。同
時可以看到 TFTLCD 模塊的顯示如圖 18.4.1 所示:
我們可以看到屏幕的背景是不停切換的,同時 DS0 不停的閃爍,證明我們的代碼被正確的
執行了,達到了我們預期的目的。
18.5 STM32CubeMX 配置 FMC(SRAM)
當大家了解了 FMC 的基本工作原理,那么使用 STM32CubeMX 配置 FMC 相關參數就會非
常簡單。如果大家對 FMC 沒有理解,請仔細看教程學習。這里我們們不再詳細講解每個配置
項的含義。使用 STM32CubeMX 配置 FMC 的一般步驟為:
① 進入 Pinout->FMC 配置欄,配置 FMC 基本參數。根據前面的講解,這里我們使用的是
BANK1 的第一個分區 NE1,同時吧 LCD 作為 SRAM 使用,19 位地址線,16 位數據線。
配置參數如下圖 18.5.1 所示:
② 點擊 Configuration->FMC 進入 FMC 配置界面,在 NOR/SRAM 1 選項卡之下配置相關參
數。這些參數的含義這里我們不累贅,在 18.1 小節講解 HAL_SRAM_Init 函數的時候都
有講解。配置方法如下圖 18.5.2 所示:
在該配置界面,點擊右邊的 GPIO Settigns 選項卡,還可以配置相關 IO 口的信息。
經過上面配置步驟,我們就可以生成相應的初始化代碼,大家生成后和本章實驗工
程對比學習。
- LCD液晶屏結構原理 08-01
- 鑫平電子工控液晶顯示屏的三大特性 07-29
- LCD/LCM成品檢驗要求,及常見的不良現象 06-30
- 醫療屏發生抗干擾要怎么辦? 06-29
- TFT顯示屏是如何顯示圖案的? 06-28
- 顯示屏的保養方法 06-27
- tft液晶屏幕貼合技術介紹 06-24
- 如何選擇單色液晶屏? 06-23
- 12864液晶模塊名稱的命名和原理 06-22
- 如何區分LCD液晶顯示屏的視角 06-21