第29章:RGB LCD彩条显示实验

本章对应原书第 686~718 页,属于「第五篇 实战篇」。

本章在讲什么?(先看这个)

屏幕是怎么被"点亮"的?这一章是进入视频显示世界的第一课。你要学会 RGB LCD 屏的核心概念:像素、分辨率、像素格式(RGB888)、以及最重要的——行场扫描时序(HSYNC/VSYNC/DE)。FPGA 会像一支"画笔",按顺序逐个像素地给屏幕上色。

实验任务是驱动正点原子全系 RGB LCD 屏(4.3 寸到 10.1 寸),在屏幕上显示五条彩条。这一章的时序知识是后面 HDMI、触摸屏、字符显示等所有显示类实验的地基,务必吃透。

核心概念拆解

29.1 RGB TFT-LCD屏简介

TFT-LCD(Thin Film Transistor-Liquid Crystal Display,薄膜晶体管液晶显示屏)的每个像素点都由集成在液晶后面的薄膜晶体管独立驱动,响应速度快、图像质量好。从使用角度,我们不需要深究液晶怎么转动,只需要关注下面几个重点。

分辨率

LCD 屏由一个一个的像素点组成,每个像素点像一个"小灯",由 R(红)、G(绿)、B(蓝)三种颜色组成——RGB 是光的三基色。1080P 的意思是屏幕有 1920×1080 = 2073600 个像素点(横轴 X 有 1920 列,竖轴 Y 有 1080 行),第一个像素点在左上角 A,最后一个在右下角 C:

图 29.1.2 像素点排布

屏幕尺寸不变时,分辨率越高越清晰;分辨率不变时,屏幕越小越细腻(同是 1080P,5 寸手机比 24 寸显示器细腻得多)。但分辨率只是屏幕好坏的参数之一,还有色彩还原、亮度、可视角度、刷新率等。

像素格式

R、G、B 三部分通常各用 8bit,一个像素点就是 24bit(3 个字节),称为 RGB888

图 29.1.3 RGB888 数据格式

  • bit23~16:RED 通道;bit15~8:GREEN 通道;bit7~0:BLUE 通道;
  • 红色 = 24'hFF0000,绿色 = 24'h00FF00,蓝色 = 24'h0000FF;
  • 24'hFFFF00 是黄色,24'h000000 是黑色,24'hFFFFFF 是白色。

常用的还有 RGB565(2 字节),但色彩较鲜艳度差一些。想知道任意颜色的数值?用电脑"画图"工具的调色板就能查:

图 29.1.4 调色板颜色选取

LCD屏接口

RGB LCD 屏接口的信号线如下:

信号线 描述
R[7:0] 8 根红色数据线
G[7:0] 8 根绿色数据线
B[7:0] 8 根蓝色数据线
DE 数据使能线
VSYNC 垂直同步信号线
HSYNC 水平同步信号线
PCLK 像素时钟信号线

正点原子有五款 RGB LCD 屏:ATK-4342(4.3 寸 480×272,已停产)、ATK-4384(4.3 寸 800×480)、ATK-7084(7 寸 800×480)、ATK-7016(7 寸 1024×600)、ATK-1018(10.1 寸 1280×800)。以 ATK-7016 为例,接口是一个 40PIN FPC 座(0.5mm 间距):

图 29.1.5 RGB LCD 屏接口原理图

RGB LCD 屏没有读写寄存器,所以没有真正的 ID,模块通过 R7/G7/B7 三个引脚的上/下拉来自定义屏幕 ID,程序读这三个脚就能知道当前接的是哪款屏,从而兼容不同屏幕:

M2(LCD_B7) M1(LCD_G7) M0(LCD_R7) LCD ID 说明
0 0 0 ATK-4342 480*272
0 0 1 ATK-7084 800*480
0 1 0 ATK-7016 1024*600
1 0 0 ATK-4384 800*480
1 0 1 ATK-1018 1280*800

LCD屏时间参数:那一圈"黑边"是怎么来的

把屏幕显示想象成一支"画笔"从左到右、从上到下逐个像素涂色,扫完一屏就画好了一帧:

图 29.1.6 LCD 屏一帧图像扫描图

图中可以看到有效显示区外有一圈"黑边",这要追溯到 CRT 显示器的"祖先":CRT 靠电子枪逐行打荧光粉,扫完一行要关掉电子枪快速移回左边(走 Z 形轨迹),扫完一帧要移回左上角。这些移动过程对应的延时就是 HBP、HFP、VBP、VFP。如今的 LCD 屏虽然不需要电子枪了,但屏内 IC 处理一行/一帧数据也需要反应时间,所以这四个参数被保留下来,作用是锁定有效像素数据

  • HSYNC:行同步信号,有效时表示开始显示新的一行;
  • VSYNC:帧同步信号,有效时表示开始显示新的一帧。

行时序与帧时序

行显示时序的参数:

图 29.1.7 LCD 屏行显示时序

  • HSPW:行同步信号宽度(HSYNC 持续时间,单位 CLK);
  • HBP:行显示后沿(后肩);HOZVAL:行有效显示区(如 1024600 屏为 1024);HFP*:行显示前沿(前肩)。

HSYNC 发出后要等 HSPW+HBP 个 CLK 才出现有效像素数据;一行显示完再等 HFP 个 CLK 才发下一个 HSYNC。所以显示一行的时间 = HSPW + HBP + HOZVAL + HFP

帧显示时序类似,只是单位变成"行时间":

图 29.1.8 LCD 屏帧显示时序

显示一帧的时间 = (VSPW + VBP + LINE + VFP) × (HSPW + HBP + HOZVAL + HFP)

配置一款 RGB LCD 屏需要 8 个参数:HSPW、HBP、HOZVAL、HFP、VSPW、VBP、LINE、VFP。

同步模式与像素时钟

RGB LCD 有两种数据同步方式:

  • HV Mode(行场同步模式):HSYNC 和 VSYNC 都作为同步信号,DE 必须为低;
  • DE Mode(数据使能模式):只有同时处于帧有效区和行有效区时 DE 才为高,此时 VSYNC 和 HSYNC 必须为高。

RGB LCD 屏一般都支持 DE 模式,本章就采用 DE 同步模式

像素时钟怎么算?以 ATK-7016(1024×600)为例:一帧需要 (3+20+600+12) × (20+140+1024+160) = 635 × 1344 = 853440 个时钟;刷新率 60Hz 时需要 853440 × 60 ≈ 51.2MHz 的像素时钟。实践中不用这么严格,给 ATK-7016 输出 50MHz 即可,刷新率接近 60Hz 且代码好写。

29.2 硬件设计

底板左侧板载 1 个 RGB LCD 接口:

图 29.2.1 RGB LCD 接口实物图

FPGA 引脚输出的颜色数据为 24bit RGB888。特别注意:LCD_R7、LCD_G7、LCD_B7 既作为颜色数据输出,又用来读取屏幕 ID,所以这 24 位数据线对 FPGA 是双向引脚

图 29.2.2 RGB TFTLCD 接口原理图

模块上以 T 开头的 5 个信号(T_PEN、T_SCK 等)连接触摸芯片,本实验不涉及触摸,后续触摸屏实验再讲。

29.3 实验任务

用开发板的 RGB LCD 接口驱动 RGB LCD 屏(兼容全系型号),显示彩条。

29.4 程序设计

整体模块设计

要兼容所有屏幕,就得先知道接的是哪款屏——所以有四个功能模块加一个顶层:

图 29.4.1 系统架构图

  • 读ID模块(rd_id):上电时把 RGB 双向总线设为输入,读取 lcd_rgb 得到屏幕 ID;
  • 时钟分频模块(clk_div):根据 ID 输出对应频率的像素时钟 lcd_pclk;
  • LCD显示模块(lcd_display):根据像素坐标输出彩条数据 pixel_data;
  • LCD驱动模块(lcd_driver):根据 ID 输出对应时序参数,驱动屏幕并把彩条显示上去。

图 29.4.2 RGB TFT-LCD 彩条显示系统框图

顶层模块为 lcd_rgb_colorbar,完成各模块例化。

读ID模块

lcd_rgb[7](B7)、lcd_rgb[15](G7)、lcd_rgb[23](R7)分别对应 M2、M1、M0,并译码成 16 位 ID(如 3'b010 → 16'h7016,表示 1024×600 的 7 寸屏)。

图 29.4.3 读ID 模块

ID 只需要读一次,用 rd_flag 标志:rd_flag=0 时读一次 ID 并置 1,之后不再读取:

图 29.4.4 读ID 波形图

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

图 29.4.5 读ID 模块的仿真波形

时钟分频模块

各屏幕需要的像素时钟:480×272 为 12.5MHz,800×480 和 1024×600 为 25/50MHz 档,1280×800 理论要 70MHz(需 MMCM/PLL IP 核才能产生,这里简化为输出 50MHz)。本设计用计数分频从 50MHz 系统时钟得到 12.5MHz(4 分频)、25MHz(2 分频),50MHz 直接用系统时钟:

图 29.4.6 时钟分频模块

图 29.4.7 时钟分频模块波形图

module clk_div(
    input               clk,          //50MHz
    input               rst_n,
    input       [15:0]  lcd_id,
    output  reg         lcd_pclk
    );
//reg define
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;
        else
            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

图 29.4.8 时钟分频模块仿真波形

LCD驱动模块(本章核心)

LCD 驱动模块用 DE 时序把彩条数据扫描显示到屏幕上:

图 29.4.9 LCD 驱动模块框图

要点:DE 模式下行场同步信号直接拉高(lcd_hs = lcd_vs = 1'b1),背光 lcd_bl 和复位 lcd_rst 也直接赋 1。以 4384 屏(800×480)为例,时序参数为:h_sync=128、h_back=88、h_disp=800、h_total=1056;v_sync=2、v_back=33、v_disp=480、v_total=525。

模块内部用两个计数器搭出扫描框架:h_cnt(行计数器,对像素时钟计数,0~h_total-1 循环)和 v_cnt(场计数器,每扫完一行加 1,0~v_total-1 循环)。

图 29.4.10 LCD 驱动模块波形图

lcd_de 拉高的条件:h_cnt ≥ h_sync+h_back 且 < h_sync+h_back+h_disp,同时 v_cnt ≥ v_sync+v_back 且 < v_sync+v_back+v_disp。注意 data_req 比 lcd_de 提前一个时钟周期,这是给像素数据生成预留的一拍数据通路延时:

图 29.4.11 h_cnt、v_cnt 以及lcd_de 的波形图

图 29.4.12 lcd_de、data_req 以及像素时钟坐标

核心代码:

module lcd_driver(
    input                lcd_pclk,    //时钟
    input                rst_n,       //复位,低电平有效
    input        [15:0]  lcd_id,      //LCD 屏ID
    input        [23:0]  pixel_data,  //像素数据
    output  reg  [10:0]  pixel_xpos,  //当前像素点横坐标
    output  reg  [10:0]  pixel_ypos,  //当前像素点纵坐标
    output  reg  [10:0]  h_disp,      //LCD 屏水平分辨率
    output  reg  [10:0]  v_disp,      //LCD 屏垂直分辨率
    output  reg          data_req,    //数据请求信号
    //RGB LCD 接口
    output  reg          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;    //场扫描周期
// ……其余分辨率参数同理(7084/7016/4384/1018),此处省略

//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  ;

//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 复位

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

//像素点x 坐标
always@ (posedge lcd_pclk or negedge rst_n) begin
    if(!rst_n)
        pixel_xpos <= 11'd0;
    else if(data_req)
        pixel_xpos <= h_cnt + 11'd2 - h_sync - h_back ;
    else
        pixel_xpos <= 11'd0;
end

//像素点y 坐标
always@ (posedge lcd_pclk or negedge rst_n) begin
    if(!rst_n)
        pixel_ypos <= 11'd0;
    else if(v_cnt >= (v_sync + v_back) && v_cnt < (v_sync + v_back + v_disp))
        pixel_ypos <= v_cnt + 11'd1 - (v_sync + v_back) ;
    else
        pixel_ypos <= 11'd0;
end

//行场时序参数(根据lcd_id选择对应分辨率参数,省略部分case分支)
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
        // ……其余ID分支省略
    endcase
end

//数据使能信号
always@ (posedge lcd_pclk or negedge rst_n) begin
    if(!rst_n)
        lcd_de <= 1'b0;
    else
        lcd_de <= data_req;
end

//请求像素点颜色数据输入(比lcd_de提前一拍)
always@ (posedge lcd_pclk or negedge rst_n) begin
    if(!rst_n)
        data_req<=1'b0;
    else if((h_cnt >= h_sync + h_back - 11'd2) && (h_cnt < h_sync + h_back + h_disp - 11'd2)
             && (v_cnt >= v_sync + v_back) && (v_cnt < v_sync + v_back + v_disp))
        data_req <= 1'b1;
    else
        data_req <= 1'b0;
end

//行计数器对像素时钟计数
always@ (posedge lcd_pclk or negedge rst_n) begin
    if(!rst_n)
        h_cnt <= 11'd0;
    else begin
        if(h_cnt == h_total - 11'd1)
            h_cnt <= 11'd0;
        else
            h_cnt <= h_cnt + 11'd1;
    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 - 11'd1) begin
            if(v_cnt == v_total - 11'd1)
                v_cnt <= 11'd0;
            else
                v_cnt <= v_cnt + 11'd1;
        end
    end
end

endmodule

图 29.4.13 LCD 驱动模块波形图a

图 29.4.14 LCD 驱动模块波形图b

仿真验证了 lcd_de 在 v_cnt 为 35~514、h_cnt 为 215~1015 范围内有效,且 data_req 比 lcd_de 提前一拍。

LCD显示模块

功能很简单:把横坐标 pixel_xpos 在有效显示区内分成五等份,每份填一种颜色(白、黑、红、绿、蓝):

图 29.4.17 LCD 显示模块

图 29.4.18 LCD 显示模块波形图

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

顶层模块

顶层的关键是处理 lcd_rgb 双向引脚的方向切换:lcd_de 为高时(输出像素有效)把引脚切成输出,连接 lcd_rgb_o;lcd_de 为低时把引脚切成高阻态 'Z',此时可以读取引脚电平获取屏幕 ID:

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    ;
wire          lcd_pclk  ;
wire  [10:0]  pixel_xpos;
wire  [10:0]  pixel_ypos;
wire  [10:0]  h_disp    ;
wire  [10:0]  v_disp    ;
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    ),
    .data_req      (          ),
    .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

仿真验证

TestBench 的关键点:模拟屏幕 ID 输入。4384 屏的 ID 为 3'b100,反映到 lcd_rgb 引脚上是 24'h80(只有 bit7 为 1)。在 lcd_de 为低时给 lcd_rgb 赋 24'h80 即可:

`timescale 1ns / 1ns

module tb_lcd_rgb_colorbar();

//reg define
reg     sys_clk;
reg     sys_rst_n;

//wire define
wire          lcd_de ;
wire          lcd_hs ;
wire          lcd_vs ;
wire          lcd_bl ;
wire          lcd_clk;
wire  [23:0]  lcd_rgb;

always #10 sys_clk = ~sys_clk;
//lcd_de为低时提供ID激励(4384屏ID为24'h80),为高时释放总线
assign lcd_rgb = lcd_de ?  {24{1'bz}} :  24'h80;

initial begin
    sys_clk = 1'b0;
    sys_rst_n = 1'b0;
    #200
    sys_rst_n = 1'b1;
end

lcd_rgb_colorbar u_lcd_rgb_colorbar(
    .sys_clk          (sys_clk  ),
    .sys_rst_n        (sys_rst_n),
    .lcd_de           (lcd_de ),
    .lcd_hs           (lcd_hs ),
    .lcd_vs           (lcd_vs ),
    .lcd_bl           (lcd_bl ),
    .lcd_clk          (lcd_clk),
    .lcd_rgb          (lcd_rgb)
    );

endmodule

图 29.4.20 LCD 彩条顶层仿真波形

波形中 lcd_rgb 在 lcd_de 拉高范围内依次输出白(24'hffffff)、黑(24'h000000)、红(24'hff0000)、绿(24'h00ff00)、蓝(24'h0000ff)五种颜色,符合设计。

29.5 下载验证

管脚分配(部分,全部 24 位 lcd_rgb 都需约束,此处列出控制信号和说明):

信号名 方向 管脚 说明
sys_clk input U18 系统时钟 50M
sys_rst_n input N16 复位,低有效
lcd_rgb[23:0] inout U17/W16/…(每位一个脚) RGB888 颜色数据(双向)
lcd_hs output N18 行同步
lcd_vs output T20 场同步
lcd_de output U20 数据使能
lcd_bl output M20 背光控制
lcd_clk output P19 像素时钟
lcd_rst output L17 复位

约束语句示例(完整工程需对 lcd_rgb[0]~[23] 全部约束):

create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN W18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[0]}]
# …… lcd_rgb[1]~[23] 同理,依次为 W19/R16/R17/W20/V20/P18/N17/V17/V18/T17/R18/Y18/Y19/P15/P16/V16/W16/T14/T15/Y17/Y16/T16/U17
set_property -dict {PACKAGE_PIN N18 IOSTANDARD LVCMOS33} [get_ports lcd_hs]
set_property -dict {PACKAGE_PIN T20 IOSTANDARD LVCMOS33} [get_ports lcd_vs]
set_property -dict {PACKAGE_PIN U20 IOSTANDARD LVCMOS33} [get_ports lcd_de]
set_property -dict {PACKAGE_PIN M20 IOSTANDARD LVCMOS33} [get_ports lcd_bl]
set_property -dict {PACKAGE_PIN P19 IOSTANDARD LVCMOS33} [get_ports lcd_clk]
set_property -dict {PACKAGE_PIN L17 IOSTANDARD LVCMOS33} [get_ports lcd_rst]

上板步骤:将 FPC 排线一端接 LCD 模块 J1 接口(掀开黑色翻盖、蓝色面朝上插入、压下翻盖固定),另一端接开发板 RGB TFTLCD 接口;连接 JTAG 下载器与电源,下载程序后屏幕显示五色彩条即验证成功。

图 29.5.3 RGB LCD 彩条显示

初学者容易踩的坑

  • 时序参数记不住 8 个就抓瞎:HSPW/HBP/HOZVAL/HFP、VSPW/VBP/LINE/VFP,行单位是 CLK、场单位是"行时间",配置不同屏幕务必对照各自参数表,不能张冠李戴。
  • lcd_rgb 是双向引脚:它既要输出像素数据,又要在上电时读屏幕 ID(R7/G7/B7),顶层必须用三态门做方向切换(lcd_de ? lcd_rgb_o : 24'bz),忘了这步就读不到 ID。
  • data_req 比 lcd_de 提前一拍:像素数据是时序逻辑生成,滞后一拍;如果直接用 lcd_de 请求数据,屏幕上会出现错位或第一条线异常。
  • DE 模式下行场同步要拉高lcd_hslcd_vs 直接赋 1,别按 HV 模式的思路去输出同步脉冲。
  • 10.1 寸屏时钟只有近似值:1280×800 严格要 70MHz,计数分频得不到,须用 MMCM/PLL IP 核;本例程用 50MHz 是有意为之的简化,显示刷新率略低于 60Hz。
  • FPC 排线方向别插反:蓝色面朝上、掀盖压紧,接触不良会导致花屏或无显示。

小结 & 下一步

本章建立了 RGB LCD 显示的完整概念链:像素 → RGB888 格式 → 行场扫描时序(HSPW/HBP/HFP、VSPW/VBP/VFP)→ DE 同步模式 → 像素时钟计算。工程上用"读 ID → 分频出像素时钟 → 行场计数器生成时序与坐标 → 按坐标填颜色"的套路,一次兼容五款屏幕。这套 h_cnt/v_cnt 扫描框架是所有显示实验的母版。

动手建议:完成拓展训练——把彩条从竖向改成横向显示(把判断条件从 pixel_xpos 换成 pixel_ypos 即可),体会坐标与颜色的映射关系。