實驗二十七: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 begin C2 <= C2 + 1'b1; i <= Go; end
58.
59. 6: // Loop 3, color and times
60. if( C3 == 32 -1 ) begin C3 <= 8'd0; i <= i + 1'b1; end
61. else begin C3 <= C3 + 1'b1; i <= Go; end
62.
63. /**********/
64.
65. 7:
66. begin isDone <= 1'b1; i <= i + 1'b1; end
67.
68. 8:
69. begin isDone <= 1'b0; i <= 6'd0; end
70.
71. endcase
以上內容為繪圖功能。步驟0配置寫入地址X為0,步驟1配置寫入地址Y為0,步驟2配置寫數據的目標地址。步驟3則是寫數據,其中 { C3[4:0], 6’d0, 5’d0 },紅色信息取自 C3,綠色為0,藍色為0。步驟4,C1控制循環240次,主要是寫完1行240列。
步驟5,C2用來控制C1,主要是控制矩形的高度為10。步驟6,C3用來控制C2之余,它也用來控制矩形的數量還有顏色漸變。
for( C3 = 0; C3 < 32; C3++ ) for( C2 = 0; C2 < 10; C2++ ) for( C1 = 0; C1 <240; C1++ ) Write_Data( C3,0,0 ); // <R><G><B>
代碼27.20
簡單來說,操作過程如代碼27.20所示。
72. else if( iStart[1] ) // White blank page
73. case( i )
74.
75. 0: // X0
76. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
77. else begin isDo[2] <= 1'b1; D1 <= 8'h4E; D2 <= 16'h0000; end
78.
79. 1: // Y0
80. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
81. else begin isDo[2] <= 1'b1; D1 <= 8'h4F; D2 <= 16'h0000; end
82.
83. 2: // Write data to ram 0x22
84. if( iDone ) begin isDo[1] <= 1'b0; i <= i + 1'b1; end
85. else begin isDo[1] <= 1'b1; D1 <= 8'h22; end
86.
87. /**********/
88.
89. 3: // Write white color 0~768000
90. if( iDone ) begin isDo[0] <= 1'b0; i <= i + 1'b1; Go <= i; end
91. else begin isDo[0] <= 1'b1; D2 <= 16'hFFFF ; end
92.
93. 4:
94. if( C1 == 76800 -1 ) begin C1 <= 17'd0; i <= i + 1'b1; end
95. else begin C1 <= C1 + 1'b1; i <= Go; end
96.
97. 5:
98. begin isDone <= 1'b1; i <= i + 1'b1; end
99.
100. 6:
101. begin isDone <= 1'b0; i <= 6'd0; end
102.
103. endcase
以上內容為清屏功能。步驟0配置寫入地址X,步驟1則是配置寫入地址Y,步驟2配置寫數據的目的地址。步驟3將白色的圖像信息寫入CGRAM,步驟4則是控制步驟3執行76800次(0~76799),因為 320 × 240 等價 76800,完后便能達成清屏功能。
104. else if( iStart[0] ) // Initial TFT
105. case( i )
106.
107. 0: // Oscillator, On
108. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
109. else begin isDo[2] <= 1'b1; D1 <= 8'h00; D2 <= 16'h0001; end
110.
111. 1: // Power control 1
112. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
113. else begin isDo[2] <= 1'b1; D1 <= 8'h03; D2 <= 16'h6664; end
114.
115. 2: // Power control 2
116. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
117. else begin isDo[2] <= 1'b1; D1 <= 8'h0C; D2 <= 16'h0000; end
118.
119. 3: // Power control 3
120. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
121. else begin isDo[2] <= 1'b1; D1 <= 8'h0D; D2 <= 16'h080C; end
122.
123. 4: // Power control 4
124. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
125. else begin isDo[2] <= 1'b1; D1 <= 8'h0E; D2 <= 16'h2B00; end
126.
127. 5: // Power control 5
128. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
129. else begin isDo[2] <= 1'b1; D1 <= 8'h1E; D2 <= 16'h00B0; end
130.
131. 6: // Driver output control, MUX = 319, <R><G><B>
132. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
133. else begin isDo[2] <= 1'b1; D1 <= 8'h01; D2 <= 16'h2B3F; end
134.
135. 7: // LCD driving waveform control
136. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
137. else begin isDo[2] <= 1'b1; D1 <= 8'h02; D2 <= 16'h0600; end
138.
139. 8: // Sleep mode, weak-up
140. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
141. else begin isDo[2] <= 1'b1; D1 <= 8'h10; D2 <= 16'h0000; end
142.
143. 9: // Entry mode, 65k color, DM = 2'b00, AM = 0, ID = 2'b11
144. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
145. else begin isDo[2] <= 1'b1; D1 <= 8'h11; D2 <= 16'h6070; end
146.
147. 10: // Compare register
148. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
149. else begin isDo[2] <= 1'b1; D1 <= 8'h05; D2 <= 16'h0000; end
150.
151. 11: // Compare register
152. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
153. else begin isDo[2] <= 1'b1; D1 <= 8'h06; D2 <= 16'h0000; end
154.
155. 12: // Horizontal porch, HBP = 30, XL = 240
156. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
157. else begin isDo[2] <= 1'b1; D1 <= 8'h16; D2 <= 16'hEF1C; end
158.
159. 13: // Vertical porch, VBP = 1, VBP = 4
160. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
161. else begin isDo[2] <= 1'b1; D1 <= 8'h17; D2 <= 16'h0003; end
162.
163. 14: // Display control, 8 color mode, display on, operation on
164. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
165. else begin isDo[2] <= 1'b1; D1 <= 8'h07; D2 <= 16'h0233; end
166.
167. 15: // Frame cycle control
168. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
169. else begin isDo[2] <= 1'b1; D1 <= 8'h0B; D2 <= 16'h0000; end
170.
171. 16: // Gate scan position, SCN = 0
172. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
173. else begin isDo[2] <= 1'b1; D1 <= 8'h0F; D2 <= 16'h0000; end
174.
175. 17: // Vertical scroll control
176. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
177. else begin isDo[2] <= 1'b1; D1 <= 8'h41; D2 <= 16'h0000; end
178.
179. 18: // Vertical scroll control
180. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
181. else begin isDo[2] <= 1'b1; D1 <= 8'h42; D2 <= 16'h0000; end
182.
183. 19: // 1st screen driving position, G0~
184. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
185. else begin isDo[2] <= 1'b1; D1 <= 8'h48; D2 <= 16'h0000; end
186.
187. 20: // 1st screen driving position, ~G319
188. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
189. else begin isDo[2] <= 1'b1; D1 <= 8'h49; D2 <= 16'h013F; end
190.
191. 21: // 2nd screen driving position
192. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
193. else begin isDo[2] <= 1'b1; D1 <= 8'h4A; D2 <= 16'h0000; end
194.
195. 22: // 2nd screen driving position
196. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
197. else begin isDo[2] <= 1'b1; D1 <= 8'h4B; D2 <= 16'h0000; end
198.
199. 23: // Horizontal RAM address position, HSA = 0 HEA = 239
200. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
201. else begin isDo[2] <= 1'b1; D1 <= 8'h44; D2 <= 16'hEF00; end
202.
203. 24: // Vertical RAM address position, VSA = 0
204. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
205. else begin isDo[2] <= 1'b1; D1 <= 8'h45; D2 <= 16'h0000; end
206.
207. 25: // Vertical RAM address position, VEA = 319
208. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
209. else begin isDo[2] <= 1'b1; D1 <= 8'h46; D2 <= 16'h013F; end
210.
211. 26: // Gamma control, PKP0~1
212. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
213. else begin isDo[2] <= 1'b1; D1 <= 8'h30; D2 <= 16'h0707; end
214.
215. 27: // Gamma control, PKP2~3
216. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
217. else begin isDo[2] <= 1'b1; D1 <= 8'h31; D2 <= 16'h0204; end
218.
219. 28: // Gamma control, PKP4~5
220. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
221. else begin isDo[2] <= 1'b1; D1 <= 8'h32; D2 <= 16'h0204; end
222.
223. 29: // Gamma control, PRP0~1
224. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
225. else begin isDo[2] <= 1'b1; D1 <= 8'h33; D2 <= 16'h0502; end
226.
227. 30: // Gamma control, PKN0~1
228. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
229. else begin isDo[2] <= 1'b1; D1 <= 8'h34; D2 <= 16'h0507; end
230.
231. 31: // Gamma control, PKN2~3
232. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
233. else begin isDo[2] <= 1'b1; D1 <= 8'h35; D2 <= 16'h0204; end
234.
235. 32: // Gamma control, PKN4~5
236. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
237. else begin isDo[2] <= 1'b1; D1 <= 8'h36; D2 <= 16'h0204; end
238.
239. 33: // Gamma control, PRN0~1
240. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
241. else begin isDo[2] <= 1'b1; D1 <= 8'h37; D2 <= 16'h0502; end
242.
243. 34: // Gamma control, VRP0~1
244. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
245. else begin isDo[2] <= 1'b1; D1 <= 8'h3A; D2 <= 16'h0302; end
246.
247. 35: // Gamma control, VRN0~1
248. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
249. else begin isDo[2] <= 1'b1; D1 <= 8'h3B; D2 <= 16'h0302; end
250.
251. 36: // RAM write data mask
252. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
253. else begin isDo[2] <= 1'b1; D1 <= 8'h23; D2 <= 16'h0000; end
254.
255. 37: // RAM write data mask
256. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
257. else begin isDo[2] <= 1'b1; D1 <= 8'h24; D2 <= 16'h0000; end
258.
259. 38: // Unknown
260. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
261. else begin isDo[2] <= 1'b1; D1 <= 8'h25; D2 <= 16'h8000; end
262.
263. 39: // RAM address set, horizontal(X)
264. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
265. else begin isDo[2] <= 1'b1; D1 <= 8'h4E; D2 <= 16'h0000; end
266.
267. 40: // RAM address set, vertical(Y)
268. if( iDone ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
269. else begin isDo[2] <= 1'b1; D1 <= 8'h4F; D2 <= 16'h0000; end
270.
271. 41:
272. begin isDone <= 1'b1; i <= i + 1'b1; end
273.
274. 42:
275. begin isDone <= 1'b0; i <= 6'd0; end
276.
277. endcase
278.
以上內容為初始化過程。該控制模塊之所以變煩,原因都是這家伙在搞鬼。
279. assign oStart = isDo;
280. assign oDone = isDone;
281. assign oAddr = D1;
282. assign oData = D2;
283.
284. endmodule
以上內容為相關的輸出驅動聲明。
tft_basemode.v
該組合模塊的連線部署請參考圖27.20。
1. module tft_basemod
2. (
3. input CLOCK,RESET,
4. output TFT_RST,
5. output TFT_RS, // 1 = Data, 0 = Command(Register)
6. output TFT_CS_N,
7. output TFT_WR_N,
8. output TFT_RD_N,
9. output [15:0]TFT_DB,
10. input [2:0]iStart,
11. output oDone
12. );
13. wire [2:0]StartU1;
14. wire [7:0]AddrU1;
15. wire [15:0]DataU1;
16.
17. tft_ctrlmod U1
18. (
19. .CLOCK( CLOCK ),
20. .RESET( RESET ),
21. .iStart( iStart ), // < top
22. .oDone( oDone ), // > top
23. .oStart( StartU1 ), // > U2
24. .iDone( DoneU2 ), // < U2
25. .oAddr( AddrU1 ), // > U2
26. .oData( DataU1 ) // > U2
27. );
28.
29. wire DoneU2;
30.
31. tft_funcmod U2
32. (
33. .CLOCK( CLOCK ),
34. .RESET( RESET ),
35. .TFT_RS( TFT_RS ), // > top
36. .TFT_CS_N( TFT_CS_N ), // > top
37. .TFT_WR_N( TFT_WR_N ), // > top
38. .TFT_RD_N( TFT_RD_N ), // > top
39. .TFT_DB( TFT_DB ), // > top
40. .iStart( StartU1 ), // < U1
41. .oDone( DoneU2 ), // > U1
42. .iAddr( AddrU1 ), // < U1
43. .iData( DataU1 ) // < U1
44. );
45.
46. assign TFT_RST = 1'b1;
47.
48. endmodule
內容自己看着辦吧,不過還是注意一下第46行的輸出聲明,其中TFT_RST賦值為1。
tft_demov.
圖27.23 實驗二十七的建模圖。
圖27.23是實驗二十七的建模圖,其中TFT基礎模塊被核心操作調用,具體內容讓我們來看代碼吧。
1. module tft_demo
2. (
3. input CLOCK, RESET,
4. output TFT_RST,
5. output TFT_RS,
6. output TFT_CS_N,
7. output TFT_WR_N,
8. output TFT_RD_N,
9. output [15:0]TFT_DB
10. );
以上內容為相關的出入端聲明。
11. wire DoneU1;
12.
13. tft_basemod U1
14. (
15. .CLOCK( CLOCK ),
16. .RESET( RESET ),
17. .TFT_RST( TFT_RST ),
18. .TFT_RS( TFT_RS ),
19. .TFT_CS_N( TFT_CS_N ),
20. .TFT_WR_N( TFT_WR_N ),
21. .TFT_RD_N( TFT_RD_N ),
22. .TFT_DB( TFT_DB ),
23. .iStart( isDo ),
24. .oDone( DoneU1 )
25. );
26.
以上內容為 TFT基礎模塊的實例化。
27. reg [3:0]i;
28. reg [2:0]isDo;
29.
30. always @ ( posedge CLOCK or negedge RESET )
31. if( !RESET )
32. begin
33. i <= 4'd0;
34. isDo <= 3'd0;
35. end
36. else
以上內容為相關的寄存器聲明還有復位操作。
37. case( i )
38.
39. 0: // Inital TFT
40. if( DoneU1 ) begin isDo[0] <= 1'b0; i <= i + 1'b1; end
41. else begin isDo[0] <= 1'b1; end
42.
43. 1: // Clear Screen
44. if( DoneU1 ) begin isDo[1] <= 1'b0; i <= i + 1'b1; end
45. else begin isDo[1] <= 1'b1; end
46.
47. 2: // Draw Function
48. if( DoneU1 ) begin isDo[2] <= 1'b0; i <= i + 1'b1; end
49. else begin isDo[2] <= 1'b1; end
50.
51. 3:
52. i <= i;
53.
54. endcase
55.
56. endmodule
以上內容為核心操作。步驟0調用isDo[0]執行初始化,步驟1調用isDo[1]執行清屏,步驟2調用isDo[2]執行繪圖。
圖27.24 演示結果。
綜合完畢便下載程序,如果TFT LCD戶縣由上至下的紅色漸變,結果如圖27.24所示,表示實驗成功。事實上,圖27.24是由 32 個,高為10寬為240的矩形組成,第0個矩形接近黑色,第31個矩形則接近紅色。如此一來,便產生紅色漸變的效果。
細節一:完整的個體模塊
事實上,本實驗的TFT基礎模塊還不能稱為完整的個體,因為實驗二十七並沒有明確的設計目的,所以TFT基礎模塊也沒有具體封裝要求,這種情況好比VGA基礎模塊。TFT基礎模塊除了初始化功能還有清屏功能以外,繪圖功能則是為了演示才故意加上去而已。往后如果有需要,讀者再執行擴充吧。
細節二:提高速率
tft功能模塊曾經這樣聲明過,TCSL為50ns,量化結果為 3,還有TCSH 為 500ns,量化結果則是 25。根據手冊,寫周期 TCYCLE 最小可以去到 100ns,亦即TCSL還有TCSH皆為50ns。因此,速率也從原本的 1.818181 Mhz 飛升為 10Mhz。經過測試,筆者也沒發現什么問題。不過,膽小的筆者認為,如果沒有必要,TCSH還是設為500ns 比較保險,因為意外從總是突如其來。