RGB LCD 示例

编辑于2022.04.12

先介绍时序

RGB LCD 显示协议和 VGA 类似,通信都有专用的行同步、场同步信号线。它们的主要区别是前者传输用的是数字信号,后者传输走的是模拟信号。

下面就介绍 VGA 的时序

上图分别是 VGA 在数据传输中的行同步、场同步时序

从时序图中可以看出,不论是显示一行数据还是一列数据,都需要一个对应的同步(sync)信号,数据的传输在两个同步信号的脉冲之间完成

每一行的数据包括显示前沿(back porch)、有效数据(active video)、显示后沿(front porch)三个阶段

其中的有效数据就是我们常说的分辨率,而显示前后沿的参数需要参考具体的分辨率与帧数进行设置,相关参数可以参考典型参数,链接在此: http://www.tinyvga.com/vga-timing

这块5寸屏幕的控制时序略有不同,相关参数的设置可以查看规格书

其他尺寸的屏幕相关规格书均可以在这里下载 点我

下面为这块 LCD 时序相关的截图

上面一张图是时序中的参数表,下面的图是时序图

从时序图中看出,这块屏幕可以不用设置前后沿,可以只设置消影(blanking)时间,通过实际的程序证明,两种方式都是可以的

新建工程

新建工程方法参考自建点灯文章(点我)

生成屏幕时钟

  • 这里需要用到高云半导体官方的IP核

板载的晶振时钟为 24MHz ,但是我们的屏幕要求 33.3MHZ 的时钟,所以我们需要使用对应的ip核来生成相应的时钟

pll

这里需要使用到 IP Core Generate ,位置在 Tools -> IP Core Generate

双击 rPLL ,在弹出窗口 language 选择 Verilog ,CLKIN 为 24MHz ,CLKOUT 为 200MHz,CLKOUTD 要选择 Enable,然后生成时钟为 33.33MHz,Tolerance 选择 0.2%

点击ok后提示是否需要添加到当前工程,此时应当选择确定

接着会出现一个例化的tmp文件,用来例化所设置的ip。比如下图中例子

osc

这一步可以不做

系统的时钟可以由外部时钟提供,也可以使用 OSC 生成的时钟

同样也是使用 IP Core Generate

找到 OSC 并双击打开进行分频的设置

在帮助页面可以知道,GW1N-1 系列的 fpga 的 OSC 是从 240MHz 进行分频的,所以要产生 24MHz 的时钟,只需要进行 10 的分频

屏幕驱动代码

  • 首先新建一个额外的verilog文件来保存下面要编写的代码

端口定义

首先需要先定义出驱动屏幕所需要的端口

module VGAMod
(
    input                   CLK,
    input                   nRST,

    input                   PixelClk,

    output                  LCD_DE,
    output                  LCD_HSYNC,
    output                  LCD_VSYNC,

    output          [4:0]   LCD_B,
    output          [5:0]   LCD_G,
    output          [4:0]   LCD_R
);

本例程使用RGB565作为驱动方式;

时序常量

接着定义出时序图上所要求的常量


localparam      V_BackPorch = 16'd6; //0 or 45
localparam      V_Pluse 	= 16'd5; 
localparam      HightPixel  = 16'd480;
localparam      V_FrontPorch= 16'd62; //45 or 0

localparam      H_BackPorch = 16'd182; 	
localparam      H_Pluse 	= 16'd1; 
localparam      WidthPixel  = 16'd800;
localparam      H_FrontPorch= 16'd210;

localparam      PixelForHS  =   WidthPixel + H_BackPorch + H_FrontPorch;  	
localparam      LineForVS   =   HightPixel + V_BackPorch + V_FrontPorch;

首先是设置时序相关的参数:前沿、后沿、有效像素

关于显示前沿、后沿,前面也说了,可以合并为一个消影时间,就是可以把其中一个设置为0,另一个设置为消影时间。反正前后沿的时间加起来符合表中的时间要求就可以

定义变量

  • 定义一些变量能够容易编写程序

reg [15:0] LineCount;
reg [15:0] PixelCount;

reg	[9:0]  Data_R;
reg	[9:0]  Data_G;
reg	[9:0]  Data_B;

同步信号

这段代码产生同步信号,需要注意的是,这块屏幕的同步信号是负极性使能

    always @(  posedge PixelClk or negedge nRST  )begin
        if( !nRST ) begin
            LineCount       <=  16'b0;    
            PixelCount      <=  16'b0;
            end
        else if(  PixelCount  ==  PixelForHS ) begin
            PixelCount      <=  16'b0;
            LineCount       <=  LineCount + 1'b1;
            end
        else if(  LineCount  == LineForVS  ) begin
            LineCount       <=  16'b0;
            PixelCount      <=  16'b0;
            end
        else
            PixelCount      <=  PixelCount + 1'b1;
    end

   always @(  posedge PixelClk or negedge nRST  )begin
        if( !nRST ) begin
    		Data_R <= 9'b0;
    		Data_G <= 9'b0;
    		Data_B <= 9'b0;
            end
        else begin
    		end
    end

//注意这里HSYNC和VSYNC负极性
assign  LCD_HSYNC = (( PixelCount >= H_Pluse)&&( PixelCount <= (PixelForHS-H_FrontPorch))) ? 1'b0 : 1'b1;
assign  LCD_VSYNC = ((( LineCount  >= V_Pluse )&&( LineCount  <= (LineForVS-0) )) ) ? 1'b0 : 1'b1;

使能信号

这段代码设置 LCD 使能图像显示,这块屏幕需要控制一个管脚用作显示开关,实际这个信号就是传输图像有效的那 800*480 的数据时置 1

assign  LCD_DE = (  ( PixelCount >= H_BackPorch )&&
                    ( PixelCount <= PixelForHS-H_FrontPorch ) &&
                    ( LineCount >= V_BackPorch ) &&
                    ( LineCount <= LineForVS-V_FrontPorch-1 ))  ? 1'b1 : 1'b0;
                    //这里不减一,会抖动

测试彩条

  • 这段代码用来产生 LCD 的测试数据,产生彩条显示
    localparam          Colorbar_width   =   WidthPixel / 16;

    assign  LCD_R     = ( PixelCount < ( H_BackPorch +  Colorbar_width * 0  )) ? 5'b00000 :
                        ( PixelCount < ( H_BackPorch +  Colorbar_width * 1  )) ? 5'b00001 : 
                        ( PixelCount < ( H_BackPorch +  Colorbar_width * 2  )) ? 5'b00010 :    
                        ( PixelCount < ( H_BackPorch +  Colorbar_width * 3  )) ? 5'b00100 :    
                        ( PixelCount < ( H_BackPorch +  Colorbar_width * 4  )) ? 5'b01000 :    
                        ( PixelCount < ( H_BackPorch +  Colorbar_width * 5  )) ? 5'b10000 :  5'b00000;

    assign  LCD_G    =  ( PixelCount < ( H_BackPorch +  Colorbar_width * 6  )) ? 6'b000001: 
                        ( PixelCount < ( H_BackPorch +  Colorbar_width * 7  )) ? 6'b000010:    
                        ( PixelCount < ( H_BackPorch +  Colorbar_width * 8  )) ? 6'b000100:    
                        ( PixelCount < ( H_BackPorch +  Colorbar_width * 9  )) ? 6'b001000:    
                        ( PixelCount < ( H_BackPorch +  Colorbar_width * 10 )) ? 6'b010000:    
                        ( PixelCount < ( H_BackPorch +  Colorbar_width * 11 )) ? 6'b100000:  6'b000000;

    assign  LCD_B    =  ( PixelCount < ( H_BackPorch +  Colorbar_width * 12 )) ? 5'b00001 : 
                        ( PixelCount < ( H_BackPorch +  Colorbar_width * 13 )) ? 5'b00010 :    
                        ( PixelCount < ( H_BackPorch +  Colorbar_width * 14 )) ? 5'b00100 :    
                        ( PixelCount < ( H_BackPorch +  Colorbar_width * 15 )) ? 5'b01000 :    
                        ( PixelCount < ( H_BackPorch +  Colorbar_width * 16 )) ? 5'b10000 :  5'b00000;

当然在最后的驱动文件最后别忘记换行加上 endmodule

到这里驱动模块的编写已经完成了。

在顶层模块中例化

  • 这里也是要新建verilog文件的
  • 新建文件之后直接把下面的内容复制进去保存即可
module TOP //设置顶层模块
(
    input			nRST,
    input           XTAL_IN,

    output			LCD_CLK,
    output			LCD_HYNC,
    output			LCD_SYNC,
    output			LCD_DEN,
    output	[4:0]	LCD_R,
    output	[5:0]	LCD_G,
    output	[4:0]	LCD_B

); // 列出需要的端口

    wire		CLK_SYS;	
    wire		CLK_PIX;

    //例化pll
    Gowin_rPLL chip_pll(
        .clkout(CLK_SYS), //output clkout     //200M
        .clkoutd(CLK_PIX), //output clkoutd   //33.00M
        .clkin(XTAL_IN)    //input clkin
    );	

    VGAMod	VGAMod_inst //例化vga驱动
    (
    	.CLK		(	CLK_SYS     ),
    	.nRST		(	nRST		),

    	.PixelClk	(	CLK_PIX		),
    	.LCD_DE		(	LCD_DEN	 	),
    	.LCD_HSYNC	(	LCD_HYNC 	),
    	.LCD_VSYNC	(	LCD_SYNC 	),

    	.LCD_B		(	LCD_B		),
    	.LCD_G		(	LCD_G		),
    	.LCD_R		(	LCD_R		)
    );

    assign		LCD_CLK		=	CLK_PIX;

endmodule

综合、约束、布局布线

综合

完成上面步骤后转到“Process”界面下,对编辑好的代码进行综合,即运行“Synthesize”

运行的结果如下图出现

说明前面编辑的代码无误;如果有错,根据错误提示进行改正即可。

管脚约束

对应的管脚约束如下表格所示;
关于管脚约束方法可以参考自建点灯文章(点我)里面的约束方法。
感觉麻烦的话也可以直接复制准备好的文件(点我),将页面里的内容复制到工程目录里 .cst 文件中(如果没有.cst 文件那么自己新建一个物理管脚约束文件) 即可。

PORT PIN PORT PIN PORT PIN
LCD_CLK 11 nRST 14 XTAL_IN 35
LCD_B[4] 45 LCD_B[3] 44 LCD_B[2] 43
LCD_B[1] 42 LCD_B[0] 41 LCD_G[5] 40
LCD_G[4] 39 LCD_G[3] 38 LCD_G[2] 34
LCD_G[1] 33 LCD_G[0] 32 LCD_R[4] 31
LCD_R[3] 30 LCD_R[2] 29 LCD_R[1] 28
LCD_R[0] 27 LCD_DEN 5 LCD_SYNC 46
LCD_HYNC 10

布局布线

管脚约束之后需要在设置里面开启引脚复用才能完成布局布线。
具体位置在 软件顶部菜单栏 Project -> Configuration -> Place&Route -> Dual-Purpose Pin

设置完上面的之后。
就可以开始布局布线(Place&Route)了。
完成后就可以给开发板验证代码内容了。

烧录

布局布线结束后生成比特流,就可以烧录开发板了。

结束

上面差不多叙述了所有代码。
整个工程可以参考 这里

返回上一页(Back)