S5PV210系列 (裸機十七)之 LCD顯示器
LCD 簡介
什麼是 LCD ?
(1) LCD ( Liquid Crystal Display )俗稱液晶.
(2)液晶是一種材料,液晶這種材料具有一種特點:可以在電訊號的驅動下液晶分子進行旋轉,旋轉時會影響透光性,因此我們可以在整個液晶面板後面用白光照(稱為背光),可以通過不同電訊號讓液晶分子進行選擇性的透光,此時在液晶面板前面看到的就是各種各樣不同的顏色,這就是 LCD 顯示。
(3)被動發光和主動發光。有些顯示器(譬如 LED 顯示器、CRT 顯示器)自己本身會發光稱為主動發光,有些( LCD )本身不會發光只會透光,需要背光的協助才能看起來是發光的,稱為被動發光。
(4)液晶應用領域:電視機、電腦顯示屏、手機顯示屏、工業顯示屏等····
其他主流顯示裝置( LED、CRT、等離子、OLED )
(1) CRT :陰極攝像管顯示器。
(2)等離子顯示:未成為主流
(3) OLED :目前未成為主流,但是很有市場潛力
(4) LED :主要用在戶外大螢幕
(5) LCD :目前是主流顯示器
LCD 的顯示原理和特點(液晶分子透光+背光)
(1)白光其實是由各種不同顏色的光組成的,所以白光被選擇性透光之後可以產生各種不同顏色的光。
LCD 的發展史和種類( TN/STN/TFT )
(1)TN最早。壞處是響應性不夠好,有拖尾現象。
(2)STN是TN的升級版。有效解決拖尾現象,顯示更清晰。
(3)TFT的最大特點就是超薄。
(4)TFT技術之上發展出來很多更新的技術。
LCD的介面技術
從電平角度來講本質上都是 TTL 訊號
(1)什麼是 TTL 介面。+5V 表示邏輯 1,0V 表示邏輯 0 .這種就叫 TTL 電平,和CMOS 電平相對比。
(2) SoC 的 LCD 控制器硬體介面是 TTL 電平的,LCD 這邊硬體介面也是 TTL 電平的。所以他們倆本來是可以直接對接的,手機、平板、開發板都是這樣直接對接的(一般用軟排線連線)。
(3) TTL 電平的缺陷就是不能傳遞太遠,如果 LCD 螢幕和主機板控制器太遠( 1 米甚至更遠)就不能直接 TTL 連線了,要進行轉換。轉換方式:主機 SoC(TTL) ->VGA-> LCD 螢幕( TTL )
RGB介面詳解(參考資料手冊P1207頁時序圖)
(1)VD[23:0]:24 根資料線,用來傳輸影象資訊。可見 LCD 是並行介面,速率才夠快。
(2)HSYNC ( 水平同步訊號 )
(3)VSYNC(垂直同步訊號):時序訊號線,為了讓 LCD 能夠正常顯示給的控制訊號
(4)VCLK(畫素時鐘):LCD 工作時需要主機板控制器給 LCD 模組一個工作時鐘訊號,就是 VCLK。
(5)VDEN (資料有效標誌):時序訊號,和 HSYNC、VSYNC 結合使用。
(6)LEND(行結束標誌,不是必須的):時序訊號,非必須,譬如 X210 介面就沒有。
LCD如何顯示影象
畫素(pixel)
(1)畫素就是組成影象的最基本元素,或者說顯示中可以被控制的最小單位,整個影象就是由很多個畫素組成的。
(2)畫素可以被單獨控制,或控制其亮或不亮(單色屏)、或控制其亮度強弱(譬如亮 50%,35%,這樣叫灰度屏,以前的黑白電視機)、或控制其顯示一定的顏色(這就是我們現在最常用的彩色顯示屏)。
總結:
畫素很重要,整個顯示影象是由一個個的畫素組成的。我們要在顯示器上顯示一個影象,就是把這個影象離散化成一個一個的點,然後把各個點的顏色對應在顯示器的畫素上。
掃描
(1)掃描是一個動作而不是一個名字,掃描就是依次將顏色數值放入螢幕中所有的畫素的這個過程。
(2)掃描這個詞是由最早的 CRT 顯示器遺留下來的,到 LCD 顯示器的年代本來已經失去意義了,但是我們還是延續著這麼叫。
(3)顯示器的掃描顯示原理依賴於人眼的視覺暫留。只要顯示器掃描頻率大於人眼的發現頻率,人眼看到的影象就是恆定的。如果掃描頻率偏小人眼就會看到閃動。(掃描頻率的概念就叫做重新整理率)
驅動器&控制器
(1) LCD 驅動器一般和 LCD 顯示面板整合在一起(本來是分開的,做面板的是隻做面板的,譬如說三星、LG、臺灣的友達、奇美都是做面板的;驅動器也由專門的 IC 廠商生產;整合廠商買來面板和驅動器後集成在一起做成 LCD 螢幕),面板只負責裡面的液晶分子旋轉透光,面板需要一定的模擬電訊號來控制液晶分子;LCD 驅動器晶片負責給面板提供控制液晶分子的模擬電訊號,驅動器的控制訊號(數字訊號)來自於自己的數字介面,這個介面就是 LCD 螢幕的外部介面(第二節中講到的介面)
(2) LCD 控制器一般整合在 SoC 內部,他負責通過數字介面向遠端的 LCD 驅動器提供控制畫素顯示的數字訊號。LCD 控制器的關鍵在於時序,它必須按照一定的時序和 LCD 驅動器通訊;LCD 控制器受 SoC 控制,SoC 會從記憶體中拿畫素資料給 LCD 控制器並最終傳給 LCD 驅動器。
顯示記憶體(簡稱:視訊記憶體)
(1) SoC 在記憶體中挑選一段記憶體(一般來說是程式設計師隨便挑選的,但是挑選的時候必須符合一定規矩),然後通過配置將 LCD 控制器和這一段記憶體(以後稱為視訊記憶體)連線起來構成一個對映關係。一旦這個關係建立之後,LCD 控制器就會自動從視訊記憶體中讀取畫素資料傳輸給 LCD 驅動器。這個顯示的過程不需要 CPU 的參與。
(2)顯示體系建立起來後,CPU 就不用再管 LCD 控制器、驅動器、面板這些東西了;以後 CPU 就只關心視訊記憶體了,因為我只要把要顯示的影象的畫素資料丟到視訊記憶體中,硬體就會自動響應(螢幕上就能自動看到顯示的影象了)。
總結:LCD 顯示是分為 2 個階段的:第一個階段就是建立顯示體系的過程,目的就是 CPU 初始化 LCD 控制器使其和視訊記憶體聯絡起來構成對映;第二個階段就是對映建立之後,此階段主要任務是將要顯示的影象丟到視訊記憶體中去。
LCD的六個主要時序引數
LCD 顯示單位:幀( frame )
(1)顯示器上一整個畫面的內容成為一個幀 ( frame ),整個顯示器工作時是一幀一幀的在顯示。
(2)電影實際就是以每秒種 24 幀的速度在播放圖片。
(3)幀內資料:一幀分為多行,一行分為多畫素,因此一幀影象其實就是多個畫素組成的矩陣。
(4)幀外資料:整個視訊由很多個幀構成,最終播放視訊時逐個播放各個影象幀即可。
LCD 顯示一幀影象的過程
(1)首先把幀分為行,然後再把行分為畫素,然後逐個畫素去顯示。(顯示畫素:其實就是 LCD 驅動器按照接收到的 LCD 控制器給的顯示資料,驅動一個畫素的液晶分子旋轉,讓這個畫素顯示出相應的顏色值的過程)
(2)關鍵點:LCD 控制器和驅動器之間一次只能傳一個畫素點的顯示資料。所以一幀影象在螢幕上其實是序列的依次被顯示上去的,不是同一時間顯示出來的。
為了向前相容出現的六個時序引數
HSPW 水平同步訊號脈寬
HBPD 水平同步訊號前肩
HFPD 水平同步訊號後肩
VSPW 垂直同步訊號脈寬
VBPD 垂直同步訊號前肩
VFPD 垂直同步訊號後肩
(1)一行的通訊過程是這樣的:LCD 控制器先發送一個 HSYNC 高電平脈衝(脈衝寬度是 HSPW ),脈衝告訴驅動器下面的資訊是一行資訊。然後開始這一行資訊,這一行資訊包括 3 部分:HBPD + 有效行資訊 + HFPD。其中前肩和後肩都屬於時序資訊(和 LCD 螢幕具體有關),有效行資訊就是橫向解析度。所以你可以認為一行總共包含 4 部分:HSPW + HBPD + 有效行資訊 + HFPD。
(2)一幀影象其實就是一列,一列影象由多個行組成,每行都是上面講的這個時序。
(3)一幀影象的通訊過程是這樣的:整個幀影象訊號分為4部分:VSPW + VBPD + 幀有效訊號 + VFPD。VSPW 是幀同步訊號寬度,用來告訴驅動器一幀影象要開始了;VBPD 和 VFPD 分別是垂直同步訊號前後肩。
(4)必須說明:這 6 個引數對於 LCD 顯示器其實本來是沒用的,這些訊號其實是老式的 CRT 顯示器才需要的,LCD 本身不需要,但是出於歷史相容性要求,LCD 選擇了相容 CRT 顯示器的這些時序要求,所以理解 LCD 顯示器時序和程式設計時,用 CRT 的方式來理解不會錯。
(5)要注意,這幾個時序引數本身是 LCD 螢幕本身的引數,與 LCD 控制器無關。所以同一個主機板如果接的螢幕不一樣則時序引數設定也會不同。這些引數的來源一般是:第一,廠家會直接給出,一般以例項程式碼的形式給出;第二,來自於 LCD 的資料手冊。
第一種方式,檢視九鼎的 210 裸機教程(x210v3裸機開發教程\src\template-framebuffer-font\source\hardware\s5pv210-fb.c的第774行)
.h_fp = 210, // 160-210-354
.h_bp = 38, // 46
.h_sw = 10, // 1-40
.v_fp = 22, // 7-22-147
.v_fpe = 1,
.v_bp = 18, // 23
.v_bpe = 1,
.v_sw = 7, // 1-20
第二種方式,檢視 LCD 資料手冊( X210 光碟資料 \A盤\DataSheet\AT070TN92.pdf)
補充:
1、注意這些數字的單位。H 開頭的三個單位都是 DCLK (畫素時鐘), V 開頭的三個單位是 TH 。這樣設定的好處是我們改變了畫素時鐘的設定時,不用改變這裡的時序引數。
2、這些時序引數如果沒設定好會影響什麼?螢幕會跑偏。
LCD顯示的主要相關概念
畫素( pixel )
(1)整個影象是由一個個的畫素組成的,畫素就是一個顯示點。
畫素間距( pitch )
(1) pitch 是連續 2 個畫素的畫素中心的距離。一般的畫素是方形的,所以橫向 pitch 和縱向的 pitch 一樣的。但是也有不一樣的。
(2)畫素間距會影響螢幕的最佳觀看距離。畫素間距大的適合遠距離看,畫素間距小的適合近距離看。
解析度( resolution )
(1)整個螢幕的橫向和縱向的畫素個數就叫解析度,譬如 X210 開發板用的螢幕是 800×480.
(2)螢幕尺寸和解析度無關的,像開發板的螢幕尺寸是 7 寸的(純螢幕對角線尺寸是 7 英寸)。
(3)螢幕尺寸和解析度和畫素間距三者之間有關聯。
清晰度
(1)清晰度是一個主觀概念,是人眼對顯示效果的一個主觀判斷。說白了就是人看起來感覺清晰不清晰。
(2)客觀來講,清晰度由解析度和畫素間距共同決定。一般的,螢幕尺寸固定時解析度越高越清晰,解析度越低就越不清晰;解析度固定下,螢幕尺寸越小越清晰,越大越不清晰。
(3)清晰度還由其他很多因素共同決定。
畫素深度( bits per pixel,簡稱 bpp )
(1)一個畫素在計算機中由多少個位元組資料來描述。
(2)計算機中用二進位制位來表示一個畫素的資料,用來表示一個畫素的資料位越多,則這個畫素的顏色值更加豐富、分的更細,顏色深度就更深。
(3)一般來說畫素深度有這麼幾種:1位、8位、16位、24位、32位。
顏色在計算機中的表示
顏色的本質
(1)顏色是主觀存在,顏色其實是自然光在人的眼睛中和大腦中產生的一種映像。
(2)顏色的本質決定於光的波長。
自然光的顏色是連續的
(1)光的波長是連續的,導致顏色也是連續的。理論上,只要你的眼睛分辨能力足夠好,可以在自然界中發現無數種顏色。
計算機中的顏色是離散的
(1)計算機中不可能儲存無數種顏色,所以必須將顏色有限化,所以就用有限種顏色來代表自然界中的無限種顏色。這個理論非常類似於之前學過的 AD 轉換。
(2)這種離散化表達顏色的缺點是不夠真實,漏掉了很多種顏色。因此計算機中所能表達的顏色沒有自然界中豐富(計算機螢幕上顯示的影象和真實影象有差別)
(3)計算機所能表達的顏色種類個數,這個引數叫:畫素深度 bpp。
常見畫素深度:1位、8位、16位、24位、32位
1位:用 1 個二進位制位來表示顏色,這種就叫單色顯示。示例就是小飯店、理髮店門口的 LED 屏。
8位:用 8 個二進位制位來表示顏色,此時能表示 256 種顏色。這種叫灰度顯示。這時候是黑白的,沒有彩色,我們把純白到純黑分別對應 255 到 0,中間的數值對應不同的灰。示例就是以前的黑白電視機。
16 位:用 16 個二進位制位表示顏色,此時能表示 65536 種顏色。這時候就可以彩色顯示了,一般是 RGB565 的顏色分佈(用 5 位二進位制表示紅色、用 6 位二進位制表示綠色、用 5 位二進位制表示藍色)。這種紅綠藍都有的顏色表示法就是一種模擬自然界中所有顏色的表示方式。但是因為 RGB 的顏色表達本身二進位制位數不夠多(導致紅綠藍三種顏色本身分的都不夠細緻),所以這樣顯示的彩色失真比較重,人眼能明顯看到顯示的不真實。
24 位:用 24 個二進位制位來表示顏色,此時能表示 16777216 種顏色。這種表示方式和 16 位色原理是一樣的,只是 RG B三種顏色各自的精度都更高了( RGB 各 8 位),叫 RGB888。此時顏色比 RGB565 更加真實細膩,雖然說比自然界無數種顏色還是少了很多,不過由於人眼的不理想性所以人眼幾乎不能區分 1677萬種顏色和無數種顏色的差別了。於是乎就把這種 RGB888 的表示方法叫做真彩色。(RGB565就是假彩色)
32位:總共用32位二進位制來表示顏色,其中 24 位表示紅綠藍三元色(還是RGB888分佈),剩下8位表示透明度。這種顯色方式就叫 ARGB(A是阿爾法,表示透明度),現在PC機中一般都用ARGB表示顏色。
補充:顏色的組成,三元色(三基色)是RGB,也就是說所有的顏色都可以由紅綠藍三種顏色組成。
S5PV210的LCD控制器
FIMD結構框圖
(1) 210 的 LCD 控制器叫 FIMD,FIMD 是 210 內部和影象處理相關的一些部件,在攝像頭等和影象處理有關的部分都可以有關聯。
(2) FIMD 在內部與 AHB 匯流排等相連線,在外部提供 RGB 介面、I80 介面、YUV 介面與外部相連線,我們實際使用的是 RGB 介面。
虛擬螢幕疊加(資料手冊 P1194)
(1)虛擬螢幕的意思是,我們平時看到的螢幕上顯示出來的場景實際是很多個螢幕顯示疊加在一起的效果(譬如新聞影象、電視臺臺標、下方飄動的字幕新聞)
(2)像 S5PV210 的 LCD 控制器中有 5 個虛擬螢幕 Window0 到 Window4,虛擬螢幕不存在於真實而存在於記憶體中。(之前講過,LCd 顯示時實際是顯示的是對應的記憶體中的視訊記憶體區域的數值)虛擬螢幕其實就是一個記憶體中的視訊記憶體區域,有幾個視訊記憶體區域就有幾個虛擬螢幕,但是這些虛擬螢幕都被對映到一個真實的顯示屏上面,所以將來真實的現實效果實際是這幾個虛擬螢幕的顯示內容的疊加。(疊加時要注意上面一層會覆蓋下面一層,所以要注意誰在前誰在後,設定暫存器時有這個選項)
(3)使用虛擬螢幕而不是整個 LCD 使用一個視訊記憶體是有一定好處的:第一,可以保證不汙染源影象,方便程式處理;第二,可以減少螢幕重新整理,提高顯示效率,減少 CPU 工作量。
虛擬顯示(資料手冊 P1206 )
(1)如何實現在小解析度的螢幕上(真實)顯示大解析度的影象
(2)細節上,我們需要螢幕上看到不同影象時,需要對視訊記憶體區域進行重新整理。即使我們只需要螢幕顯示移動一點點,整個螢幕對應的視訊記憶體空間也需要整個重新重新整理,工作量和完全重新顯示一幅影象是一樣的。這個顯然不好,這樣 CPU 重新整理螢幕的工作量太大了,效率很低。
(3)如何能夠在顯示一個大圖片的不同區域時讓 CPU 重新整理螢幕工作量減少?有,方法就是虛擬顯示。具體做法就是在記憶體中建立顯示快取的時候實際建立一個很大的區域,然後讓 LCD 去對應其中的一部分割槽域作為有效的顯示區域。將來要顯示大影象時,直接將大影象全部一次性載入入顯示快取區,然後通過移動有效顯示區域就可以顯示大影象的不同區域了。
LCD 程式設計實戰 - LCD 控制器初始化
(1)要想 LCD 工作,必須給 LCD 螢幕和視訊記憶體之間建立一個對映(對映是在 CPU 初始化 LCD 控制器來完成的)。本部分就是在完成這個過程(這也是 LCD 顯示的 2 個階段的第一階段,第二階段中我們只需要給視訊記憶體中丟入相應的資料,LCD 螢幕就會自動顯示相應內容)
#include "main.h"
#define GPF0CON (*(volatile unsigned long *)0xE0200120)
#define GPF1CON (*(volatile unsigned long *)0xE0200140)
#define GPF2CON (*(volatile unsigned long *)0xE0200160)
#define GPF3CON (*(volatile unsigned long *)0xE0200180)
#define GPD0CON (*(volatile unsigned long *)0xE02000A0)
#define GPD0DAT (*(volatile unsigned long *)0xE02000A4)
#define CLK_SRC1 (*(volatile unsigned long *)0xe0100204)
#define CLK_DIV1 (*(volatile unsigned long *)0xe0100304)
#define DISPLAY_CONTROL (*(volatile unsigned long *)0xe0107008)
#define VIDCON0 (*(volatile unsigned long *)0xF8000000)
#define VIDCON1 (*(volatile unsigned long *)0xF8000004)
#define VIDTCON2 (*(volatile unsigned long *)0xF8000018)
#define WINCON0 (*(volatile unsigned long *)0xF8000020)
#define WINCON2 (*(volatile unsigned long *)0xF8000028)
#define SHADOWCON (*(volatile unsigned long *)0xF8000034)
#define VIDOSD0A (*(volatile unsigned long *)0xF8000040)
#define VIDOSD0B (*(volatile unsigned long *)0xF8000044)
#define VIDOSD0C (*(volatile unsigned long *)0xF8000048)
#define VIDW00ADD0B0 (*(volatile unsigned long *)0xF80000A0)
#define VIDW00ADD1B0 (*(volatile unsigned long *)0xF80000D0)
#define VIDTCON0 (*(volatile unsigned long *)0xF8000010)
#define VIDTCON1 (*(volatile unsigned long *)0xF8000014)
#define HSPW (40) // 1~40 DCLK
#define HBPD (10 - 1) // 46
#define HFPD (240 - 1) // 16 210 354
#define VSPW (20) // 1~20 DCLK
#define VBPD (10 - 1) // 23
#define VFPD (30 - 1) // 7 22 147
// FB地址
#define FB_ADDR (0x23000000)
#define ROW (480)
#define COL (800)
#define HOZVAL (COL-1)
#define LINEVAL (ROW-1)
#define XSIZE COL
#define YSIZE ROW
// 初始化LCD
void lcd_init(void)
{
// 配置引腳用於LCD功能
GPF0CON = 0x22222222;
GPF1CON = 0x22222222;
GPF2CON = 0x22222222;
GPF3CON = 0x22222222;
// 開啟背光 GPD0_0(PWMTOUT0)
GPD0CON &= ~(0xf<<0);
GPD0CON |= (1<<0); // output mode
GPD0DAT &= ~(1<<0); // output 0 to enable backlight
// 10: RGB=FIMD I80=FIMD ITU=FIMD
DISPLAY_CONTROL = 2<<0;
// bit[26~28]:使用RGB介面
// bit[18]:RGB 並行
// bit[2]:選擇時鐘源為HCLK_DSYS=166MHz
VIDCON0 &= ~( (3<<26)|(1<<18)|(1<<2) );
// bit[1]:使能lcd控制器
// bit[0]:當前幀結束後使能lcd控制器
VIDCON0 |= ( (1<<0)|(1<<1) );
// bit[6]:選擇需要分頻
// bit[6~13]:分頻係數為5,即VCLK = 166M/(4+1) = 33M
VIDCON0 |= 4<<6 | 1<<4;
// H43-HSD043I9W1.pdf(p13) 時序圖:VSYNC和HSYNC都是低脈衝
// s5pv210晶片手冊(p1207) 時序圖:VSYNC和HSYNC都是高脈衝有效,所以需要反轉
VIDCON1 |= 1<<5 | 1<<6;
// 設定時序
VIDTCON0 = VBPD<<16 | VFPD<<8 | VSPW<<0;
VIDTCON1 = HBPD<<16 | HFPD<<8 | HSPW<<0;
// 設定長寬(物理螢幕)
VIDTCON2 = (LINEVAL << 11) | (HOZVAL << 0);
// 設定window0
// bit[0]:使能
// bit[2~5]:24bpp(RGB888)
WINCON0 |= 1<<0;
WINCON0 &= ~(0xf << 2);
WINCON0 |= (0xB<<2) | (1<<15);
#define LeftTopX 0
#define LeftTopY 0
#define RightBotX 799
#define RightBotY 479
// 設定window0的上下左右
// 設定的是視訊記憶體空間的大小
VIDOSD0A = (LeftTopX<<11) | (LeftTopY << 0);
VIDOSD0B = (RightBotX<<11) | (RightBotY << 0);
VIDOSD0C = (LINEVAL + 1) * (HOZVAL + 1);
// 設定fb的地址
VIDW00ADD0B0 = FB_ADDR;
VIDW00ADD1B0 = (((HOZVAL + 1)*4 + 0) * (LINEVAL + 1)) & (0xffffff);
// 使能channel 0傳輸資料
SHADOWCON = 0x1;
}
LCD 程式設計實戰 - 顯示畫素&刷背景
// 在畫素點(x, y)處填充為color顏色
static inline void lcd_draw_pixel(u32 x, u32 y, u32 color)
{
*(pfb + COL * y + x) = color;
}
// 把整個螢幕全部填充成一個顏色color
static void lcd_draw_background(u32 color)
{
u32 i, j;
for (j=0; j<ROW; j++)
{
for (i=0; i<COL; i++)
{
lcd_draw_pixel(i, j, color);
}
}
}
static void delay(void)
{
volatile u32 i, j;
for (i=0; i<4000; i++)
for (j=0; j<1000; j++);
}
void lcd_test(void)
{
lcd_init();
while (1)
{
lcd_draw_background(RED);
delay();
lcd_draw_background(GREEN);
delay();
lcd_draw_background(BLUE);
delay();
}
LCD 程式設計實戰 - 橫線豎線斜線&畫圓
// 繪製橫線,起始座標為(x1, y)到(x2, y),顏色是color
static void lcd_draw_hline(u32 x1, u32 x2, u32 y, u32 color)
{
u32 x;
for (x = x1; x<x2; x++)
{
lcd_draw_pixel(x, y, color);
}
}
// 繪製豎線,起始座標為(x, y1)到(x, y2),顏色是color
static void lcd_draw_vline(u32 x, u32 y1, u32 y2, u32 color)
{
u32 y;
for (y = y1; y<y2; y++)
{
lcd_draw_pixel(x, y, color);
}
}
/ glib庫中的畫線函式,可以畫斜線,線兩端分別是(x1, y1)和(x2, y2)
void glib_line(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, unsigned int color)
{
int dx,dy,e;
dx=x2-x1;
dy=y2-y1;
if(dx>=0)
{
if(dy >= 0) // dy>=0
{
if(dx>=dy) // 1/8 octant
{
e=dy-dx/2;
while(x1<=x2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){y1+=1;e-=dx;}
x1+=1;
e+=dy;
}
}
else // 2/8 octant
{
e=dx-dy/2;
while(y1<=y2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){x1+=1;e-=dy;}
y1+=1;
e+=dx;
}
}
}
else // dy<0
{
dy=-dy; // dy=abs(dy)
if(dx>=dy) // 8/8 octant
{
e=dy-dx/2;
while(x1<=x2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){y1-=1;e-=dx;}
x1+=1;
e+=dy;
}
}
else // 7/8 octant
{
e=dx-dy/2;
while(y1>=y2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){x1+=1;e-=dy;}
y1-=1;
e+=dx;
}
}
}
}
else //dx<0
{
dx=-dx; //dx=abs(dx)
if(dy >= 0) // dy>=0
{
if(dx>=dy) // 4/8 octant
{
e=dy-dx/2;
while(x1>=x2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){y1+=1;e-=dx;}
x1-=1;
e+=dy;
}
}
else // 3/8 octant
{
e=dx-dy/2;
while(y1<=y2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){x1-=1;e-=dy;}
y1+=1;
e+=dx;
}
}
}
else // dy<0
{
dy=-dy; // dy=abs(dy)
if(dx>=dy) // 5/8 octant
{
e=dy-dx/2;
while(x1>=x2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){y1-=1;e-=dx;}
x1-=1;
e+=dy;
}
}
else // 6/8 octant
{
e=dx-dy/2;
while(y1>=y2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){x1-=1;e-=dy;}
y1-=1;
e+=dx;
}
}
}
}
}
//畫圓函式,圓心座標是(centerX, centerY),半徑是radius,圓的顏色是color
void draw_circular(unsigned int centerX, unsigned int centerY, unsigned int radius, unsigned int color)
{
int x,y ;
int tempX,tempY;;
int SquareOfR = radius*radius;
for(y=0; y<XSIZE; y++)
{
for(x=0; x<YSIZE; x++)
{
if(y<=centerY && x<=centerX)
{
tempY=centerY-y;
tempX=centerX-x;
}
else if(y<=centerY&& x>=centerX)
{
tempY=centerY-y;
tempX=x-centerX;
}
else if(y>=centerY&& x<=centerX)
{
tempY=y-centerY;
tempX=centerX-x;
}
else
{
tempY = y-centerY;
tempX = x-centerX;
}
if ((tempY*tempY+tempX*tempX)<=SquareOfR)
lcd_draw_pixel(x, y, color);
}
}
}
LCD 程式設計實戰 - 寫英文中文字元
// 寫字
// 寫字的左上角座標(x, y),字的顏色是color,字的字模資訊儲存在data中
static void show_8_16(unsigned int x, unsigned int y, unsigned int color, unsigned char *data)
{
// count記錄當前正在繪製的畫素的次序
int i, j, count = 0;
for (j=y; j<(y+16); j++)
{
for (i=x; i<(x+8); i++)
{
if (i<XSIZE && j<YSIZE)
{
// 在座標(i, j)這個畫素處判斷是0還是1,如果是1寫color;如果是0直接跳過
if (data[count/8] & (1<<(count%8)))
lcd_draw_pixel(i, j, color);
}
count++;
}
}
}
// 寫字串
// 字串起始座標左上角為(x, y),字串文字顏色是color,字串內容為str
void draw_ascii_ok(unsigned int x, unsigned int y, unsigned int color, unsigned char *str)
{
int i;
unsigned char *ch;
for (i=0; str[i]!='\0'; i++)
{
ch = (unsigned char *)ascii_8_16[(unsigned char)str[i]-0x20];
show_8_16(x, y, color, ch);
x += 8;
if (x >= XSIZE)
{
x -= XSIZE; // 回車
y += 16; // 換行
}
}
}
// 畫800×480的圖,影象資料儲存在pData所指向的陣列中
void lcd_draw_picture(const unsigned char *pData)
{
u32 x, y, color, p = 0;
for (y=0; y<480; y++)
{
for (x=0; x<800; x++)
{
// 在這裡將座標點(x, y)的那個畫素填充上相應的顏色值即可
color = (pData[p+0] << 0) | (pData[p+1] << 8) | (pData[p+2] << 16);
lcd_draw_pixel(x, y, color);
p += 3;
}
}
}
LCD 程式設計實戰 - 畫圖
圖片顯示分析
(1)影象是彩色的,而之前的文字、圖形都是單色的。之前的圖形文字繪製函式都有個color引數,就是傳給視訊記憶體告訴它這個畫素的顯示顏色。
(2)一副解析度是800×480,BPP是24的圖片,實際上就是800×480×3位元組的資料。將來寫程式碼將圖片顯示到LCD中時,圖片將會以 unsigned char pic_data[800×480×3]的形式出現。
#include "main.h"
#include "ascii.h"
#include "800480.h"
#define GPF0CON (*(volatile unsigned long *)0xE0200120)
#define GPF1CON (*(volatile unsigned long *)0xE0200140)
#define GPF2CON (*(volatile unsigned long *)0xE0200160)
#define GPF3CON (*(volatile unsigned long *)0xE0200180)
#define GPD0CON (*(volatile unsigned long *)0xE02000A0)
#define GPD0DAT (*(volatile unsigned long *)0xE02000A4)
#define CLK_SRC1 (*(volatile unsigned long *)0xe0100204)
#define CLK_DIV1 (*(volatile unsigned long *)0xe0100304)
#define DISPLAY_CONTROL (*(volatile unsigned long *)0xe0107008)
#define VIDCON0 (*(volatile unsigned long *)0xF8000000)
#define VIDCON1 (*(volatile unsigned long *)0xF8000004)
#define VIDTCON2 (*(volatile unsigned long *)0xF8000018)
#define WINCON0 (*(volatile unsigned long *)0xF8000020)
#define WINCON2 (*(volatile unsigned long *)0xF8000028)
#define SHADOWCON (*(volatile unsigned long *)0xF8000034)
#define VIDOSD0A (*(volatile unsigned long *)0xF8000040)
#define VIDOSD0B (*(volatile unsigned long *)0xF8000044)
#define VIDOSD0C (*(volatile unsigned long *)0xF8000048)
#define VIDW00ADD0B0 (*(volatile unsigned long *)0xF80000A0)
#define VIDW00ADD1B0 (*(volatile unsigned long *)0xF80000D0)
#define VIDTCON0 (*(volatile unsigned long *)0xF8000010)
#define VIDTCON1 (*(volatile unsigned long *)0xF8000014)
#define HSPW (40) // 1~40 DCLK
#define HBPD (10 - 1) // 46
#define HFPD (240 - 1) // 16 210 354
#define VSPW (20) // 1~20 DCLK
#define VBPD (10 - 1) // 23
#define VFPD (30 - 1) // 7 22 147
// FB地址
#define FB_ADDR (0x23000000)
#define ROW (480)
#define COL (800)
#define HOZVAL (COL-1)
#define LINEVAL (ROW-1)
#define XSIZE COL
#define YSIZE ROW
u32 *pfb = (u32 *)FB_ADDR;
// 常用顏色定義
#define BLUE 0x0000FF
#define RED 0xFF0000
#define GREEN 0x00FF00
#define WHITE 0xFFFFFF
// 初始化LCD
static void lcd_init(void)
{
// 配置引腳用於LCD功能
GPF0CON = 0x22222222;
GPF1CON = 0x22222222;
GPF2CON = 0x22222222;
GPF3CON = 0x22222222;
// 開啟背光 GPD0_0(PWMTOUT0)
GPD0CON &= ~(0xf<<0);
GPD0CON |= (1<<0); // output mode
GPD0DAT &= ~(1<<0); // output 0 to enable backlight
// 10: RGB=FIMD I80=FIMD ITU=FIMD
DISPLAY_CONTROL = 2<<0;
// bit[26~28]:使用RGB介面
// bit[18]:RGB 並行
// bit[2]:選擇時鐘源為HCLK_DSYS=166MHz
VIDCON0 &= ~( (3<<26)|(1<<18)|(1<<2) );
// bit[1]:使能lcd控制器
// bit[0]:當前幀結束後使能lcd控制器
VIDCON0 |= ( (1<<0)|(1<<1) );
// bit[6]:選擇需要分頻
// bit[6~13]:分頻係數為5,即VCLK = 166M/(4+1) = 33M
VIDCON0 |= 4<<6 | 1<<4;
// H43-HSD043I9W1.pdf(p13) 時序圖:VSYNC和HSYNC都是低脈衝
// s5pv210晶片手冊(p1207) 時序圖:VSYNC和HSYNC都是高脈衝有效,所以需要反轉
VIDCON1 |= 1<<5 | 1<<6;
// 設定時序
VIDTCON0 = VBPD<<16 | VFPD<<8 | VSPW<<0;
VIDTCON1 = HBPD<<16 | HFPD<<8 | HSPW<<0;
// 設定長寬(物理螢幕)
VIDTCON2 = (LINEVAL << 11) | (HOZVAL << 0);
// 設定window0
// bit[0]:使能
// bit[2~5]:24bpp(RGB888)
WINCON0 |= 1<<0;
WINCON0 &= ~(0xf << 2);
WINCON0 |= (0xB<<2) | (1<<15);
#define LeftTopX 0
#define LeftTopY 0
#define RightBotX 799
#define RightBotY 479
// 設定window0的上下左右
// 設定的是視訊記憶體空間的大小
VIDOSD0A = (LeftTopX<<11) | (LeftTopY << 0);
VIDOSD0B = (RightBotX<<11) | (RightBotY << 0);
VIDOSD0C = (LINEVAL + 1) * (HOZVAL + 1);
// 設定fb的地址
VIDW00ADD0B0 = FB_ADDR;
VIDW00ADD1B0 = (((HOZVAL + 1)*4 + 0) * (LINEVAL + 1)) & (0xffffff);
// 使能channel 0傳輸資料
SHADOWCON = 0x1;
}
// 在畫素點(x, y)處填充為color顏色
static inline void lcd_draw_pixel(u32 x, u32 y, u32 color)
{
*(pfb + COL * y + x) = color;
}
// 把整個螢幕全部填充成一個顏色color
static void lcd_draw_background(u32 color)
{
u32 i, j;
for (j=0; j<ROW; j++)
{
for (i=0; i<COL; i++)
{
lcd_draw_pixel(i, j, color);
}
}
}
static void delay(void)
{
volatile u32 i, j;
for (i=0; i<4000; i++)
for (j=0; j<1000; j++);
}
// 繪製橫線,起始座標為(x1, y)到(x2, y),顏色是color
static void lcd_draw_hline(u32 x1, u32 x2, u32 y, u32 color)
{
u32 x;
for (x = x1; x<x2; x++)
{
lcd_draw_pixel(x, y, color);
}
}
// 繪製豎線,起始座標為(x, y1)到(x, y2),顏色是color
static void lcd_draw_vline(u32 x, u32 y1, u32 y2, u32 color)
{
u32 y;
for (y = y1; y<y2; y++)
{
lcd_draw_pixel(x, y, color);
}
}
// glib庫中的畫線函式,可以畫斜線,線兩端分別是(x1, y1)和(x2, y2)
void glib_line(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, unsigned int color)
{
int dx,dy,e;
dx=x2-x1;
dy=y2-y1;
if(dx>=0)
{
if(dy >= 0) // dy>=0
{
if(dx>=dy) // 1/8 octant
{
e=dy-dx/2;
while(x1<=x2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){y1+=1;e-=dx;}
x1+=1;
e+=dy;
}
}
else // 2/8 octant
{
e=dx-dy/2;
while(y1<=y2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){x1+=1;e-=dy;}
y1+=1;
e+=dx;
}
}
}
else // dy<0
{
dy=-dy; // dy=abs(dy)
if(dx>=dy) // 8/8 octant
{
e=dy-dx/2;
while(x1<=x2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){y1-=1;e-=dx;}
x1+=1;
e+=dy;
}
}
else // 7/8 octant
{
e=dx-dy/2;
while(y1>=y2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){x1+=1;e-=dy;}
y1-=1;
e+=dx;
}
}
}
}
else //dx<0
{
dx=-dx; //dx=abs(dx)
if(dy >= 0) // dy>=0
{
if(dx>=dy) // 4/8 octant
{
e=dy-dx/2;
while(x1>=x2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){y1+=1;e-=dx;}
x1-=1;
e+=dy;
}
}
else // 3/8 octant
{
e=dx-dy/2;
while(y1<=y2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){x1-=1;e-=dy;}
y1+=1;
e+=dx;
}
}
}
else // dy<0
{
dy=-dy; // dy=abs(dy)
if(dx>=dy) // 5/8 octant
{
e=dy-dx/2;
while(x1>=x2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){y1-=1;e-=dx;}
x1-=1;
e+=dy;
}
}
else // 6/8 octant
{
e=dx-dy/2;
while(y1>=y2)
{
lcd_draw_pixel(x1,y1,color);
if(e>0){x1-=1;e-=dy;}
y1-=1;
e+=dx;
}
}
}
}
}
//畫圓函式,圓心座標是(centerX, centerY),半徑是radius,圓的顏色是color
void draw_circular(unsigned int centerX, unsigned int centerY, unsigned int radius, unsigned int color)
{
int x,y ;
int tempX,tempY;;
int SquareOfR = radius*radius;
for(y=0; y<XSIZE; y++)
{
for(x=0; x<YSIZE; x++)
{
if(y<=centerY && x<=centerX)
{
tempY=centerY-y;
tempX=centerX-x;
}
else if(y<=centerY&& x>=centerX)
{
tempY=centerY-y;
tempX=x-centerX;
}
else if(y>=centerY&& x<=centerX)
{
tempY=y-centerY;
tempX=centerX-x;
}
else
{
tempY = y-centerY;
tempX = x-centerX;
}
if ((tempY*tempY+tempX*tempX)<=SquareOfR)
lcd_draw_pixel(x, y, color);
}
}
}
// 寫字
// 寫字的左上角座標(x, y),字的顏色是color,字的字模資訊儲存在data中
static void show_8_16(unsigned int x, unsigned int y, unsigned int color, unsigned char *data)
{
// count記錄當前正在繪製的畫素的次序
int i, j, count = 0;
for (j=y; j<(y+16); j++)
{
for (i=x; i<(x+8); i++)
{
if (i<XSIZE && j<YSIZE)
{
// 在座標(i, j)這個畫素處判斷是0還是1,如果是1寫color;如果是0直接跳過
if (data[count/8] & (1<<(count%8)))
lcd_draw_pixel(i, j, color);