实验 10:RGB 屏幕彩条显示
🛒 开发板购买: EK303 GW1N FPGA核心板
一、实验目的
- 掌握 RGB 并行接口 LCD 的驱动时序协议(HSYNC/VSYNC/DE/PCLK)。
- 理解"行消隐"和"场消隐"的概念——屏幕显示区域之外的时间用于同步。
- 学会根据屏幕规格书确定时序参数,并用 FPGA 计数器精确生成时序信号。
- 根据像素坐标输出不同颜色,验证屏幕驱动是否正确。
二、实验过程
2.1 硬件连接
| 信号组 | 位宽 | 方向 | 说明 |
|---|---|---|---|
| R[7:0] | 8 | out | 红色分量 |
| G[7:0] | 8 | out | 绿色分量 |
| B[7:0] | 8 | out | 蓝色分量 |
| HSYNC | 1 | out | 行同步信号 |
| VSYNC | 1 | out | 场同步信号 |
| DE | 1 | out | 数据有效标志 |
| PCLK | 1 | out | 像素时钟 |
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_total | 1056 | 一行总像素(含消隐) |
| V_total | 525 | 一屏总行数(含消隐) |
| H_active | 800 | 有效列 |
| V_active | 480 | 有效行 |
| H_BP | 216 | 行后沿 |
| V_BP | 35 | 场后沿 |
⚠️ 不同屏幕时序参数不同,务必查阅屏幕规格书!
彩条生成:将 800 列水平方向均分为 8 段,每段 100 像素,对应不同的 R/G/B 组合。
| 段 | 颜色 | R | G | B |
|---|---|---|---|---|
| 1 | 白 | 255 | 255 | 255 |
| 2 | 黄 | 255 | 255 | 0 |
| 3 | 青 | 0 | 255 | 255 |
| 4 | 绿 | 0 | 255 | 0 |
| 5 | 品红 | 255 | 0 | 255 |
| 6 | 红 | 255 | 0 | 0 |
| 7 | 蓝 | 0 | 0 | 255 |
| 8 | 黑 | 0 | 0 | 0 |
2.3 Verilog 模块设计
模块名:rgb_colorbar
顶层端口:
| 端口 | 方向 | 位宽 | 说明 |
|---|---|---|---|
clk_50m | input | 1 | 系统时钟 |
hsync | output | 1 | 行同步 |
vsync | output | 1 | 场同步 |
de | output | 1 | 数据有效 |
r, g, b | output | 8 | 颜色分量 |
内部信号:
| 信号 | 位宽 | 说明 |
|---|---|---|
h_cnt | 11 | 列计数器 (0→H_total-1) |
v_cnt | 10 | 行计数器 (0→V_total-1) |
pixel_x | 10 | 当前有效像素的 X 坐标 |
pixel_y | 9 | 当前有效像素的 Y 坐标 |
逻辑描述:
- PCLK 生成:使用 PLL 或分频从 50MHz 产生约 33MHz。
h_cnt每个 PCLK 周期 +1,到达 H_total-1 后归零并触发v_cnt +1。- HSYNC / VSYNC 在消隐期间为低电平。
- DE = 1 仅当
h_cnt和v_cnt均在有效区域时。 - DE 有效时,根据
pixel_x / 100的值选择对应颜色的 R/G/B 输出。 - 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 |
五、思考题
- 如果屏幕分辨率是 480×272,时序参数应该怎么改?
- RGB565 和 RGB888 有什么区别?什么时候用哪个?
- 如何实现横条彩条(水平方向分段)而不是竖条?
- 如果不用 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