原创 基于Avalon总线的TFT LCD 控制器的设计

2008-7-16 19:24 2925 4 4 分类: FPGA/CPLD

一、设计目标
    设计一个LCD控制器,该控制器基于Altera的SOPC系统,通过SOPC中的Avalon总线接口与Nios II处理器和SDRAM控制器通信,使之能显示640*480分辨率,显示颜色深度达到16bit,输出接口兼容TFT LCD。


二.设计原理
1、系统工作原理:
    如图1所示,Nios II处理器在SDRAM中开辟framebuffer,可以是单缓冲也可以是双缓冲,以单缓冲为例。Nios II处理器将一帧图象数据(640×480×2Bytes,RGB565,16bit)存入framebuffer,然后将framebuffer的首地址写入到LCD控制器,并启动LCD控制器,该控制器自动从传来的首地址处开始读取图象数据,并按照TFT的格式输出。
 
    图中各模块由Avalon Bus连接在一起。Avalon Bus是一种简单的总线结构,在SOPC中,Nios II软核处理器和各种外设都通过Avalon Bus连接在一起。由图1可以看出,作为Slaver的SDRAM Controller分别要受到Processor 和LCD Controller的控制,为了解决总线冲突,Avalon Bus自动在有冲突的接口上加入了Arbitrator这样一个仲裁模块,用于合理分配总线时间,用户通过改变每个模块的权值来改变对其分配总线时间的多少。在这个系统中,SDRAM Controller处的冲突是影响整个系统性能的关键。以SDRAM时钟频率为100MHz计算,16bit位宽的SDRAM其数据总带宽为200MByte/s,640*480*2Bytes*60Hz的TFT LCD要占用36MByte/s左右的带宽,这对于还要处理其他任务的处理器来说是很大的影响。解决的办法是另外增加一块SDRAM,专门用作Frame buffer,这样就可以有效减少对系统总线带宽的占用。

2、LCD Controller工作原理
    如图2所示,LCD Controller主要由Avalon 总线接口、寄存器组、控制模块、DMA、FIFO以及时序生成模块组成。每个寄存器独立编址,处理器可以通过总线读写存储器的方式来访问。处理器通过Avalon总线接读写寄存器,从而完成对LCD控制器工作状态的设定与控制。
 
    控制器启动后,DMA Master通过avalon总线读取SDRAM中的数据,然后存入FIFO中,时序生成模块按照TFT时序要求从FIFO中读取数据,然后送出去显示。整个数据读取过程不需要处理器干涉,实际上是一个DMA过程。
    考虑到DMA读取的速度与TFT时序发生器输出的速度不一致(前者大于后者),所以在DMA与TFT时序发生器之间加入了FIFO用来缓冲数据。DMA有控制模块控制其工作。控制模块不断检测FIFO状态,当FIFO快满时,暂停DMA,当FIFO快空时,重新启动DMA,如此循环,可保证FIFO不溢出,保证显示画面连续稳定。


三、设计细节
1、Avalon Bus Slaver从总线接口
Avalon从总线接口负责处理器与LCD控制器的接口控制,LCD控制器在整个系统中作为从设备,通过该接口,接受CPU的控制。根据设计需要,该接口所需要的各信号为:

input: 
chipselect片选,高电平有效
Write_n写信号,低电平有效
Read_n读信号,低电平有效
Reset_n复位,低电平有效
clk系统时钟
address[1:0]地址信号
writedata[31:0]写数据
output: 
readdata[31:0]读数据
irq_n中断请求,低电平有效

这些信号均要求满足Avalon总线的时序要求:(采用基本时序)
读时序:
 
    如图3所示,读时序在一个系统时钟周期内完成。在第一个clk上升沿,系统将address、byteenable、read信号输出到从设备接口,系统内部对address进行地址译码,生成chipselect信号也传输到从设备接口,一旦chipselect被置高,从设备立即将readdata信号输出到从设备接口,系统在第二个时钟上升沿读取该数据,从而完成一次读操作。
    为满足此时序,可利用第一个clk的下降沿来检测address、read、chipselect信号是否有效,如果有效,立即输出数据。
代码如下:
always @(posedge slave_clk or negedge reset_n)
begin
if(reset_n == 0)
begin
slave_readdata<=0;
end
else if(slave_chipselect && ~slave_read_n)
begin
case(slave_address)
2''b00:
slave_readdata<=controlReg;
2''b01:
slave_readdata<=interStatus;
2''b10:
slave_readdata<=startAddress;
2''b11:
slave_readdata<=addressCounter;
endcase
end

写时序:
 
    如图4所示,写操作也在一个时钟周期内完成。在第一个clk上升沿,系统设置address、byteenable、writedata、write信号有效,然后内部将address信号译码得出chipselect信号并立即传输到从设备接口,从设备在第二个clk上升沿捕获接口上的数据,从而完成一个写操作。
    为满足此时序,从设备接口在第二个clk上升沿检测address、write、chipselect信号是否有效,若有效,则将数据口上的数据writedata写入到内部寄存器。
代码如下:
always @(posedge slave_clk or negedge reset_n)
begin
if(reset_n == 0)
begin
controlReg<=0;
//
statusReg<=0;
startAddress<=0;
frameLength<=0;
end
else if(slave_chipselect && ~slave_write_n)
begin
case(slave_address)
2''b00:
controlReg<=slave_writedata;

//2''b01:
statusReg<=slave_writedata;
2''b10:
startAddress<=slave_writedata;
2''b11:
frameLength<=slave_writedata;
endcase
end
end
clk信号为系统时钟信号,这里取100MHz。
reset_n为复位信号,低电平有效,复位后,LCD控制器应该处于一个设定好的状态。
irq为中断请求信号,当控制器满足下列条件之一时,向系统发出中断请求信号,将irq信号置高,直到系统响应该信号,并用软件将其清零。(暂时未使用)

2、Avalon Bus DMA Master主设备接口
    Avalon Bus DMA Master负责按照控制模块的指令,读取SDRAM中的数据,并写入到FIFO中。其核心部分是DMA地址累加器,当条件满足时,地址累加器开始在100MHz的时钟下以4为单位开始累加(为何以4为单位,是由avalon总线的地址映射方式决定的。avalon总线按字节进行编址,而SDRAM控制器一次读取32bit即4Byte的数据,所以地址要以4为单位累加),用于生成读取SDRAM的地址。读完一帧的数据后,自动复位到首地址,继续累加。
主设备接口信号列表:

input: 
Master_clk主设备时钟100MHz
Master_waitrequest主设备等待信号
Master_readdatavalid主设备数据有效信号
Master_readdata[31:0]主设备数据
Output: 
Master_read_n主设备读信号
Master_address[31:0]主设备地址信号

主设备接口时序:
 
    如图5所示,主设备接口采用带延迟的主设备读传输模式,在这种传输模式下,即使没有接收到上一次的有效数据,主设备也可以发起下一次读命令。当waitrequest信号无效(低电平)时,主设备可以连续的发起读命令,当waitrequest信号有效(高电平)时,主设备开始等待,知道其变为低电平。当readdatavalid信号有效(高电平)时,表示读数据有效,此时主设备可以锁存数据口上的有效数据。这里我们没有使用flush信号,flush信号会清楚前面一切未完成的读命令。Avalon总线保证数据的输出顺序与主设备要求的顺序一致(即与主设备地址输出顺序一致)。readdatavalid信号可以作为FIFO的wrreq信号,这样可以直接将读回来的数据写入到FIFO中。
    地址累加器满足以下条件时,则开始累加,当当前地址等于尾地址时,则复位累加器,使之重新开始从首地址累加:
1)、非FIFO快满(即fifo_almostfull信号无效)
2)、master_waitrequest无效
地址累加器代码:
wire[31:0] endAddress;
reg[31:0] addressCounter;
wire addressCounter_sload;
wire addressCounter_inc;
assign endAddress="startAddress"+frameLength;
assign addressCounter_sload=(~controller_GoBit) | (addressCounter == endAddress);
assign addressCounter_inc=(~master_read_n) & (~master_waitrequest) &(controller_GoBit);
assign master_address=addressCounter;
assign master_read_n=~(fifo_almostfull ^ controller_GoBit);
always @(posedge master_clk or negedge reset_n)
begin

if(reset_n == 0)
begin
addressCounter<=0;
end
else if(addressCounter_sload == 1)
begin
addressCounter<=startAddress;
end
else if(addressCounter_inc == 1)
begin
// address incs as 0,4,8,16,...
addressCounter<=addressCounter+4;
end
end

3、FIFO(先入先出存储器)
    FIFO的作用是对DMA输出的图象数据进行缓存,以匹配时序控制模块的输出速度。FIFO大小暂定为4096*16bit,在实际设计时,再根据系统需要以及资源状况作出适当调整,原则是,在系统资源允许的情况下,将FIFO大小尽量设置大点。
    FIFO由DMA控制器写入数据,写入时钟为100MHz;由LCD控制器的时序发生模块读出数据,读出时钟为PCLK,即LCD的象素点扫描频率,通常取25MHz。在独立的写时钟和读时钟作用下,FIFO可以提供rdusedw[11:0]信号,用于指示FIFO中已经使用掉的容量。系统可以设置一个上限和一个下限,当FIFO中的数据量高于上限或低于下限时,控制器暂停DMA传输或启动DMA传输,用以保证系统性能。
下图是该FIFO的读写时序图。
 
    在本例应用中,将wrclk接系统时钟(100MHz),wrreq接master_readdatavalid,data接writedata,即可完成DMA的数据写入操作;将rdclk接12.5MHz(因为TFT的时钟为25MHz,数据宽度为16bit,而FIFO的宽度为32bit,所以用一半的时钟12.5MHz去读取FIFO,然后依次输出32bit的高16bit和低16bit),rdreq由时序发生模块控制,即可在每个rdclk的上升沿读出一个数据到q。aclr接~reset_n,可以完成复位操作。当然,所有信号都受controller_GoBit的控制。
FIFO代码如下:
assign fifo_aclr=(~reset_n) | (~controller_GoBit);
assign fifo_wrdata=master_readdata;
assign fifo_wrclk=master_clk;
assign fifo_wrreq=master_readdatavalid & controller_GoBit;
assign fifo_rdclk=clk_12M;
assign tft_data[15:0]=clk_12M ? fifo_rddata[31:16] : fifo_rddata[15:0];
lcd_fifo lcd_fifo_0(
.aclr(fifo_aclr),
.data(fifo_wrdata),
.rdclk(fifo_rdclk),
.rdreq(fifo_rdreq),
.wrclk(fifo_wrclk),
.wrreq(fifo_wrreq),
.q(fifo_rddata),
.wrusedw(fifo_wrusedw)
);

4、Registers(寄存器组)
为了简化设计,本例只设置四个32bit的可读写寄存器,用于控制LCD控制器的工作和指示其工作状态。

controlReg控制寄存器,用于控制LCD控制器的运行与停止
statusReg 状态寄存器,用于指示LCD控制器的工作状态(保留)
startAddressDMA首地址,即缓冲区首地址
frameLength帧长度

5、Control(控制模块)
       控制模块用于协调LCD控制器各部分的工作,用于连接寄存器组与时序发生器。在实际的设计中并没有独立的这么一个控制模块,各种控制关系实际上已经集成到各个分模块里面去了。

6、LCD Timing Generator(LCD显示时序发生器)
    时序发生器用于产生TFT屏幕所需的时序,将图象数据按特定的时序输出。本设计专门针对三菱公司的AA084VC05液晶屏设计,下面是其时序分析:

  
    LCD时序发生器以DCLK为时钟基准,该DCLK即上面所说的PCLK,也就是像点时钟,每个象素点的数据以该时钟驱动进入LCD。如上图所示,为AA084VC05的水平扫描时序,其中,DATA为18位数据信号(本设计中只用其中的16位),DENA为数据有效信号,高电平使能,其有效宽度THA为640个DCLK;HD为水平同步信号,低电平有效,其有效宽度TWHL为96个DCLK。一行640个象素扫描完毕之后,控制器将驱动HD有效,在HD有效之前插入THFP(Horizontal Front Porch)为16个DCLK,有效之后插入THBP(Horizontal Back Porch)为144个DCLK,然后再开始下一行的扫描。如此一来,行扫描信号的频率FH典型值为31.5KHz。而读FIFO信号要提前DENA信号一个时钟节拍到来,提前一个时钟节拍结束,因为该FIFO有一个时钟节拍的延迟。
       AA084VC05的垂直扫描时序与水平扫描时序类似,该时序以HD为时钟基准,其中,VD为垂直同步信号(帧同步)。每扫描完一帧(480行),控制器将驱动VD有效(低电平),有效宽度TWVL为2个HD。同样,在VD有效之前插入TVFP(Vertical Front Porch)为10个HD,有效之后插入TVBP(Vertical Back Porch)为35个HD,如此一来,垂直扫描信号频率FV的典型值为60Hz。
    在本设计中,所有这些时序参数都用parameter来表示,在以后更一般化的设计中,这些参数将由寄存器组来设置。
parameter LINES= 480;
//Number of lines
parameter COLUMNS = 640;
//Pixel Number Per Line
parameter THFP= 16;
// Horizontal Front Porch(DCLK)
parameter THBP= 144;
// Horizontal Back Porch(DCLK)
parameter TWHL= 96;
// Horizontal Low Width(DCLK)
parameter TVFP= 10;
// Vertical Front Porch(HD)
parameter TVBP= 35;
// Vertical Back Porch(HD)
parameter TWVL= 2;
// Vertical Low Width(HD)
时序发生器采用状态机实现,如下代码:
reg oHSYNC;
reg oVSYNC;
reg oDENA;
reg lineENA;
reg[9:0] cCounter;
reg[9:0] lCounter;
assign oSC= 0;
assign oPCLK = clk_25M;
assign oPDATA =oDENA ? {tft_data[15:11],1''b0,tft_data[10:0],1''b0} : 18''hz;
//horizontal sync signal generator
always @(posedge clk_25M)
begin
if(tft_reset_n == 0)
//reset active
begin
oDENA<=0;
oHSYNC<=0;
cCounter[9:0]<=0;
fifo_rdreq<=0;
end
else if(tft_GoBit == 1)
begin
case(cCounter)
0:
begin
oHSYNC<=0;
cCounter<=cCounter + 1;
end
TWHL - 1:
begin
oHSYNC<=1;
cCounter<=cCounter + 1;
end
THBP - 2:
begin
//generate the fifo read request signal,
//sets earlier for 1 clock than oDENA
fifo_rdreq<=lineENA & tft_GoBit;
cCounter<=cCounter + 1;
end
THBP - 1:
begin
oDENA<=lineENA & tft_GoBit;
//only when lineENA active then oDENA active
cCounter<=cCounter + 1;
end
THBP + COLUMNS - 2:
begin
//fifo read request signal ends earlier for 1 clock than oDENA
fifo_rdreq<=0;
cCounter<=cCounter + 1;
end
THBP + COLUMNS - 1:
begin
oDENA<=0;
cCounter<=cCounter + 1;
end
THBP + COLUMNS + THFP - 1:
begin
oHSYNC<=0;
cCounter<=0;
end
default:
cCounter<=cCounter + 1;
endcase
end
end

//vertical sync signal generator
always @(negedge oHSYNC)
begin
if(tft_reset_n == 0)
//reset active
begin
lineENA<=0;
oVSYNC<=0;
lCounter[9:0]<=0;
end
else if(tft_GoBit == 1)begin
case(lCounter)
0:
begin
oVSYNC<=0;
lCounter<=lCounter + 1;
end
TWVL - 1:
begin
oVSYNC<=1;
lCounter<=lCounter + 1;
end
TVBP - 1:
begin
lineENA<=1;
lCounter<=lCounter + 1;
end
TVBP + LINES - 1:
begin
lineENA<=0;
lCounter<=lCounter + 1;
end
TVBP + LINES + TVFP - 1:
begin
oVSYNC<=0;
lCounter<=0;
end
default:
lCounter<=lCounter + 1;
endcase
end
end


四、设计总结
    本文设计实现了一个简单的基于Avalon总线的TFT LCD控制器,能实现640×480,颜色深度为16bit的彩色图形显示,可应用于各种TFT LCD,亦可改写为VGA控制器。在SOPC系统的使用中,有较大的灵活性。同时,通过本设计的实现过程,我也从中学到了不少知识,对SOPC,对Avalon总线等的理解更深了一步。

文章评论0条评论)

登录后参与讨论
我要评论
0
4
关闭 站长推荐上一条 /2 下一条