实验二十七: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 比较保险,因为意外从总是突如其来。