FPGA(六)LCD

6.1 LCD原理

LCD(Liquid Crystal Display):液晶显示器。

6.1.1 接口与管脚

常见LCD屏幕接口:RGB、MCU、LVDS、MIPI等。

RGB LCD管脚如下,包含控制信号和RGB颜色信号(3*8=24位)。

6.1.2 时序

下面是显示的示意图,从左上角开始一行一行地发送数据,只有中间的部分才是有效显示区域,四周阴影是同步信号等。

行显示时序:显示一行包含如下4个阶段。

帧显示时序:显示一帧包含如下4个阶段,其中LINE部分包含每一行的显示。

具体每阶段持续多长时间参见下面的时序参数:

计算举例如下:

6.1.3 模式

LCD分为以下两种模式:

  • DE模式:使用DE信号线来表示有效数据的开始和结束,高电平表示数据有效,低电平表示无效(上图阴影区域)。该模式下不需要行场同步信号(HS和VS)。
  • HV模式:使用HS和VS信号线来确定时序。(这种模式比较老了)

6.2 彩条显示

6.2.1 顶层模块

大致逻辑为:rd_id模块从屏幕读取分辨率等信息,生成id;clk_div模块根据id生成所需的时钟;lcd_display根据传入的像素坐标给出显示的数据;lcd_driver驱动LCD显示。

代码中lcd_rgb是inout接口,需要通过28、29行的语句来生成IOBUF,也可以通过下面的原语。(参见三态门说明

IOBUF #(
    .DRIVE(12), // Specify the output drive strength
    .IBUF_LOW_PWR("TRUE"),  // Low Power - "TRUE", High Performance = "FALSE" 
    .IOSTANDARD("DEFAULT"), // Specify the I/O standard
    .SLEW("SLOW") // Specify the output slew rate
) IOBUF_inst (
    .O(lcd_rgb_o),     // Buffer output
    .IO(lcd_rgb),   // Buffer inout port (connect directly to top-level port)
    .I(lcd_rgb_i),     // Buffer input
    .T(lcd_de)      // 3-state enable input, high=input, low=output
);
module lcd_rgb_colorbar(
    input                sys_clk,     //系统时钟
    input                sys_rst_n,   //系统复位

    //RGB LCD接口
    output               lcd_de,      //LCD 数据使能信号
    output               lcd_hs,      //LCD 行同步信号
    output               lcd_vs,      //LCD 场同步信号
    output               lcd_bl,      //LCD 背光控制信号
    output               lcd_clk,     //LCD 像素时钟
    output               lcd_rst,     //LCD 复位
    inout        [23:0]  lcd_rgb      //LCD RGB888颜色数据
    );
    
//wire define    
wire  [15:0]  lcd_id    ;    //LCD屏ID
wire          lcd_pclk  ;    //LCD像素时钟
              
wire  [10:0]  pixel_xpos;    //当前像素点横坐标
wire  [10:0]  pixel_ypos;    //当前像素点纵坐标
wire  [10:0]  h_disp    ;    //LCD屏水平分辨率
wire  [10:0]  v_disp    ;    //LCD屏垂直分辨率
wire  [23:0]  pixel_data;    //像素数据
wire  [23:0]  lcd_rgb_o ;    //输出的像素数据
wire  [23:0]  lcd_rgb_i ;    //输入的像素数据

//像素数据方向切换
assign lcd_rgb = lcd_de ?  lcd_rgb_o :  {24{1'bz}};
assign lcd_rgb_i = lcd_rgb;

//读LCD ID模块
rd_id u_rd_id(
    .clk          (sys_clk  ),
    .rst_n        (sys_rst_n),
    .lcd_rgb      (lcd_rgb_i),
    .lcd_id       (lcd_id   )
    );    

//时钟分频模块    
clk_div u_clk_div(
    .clk           (sys_clk  ),
    .rst_n         (sys_rst_n),
    .lcd_id        (lcd_id   ),
    .lcd_pclk      (lcd_pclk )
    );    

//LCD显示模块    
lcd_display u_lcd_display(
    .lcd_pclk       (lcd_pclk  ),
    .rst_n          (sys_rst_n ),
    .pixel_xpos     (pixel_xpos),
    .pixel_ypos     (pixel_ypos),
    .h_disp         (h_disp    ),
    .v_disp         (v_disp    ),
    .pixel_data     (pixel_data)
    );    

//LCD驱动模块
lcd_driver u_lcd_driver(
    .lcd_pclk      (lcd_pclk  ),
    .rst_n         (sys_rst_n ),
    .lcd_id        (lcd_id    ),
    .pixel_data    (pixel_data),
    .pixel_xpos    (pixel_xpos),
    .pixel_ypos    (pixel_ypos),
    .h_disp        (h_disp    ),
    .v_disp        (v_disp    ),

    .lcd_de        (lcd_de    ),
    .lcd_hs        (lcd_hs    ),
    .lcd_vs        (lcd_vs    ),
    .lcd_bl        (lcd_bl    ),
    .lcd_clk       (lcd_clk   ),
    .lcd_rst       (lcd_rst   ),
    .lcd_rgb       (lcd_rgb_o )
    );

endmodule

6.2.2 ID模块

这是正点原子开发板的设计,从{lcd_rgb[7],lcd_rgb[15],lcd_rgb[23]}读取LCD信息。

module rd_id(
    input                   clk    ,    //时钟
    input                   rst_n  ,    //复位,低电平有效
    input           [23:0]  lcd_rgb,    //RGB LCD像素数据,用于读取ID
    output   reg    [15:0]  lcd_id     //LCD屏ID
    );

//reg define
reg            rd_flag;  //读ID标志

//获取LCD ID   M2:B7  M1:G7  M0:R7
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        rd_flag <= 1'b0;
        lcd_id <= 16'd0;
    end    
    else begin
        if(rd_flag == 1'b0) begin
            rd_flag <= 1'b1; 
            case({lcd_rgb[7],lcd_rgb[15],lcd_rgb[23]})
                3'b000 : lcd_id <= 16'h4342;    //4.3' RGB LCD  RES:480x272
                3'b001 : lcd_id <= 16'h7084;    //7'   RGB LCD  RES:800x480
                3'b010 : lcd_id <= 16'h7016;    //7'   RGB LCD  RES:1024x600
                3'b100 : lcd_id <= 16'h4384;    //4.3' RGB LCD  RES:800x480
                3'b101 : lcd_id <= 16'h1018;    //10'  RGB LCD  RES:1280x800
                default : lcd_id <= 16'd0;
            endcase    
        end
    end    
end

endmodule

6.2.3 时钟分频模块

module clk_div(
    input               clk,          //50Mhz
    input               rst_n,
    input       [15:0]  lcd_id,
    output  reg         lcd_pclk
    );

reg          clk_25m;
reg          clk_12_5m;
reg          div_4_cnt;

//时钟2分频 输出25MHz时钟 
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        clk_25m <= 1'b0;
    else 
        clk_25m <= ~clk_25m;
end

//时钟4分频 输出12.5MHz时钟 
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        div_4_cnt <= 1'b0;
        clk_12_5m <= 1'b0;
    end    
    else begin
        div_4_cnt <= div_4_cnt + 1'b1;
        if(div_4_cnt == 1'b1)
            clk_12_5m <= ~clk_12_5m;
    end        
end

always @(*) begin
    case(lcd_id)
        16'h4342 : lcd_pclk = clk_12_5m;
        16'h7084 : lcd_pclk = clk_25m;       
        16'h7016 : lcd_pclk = clk;
        16'h4384 : lcd_pclk = clk_25m;
        16'h1018 : lcd_pclk = clk;
        default :  lcd_pclk = 1'b0;
    endcase      
end

endmodule

6.2.4 彩条显示模块

module lcd_display(
    input                lcd_pclk,    //时钟
    input                rst_n,       //复位,低电平有效
    input        [10:0]  pixel_xpos,  //当前像素点横坐标
    input        [10:0]  pixel_ypos,  //当前像素点纵坐标  
    input        [10:0]  h_disp,      //LCD屏水平分辨率
    input        [10:0]  v_disp,      //LCD屏垂直分辨率       
    output  reg  [23:0]  pixel_data   //像素数据
    );

//parameter define  
parameter WHITE = 24'hFFFFFF;  //白色
parameter BLACK = 24'h000000;  //黑色
parameter RED   = 24'hFF0000;  //红色
parameter GREEN = 24'h00FF00;  //绿色
parameter BLUE  = 24'h0000FF;  //蓝色

//根据当前像素点坐标指定当前像素点颜色数据,在屏幕上显示彩条
always @(posedge lcd_pclk or negedge rst_n) begin
    if(!rst_n)
        pixel_data <= BLACK;
    else begin
        if((pixel_xpos >= 11'd0) && (pixel_xpos < h_disp/5*1))
            pixel_data <= WHITE;
        else if((pixel_xpos >= h_disp/5*1) && (pixel_xpos < h_disp/5*2))    
            pixel_data <= BLACK;
        else if((pixel_xpos >= h_disp/5*2) && (pixel_xpos < h_disp/5*3))    
            pixel_data <= RED;   
        else if((pixel_xpos >= h_disp/5*3) && (pixel_xpos < h_disp/5*4))    
            pixel_data <= GREEN;                
        else 
            pixel_data <= BLUE;      
    end    
end

endmodule

6.2.5 LCD驱动模块

核心逻辑为对行/场计数器进行计数,根据计数器的值生成lcd_endata_req信号。

module lcd_driver(
    input                lcd_pclk,    //时钟
    input                rst_n,       //复位,低电平有效
    input        [15:0]  lcd_id,      //LCD屏ID
    input        [23:0]  pixel_data,  //像素数据
    output       [10:0]  pixel_xpos,  //当前像素点横坐标
    output       [10:0]  pixel_ypos,  //当前像素点纵坐标   
    output  reg  [10:0]  h_disp,      //LCD屏水平分辨率
    output  reg  [10:0]  v_disp,      //LCD屏垂直分辨率   
    //RGB LCD接口
    output               lcd_de,      //LCD 数据使能信号
    output               lcd_hs,      //LCD 行同步信号
    output               lcd_vs,      //LCD 场同步信号
    output               lcd_bl,      //LCD 背光控制信号
    output               lcd_clk,     //LCD 像素时钟
    output               lcd_rst,     //LCD复位
    output       [23:0]  lcd_rgb      //LCD RGB888颜色数据
    );

//parameter define  
// 4.3' 480*272
parameter  H_SYNC_4342   =  11'd41;     //行同步
parameter  H_BACK_4342   =  11'd2;      //行显示后沿
parameter  H_DISP_4342   =  11'd480;    //行有效数据
parameter  H_FRONT_4342  =  11'd2;      //行显示前沿
parameter  H_TOTAL_4342  =  11'd525;    //行扫描周期
   
parameter  V_SYNC_4342   =  11'd10;     //场同步
parameter  V_BACK_4342   =  11'd2;      //场显示后沿
parameter  V_DISP_4342   =  11'd272;    //场有效数据
parameter  V_FRONT_4342  =  11'd2;      //场显示前沿
parameter  V_TOTAL_4342  =  11'd286;    //场扫描周期
   
// 7' 800*480   
parameter  H_SYNC_7084   =  11'd128;    //行同步
parameter  H_BACK_7084   =  11'd88;     //行显示后沿
parameter  H_DISP_7084   =  11'd800;    //行有效数据
parameter  H_FRONT_7084  =  11'd40;     //行显示前沿
parameter  H_TOTAL_7084  =  11'd1056;   //行扫描周期
   
parameter  V_SYNC_7084   =  11'd2;      //场同步
parameter  V_BACK_7084   =  11'd33;     //场显示后沿
parameter  V_DISP_7084   =  11'd480;    //场有效数据
parameter  V_FRONT_7084  =  11'd10;     //场显示前沿
parameter  V_TOTAL_7084  =  11'd525;    //场扫描周期       
   
// 7' 1024*600   
parameter  H_SYNC_7016   =  11'd20;     //行同步
parameter  H_BACK_7016   =  11'd140;    //行显示后沿
parameter  H_DISP_7016   =  11'd1024;   //行有效数据
parameter  H_FRONT_7016  =  11'd160;    //行显示前沿
parameter  H_TOTAL_7016  =  11'd1344;   //行扫描周期
   
parameter  V_SYNC_7016   =  11'd3;      //场同步
parameter  V_BACK_7016   =  11'd20;     //场显示后沿
parameter  V_DISP_7016   =  11'd600;    //场有效数据
parameter  V_FRONT_7016  =  11'd12;     //场显示前沿
parameter  V_TOTAL_7016  =  11'd635;    //场扫描周期
   
// 10.1' 1280*800   
parameter  H_SYNC_1018   =  11'd10;     //行同步
parameter  H_BACK_1018   =  11'd80;     //行显示后沿
parameter  H_DISP_1018   =  11'd1280;   //行有效数据
parameter  H_FRONT_1018  =  11'd70;     //行显示前沿
parameter  H_TOTAL_1018  =  11'd1440;   //行扫描周期
   
parameter  V_SYNC_1018   =  11'd3;      //场同步
parameter  V_BACK_1018   =  11'd10;     //场显示后沿
parameter  V_DISP_1018   =  11'd800;    //场有效数据
parameter  V_FRONT_1018  =  11'd10;     //场显示前沿
parameter  V_TOTAL_1018  =  11'd823;    //场扫描周期

// 4.3' 800*480   
parameter  H_SYNC_4384   =  11'd128;    //行同步
parameter  H_BACK_4384   =  11'd88;     //行显示后沿
parameter  H_DISP_4384   =  11'd800;    //行有效数据
parameter  H_FRONT_4384  =  11'd40;     //行显示前沿
parameter  H_TOTAL_4384  =  11'd1056;   //行扫描周期
   
parameter  V_SYNC_4384   =  11'd2;      //场同步
parameter  V_BACK_4384   =  11'd33;     //场显示后沿
parameter  V_DISP_4384   =  11'd480;    //场有效数据
parameter  V_FRONT_4384  =  11'd10;     //场显示前沿
parameter  V_TOTAL_4384  =  11'd525;    //场扫描周期    

//reg define
reg  [10:0] h_sync ;
reg  [10:0] h_back ;
reg  [10:0] h_total;
reg  [10:0] v_sync ;
reg  [10:0] v_back ;
reg  [10:0] v_total;
reg  [10:0] h_cnt  ;
reg  [10:0] v_cnt  ;

//wire define    
wire        lcd_en;
wire        data_req;

//RGB LCD 采用DE模式时,行场同步信号需要拉高
assign  lcd_hs = 1'b1;        //LCD行同步信号
assign  lcd_vs = 1'b1;        //LCD场同步信号

assign  lcd_bl = 1'b1;        //LCD背光控制信号  
assign  lcd_clk = lcd_pclk;   //LCD像素时钟
assign  lcd_rst= 1'b1;        //LCD复位
assign  lcd_de = lcd_en;      //LCD数据有效信号

//使能RGB888数据输出
assign  lcd_en = ((h_cnt >= h_sync + h_back) && (h_cnt < h_sync + h_back + h_disp)
                  && (v_cnt >= v_sync + v_back) && (v_cnt < v_sync + v_back + v_disp)) 
                  ? 1'b1 : 1'b0;

//请求像素点颜色数据输入,提前了一拍
assign data_req = ((h_cnt >= h_sync + h_back - 1'b1) && (h_cnt < h_sync + h_back + h_disp - 1'b1)
                  && (v_cnt >= v_sync + v_back) && (v_cnt < v_sync + v_back + v_disp)) 
                  ? 1'b1 : 1'b0;

//像素点坐标  
assign pixel_xpos = data_req ? (h_cnt - (h_sync + h_back - 1'b1)) : 11'd0;
assign pixel_ypos = data_req ? (v_cnt - (v_sync + v_back - 1'b1)) : 11'd0;

//RGB888数据输出
assign lcd_rgb = lcd_en ? pixel_data : 24'd0;

//行场时序参数
always @(*) begin
    case(lcd_id)
        16'h4342 : begin
            h_sync  = H_SYNC_4342; 
            h_back  = H_BACK_4342; 
            h_disp  = H_DISP_4342; 
            h_total = H_TOTAL_4342;
            v_sync  = V_SYNC_4342; 
            v_back  = V_BACK_4342; 
            v_disp  = V_DISP_4342; 
            v_total = V_TOTAL_4342;            
        end
        16'h7084 : begin
            h_sync  = H_SYNC_7084; 
            h_back  = H_BACK_7084; 
            h_disp  = H_DISP_7084; 
            h_total = H_TOTAL_7084;
            v_sync  = V_SYNC_7084; 
            v_back  = V_BACK_7084; 
            v_disp  = V_DISP_7084; 
            v_total = V_TOTAL_7084;        
        end
        16'h7016 : begin
            h_sync  = H_SYNC_7016; 
            h_back  = H_BACK_7016; 
            h_disp  = H_DISP_7016; 
            h_total = H_TOTAL_7016;
            v_sync  = V_SYNC_7016; 
            v_back  = V_BACK_7016; 
            v_disp  = V_DISP_7016; 
            v_total = V_TOTAL_7016;            
        end
        16'h4384 : begin
            h_sync  = H_SYNC_4384; 
            h_back  = H_BACK_4384; 
            h_disp  = H_DISP_4384; 
            h_total = H_TOTAL_4384;
            v_sync  = V_SYNC_4384; 
            v_back  = V_BACK_4384; 
            v_disp  = V_DISP_4384; 
            v_total = V_TOTAL_4384;             
        end        
        16'h1018 : begin
            h_sync  = H_SYNC_1018; 
            h_back  = H_BACK_1018; 
            h_disp  = H_DISP_1018; 
            h_total = H_TOTAL_1018;
            v_sync  = V_SYNC_1018; 
            v_back  = V_BACK_1018; 
            v_disp  = V_DISP_1018; 
            v_total = V_TOTAL_1018;        
        end
        default : begin
            h_sync  = H_SYNC_4342; 
            h_back  = H_BACK_4342; 
            h_disp  = H_DISP_4342; 
            h_total = H_TOTAL_4342;
            v_sync  = V_SYNC_4342; 
            v_back  = V_BACK_4342; 
            v_disp  = V_DISP_4342; 
            v_total = V_TOTAL_4342;          
        end
    endcase
end

//行计数器对像素时钟计数
always@ (posedge lcd_pclk or negedge rst_n) begin
    if(!rst_n) 
        h_cnt <= 11'd0;
    else begin
        if(h_cnt == h_total - 1'b1)
            h_cnt <= 11'd0;
        else
            h_cnt <= h_cnt + 1'b1;           
    end
end

//场计数器对行计数
always@ (posedge lcd_pclk or negedge rst_n) begin
    if(!rst_n) 
        v_cnt <= 11'd0;
    else begin
        if(h_cnt == h_total - 1'b1) begin
            if(v_cnt == v_total - 1'b1)
                v_cnt <= 11'd0;
            else
                v_cnt <= v_cnt + 1'b1;    
        end
    end    
end

endmodule

6.3 字符和图片显示

  • 字符显示:使用字模软件将汉字转换成矩阵值(比如PCtoLCD软件)。然后直接在代码中为每行赋值,如下所示。

    always @(posedge lcd_pclk) begin
        char[0] <= 128'h00000000000000000000000000000000;
        char[1] <= 128'h00000000000000000000000000000000;
        char[2] <= 128'h00000000000100000000002000000000;
        char[3] <= 128'h000000100001800002000070000000C0;
        char[4] <= 128'h000000380001800003FFFFF803FFFFE0;
        ......
    end

  • 图片显示:使用软件将图片转换为coe文件(比如PicToLCD软件)。然后将coe文件加载到ROM中,依次读取每一行像素。coe文件格式如下,每行是24位RGB值。

    memory_initialization_radix=16;
    memory_initialization_vector=
    FFFFFF,
    FFFFFF,
    FFFFFF,
    FFFFFF,
    ......

下面是修改后的显示模块,其余模块不变。

module lcd_display(
    input             lcd_pclk,     //时钟
    input             rst_n,        //复位,低电平有效
                                    
    input      [10:0] pixel_xpos,   //像素点横坐标
    input      [10:0] pixel_ypos,   //像素点纵坐标    
    output reg [23:0] pixel_data    //像素点数据,
);                                   
                                     
//parameter define                   
localparam PIC_X_START = 11'd1;      //图片起始点横坐标
localparam PIC_Y_START = 11'd1;      //图片起始点纵坐标
localparam PIC_WIDTH   = 11'd100;    //图片宽度
localparam PIC_HEIGHT  = 11'd100;    //图片高度
                       
localparam CHAR_X_START= 11'd1;      //字符起始点横坐标
localparam CHAR_Y_START= 11'd110;    //字符起始点纵坐标
localparam CHAR_WIDTH  = 11'd128;    //字符宽度,4个字符:32*4
localparam CHAR_HEIGHT = 11'd32;     //字符高度
                       
localparam BACK_COLOR  = 24'hE0FFFF; //背景色,浅蓝色
localparam CHAR_COLOR  = 24'hff0000; //字符颜色,红色

//reg define
reg   [127:0] char[31:0];  //字符数组
reg   [13:0]  rom_addr  ;  //ROM地址

//wire define   
wire  [10:0]  x_cnt;       //横坐标计数器
wire  [10:0]  y_cnt;       //纵坐标计数器
wire          rom_rd_en ;  //ROM读使能信号
wire  [23:0]  rom_rd_data ;//ROM数据

assign  x_cnt = pixel_xpos - CHAR_X_START; //像素点相对于字符区域起始点水平坐标
assign  y_cnt = pixel_ypos - CHAR_Y_START; //像素点相对于字符区域起始点垂直坐标
assign  rom_rd_en = 1'b1;                  //读使能拉高,即一直读ROM数据

//给字符数组赋值,显示汉字“正点原子”,每个汉字大小为32*32
always @(posedge lcd_pclk) begin
    char[0 ]  <= 128'h00000000000000000000000000000000;
    char[1 ]  <= 128'h00000000000000000000000000000000;
    char[2 ]  <= 128'h00000000000100000000002000000000;
    char[3 ]  <= 128'h000000100001800002000070000000C0;
    char[4 ]  <= 128'h000000380001800003FFFFF803FFFFE0;
    char[5 ]  <= 128'h07FFFFFC0001800003006000000001E0;
    char[6 ]  <= 128'h0000C000000180600300600000000300;
    char[7 ]  <= 128'h0000C0000001FFF00300C00000000600;
    char[8 ]  <= 128'h0000C000000180000310804000001800;
    char[9 ]  <= 128'h0000C00000018000031FFFE000003000;
    char[10]  <= 128'h0000C00000018000031800400001C000;
    char[11]  <= 128'h0000C00000018000031800400001C000;
    char[12]  <= 128'h00C0C000018181800318004000018000;
    char[13]  <= 128'h00C0C00001FFFFC0031FFFC000018010;
    char[14]  <= 128'h00C0C060018001800318004000018038;
    char[15]  <= 128'h00C0FFF001800180031800403FFFFFFC;
    char[16]  <= 128'h00C0C000018001800318004000018000;
    char[17]  <= 128'h00C0C000018001800218004000018000;
    char[18]  <= 128'h00C0C00001800180021FFFC000018000;
    char[19]  <= 128'h00C0C000018001800210304000018000;
    char[20]  <= 128'h00C0C00001FFFF800200300000018000;
    char[21]  <= 128'h00C0C000018001800606300000018000;
    char[22]  <= 128'h00C0C000018001000607370000018000;
    char[23]  <= 128'h00C0C00000000000060E31C000018000;
    char[24]  <= 128'h00C0C000001000400418307000018000;
    char[25]  <= 128'h00C0C000020830600430303800018000;
    char[26]  <= 128'h00C0C010020C18300860301800018000;
    char[27]  <= 128'h00C0C038060E18180883700800018000;
    char[28]  <= 128'h3FFFFFFC0C0618181100F008003F8000;
    char[29]  <= 128'h000000001C0408182000600000070000;
    char[30]  <= 128'h00000000000000000000000000020000;
    char[31]  <= 128'h00000000000000000000000000000000;
end

//为LCD不同显示区域绘制图片、字符和背景色
always @(posedge lcd_pclk or negedge rst_n) begin
    if (!rst_n)
        pixel_data <= BACK_COLOR;
    else if( (pixel_xpos >= PIC_X_START) && (pixel_xpos < PIC_X_START + PIC_WIDTH) 
          && (pixel_ypos >= PIC_Y_START) && (pixel_ypos < PIC_Y_START + PIC_HEIGHT) )
        pixel_data <= rom_rd_data ;  //显示图片
    else if((pixel_xpos >= CHAR_X_START) && (pixel_xpos < CHAR_X_START + CHAR_WIDTH)
         && (pixel_ypos >= CHAR_Y_START) && (pixel_ypos < CHAR_Y_START + CHAR_HEIGHT)) begin
        if(char[y_cnt][CHAR_WIDTH -1'b1 - x_cnt])
            pixel_data <= CHAR_COLOR;    //显示字符
        else
            pixel_data <= BACK_COLOR;    //显示字符区域的背景色
    end
    else
        pixel_data <= BACK_COLOR;        //屏幕背景色
end

//根据当前扫描点的横纵坐标为ROM地址赋值
always @(posedge lcd_pclk or negedge rst_n) begin
    if(!rst_n)
        rom_addr <= 14'd0;
    //当横纵坐标位于图片显示区域时,累加ROM地址    
    else if((pixel_ypos >= PIC_Y_START) && (pixel_ypos < PIC_Y_START + PIC_HEIGHT) 
        && (pixel_xpos >= PIC_X_START) && (pixel_xpos < PIC_X_START + PIC_WIDTH)) 
        rom_addr <= rom_addr + 1'b1;
    //当横纵坐标位于图片区域最后一个像素点时,ROM地址清零    
    else if((pixel_ypos >= PIC_Y_START + PIC_HEIGHT))
        rom_addr <= 14'd0;
end

//ROM:存储图片
blk_mem_gen_0  blk_mem_gen_0 (
  .clka  (lcd_pclk),    // input wire clka
  .ena   (rom_rd_en),   // input wire ena
  .addra (rom_addr),    // input wire [13 : 0] addra
  .douta (rom_rd_data)  // output wire [23 : 0] douta
);

endmodule

FPGA(六)LCD
https://shuusui.site/blog/2026/01/09/fpga-6/
作者
Shuusui
发布于
2026年1月9日
更新于
2026年1月10日
许可协议