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 )
);
endmodule6.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
endmodule6.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
endmodule6.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
endmodule6.2.5 LCD驱动模块
核心逻辑为对行/场计数器进行计数,根据计数器的值生成lcd_en和data_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
);
endmoduleFPGA(六)LCD
https://shuusui.site/blog/2026/01/09/fpga-6/
