1. 程式人生 > >【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗二十七:TFT模組

【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗二十七:TFT模組

實驗二十七:TFT模組 - 顯示

所謂TFT(Thin Film Transistor)就是眾多LCD當中,其中一種支援顏色的LCD,相較古老的點陣LCD(12864笑),它可謂高階了。黑金的TFT LCD除了320×240大小以外,內建SSD1289控制器,同時也是獨立模組。事實上,無論是驅動點陣LCD還是TFT LCD,結果都是配置裡邊的控制器,差別就在於控制器的複雜程度而已。不管怎麼樣,如果只是單純地顯示內容,SSD1289控制器也不會難道那裡去。

未進入主題之前,請容許筆者補足一下迴圈操作的內容。首先筆者必須強調,迴圈操作與迴圈操作模式本身是不同性質的東西,操作模式是為了優化操作才存在這個世界上,例如空間換時鐘,或者時鐘換空間等優化傾向。反之,迴圈操作類似關鍵字for,while,do ... while等程式碼意義上的重複執行。

筆者曾在實驗二十一說過,順序語言for,while,do ... while等的鍵字,它們的作用主要是簡化程式碼的重複性。不過很遺憾的是,這些關鍵字並不怎麼喜歡描述語言,所以迴圈操作一直是描述語言的痛。對此,筆者才慫恿描述語言,先模仿順序操作,再模仿迴圈操作。這種操縱它人的背德感,筆者無比滿足與喜悅 ... 嘻哈嘻哈(興奮聲)。

相較先判斷後執行,筆者比較傾向先執行後判斷,結果 do ... while 是描述語言最佳的模仿物件。舉例而言,如程式碼27.1所示:

// C 語言 do ... while迴圈
do { FuncA(); i++; } while( i < 4  )
i 為 0, 執行函式A,i遞增為1,i小於4,如是繼續;
i 為1, 執行函式A,i遞增為2,i小於4,如是繼續;
i 為2, 執行函式A,i遞增為3,i小於4,如是繼續;
i 為3, 執行函式A,i遞增為4,i小於4,不是則結束操作。

程式碼27.1

如程式碼27.1所示, C語言使用 do ... while 重複執行函式A,其中i控制迴圈,i < 4為執行條件。i為0~3期間,總共執行4次函式A。至於 Verilog 可以這樣模仿程式碼27.1,結果如程式碼27.2所示:

// Verilog 語言
case( i )
    0:
    if
(DoneA)begin isStart <= 1’b0; i <= i + 1’b1; end
    else begin isStart <= 1’b1; end
    1:
    if( C1 == 4 -1) begin C1 <= 8’d0; i <= i + 1’b1; end
else begin C1 <= C1 + 1’b1; i <= 4’d0; end
步驟0,i為0,執行功能A; 步驟1,i為0,條件不成立,i遞增為1,返回步驟;
步驟0,i為1,執行功能A; 步驟1,i為1,條件不成立,i遞增為2,返回步驟;
步驟0,i為2,執行功能A; 步驟1,i為2,條件不成立,i遞增為3,返回步驟;
步驟0,i為3,執行功能A; 步驟1,i為3,條件成立,i清零,繼續步驟;

程式碼27.2

如程式碼27.2所示,那是Verilog模仿do .. while迴圈的結果。程式碼27.1與程式碼27.2之間最大的差別就是後者(描述語言)必須自行建立順序結構,執行建立迴圈操作。雖說,描述語言什麼都要自己動手,非常勞動。不過,只要模仿起來似模似樣,描述語言也有迴圈利器。為了磨尖這把利器,筆者需要改動一下程式碼27.2,結果如程式碼27.3所示:

// Verilog 語言
case( i )
    0:
    if(DoneA)begin isStart <= 1’b0; Go <= i; i <= i + 1’b1; end
    else begin isStart <= 1’b1; end
    1:
    if( C1 == 4 -1) begin C1 <= 8’d0; i <= i + 1’b1; end
else begin C1 <= C1 + 1’b1; i <= Go; end

程式碼27.3

如程式碼27.3所示,筆者為迴圈加入返回作用的Go。步驟0之際,功能A執行完畢,Go便會暫存當前步驟,然後i繼續步驟。步驟1之際,如果if條件不成立,C1遞增,i返回Go指向的地方。反之,如果if條件成立,C1會清零,然後i繼續步驟。好奇的朋友一定會覺得疑惑,這樣作究竟有什麼好處?

嘻嘻!好處可多了 ... 筆者這樣做除了讓程式碼變漂亮一些以外,我們還能實現夢寐以求的巢狀迴圈,並且不失程式碼的表達能力,舉例而言,如程式碼27.4:

// C語言, 2層巢狀for
for( C2 = 0;C2 < 8;C2++ )
    for( C1 = 0;C1 < 4;C1 ++ )
        FuncA();   

程式碼27.4

如程式碼27.4所示,哪裡有一組巢狀for,C1控制函式A執行4次,C2則控制C1執行8次,結果函式A一共執行32次。一般而言,描述語言是很難實現這種巢狀for,即使僥倖成功,我們也得付出巨大的代價。如今仿順序操作在後面撐腰,慘痛已經成為過去式的悲劇 ... 廢話不多說,讓我們瞧瞧低階建模II的力量吧!少年少女們!

// Verilog 語言
case( i )
    0:
    if(DoneA)begin isStart <= 1’b0; Go <= i; i <= i + 1’b1; end
    else begin isStart <= 1’b1; end
    1:
    if( C1 == 4 -1) begin C1 <= 8’d0; i <= i + 1’b1; end
else begin C1 <= C1 + 1’b1; i <= Go; end
    2:
    if( C2 == 8 -1) begin C2 <= 8’d0; i <= i + 1’b1; end
else begin C2 <= C2 + 1’b1; i <= Go; end

程式碼27.5

如程式碼27.5所示,我們只要在步驟1的下面再新增一段程式碼即可,其中C1控制步驟0執行4次,步驟2則控制步驟1執行8次。操作期間,如果C1不滿足便會返回步驟0,反之繼續步驟;如果C2不滿足也會返回步驟0,反之繼續。步驟之間不停來回跳轉,如此一來,功能A總共執行32次。

即使物件是3層for巢狀,我們照搬無誤,如程式碼27.6所示:

// C語言, 3層巢狀for
for( C3 = 0;C3 < 8;C3++ )
    for( C2 = 0;C2 < 8;C2++ )
        for( C1 = 0;C1 < 4;C1 ++ )
            FuncA();

// Verilog 語言
case( i )

    0:
    if(DoneA)begin isStart <= 1’b0; Go <= i; i <= i + 1’b1; end
    else begin isStart <= 1’b1; end

    1:
    if( C1 == 4 -1) begin C1 <= 8’d0; i <= i + 1’b1; end
else begin C1 <= C1 + 1’b1; i <= Go; end

    2:
    if( C2 == 8 -1) begin C2 <= 8’d0; i <= i + 1’b1; end
else begin C2 <= C2 + 1’b1; i <= Go; end

    3:
    if( C3 == 10 -1) begin C3 <= 8’d0; i <= i + 1’b1; end
else begin C3 <= C3 + 1’b1; i <= Go; end

程式碼27.6

如程式碼27.6所示,C語言一共擁有3層for巢狀,反之Verilog只要再新增步驟3即可。期間,C3控制C2執行10次,C2控制C1執行8次,C1則控制功能A執行4次 ... 如此一來,功能A一共執行320次。如果一鼓作氣下去的話,不管迴圈for有多少層也勢如破竹。讀者是不是覺得很神奇呢?然而,最神奇的地方是,步驟依然從上之下解讀。

// C語言, 3層巢狀for
for( C3 = 0;C3 < 8;C3++ )
for( C2 = 0;C2 < 8;C2++ )
    {
            for( C1 = 0; C1 < 4; C1 ++ ) FuncA();
            FuncB();
        }

程式碼27.7

假設筆者稍微頑皮一點,讓C2控制C1以外,也讓它控制函式B。對此,Verilog又該怎樣描述呢?

// Verilog 語言
case( i )
    0:
    if(DoneA)begin isStart[1] <= 1’b0; Go <= i; i <= i + 1’b1; end
    else begin isStart[1] <= 1’b1; end
    1:
    if( C1 == 4 -1) begin C1 <= 8’d0; i <= i + 1’b1; end
else begin C1 <= C1 + 1’b1; i <= Go; end
2:
if( DoneB ) begin isStart[0] <= 1’b0; i <= i + 1’b1; end
else begin isStart[0] <= 1’b1; end 
    3:
    if( C2 == 8 -1) begin C2 <= 8’d0; i <= i + 1’b1; end
else begin C2 <= C2 + 1’b1; i <= Go; end
    4:
    if( C3 == 10 -1) begin C3 <= 8’d0; i <= i + 1’b1; end
else begin C3 <= C3 + 1’b1; i <= Go; end

程式碼27.8

如程式碼27.8所示,我們只要在C2~C1之間再插入一段步驟即可。期間,步驟2先執行功能B,然後再進入步驟3。如此一來,功能A一共執行320次,然而功能B一共執行80次。好奇的同學一定會覺得奇怪,怎麼說說TFT LCD忽然插入迴圈操作的話題呢?原因很單純,因為繪圖功能必須借用迴圈操作才行。因此,筆者事先為讀者洗白白了 ... 好了,理解完畢以後,我們便可以進入主題了?

clip_image002

圖27.1 TFT連線FPGA。

一般而言,如果TFT LCD 只是單純地顯示影象,然後FPGA也是單純地寫入地址或者寫入影象資訊 ... 對此,它們之間所需的連線並不多。如圖27.1所示,RST訊號用來複位TFT LCD,RS訊號用來分辨寫入資料是命令還是影象資訊,CS是使能訊號,WR是寫入有效訊號,RD是讀出有效訊號,DB則是資料訊號。此外,為了簡化設計,FPGA只需負責寫資料而已,所以連線箭頭往左一面倒。

clip_image004

圖27.2 控制器SSD1289的寫時序。

圖27.1是控制器SSD1289的寫命令還有寫資料的時序圖,左圖是寫命令,右圖則是寫資料。寫命令與寫資料的區別就在乎 RS訊號拉低還是拉高而已,寫命令拉低 RS,寫資料則拉高RS。如果我們不打算復位控制器,RST訊號可以常年拉高。此外,筆者也說過FPGA只寫不讀,所以WR訊號常年拉低,RD訊號則是常年拉高。餘下只有CS訊號還有DB訊號而已。

在這裡,CS訊號充當時鐘訊號,因為CS訊號由低變高所產生的上升沿會使 DB訊號的內容打入控制器裡面。為使上升沿穩如泰山,我們必須滿足TCSL與TCSH這兩隻時間要求。根據手冊,TCSL最少為50ns,TCSH最少則是500ns,因此CS最小週期需要550ns,或者說該控制器擁有速率1.818181 Mhz。此外,TDSW與TDHW都是常見的TSETUP與THOLD,手冊顯示只有5ns而已,所以我們可以無視。

parameter TCSL = 3, TCSH = 25; // tCSL = 50ns, tCSH = 500ns;

程式碼27.9

1.     case( i )
2.                        
3.            0:
4.             if( C1 == TCSL -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
5.            else begin { rRS,rCS } <= 2'b00; D1 <= { 8'd0, iAddr }; C1 <= C1 + 1'b1; end
6.                         
7.            1:
8.             if( C1 == TCSH -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
9.            else begin { rRS,rCS } <= 2'b01; C1 <= C1 + 1'b1; end

程式碼27.10

如程式碼27.10所示,那是寫命令操作。CS在閒置狀態下都處於拉高狀態,步驟0拉低RS與CS,還有賦值命令(地址),然後等待 TCSL。步驟2拉高CS之餘,它也等待TCSH。

1.    case( i )
2.                        
3.        0:
4.         if( C1 == TCSL -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
5.        else begin { rRS,rCS } <= 2'b10; D1 <= iData; C1 <= C1 + 1'b1; end
6.                         
7.        1:
8.         if( C1 == TCSH -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
9.        else begin { rRS,rCS } <= 2'b11; C1 <= C1 + 1'b1; end

程式碼27.11

如程式碼27.11所示,那是寫資料。預設下,CS處於高電平。步驟0,拉高RS之餘也拉低CS,準備寫命令之後,便等待 TCSL。步驟1,拉高RS之餘也拉高CS,然後等待TCSH。基本上,控制器SSD1289的寫時序就是那麼簡單而已,接下來就是該控制器的配置資訊。

控制器SSD1289內建超過50個配置暫存器,如果逐個解釋,筆者一定會捏爆自己的蛋蛋 ... 對此,筆者僅對看懂又重要的暫存器解釋而已。

clip_image006

圖27.3 Oscillator配置暫存器。

如圖27.3所示,那是Oscillator配置暫存器。它可謂是最簡單的暫存器,IB0為1,內部的晶振就開始鼓動,反之亦然。

clip_image008

圖27.4 Driver Output Control 配置暫存器。

如圖27.4所示,那是 Driver Output Control 配置暫存器。MUX0~8用來設定TFT的顯示高度(Vertical),最大為319(從0開始計算)或者0x13F。BGR用來設定顏色的排序,BGR為1,顏色排序為 <R><G><B>,為0則是 <B><G><R>。

clip_image010

圖27.5 Sleep Mode 配置暫存器。

如圖27.5所示,那是Sleep Mode 配置暫存器,其中IBO為1表示控制器在睡覺。我們只要將其設定為0,該控制器就會起床。

clip_image012

圖27.6 Entry Mode 配置暫存器

圖27.6所示是 Entry Mode 配置暫存器,它可謂是重量級的配置暫存器之一。DFM表示色彩的解析度, DFM為2’b11 就是16位RGB(65k顏色),DFM為2’b10就是18位RGB(262k)。如果選擇16位RGB,我們可以無視 TY。DMode為2’b00,表示顯示ram裡邊的影象資訊。

clip_image014

圖27.7 掃描次序。

再者就是 ID與AM了,根據配置內容不同,控制器也會產生不同的掃描次序,結果如圖27.7所示。筆者選擇先左至右,後上至下的掃描次序,目的是為了迎合 VGA的掃描次序,所以AM設定為0,ID則是 2’b11。

clip_image016

圖27.8 Horizontal Porch 配置暫存器

圖27.8顯示Horizontal Porch 配置暫存器的內容,其中 XL是HSYNC的資料段長度,HBP則是起始段還有準備段的長度。讀者是否還記得 VGA 時序?我們利用 HSYNC訊號還有 VSYNC訊號控制VGA的顯示標準。同樣,TFT LCD 內部也使用了 VGA時序,不過驅動物件卻是控制器 SSD 1289。

clip_image018

圖27.9 TFT LCD 內部的 HSYNC 時序。

如圖27.9所示,那是TFT LCD 內部的 HSYNC 時序。其中 HBP 配置起始段還有準備段的長度,HBP預設下為30,配置資訊則是 8’b1C。換之,XL用來控制資料段的長度,而且 240 即是預設值也是最大值,配置資訊則是 8’hEF。

clip_image020

圖27.9 Vertical Porch 配置暫存器。

圖27.9顯示Vertical Porch 配置暫存器的內容,亦即控制內部的 VSYNC訊號。VFP用來配置結束段的長度,VBP則是配置起始段還有準備段的長度。

clip_image022

圖27.10 TFT LCD 內部的CSYNC時序。

如圖27.10所示,VBP的預設長度為4個HSYNC的下降沿(起始段),結果預設值為4,配置資訊則是 8’h03。換之,VFP的預設長度為1個HSYNC週期,所以預設值為1,配置資訊則是8’h00。至於VSYNC的資料段則在Driver Output Control 哪裡配置。

clip_image024

圖27.11 Display Control 配置暫存器。

圖27.11是Display Control 配置暫存器,而重點內容就在D1與D0。D1控制螢幕開關,值1顯示,值0關閉。D0控制控制器的活動狀態,值1幹活,值0掛機。為此,螢幕正常活動的時候 D1與D0 必須設為 2’b11。

clip_image026

圖27.12 Gate Scan Position 配置暫存器。

圖27.12是Gate Scan Position 配置暫存器,其中SCN表示掃描的起始位置。

clip_image028

圖27.13 預設掃描起始位置(左),配置過後的掃描起始位置(右)。

如圖27.13所示,左圖是預設下的起始位置,右圖則是從29行開始掃描,結果文字資訊與圖示資訊調轉位置了。所以,SCN一般都設為0值。

clip_image030

圖27.14 1st Screen Driving Position配置暫存器。

圖27.14是 1st Screen Driving Position 配置暫存器。黑金所用的 TFT LCD,主要分為主螢幕(First Screen)還有次螢幕(Second Screen)。主螢幕一般作為主要顯示源,而且掃描也是一行一行執行,期間SS1表示掃描的開始位置,SE1則表示掃描的結束位置。預設下,SS1 為 0,配置資訊則是 9’d0,SE1為319,配置資訊則是 9’h13F。

clip_image032

圖27.15 Horizontal RAM Address Position配置暫存器。

圖27.15是 Horizontal RAM Address Position 配置暫存器。HSA表示有效的起始列,預設下為0,配置資訊則是 8’h00。換之,HEA表示有效的結束列,預設下為239,配置資訊則是 8’hEF。

clip_image034

圖27.16 Vertical RAM Address Position配置暫存器

圖27.16是 Vertical RAM Address Position 配置暫存器。VSA表示有效的起始行,預設下為0,配置資訊則是 9’h000。VEA表示有效的結束行,預設下為 319,配置資訊則是9’h13F。

感覺上,Horizontal RAM Address Position 好比vga_ctrlmod 的 isX,Vertical RAM Address Position 好比 isY,兩者求與後便構成 isReady。例如實驗二十六,顯示空間雖然有1024 × 768的範圍,不過筆者只用左上一角顯示 128 × 96 的影象而已。其中,列0至列127之間為有效 X,行0至行95之間為有效Y,結果兩者構成有效的顯示區域。

clip_image036

圖27.17 RAM Write Data Mask配置暫存器。

圖27.17是 RAM Write Data Mask 配置暫存器,WMR表示紅色源的遮蔽資訊,WMG表示綠色源的遮蔽資訊,WMB則是藍色源的遮蔽資訊。值1表示遮蔽有效,值0表示遮蔽有效。遮蔽一旦啟動,相關的顏色位便會寫入失效。其實這些傢伙並沒有多大用處,筆者也是循例介紹而已。

clip_image038

圖27.18 RAM Address set配置暫存器

圖27.18是 RAM Address set 配置暫存器,XAD表示列計數器的內容,YAD則表示行計數器的內容。寫資料期間,CS每次上升沿都使 XAD遞增,直至239為止便會清零(預設下),然後遞增YAD。預設下,YAD遞增到319也會清零。每當寫資料之前,我們都會順手將它們設為0值。

clip_image040

圖27.19 Write Data to CGRAM 暫存器。

圖27.19是Write Data to CGRAM 暫存器。根據手冊,寫影象資訊之前,我們必須事先設定寫資料的目標地址 ... 然而,那個目標地址就是 Write Data to GRAM 暫存器。隨後,寫入資訊再由控制器寫入內部CGRAM。基本上,有用又看懂的暫存器就是這些而已,接下來讓我們來瞧瞧如何初始化控制器SSD 1289。

因為犯懶的關係,筆者嘗試將寫命令和寫資料捆綁在一起,結果如程式碼27.12所示:

1.    case( i )
2.                        
3.            0:
4.             if( C1 == TCSL -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
5.            else begin { rRS,rCS } <= 2'b00; D1 <= { 8'd0, iAddr }; C1 <= C1 + 1'b1; end
6.                         
7.            1:
8.             if( C1 == TCSH -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
9.            else begin { rRS,rCS } <= 2'b01; C1 <= C1 + 1'b1; end
10.    
11.            2:
12.             if( C1 == TCSL -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
13.            else begin { rRS,rCS } <= 2'b10; D1 <= iData; C1 <= C1 + 1'b1; end
14.                         
15.            3:
16.             if( C1 == TCSH -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
17.            else begin { rRS,rCS } <= 2'b11; C1 <= C1 + 1'b1; end

程式碼27.12

如程式碼27.12所示,步驟0~1是寫命令(地址),步驟2~3則是寫資料。如此一來,筆者只要呼叫一次該功能便能同時執行寫命令還有寫資料。

根據官方原始碼,控制器SSD1289的初始化過程是很長很臭的,對此讓筆者N行,N行慢慢解釋吧。

1.    case( i )
2.                    
3.        0: // Oscillator, On
4.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
5.        else begin isDo[2] <= 1'b1; D1 <= 8'h00; D2 <= 16'h0001; end

程式碼27.13

假設拉高 isDo[2] 呼叫程式碼27.12,步驟0開啟使能晶振。

1.        1: // Power control 1
2.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
3.        else begin isDo[2] <= 1'b1; D1 <= 8'h03; D2 <= 16'h6664; end
4.                         
5.        2: // Power control 2
6.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
7.        else begin isDo[2] <= 1'b1; D1 <= 8'h0C; D2 <= 16'h0000; end
8.                         
9.        3: // Power control 3
10.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
11.        else begin isDo[2] <= 1'b1; D1 <= 8'h0D; D2 <= 16'h080C; end
12.                         
13.        4: // Power control 4
14.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
15.        else begin isDo[2] <= 1'b1; D1 <= 8'h0E; D2 <= 16'h2B00; end
16.                         
17.        5: // Power control 5
18.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
19.        else begin isDo[2] <= 1'b1; D1 <= 8'h1E; D2 <= 16'h00B0; end

程式碼27.14

步驟1~5是相關的電源配置資訊,別問筆者為什麼,筆者也是照搬而已。

1.        6: // Driver output control, MUX = 319, <R><G><B>
2.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
3.        else begin isDo[2] <= 1'b1; D1 <= 8'h01; D2 <= 16'h2B3F; end
4.                         
5.        7: // LCD driving waveform control
6.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
7.        else begin isDo[2] <= 1'b1; D1 <= 8'h02; D2 <= 16'h0600; end
8.                         
9.        8: // Sleep mode, weak-up
10.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
11.        else begin isDo[2] <= 1'b1; D1 <= 8'h10; D2 <= 16'h0000; end
12.                         
13.        9: // Entry mode, 65k color, DM = 2’b00, AM = 0, ID = 2’b11 
14.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
15.        else begin isDo[2] <= 1'b1; D1 <= 8'h11; D2 <= 16'h6070; end

程式碼27.15

步驟6用來配置VSYNC的資料段長度之餘,也設定 RGB的排序。步驟7不清楚,步驟8喚醒控制器。步驟9用來配置 16 位RGB,還有掃描次序。

1.        10: // Compare register
2.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
3.        else begin isDo[2] <= 1'b1; D1 <= 8'h05; D2 <= 16'h0000; end
4.                    
5.        11: // Compare register
6.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
7.        else begin isDo[2] <= 1'b1; D1 <= 8'h06; D2 <= 16'h0000; end
8.                         
9.        12: // Horizontal porch, HBP = 30, XL = 239
10.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
11.        else begin isDo[2] <= 1'b1; D1 <= 8'h16; D2 <= 16'hEF1C; end
12.                         
13.        13: // Vertical porch, VBP = 1, VBP = 4
14.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
15.        else begin isDo[2] <= 1'b1; D1 <= 8'h17; D2 <= 16'h0003; end
16.                         
17.        14: // Display control, 8 color mode, display on, operation on
18.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
19.        else begin isDo[2] <= 1'b1; D1 <= 8'h07; D2 <= 16'h0233; end

程式碼27.16

步驟10~11 不清楚,步驟12用來配置 HSYNC,步驟13則用來配置 VSYNC。步驟14用來配置顯示器開啟,控制器進入活動。

1.        15: // Frame cycle control
2.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
3.        else begin isDo[2] <= 1'b1; D1 <= 8'h0B; D2 <= 16'h0000; end
4.                         
5.        16: // Gate scan position, SCN = 0
6.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
7.        else begin isDo[2] <= 1'b1; D1 <= 8'h0F; D2 <= 16'h0000; end
8.                         
9.        17: // Vertical scroll control
10.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
11.        else begin isDo[2] <= 1'b1; D1 <= 8'h41; D2 <= 16'h0000; end
12.                         
13.        18: // Vertical scroll control
14.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
15.        else begin isDo[2] <= 1'b1; D1 <= 8'h42; D2 <= 16'h0000; end

程式碼27.16

步驟15不清楚,步驟16配置掃描的起始位置。步驟17~18好像與拖動效果有關,具體內容並不清楚,也不使用。

1.        19: // 1st screen driving position, G0~
2.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
3.        else begin isDo[2] <= 1'b1; D1 <= 8'h48; D2 <= 16'h0000; end
4.                         
5.        20: // 1st screen driving position, ~G319
6.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
7.        else begin isDo[2] <= 1'b1; D1 <= 8'h49; D2 <= 16'h013F; end
8.                         
9.        21: // 2nd screen driving position
10.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
11.        else begin isDo[2] <= 1'b1; D1 <= 8'h4A; D2 <= 16'h0000; end
12.                         
13.        22: // 2nd screen driving position
14.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
15.        else begin isDo[2] <= 1'b1; D1 <= 8'h4B; D2 <= 16'h0000; end

程式碼27.17

步驟19~20用來配置主螢幕的掃描範圍,步驟21~22則是用來配置次螢幕的掃描範圍,不過只有主螢幕被使用而已。

1.        23: // Horizontal RAM address position, HSA = 0 HEA = 239
2.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
3.        else begin isDo[2] <= 1'b1; D1 <= 8'h44; D2 <= 16'hEF00; end
4.                         
5.        24: // Vertical RAM address position, VSA = 0 
6.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
7.        else begin isDo[2] <= 1'b1; D1 <= 8'h45; D2 <= 16'h0000; end
8.                         
9.        25: // Vertical RAM address position, VEA = 319 
10.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
11.        else begin isDo[2] <= 1'b1; D1 <= 8'h46; D2 <= 16'h013F; end

程式碼27.18

步驟23用來配置有效列,步驟24~25則是用來配置有效行。

1.        26: // Gamma control, PKP0~1
2.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
3.        else begin isDo[2] <= 1'b1; D1 <= 8'h30; D2 <= 16'h0707; end
4.                         
5.        27: // Gamma control, PKP2~3
6.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
7.        else begin isDo[2] <= 1'b1; D1 <= 8'h31; D2 <= 16'h0204; end
8.                         
9.        28: // Gamma control, PKP4~5
10.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
11.        else begin isDo[2] <= 1'b1; D1 <= 8'h32; D2 <= 16'h0204; end
12.                         
13.        29: // Gamma control, PRP0~1
14.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
15.        else begin isDo[2] <= 1'b1; D1 <= 8'h33; D2 <= 16'h0502; end
16.                         
17.        30: // Gamma control, PKN0~1
18.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
19.        else begin isDo[2] <= 1'b1; D1 <= 8'h34; D2 <= 16'h0507; end
20.                         
21.        31: // Gamma control, PKN2~3
22.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
23.        else begin isDo[2] <= 1'b1; D1 <= 8'h35; D2 <= 16'h0204; end
24.                         
25.        32: // Gamma control, PKN4~5
26.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
27.        else begin isDo[2] <= 1'b1; D1 <= 8'h36; D2 <= 16'h0204; end
28.                         
29.        33: // Gamma control, PRN0~1
30.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
31.        else begin isDo[2] <= 1'b1; D1 <= 8'h37; D2 <= 16'h0502; end
32.                         
33.        34: // Gamma control, VRP0~1
34.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
35.        else begin isDo[2] <= 1'b1; D1 <= 8'h3A; D2 <= 16'h0302; end
36.                         
37.        35: // Gamma control, VRN0~1
38.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
39.        else begin isDo[2] <= 1'b1; D1 <= 8'h3B; D2 <= 16'h0302; end

程式碼27.19

步驟26~35好像是用來配置螢幕的亮度或者對比度,雖然手冊有詳細的介紹與公式,不過真心看不懂,所以照搬而已。

1.        36: // RAM write data mask
2.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
3.        else begin isDo[2] <= 1'b1; D1 <= 8'h23; D2 <= 16'h0000; end
4.                         
5.        37: // RAM write data mask
6.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
7.        else begin isDo[2] <= 1'b1; D1 <= 8'h24; D2 <= 16'h0000; end
8.                         
9.        38: // Unknown
10.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
11.        else begin isDo[2] <= 1'b1; D1 <= 8'h25; D2 <= 16'h8000; end
12.                         
13.        39: // RAM address set, horizontal(X)
14.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
15.        else begin isDo[2] <= 1'b1; D1 <= 8'h4E; D2 <= 16'h0000; end
16.                         
17.        40: // RAM address set, vertical(Y)
18.        if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
19.        else begin isDo[2] <= 1'b1; D1 <= 8'h4F; D2 <= 16'h0000; end

程式碼27.20

步驟36~37用來配置顏色源的遮蔽資訊,步驟38不明因為手冊沒有解釋,步驟39~40則是用來配置 X計數器與Y計數器的初值。基本上,控制器SSD1289的初始化就這樣而已。準備知識理解完畢以後,我們便可以開始建模了。

clip_image042

圖27.20 TFT基礎模組的建模圖。

圖27.20是TFT基礎模組的建模圖,TFT功能模組作有三位寬的溝通訊號,結果表示它有3項功能,[2]為寫命令與寫資料,[1]為寫命令,[0]為寫資料。換之,右邊則是驅動TFT LCD的頂層訊號。至於TFT控制模組除了呼叫功能模組以外,左邊也有3位寬的溝通訊號,其中[0]為初始化,[1]為清屏,[2]為畫圖。

tft_funcmod.v

clip_image044

圖27.21 TFT功能模組的建模圖。

圖27.21是TFT功能模組的建模圖,它雖然渾身佈滿箭頭,不過它還是簡單易懂的好傢伙。左邊的 Start/Done 有3位寬,恰好表示它有3個功能,其中[2]為寫命令再寫資料,[1]為寫命令,[0]則是寫資料。iAddr是寫入命令所需的內容,iData則是寫資料所需的內容。

1.    module tft_funcmod
2.    (
3.         input CLOCK, RESET,
4.         output TFT_RS,     // 1 = Data, 0 = Command(Register)
5.         output TFT_CS_N,  
6.         output TFT_WR_N,
7.         output TFT_RD_N,
8.         output [15:0]TFT_DB,
9.         input [2:0]iStart,  
10.         output oDone,
11.         input [7:0]iAddr,
12.         input [15:0]iData
13.    );
14.         parameter TCSL = 3, TCSH = 25; // tCSL = 50ns, tCSH = 500ns;
15.         

以上內容為相關的出入端宣告還有相關的常量宣告。

16.         reg [3:0]i;
17.         reg [4:0]C1;
18.         reg [15:0]D1;
19.         reg rRS,rCS,rWR,rRD;
20.         reg isDone;
21.         
22.        always @ ( posedge CLOCK or negedge RESET )
23.            if( !RESET )
24.                  begin
25.                         i <= 4'd0;
26.                         C1 <= 5'd0;
27.                         D1 <= 16'd0;
28.                         { rRS, rCS, rWR, rRD } <= 4'b1101;
29.                         isDone <= 1'b0;
30.                    end

以上內容為相關的暫存器宣告還有復位操作,其中第28行表示 WR 常年拉低,RD則是常年拉高。

31.              else if( iStart[2] ) // Write command and data
32.                  case( i )
33.                        
34.                         0:
35.                          if( C1 == TCSL -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
36.                         else begin { rRS,rCS } <= 2'b00; D1 <= { 8'd0, iAddr }; C1 <= C1 + 1'b1; end
37.                         
38.                         1:
39.                          if( C1 == TCSH -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
40.                         else begin { rRS,rCS } <= 2'b01; C1 <= C1 + 1'b1; end
41.                         
42.                         2:
43.                          if( C1 == TCSL -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
44.                         else begin { rRS,rCS } <= 2'b10; D1 <= iData; C1 <= C1 + 1'b1; end
45.                         
46.                         3:
47.                          if( C1 == TCSH -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
48.                         else begin { rRS,rCS } <= 2'b11; C1 <= C1 + 1'b1; end
49.                         
50.                         4:
51.                         begin isDone <= 1'b1; i <= i + 1'b1; end
52.                         
53.                         5:
54.                         begin isDone <= 1'b0; i <= 4'd0; end
55.                        
56.                    endcase

以上內容為寫命令再寫資料。

57.              else if( iStart[1] ) // Write command 
58.                  case( i )
59.                        
60.                         0:
61.                          if( C1 == TCSL -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
62.                         else begin { rRS,rCS } <= 2'b00; D1 <= { 8'd0, iAddr }; C1 <= C1 + 1'b1; end
63.                         
64.                         1:
65.                          if( C1 == TCSH -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
66.                         else begin { rRS,rCS } <= 2'b01; C1 <= C1 + 1'b1; end
67.                         
68.                         2:
69.                         begin isDone <= 1'b1; i <= i + 1'b1; end
70.                         
71.                         3:
72.                         begin isDone <= 1'b0; i <= 4'd0; end
73.                        
74.                    endcase

以上內容為寫命令。

75.             else if( iStart[0] ) // Write data
76.                  case( i )
77.                        
78.                         0:
79.                          if( C1 == TCSL -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
80.                         else begin { rRS,rCS } <= 2'b10; D1 <= iData; C1 <= C1 + 1'b1; end
81.                         
82.                         1:
83.                          if( C1 == TCSH -1 ) begin C1 <= 5'd0; i <= i + 1'b1; end
84.                         else begin { rRS,rCS } <= 2'b11; C1 <= C1 + 1'b1; end
85.                         
86.                         2:
87.                         begin isDone <= 1'b1; i <= i + 1'b1; end
88.                         
89.                         3:
90.                         begin isDone <= 1'b0; i <= 4'd0; end
91.                        
92.                    endcase
93.                    

以上內容為寫資料。

94.         assign TFT_DB = D1;
95.         assign TFT_RS = rRS;
96.         assign TFT_CS_N = rCS;
97.         assign TFT_WR_N = rWR;
98.         assign TFT_RD_N = rRD;
99.         assign oDone = isDone;
100.            
101.    endmodule

以上內容為相關的輸出驅動宣告。

tft_ctrlmod.v

clip_image046

圖27.22 TFT控制模組的建模圖。

圖27.22是TFT控制模組的建模圖,它外表雖然簡單,內容卻很囉嗦 ... 具體情況讓我們來看程式碼吧。

1.    module tft_ctrlmod
2.    (
3.         input CLOCK, RESET,
4.         input [2:0]iStart,
5.         output oDone,
6.         output [2:0]oStart,
7.         input iDone,
8.         output [7:0]oAddr,
9.         output [15:0]oData
10.    );

以上內容為相關的出入端宣告。

11.        reg [5:0]i,Go;
12.         reg [7:0]D1;   // Command(Register)
13.         reg [15:0]D2;  // Data
14.         reg [16:0]C1;
15.         reg [7:0]C2,C3;
16.         reg [2:0]isDo;
17.         reg isDone;
18.         
19.         always @ ( posedge CLOCK or negedge RESET )
20.             if( !RESET )
21.                  begin 
22.                        { i,Go } <= { 6'd0,6'd0 };
23.                         D1 <= 8'd0;
24.                         D2 <= 16'd0;
25.                         C1 <= 17'd0;
26.                         { C2,C3 } <= { 8'd0,8'd0 };
27.                         isDo <= 3'd0;
28.                         isDone <= 1'b0;
29.                    end

以上內容為相關的暫存器宣告還有復位操作。其中第16行的isDo作用類似isStart,D1暫存地址(命令),D2暫存讀寫資料,C1~C3則是用來控制迴圈。

30.              else if( iStart[2] )  // White blank page
31.                  case( i )
32.                    
33.                         0: // X0
34.                         if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
35.                         else begin isDo[2] <= 1'b1; D1 <= 8'h4E; D2 <= 16'h0000; end
36.                         
37.                         1: // Y0
38.                         if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
39.                         else begin isDo[2] <= 1'b1; D1 <= 8'h4F; D2 <= 16'h0000; end
40.                         
41.                         2: // Write data to ram 0x22
42.                         if( iDone ) begin isDo[1] <= 1'b0; i <= i + 1'b1; end
43.                         else begin isDo[1] <= 1'b1; D1 <= 8'h22; end
44.                         
45.                         /**********/
46.                         
47.                         3: // Write color
48.                         if( iDone ) begin isDo[0] <= 1'b0; i <= i + 1'b1; Go <= i; end
49.                         else begin isDo[0] <= 1'b1; D2 <= { C3[4:0],6'd0,5'd0}  ; end
50.                         
51.                         4: // Loop 1, rectangle width
52.                         if( C1 == 240 -1 ) begin C1 <= 17'd0; i <= i + 1'b1; end
53.                         else begin C1 <= C1 + 1'b1; i <= Go; end
54.                          
55.                         5: // Loop 2, rectangle high
56.                         if( C2 == 10 -1 ) begin C2 <= 8'd0; i <= i + 1'b1; end
57.                         else<