跳到主要内容

实验 10:RGB 屏幕彩条显示

🛒 开发板购买: EK303 GW1N FPGA核心板


一、实验目的

  1. 掌握 RGB 并行接口 LCD 的驱动时序协议(HSYNC/VSYNC/DE/PCLK)。
  2. 理解"行消隐"和"场消隐"的概念——屏幕显示区域之外的时间用于同步。
  3. 学会根据屏幕规格书确定时序参数,并用 FPGA 计数器精确生成时序信号。
  4. 根据像素坐标输出不同颜色,验证屏幕驱动是否正确。

二、实验过程

2.1 硬件连接

信号组位宽方向说明
R[7:0]8out红色分量
G[7:0]8out绿色分量
B[7:0]8out蓝色分量
HSYNC1out行同步信号
VSYNC1out场同步信号
DE1out数据有效标志
PCLK1out像素时钟

2.2 设计思路

RGB 时序协议(以 800×480@60Hz 为例)

一行的时间线:
|←──── 行消隐 ────→|←───── 有效显示区域 (800 px) ──────→|
HSYNC=0 HSYNC=1, DE=1

一场的时间线 (共 525 行):
|← 场消隐 (45行) →|←──── 有效显示区域 (480 行) ─────→|
VSYNC=0 VSYNC=1, DE=1

关键参数

参数说明
分辨率800×480有效像素
PCLK 频率~33 MHz像素时钟
H_total1056一行总像素(含消隐)
V_total525一屏总行数(含消隐)
H_active800有效列
V_active480有效行
H_BP216行后沿
V_BP35场后沿

⚠️ 不同屏幕时序参数不同,务必查阅屏幕规格书!

彩条生成:将 800 列水平方向均分为 8 段,每段 100 像素,对应不同的 R/G/B 组合。

颜色RGB
1255255255
22552550
30255255
4绿02550
5品红2550255
625500
700255
8000

2.3 Verilog 模块设计

模块名rgb_colorbar

顶层端口

端口方向位宽说明
clk_50minput1系统时钟
hsyncoutput1行同步
vsyncoutput1场同步
deoutput1数据有效
r, g, boutput8颜色分量

内部信号

信号位宽说明
h_cnt11列计数器 (0→H_total-1)
v_cnt10行计数器 (0→V_total-1)
pixel_x10当前有效像素的 X 坐标
pixel_y9当前有效像素的 Y 坐标

逻辑描述

  1. PCLK 生成:使用 PLL 或分频从 50MHz 产生约 33MHz。
  2. h_cnt 每个 PCLK 周期 +1,到达 H_total-1 后归零并触发 v_cnt +1
  3. HSYNC / VSYNC 在消隐期间为低电平。
  4. DE = 1 仅当 h_cntv_cnt 均在有效区域时。
  5. DE 有效时,根据 pixel_x / 100 的值选择对应颜色的 R/G/B 输出。
  6. DE 无效时 R=G=B=0(消隐期输出全黑)。

2.4 引脚约束

约 28 个引脚(24 根数据线 + 4 根控制线),全部约束到核心板的 RGB 屏幕接口。

如果使用 16-bit RGB565 模式(R[4:0]/G[5:0]/B[4:0]),引脚数减至 18 个。


三、实验现象

观察点现象
屏幕整体点亮,画面稳定无闪烁、无撕裂
彩条从左到右依次为白→黄→青→绿→品红→红→蓝→黑
颜色纯正无偏色,过渡边界锐利
消隐屏幕边框无亮线、无噪点

四、掌握知识点

知识点说明
RGB 时序协议HSYNC/VSYNC/DE/PCLK 四信号协同
行消隐/场消隐电子束回扫时间的数字等价概念
时序参数设计从规格书提取 H_total/V_total/H_BP 等参数
像素坐标映射pixel_x = h_cnt - H_BP 计算有效坐标
颜色编码RGB888 (24-bit) vs RGB565 (16-bit)
PLL/IP 核可选使用 Gowin PLL 生成精确 PCLK

五、思考题

  1. 如果屏幕分辨率是 480×272,时序参数应该怎么改?
  2. RGB565 和 RGB888 有什么区别?什么时候用哪个?
  3. 如何实现横条彩条(水平方向分段)而不是竖条?
  4. 如果不用 PLL,直接从 50MHz 分频能驱动屏幕吗?有什么限制?

附录:源码清单

top.v

// =============================================================================
// exp_10 - RGB 彩条 (对齐验证工程架构)
// 芯片: GW1N-LV9LQ144C6/I5, 800×480 DE模式
// =============================================================================

module top (
input wire clk_50m, // Pin 98
output wire lcd_de, // DE信号
output wire lcd_hs, // HSYNC (DE模式: 拉高)
output wire lcd_vs, // VSYNC (DE模式: 拉高)
output wire lcd_bl, // 背光 Pin 111
output wire lcd_clk, // 像素时钟 Pin 78
output wire [23:0] lcd_rgb // RGB888总线
);

// ===== PCLK 25MHz =====
reg pclk_r;
always @(posedge clk_50m) pclk_r <= ~pclk_r;
assign lcd_clk = pclk_r;

// ===== DE模式 =====
assign lcd_hs = 1'b1;
assign lcd_vs = 1'b1;
assign lcd_bl = 1'b1;

// ===== 800×480 时序 (对齐验证工程) =====
localparam H_SYNC=128, H_BACK=88, H_DISP=800, H_TOTAL=1056;
localparam V_SYNC=2, V_BACK=33, V_DISP=480, V_TOTAL=525;

// ===== 行列计数器 (pclk时钟域) =====
reg [10:0] h_cnt, v_cnt;
always @(posedge pclk_r) begin
if (h_cnt == H_TOTAL - 1) begin
h_cnt <= 0;
v_cnt <= (v_cnt == V_TOTAL - 1) ? 0 : v_cnt + 1;
end else begin
h_cnt <= h_cnt + 1;
end
end

// ===== data_req: 提前2周期请求像素数据 (对齐 lcd_driver.v) =====
wire data_req = (h_cnt >= H_SYNC + H_BACK - 2) &&
(h_cnt < H_SYNC + H_BACK + H_DISP - 2) &&
(v_cnt >= V_SYNC + V_BACK) &&
(v_cnt < V_SYNC + V_BACK + V_DISP);

// ===== lcd_de: data_req寄存一拍 =====
reg de_r;
always @(posedge pclk_r) de_r <= data_req;
assign lcd_de = de_r;

// ===== 像素坐标 (data_req时计算) =====
reg [10:0] px, py;
always @(posedge pclk_r) begin
if (data_req) begin
px <= h_cnt + 2 - H_SYNC - H_BACK;
py <= v_cnt + 1 - V_SYNC - V_BACK;
end
end

// ===== 彩条像素数据 =====
reg [23:0] pd;
always @(posedge pclk_r) begin
if (px < 100) pd <= {8'hFF,8'hFF,8'hFF};
else if (px < 200) pd <= {8'hFF,8'hFF,8'h00};
else if (px < 300) pd <= {8'h00,8'hFF,8'hFF};
else if (px < 400) pd <= {8'h00,8'hFF,8'h00};
else if (px < 500) pd <= {8'hFF,8'h00,8'hFF};
else if (px < 600) pd <= {8'hFF,8'h00,8'h00};
else if (px < 700) pd <= {8'h00,8'h00,8'hFF};
else pd <= 24'd0;
end

// ===== 输出: DE有效时驱动总线, 否则高阻 (对齐原工程) =====
assign lcd_rgb = lcd_de ? pd : 24'hz;

endmodule