第27章 LTDC/DMA2D—液晶顯示
全套200集視頻教程和1000頁PDF教程請到秉火論壇下載:www.firebbs.cn
野火視頻教程優酷觀看網址:http://i.youku.com/firege
本章參考資料:《STM32F4xx 中文參考手冊2》、《STM32F4xx規格書》、庫幫助文檔《stm32f4xx_dsp_stdperiph_lib_um.chm》。
關於開發板配套的液晶屏參數可查閱《5.0寸液晶屏數據手冊》配套資料獲知。
27.1 顯示器簡介
顯示器屬於計算機的I/O設備,即輸入輸出設備。它是一種將特定電子信息輸出到屏幕上再反射到人眼的顯示工具。常見的有CRT顯示器、液晶顯示器、LED點陣顯示器及OLED顯示器。
27.1.1 液晶顯示器
液晶顯示器,簡稱LCD(Liquid Crystal Display),相對於上一代CRT顯示器(陰極射線管顯示器),LCD顯示器具有功耗低、體積小、承載的信息量大及不傷眼的優點,因而它成為了現在的主流電子顯示設備,其中包括電視、電腦顯示器、手機屏幕及各種嵌入式設備的顯示器。圖 271是液晶電視與CRT電視的外觀對比,很明顯液晶電視更薄,"時尚"是液晶電視給人的第一印象,而CRT 電視則感覺很"笨重"。
圖 271 液晶電視及CRT電視
液晶是一種介於固體和液體之間的特殊物質,它是一種有機化合物,常態下呈液態,但是它的分子排列卻和固體晶體一樣非常規則,因此取名液晶。如果給液晶施加電場,會改變它的分子排列,從而改變光線的傳播方向,配合偏振光片,它就具有控制光線透過率的作用,再配合彩色濾光片,改變加給液晶電壓大小,就能改變某一顏色透光量的多少,圖 272中的就是綠色顯示結構。利用這種原理,做出可控紅、綠、藍光輸出強度的顯示結構,把三種顯示結構組成一個顯示單位,通過控制紅綠藍的強度,可以使該單位混合輸出不同的色彩,這樣的一個顯示單位被稱為像素。
圖 272 液晶屏的綠色顯示結構
注意液晶本身是不發光的,所以需要有一個背光燈提供光源,光線經過一系列處理過程才到輸出,所以輸出的光線強度是要比光源的強度低很多的,比較浪費能源(當然,比CRT顯示器還是節能多了)。而且這些處理過程會導致顯示方向比較窄,也就是它的視角較小,從側面看屏幕會看不清它的顯示內容。另外,輸出的色彩變換時,液晶分子轉動也需要消耗一定的時間,導致屏幕的響應速度低。
27.1.2 LED和OLED顯示器
LED點陣顯示器不存在以上液晶顯示器的問題,LED點陣彩色顯示器的單個像素點內包含紅綠藍三色LED燈,顯示原理類似我們實驗板上的LED彩燈,通過控制紅綠藍顏色的強度進行混色,實現全彩顏色輸出,多個像素點構成一個屏幕。由於每個像素點都是LED燈自發光的,所以在戶外白天也顯示得非常清晰,但由於LED燈體積較大,導致屏幕的像素密度低,所以它一般只適合用於廣場上的巨型顯示器。相對來說,單色的LED點陣顯示器應用得更廣泛,如公交車上的信息展示牌、店招等,見圖 273。
圖 273 LED點陣彩屏有LED單色顯示屏
新一代的OLED顯示器與LED點陣彩色顯示器的原理類似,但由於它采用的像素單元是"有機發光二極管"(Organic Light Emitting Diode),所以像素密度比普通LED點陣顯示器高得多,見圖 275。
圖 274 OLED像素結構
OLED顯示器不需要背光源、對比度高、輕薄、視角廣及響應速度快等優點。待到生產工藝更加成熟時,必將取代現在液晶顯示器的地位,見圖 275。
圖 275 采用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 液晶控制原理
圖 276是兩種適合於STM32芯片使用的顯示屏,我們以它為例講解控制液晶屏的原理。
圖 276適合STM32控制的顯示屏實物圖
這個完整的顯示屏由液晶顯示面板、電容觸摸面板以及PCB底板構成。圖中的觸摸面板帶有觸摸控制芯片,該芯片處理觸摸信號並通過引出的信號線與外部器件通訊面板中間是透明的,它貼在液晶面板上面,一起構成屏幕的主體,觸摸面板與液晶面板引出的排線連接到PCB底板上,根據實際需要,PCB底板上可能會帶有"液晶控制器芯片"。因為控制液晶面板需要比較多的資源,所以大部分低級微控制器都不能直接控制液晶面板,需要額外配套一個專用液晶控制器來處理顯示過程,外部微控制器只要把它希望顯示的數據直接交給液晶控制器即可。而不帶液晶控制器的PCB底板,只有小部分的電源管理電路,液晶面板的信號線與外部微控制器相連,直接控制。STM32F429系列的芯片不需要額外的液晶控制器,也就是說它把專用液晶控制器的功能集成到STM32F429芯片內部了,節約了額外的控制器成本。
27.2.1 液晶面板的控制信號
本章我們主要講解控制液晶面板(不帶控制器),液晶面板的控制信號線見表 271。
表 271 液晶面板的信號線
信號名稱 |
說明 |
R[7:0] |
紅色數據 |
G[7:0] |
綠色數據 |
B[7:0] |
藍色數據 |
CLK |
像素同步時鍾信號 |
HSYNC |
水平同步信號 |
VSYNC |
垂直同步信號 |
DE |
數據使能信號 |
(1) RGB信號線
RGB信號線各有8根,分別用於表示液晶屏一個像素點的紅、綠、藍顏色分量。使用紅綠藍顏色分量來表示顏色是一種通用的做法,打開Windows系統自帶的畫板調色工具,可看到顏色的紅綠藍分量值,見圖 277。常見的顏色表示會在"RGB"后面附帶各個顏色分量值的數據位數,如RGB565表示紅綠藍的數據線數分別為5、6、5根,一共為16個數據位,可表示216種顏色;而這個液晶屏的種顏色分量的數據線都有8根,所以它支持RGB888格式,一共24位數據線,可表示的顏色為224種。
圖 277 顏色表示法
(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信號線表示的數據有效。
27.2.2 液晶數據傳輸時序
通過上述信號線向液晶屏傳輸像素數據時,各信號線的時序見圖 278。圖中表示的是向液晶屏傳輸一幀圖像數據的時序,中間省略了多行及多個像素點。
圖 278 液晶時序圖
液晶屏顯示的圖像可看作一個矩形,結合圖 279來理解。液晶屏有一個顯示指針,它指向將要顯示的像素。顯示指針的掃描方向方向從左到右、從上到下,一個像素點一個像素點地描繪圖形。這些像素點的數據通過RGB數據線傳輸至液晶屏,它們在同步時鍾CLK的驅動下一個一個地傳輸到液晶屏中,交給顯示指針,傳輸完成一行時,水平同步信號HSYNC電平跳變一次,而傳輸完一幀時VSYNC電平跳變一次。
圖 279 液晶數據傳輸圖解
但是,液晶顯示指針在行與行之間,幀與幀之間切換時需要延時,而且HSYNC及VSYNC信號本身也有寬度,這些時間參數說明見表 272。
表 272 液晶通訊中的時間參數
時間參數 |
參數說明 |
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液晶控制器簡介
STM32F429系列芯片內部自帶一個LTDC液晶控制器,使用SDRAM的部分空間作為顯存,可直接控制液晶面板,無需額外增加液晶控制器芯片。STM32的LTDC液晶控制器最高支持800x600分辨率的屏幕;可支持多種顏色格式,包括RGB888、RGB565、ARGB8888和ARGB1555等(其中的"A"是指透明像素);支持2層顯示數據混合,利用這個特性,可高效地做出背景和前景分離的顯示效果,如以視頻為背景,在前景顯示彈幕。
27.3.1 圖像數據混合
LTDC外設支持2層數據混合,混合前使用2層數據源,分別為前景層和背景層,見圖 2710。在輸出時,實際上液晶屏只能顯示一層圖像,所以LTDC在輸出數據到液晶屏前需要把2層圖像混合成一層,跟Photoshop軟件的分層合成圖片過程類似。混合時,直接用前景層中的不透明像素替換相同位置的背景像素;而前景層中透明像素的位置,則使用背景的像素數據,即顯示背景層的像素。
圖 2710 圖像的分層與混合
如果想使用圖像混合功能,前景層必須使用包含透明的像素格式,如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結構框圖剖析
圖 2711是LTDC控制器的結構框圖,它主要包含信號線、圖像處理單元、寄存器及時鍾信號。
圖 2711 LTDC控制器框圖
1. LTDC信號線
LTDC的控制信號線與液晶顯示面板的數據線一一對應,包含有HSYNC、VSYNC、DE、CLK及RGB數據線各8根。設計硬件時把液晶面板與STM32對應的這些引腳連接起來即可,查閱《STM32F4xx規格書》可知LTDC信號線對應的引腳,見表 273。
表 273 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用於生成與液晶面板通訊的同步時鍾,見圖 2712,它的來源是HSE(高速外部晶振),經過"/M"分頻因子分頻輸出到"PLLSAI"分頻器,信號由"PLLSAI"中的倍頻因子N倍頻得到"PLLSAIN"時鍾、然后由"/R"因子分頻得到"PLLCDCLK"時鍾,再經過"DIV"因子得到"LCD-TFT clock","LCD-TFT clock"即通訊中的同步時鍾LCD_CLK,它使用LCD_CLK引腳輸出。
圖 2712 LCD_CLK時鍾來源
27.4 DMA2D圖形加速器簡介
在實際使用LTDC控制器控制液晶屏時,使LTDC正常工作后,往配置好的顯存地址寫入要顯示的像素數據,LTDC就會把這些數據從顯存搬運到液晶面板進行顯示,而顯示數據的容量非常大,所以我們希望能用DMA來操作,針對這個需求,STM32專門定制了DMA2D外設,它可用於快速繪制矩形、直線、分層數據混合、數據復制以及進行圖像數據格式轉換,可以把它理解為圖形專用的DMA。
27.4.1 DMA2D結構框圖剖析
圖 2713是DMA2D的結構框圖,它與前面LTDC結構里的圖像處理單元很類似,主要為分層FIFO、PFC及彩色混合器。
圖 2713 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。見圖 2714,利用顏色查找表,實際的圖像只使用這256種顏色,而圖像的每個像素使用8位的數據來表示,該數據並不是直接的RGB顏色數據,而是指向顏色查找表的地址偏移,即表示這個像素點應該顯示顏色查找表中的哪一種顏色。在圖像大小不變的情況下,利用顏色查找表可以擴展顏色顯示的能力,其特點是用8位的數據表示了一個24或32位的顏色,但整個圖像顏色的種類局限於顏色表中的256種。DMA2D的顏色查找表可以由CPU自動加載或編程手動加載。
圖 2714 使用顏色查找表顯示圖像的過程
3. 混合器
FIFO中的數據源經過PFC像素格式轉換器后,前景層和背景層的圖像都輸入到混合器中運算,運算公式見圖 2715。
圖 2715 混合公式
從公式可以了解到混合器的運算主要是使用前景和背景的透明度作為因子,對像素RGB顏色值進行加權運算。經過混合器后,兩層數據合成為一層ARGB8888格式的圖像。
4. OUT PFC
OUT PFC是輸出像素格式轉換器,它把混合器轉換得到的圖像轉換成目標格式,如ARGB8888、RGB888、RGB565、ARGB1555或ARGB4444,具體的格式可根據需要在輸出PFC控制寄存器DMA2D_OPFCCR中選擇。
STM32F429芯片使用LTDC、DMA2D及RAM存儲器,構成了一個完整的液晶控制器。LTDC負責不斷刷新液晶屏,DMA2D用於圖像數據搬運、混合及格式轉換,RAM存儲器作為顯存。其中顯存可以使用STM32芯片內部的SRAM或外擴SDRAM/SRAM,只要容量足夠大即可(至少要能存儲一幀圖像數據)。
27.5 LTDC初始化結構體
控制LTDC涉及到非常多的寄存器,利用LTDC初始化結構體可以減輕開發和維護的工作量,LTDC初始化結構體見代碼清單 241。
代碼清單 271 LTDC初始化結構體LTDC_InitTypeDef
1 /**
2 * @brief LTDC Init structure definition
3 */
4 typedef struct
5 {
6 uint32_t LTDC_HSPolarity; /*配置行同步信號HSYNC的極性 */
7 uint32_t LTDC_VSPolarity; /*配置垂直同步信號VSYNC的極性 */
8 uint32_t LTDC_DEPolarity; /*配置數據使能信號DE的極性*/
9 uint32_t LTDC_PCPolarity; /*配置像素時鍾信號CLK的極性 */
10 uint32_t LTDC_HorizontalSync; /*配置行同步信號HSYNC的寬度(HSW-1) */
11 uint32_t LTDC_VerticalSync; /*配置垂直同步信號VSYNC的寬度(VSW-1) */
12 uint32_t LTDC_AccumulatedHBP; /*配置(HSW+HBP-1)的值*/
13 uint32_t LTDC_AccumulatedVBP; /*配置(VSW+VBP-1)的值*/
14 uint32_t LTDC_AccumulatedActiveW; /*配置(HSW+HBP+有效寬度-1)的值*/
15 uint32_t LTDC_AccumulatedActiveH; /*配置(VSW+VBP+有效高度-1)的值*/
16 uint32_t LTDC_TotalWidth; /*配置(HSW+HBP+有效寬度+HFP-1)的值*/
17 uint32_t LTDC_TotalHeigh; /*配置(VSW+VBP+有效高度+VFP-1)的值*/
18 uint32_t LTDC_BackgroundRedValue; /*配置背景的紅色值*/
19 uint32_t LTDC_BackgroundGreenValue; /*配置背景的綠色值*/
20 uint32_t LTDC_BackgroundBlueValue; /*配置背景的藍色值*/
21 } LTDC_InitTypeDef;
這個結構體大部分成員都是用於定義LTDC的時序參數的,包括信號有效電平及各種時間參數的寬度,配合"液晶數據傳輸時序"中的說明更易理解。各個成員介紹如下,括號中的是STM32標准庫定義的宏:
(1) LTDC_HSPolarity
本成員用於設置行同步信號HSYNC的極性,即HSYNC有效時的電平,該成員的值可設置為高電平(LTDC_HSPolarity_AH)或低電平(LTDC_HSPolarity_AL)。
(2) LTDC_VSPolarity
本成員用於設置垂直同步信號VSYNC的極性,可設置為高電平(LTDC_VSPolarity_AH)或低電平(LTDC_VSPolarity_AL)。
(3) LTDC_DEPolarity
本成員用於設置數據使能信號DE的極性,可設置為高電平(LTDC_DEPolarity_AH)或低電平(LTDC_DEPolarity_AL)。
(4) LTDC_PCPolarity
本成員用於設置像素時鍾信號CLK的極性,可設置為上升沿(LTDC_DEPolarity_AH)或下降沿(LTDC_DEPolarity_AL),表示RGB數據信號在CLK的哪個時刻被采集。
(5) LTDC_HorizontalSync
本成員設置行同步信號HSYNC的寬度HSW,它以像素時鍾CLK的周期為單位,實際寫入該參數時應寫入(HSW-1),參數范圍為0x000- 0xFFF。
(6) LTDC_VerticalSync
本成員設置垂直同步信號VSYNC的寬度VSW,它以"行"為位,實際寫入該參數時應寫入(VSW-1) ,參數范圍為0x000- 0x7FF。
(7) LTDC_AccumulatedHBP
本成員用於配置"水平同步像素HSW"加"水平后沿像素HBP"的累加值,實際寫入該參數時應寫入(HSW+HBP-1) ,參數范圍為0x000- 0xFFF。
(8) LTDC_AccumulatedVBP
本成員用於配置"垂直同步行VSW"加"垂直后沿行VBP"的累加值,實際寫入該參數時應寫入(VSW+VBP-1) ,參數范圍為0x000- 0x7FF。
(9) LTDC_AccumulatedActiveW
本成員用於配置"水平同步像素HSW"加"水平后沿像素HBP"加"有效像素"的累加值,實際寫入該參數時應寫入(HSW+HBP+有效寬度-1) ,參數范圍為0x000- 0xFFF。
(10) LTDC_AccumulatedActiveH
本成員用於配置"垂直同步行VSW"加"垂直后沿行VBP"加"有效行"的累加值,實際寫入該參數時應寫入(VSW+VBP+有效高度-1) ,參數范圍為0x000- 0x7FF。
(11) LTDC_TotalWidth
本成員用於配置"水平同步像素HSW"加"水平后沿像素HBP"加"有效像素"加"水平前沿像素HFP"的累加值,即總寬度,實際寫入該參數時應寫入(HSW+HBP+有效寬度+HFP-1) ,參數范圍為0x000- 0xFFF。
(12) LTDC_TotalHeigh
本成員用於配置"垂直同步行VSW"加"垂直后沿行VBP"加"有效行"加"垂
直前沿行VFP"的累加值,即總高度,實際寫入該參數時應寫入(HSW+HBP+有效高度+VFP-1) ,參數范圍為0x000- 0x7FF。
(13) LTDC_BackgroundRedValue/ GreenValue/ BlueValue
這三個結構體成員用於配置背景的顏色值,見圖 2716,這里說的背景層與前面提到的"前景層/背景層"概念有點區別,它們對應下圖中的"第2層/第1層",而在這兩層之外,還有一個最終的背景層,當第1第2層都透明時,這個背景層就會被顯示,而這個背景層是一個純色的矩形,它的顏色值就是由這三個結構體成員配置的,各成員的參數范圍為0x00- 0xFF。
圖 2716 兩層與背景混合
對這些LTDC初始化結構體成員賦值后,調用庫函數LTDC_Init可把這些參數寫入到LTDC的各個配置寄存器,LTDC外設根據這些配置控制時序。
27.6 LTDC層級初始化結構體
LTDC初始化結構體只是配置好了與液晶屏通訊的基本時序,還有像素格式、顯存地址等諸多參數需要使用LTDC層級初始化結構體完成,見代碼清單 272。
代碼清單 272 LTDC層級初始化結構體LTDC_Layer_InitTypeDef
1 /**
2 * @brief LTDC Layer structure definition
3 */
4 typedef struct
5 {
6 uint32_t LTDC_HorizontalStart; /*配置窗口的行起始位置 */
7 uint32_t LTDC_HorizontalStop; /*配置窗口的行結束位置 */
8 uint32_t LTDC_VerticalStart; /*配置窗口的垂直起始位置 */
9 uint32_t LTDC_VerticalStop; /*配置窗口的垂直束位置 */
10 uint32_t LTDC_PixelFormat; /*配置當前層的像素格式*/
11 uint32_t LTDC_ConstantAlpha; /*配置當前層的透明度Alpha常量值*/
12 uint32_t LTDC_DefaultColorBlue; /*配置當前層的默認藍色值 */
13 uint32_t LTDC_DefaultColorGreen; /*配置當前層的默認綠色值 */
14 uint32_t LTDC_DefaultColorRed; /*配置當前層的默認紅色值 */
15 uint32_t LTDC_DefaultColorAlpha; /*配置當前層的默認透明值 */
16 uint32_t LTDC_BlendingFactor_1; /*配置混合因子BlendingFactor1 */
17 uint32_t LTDC_BlendingFactor_2; /*配置混合因子BlendingFactor2 */
18 uint32_t LTDC_CFBStartAdress; /*配置當前層的顯存起始位置*/
19 uint32_t LTDC_CFBLineLength; /*配置當前層的行數據長度 */
20 uint32_t LTDC_CFBPitch; /*配置從某行的起始到下一行像素起始處的增量*/
21 uint32_t LTDC_CFBLineNumber; /* 配置當前層的行數*/
22 } LTDC_Layer_InitTypeDef;
LTDC_Layer_InitTypeDef各個結構體成員的功能介紹如下:
(1) LTDC_ HorizontalStart /HorizontalStop/ VerticalStart/ VerticalStop
這些成員用於確定該層顯示窗口的邊界,分別表示行起始、行結束、垂直起始及垂直結束的位置,見圖 2717。注意這些參數包含同步HSW/VSW、后沿大小HBP/VBP和有效數據區域的內部時序發生器的配置,表 274的是各個窗口配置成員應寫入的數值。
圖 2717 配置可層的顯示窗口
表 274 各個窗口成員值
LTDC層級窗口配置成員 |
等效於LTDC時序參數配置成員的值 |
實際值 |
LTDC_HorizontalStart |
(LTDC_AccumulatedHBP+1) |
HBP + HSW |
LTDC_HorizontalStop |
LTDC_AccumulatedActiveW |
HSW+HBP+LCD_PIXEL_WIDTH-1 |
LTDC_VerticalStart |
(LTDC_AccumulatedVBP+1) |
VBP + VSW |
LTDC_VerticalStop |
LTDC_AccumulatedActiveH |
VSW+VBP+LCD_PIXEL_HEIGHT-1 |
(2) LTDC_PixelFormat
本成員用於設置該層數據的像素格式,可以設置為LTDC_Pixelformat_ARGB8888/ RGB888/ RGB565/ ARGB1555/ ARGB4444/ L8/ AL44/ AL88格式。
(3) LTDC_ConstantAlpha
本成員用於設置該層恆定的透明度常量Alpha,稱為恆定Alpha,參數范圍為0x00-0xFF,在圖層混合時,可根據后面的BlendingFactor成員的配置,選擇是只使用這個恆定Alpha進行混合運算還是把像素本身的Alpha值也加入到運算中。
(4) LTDC_DefaultColorBlue/Green/Red/Alpha
這些成員用於配置該層的默認顏色值,分別為藍、綠、紅及透明分量,該顏色在定義的層窗口外或在層禁止時使用。
(5) LTDC_BlendingFactor_1/2
本成員用於設置混合系數 BF1 和 BF2。每一層實際顯示的顏色都需要使用透明度參與運算,計算出不包含透明度的直接RGB顏色值,然后才傳輸給液晶屏(因為液晶屏本身沒有透明的概念)。混合的計算公式為:
BC = BF1 x C + BF2 x Cs,
公式中的參數見表 275:
表 275 混合公式參數說明表
參數 |
說明 |
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)。
圖 2718 兩層與背景混合
見圖 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) LTDC_CFBStartAdress
本成員用於設置該層的顯存首地址,該層的像素數據保存在從這個地址開始的存儲空間內。
(7) LTDC_CFBLineLength
本成員用於設置當前層的行數據長度,即每行的有效像素點個數x每個像素的字節數,實際配置該參數時應寫入值(行有效像素個數x每個像素的字節數+3),每個像素的字節數跟像素格式有關,如RGB565為2字節,RGB888為3字節,ARGB8888為4字節。
(8) LTDC_CFBPitch
本成員用於設置從某行的有效像素起始位置到下一行起始位置處的數據增量,無特殊情況的話,它一般就直接等於行的有效像素個數x每個像素的字節數。
(9) LTDC_CFBLineNumber
本成員用於設置當前層的顯示行數。
配置完LTDC_Layer_InitTypeDef層級初始化結構體后,調用庫函數LTDC_LayerInit可把這些配置寫入到LTDC的層級控制寄存器中,完成初始化。初始化完成后LTDC會不斷把顯存空間的數據傳輸到液晶屏進行顯示,我們可以直接修改或使用DMA2D修改顯存中的數據,從而改變顯示的內容。
27.7 DMA2D初始化結構體
在實際顯示時,我們常常采用DMA2D描繪直線和矩形,這個時候會用到DMA2D結構體,見代碼清單 273。
代碼清單 273 DMA2D初始化結構體
1 /**
2 * @brief DMA2D Init structure definition
3 */
4 typedef struct
5 {
6 uint32_t DMA2D_Mode; /*配置DMA2D的傳輸模式*/
7 uint32_t DMA2D_CMode; /*配置DMA2D的顏色模式 */
8 uint32_t DMA2D_OutputBlue; /*配置輸出圖像的藍色分量*/
9 uint32_t DMA2D_OutputGreen; /*配置輸出圖像的綠色分量*/
10 uint32_t DMA2D_OutputRed; /*配置輸出圖像的紅色分量*/
11 uint32_t DMA2D_OutputAlpha; /*配置輸出圖像的透明度分量*/
12 uint32_t DMA2D_OutputMemoryAdd; /*配置顯存地址*/
13 uint32_t DMA2D_OutputOffset; /*配置輸出地址偏移*/
14 uint32_t DMA2D_NumberOfLine; /*配置要傳輸多少行 */
15 uint32_t DMA2D_PixelPerLine; /*配置每行有多少個像素 */
16 } DMA2D_InitTypeDef;
DMA2D初始化結構體中的各個成員介紹如下:
(5) DMA2D_Mode
本成員用於配置DMA2D的工作模式,它可以被設置為表 276中的值。
表 276 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)
(6) DMA2D_CMode
本成員用於配置DMA2D的輸出PFC顏色格式,即它將要傳輸給顯存的格式。
(7) DMA2D_OutputBlue/ Green/ Red/ Alpha
這幾個成員用於配置DMA2D的寄存器顏色值,若DMA2D工作在"寄存器到存儲器"(DMA2D_R2M)模式時,這個顏色值作為數據源,被DMA2D復制到顯存空間,即目標空間都會被填入這一種色彩。
(8) DMA2D_OutputMemoryAdd
本成員用於配置DMA2D的輸出FIFO的地址, DMA2D的數據會被搬運到該空間,一般把它設置為本次傳輸顯示位置的起始地址。
(9) DMA2D_OutputOffset
本成員用於配置行偏移(以像素為單位),行偏移會被添加到各行的結尾,用於確定下一行的起始地址。如表 277中的黃色格子表示行偏移,綠色格子表示要顯示的數據。左表中顯示的是一條垂直的線,且線的寬度為1像素,所以行偏移的值=7-1=6,即"行偏移的值=行寬度-線的寬度",右表中的線寬度為2像素,行偏移的值=7-2=5。
表 277 數據傳輸示例(綠色的為要顯示的數據,黃色的為行偏移)
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(10) DMA2D_NumberOfLine
本成員用於配置DMA2D一共要傳輸多少行數據,如表 277中一共有5行數據。
(11) DMA2D_PixelPerLine
本成員用於配置每行有多少個像素點,如表 277左側表示每行有1個像素點,右側表示每行有2個像素點。
配置完這些結構體成員,調用庫函數DMA2D_Init即可把這些參數寫入到DMA2D的控制寄存器中,然后再調用DMA2D_StartTransfer函數開啟數據傳輸及轉換。
27.8 LTDC/DMA2D—液晶顯示實驗
本小節講解如何使用LTDC及DMA2D外設控制型號為"STD800480"的5寸液晶屏,見圖 2719,該液晶屏的分辨率為800x480,支持RGB888格式。
學習本小節內容時,請打開配套的"LTDC/DMA2D—液晶顯示英文"工程配合閱讀。
27.8.1 硬件設計
圖 2719 液晶屏實物圖
圖 2719液晶屏背面的PCB電路對應圖 2720、圖 2721、圖 2722、圖 2724中的原理圖,分別是升壓電路、觸摸屏接口、液晶屏接口及排針接口。升壓電路把輸入的5V電源升壓為20V,輸出到液晶屏的背光燈中;觸摸屏及液晶屏接口通過FPC插座把兩個屏的排線連接到PCB電路板上,這些FPC插座與信號引出到屏幕右側的排針處,方便整個屏幕與外部器件相連。
圖 2720升壓電路原理圖
升壓電路中的BK引腳可外接PWM信號,控制液晶屏的背光強度,BK為高電平時輸出電壓。
圖 2721 電容屏接口
電容觸摸屏使用I2C通訊,它的排線接口包含了I2C的通訊引腳SCL、SDA,還包含控制觸摸屏芯片復位的RSTN信號以及觸摸中斷信號INT。
圖 2722 液晶屏接口
關於這部分液晶屏的排線接口說明見圖 2723。
圖 2723 液晶排線接口
圖 2724 排針接口
以上是我們STM32F429實驗板使用的5寸屏原理圖,它通過屏幕上的排針接入到實驗板的液晶排母接口,與STM32芯片的引腳相連,連接見圖 2725。
圖 2725 屏幕與實驗板的引腳連接
由於液晶屏的部分引腳與實驗板的CAN芯片信號引腳相同,所以使用液晶屏的時候不能使用CAN通訊。
以上原理圖可查閱《LCD5.0-黑白原理圖》及《秉火F429開發板黑白原理圖》文檔獲知,若您使用的液晶屏或實驗板不一樣,請根據實際連接的引腳修改程序。
27.8.2 軟件設計
為了使工程更加有條理,我們把LCD控制相關的代碼獨立分開存儲,方便以后移植。在"FMC—讀寫SDRAM"工程的基礎上新建"bsp_lcd.c"及"bsp_lcd.h"文件,這些文件也可根據您的喜好命名,它們不屬於STM32標准庫的內容,是由我們自己根據應用需要編寫的。
1. 編程要點
(19) 初始化LTDC時鍾、DMA2D時鍾、GPIO時鍾;
(20) 初始化SDRAM,以便用作顯存;
(21) 根據液晶屏的參數配置LTDC外設的通訊時序;
(22) 配置LTDC層級控制參數,配置顯存地址;
(23) 初始化DMA2D,使用DMA2D輔助顯示;
(24) 編寫測試程序,控制液晶輸出。
2. 代碼分析
LTDC硬件相關宏定義
我們把LTDC控制液晶屏硬件相關的配置都以宏的形式定義到"bsp_lcd.h"文件中,見代碼清單 242。
代碼清單 274 LTDC硬件配置相關的宏(省略了部分數據線)
1 /*部分液晶信號線的引腳復用編號是AF9*/
2 #define GPIO_AF_LTDC_AF9 ((uint8_t)0x09)
3
4 //紅色數據線
5 #define LTDC_R0_GPIO_PORT GPIOH
6 #define LTDC_R0_GPIO_CLK RCC_AHB1Periph_GPIOH
7 #define LTDC_R0_GPIO_PIN GPIO_Pin_2
8 #define LTDC_R0_PINSOURCE GPIO_PinSource2
9 #define LTDC_R0_AF GPIO_AF_LTDC //使用LTDC復用編號
10
11 #define LTDC_R3_GPIO_PORT GPIOB
12 #define LTDC_R3_GPIO_CLK RCC_AHB1Periph_GPIOB
13 #define LTDC_R3_GPIO_PIN GPIO_Pin_0
14 #define LTDC_R3_PINSOURCE GPIO_PinSource0
15 #define LTDC_R3_AF GPIO_AF_LTDC_AF9 //使用AF9復用編號
16 /*此處省略R1、R2、R4-R7*/
17 //綠色數據線
18 #define LTDC_G0_GPIO_PORT GPIOE
19 #define LTDC_G0_GPIO_CLK RCC_AHB1Periph_GPIOE
20 #define LTDC_G0_GPIO_PIN GPIO_Pin_5
21 #define LTDC_G0_PINSOURCE GPIO_PinSource5
22 #define LTDC_G0_AF GPIO_AF_LTDC
23 /*此處省略G1-G7*/
24 //藍色數據線
25 #define LTDC_B0_GPIO_PORT GPIOE
26 #define LTDC_B0_GPIO_CLK RCC_AHB1Periph_GPIOE
27 #define LTDC_B0_GPIO_PIN GPIO_Pin_4
28 #define LTDC_B0_PINSOURCE GPIO_PinSource4
29 #define LTDC_B0_AF GPIO_AF_LTDC
30 /*此處省略B1-B7*/
31
32 //控制信號線
33 /*像素時鍾CLK*/
34 #define LTDC_CLK_GPIO_PORT GPIOG
35 #define LTDC_CLK_GPIO_CLK RCC_AHB1Periph_GPIOG
36 #define LTDC_CLK_GPIO_PIN GPIO_Pin_7
37 #define LTDC_CLK_PINSOURCE GPIO_PinSource7
38 #define LTDC_CLK_AF GPIO_AF_LTDC
39 /*水平同步信號HSYNC*/
40 #define LTDC_HSYNC_GPIO_PORT GPIOI
41 #define LTDC_HSYNC_GPIO_CLK RCC_AHB1Periph_GPIOI
42 #define LTDC_HSYNC_GPIO_PIN GPIO_Pin_10
43 #define LTDC_HSYNC_PINSOURCE GPIO_PinSource10
44 #define LTDC_HSYNC_AF GPIO_AF_LTDC
45 /*垂直同步信號VSYNC*/
46 #define LTDC_VSYNC_GPIO_PORT GPIOI
47 #define LTDC_VSYNC_GPIO_CLK RCC_AHB1Periph_GPIOI
48 #define LTDC_VSYNC_GPIO_PIN GPIO_Pin_9
49 #define LTDC_VSYNC_PINSOURCE GPIO_PinSource9
50 #define LTDC_VSYNC_AF GPIO_AF_LTDC
51 /*數據使能信號DE*/
52 #define LTDC_DE_GPIO_PORT GPIOF
53 #define LTDC_DE_GPIO_CLK RCC_AHB1Periph_GPIOF
54 #define LTDC_DE_GPIO_PIN GPIO_Pin_10
55 #define LTDC_DE_PINSOURCE GPIO_PinSource10
56 #define LTDC_DE_AF GPIO_AF_LTDC
57 /*液晶屏使能信號DISP,高電平使能*/
58 #define LTDC_DISP_GPIO_PORT GPIOD
59 #define LTDC_DISP_GPIO_CLK RCC_AHB1Periph_GPIOD
60 #define LTDC_DISP_GPIO_PIN GPIO_Pin_4
61 /*液晶屏背光信號,高電平使能*/
62 #define LTDC_BL_GPIO_PORT GPIOD
63 #define LTDC_BL_GPIO_CLK RCC_AHB1Periph_GPIOD
64 #define LTDC_BL_GPIO_PIN GPIO_Pin_7
以上代碼根據硬件的連接,把與LTDC與液晶屏通訊使用的引腳號、引腳源以及復用功能映射都以宏封裝起來。其中部分LTDC信號的復用功能映射比較特殊,如用作R3信號線的PB0,它的復用功能映射值為AF9,而大部分LTDC的信號線都是AF14,見圖 2726,在編寫宏的時候要注意區分。
圖 2726 LTDC的復用功能映射
初始化LTDC的 GPIO
利用上面的宏,編寫LTDC的GPIO引腳初始化函數,見代碼清單 243。
代碼清單 275 LTDC的GPIO初始化函數(省略了部分數據線)
1 /**
2 * @brief 初始化控制LCD的IO
3 * @param 無
4 * @retval 無
5 */
6 static void LCD_GPIO_Config(void)
7 {
8 GPIO_InitTypeDef GPIO_InitStruct;
9
10 /* 使能LCD使用到的引腳時鍾 */
11 //紅色數據線 /*此處省略部分信號線......*/
12 RCC_AHB1PeriphClockCmd(LTDC_R0_GPIO_CLK |
13 //控制信號線
14 LTDC_CLK_GPIO_CLK | LTDC_HSYNC_GPIO_CLK |LTDC_VSYNC_GPIO_CLK|
15 LTDC_DE_GPIO_CLK | LTDC_BL_GPIO_CLK |LTDC_DISP_GPIO_CLK ,ENABLE);
16
17 /* GPIO配置 */
18
19 /* 紅色數據線 */
20 GPIO_InitStruct.GPIO_Pin = LTDC_R0_GPIO_PIN;
21 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
22 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
23 GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
24 GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
25
26 GPIO_Init(LTDC_R0_GPIO_PORT, &GPIO_InitStruct);
27 GPIO_PinAFConfig(LTDC_R0_GPIO_PORT, LTDC_R0_PINSOURCE, LTDC_R0_AF);
28 /*此處省略部分數據信號線......*/
29 //控制信號線
30 GPIO_InitStruct.GPIO_Pin = LTDC_CLK_GPIO_PIN;
31 GPIO_Init(LTDC_CLK_GPIO_PORT, &GPIO_InitStruct);
32 GPIO_PinAFConfig(LTDC_CLK_GPIO_PORT, LTDC_CLK_PINSOURCE, LTDC_CLK_AF);
33
34 GPIO_InitStruct.GPIO_Pin = LTDC_HSYNC_GPIO_PIN;
35 GPIO_Init(LTDC_HSYNC_GPIO_PORT, &GPIO_InitStruct);
36GPIO_PinAFConfig(LTDC_HSYNC_GPIO_PORT, LTDC_HSYNC_PINSOURCE, LTDC_HSYNC_AF);
37
38 GPIO_InitStruct.GPIO_Pin = LTDC_VSYNC_GPIO_PIN;
39 GPIO_Init(LTDC_VSYNC_GPIO_PORT, &GPIO_InitStruct);
40GPIO_PinAFConfig(LTDC_VSYNC_GPIO_PORT, LTDC_VSYNC_PINSOURCE, LTDC_VSYNC_AF);
41
42 GPIO_InitStruct.GPIO_Pin = LTDC_DE_GPIO_PIN;
43 GPIO_Init(LTDC_DE_GPIO_PORT, &GPIO_InitStruct);
44 GPIO_PinAFConfig(LTDC_DE_GPIO_PORT, LTDC_DE_PINSOURCE, LTDC_DE_AF);
45
46 //背光BL 及液晶使能信號DISP
47 GPIO_InitStruct.GPIO_Pin = LTDC_DISP_GPIO_PIN;
48 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
49 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
50 GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
51 GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
52
53 GPIO_Init(LTDC_DISP_GPIO_PORT, &GPIO_InitStruct);
54
55 GPIO_InitStruct.GPIO_Pin = LTDC_BL_GPIO_PIN;
56 GPIO_Init(LTDC_BL_GPIO_PORT, &GPIO_InitStruct);
57
58 //拉高使能lcd
59 GPIO_SetBits(LTDC_DISP_GPIO_PORT,LTDC_DISP_GPIO_PIN);
60 GPIO_SetBits(LTDC_BL_GPIO_PORT,LTDC_BL_GPIO_PIN);
61 }
與所有使用到GPIO的外設一樣,都要先把使用到的GPIO引腳模式初始化,以上代碼把LTDC的信號線全都初始化為LCD復用功能,而背光BL及液晶使能DISP信號則被初始化成普通的推挽輸出模式,並且在初始化完畢后直接控制它們開啟背光及使能液晶屏。
配置LTDC的模式
接下來需要配置LTDC的工作模式,這個函數的主體是根據液晶屏的硬件特性,設置LTDC與液晶屏通訊的時序參數及信號有效極性。代碼清單 244。
代碼清單 276 配置LTDC的模式
1 /*根據液晶數據手冊的參數配置*/
2 #define HBP 46 //HSYNC后的無效像素
3 #define VBP 23 //VSYNC后的無效行數
4
5 #define HSW 1 //HSYNC寬度
6 #define VSW 1 //VSYNC寬度
7
8 #define HFP 16 //HSYNC前的無效像素
9 #define VFP 7 //VSYNC前的無效行數
10 /* LCD Size (Width and Height) */
11 #define LCD_PIXEL_WIDTH ((uint16_t)800)
12 #define LCD_PIXEL_HEIGHT ((uint16_t)480)
13
14 /**
15 * @brief LCD參數配置
16 * @note 這個函數用於配置LTDC外設:
17 */
18 void LCD_Init(void)
19 {
20 LTDC_InitTypeDef LTDC_InitStruct;
21
22 /* 使能LTDC外設時鍾 */
23 RCC_APB2PeriphClockCmd(RCC_APB2Periph_LTDC, ENABLE);
24 /* 使能DMA2D時鍾 */
25 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2D, ENABLE);
26 /* 初始化LCD的控制引腳 */
27 LCD_GPIO_Config();
28 /* 初始化SDRAM,以便使用SDRAM作顯存 */
29 SDRAM_Init();
30
31 /* 配置 PLLSAI 分頻器,它的輸出作為像素同步時鍾CLK*/
32 /* PLLSAI_VCO 輸入時鍾 = HSE_VALUE/PLL_M = 1 Mhz */
33 /* PLLSAI_VCO 輸出時鍾 = PLLSAI_VCO輸入 * PLLSAI_N = 420 Mhz */
34 /* PLLLCDCLK = PLLSAI_VCO 輸出/PLLSAI_R = 420/6 Mhz */
35 /* LTDC 時鍾頻率 = PLLLCDCLK / RCC_PLLSAI = 420/6/8 = 8.75 Mhz */
36 /* LTDC時鍾太高會導花屏,若對刷屏速度要求不高,降低時鍾頻率可減少花屏現象*/
37 /* 以下函數三個參數分別為:PLLSAIN,PLLSAIQ,PLLSAIR,其中PLLSAIQ與LTDC無關*/
38 RCC_PLLSAIConfig(420, 7, 6);
39 RCC_LTDCCLKDivConfig(RCC_PLLSAIDivR_Div8); //這個函數的參數值為DIV
40
41 /* 使能 PLLSAI 時鍾 */
42 RCC_PLLSAICmd(ENABLE);
43 /* 等待 PLLSAI 初始化完成 */
44 while (RCC_GetFlagStatus(RCC_FLAG_PLLSAIRDY) == RESET);
45
46 /* LTDC配置*********************************************************/
47 /*信號極性配置*/
48 /* 行同步信號極性 */
49 LTDC_InitStruct.LTDC_HSPolarity = LTDC_HSPolarity_AL;
50 /* 垂直同步信號極性 */
51 LTDC_InitStruct.LTDC_VSPolarity = LTDC_VSPolarity_AL;
52 /* 數據使能信號極性 */
53 LTDC_InitStruct.LTDC_DEPolarity = LTDC_DEPolarity_AL;
54 /* 像素同步時鍾極性 */
55 LTDC_InitStruct.LTDC_PCPolarity = LTDC_PCPolarity_IPC;
56
57 /* 配置LCD背景顏色 */
58 LTDC_InitStruct.LTDC_BackgroundRedValue = 0;
59 LTDC_InitStruct.LTDC_BackgroundGreenValue = 0;
60 LTDC_InitStruct.LTDC_BackgroundBlueValue = 0;
61
62 /* 時間參數配置 */
63 /* 配置行同步信號寬度(HSW-1) */
64 LTDC_InitStruct.LTDC_HorizontalSync =HSW-1;
65 /* 配置垂直同步信號寬度(VSW-1) */
66 LTDC_InitStruct.LTDC_VerticalSync = VSW-1;
67 /* 配置(HSW+HBP-1) */
68 LTDC_InitStruct.LTDC_AccumulatedHBP =HSW+HBP-1;
69 /* 配置(VSW+VBP-1) */
70 LTDC_InitStruct.LTDC_AccumulatedVBP = VSW+VBP-1;
71 /* 配置(HSW+HBP+有效像素寬度-1) */
72 LTDC_InitStruct.LTDC_AccumulatedActiveW = HSW+HBP+LCD_PIXEL_WIDTH-1;
73 /* 配置(VSW+VBP+有效像素高度-1) */
74 LTDC_InitStruct.LTDC_AccumulatedActiveH = VSW+VBP+LCD_PIXEL_HEIGHT-1;
75 /* 配置總寬度(HSW+HBP+有效像素寬度+HFP-1) */
76 LTDC_InitStruct.LTDC_TotalWidth =HSW+ HBP+LCD_PIXEL_WIDTH + HFP-1;
77 /* 配置總高度(VSW+VBP+有效像素高度+VFP-1) */
78 LTDC_InitStruct.LTDC_TotalHeigh =VSW+ VBP+LCD_PIXEL_HEIGHT + VFP-1;
79
80 LTDC_Init(<DC_InitStruct);
81 LTDC_Cmd(ENABLE);
82 }
該函數的執行流程如下:
(4) 初始化GPIO引腳以及LTDC、DMA2D時鍾
函數開頭調用了前面定義的LCD_GPIO_Config函數對液晶屏用到的GPIO進行初始化,並且使用庫函數RCC_APB2PeriphClockCmd及RCC_AHB1PeriphClockCmd使能LTDC和DMA2D外設的時鍾。
(5) 初始化SDRAM
接下來調用前面章節講解的SDRAM_Init函數初始化FMC外設控制SDRAM,以便使用SDRAM的存儲空間作為顯存。
(6) 設置像素同步時鍾
在"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=420,R=6,DIV=8,計算得LCD_CLK的時鍾頻率為8.75MHz,這個時鍾頻率是我們根據實測效果選定的,若使用的是16位數據格式,可把時鍾頻率設置為24MHz,若只使用單層液晶屏數據源,則可配置為34MHz。然而根據液晶屏的數據手冊查詢可知它支持最大的同步時鍾為50MHz,典型速率為33.3Mhz,見圖 2728,由此說明傳輸速率主要受限於STM32一方。LTDC外設需要從SDRAM顯存讀取數據,這會消耗一定的時間,所以使用32位像素格式的數據要比使用16位像素格式的慢,如若只使用單層數據源,還可以進一步減少一半的數據量,所以更快。
(7) 配置信號極性
接下來根據液晶屏的時序要求,配置LTDC與液晶屏通訊時的信號極性,見圖 2727。在程序中配置的HSYNC、VSYNC、DE有效信號極性均為低電平,同步時鍾信號極性配置為上升沿。其中DE信號的極性跟液晶屏時序圖的要求不一樣,文檔中DE的有效電平為高電平,而實際測試中把設置為DE低電平有效時屏幕才能正常工作,我們以實際測試為准。
圖 2727 液晶屏時序中的有效電平
(8) 配置時間參數
液晶屏通訊中還有時間參數的要求,接下來的程序我們根據液晶屏手冊給出的時間參數,配置HSW、VSW、HBP、HFP、VBP、VFP、有效像素寬度及有效行數。這些參數都根據圖 2728的說明以宏定義在程序中給出。
圖 2728 液晶屏數據手冊標注的時間參數
(9) 寫入參數到寄存器並使能外設
經過上面步驟,賦值完了初始化結構體,接下來調用庫函數LTDC_Init把各種參數寫入到LTDC的控制寄存器中,然后調用庫函數LTDC_Cmd使能LTDC。
配置LTDC的層級初始化
在上面配置完成STM32的LTDC外設基本工作模式后,還需要針對液晶屏的各個數據源層進行初始化,才能正常工作,代碼清單 458。
代碼清單 277 LTDC的層級初始化
1 /* LCD Size (Width and Height) */
2 #define LCD_PIXEL_WIDTH ((uint16_t)800)
3 #define LCD_PIXEL_HEIGHT ((uint16_t)480)
4
5 #define LCD_FRAME_BUFFER ((uint32_t)0xD0000000) //第一層首地址
6 #define BUFFER_OFFSET ((uint32_t)800*480*3) //一層液晶的數據量
7 #define LCD_PIXCELS ((uint32_t)800*480)
8 /**
9 * @brief 初始化LTD的層參數
10 * - 設置顯存空間
11 * - 設置分辨率
12 * @param None
13 * @retval None
14 */
15 void LCD_LayerInit(void)
16 {
17 LTDC_Layer_InitTypeDef LTDC_Layer_InitStruct;
18
19 /* 層窗口配置 */
20 /* 配置本層的窗口邊界,注意這些參數是包含HBP HSW VBP VSW的 */
21 //一行的第一個起始像素,該值應為 (LTDC_InitStruct.LTDC_AccumulatedHBP+1)的值
22 LTDC_Layer_InitStruct.LTDC_HorizontalStart = HBP + HSW;
23 //一行的最后一個像素,該值應為 (LTDC_InitStruct.LTDC_AccumulatedActiveW)的值
24 LTDC_Layer_InitStruct.LTDC_HorizontalStop = HSW+HBP+LCD_PIXEL_WIDTH-1;
25 //一列的第一個起始像素,該值應為 (LTDC_InitStruct.LTDC_AccumulatedVBP+1)的值
26 LTDC_Layer_InitStruct.LTDC_VerticalStart = VBP + VSW;
27 //一列的最后一個像素,該值應為 (LTDC_InitStruct.LTDC_AccumulatedActiveH)的值
28 LTDC_Layer_InitStruct.LTDC_VerticalStop = VSW+VBP+LCD_PIXEL_HEIGHT-1;
29
30 /* 像素格式配置*/
31 LTDC_Layer_InitStruct.LTDC_PixelFormat = LTDC_Pixelformat_RGB888;
32
33 /* 默認背景顏色,該顏色在定義的層窗口外或在層禁止時使用。 */
34 LTDC_Layer_InitStruct.LTDC_DefaultColorBlue = 0;
35 LTDC_Layer_InitStruct.LTDC_DefaultColorGreen = 0;
36 LTDC_Layer_InitStruct.LTDC_DefaultColorRed = 0;
37 LTDC_Layer_InitStruct.LTDC_DefaultColorAlpha = 0;
38 /* 恆定Alpha值配置,0-255 */
39 LTDC_Layer_InitStruct.LTDC_ConstantAlpha = 255;
40 /* 配置混合因子 CA表示使用恆定Alpha值,PAxCA表示使用像素Alpha x 恆定Alpha值 */
41 LTDC_Layer_InitStruct.LTDC_BlendingFactor_1 = LTDC_BlendingFactor1_CA;
42 LTDC_Layer_InitStruct.LTDC_BlendingFactor_2 = LTDC_BlendingFactor2_CA;
43
44 /* 該成員應寫入(一行像素數據占用的字節數+3)
45 Line Lenth = 行有效像素個數 x 每個像素的字節數 + 3
46 行有效像素個數 = LCD_PIXEL_WIDTH
47 每個像素的字節數 = 2(RGB565/RGB1555)/ 3 (RGB888)/ 4(ARGB8888)
48 */
49 LTDC_Layer_InitStruct.LTDC_CFBLineLength = ((LCD_PIXEL_WIDTH * 3) + 3);
50 /* 從某行的起始位置到下一行起始位置處的像素增量
51 Pitch = 行有效像素個數 x 每個像素的字節數 */
52 LTDC_Layer_InitStruct.LTDC_CFBPitch = (LCD_PIXEL_WIDTH * 3);
53
54 /* 配置有效的行數 */
55 LTDC_Layer_InitStruct.LTDC_CFBLineNumber = LCD_PIXEL_HEIGHT;
56 /* 配置本層的顯存首地址 */
57 LTDC_Layer_InitStruct.LTDC_CFBStartAdress = LCD_FRAME_BUFFER;
58 /* 以上面的配置初始化第 1 層*/
59 LTDC_LayerInit(LTDC_Layer1, <DC_Layer_InitStruct);
60
61 /*配置第 2 層,若沒有重寫某個成員的值,則該成員使用跟第1層一樣的配置 */
62 /* 配置本層的顯存首地址,這里配置它緊挨在第1層的后面*/
63 LTDC_Layer_InitStruct.LTDC_CFBStartAdress = LCD_FRAME_BUFFER+ BUFFER_OFFSET;
64
65 /* 配置混合因子,使用像素Alpha參與混合 */
66 LTDC_Layer_InitStruct.LTDC_BlendingFactor_1 = LTDC_BlendingFactor1_PAxCA;
67 LTDC_Layer_InitStruct.LTDC_BlendingFactor_2 = LTDC_BlendingFactor2_PAxCA;
68 /* 初始化第2層 */
69 LTDC_LayerInit(LTDC_Layer2, <DC_Layer_InitStruct);
70
71 /* 立即重載配置 */
72 LTDC_ReloadConfig(LTDC_IMReload);
73 /*使能前景及背景層 */
74 LTDC_LayerCmd(LTDC_Layer1, ENABLE);
75 LTDC_LayerCmd(LTDC_Layer2, ENABLE);
76
77 /* 立即重載配置 */
78 LTDC_ReloadConfig(LTDC_IMReload);
79 }
80
LTDC的層級初始化函數執行流程如下:
(1) 配置窗口邊界
每層窗口都需要配置有效顯示窗口,使用LTDC_HorizontalStart/ HorizontalStop/ LTDC_VerticalStart/ LTDC_VerticalStop成員來確定這個窗口的左右上下邊界,各個成員應寫入的值與前面LTDC初始化結構體中某些參數類似,注意某些成員要求加1或減1。
(2) 配置像素的格式
LTDC_PixelFormat成員用於配置本層像素的格式,在這個實驗中我們把這層設置為RGB888格式,兩層數據源的像素可以配置成不同的格式,層與層之間是獨立的。
(3) 配置默認背景顏色
在定義的層窗口外或在層禁止時,該層會使用默認顏色作為數據源,默認顏色使用LTDC_DefaultColorBlue/Green/Red/Alpha成員來配置,本實驗中我們把默認顏色配置成透明了。
(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) 配置層的數據量
通過參數LTDC_CFBLineLength及LTDC_CFBLineNumber可設定層的數據量,層的數據量跟顯示窗口大小及像素格式有關,由於我們把這層的像素格式設置成了RGB888,所以每個像素點的大小為3字節,LTDC_CFBLineLength參數寫入值:行有效像素個數x3+3 。而LTDC_CFBLineNumber參數直接寫入有效行數(LCD_PIXEL_HEIGHT)。還有一個參數LTDC_CFBPitch它用於配置上一行起始像素與下一行起始像素的數據增量,我們直接寫入:行有效像素個數x3 即可。
(6) 配置顯存首地址
每一層都有獨立的顯存空間,向LTDC_CFBStartAdress參數賦值可設置該層的顯存首地址,我們把第1層的顯存首地址直接設置成宏LCD_FRAME_BUFFER,該宏表示的地址為0xD0000000,即SDRAM的首地址,從該地址開始,BUFFER_OFFSET個字節的空間都用作這一層的顯存空間(BUFFER_OFFSET宏的值為800x480x3:行有效像素寬度x行數x每個字節的數據量),向這些空間寫入的數據會被解釋成像素數據,LTDC會把這些數據傳輸到液晶屏上,所以我們要控制液晶屏的輸出,只要修改這些空間的數據即可,包括變量操作、指針操作、DMA操作以及DMA2D操作等一切可修改SDRAM內容的操作都支持。
實際設置中不需要刻意設置成SDRAM首地址,只要能保證該地址后面的數據空間足夠存儲該層的一幀數據即可。
(7) 向寄存器寫入配置參數
賦值完后,調用庫函數LTDC_LayerInit可把這些參數寫入到LTDC的層控制寄存器,根據函數的第一個參數LTDC_Layer1/2來決定配置的是第1層還是第2層。
(8) 配置第2層控制參數
要想有混合效果,還需要使用第2層數據源,它與第1層的配置大致是一樣的,主要區別是顯存首地址和混合因子。在程序中我們把第2層的顯存首地址設置成緊挨着第1層顯存空間的結尾。而混合因子都配置成PAxCA以便它的透明像素能參與運算,實現透明效果,但實際上我們並沒有修改第2層像素數據的格式,它依然使用RGB888格式,由於像素本身並沒有Alpha通道的數據,所以是沒有透明混合效果的。
正常使用時可把第2層配置成ARGB8888或ARGB1555格式,才能正常使用兩層數據混合的功能。本程序沒有配置透明格式主要是因為各種描繪函數(如畫點、畫線等)是要根據像素格式進行修改的。兩層使用不同的像素格式那么就要有兩套同樣功能的函數,容易造成混亂,而ARGB8888的數據量太大,所以我們把兩層的像素格式都設置成了RGB888。
如果想了解使用透明像素格式如何使用,可把工程里的"bsp_lcd.h"文件的宏"LCD_RGB888"值修改為0,這樣"bsp_lcd.c"文件會使用兩層都配置為ARGB1555的格式及控制函數:
1 /*把這個宏設置成非0值液晶屏使用RGB888色彩,若為0則使用ARGB1555色彩*/
2 #define LCD_RGB_888 1
(9) 重載LTDC配置並使能數據層
把兩層的參數都寫入到寄存器后,使用庫函數LTDC_ReloadConfig讓LTDC外設立即重新加載這些配置,並使用庫函數LTDC_LayerCmd使能兩層的數據源。至此,LTDC配置就完成,可以向顯存空間寫入數據進行顯示了。
輔助顯示的全局變量及函數
為方便顯示操作,我們定義了一些全局變量及函數來輔助修改顯存內容,這些函數都是我們自己定義的,不是STM32標准庫提供的內容。見代碼清單 278。
代碼清單 278 輔助顯示的全局變量及函數
1
2 /*字體格式*/
3 typedef struct _tFont
4 {
5 const uint16_t *table; /*指向字模數據的指針*/
6 uint16_t Width; /*字模的像素寬度*/
7 uint16_t Height; /*字模的像素高度*/
8 } sFONT;
9 /*這些可選的字體格式定義在fonts.c文件*/
10 extern sFONT Font16x24;
11 extern sFONT Font12x12;
12 extern sFONT Font8x12;
13 extern sFONT Font8x8;
14
15 /**
16 * @brief LCD Layer
17 */
18 #define LCD_BACKGROUND_LAYER 0x0000
19 #define LCD_FOREGROUND_LAYER 0x0001
20
21 #define LCD_FRAME_BUFFER ((uint32_t)0xD0000000) //第一層首地址
22 #define BUFFER_OFFSET ((uint32_t)800*480*3) //一層液晶的數據量
23
24 /*用於存儲當前選擇的字體格式*/
25 static sFONT *LCD_Currentfonts;
26 /* 用於存儲當前字體顏色和字體背景顏色的變量*/
27 static uint32_t CurrentTextColor = 0x000000;
28 static uint32_t CurrentBackColor = 0xFFFFFF;
29 /* 用於存儲層對應的顯存空間和當前選擇的層*/
30 static uint32_t CurrentFrameBuffer = LCD_FRAME_BUFFER;
31 static uint32_t CurrentLayer = LCD_BACKGROUND_LAYER;
32
33 /**
34 * @brief 設置字體格式(英文)
35 * @param fonts: 選擇要設置的字體格式
36 * @retval None
37 */
38 void LCD_SetFont(sFONT *fonts)
39 {
40 LCD_Currentfonts = fonts;
41 }
42 /**
43 * @brief 選擇要控制的層.
44 * @param Layerx: 選擇要操作前景層(第2層)還是背景層(第1層)
45 * @retval None
46 */
47 void LCD_SetLayer(uint32_t Layerx)
48 {
49 if (Layerx == LCD_BACKGROUND_LAYER)
50 {
51 CurrentFrameBuffer = LCD_FRAME_BUFFER;
52 CurrentLayer = LCD_BACKGROUND_LAYER;
53 }
54 else
55 {
56 CurrentFrameBuffer = LCD_FRAME_BUFFER + BUFFER_OFFSET;
57 CurrentLayer = LCD_FOREGROUND_LAYER;
58 }
59 }
60
61 /**
62 * @brief 設置字體的顏色及字體的背景顏色
63 * @param TextColor: 字體顏色
64 * @param BackColor: 字體的背景顏色
65 * @retval None
66 */
67 void LCD_SetColors(uint32_t TextColor, uint32_t BackColor)
68 {
69 CurrentTextColor = TextColor;
70 CurrentBackColor = BackColor;
71 }
72
73 /**
74 * @brief 設置字體顏色
75 * @param Color: 字體顏色
76 * @retval None
77 */
78 void LCD_SetTextColor(uint32_t Color)
79 {
80 CurrentTextColor = Color;
81 }
82
83 /**
84 * @brief 設置字體的背景顏色
85 * @param Color: 字體的背景顏色
86 * @retval None
87 */
88 void LCD_SetBackColor(uint32_t Color)
89 {
90 CurrentBackColor = Color;
91 }
(1) 切換字體大小格式
液晶顯示中,文字內容占據了很大部分,顯示文字需要有"字模數據"配合。關於字模的知識我們在下一章節講解,在這里只簡單介紹一下基本概念。字模是一個個像素點陣方塊,如上述代碼中的sFont結構體,包含了指向字模數據的指針以及每個字模的像素寬度、高度,即字體的大小。本實驗的工程中提供了像素格式為16x24、12x12、8x12、8x8的英文字模。為了方便選擇字模,定義了全局指針變量LCD_Currentfonts用來存儲當前選擇的字模格式,實際顯示時根據該指針指向的字模格式來顯示文字,可以使用下面的LCD_SetFont函數切換指針指向的字模格式,該函數的可輸入參數為: Font16x24/ Font12x12/ Font8x12/ Font8x8。
(2) 切換字體顏色和字體背景顏色
很多時候我們還希望文字能以不同的色彩顯示,為此定義了全局變量CurrentTextColor和CurrentBackColor用於設定要顯示字體的顏色和字體背景顏色,如:
字體為紅色和字體背景為藍色
使用函數LCD_SetColors、LCD_SetTextColor以及LCD_SetBackColor可以方便修改這兩個全局變量的值。若液晶的像素格式支持透明,可把字體背景設置為透明值,實現彈幕顯示的效果(文字浮在圖片之上,透過文字可看到背景圖片)。
(3) 切換當前操作的液晶層
由於顯示的數據源有兩層,在寫入數據時需要區分到底要寫入哪個顯存空間,為此,我們定義了全局變量CurrentLayer和CurrentFrameBuffer用於存儲要操作的液晶層及該層的顯存首地址。使用函數LCD_SetLayer可切換要操作的層及顯存地址。
繪制像素點
有了以上知識准備,就可以開始向液晶屏繪制像素點了,見代碼清單 279。
代碼清單 279 繪制像素點
1 /**
2 * @brief 顯示一個像素點
3 * @param x: 像素點的x坐標
4 * @param y: 像素點的y坐標
5 * @retval None
6 */
7 void PutPixel(int16_t x, int16_t y)
8 {
9 if (x < 0 || x > LCD_PIXEL_WIDTH || y < 0 || y > LCD_PIXEL_HEIGHT)
10 {
11 return;
12 }
13 {
14 /*RGB888*/
15 uint32_t Xaddress =0;
16 Xaddress = CurrentFrameBuffer + 3*(LCD_PIXEL_WIDTH*y + x);
17 *(__IO uint16_t*) Xaddress= (0x00FFFF & CurrentTextColor); //GB
18 *(__IO uint8_t*)( Xaddress+2)= (0xFF0000 & CurrentTextColor) >> 16; //R
19 }
20 }
這個繪制像素點的函數可輸入x,y兩個參數,用於指示要繪制像素點的坐標。得到輸入參數后它首先進行參數檢查,若坐標超出液晶顯示范圍則直接退出函數,不進行操作。坐標檢查通過后根據坐標計算該像素所在的顯存地址,液晶屏中的每個像素點都有對應的顯存空間,像素點的坐標與顯存地址有固定的映射關系,見表 278。
表 278 顯存存儲像素數據的方式 (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
利用這些映射關系,繪制點函數代入存儲了當前要操作的層顯存首地址的全局變量CurrentFrameBuffer計算出像素點的顯存基地址Xaddress,再利用指針操作把當前字體顏色CurrentTextColor中的RGB顏色分量分別存儲到對應的位置。由於LTDC工作后會一直刷新顯存的數據到液晶屏,所以在下一次LTDC刷新的時候,被修改的顯存數據就會顯示到液晶屏上了。
掌握了繪制任意像素點顏色的操作后,就能隨心所欲地控制液晶屏了,其它復雜的顯示操作如繪制直線、矩形、圓形、文字、圖片以及視頻都是一樣的,本質上都是操縱一個個像素點而已。如直線由點構成,矩形由直線構成,它們的區別只是點與點之間幾何關系的差異,對液晶屏來說並沒有什么特別。
使用DMA2D繪制直線和矩形
利用上面的像素點繪制方式可以實現所有液晶操作,但直接使用指針訪問內存空間效率並不高,在某些場合下可使用DMA2D搬運內存數據,加速傳輸。繪制純色直線和矩形的時候十分適合,代碼清單 2710。
代碼清單 2710 使用DMA2D繪制直線
1 /**
2 * @brief LCD Direction
3 */
4 #define LCD_DIR_HORIZONTAL 0x0000
5 #define LCD_DIR_VERTICAL 0x0001
6 /**
7 * @brief 顯示一條直線
8 * @param Xpos: 直線起點的x坐標
9 * @param Ypos: 直線起點的y坐標
10 * @param Length: 直線的長度
11 * @param Direction: 直線的方向,可輸入LCD_DIR_HORIZONTAL(水平方向)
12 LCD_DIR_VERTICAL(垂直方向).
13 * @retval None
14 */
15 void LCD_DrawLine(uint16_t Xpos, uint16_t Ypos, uint16_t Length,
16 uint8_t Direction)
17 {
18 DMA2D_InitTypeDef DMA2D_InitStruct;
19
20 uint32_t Xaddress = 0;
21 uint16_t Red_Value = 0, Green_Value = 0, Blue_Value = 0;
22
23 /*計算目標地址*/
24 Xaddress = CurrentFrameBuffer + 3*(LCD_PIXEL_WIDTH*Ypos + Xpos);
25
26 /*提取顏色分量*/
27 Red_Value = (0xFF0000 & CurrentTextColor) >>16;
28 Blue_Value = 0x0000FF & CurrentTextColor;
29 Green_Value = (0x00FF00 & CurrentTextColor)>>8 ;
30
31 /* 配置DMA2D */
32 DMA2D_DeInit();
33 DMA2D_InitStruct.DMA2D_Mode = DMA2D_R2M;
34 DMA2D_InitStruct.DMA2D_CMode = DMA2D_RGB888;
35 DMA2D_InitStruct.DMA2D_OutputGreen = Green_Value;
36 DMA2D_InitStruct.DMA2D_OutputBlue = Blue_Value;
37 DMA2D_InitStruct.DMA2D_OutputRed = Red_Value;
38 DMA2D_InitStruct.DMA2D_OutputAlpha = 0x0F;
39 DMA2D_InitStruct.DMA2D_OutputMemoryAdd = Xaddress;
40
41 /*水平方向*/
42 if (Direction == LCD_DIR_HORIZONTAL)
43 {
44 DMA2D_InitStruct.DMA2D_OutputOffset = 0;
45 DMA2D_InitStruct.DMA2D_NumberOfLine = 1;
46 DMA2D_InitStruct.DMA2D_PixelPerLine = Length;
47 }
48 else /*垂直方向*/
49 {
50 DMA2D_InitStruct.DMA2D_OutputOffset = LCD_PIXEL_WIDTH - 1;
51 DMA2D_InitStruct.DMA2D_NumberOfLine = Length;
52 DMA2D_InitStruct.DMA2D_PixelPerLine = 1;
53 }
54 DMA2D_Init(&DMA2D_InitStruct);
55 /*開始DMA2D傳輸 */
56 DMA2D_StartTransfer();
57 /*等待傳輸結束 */
58 while (DMA2D_GetFlagStatus(DMA2D_FLAG_TC) == RESET);
59 }
這個繪制直線的函數輸入參數為直線起始像素點的坐標,直線長度,以及直線的方向(它只能描繪水平直線或垂直直線),函數主要利用了前面介紹的DMA2D初始化結構體,執行流程介紹如下:
(1) 計算起始像素點的顯存位置
與繪制單個像素點一樣,使用DMA2D繪制也需要知道像素點對應的顯存地址。利用直線起始像素點的坐標計算出直線在顯存的基本位置Xaddress。
(2) 提取直線顏色的RGB分量
使用DMA2D繪制純色數據時,需要向它的寄存器寫入RGB通道的數據,所以我們先把直線顏色CurrentTextColor的RGB分量提取到RED/ Green / Blue _Value變量值中。
(3) 配置DMA2D傳輸模式像素格式、顏色分量及偏移地址。
接下來開始向DMA2D初始化結構體賦值,在賦值前先調用了庫函數DMA2D_DeInit,以便關閉DMA2D,因為它只有在非工作狀態下才能重新寫入配置。配置時把DMA2D的模式設置成了DMA2D_R2M,以寄存器中的顏色作為數據源,即DMA2D_OutputGreen/Blue/Red/Alpha中的值,我們向這些參數寫入上面提取得到的顏色分量。DMA2D輸出地址設置為上面計算得的Xaddress。
(4) 配置DMA2D的輸出偏移、行數及每行的像素點個數
直線方向不同時,對DMA2D_OutputOffset(行偏移)、DMA2D_NumberOfLine(行的數量)及DMA2D_PixelPerLine(每行的像素寬度)這幾個參數的配置是不一樣的。在顯示垂直線的時候才需要行偏移,而在顯示水平線的時候,由於水平線寬度只有一個像素點,只占據一行,像素點,不需要換行,所以行偏移設置為任意值都不影響。行偏移的概念比較抽象,請參考前面解釋"DMA2D初始化結構體"的內容理解。
(5) 寫入參數到寄存器並傳輸
配置完DMA2D的參數后,調用庫函數DMA2D_Init把參數寫入到寄存器中,然后調用DMA2D_StartTransfer開始傳輸,然后檢測標志位等待傳輸結束。
使用DMA2D繪制矩形
與繪制直線很類似,利用DMA2D繪制純色矩形的方法見代碼清單 2711。
代碼清單 2711 使用DMA2D繪制矩形
1 /**
2 * @brief 繪制實心矩形
3 * @param Xpos: 起始X坐標
4 * @param Ypos: 起始Y坐標
5 * @param Height: 矩形高
6 * @param Width: 矩形寬
7 * @retval None
8 */
9 void LCD_DrawFullRect(uint16_t Xpos, uint16_t Ypos, uint16_t Width,
10 uint16_t Height)
11 {
12 DMA2D_InitTypeDef DMA2D_InitStruct;
13
14 uint32_t Xaddress = 0;
15 uint16_t Red_Value = 0, Green_Value = 0, Blue_Value = 0;
16
17 Red_Value = (0xFF0000 & CurrentTextColor)>>16 ;
18 Blue_Value = 0x0000FF & CurrentTextColor;
19 Green_Value = (0x00FF00 & CurrentTextColor)>>8;
20
21 Xaddress = CurrentFrameBuffer + 3*(LCD_PIXEL_WIDTH*Ypos + Xpos);
22
23 /* 配置DMA2D DMA2D */
24 DMA2D_DeInit();
25 DMA2D_InitStruct.DMA2D_Mode = DMA2D_R2M;
26 DMA2D_InitStruct.DMA2D_CMode = DMA2D_RGB888;
27 DMA2D_InitStruct.DMA2D_OutputGreen = Green_Value;
28 DMA2D_InitStruct.DMA2D_OutputBlue = Blue_Value;
29 DMA2D_InitStruct.DMA2D_OutputRed = Red_Value;
30 DMA2D_InitStruct.DMA2D_OutputAlpha = 0x0F;
31 DMA2D_InitStruct.DMA2D_OutputMemoryAdd = Xaddress;
32 DMA2D_InitStruct.DMA2D_OutputOffset = (LCD_PIXEL_WIDTH - Width);
33 DMA2D_InitStruct.DMA2D_NumberOfLine = Height;
34 DMA2D_InitStruct.DMA2D_PixelPerLine = Width;
35 DMA2D_Init(&DMA2D_InitStruct);
36 /* 開始DMA2D傳輸 */
37 DMA2D_StartTransfer();
38
39 /* 等待傳輸結束 */
40 while (DMA2D_GetFlagStatus(DMA2D_FLAG_TC) == RESET);
41 }
對於DMA2D來說,繪制矩形實際上就是繪制一條很粗的直線,與繪制直線的主要區別是行偏移、行數以及每行的像素個數。
3. main函數
最后我們來編寫main函數,使用液晶屏顯示圖像,見代碼清單 2414。
代碼清單 2712 main函數
1 /**
2 * @brief 主函數
3 * @param 無
4 * @retval 無
5 */
6 int main(void)
7 {
8 /* LED 端口初始化 */
9 LED_GPIO_Config();
10
11 /*初始化液晶屏*/
12 LCD_Init();
13 LCD_LayerInit();
14 LTDC_Cmd(ENABLE);
15
16 /*把背景層刷黑色*/
17 LCD_SetLayer(LCD_BACKGROUND_LAYER);
18 LCD_Clear(LCD_COLOR_BLACK);
19
20 /*初始化后默認使用前景層*/
21 LCD_SetLayer(LCD_FOREGROUND_LAYER);
22 /*默認設置不透明,該函數參數為不透明度,范圍 0-0xff ,0為全透明,0xff為不透明*/
23 LCD_SetTransparency(0xFF);
24 LCD_Clear(LCD_COLOR_BLACK);
25 /*經過LCD_SetLayer(LCD_FOREGROUND_LAYER)函數后,
26 以下液晶操作都在前景層刷新,除非重新調用過LCD_SetLayer函數設置背景層*/
27
28 LED_BLUE;
29
30 Delay(0xfff);
31
32 while (1)
33 {
34 LCD_Test();
35 }
36 }
上電后,調用了LCD_Init、LCD_LayerInit函數初始化LTDC外設,然后使用LCD_SetLayer函數切換到背景層,使用LCD_Clear函數把背景層都刷成黑色,LCD_Clear實質是一個使用DMA2D顯示矩形的函數,只是它默認矩形的寬和高直接設置成液晶屏的分辨率,把整個屏幕都刷成同一種顏色。刷完背景層的顏色后再調用LCD_SetLayer切換到前景層,然后在前景層繪制圖形。中間還有一個LCD_SetTransparency函數,它用於設置當前層的透明度,設置后整一層的像素包含該透明值,由於整層透明並沒有什么用(一般應用是某些像素點透明看到背景,而其它像素點不透明),我們把前景層設置為完全不透明。
初始化完成后,我們調用LCD_Test函數顯示各種圖形進行測試(如直線、矩形、圓形),具體內容請直接在工程中閱讀源碼,這里不展開講解。LCD_Test中還調用了文字顯示函數,其原理在下一章節詳細說明。
下載驗證
用USB線連接開發板,編譯程序下載到實驗板,並上電復位,液晶屏會顯示各種內容。
27.9 每課一問
6. 不使用DMA2D,編寫程序繪制純色直線和純色矩形,並測試比較它與DMA2D方式的速度差異。(RGB888像素格式,可參考PutPixel函數)
7. 如果像素格式分別為RGB1555和ARGB8888,應如何修改繪制像素點函數(PutPixel) 以及DMA2D繪制矩形函數(LCD_DrawFullRect)。