【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十七:TFT模块 - 显示

简介: 实验二十七:TFT模块 - 显示 所谓TFT(Thin Film Transistor)就是众多LCD当中,其中一种支持颜色的LCD,相较古老的点阵LCD(12864笑),它可谓高级了。黑金的TFT LCD除了320×240大小以外,内置SSD1289控制器,同时也是独立模块。

实验二十七: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 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.

clip_image048

图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]执行绘图。

clip_image050

图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 比较保险,因为意外从总是突如其来。

目录
相关文章
|
4月前
|
数据采集 传感器 监控
如何在LabVIEW中使用FPGA模块
如何在LabVIEW中使用FPGA模块
102 1
|
12天前
|
算法 数据安全/隐私保护 异构计算
基于FPGA的64QAM基带通信系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本文介绍了基于FPGA的64QAM调制解调通信系统的设计与实现,包括信号生成、调制、解调和误码率测试。系统在Vivado 2019.2中进行了仿真,通过设置不同SNR值(15、20、25)验证了系统的性能,并展示了相应的星座图。核心程序使用Verilog语言编写,加入了信道噪声模块和误码率统计功能,提升了仿真效率。
34 4
|
26天前
|
存储 算法 数据处理
基于FPGA的8PSK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本系统在原有的8PSK调制解调基础上,新增了高斯信道与误码率统计模块,验证了不同SNR条件下的8PSK性能。VIVADO2019.2仿真结果显示,在SNR分别为30dB、15dB和10dB时,系统表现出不同的误码率和星座图分布。8PSK作为一种高效的相位调制技术,广泛应用于无线通信中。FPGA凭借其高度灵活性和并行处理能力,成为实现此类复杂算法的理想平台。系统RTL结构展示了各模块间的连接与协同工作。
45 16
|
16天前
|
算法 数据安全/隐私保护 异构计算
基于FPGA的16QAM基带通信系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本项目基于FPGA实现16QAM调制解调通信系统,使用Verilog语言编写,包括信道模块、误码率统计模块。通过设置不同SNR值(如8dB、12dB、16dB),仿真测试系统的误码性能。项目提供了完整的RTL结构图及操作视频,便于理解和操作。核心程序实现了信号的生成、调制、信道传输、解调及误码统计等功能。
32 3
|
20天前
|
算法 数据安全/隐私保护 异构计算
基于FPGA的16PSK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
### 简介 本项目采用VIVADO 2019.2进行了十六进制相位移键控(16PSK)算法仿真,结果显示,在SNR=30dB时效果为Tttttttttttttt12,在SNR=20dB时效果为Tttttttttttttt34。系统RTL结构如Tttttttttttttt555555所示。16PSK是一种高效的相位调制技术,能在每个符号时间内传输4比特信息,适用于高速数据传输。其工作原理包括将比特流映射到16个相位状态之一(Tttttttttttttt777777),并通过匹配滤波和决策进行解调。具体Verilog核心程序见完整代码。
26 1
|
2月前
|
算法 测试技术 开发工具
基于FPGA的QPSK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
该系统在原有的QPSK调制解调基础上,新增了高斯信道和误码率统计模块,验证了不同SNR条件下的QPSK误码性能。系统包括数据生成、QPSK调制与解调等模块,使用Vivado 2019.2进行仿真,展示了SNR分别为15dB、10dB、5dB和1dB时的误码情况。系统采用Verilog语言实现,具有高效、可靠的特点。
46 3
|
2月前
|
算法 数据安全/隐私保护 异构计算
基于FPGA的2ASK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本项目基于Vivado 2019.2实现了2ASK调制解调系统,新增高斯信道及误码率统计模块,验证了不同SNR条件下的ASK误码表现。2ASK通过改变载波振幅传输二进制信号,其调制解调过程包括系统设计、Verilog编码、仿真测试及FPGA实现,需考虑实时性与并行性,并利用FPGA资源优化非线性操作。
60 0
|
4月前
|
算法 数据安全/隐私保护 异构计算
基于FPGA的MSK调制解调系统verilog开发,包含testbench,同步模块,高斯信道模拟模块,误码率统计模块
升级版FPGA MSK调制解调系统集成AWGN信道模型,支持在Vivado 2019.2中设置不同SNR仿真误码率。示例SNR值从0到15,结果展示解调质量随SNR提升。MATLAB仿真验证了MSK性能,图片显示了仿真结果。 ### 理论概要 研究聚焦于软件无线电中的MSK调制解调,利用Verilog实现。MSK是一种相位连续、恒包络的二进制调制技术,优点包括频谱效率高。系统采用无核设计,关键模块包括调制器、解调器和误码检测。复位、输入数据、中频信号等关键信号通过Verilog描述,并通过Chipscope在线观察。
94 6
基于FPGA的MSK调制解调系统verilog开发,包含testbench,同步模块,高斯信道模拟模块,误码率统计模块
|
3月前
|
算法 数据安全/隐私保护 异构计算
基于FPGA的BPSK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本系统基于Vivado2019.2,在原有BPSK调制解调基础上新增高斯信道及误码率统计模块,可测试不同SNR条件下的误码性能。仿真结果显示,在SNR=0dB时误码较高,随着SNR增至5dB,误码率降低。理论上,BPSK与2ASK信号形式相似,但基带信号不同。BPSK信号功率谱仅含连续谱,且其频谱特性与2ASK相近。系统采用Verilog实现,包括调制、加噪、解调及误码统计等功能,通过改变`i_SNR`值可调整SNR进行测试。
44 1
|
3月前
|
算法 数据安全/隐私保护 异构计算
基于FPGA的2FSK调制解调系统,包含testbench,高斯信道模块,误码率统计模块,可以设置不同SNR
本系统基于FSK调制解调,通过Vivado 2019.2仿真验证了不同信噪比(SNR)下的误码率表现。加入高斯信道与误码统计模块后,仿真结果显示:SNR=16dB时误码极少;随SNR下降至0dB,误码逐渐增多。FSK利用频率变化传输信息,因其易于实现且抗干扰性强,在中低速通信中有广泛应用。2FSK信号由连续谱与离散谱构成,相位连续与否影响功率谱密度衰减特性。Verilog代码实现了FSK调制、加性高斯白噪声信道及解调功能,并计算误码数量。
58 5

热门文章

最新文章