本章參考資料:《STM32F76xxx參考手冊2》、《STM32F7xx規格書》、庫幫助文檔《STM32F779xx_User_Manual.chm》。html
關於開發板配套的液晶屏參數可查閱《5.0寸液晶屏數據手冊》配套資料獲知。編程
顯示器屬於計算機的I/O設備,即輸入輸出設備。它是一種將特定電子信息輸出到屏幕上再反射到人眼的顯示工具。常見的有CRT顯示器、液晶顯示器、LED點陣顯示器及OLED顯示器。緩存
液晶顯示器,簡稱LCD(Liquid Crystal Display),相對於上一代CRT顯示器(陰極射線管顯示器),LCD顯示器具備功耗低、體積小、承載的信息量大及不傷眼的優勢,於是它成爲了如今的主流電子顯示設備,其中包括電視、電腦顯示器、手機屏幕及各類嵌入式設備的顯示器。圖 27-1是液晶電視與CRT電視的外觀對比,很明顯液晶電視更薄,「時尚」是液晶電視給人的第一印象,而CRT 電視則感受很「笨重」。函數
圖 27-1 液晶電視及CRT電視工具
液晶是一種介於固體和液體之間的特殊物質,它是一種有機化合物,常態下呈液態,可是它的分子排列卻和固體晶體同樣很是規則,所以取名液晶。若是給液晶施加電場,會改變它的分子排列,從而改變光線的傳播方向,配合偏振光片,它就具備控制光線透過率的做用,再配合彩色濾光片,改變加給液晶電壓大小,就能改變某一顏色透光量的多少,圖 27-2中的就是綠色顯示結構。利用這種原理,作出可控紅、綠、藍光輸出強度的顯示結構,把三種顯示結構組成一個顯示單位,經過控制紅綠藍的強度,可使該單位混合輸出不一樣的色彩,這樣的一個顯示單位被稱爲像素。學習
圖 27-2 液晶屏的綠色顯示結構測試
注意液晶自己是不發光的,因此須要有一個背光燈提供光源,光線通過一系列處理過程纔到輸出,因此輸出的光線強度是要比光源的強度低不少的,比較浪費能源(固然,比CRT顯示器仍是節能多了)。並且這些處理過程會致使顯示方向比較窄,也就是它的視角較小,從側面看屏幕會看不清它的顯示內容。另外,輸出的色彩變換時,液晶分子轉動也須要消耗必定的時間,致使屏幕的響應速度低。字體
LED點陣顯示器不存在以上液晶顯示器的問題,LED點陣彩色顯示器的單個像素點內包含紅綠藍三色LED燈,顯示原理相似咱們實驗板上的LED彩燈,經過控制紅綠藍顏色的強度進行混色,實現全綵顏色輸出,多個像素點構成一個屏幕。因爲每一個像素點都是LED燈自發光的,因此在戶外白天也顯示得很是清晰,但因爲LED燈體積較大,致使屏幕的像素密度低,因此它通常只適合用於廣場上的巨型顯示器。相對來講,單色的LED點陣顯示器應用得更普遍,如公交車上的信息展現牌、店招等,見圖 27-3。ui
圖 27-3 LED點陣彩屏有LED單色顯示屏spa
新一代的OLED顯示器與LED點陣彩色顯示器的原理相似,但因爲它採用的像素單元是「有機發光二極管」(Organic Light Emitting Diode),因此像素密度比普通LED點陣顯示器高得多,見圖 27-4。
圖 27-4 OLED像素結構
OLED顯示器不須要背光源、對比度高、輕薄、視角廣及響應速度快等優勢。待到生產工藝更加成熟時,必將取代如今液晶顯示器的地位,見圖 27-5。
圖 27-5 採用OLED屏幕的電視及智能手錶
無論是哪種顯示器,都有必定的參數用於描述它們的特性,各個參數介紹以下:
(1) 像素
像素是組成圖像的最基本單元要素,顯示器的像素指它成像最小的點,即前面講解液晶原理中提到的一個顯示單元。
(1) 分辨率
一些嵌入式設備的顯示器經常以「行像素值x列像素值」表示屏幕的分辨率。如分辨率800x480表示該顯示器的每一行有800個像素點,每一列有480個像素點,也可理解爲有800列,480行。
(2) 色彩深度
色彩深度指顯示器的每一個像素點能表示多少種顏色,通常用「位」(bit)來表示。如單色屏的每一個像素點能表示亮或滅兩種狀態(即實際上能顯示2種顏色),用1個數據位就能夠表示像素點的全部狀態,因此它的色彩深度爲1bit,其它常見的顯示屏色深爲16bit、24bit。
(3) 顯示器尺寸
顯示器的大小通常以英寸表示,如5英寸、21英寸、24英寸等,這個長度是指屏幕對角線的長度, 經過顯示器的對角線長度及長寬比可肯定顯示器的實際長寬尺寸。
(4) 點距
點距指兩個相鄰像素點之間的距離,它會影響畫質的細膩度及觀看距離,相同尺寸的屏幕,若分辨率越高,則點距越小,畫質越細膩。如如今有些手機的屏幕分辨率比電腦顯示器的還大,這是手機屏幕點距小的緣由;LED點陣顯示屏的點距通常都比較大,因此適合遠距離觀看。
圖 27-6是兩種適合於STM32芯片使用的顯示屏,咱們以它爲例講解控制液晶屏的原理。
圖 27-6適合STM32控制的顯示屏實物圖
這個完整的顯示屏由液晶顯示面板、電容觸摸面板以及PCB底板構成。圖中的觸摸面板帶有觸摸控制芯片,該芯片處理觸摸信號並經過引出的信號線與外部器件通信面板中間是透明的,它貼在液晶面板上面,一塊兒構成屏幕的主體,觸摸面板與液晶面板引出的排線鏈接到PCB底板上,根據實際須要,PCB底板上可能會帶有「液晶控制器芯片」。由於控制液晶面板須要比較多的資源,因此大部分低級微控制器都不能直接控制液晶面板,須要額外配套一個專用液晶控制器來處理顯示過程,外部微控制器只要把它但願顯示的數據直接交給液晶控制器便可。而不帶液晶控制器的PCB底板 ,只有小部分的電源管理電路,液晶面板的信號線與外部微控制器相連,直接控制。STM32F767系列的芯片不須要額外的液晶控制器,也就是說它把專用液晶控制器的功能集成到STM32F767芯片內部了,節約了額外的控制器成本。
本章咱們主要講解控制液晶面板(不帶控制器),液晶面板的控制信號線見表 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根,一共爲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信號線表示的數據有效。
經過上述信號線向液晶屏傳輸像素數據時,各信號線的時序見圖 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」爲高電平時,表示的數據有效,傳輸的數據會直接影響液晶屏的顯示區域。
液晶屏中的每一個像素點都是數據,在實際應用中須要把每一個像素點的數據緩存起來,再傳輸給液晶屏,這種存儲顯示數據的存儲器被稱爲顯存。顯存通常至少要能存儲液晶屏的一幀顯示數據,如分辨率爲800x480的液晶屏,使用RGB888格式顯示,它的一幀顯示數據大小爲:3x800x480=1152000字節;若使用RGB565格式顯示,一幀顯示數據大小爲:2x800x480=768000字節。
STM32F767系列芯片內部自帶一個LTDC液晶控制器,使用SDRAM的部分空間做爲顯存,可直接控制液晶面板,無需額外增長液晶控制器芯片。STM32的LTDC液晶控制器最高支持800x600分辨率的屏幕;可支持多種顏色格式,包括RGB88八、RGB56五、ARGB8888和ARGB1555等(其中的「A」是指透明像素);支持2層顯示數據混合,利用這個特性,可高效地作出背景和前景分離的顯示效果,如以視頻爲背景,在前景顯示彈幕。
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-11是LTDC控制器的結構框圖,它主要包含信號線、圖像處理單元、寄存器及時鐘信號。
圖 27-11 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 |
LTDC框圖標號‚表示的是圖像處理單元,它經過「AHB接口」獲取顯存中的數據,而後按分層把數據分別發送到兩個「層FIFO」緩存,每一個FIFO可緩存64x32位的數據,接着從緩存中獲取數據交給「PFC」(像素格式轉換器),它把數據從像素格式轉換成字(ARGB8888)的格式,再通過「混合單元」把兩層數據合併起來,最終混合獲得的是單層要顯示的數據,經過信號線輸出到液晶面板。這部分結構與DMA2D的很相似,咱們在下一小節詳細講解。
在輸出前混合單元的數據還通過一個「抖動單元」,它的做用是當像素數據格式的色深大於液晶面板實際色深時,對像素數據顏色進行舍入操做,如向18位顯示器上顯示24位數據時,抖動單元把像素數據的低6位與閾值比較,若大於閾值,則向數據的第7位進1,不然直接舍掉低6位。
框圖中標號„表示的是LTDC的控制邏輯,它包含了LTDC的各類配置和狀態寄存器。如配置與液晶面板通信時信號線的有效電平、各類時間參數、有效數據寬度、像素格式及顯存址等等,LTDC外設根據這些配置控制數據輸出,使用AHB接口從顯存地址中搬運數據到液晶面板。還有一系列用於指示當前顯示狀態和位置的狀態寄存器,經過讀取這些寄存器能夠了解LTDC的工做狀態。
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時鐘來源
在實際使用LTDC控制器控制液晶屏時,使LTDC正常工做後,往配置好的顯存地址寫入要顯示的像素數據,LTDC就會把這些數據從顯存搬運到液晶面板進行顯示,而顯示數據的容量很是大,因此咱們但願能用DMA來操做,針對這個需求,STM32專門定製了DMA2D外設,它可用於快速繪製矩形、直線、分層數據混合、數據複製以及進行圖像數據格式轉換,能夠把它理解爲圖形專用的DMA。
圖 27-13是DMA2D的結構框圖,它與前面LTDC結構裏的圖像處理單元很相似,主要爲分層FIFO、PFC及彩色混合器。
圖 27-13 DMA2D結構框圖
FG FIFO(Foreground FIFO)與BG FIFO(Backgroun FIFO)是兩個64x32位大小的緩衝區,它們用於緩存從AHB總線獲取的像素數據,分別專用於緩衝前景層和背景層的數據源。
AHB總線的數據源通常是SDRAM,也就是說在LTDC外設中配置的前景層及背景層數據源地址通常指向SDRAM的存儲空間,使用SDRAM的部分空間做爲顯存。
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 使用顏色查找表顯示圖像的過程
FIFO中的數據源通過PFC像素格式轉換器後,前景層和背景層的圖像都輸入到混合器中運算,運算公式見圖 27-15。
圖 27-15 混合公式
從公式能夠了解到混合器的運算主要是使用前景和背景的透明度做爲因子,對像素RGB顏色值進行加權運算。通過混合器後,兩層數據合成爲一層ARGB8888格式的圖像。
OUT PFC是輸出像素格式轉換器,它把混合器轉換獲得的圖像轉換成目標格式,如ARGB888八、RGB88八、RGB56五、ARGB1555或ARGB4444,具體的格式可根據須要在輸出PFC控制寄存器DMA2D_OPFCCR中選擇。
STM32F767芯片使用LTDC、DMA2D及RAM存儲器,構成了一個完整的液晶控制器。LTDC負責不斷刷新液晶屏,DMA2D用於圖像數據搬運、混合及格式轉換,RAM存儲器做爲顯存。其中顯存可使用STM32芯片內部的SRAM或外擴SDRAM/SRAM,只要容量足夠大便可(至少要能存儲一幀圖像數據)。
控制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外設根據這些配置控制時序。
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修改顯存中的數據,從而改變顯示的內容。
在實際顯示時,咱們經常採用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函數開啓數據傳輸及轉換。
本小節講解如何使用LTDC及DMA2D外設控制型號爲「STD800480」的5寸液晶屏,見圖 27-19,該液晶屏的分辨率爲800x480,支持RGB888格式。
學習本小節內容時,請打開配套的「LTDC/DMA2D—液晶顯示英文」工程配合閱讀。
圖 271-9 液晶屏實物圖
圖 27-19液晶屏背面的PCB電路對應圖 2720、圖 272一、圖 272二、圖 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開發板黑白原理圖》文檔獲知,若您使用的液晶屏或實驗板不同,請根據實際鏈接的引腳修改程序。
爲了使工程更加有條理,咱們把LCD控制相關的代碼獨立分開存儲,方便之後移植。在「FMC—讀寫SDRAM」工程的基礎上新建「bsp_lcd.c」及「bsp_lcd.h」文件,這些文件也可根據您的喜愛命名,它們不屬於STM32 HAL庫的內容,是由咱們本身根據應用須要編寫的。
(1) 初始化LTDC時鐘、DMA2D時鐘、GPIO時鐘;
(2) 初始化SDRAM,以便用做顯存;
(3) 根據液晶屏的參數配置LTDC外設的通信時序;
(4) 配置LTDC層級控制參數,配置顯存地址;
(5) 初始化DMA2D,使用DMA2D輔助顯示;
(6) 編寫測試程序,控制液晶輸出。
咱們把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引腳初始化函數,見代碼清單 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與液晶屏通信的時序參數及信號有效極性。錯誤!未找到引用源。。
代碼清單 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) 給液晶屏設定一個默認字體
在上面配置完成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結構體,包含了指向字模數據的指針以及每一個字模的像素寬度、高度,即字體的大小。本實驗的工程中提供了像素格式爲17x2四、14x20、7x十二、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搬運內存數據,加速傳輸。繪製純色直線和矩形的時候十分適合,代碼清單 27-10。
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繪製純色矩形的方法見代碼清單 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來講,繪製矩形實際上就是繪製一條很粗的直線,與繪製直線的主要區別是行偏移、行數以及每行的像素個數。
最後咱們來編寫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線鏈接開發板,編譯程序下載到實驗板,並上電覆位,液晶屏會顯示各類內容