本章參考資料:《STM32F76xxx參考手冊2》、《STM32F7xx規格書》、庫幫助文檔《STM32F779xx_User_Manual.chm》。
關於開發板配套的液晶屏參數可查閱《5.0寸液晶屏數據手冊》配套資料獲知。
27.1 顯示器簡介
顯示器屬於計算機的I/O設備,即輸入輸出設備。它是一種將特定電子信息輸出到屏幕上再反射到人眼的顯示工具。常見的有CRT顯示器、液晶顯示器、LED點陣顯示器及OLED顯示器。
27.1.1 液晶顯示器
液晶顯示器,簡稱LCD(Liquid Crystal Display),相對於上一代CRT顯示器(陰極射線管顯示器),LCD顯示器具有功耗低、體積小、承載的信息量大及不傷眼的優點,因而它成為了現在的主流電子顯示設備,其中包括電視、電腦顯示器、手機屏幕及各種嵌入式設備的顯示器。圖 27-1是液晶電視與CRT電視的外觀對比,很明顯液晶電視更薄,“時尚”是液晶電視給人的第一印象,而CRT 電視則感覺很“笨重”。
圖 27-1 液晶電視及CRT電視
液晶是一種介於固體和液體之間的特殊物質,它是一種有機化合物,常態下呈液態,但是它的分子排列卻和固體晶體一樣非常規則,因此取名液晶。如果給液晶施加電場,會改變它的分子排列,從而改變光線的傳播方向,配合偏振光片,它就具有控制光線透過率的作用,再配合彩色濾光片,改變加給液晶電壓大小,就能改變某一顏色透光量的多少,圖 27-2中的就是綠色顯示結構。利用這種原理,做出可控紅、綠、藍光輸出強度的顯示結構,把三種顯示結構組成一個顯示單位,通過控制紅綠藍的強度,可以使該單位混合輸出不同的色彩,這樣的一個顯示單位被稱為像素。
圖 27-2 液晶屏的綠色顯示結構
注意液晶本身是不發光的,所以需要有一個背光燈提供光源,光線經過一系列處理過程才到輸出,所以輸出的光線強度是要比光源的強度低很多的,比較浪費能源(當然,比CRT顯示器還是節能多了)。而且這些處理過程會導致顯示方向比較窄,也就是它的視角較小,從側面看屏幕會看不清它的顯示內容。另外,輸出的色彩變換時,液晶分子轉動也需要消耗一定的時間,導致屏幕的響應速度低。
27.1.2 LED和OLED顯示器
LED點陣顯示器不存在以上液晶顯示器的問題,LED點陣彩色顯示器的單個像素點內包含紅綠藍三色LED燈,顯示原理類似我們實驗板上的LED彩燈,通過控制紅綠藍顏色的強度進行混色,實現全彩顏色輸出,多個像素點構成一個屏幕。由於每個像素點都是LED燈自發光的,所以在戶外白天也顯示得非常清晰,但由於LED燈體積較大,導致屏幕的像素密度低,所以它一般只適合用於廣場上的巨型顯示器。相對來說,單色的LED點陣顯示器應用得更廣泛,如公交車上的信息展示牌、店招等,見圖 27-3。
圖 27-3 LED點陣彩屏有LED單色顯示屏
新一代的OLED顯示器與LED點陣彩色顯示器的原理類似,但由於它采用的像素單元是“有機發光二極管”(Organic Light Emitting Diode),所以像素密度比普通LED點陣顯示器高得多,見圖 27-4。
圖 27-4 OLED像素結構
OLED顯示器不需要背光源、對比度高、輕薄、視角廣及響應速度快等優點。待到生產工藝更加成熟時,必將取代現在液晶顯示器的地位,見圖 27-5。
圖 27-5 采用OLED屏幕的電視及智能手表
27.1.3 顯示器的基本參數
不管是哪一種顯示器,都有一定的參數用於描述它們的特性,各個參數介紹如下:
(1) 像素
像素是組成圖像的最基本單元要素,顯示器的像素指它成像最小的點,即前面講解液晶原理中提到的一個顯示單元。
(1) 分辨率
一些嵌入式設備的顯示器常常以“行像素值x列像素值”表示屏幕的分辨率。如分辨率800x480表示該顯示器的每一行有800個像素點,每一列有480個像素點,也可理解為有800列,480行。
(2) 色彩深度
色彩深度指顯示器的每個像素點能表示多少種顏色,一般用“位”(bit)來表示。如單色屏的每個像素點能表示亮或滅兩種狀態(即實際上能顯示2種顏色),用1個數據位就可以表示像素點的所有狀態,所以它的色彩深度為1bit,其它常見的顯示屏色深為16bit、24bit。
(3) 顯示器尺寸
顯示器的大小一般以英寸表示,如5英寸、21英寸、24英寸等,這個長度是指屏幕對角線的長度, 通過顯示器的對角線長度及長寬比可確定顯示器的實際長寬尺寸。
(4) 點距
點距指兩個相鄰像素點之間的距離,它會影響畫質的細膩度及觀看距離,相同尺寸的屏幕,若分辨率越高,則點距越小,畫質越細膩。如現在有些手機的屏幕分辨率比電腦顯示器的還大,這是手機屏幕點距小的原因;LED點陣顯示屏的點距一般都比較大,所以適合遠距離觀看。
27.2 液晶控制原理
圖 27-6是兩種適合於STM32芯片使用的顯示屏,我們以它為例講解控制液晶屏的原理。
圖 27-6適合STM32控制的顯示屏實物圖
這個完整的顯示屏由液晶顯示面板、電容觸摸面板以及PCB底板構成。圖中的觸摸面板帶有觸摸控制芯片,該芯片處理觸摸信號並通過引出的信號線與外部器件通訊面板中間是透明的,它貼在液晶面板上面,一起構成屏幕的主體,觸摸面板與液晶面板引出的排線連接到PCB底板上,根據實際需要,PCB底板上可能會帶有“液晶控制器芯片”。因為控制液晶面板需要比較多的資源,所以大部分低級微控制器都不能直接控制液晶面板,需要額外配套一個專用液晶控制器來處理顯示過程,外部微控制器只要把它希望顯示的數據直接交給液晶控制器即可。而不帶液晶控制器的PCB底板 ,只有小部分的電源管理電路,液晶面板的信號線與外部微控制器相連,直接控制。STM32F767系列的芯片不需要額外的液晶控制器,也就是說它把專用液晶控制器的功能集成到STM32F767芯片內部了,節約了額外的控制器成本。
27.2.1 液晶面板的控制信號
本章我們主要講解控制液晶面板(不帶控制器),液晶面板的控制信號線見表 27-1。
表 27-1 液晶面板的信號線
信號名稱 |
說明 |
R[7:0] |
紅色數據 |
G[7:0] |
綠色數據 |
B[7:0] |
藍色數據 |
CLK |
像素同步時鍾信號 |
HSYNC |
水平同步信號 |
VSYNC |
垂直同步信號 |
DE |
數據使能信號 |
(1) RGB信號線
RGB信號線各有8根,分別用於表示液晶屏一個像素點的紅、綠、藍顏色分量。使用紅綠藍顏色分量來表示顏色是一種通用的做法,打開Windows系統自帶的畫板調色工具,可看到顏色的紅綠藍分量值,見圖 27-7。常見的顏色表示會在“RGB”后面附帶各個顏色分量值的數據位數,如RGB565表示紅綠藍的數據線數分別為5、6、5根,一共為16個數據位,可表示216種顏色;而這個液晶屏的種顏色分量的數據線都有8根,所以它支持RGB888格式,一共24位數據線,可表示的顏色為224種。
圖 27-7 顏色表示法
(2) 同步時鍾信號CLK
液晶屏與外部使用同步通訊方式,以CLK信號作為同步時鍾,在同步時鍾的驅動下,每個時鍾傳輸一個像素點數據。
(3) 水平同步信號HSYNC
水平同步信號HSYNC(Horizontal Sync)用於表示液晶屏一行像素數據的傳輸結束,每傳輸完成液晶屏的一行像素數據時,HSYNC會發生電平跳變,如分辨率為800x480的顯示屏(800列,480行),傳輸一幀的圖像HSYNC的電平會跳變480次。
(4) 垂直同步信號VSYNC
垂直同步信號VSYNC(Vertical Sync)用於表示液晶屏一幀像素數據的傳輸結束,每傳輸完成一幀像素數據時,VSYNC會發生電平跳變。其中“幀”是圖像的單位,一幅圖像稱為一幀,在液晶屏中,一幀指一個完整屏液晶像素點。人們常常用“幀/秒”來表示液晶屏的刷新特性,即液晶屏每秒可以顯示多少幀圖像,如液晶屏以60幀/秒的速率運行時,VSYNC每秒鍾電平會跳變60次。
(5) 數據使能信號DE
數據使能信號DE(Data Enable)用於表示數據的有效性,當DE信號線為高電平時,RGB信號線表示的數據有效。
1.2.2 液晶數據傳輸時序
通過上述信號線向液晶屏傳輸像素數據時,各信號線的時序見圖 27-8。圖中表示的是向液晶屏傳輸一幀圖像數據的時序,中間省略了多行及多個像素點。
圖 27-8 液晶時序圖
液晶屏顯示的圖像可看作一個矩形,結合圖 27-9來理解。液晶屏有一個顯示指針,它指向將要顯示的像素。顯示指針的掃描方向方向從左到右、從上到下,一個像素點一個像素點地描繪圖形。這些像素點的數據通過RGB數據線傳輸至液晶屏,它們在同步時鍾CLK的驅動下一個一個地傳輸到液晶屏中,交給顯示指針,傳輸完成一行時,水平同步信號HSYNC電平跳變一次,而傳輸完一幀時VSYNC電平跳變一次。
圖 27-9 液晶數據傳輸圖解
但是,液晶顯示指針在行與行之間,幀與幀之間切換時需要延時,而且HSYNC及VSYNC信號本身也有寬度,這些時間參數說明見表 27-2。
表 27-2 液晶通訊中的時間參數
時間參數 |
參數說明 |
VBP (vertical back porch) |
表示在一幀圖像開始時,垂直同步信號以后的無效的行數 |
VFP (vertical front porch) |
表示在一幀圖像結束后,垂直同步信號以前的無效的行數 |
HBP (horizontal back porch) |
表示從水平同步信號開始到一行的有效數據開始之間的CLK的個數 |
HFP (horizontal front porth) |
表示一行的有效數據結束到下一個水平同步信號開始之間的CLK的個數 |
VSW (vertical sync width) |
表示垂直同步信號的寬度,單位為行 |
HSW (horizontal sync width) |
表示水平同步信號的寬度,單位為同步時鍾CLK的個數 |
在這些時間參數控制的區域,數據使能信號線“DE”都為低電平,RGB數據線的信號無效,當“DE”為高電平時,表示的數據有效,傳輸的數據會直接影響液晶屏的顯示區域。
27.2.3 顯存
液晶屏中的每個像素點都是數據,在實際應用中需要把每個像素點的數據緩存起來,再傳輸給液晶屏,這種存儲顯示數據的存儲器被稱為顯存。顯存一般至少要能存儲液晶屏的一幀顯示數據,如分辨率為800x480的液晶屏,使用RGB888格式顯示,它的一幀顯示數據大小為:3x800x480=1152000字節;若使用RGB565格式顯示,一幀顯示數據大小為:2x800x480=768000字節。
27.3 LTDC液晶控制器簡介
STM32F767系列芯片內部自帶一個LTDC液晶控制器,使用SDRAM的部分空間作為顯存,可直接控制液晶面板,無需額外增加液晶控制器芯片。STM32的LTDC液晶控制器最高支持800x600分辨率的屏幕;可支持多種顏色格式,包括RGB888、RGB565、ARGB8888和ARGB1555等(其中的“A”是指透明像素);支持2層顯示數據混合,利用這個特性,可高效地做出背景和前景分離的顯示效果,如以視頻為背景,在前景顯示彈幕。
27.3.1 圖像數據混合
LTDC外設支持2層數據混合,混合前使用2層數據源,分別為前景層和背景層,見圖 27-10。在輸出時,實際上液晶屏只能顯示一層圖像,所以LTDC在輸出數據到液晶屏前需要把2層圖像混合成一層,跟Photoshop軟件的分層合成圖片過程類似。混合時,直接用前景層中的不透明像素替換相同位置的背景像素;而前景層中透明像素的位置,則使用背景的像素數據,即顯示背景層的像素。
圖 27-10 圖像的分層與混合
如果想使用圖像混合功能,前景層必須使用包含透明的像素格式,如ARGB1555或ARGB8888。其中ARGB1555使用1個數據位表示透明元素,它只能表示像素是透明或不透明,當最高位(即“A”位)為1時,表示這是一個不透明的像素,具體顏色值為RGB位表示的顏色,而當最高位為0時,表示這是一個完全透明的像素,RGB位的數據無效;而ARGB8888的像素格式使用8個數據位表示透明元素,它使用高8位表示“透明度”(即代表“A”的8個數據位),若A的值為“0xFF”,則表示這個像素完全不透明,若A的值為“0x00”則表示這個像素完全透明,介於它們之間的值表示其RGB顏色不同程度的透明度,即混合后背景像素根據這個值按比例來表示。
注意液晶屏本身是沒有透明度概念的,如24位液晶屏的像素數據格式是RGB888,RGB顏色各有對應的8根數據線,不存在用於表示透明度的數據線,所以實際上ARGB只是針對內部分層數據處理的格式,最終經過混合運算得出直接顏色數據RGB888才能交給液晶屏顯示。
27.3.2 LTDC結構框圖剖析
圖 27-11是LTDC控制器的結構框圖,它主要包含信號線、圖像處理單元、寄存器及時鍾信號。
圖 27-11 LTDC控制器框圖
1. LTDC信號線
LTDC的控制信號線與液晶顯示面板的數據線一一對應,包含有HSYNC、VSYNC、DE、CLK及RGB數據線各8根。設計硬件時把液晶面板與STM32對應的這些引腳連接起來即可,查閱《STM32F7xx規格書》可知LTDC信號線對應的引腳,見表 27-3。
表 27-3 LTDC引腳表
引腳號 |
LTDC信號 |
引腳號 |
LTDC信號 |
引腳號 |
LTDC信號 |
引腳號 |
LTDC信號 |
PA3 |
LCD_B5 |
PE11 |
LCD_G3 |
PH14 |
LCD_G3 |
PJ4 |
LCD_R5 |
PA4 |
LCD_VSYNC |
PE12 |
LCD_B4 |
PH15 |
LCD_G4 |
PJ5 |
LCD_R6 |
PA6 |
LCD_G2 |
PE13 |
LCD_DE |
PI0 |
LCD_G5 |
PJ6 |
LCD_R7 |
PA8 |
LCD_R6 |
PE14 |
LCD_CLK |
PI1 |
LCD_G6 |
PJ7 |
LCD_G0 |
PA11 |
LCD_R4 |
PE15 |
LCD_R7 |
PI2 |
LCD_G7 |
PJ8 |
LCD_G1 |
PA12 |
LCD_R5 |
PF10 |
LCD_DE |
PI4 |
LCD_B4 |
PJ9 |
LCD_G2 |
PB8 |
LCD_B6 |
PG6 |
LCD_R7 |
PI5 |
LCD_B5 |
PJ10 |
LCD_G3 |
PB9 |
LCD_B7 |
PG7 |
LCD_CLK |
PI6 |
LCD_B6 |
PJ11 |
LCD_G4 |
PB10 |
LCD_G4 |
PG10 |
LCD_B2 |
PI7 |
LCD_B7 |
PJ12 |
LCD_B0 |
PB11 |
LCDG5 |
PG11 |
LCD_B3 |
PI9 |
LCD_VSYNC |
PJ13 |
LCD_B1 |
PC6 |
LCD_HSYNC |
PG12 |
LCD_B1 |
PI10 |
LCD_HSYNC |
PJ14 |
LCD_B2 |
PC7 |
LCD_G6 |
PH2 |
LCD_R0 |
PI12 |
LCD_HSYNC |
PJ15 |
LCD_B3 |
PC10 |
LCD_R2 |
PH3 |
LCD_R1 |
PI13 |
LCD_VSYNC |
PK0 |
LCD_G5 |
PD3 |
LCD_G7 |
PH8 |
LCD_R2 |
PI14 |
LCD_CLK |
PK1 |
LCD_G6 |
PD6 |
LCD_B2 |
PH9 |
LCD_R3 |
PI15 |
LCD_R0 |
PK2 |
LCD_G7 |
PD10 |
LCD_B3 |
PH10 |
LCD_R4 |
PJ0 |
LCD_R1 |
PK3 |
LCD_B4 |
PE4 |
LCD_B0 |
PH11 |
LCD_R5 |
PJ1 |
LCD_R2 |
PK4 |
LCD_B5 |
PE5 |
LCD_G0 |
PH12 |
LCD_R6 |
PJ2 |
LCD_R3 |
PK5 |
LCD_B6 |
PE6 |
LCD_G1 |
PH13 |
LCD_G2 |
PJ3 |
LCD_R4 |
PK6 |
LCD_B7 |
2. 圖像處理單元
LTDC框圖標號‚表示的是圖像處理單元,它通過“AHB接口”獲取顯存中的數據,然后按分層把數據分別發送到兩個“層FIFO”緩存,每個FIFO可緩存64x32位的數據,接着從緩存中獲取數據交給“PFC”(像素格式轉換器),它把數據從像素格式轉換成字(ARGB8888)的格式,再經過“混合單元”把兩層數據合並起來,最終混合得到的是單層要顯示的數據,通過信號線輸出到液晶面板。這部分結構與DMA2D的很類似,我們在下一小節詳細講解。
在輸出前混合單元的數據還經過一個“抖動單元”,它的作用是當像素數據格式的色深大於液晶面板實際色深時,對像素數據顏色進行舍入操作,如向18位顯示器上顯示24位數據時,抖動單元把像素數據的低6位與閾值比較,若大於閾值,則向數據的第7位進1,否則直接舍掉低6位。
3. 配置和狀態寄存器
框圖中標號„表示的是LTDC的控制邏輯,它包含了LTDC的各種配置和狀態寄存器。如配置與液晶面板通訊時信號線的有效電平、各種時間參數、有效數據寬度、像素格式及顯存址等等,LTDC外設根據這些配置控制數據輸出,使用AHB接口從顯存地址中搬運數據到液晶面板。還有一系列用於指示當前顯示狀態和位置的狀態寄存器,通過讀取這些寄存器可以了解LTDC的工作狀態。
4. 時鍾信號
LTDC外設使用3種時鍾信號,包括AHB時鍾、APB2時鍾及像素時鍾LCD_CLK。AHB時鍾用於驅動數據從存儲器存儲到FIFO,APB2時鍾用於驅動LTDC的寄存器。而LCD_CLK用於生成與液晶面板通訊的同步時鍾,見圖 27-12,它的來源是HSE(高速外部晶振),經過“/M”分頻因子分頻輸出到“PLLSAI”分頻器,信號由“PLLSAI”中的倍頻因子N倍頻得到“PLLSAIN”時鍾、然后由“/R”因子分頻得到“PLLCDCLK”時鍾,再經過“DIV”因子得到“LCD-TFT clock”,“LCD-TFT clock”即通訊中的同步時鍾LCD_CLK,它使用LCD_CLK引腳輸出。
圖 27-12 LCD_CLK時鍾來源
27.4 DMA2D圖形加速器簡介
在實際使用LTDC控制器控制液晶屏時,使LTDC正常工作后,往配置好的顯存地址寫入要顯示的像素數據,LTDC就會把這些數據從顯存搬運到液晶面板進行顯示,而顯示數據的容量非常大,所以我們希望能用DMA來操作,針對這個需求,STM32專門定制了DMA2D外設,它可用於快速繪制矩形、直線、分層數據混合、數據復制以及進行圖像數據格式轉換,可以把它理解為圖形專用的DMA。
27.4.1 DMA2D結構框圖剖析
圖 27-13是DMA2D的結構框圖,它與前面LTDC結構里的圖像處理單元很類似,主要為分層FIFO、PFC及彩色混合器。
圖 27-13 DMA2D結構框圖
1. FG FIFO與BG FIFO
FG FIFO(Foreground FIFO)與BG FIFO(Backgroun FIFO)是兩個64x32位大小的緩沖區,它們用於緩存從AHB總線獲取的像素數據,分別專用於緩沖前景層和背景層的數據源。
AHB總線的數據源一般是SDRAM,也就是說在LTDC外設中配置的前景層及背景層數據源地址一般指向SDRAM的存儲空間,使用SDRAM的部分空間作為顯存。
2. FG PFC與BG PFC
FG PFC(FG Pixel Format Convertor)與BG PFC(BG Pixel Format Convertor)是兩個像素格式轉換器,分別用於前景層和背景層的像素格式轉換,不管從FIFO的數據源格式如何,都把它轉化成字的格式(即32位),ARGB8888。
圖中的“ɑ”表示Alpha,即透明度,經過PFC,透明度會被擴展成8位的格式。
圖中的“CLUT”表示顏色查找表(Color Lookup Table),顏色查找表是一種間接的顏色表示方式,它使用一個256x32位的空間緩存256種顏色,顏色的格式是ARGB8888或RGB888。見圖 27-14,利用顏色查找表,實際的圖像只使用這256種顏色,而圖像的每個像素使用8位的數據來表示,該數據並不是直接的RGB顏色數據,而是指向顏色查找表的地址偏移,即表示這個像素點應該顯示顏色查找表中的哪一種顏色。在圖像大小不變的情況下,利用顏色查找表可以擴展顏色顯示的能力,其特點是用8位的數據表示了一個24或32位的顏色,但整個圖像顏色的種類局限於顏色表中的256種。DMA2D的顏色查找表可以由CPU自動加載或編程手動加載。
圖 27-14 使用顏色查找表顯示圖像的過程
3. 混合器
FIFO中的數據源經過PFC像素格式轉換器后,前景層和背景層的圖像都輸入到混合器中運算,運算公式見圖 27-15。
圖 27-15 混合公式
從公式可以了解到混合器的運算主要是使用前景和背景的透明度作為因子,對像素RGB顏色值進行加權運算。經過混合器后,兩層數據合成為一層ARGB8888格式的圖像。
4. OUT PFC
OUT PFC是輸出像素格式轉換器,它把混合器轉換得到的圖像轉換成目標格式,如ARGB8888、RGB888、RGB565、ARGB1555或ARGB4444,具體的格式可根據需要在輸出PFC控制寄存器DMA2D_OPFCCR中選擇。
STM32F767芯片使用LTDC、DMA2D及RAM存儲器,構成了一個完整的液晶控制器。LTDC負責不斷刷新液晶屏,DMA2D用於圖像數據搬運、混合及格式轉換,RAM存儲器作為顯存。其中顯存可以使用STM32芯片內部的SRAM或外擴SDRAM/SRAM,只要容量足夠大即可(至少要能存儲一幀圖像數據)。
27.5 LTDC初始化結構體
控制LTDC涉及到非常多的寄存器,利用LTDC初始化結構體可以減輕開發和維護的工作量,LTDC初始化結構體見代碼清單 24-。
代碼清單 27-1 LTDC初始化結構體LTDC_InitTypeDef
1 /**
2 * @brief LTDC Init structure definition
3 */
4 typedef struct
5 {
6 uint32_t HSPolarity; /*配置行同步信號HSYNC的極性 */
7 uint32_t VSPolarity; /*配置垂直同步信號VSYNC的極性 */
8 uint32_t DEPolarity; /*配置數據使能信號DE的極性*/
9 uint32_t PCPolarity; /*配置像素時鍾信號CLK的極性 */
10 uint32_t HorizontalSync; /*配置行同步信號HSYNC的寬度(HSW-1) */
11 uint32_t VerticalSync; /*配置垂直同步信號VSYNC的寬度(VSW-1) */
12 uint32_t AccumulatedHBP; /*配置(HSW+HBP-1)的值*/
13 uint32_t AccumulatedVBP; /*配置(VSW+VBP-1)的值*/
14 uint32_t AccumulatedActiveW; /*配置(HSW+HBP+有效寬度-1)的值*/
15 uint32_t AccumulatedActiveH; /*配置(VSW+VBP+有效高度-1)的值*/
16 uint32_t TotalWidth; /*配置(HSW+HBP+有效寬度+HFP-1)的值*/
17 uint32_t TotalHeigh; /*配置(VSW+VBP+有效高度+VFP-1)的值*/
18 uint32_t Backcolor; /*配置背景顏色值*/
19 } LTDC_InitTypeDef;
這個結構體大部分成員都是用於定義LTDC的時序參數的,包括信號有效電平及各種時間參數的寬度,配合“液晶數據傳輸時序”中的說明更易理解。各個成員介紹如下,括號中的是STM32 HAL庫定義的宏:
(1) HSPolarity
本成員用於設置行同步信號HSYNC的極性,即HSYNC有效時的電平,該成員的值可設置為高電平(HSPolarity_AH)或低電平(LTDC_HSPolarity_AL)。
(2) VSPolarity
本成員用於設置垂直同步信號VSYNC的極性,可設置為高電平(VSPolarity_AH)或低電平(VSPolarity_AL)。
(3) DEPolarity
本成員用於設置數據使能信號DE的極性,可設置為高電平(DEPolarity_AH)或低電平(DEPolarity_AL)。
(4) PCPolarity
本成員用於設置像素時鍾信號CLK的極性,可設置為上升沿(DEPolarity_AH)或下降沿(DEPolarity_AL),表示RGB數據信號在CLK的哪個時刻被采集。
(5) HorizontalSync
本成員設置行同步信號HSYNC的寬度HSW,它以像素時鍾CLK的周期為單位,實際寫入該參數時應寫入(HSW-1),參數范圍為0x000- 0xFFF。
(6) VerticalSync
本成員設置垂直同步信號VSYNC的寬度VSW,它以“行”為位,實際寫入該參數時應寫入(VSW-1) ,參數范圍為0x000- 0x7FF。
(7) AccumulatedHBP
本成員用於配置“水平同步像素HSW”加“水平后沿像素HBP”的累加值,實際寫入該參數時應寫入(HSW+HBP-1) ,參數范圍為0x000- 0xFFF。
(8) AccumulatedVBP
本成員用於配置“垂直同步行VSW”加“垂直后沿行VBP”的累加值,實際寫入該參數時應寫入(VSW+VBP-1) ,參數范圍為0x000- 0x7FF。
(9) AccumulatedActiveW
本成員用於配置“水平同步像素HSW”加“水平后沿像素HBP”加“有效像素”的累加值,實際寫入該參數時應寫入(HSW+HBP+有效寬度-1) ,參數范圍為0x000- 0xFFF。
(10) AccumulatedActiveH
本成員用於配置“垂直同步行VSW”加“垂直后沿行VBP”加“有效行”的累加值,實際寫入該參數時應寫入(VSW+VBP+有效高度-1) ,參數范圍為0x000- 0x7FF。
(11) TotalWidth
本成員用於配置“水平同步像素HSW”加“水平后沿像素HBP”加“有效像素”加“水平前沿像素HFP”的累加值,即總寬度,實際寫入該參數時應寫入(HSW+HBP+有效寬度+HFP-1) ,參數范圍為0x000- 0xFFF。
(12) TotalHeigh
本成員用於配置“垂直同步行VSW”加“垂直后沿行VBP”加“有效行”加“垂
直前沿行VFP”的累加值,即總高度,實際寫入該參數時應寫入(HSW+HBP+有效高度+VFP-1) ,參數范圍為0x000- 0x7FF。
(13) BackgroundRedValue/ GreenValue/ BlueValue
這三個結構體成員用於配置背景的顏色值,見圖 27-16,這里說的背景層與前面提到的“前景層/背景層”概念有點區別,它們對應下圖中的“第2層/第1層”,而在這兩層之外,還有一個最終的背景層,當第1第2層都透明時,這個背景層就會被顯示,而這個背景層是一個純色的矩形,它的顏色值就是由這三個結構體成員配置的,各成員的參數范圍為0x00- 0xFF。
圖 27-16 兩層與背景混合
對這些LTDC初始化結構體成員賦值后,調用庫函數HAL_LTDC_Init可把這些參數寫入到LTDC的各個配置寄存器,LTDC外設根據這些配置控制時序。
27.6 LTDC層級初始化結構體
LTDC初始化結構體只是配置好了與液晶屏通訊的基本時序,還有像素格式、顯存地址等諸多參數需要使用LTDC層級初始化結構體完成,見代碼清單 27-2。
代碼清單 27-2 LTDC層級初始化結構體LTDC_Layer_InitTypeDef
1 /**
2 * @brief LTDC Layer structure definition
3 */
4 typedef struct
5 {
6 uint32_t WindowX0; /*配置窗口的行起始位置 */
7 uint32_t WindowX1; /*配置窗口的行結束位置 */
8 uint32_t WindowY0; /*配置窗口的垂直起始位置 */
9 uint32_t WindowY1; /*配置窗口的垂直束位置 */
10 uint32_t PixelFormat; /*配置當前層的像素格式*/
11 uint32_t Alpha; /*配置當前層的透明度Alpha常量值*/
12 uint32_t Alpha0; /*配置當前層的默認透明值*/
16 uint32_t BlendingFactor_1; /*配置混合因子BlendingFactor1 */
17 uint32_t BlendingFactor_2; /*配置混合因子BlendingFactor2 */
18 uint32_t FBStartAdress; /*配置當前層的顯存起始位置*/
19 uint32_t ImageWidth; /*配置當前層的圖像寬度 */
20 uint32_t ImageHeight; /*配置當前層的圖像高度*/
21 LTDC_ColorTypeDef Backcolor;/* 配置當前層的背景顏色*/
22 } LTDC_LayerCfgTypeDef;
LTDC_LayerCfgTypeDef各個結構體成員的功能介紹如下:
(1) WindowX0 / WindowX1/ WindowY0/ WindowY1這些成員用於確定該層顯示窗口的邊界,分別表示行起始、行結束、垂直起始及垂直結束的位置,見圖 27-17。注意這些參數包含同步HSW/VSW、后沿大小HBP/VBP和有效數據區域的內部時序發生器的配置,表 27-4的是各個窗口配置成員應寫入的數值。
圖 27-17 配置可層的顯示窗口
表 27-4 各個窗口成員值
LTDC層級窗口配置成員 |
等效於LTDC時序參數配置成員的值 |
實際值 |
WindowX0 |
(LTDC_AccumulatedHBP+1) |
HBP + HSW |
WindowX1 |
LTDC_AccumulatedActiveW |
HSW+HBP+LCD_PIXEL_WIDTH-1 |
WindowY0 |
(LTDC_AccumulatedVBP+1) |
VBP + VSW |
WindowY1 |
LTDC_AccumulatedActiveH |
VSW+VBP+LCD_PIXEL_HEIGHT-1 |
(2) PixelFormat
本成員用於設置該層數據的像素格式,可以設置為LTDC_PIXEL_FORMAT_ARGB8888/ RGB888/ RGB565/ ARGB1555/ ARGB4444/ L8/ AL44/ AL88格式。
(3) Alpha
本成員用於設置該層恆定的透明度常量Alpha,稱為恆定Alpha,參數范圍為0x00-0xFF,在圖層混合時,可根據后面的BlendingFactor成員的配置,選擇是只使用這個恆定Alpha進行混合運算還是把像素本身的Alpha值也加入到運算中。
(4) Alpha0
這些成員用於配置該層的默認透明分量,該顏色在定義的層窗口外或在層禁止時使用。
(5) LTDC_BlendingFactor_1/2
本成員用於設置混合系數 BF1 和 BF2。每一層實際顯示的顏色都需要使用透明度參與運算,計算出不包含透明度的直接RGB顏色值,然后才傳輸給液晶屏(因為液晶屏本身沒有透明的概念)。混合的計算公式為:
BC = BF1 x C + BF2 x Cs,
公式中的參數見表 27-5:
表 27-5 混合公式參數說明表
參數 |
說明 |
CA |
PAxCA |
BC |
混合后的顏色(混合結果) |
- |
- |
C |
當前層顏色 |
- |
- |
Cs |
底層混合后的顏色 |
- |
- |
BF1 |
混合系數1 |
等於(恆定Alpha值) |
等於(恆定Alpha x 像素Alpha值) |
BF2 |
混合系數2 |
等於(1-恆定Alpha) |
等於(1-恆定Alpha x 像素Alpha值) |
本結構體成員可以設置BF1/BF2參數使用CA配置(LTDC_BlendingFactor1/2_CA)還是PAxCA配置(LTDC_BlendingFactor1/2_PAxCA)。配置成CA表示混合系數中只包含恆定的Alpha值,即像素本身的Alpha不會影響混合效果,若配置成PAxCA,則混合系數中包含有像素本身的Alpha值,即把像素本身的Alpha加入到混合運算中。其中的恆定Alpha值即前面“LTDC_ConstantAlpha”結構體配置參數的透明度百分比:(配置的Alpha值/0xFF)。
圖 27-18 兩層與背景混合
見圖 276,數據源混合時,由下至上,如果使用了2層,則先將第1層與LTDC背景混合,隨后再使用該混合顏色與第2層混合得到最終結果。例如,當只使用第1層數據源時,且BF1及BF2都配置為使用恆定Alpha,該Alpha值在LTDC_ConstantAlpha結構體成員值中被配置為240(0xF0)。因此,恆定Alpha值為240/255=0.94。若當前層顏色C=128,背景色Cs=48,那么第1層與背景色的混合結果為:
BC=恆定Alpha x C + (1- 恆定Alpha) x Cs=0.94 x Cs +(1-0.94)x 48=123
(6) FBStartAdress
本成員用於設置該層的顯存首地址,該層的像素數據保存在從這個地址開始的存儲空間內。
(7) ImageWidth
本成員用於設置當前層的行數據長度,即每行的有效像素點個數x每個像素的字節數,實際配置該參數時應寫入值(行有效像素個數x每個像素的字節數+3),每個像素的字節數跟像素格式有關,如RGB565為2字節,RGB888為3字節,ARGB8888為4字節。
(8) ImageHeight
本成員用於設置從某行的有效像素起始位置到下一行起始位置處的數據增量,無特殊情況的話,它一般就直接等於行的有效像素個數x每個像素的字節數。
(9) Backcolor
本成員用於設置當前層的背景顏色。
配置完LTDC_LayerCfgTypeDef層級初始化結構體后,調用庫函數LTDC_LayerInit可把這些配置寫入到LTDC的層級控制寄存器中,完成初始化。初始化完成后LTDC會不斷把顯存空間的數據傳輸到液晶屏進行顯示,我們可以直接修改或使用DMA2D修改顯存中的數據,從而改變顯示的內容。
27.7 DMA2D初始化結構體
在實際顯示時,我們常常采用DMA2D描繪直線和矩形,這個時候會用到DMA2D結構體,見代碼清單 27-3。
代碼清單 27-3 DMA2D初始化結構體
1 /**
2 * @brief DMA2D Init structure definition
3 */
4 typedef struct
5 {
6 uint32_t Mode; /*配置DMA2D的傳輸模式*/
7 uint32_t ColorMode; /*配置DMA2D的顏色模式 */
8 uint32_t OutputOffset; /*配置輸出圖像的偏移量*/
9 uint32_t AlphaInverted; /*為輸出像素格式轉換器選擇常規或反轉 alpha 值*/
10 uint32_t RedBlueSwap; /*選擇常規模式 (RGB 或 ARGB) 或交換模式 (BGR 或 ABGR)*/
16 } DMA2D_InitTypeDef;
DMA2D初始化結構體中的各個成員介紹如下:
(1) DMA2D_Mode
本成員用於配置DMA2D的工作模式,它可以被設置為表 27-6中的值。
表 27-6 DMA2D的工作模式
宏 |
說明 |
DMA2D_M2M |
從存儲器到存儲器(僅限FG獲取數據源) |
DMA2D_M2M_PFC |
存儲器到存儲器並執行 PFC(僅限 FG PFC 激活時的 FG 獲取) |
DMA2D_M2M_BLEND |
存儲器到存儲器並執行混合(執行 PFC 和混合時的 FG 和 BG 獲取) |
DMA2D_R2M |
寄存器到存儲器(無 FG 和 BG,僅輸出階段激活) |
這幾種工作模式主要區分數據的來源、是否使能PFC以及是否使能混合器。使用DMA2D時,可把數據從某個位置搬運到顯存,該位置可以是DMA2D本身的寄存器,也可以是設置好的DMA2D前景地址、背景地址(即從存儲器到存儲器)。若使能了PFC,則存儲器中的數據源會經過轉換再傳輸到顯存。若使能了混合器,DMA2D會把兩個數據源中的數據混合后再輸出到顯存。
若使用存儲器到存儲器模式,需要調用庫函數DMA2D_FGConfig,使用初始化結構體DMA2D_FG_InitTypeDef配置數據源的格式、地址等參數。(背景層使用函數DMA2D_BGConfig和結構體DMA2D_BG_InitTypeDef)
(2) Mode
本成員用於配置DMA2D的傳輸模式。
(3) ColorMode
這幾個成員用於配置DMA2D的輸出顏色模式,若DMA2D工作在“寄存器到存儲器”(DMA2D_R2M)模式時,這個顏色值作為數據源,被DMA2D復制到顯存空間,即目標空間都會被填入這一種色彩。
(4) AlphaInverted
為輸出像素格式轉換器選擇常規或反轉 alpha 值。
(5) OutputOffset
本成員用於配置行偏移(以像素為單位),行偏移會被添加到各行的結尾,用於確定下一行的起始地址。如表 27-7中的黃色格子表示行偏移,綠色格子表示要顯示的數據。左表中顯示的是一條垂直的線,且線的寬度為1像素,所以行偏移的值=7-1=6,即“行偏移的值=行寬度-線的寬度”,右表中的線寬度為2像素,行偏移的值=7-2=5。
表 27-7 數據傳輸示例(綠色的為要顯示的數據,黃色的為行偏移)
本成員用於配置顏色序列轉換,常規模式是 (RGB 或 ARGB)可以交換為 (BGR 或 ABGR)模式。(6) RedBlueSwap
配置完這些結構體成員,調用庫函數DMA2D_Init即可把這些參數寫入到DMA2D的控制寄存器中,然后再調用HAL_DMA2D_Start函數開啟數據傳輸及轉換。
27.8 LTDC/DMA2D—液晶顯示實驗
本小節講解如何使用LTDC及DMA2D外設控制型號為“STD800480”的5寸液晶屏,見圖 27-19,該液晶屏的分辨率為800x480,支持RGB888格式。
學習本小節內容時,請打開配套的“LTDC/DMA2D—液晶顯示英文”工程配合閱讀。
27.8.1 硬件設計
圖 271-9 液晶屏實物圖
圖 27-19液晶屏背面的PCB電路對應圖 2720、圖 2721、圖 2722、圖 2724中的原理圖,分別是升壓電路、觸摸屏接口、液晶屏接口及排針接口。升壓電路把輸入的5V電源升壓為20V,輸出到液晶屏的背光燈中;觸摸屏及液晶屏接口通過FPC插座把兩個屏的排線連接到PCB電路板上,這些FPC插座與信號引出到屏幕右側的排針處,方便整個屏幕與外部器件相連。
圖 27-20升壓電路原理圖
升壓電路中的BK引腳可外接PWM信號,控制液晶屏的背光強度,BK為高電平時輸出電壓。
圖 27-21 電容屏接口
電容觸摸屏使用I2C通訊,它的排線接口包含了I2C的通訊引腳SCL、SDA,還包含控制觸摸屏芯片復位的RSTN信號以及觸摸中斷信號INT。
圖 27-22 液晶屏接口
關於這部分液晶屏的排線接口說明見圖 27-23。
圖 27-23 液晶排線接口
圖 27-24 排針接口
以上是我們STM32F767實驗板使用的5寸屏原理圖,它通過屏幕上的排針接入到實驗板的液晶排母接口,與STM32芯片的引腳相連,連接見圖 27-25。
圖 27-25 屏幕與實驗板的引腳連接
由於液晶屏的部分引腳與實驗板的CAN芯片信號引腳相同,所以使用液晶屏的時候不能使用CAN通訊。
以上原理圖可查閱《LCD5.0-黑白原理圖》及《秉火F767開發板黑白原理圖》文檔獲知,若您使用的液晶屏或實驗板不一樣,請根據實際連接的引腳修改程序。
27.8.2 軟件設計
為了使工程更加有條理,我們把LCD控制相關的代碼獨立分開存儲,方便以后移植。在“FMC—讀寫SDRAM”工程的基礎上新建“bsp_lcd.c”及“bsp_lcd.h”文件,這些文件也可根據您的喜好命名,它們不屬於STM32 HAL庫的內容,是由我們自己根據應用需要編寫的。
1. 編程要點
(1) 初始化LTDC時鍾、DMA2D時鍾、GPIO時鍾;
(2) 初始化SDRAM,以便用作顯存;
(3) 根據液晶屏的參數配置LTDC外設的通訊時序;
(4) 配置LTDC層級控制參數,配置顯存地址;
(5) 初始化DMA2D,使用DMA2D輔助顯示;
(6) 編寫測試程序,控制液晶輸出。
2. 代碼分析
LTDC硬件相關宏定義
我們把LTDC控制液晶屏硬件相關的配置都以宏的形式定義到 “bsp_lcd.h”文件中,見代碼清單 24-4。
代碼清單 274 LTDC硬件配置相關的宏(省略了部分數據線)
1 //紅色數據線
2 #define LTDC_R0_GPIO_PORT GPIOH
3 #define LTDC_R0_GPIO_CLK_ENABLE() __GPIOH_CLK_ENABLE()
4 #define LTDC_R0_GPIO_PIN GPIO_PIN_2
5 #define LTDC_R0_AF GPIO_AF14_LTDC //使用LTDC復用編號AF14
6
7 #define LTDC_R1_GPIO_PORT GPIOH
8 #define LTDC_R1_GPIO_CLK_ENABLE() __GPIOH_CLK_ENABLE()
9 #define LTDC_R1_GPIO_PIN GPIO_PIN_3
10 #define LTDC_R1_AF GPIO_AF14_LTDC
11
12 #define LTDC_R2_GPIO_PORT GPIOH
13 #define LTDC_R2_GPIO_CLK_ENABLE() __GPIOH_CLK_ENABLE()
14 #define LTDC_R2_GPIO_PIN GPIO_PIN_8
15 #define LTDC_R2_AF GPIO_AF14_LTDC
16
17 #define LTDC_R3_GPIO_PORT GPIOB
18 #define LTDC_R3_GPIO_CLK_ENABLE() __GPIOB_CLK_ENABLE()
19 #define LTDC_R3_GPIO_PIN GPIO_PIN_0
20 #define LTDC_R3_AF GPIO_AF9_LTDC //使用LTDC復用編號AF9
21
22
23 //控制信號線
24 /*像素時鍾CLK*/
25 #define LTDC_CLK_GPIO_PORT GPIOG
26 #define LTDC_CLK_GPIO_CLK_ENABLE() __GPIOG_CLK_ENABLE()
27 #define LTDC_CLK_GPIO_PIN GPIO_PIN_7
28 #define LTDC_CLK_AF GPIO_AF14_LTDC
29 /*水平同步信號HSYNC*/
30 #define LTDC_HSYNC_GPIO_PORT GPIOI
31 #define LTDC_HSYNC_GPIO_CLK_ENABLE() __GPIOI_CLK_ENABLE()
32 #define LTDC_HSYNC_GPIO_PIN GPIO_PIN_10
33 #define LTDC_HSYNC_AF GPIO_AF14_LTDC
34 /*垂直同步信號VSYNC*/
35 #define LTDC_VSYNC_GPIO_PORT GPIOI
36 #define LTDC_VSYNC_GPIO_CLK_ENABLE() __GPIOI_CLK_ENABLE()
37 #define LTDC_VSYNC_GPIO_PIN GPIO_PIN_9
38 #define LTDC_VSYNC_AF GPIO_AF14_LTDC
39
40 /*數據使能信號DE*/
41 #define LTDC_DE_GPIO_PORT GPIOF
42 #define LTDC_DE_GPIO_CLK_ENABLE() __GPIOF_CLK_ENABLE()
43 #define LTDC_DE_GPIO_PIN GPIO_PIN_10
44 #define LTDC_DE_AF GPIO_AF14_LTDC
45 /*液晶屏使能信號DISP,高電平使能*/
46 #define LTDC_DISP_GPIO_PORT GPIOD
47 #define LTDC_DISP_GPIO_CLK_ENABLE() __GPIOD_CLK_ENABLE()
48 #define LTDC_DISP_GPIO_PIN GPIO_PIN_4
49 /*液晶屏背光信號,高電平使能*/
50 #define LTDC_BL_GPIO_PORT GPIOD
51 #define LTDC_BL_GPIO_CLK_ENABLE() __GPIOD_CLK_ENABLE()
52 #define LTDC_BL_GPIO_PIN GPIO_PIN_7
以上代碼根據硬件的連接,把與LTDC與液晶屏通訊使用的引腳號、引腳源以及復用功能映射都以宏封裝起來。其中部分LTDC信號的復用功能映射比較特殊,如用作R3信號線的PB0,它的復用功能映射值為AF9,而大部分LTDC的信號線都是AF14,見圖 27-26,在編寫宏的時候要注意區分。
圖 27-26 LTDC的復用功能映射
初始化LTDC的 GPIO
利用上面的宏,編寫LTDC的GPIO引腳初始化函數,見代碼清單 24-5。
代碼清單 27-5 LTDC的GPIO初始化函數(省略了部分數據線)
1 static void LCD_GPIO_Config(void)
2 {
3 GPIO_InitTypeDef GPIO_InitStruct;
4
5 /* 使能LCD使用到的引腳時鍾 */
6 //紅色數據線,此處省略了部分代碼
7 LTDC_R0_GPIO_CLK_ENABLE();
8 LTDC_R1_GPIO_CLK_ENABLE();
9 LTDC_R2_GPIO_CLK_ENABLE();
10 \
11 LTDC_CLK_GPIO_CLK_ENABLE();
12 LTDC_HSYNC_GPIO_CLK_ENABLE();
13 LTDC_VSYNC_GPIO_CLK_ENABLE();
14 \
15 LTDC_DE_GPIO_CLK_ENABLE();
16 LTDC_DISP_GPIO_CLK_ENABLE();
17 LTDC_BL_GPIO_CLK_ENABLE();
18 /* GPIO配置 */
19
20 /* 紅色數據線 */
21 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
22 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
23 GPIO_InitStruct.Pull = GPIO_PULLUP;
24
25 GPIO_InitStruct.Pin = LTDC_R0_GPIO_PIN;
26 GPIO_InitStruct.Alternate = LTDC_R0_AF;
27 HAL_GPIO_Init(LTDC_R0_GPIO_PORT, &GPIO_InitStruct);
28
29 GPIO_InitStruct.Pin = LTDC_R1_GPIO_PIN;
30 GPIO_InitStruct.Alternate = LTDC_R1_AF;
31 HAL_GPIO_Init(LTDC_R1_GPIO_PORT, &GPIO_InitStruct);
32
33 GPIO_InitStruct.Pin = LTDC_B7_GPIO_PIN;
34 GPIO_InitStruct.Alternate = LTDC_B7_AF;
35 HAL_GPIO_Init(LTDC_B7_GPIO_PORT, &GPIO_InitStruct);
36
37 //控制信號線
38 GPIO_InitStruct.Pin = LTDC_CLK_GPIO_PIN;
39 GPIO_InitStruct.Alternate = LTDC_CLK_AF;
40 HAL_GPIO_Init(LTDC_CLK_GPIO_PORT, &GPIO_InitStruct);
41
42 GPIO_InitStruct.Pin = LTDC_HSYNC_GPIO_PIN;
43 GPIO_InitStruct.Alternate = LTDC_HSYNC_AF;
44 HAL_GPIO_Init(LTDC_HSYNC_GPIO_PORT, &GPIO_InitStruct);
45
46 GPIO_InitStruct.Pin = LTDC_VSYNC_GPIO_PIN;
47 GPIO_InitStruct.Alternate = LTDC_VSYNC_AF;
48 HAL_GPIO_Init(LTDC_VSYNC_GPIO_PORT, &GPIO_InitStruct);
49
50 GPIO_InitStruct.Pin = LTDC_DE_GPIO_PIN;
51 GPIO_InitStruct.Alternate = LTDC_DE_AF;
52 HAL_GPIO_Init(LTDC_DE_GPIO_PORT, &GPIO_InitStruct);
53
54 //背光BL 及液晶使能信號DISP
55 GPIO_InitStruct.Pin = LTDC_DISP_GPIO_PIN;
56 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
57 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
58 GPIO_InitStruct.Pull = GPIO_PULLUP;
59
60 HAL_GPIO_Init(LTDC_DISP_GPIO_PORT, &GPIO_InitStruct);
61
62
63 GPIO_InitStruct.Pin = LTDC_BL_GPIO_PIN;
64 HAL_GPIO_Init(LTDC_BL_GPIO_PORT, &GPIO_InitStruct);
65
66 }
與所有使用到GPIO的外設一樣,都要先把使用到的GPIO引腳模式初始化,以上代碼把LTDC的信號線全都初始化為LCD復用功能,而背光BL及液晶使能DISP信號則被初始化成普通的推挽輸出模式,並且在初始化完畢后直接控制它們開啟背光及使能液晶屏。
配置LTDC的模式
接下來需要配置LTDC的工作模式,這個函數的主體是根據液晶屏的硬件特性,設置LTDC與液晶屏通訊的時序參數及信號有效極性。錯誤!未找到引用源。。
代碼清單 27-6 配置LTDC的模式
1 void LCD_Init(void)
2 {
3 RCC_PeriphCLKInitTypeDef periph_clk_init_struct;
4 /* 使能LTDC時鍾 */
5 __HAL_RCC_LTDC_CLK_ENABLE();
6 /* 使能DMA2D時鍾 */
7 __HAL_RCC_DMA2D_CLK_ENABLE();
8 /* 初始化LCD引腳 */
9 LCD_GPIO_Config();
10 /* 初始化SDRAM 用作LCD 顯存*/
11 SDRAM_Init();
12 /* 配置LTDC參數 */
13 Ltdc_Handler.Instance = LTDC;
14 /* 配置行同步信號寬度(HSW-1) */
15 Ltdc_Handler.Init.HorizontalSync =HSW-1;
16 /* 配置垂直同步信號寬度(VSW-1) */
17 Ltdc_Handler.Init.VerticalSync = VSW-1;
18 /* 配置(HSW+HBP-1) */
19 Ltdc_Handler.Init.AccumulatedHBP = HSW+HBP-1;
20 /* 配置(VSW+VBP-1) */
21 Ltdc_Handler.Init.AccumulatedVBP = VSW+VBP-1;
22 /* 配置(HSW+HBP+有效像素寬度-1) */
23 Ltdc_Handler.Init.AccumulatedActiveW = HSW+HBP+LCD_PIXEL_WIDTH-1;
24 /* 配置(VSW+VBP+有效像素高度-1) */
25 Ltdc_Handler.Init.AccumulatedActiveH = VSW+VBP+LCD_PIXEL_HEIGHT-1;
26 /* 配置總寬度(HSW+HBP+有效像素寬度+HFP-1) */
27 Ltdc_Handler.Init.TotalWidth =HSW+ HBP+LCD_PIXEL_WIDTH + HFP-1;
28 /* 配置總高度(VSW+VBP+有效像素高度+VFP-1) */
29 Ltdc_Handler.Init.TotalHeigh =VSW+ VBP+LCD_PIXEL_HEIGHT + VFP-1;
30 /* 液晶屏時鍾配置 */
31 /* PLLSAI_VCO Input = HSE_VALUE/PLL_M = 1 Mhz */
32 /* PLLSAI_VCO Output = PLLSAI_VCO Input * PLLSAIN = 192 Mhz */
33 /* PLLLCDCLK = PLLSAI_VCO Output/PLLSAIR = 192/5 = 38.4 Mhz */
34 /* LTDC clock frequency = PLLLCDCLK / LTDC_PLLSAI_DIVR_4 = 38.4/4 = 9.6Mhz */
35 periph_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
36 periph_clk_init_struct.PLLSAI.PLLSAIN = 192;
37 periph_clk_init_struct.PLLSAI.PLLSAIR = 5;
38 periph_clk_init_struct.PLLSAIDivR = RCC_PLLSAIDIVR_4;
39 HAL_RCCEx_PeriphCLKConfig(&periph_clk_init_struct);
40 /* 初始化LCD的像素寬度和高度 */
41 Ltdc_Handler.LayerCfg->ImageWidth = LCD_PIXEL_WIDTH;
42 Ltdc_Handler.LayerCfg->ImageHeight = LCD_PIXEL_HEIGHT;
43 /* 設置LCD背景層的顏色,默認黑色 */
44 Ltdc_Handler.Init.Backcolor.Red = 0;
45 Ltdc_Handler.Init.Backcolor.Green = 0;
46 Ltdc_Handler.Init.Backcolor.Blue = 0;
47 /* 極性配置 */
48 /* 初始化行同步極性,低電平有效 */
49 Ltdc_Handler.Init.HSPolarity = LTDC_HSPOLARITY_AL;
50 /* 初始化場同步極性,低電平有效 */
51 Ltdc_Handler.Init.VSPolarity = LTDC_VSPOLARITY_AL;
52 /* 初始化數據有效極性,低電平有效 */
53 Ltdc_Handler.Init.DEPolarity = LTDC_DEPOLARITY_AL;
54 /* 初始化行像素時鍾極性,同輸入時鍾 */
55 Ltdc_Handler.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
56 HAL_LTDC_Init(&Ltdc_Handler);
57 /* 初始化字體 */
58 LCD_SetFont(&LCD_DEFAULT_FONT);
59 }
該函數的執行流程如下:
(1) 初始化LTDC、DMA2D時鍾
使用庫函數__HAL_RCC_LTDC_CLK_ENABLE及__HAL_RCC_DMA2D_CLK_ENABLE使能LTDC和DMA2D外設的時鍾。
(2) 初始化LTDC連接LCD的引腳。
(3) 初始化SDRAM
接下來調用前面章節講解的SDRAM_Init函數初始化FMC外設控制SDRAM,以便使用SDRAM的存儲空間作為顯存。
(4) 設置像素同步時鍾
在“LTDC結構框圖的時鍾信號”小節講解到,LTDC與液晶屏通訊的像素同步時鍾CLK是由PLLSAI分頻器控制輸出的,它的時鍾源為外部高速晶振HSE經過分頻因子M分頻后的時鍾,按照默認設置,一般分頻因子M會把HSE分頻得到1MHz的時鍾,如HSE晶振頻率為25MHz時,把M設置為25,HSE晶振頻率為8MHz時,把M設置為8,然后調用SystemInit函數初始化系統時鍾。經過M分頻得到的1MHz時鍾輸入到PLLSAI分頻器后,使用倍頻因子“N”倍頻,然后再經過“R”因子分頻,得到PLLCDCLK時鍾,再由“DIV”因子分頻得到LTDC通訊的同步時鍾LCD_CLK。
即:fLCD_CLK=fHSE/M x N/R/DIV
由於M把HSE時鍾分頻為1MHz的時鍾,所以上式等價於:
fLCD_CLK=1xN/R/DIV
利用庫函數RCC_PLLSAIConfig及RCC_LTDCCLKDivConfig函數可以配置PLLSAI分頻器的這些參數,其中庫函數RCC_PLLSAIConfig的三個輸入參數分別是倍頻因子N、分頻因子Q和分頻因子R,其中“Q”因子是作用於SAI接口的分頻時鍾,與LTDC無關,RCC_LTDCCLKDivConfig函數的輸入參數為分頻因子“DIV”。在配置完這些分頻參數后,需要調用庫函數RCC_PLLSAICmd使能PLLSAI的時鍾並且檢測標志位等待時鍾初始化完成。
在上面的代碼中調用函數設置N=192,R=5,DIV=4,計算得LCD_CLK的時鍾頻率為9.6MHz,這個時鍾頻率是我們根據實測效果選定的,若使用的是16位數據格式,可把時鍾頻率設置為24MHz,若只使用單層液晶屏數據源,則可配置為34MHz。然而根據液晶屏的數據手冊查詢可知它支持最大的同步時鍾為50MHz,典型速率為33.3Mhz,見圖 2728,由此說明傳輸速率主要受限於STM32一方。LTDC外設需要從SDRAM顯存讀取數據,這會消耗一定的時間,所以使用32位像素格式的數據要比使用16位像素格式的慢,如若只使用單層數據源,還可以進一步減少一半的數據量,所以更快。
(5) 配置信號極性
接下來根據液晶屏的時序要求,配置LTDC與液晶屏通訊時的信號極性,見圖 27-27。在程序中配置的HSYNC、VSYNC、DE有效信號極性均為低電平,同步時鍾信號極性配置為上升沿。其中DE信號的極性跟液晶屏時序圖的要求不一樣,文檔中DE的有效電平為高電平,而實際測試中把設置為DE低電平有效時屏幕才能正常工作,我們以實際測試為准。
圖 27-27 液晶屏時序中的有效電平
(6) 配置時間參數
液晶屏通訊中還有時間參數的要求,接下來的程序我們根據液晶屏手冊給出的時間參數,配置HSW、VSW、HBP、HFP、VBP、VFP、有效像素寬度及有效行數。這些參數都根據宏定義來修改。
圖 2728 液晶屏數據手冊標注的時間參數
(7) 寫入參數到寄存器並使能外設
經過上面步驟,賦值完了初始化結構體,接下來調用庫函數HAL_LTDC_Init把各種參數寫入到LTDC的控制寄存器中。
(8) 給液晶屏設定一個默認字體
配置LTDC的層級初始化
在上面配置完成STM32的LTDC外設基本工作模式后,還需要針對液晶屏的各個數據源層進行初始化,才能正常工作,代碼清單 46-8。
代碼清單 27-7 LTDC的層級初始化
1 /**
2 * @brief 初始化LCD層
3 * @param LayerIndex: 前景層(層1)或者背景層(層0)
4 * @param FB_Address: 每一層顯存的首地址
5 * @param PixelFormat: 層的像素格式
6 * @retval 無
7 */
8 void LCD_LayerInit(uint16_t LayerIndex, uint32_t FB_Address,uint32_t PixelFormat)
9 {
10 LTDC_LayerCfgTypeDef layer_cfg;
11
12 /* 層初始化 */
13 layer_cfg.WindowX0 = 0; //窗口起始位置X坐標
14 layer_cfg.WindowX1 = LCD_GetXSize(); //窗口結束位置X坐標
15 layer_cfg.WindowY0 = 0; //窗口起始位置Y坐標
16 layer_cfg.WindowY1 = LCD_GetYSize(); //窗口結束位置Y坐標
17 layer_cfg.PixelFormat = PixelFormat; //像素格式
18 layer_cfg.FBStartAdress = FB_Address; //層顯存首地址
19 layer_cfg.Alpha = 255; //用於混合的透明度常量,范圍(0—255)0為完全透明
20 layer_cfg.Alpha0 = 0; //默認透明度常量,范圍(0—255)0為完全透明
21 layer_cfg.Backcolor.Blue = 0; //層背景顏色藍色分量
22 layer_cfg.Backcolor.Green = 0; //層背景顏色綠色分量
23 layer_cfg.Backcolor.Red = 0; //層背景顏色紅色分量
24 layer_cfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;//層混合系數1
25 layer_cfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;//層混合系數2
26 layer_cfg.ImageWidth = LCD_GetXSize();//設置圖像寬度
27 layer_cfg.ImageHeight = LCD_GetYSize();//設置圖像高度
28
29 HAL_LTDC_ConfigLayer(&Ltdc_Handler, &layer_cfg, LayerIndex); //設置選中的層參數
30
31 DrawProp[LayerIndex].BackColor = LCD_COLOR_WHITE;//設置層的字體顏色
32 DrawProp[LayerIndex].pFont = &LCD_DEFAULT_FONT;//設置層的字體類型
33 DrawProp[LayerIndex].TextColor = LCD_COLOR_BLACK; //設置層的字體背景顏色
34
35 __HAL_LTDC_RELOAD_CONFIG(&Ltdc_Handler);//重載LTDC的配置參數
36 }
LTDC的層級初始化函數執行流程如下:
(1) 配置窗口邊界
每層窗口都需要配置有效顯示窗口,使用WindowX0 / WindowX1/ WindowY0/ WindowY1成員來確定這個窗口的左右上下邊界,各個成員應寫入的值與前面LTDC初始化結構體中某些參數類似。通過函數LCD_GetXSize和LCD_GetYSize來獲取屏幕的長寬。
(2) 配置像素的格式
PixelFormat成員用於配置本層像素的格式,在這個實驗中我們把這層設置為ARGB8888格式,兩層數據源的像素可以配置成不同的格式,層與層之間是獨立的。
(3) 配置默認背景顏色
在定義的層窗口外或在層禁止時,該層會使用默認顏色作為數據源,默認顏色使用Backcolor.Blue / Backcolor.Green/ Backcolor.Red/Alpha0成員來配置,本實驗中我們把默認顏色配置成透明了。
(4) 配置第1層的恆定Alpha與混合因子
前面提到兩層數據源混合時可根據混合因子設置只使用恆定Alpha運算,還是把像素的Alpha也加入到運算中。對於第1層數據源,我們不希望LTDC的默認背景層參與到混合運算中,而希望第1層直接作為背景(因為第1層的數據每個像素點都是可控的,而背景層所有像素點都是同一個顏色)。因此我們把恆定Alpha值(LTDC_ConstantAlpha)設置為255,即完全不透明,混合因子BF1/BF2參數(LTDC_BlendingFactor_1/2)都配置成LTDC_BlendingFactor1/2_CA,即只使用恆定Alpha值運算,這樣配置的結果是第1層的數據顏色直接等於它像素本身的RGB值,不受像素中的Alpha值及背景影響。
(5) 配置顯存首地址
每一層都有獨立的顯存空間,向FBStartAdress參數賦值可設置該層的顯存首地址,我們把第1層的顯存首地址直接設置成宏LCD_FB_START_ADDRESS,該宏表示的地址為0xD0000000,即SDRAM的首地址,從該地址開始,如果是ARGB8888格式則大小應該為 800x480x4:行有效像素寬度x行數x每個字節的數據量),向這些空間寫入的數據會被解釋成像素數據,LTDC會把這些數據傳輸到液晶屏上,所以我們要控制液晶屏的輸出,只要修改這些空間的數據即可,包括變量操作、指針操作、DMA操作以及DMA2D操作等一切可修改SDRAM內容的操作都支持。
實際設置中不需要刻意設置成SDRAM首地址,只要能保證該地址后面的數據空間足夠存儲該層的一幀數據即可。
(6) 向寄存器寫入配置參數
賦值完后,調用庫函數HAL_LTDC_ConfigLayer可把這些參數寫入到LTDC的層控制寄存器,根據函數的第一個參數LayerIndex來決定配置的是第1層還是第2層。
(7) 配置第2層控制參數
要想有混合效果,還需要使用第2層數據源,它與第1層的配置大致是一樣的,主要區別是顯存首地址和混合因子。在程序中我們把第2層的顯存首地址設置成緊挨着第1層顯存空間的結尾。而混合因子都配置成PAxCA以便它的透明像素能參與運算,實現透明效果。
(8) 重載LTDC配置並使能數據層
把兩層的參數都寫入到寄存器后,使用庫函數__HAL_LTDC_RELOAD_CONFIG讓LTDC外設立即重新加載這些配置。至此,LTDC配置就完成,可以向顯存空間寫入數據進行顯示了。
輔助顯示的全局變量及函數
為方便顯示操作,我們定義了一些全局變量及函數來輔助修改顯存內容,這些函數都是我們自己定義的,不是STM32 HAL庫提供的內容。見代碼清單 278。
代碼清單 278 輔助顯示的全局變量及函數
1 /* LCD 物理像素大小 (寬度和高度) */
2 #define LCD_PIXEL_WIDTH ((uint16_t)800)
3 #define LCD_PIXEL_HEIGHT ((uint16_t)480)
4
5 /* LCD 層像素格式*/
6 #define ARGB8888 LTDC_PIXEL_FORMAT_ARGB8888 /*!< ARGB8888 LTDC像素格式 */
7 #define RGB888 LTDC_PIXEL_FORMAT_RGB888 /*!< RGB888 LTDC像素格式 */
8 #define RGB565 LTDC_PIXEL_FORMAT_RGB565 /*!< RGB565 LTDC像素格式 */
9 #define ARGB1555 LTDC_PIXEL_FORMAT_ARGB1555 /*!< ARGB1555 LTDC像素格式 */
10 #define ARGB4444 LTDC_PIXEL_FORMAT_ARGB4444 /*!< ARGB4444 LTDC像素格式 */
11
12 typedef struct {
13 uint32_t TextColor;
14 uint32_t BackColor;
15 sFONT *pFont;
16 } LCD_DrawPropTypeDef;
17
18 typedef struct {
19 int16_t X;
20 int16_t Y;
21 } Point, * pPoint;
22
23 /**
24 * @brief 字體對齊模式
25 */
26 typedef enum {
27 CENTER_MODE = 0x01, /* 居中對齊 */
28 RIGHT_MODE = 0x02, /* 右對齊 */
29 LEFT_MODE = 0x03 /* 左對齊 */
30 } Text_AlignModeTypdef;
31
32 #define MAX_LAYER_NUMBER ((uint32_t)2)
33
34 #define LTDC_ACTIVE_LAYER ((uint32_t)1) /* Layer 1 */
35 /**
36 * @brief LCD status structure definition
37 */
38 #define LCD_OK ((uint8_t)0x00)
39 #define LCD_ERROR ((uint8_t)0x01)
40 #define LCD_TIMEOUT ((uint8_t)0x02)
41
42 /**
43 * @brief LCD FB_StartAddress
44 */
45 #define LCD_FB_START_ADDRESS ((uint32_t)0xD0000000)
46 /**
47 * @brief 設置LCD當前層文字顏色
48 * @param Color: 文字顏色
49 * @retval 無
50 */
51 void LCD_SetTextColor(uint32_t Color)
52 {
53 DrawProp[ActiveLayer].TextColor = Color;
54 }
55 /**
56 * @brief 獲取LCD當前層文字顏色
57 * @retval 文字顏色
58 */
59 uint32_t LCD_GetTextColor(void)
60 {
61 return DrawProp[ActiveLayer].TextColor;
62 }
63 /**
64 * @brief 設置LCD當前層的文字背景顏色
65 * @param Color: 文字背景顏色
66 * @retval 無
67 */
68 void LCD_SetBackColor(uint32_t Color)
69 {
70 DrawProp[ActiveLayer].BackColor = Color;
71 }
72 /**
73 * @brief 獲取LCD當前層的文字背景顏色
74 * @retval 文字背景顏色
75 */
76 uint32_t LCD_GetBackColor(void)
77 {
78 return DrawProp[ActiveLayer].BackColor;
79 }
80 /**
81 * @brief 設置LCD文字的顏色和背景的顏色
82 * @param TextColor: 指定文字顏色
83 * @param BackColor: 指定背景顏色
84 * @retval 無
85 */
86 void LCD_SetColors(uint32_t TextColor, uint32_t BackColor)
87 {
88 LCD_SetTextColor (TextColor);
89 LCD_SetBackColor (BackColor);
90 }
91 /**
92 * @brief 設置LCD當前層顯示的字體
93 * @param fonts: 字體類型
94 * @retval None
95 */
96 void LCD_SetFont(sFONT *fonts)
97 {
98 DrawProp[ActiveLayer].pFont = fonts;
99 }
100 /**
101 * @brief 獲取LCD當前層顯示的字體
102 * @retval 字體類型
103 */
104 sFONT *LCD_GetFont(void)
105 {
106 return DrawProp[ActiveLayer].pFont;
107 }
108 /**
109 * @brief 選擇LCD層
110 * @retval LayerIndex: 前景層(層1)或者背景層(層0)
111 */
112 void LCD_SelectLayer (uint32_t LayerIndex)
113 {
114 ActiveLayer = LayerIndex;
115 }
(1) 切換字體大小格式
液晶顯示中,文字內容占據了很大部分,顯示文字需要有“字模數據”配合。關於字模的知識我們在下一章節講解,在這里只簡單介紹一下基本概念。字模是一個個像素點陣方塊 ,如上述代碼中的sFont結構體,包含了指向字模數據的指針以及每個字模的像素寬度、高度,即字體的大小。本實驗的工程中提供了像素格式為17x24、14x20、7x12、5x8的英文字模。為了方便選擇字模,定義了全局指針變量DrawProp[ActiveLayer].pFont用來存儲當前選擇的字模格式,實際顯示時根據該指針指向的字模格式來顯示文字,可以使用下面的LCD_SetFont函數切換指針指向的字模格式,該函數的可輸入參數為: Font24/ Font20/ Font12/ Font8。
(2) 切換字體顏色和字體背景顏色
很多時候我們還希望文字能以不同的色彩顯示,為此定義了全局變量DrawProp[ActiveLayer].TextColor和DrawProp[ActiveLayer].BackColor用於設定要顯示字體的顏色和字體背景顏色,如:
字體為紅色和字體背景為藍色
使用函數LCD_SetColors、LCD_SetTextColor以及LCD_SetBackColor可以方便修改這兩個全局變量的值。若液晶的像素格式支持透明,可把字體背景設置為透明值,實現彈幕顯示的效果(文字浮在圖片之上,透過文字可看到背景圖片)。
(3) 切換當前操作的液晶層
由於顯示的數據源有兩層,在寫入數據時需要區分到底要寫入哪個顯存空間,為此,我們定義了全局變量ActiveLayer 用於存儲要操作的液晶層及該層的顯存首地址。使用函數LCD_SetLayer可切換要操作的層及顯存地址。
繪制像素點
有了以上知識准備,就可以開始向液晶屏繪制像素點了,見代碼清單 27-9。
代碼清單 27-9 繪制像素點
1 /**
2 * @brief 繪制一個點
3 * @param Xpos: X軸坐標
4 * @param Ypos: Y軸坐標
5 * @param RGB_Code: 像素顏色值
6 * @retval 無
7 */
8 void LCD_DrawPixel(uint16_t Xpos, uint16_t Ypos, uint32_t RGB_Code)
9 {
10
11 if (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_ARGB8888) {
12 *(__IO uint32_t*) (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress + \
13 (4*(Ypos*LCD_GetXSize() + Xpos))) = RGB_Code;
14 } else if (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_RGB888) {
15 *(__IO uint8_t*) (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress + \
16 (3*(Ypos*LCD_GetXSize() + Xpos))+2) = 0xFF&(RGB_Code>>16);
17 *(__IO uint8_t*) (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress + \
18 (3*(Ypos*LCD_GetXSize() + Xpos))+1) = 0xFF&(RGB_Code>>8);
19 *(__IO uint8_t*) (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress + \
20 (3*(Ypos*LCD_GetXSize() + Xpos))) = 0xFF&RGB_Code;
21 } else if ((Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_RGB565) || \
22 (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_ARGB4444) || \
23 (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_AL88)) {
24 *(__IO uint16_t*) (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress + \
25 (2*(Ypos*LCD_GetXSize() + Xpos))) = (uint16_t)RGB_Code;
26 } else {
27 *(__IO uint8_t*) (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress + \
28 ((Ypos*LCD_GetXSize() + Xpos))) = (uint16_t)RGB_Code;
29 }
30
31 }
這個繪制像素點的函數可輸入x,y兩個參數,用於指示要繪制像素點的坐標。得到輸入參數后它首先進行參數檢查,若坐標超出液晶顯示范圍則直接退出函數,不進行操作。坐標檢查通過后根據坐標計算該像素所在的顯存地址,液晶屏中的每個像素點都有對應的顯存空間,像素點的坐標與顯存地址有固定的映射關系,見表 27-8。
表 27-8 顯存存儲像素數據的方式 (RGB888格式)
… |
|
|
|
|
|
|
|
|
2 |
|
|
|
|
|
|
|
|
1 |
|
|
|
|
|
|
|
|
0 |
… |
Bx+2[7:0] |
Rx+1[7:0] |
Gx+1[7:0] |
Bx+1[7:0] |
Rx[7:0] |
Gx[7:0] |
Bx[7:0] |
行/列 |
… |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
當像素格式為RGB888時,每個像素占據3個字節,各個像素點按順序排列。而且RGB通道的數據各占一個字節空間,藍色數據存儲在低位的地址,紅色數據存儲右高位地址。據此可以得出像素點顯存地址與像素點坐標存在以下映射關系:
像素點的顯存基地址= 當前層顯存首地址 + 每個像素點的字節數*(每行像素個數*坐標y+坐標x)
而像素點內的RGB顏色分量地址如下:
藍色分量地址 = 像素點顯存基地址
綠色分量地址 = 像素點顯存基地址+1
紅色分量地址 = 像素點顯存基地址+2
利用這些映射關系,繪制點函數代入存儲了當前要操作的層顯存首地址的全局變量Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress計算出像素點的顯存基地址及偏移地址,再利用RGB顏色分量分別存儲到對應的位置。由於LTDC工作后會一直刷新顯存的數據到液晶屏,所以在下一次LTDC刷新的時候,被修改的顯存數據就會顯示到液晶屏上了。
掌握了繪制任意像素點顏色的操作后,就能隨心所欲地控制液晶屏了,其它復雜的顯示操作如繪制直線、矩形、圓形、文字、圖片以及視頻都是一樣的,本質上都是操縱一個個像素點而已。如直線由點構成,矩形由直線構成,它們的區別只是點與點之間幾何關系的差異,對液晶屏來說並沒有什么特別。
使用DMA2D繪制直線和矩形
利用上面的像素點繪制方式可以實現所有液晶操作,但直接使用指針訪問內存空間效率並不高,在某些場合下可使用DMA2D搬運內存數據,加速傳輸。繪制純色直線和矩形的時候十分適合,代碼清單 27-10。
代碼清單 27-10 使用DMA2D繪制直線
1 /**
2 * @brief 繪制水平線
3 * @param Xpos: X軸起始坐標
4 * @param Ypos: Y軸起始坐標
5 * @param Length: 線的長度
6 * @retval 無
7 */
8 void LCD_DrawHLine(uint16_t Xpos, uint16_t Ypos, uint16_t Length)
9 {
10 uint32_t Xaddress = 0;
11
12 if (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_ARGB8888) {
13 Xaddress = (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress) + 4*(LCD_GetXSize()*Ypos + Xpos);
14 } else if (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_RGB888) {
15 Xaddress = (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress) + 3*(LCD_GetXSize()*Ypos + Xpos);
16 } else if ((Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_RGB565) || \
17 (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_ARGB4444) || \
18 (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_AL88)) {
19 Xaddress = (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress) + 2*(LCD_GetXSize()*Ypos + Xpos);
20 } else {
21 Xaddress = (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress) + (LCD_GetXSize()*Ypos + Xpos);
22 }
23 /* 填充數據 */
24 LL_FillBuffer(ActiveLayer, (uint32_t *)Xaddress, Length, 1, 0, DrawProp[ActiveLayer].TextColor);
25 }
26
27 /**
28 * @brief 繪制垂直線
29 * @param Xpos: X軸起始坐標
30 * @param Ypos: Y軸起始坐標
31 * @param Length: 線的長度
32 * @retval 無
33 */
34 void LCD_DrawVLine(uint16_t Xpos, uint16_t Ypos, uint16_t Length)
35 {
36 uint32_t Xaddress = 0;
37
38 if (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_ARGB8888) {
39 Xaddress = (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress) + 4*(LCD_GetXSize()*Ypos + Xpos);
40 } else if (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_RGB888) {
41 Xaddress = (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress) + 3*(LCD_GetXSize()*Ypos + Xpos);
42 } else if ((Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_RGB565) || \
43 (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_ARGB4444) || \
44 (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_AL88)) {
45 Xaddress = (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress) + 2*(LCD_GetXSize()*Ypos + Xpos);
46 } else {
47 Xaddress = (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress) + (LCD_GetXSize()*Ypos + Xpos);
48 }
49
50 /* 填充數據 */
51 LL_FillBuffer(ActiveLayer, (uint32_t *)Xaddress, 1, Length, (LCD_GetXSize() - 1), DrawProp[ActiveLayer].TextColor);
52 }
53 /**
54 * @brief 填充一個緩沖區
55 * @param LayerIndex: 當前層
56 * @param pDst: 指向目標緩沖區指針
57 * @param xSize: 緩沖區寬度
58 * @param ySize: 緩沖區高度
59 * @param OffLine: 偏移量
60 * @param ColorIndex: 當前顏色
61 * @retval None
62 */
63 static void LL_FillBuffer(uint32_t LayerIndex, void *pDst, uint32_t xSize,
64 uint32_t ySize, uint32_t OffLine, uint32_t ColorIndex)
65 {
66
67 Dma2d_Handler.Init.Mode = DMA2D_R2M;
68 if (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_RGB565) {
69 Dma2d_Handler.Init.ColorMode = DMA2D_RGB565;
70 } else if (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_ARGB8888) {
71 Dma2d_Handler.Init.ColorMode = DMA2D_ARGB8888;
72 } else if (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_RGB888) {
73 Dma2d_Handler.Init.ColorMode = DMA2D_RGB888;
74 } else if (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_ARGB1555) {
75 Dma2d_Handler.Init.ColorMode = DMA2D_ARGB1555;
76 } else if (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_ARGB4444) {
77 Dma2d_Handler.Init.ColorMode = DMA2D_ARGB4444;
78 }
79 Dma2d_Handler.Init.OutputOffset = OffLine;
80
81 Dma2d_Handler.Instance = DMA2D;
82
83 /* DMA2D 初始化 */
84 if (HAL_DMA2D_Init(&Dma2d_Handler) == HAL_OK) {
85 if (HAL_DMA2D_ConfigLayer(&Dma2d_Handler, LayerIndex) == HAL_OK) {
86 if (HAL_DMA2D_Start(&Dma2d_Handler, ColorIndex, (uint32_t)pDst, xSize, ySize) == HAL_OK) {
87 /* DMA輪詢傳輸 */
88 HAL_DMA2D_PollForTransfer(&Dma2d_Handler, 100);
89 }
90 }
91 }
92 }
這個繪制直線的函數輸入參數為直線起始像素點的坐標,直線長度,分別有描繪水平直線何垂直直線,函數主要利用了前面介紹的DMA2D初始化結構體,執行流程介紹如下:
(1) 計算起始像素點的顯存位置
與繪制單個像素點一樣,使用DMA2D繪制也需要知道像素點對應的顯存地址。利用直線起始像素點的坐標計算出直線在顯存的基本位置Xaddress。
(2) 配置DMA2D傳輸模式像素格式、顏色分量及偏移地址。
接下來開始向DMA2D初始化結構體賦值,在賦值前先調用了庫函數配置時把DMA2D的模式設置成了DMA2D_R2M,以寄存器中的顏色作為數據源,即DMA2D_OutputGreen/Blue/Red/Alpha中的值,我們向這些參數寫入上面提取得到的顏色分量。DMA2D輸出地址設置為上面計算得的Xaddress。
(3) 配置DMA2D的輸出偏移、行數及每行的像素點個數
(4) 寫入參數到寄存器並傳輸
配置完DMA2D的參數后,就可以調用庫函數HAL_DMA2D_Init把參數寫入到寄存器中,然后調用HAL_DMA2D_Start函數配置傳輸參數,只需要輸入顏色,目標地址,長和寬,然后調用HAL_DMA2D_PollForTransfer函數啟動傳輸。
使用DMA2D繪制矩形
與繪制直線很類似,利用DMA2D繪制純色矩形的方法見代碼清單 2711。
代碼清單 2711 使用DMA2D繪制矩形
1 /**
2 * @brief 填充一個實心矩形
3 * @param Xpos: X坐標值
4 * @param Ypos: Y坐標值
5 * @param Width: 矩形寬度
6 * @param Height: 矩形高度
7 * @retval 無
8 */
9 void LCD_FillRect(uint16_t Xpos, uint16_t Ypos, uint16_t Width, uint16_t Height)
10 {
11 uint32_t x_address = 0;
12
13 /* 設置文字顏色 */
14 LCD_SetTextColor(DrawProp[ActiveLayer].TextColor);
15
16 /* 設置矩形開始地址 */
17if (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_ARGB8888) {
18 x_address = (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress) + 4*(LCD_GetXSize()*Ypos + Xpos);
19 } else if (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_RGB888) {
20 x_address = (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress) + 3*(LCD_GetXSize()*Ypos + Xpos);
21 } else if ((Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_RGB565) || \
22 (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_ARGB4444) || \
23 (Ltdc_Handler.LayerCfg[ActiveLayer].PixelFormat == LTDC_PIXEL_FORMAT_AL88)) {
24 x_address = (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress) + 2*(LCD_GetXSize()*Ypos + Xpos);
25 } else {
26 x_address = (Ltdc_Handler.LayerCfg[ActiveLayer].FBStartAdress) + 2*(LCD_GetXSize()*Ypos + Xpos);
27 }
28 /* 填充矩形 */
29 LL_FillBuffer(ActiveLayer, (uint32_t *)x_address, Width, Height, (LCD_GetXSize() - Width), DrawProp[ActiveLayer].
30 TextColor);
31 }
對於DMA2D來說,繪制矩形實際上就是繪制一條很粗的直線,與繪制直線的主要區別是行偏移、行數以及每行的像素個數。
3. main函數
最后我們來編寫main函數,使用液晶屏顯示圖像,見錯誤!未找到引用源。。
代碼清單 27-12 main函數
1 int main(void)
2 {
3 /* 系統時鍾初始化成216 MHz */
4 SystemClock_Config();
5 /* LED 端口初始化 */
6 LED_GPIO_Config();
7 /* LCD 端口初始化 */
8 LCD_Init();
9 /* LCD 第一層初始化 */
10 LCD_LayerInit(0, LCD_FB_START_ADDRESS,ARGB8888);
11 /* LCD 第二層初始化 */
12 LCD_LayerInit(1, LCD_FB_START_ADDRESS+(LCD_GetXSize()*LCD_GetYSize()*4),ARGB8888);
13 /* 使能LCD,包括開背光 */
14 LCD_DisplayOn();
15
16 /* 選擇LCD第一層 */
17 LCD_SelectLayer(0);
18
19 /* 第一層清屏,顯示全黑 */
20 LCD_Clear(LCD_COLOR_BLACK);
21
22 /* 選擇LCD第二層 */
23 LCD_SelectLayer(1);
24
25 /* 第二層清屏,顯示全黑 */
26 LCD_Clear(LCD_COLOR_TRANSPARENT);
27
28 /* 配置第一和第二層的透明度,最小值為0,最大值為255*/
29 LCD_SetTransparency(0, 255);
30 LCD_SetTransparency(1, 0);
31
32 while (1) {
33 LCD_Test();
34 }
35 }
上電后,調用了LCD_Init、LCD_LayerInit函數初始化LTDC外設,然后使用LCD_SetLayer函數切換到第一層,使用LCD_Clear函數把背景層都刷成黑色,LCD_Clear實質是一個使用DMA2D顯示矩形的函數,只是它默認矩形的寬和高直接設置成液晶屏的分辨率,把整個屏幕都刷成同一種顏色。刷完背景層的顏色后再調用LCD_SetLayer切換到第二層,然后在前景層繪制圖形。中間還有一個LCD_SetTransparency函數,它用於設置當前層的透明度,設置后整一層的像素包含該透明值,由於整層透明並沒有什么用(一般應用是某些像素點透明看到背景,而其它像素點不透明),我們把第一層設置為完全不透明。
初始化完成后,我們調用LCD_Test函數顯示各種圖形進行測試(如直線、矩形、圓形),具體內容請直接在工程中閱讀源碼,這里不展開講解。LCD_Test中還調用了文字顯示函數,其原理在下一章節詳細說明。
下載驗證
用USB線連接開發板,編譯程序下載到實驗板,並上電復位,液晶屏會顯示各種內容。