【黑金原創教程】【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忽然插入迴圈操作的話題呢?原因很單純,因為繪圖功能必須借用迴圈操作才行。因此,筆者事先為讀者洗白白了 ... 好了,理解完畢以後,我們便可以進入主題了?
圖27.1 TFT連線FPGA。
一般而言,如果TFT LCD 只是單純地顯示影象,然後FPGA也是單純地寫入地址或者寫入影象資訊 ... 對此,它們之間所需的連線並不多。如圖27.1所示,RST訊號用來複位TFT LCD,RS訊號用來分辨寫入資料是命令還是影象資訊,CS是使能訊號,WR是寫入有效訊號,RD是讀出有效訊號,DB則是資料訊號。此外,為了簡化設計,FPGA只需負責寫資料而已,所以連線箭頭往左一面倒。
圖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個配置暫存器,如果逐個解釋,筆者一定會捏爆自己的蛋蛋 ... 對此,筆者僅對看懂又重要的暫存器解釋而已。
圖27.3 Oscillator配置暫存器。
如圖27.3所示,那是Oscillator配置暫存器。它可謂是最簡單的暫存器,IB0為1,內部的晶振就開始鼓動,反之亦然。
圖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>。
圖27.5 Sleep Mode 配置暫存器。
如圖27.5所示,那是Sleep Mode 配置暫存器,其中IBO為1表示控制器在睡覺。我們只要將其設定為0,該控制器就會起床。
圖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裡邊的影象資訊。
圖27.7 掃描次序。
再者就是 ID與AM了,根據配置內容不同,控制器也會產生不同的掃描次序,結果如圖27.7所示。筆者選擇先左至右,後上至下的掃描次序,目的是為了迎合 VGA的掃描次序,所以AM設定為0,ID則是 2’b11。
圖27.8 Horizontal Porch 配置暫存器
圖27.8顯示Horizontal Porch 配置暫存器的內容,其中 XL是HSYNC的資料段長度,HBP則是起始段還有準備段的長度。讀者是否還記得 VGA 時序?我們利用 HSYNC訊號還有 VSYNC訊號控制VGA的顯示標準。同樣,TFT LCD 內部也使用了 VGA時序,不過驅動物件卻是控制器 SSD 1289。
圖27.9 TFT LCD 內部的 HSYNC 時序。
如圖27.9所示,那是TFT LCD 內部的 HSYNC 時序。其中 HBP 配置起始段還有準備段的長度,HBP預設下為30,配置資訊則是 8’b1C。換之,XL用來控制資料段的長度,而且 240 即是預設值也是最大值,配置資訊則是 8’hEF。
圖27.9 Vertical Porch 配置暫存器。
圖27.9顯示Vertical Porch 配置暫存器的內容,亦即控制內部的 VSYNC訊號。VFP用來配置結束段的長度,VBP則是配置起始段還有準備段的長度。
圖27.10 TFT LCD 內部的CSYNC時序。
如圖27.10所示,VBP的預設長度為4個HSYNC的下降沿(起始段),結果預設值為4,配置資訊則是 8’h03。換之,VFP的預設長度為1個HSYNC週期,所以預設值為1,配置資訊則是8’h00。至於VSYNC的資料段則在Driver Output Control 哪裡配置。
圖27.11 Display Control 配置暫存器。
圖27.11是Display Control 配置暫存器,而重點內容就在D1與D0。D1控制螢幕開關,值1顯示,值0關閉。D0控制控制器的活動狀態,值1幹活,值0掛機。為此,螢幕正常活動的時候 D1與D0 必須設為 2’b11。
圖27.12 Gate Scan Position 配置暫存器。
圖27.12是Gate Scan Position 配置暫存器,其中SCN表示掃描的起始位置。
圖27.13 預設掃描起始位置(左),配置過後的掃描起始位置(右)。
如圖27.13所示,左圖是預設下的起始位置,右圖則是從29行開始掃描,結果文字資訊與圖示資訊調轉位置了。所以,SCN一般都設為0值。
圖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。
圖27.15 Horizontal RAM Address Position配置暫存器。
圖27.15是 Horizontal RAM Address Position 配置暫存器。HSA表示有效的起始列,預設下為0,配置資訊則是 8’h00。換之,HEA表示有效的結束列,預設下為239,配置資訊則是 8’hEF。
圖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,結果兩者構成有效的顯示區域。
圖27.17 RAM Write Data Mask配置暫存器。
圖27.17是 RAM Write Data Mask 配置暫存器,WMR表示紅色源的遮蔽資訊,WMG表示綠色源的遮蔽資訊,WMB則是藍色源的遮蔽資訊。值1表示遮蔽有效,值0表示遮蔽有效。遮蔽一旦啟動,相關的顏色位便會寫入失效。其實這些傢伙並沒有多大用處,筆者也是循例介紹而已。
圖27.18 RAM Address set配置暫存器
圖27.18是 RAM Address set 配置暫存器,XAD表示列計數器的內容,YAD則表示行計數器的內容。寫資料期間,CS每次上升沿都使 XAD遞增,直至239為止便會清零(預設下),然後遞增YAD。預設下,YAD遞增到319也會清零。每當寫資料之前,我們都會順手將它們設為0值。
圖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的初始化就這樣而已。準備知識理解完畢以後,我們便可以開始建模了。
圖27.20 TFT基礎模組的建模圖。
圖27.20是TFT基礎模組的建模圖,TFT功能模組作有三位寬的溝通訊號,結果表示它有3項功能,[2]為寫命令與寫資料,[1]為寫命令,[0]為寫資料。換之,右邊則是驅動TFT LCD的頂層訊號。至於TFT控制模組除了呼叫功能模組以外,左邊也有3位寬的溝通訊號,其中[0]為初始化,[1]為清屏,[2]為畫圖。
tft_funcmod.v
圖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
圖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<