FPGA RGB565的LCD显示屏驱动(Verilog)

文章目录

  • 1屏幕
  • 2行场扫描时序
  • 3分析
  • 4代码
    • 4-1产生9M驱动时钟
    • 4-2驱动部分
    • 4-3显示内容部分
    • 4-3顶层
  • 5结果
    • 5-1
    • 5-2
  • 6Testbench

1屏幕

FPGA RGB565的LCD显示屏驱动(Verilog)_第1张图片
使用像素:480*272的一块RGB565屏幕。
像素时钟:9Mhz
接口:

	    lcd_bl:lcd背光
	    lcd_rgb[15:0]:色彩值
	    lcd_de:当计数器处于valid(有效)区域时应将其拉高,此时输出lcd_rgb[15:0]的值到屏幕的对应像素点上。
	    lcd_hs:可一直将其拉高没有影响
	    lcd_vs:可一直将其拉高没有影响
	    lcd_pclk:9M时钟
	    lcd_rst:lcd单独复位低电平有效

2行场扫描时序

FPGA RGB565的LCD显示屏驱动(Verilog)_第2张图片
FPGA RGB565的LCD显示屏驱动(Verilog)_第3张图片
FPGA RGB565的LCD显示屏驱动(Verilog)_第4张图片

3分析

1、利用ip核倍分频获得9M时钟。
2、使用子母两个计数器对驱动时钟进行计数。
3、子计数器的计数范围为0-524,所代表一个行扫描周期,满载时在下一个时钟周期清零,并使母计数器+1。
4、母计数器的计数范围为0-285,所代表一个场扫描周期,满载时在下一个时钟周期清零。
5、利用组合逻辑将子母俩个计数的计数值做范围限定,确定真正valid的范围。
6、当计数值处于真正valid的范围时,需要回传当前的坐标值,用户根据回传的坐标值设置RGB565的色彩值。

4代码

4-1产生9M驱动时钟

FPGA RGB565的LCD显示屏驱动(Verilog)_第5张图片
-其端口列表如下:

module pll_9m (
	areset,//注意:其为高电平复位
	inclk0,//输入时钟
	c0,    //输出时钟
	locked //稳定锁:当c0输出稳定时locked会从低电平变为高电平
	);

4-2驱动部分

    module lcd_driver(
        input           lcd_clk,      //lcd模块驱动时钟
        input           sys_rst_n,    //复位信号
        
        //下列信号与RGBLCD屏幕相连接                 
        output          lcd_hs,       //LCD 行同步信号
        output          lcd_vs,       //LCD 场同步信号
        output          lcd_de,       //LCD 数据使能
        output  [15:0]  lcd_rgb,      //LCD RGB565颜色数据
        output          lcd_bl,       //LCD 背光控制信号
        output          lcd_rst,      //LCD 复位信号
        output          lcd_pclk,     //LCD 采样时钟
     
        //接口信号,此模块推出valid坐标值,并接收用户发来的RGB565的像素点颜色值   
        input   [15:0]  pixel_data,   //像素点数据
        output  [10:0]  pixel_xpos,   //像素点横坐标
        output  [10:0]  pixel_ypos    //像素点纵坐标
        );                             
                                                            
    //参数列表
    parameter  H_SYNC   =  11'd41;   //行同步
    parameter  H_BACK   =  11'd2;    //行显示后沿
    parameter  H_DISP   =  11'd480;   //行有效数据
    parameter  H_FRONT  =  11'd2;    //行显示前沿
    parameter  H_TOTAL  =  11'd525;  //行扫描周期

    parameter  V_SYNC   =  11'd10;     //场同步
    parameter  V_BACK   =  11'd2;    //场显示后沿
    parameter  V_DISP   =  11'd272;   //场有效数据
    parameter  V_FRONT  =  11'd2;    //场显示前沿
    parameter  V_TOTAL  =  11'd286;   //场扫描周期
              
    //子母两个计数器                                    
    reg  [10:0] cnt_h;
    reg  [10:0] cnt_v;

    //...
    wire       lcd_en;
    wire       data_req; 

    //对lcd屏幕的某些信号加以固定
    assign lcd_bl   = 1'b1;           
    assign lcd_rst  = 1'b1;           
    assign lcd_pclk = lcd_clk;        
    assign lcd_hs  = 1'b1;            
    assign lcd_vs  = 1'b1;      
          
    //当子母计数器的值处于valid范围内时,将lcd_de拉高,此时输出lcd_rgb[15:0]的值到屏幕的对应像素点上。
    assign lcd_de  = lcd_en;   
    assign lcd_en  = (((cnt_h > H_SYNC+H_BACK) && (cnt_h <= H_SYNC+H_BACK+H_DISP))
                     &&((cnt_v > V_SYNC+V_BACK) && (cnt_v <= V_SYNC+V_BACK+V_DISP)))
                     ?  1'b1 : 1'b0;
                     
    //lcd_rgb[15:0]的值由用户(input)给予即为:pixel_data                                     
    assign lcd_rgb = lcd_en ? pixel_data : 16'd0;

    //...此处做一个简单的分析,lcd_de拉高,lcd_rgb[15:0]的值瞬间到达屏幕的对应像素点上。
    //   我们最开始的分析是:cnt值->产生坐标->根据坐标确认有效范围->再往范围里放颜色值
    //   所以说我们在上面写的那个就是有效范围,由于触发器的特性,所以要提前一个时钟周期将坐标值送出,才来的及在lcd_en为1时候准确的送出颜色值
    //   而一场是由很多行组成的,母计数器,要比子计数器慢。子计数器变很多次,母计数器才变一次。所以对子计数器需要提前一个时钟周期发送出坐标。
    //   PS:如果没懂的话也没关系,因为我也比较晕。但只要时序对,屏幕就能出现颜色然后进行simulation或者实物调试
                  
    assign data_req = (((cnt_h > H_SYNC+H_BACK-1'b1) && (cnt_h <= H_SYNC+H_BACK+H_DISP-1'b1))   //对子计数器需要提前一个时钟周期发送出坐标。
                      && ((cnt_v > V_SYNC+V_BACK) && (cnt_v <= V_SYNC+V_BACK+V_DISP)))          //母计数器不变
                      ?  1'b1 : 1'b0;

    //当前像素点坐标                
    assign pixel_xpos = data_req ? (cnt_h - (H_SYNC + H_BACK - 1'b1)) : 11'd0;
    assign pixel_ypos = data_req ? ((cnt_v - (V_SYNC + V_BACK - 1'b1)) - 1'b1) : 11'd0;
                                                                       //这块为什么有个-1 在最下面的测试图片中可以看到解释

    //行计数器对像素时钟计数(子)
    always @(posedge lcd_clk or negedge sys_rst_n) begin         
        if (!sys_rst_n)
            cnt_h <= 11'd0;                                  
        else begin
            if(cnt_h < H_TOTAL - 1'b1)                                               
                cnt_h <= cnt_h + 1'b1;                               
            else 
                cnt_h <= 11'd0;  
        end
    end

    //场计数器对行计数(母)
    always @(posedge lcd_clk or negedge sys_rst_n) begin         
        if (!sys_rst_n)
            cnt_v <= 11'd0;                                  
        else if(cnt_h == H_TOTAL - 1'b1) begin
            if(cnt_v < V_TOTAL - 1'b1)                                               
                cnt_v <= cnt_v + 1'b1;                               
            else 
                cnt_v <= 11'd0;  
        end
    end

    endmodule 

4-3显示内容部分

module lcd_display(
    input             lcd_clk,                  //lcd驱动时钟
    input             sys_rst_n,                //复位信号
    
    input      [10:0] pixel_xpos,               //像素点横坐标
    input      [10:0] pixel_ypos,               //像素点纵坐标    
    output reg [15:0] pixel_data                //像素点数据
    );    
    
localparam WHITE  = 16'b11111_111111_11111;     //RGB565 白色
localparam BLACK  = 16'b00000_000000_00000;     //RGB565 黑色
localparam RED    = 16'b11111_000000_00000;     //RGB565 红色
localparam GREEN  = 16'b00000_111111_00000;     //RGB565 绿色
localparam BLUE   = 16'b00000_000000_11111;     //RGB565 蓝色
localparam BROWN   = 16'h7800;                  //RGB565 棕色
    
    
  //测试边框
      always @(posedge lcd_clk or negedge sys_rst_n) begin         
          if (!sys_rst_n)
              pixel_data <= 16'hffff;
          else begin
          
            if(pixel_xpos == 11'd1)                                              
                pixel_data <= GREEN;                               
            else if(pixel_xpos == 11'd480)
                pixel_data <= GREEN;
            else if(pixel_xpos == 11'd240)
                pixel_data <= GREEN;    
                
            else if(pixel_ypos == 11'd1)
                pixel_data <= GREEN;  
            else if(pixel_ypos == 11'd272)
                pixel_data <= GREEN; 
            else if(pixel_ypos == 11'd136)
                pixel_data <= GREEN;   
            else 
            
                pixel_data <= BROWN;
          end
      end

    
    //彩条
 //   always @(posedge lcd_clk or negedge sys_rst_n) begin         
 //       if (!sys_rst_n)
 //           pixel_data <= 16'hffff;
 //       else begin
 //           if((pixel_xpos >= 1) && (pixel_xpos < (11'd480/5)*1))                                              
 //               pixel_data <= GREEN;                               
 //           else if((pixel_xpos >= (11'd480/5)*1) && (pixel_xpos < (11'd480/5)*2))
 //               pixel_data <= BLACK;  
 //           else if((pixel_xpos >= (11'd480/5)*2) && (pixel_xpos < (11'd480/5)*3))
 //               pixel_data <= RED;  
 //           else if((pixel_xpos >= (11'd480/5)*3) && (pixel_xpos < (11'd480/5)*4))
 //               pixel_data <= BLUE;  
 //           else 
 //               pixel_data <= BROWN;
 //       end
 //   end
    endmodule 

4-3顶层

module lcd_rgb_colorbar(
    input           sys_clk,        
    input           sys_rst_n,      
    
    
    output          lcd_hs,         
    output          lcd_vs,         
    output          lcd_de,         
    output  [15:0]  lcd_rgb,        
    output          lcd_bl,         
    output          lcd_rst,        
    output          lcd_pclk        
    );


wire         lcd_clk_w;            
wire         locked_w;              
wire         rst_n_w;               
wire [15:0]  pixel_data_w;          
wire [ 9:0]  pixel_xpos_w;          
wire [ 9:0]  pixel_ypos_w;          
    

//待PLL输出稳定之后,停止复位
assign rst_n_w = sys_rst_n & locked_w;
   
pll_9m	u_lcd_pll(                  //时钟分频模块
	.inclk0         (sys_clk),    
	.areset         (~sys_rst_n),
    
	.c0             (lcd_clk_w),    //lcd驱动时钟
	.locked         (locked_w)
	); 


lcd_driver u_lcd_driver(            //lcd驱动模块
    .lcd_clk        (lcd_clk_w),    
    .sys_rst_n      (rst_n_w),    

    .lcd_hs         (lcd_hs),       
    .lcd_vs         (lcd_vs),       
    .lcd_de         (lcd_de),       
    .lcd_rgb        (lcd_rgb),
    .lcd_bl         (lcd_bl),
    .lcd_rst        (lcd_rst),
    .lcd_pclk       (lcd_pclk),
    
    .pixel_data     (pixel_data_w), 
    .pixel_xpos     (pixel_xpos_w), 
    .pixel_ypos     (pixel_ypos_w)
    ); 
    
    
lcd_display u_lcd_display(          //lcd显示模块
    .lcd_clk        (lcd_clk_w),    
    .sys_rst_n      (rst_n_w),
    
    .pixel_xpos     (pixel_xpos_w),
    .pixel_ypos     (pixel_ypos_w),
    .pixel_data     (pixel_data_w)
    );   
    
endmodule 

5结果

5-1

FPGA RGB565的LCD显示屏驱动(Verilog)_第6张图片
可以看到,测试中出现了列偏移(1-480)*(0~271),所以对代码进行修整
见上面代码中的这一句:在这里插入图片描述

5-2

OK!(1-480)*(1~272)
FPGA RGB565的LCD显示屏驱动(Verilog)_第7张图片

6Testbench

给予时钟激励,观察信号关系即可。

你可能感兴趣的:(Verilog,verilog,fpga)