RGB888简介
一个像素点由三种颜色控制,每个颜色8bit,共24bit,三个字节,这就是RGB888。同样的还有RGB565等。
LCD屏幕介绍
1、HSYNC(水平同步信号、行同步信号):产生此信号,说明开始显示新的一行。
2、VSYNC(垂直同步信号、帧同步信号):当产生此信号的话就表示开始显示新的一帧图像。
3、LCD 屏幕中继续存在HBP、 HFP、 VPB 和 VFP 这四个参数的主要目的是为了锁定有效的像素数据。 (白色区域为显示区域)
LCD屏幕时序
重要参数:
- HSYNC:行同步信号,当此信号有效的时候就表示开始显示新的一行数据, 图中低电平有效。
- HSPW:行同步信号宽度,也就是 HSYNC 信号持续时间。 HSYNC 信号不是一个脉冲,而是需要持续一段时间才是有效的,单位为 CLK。
- HBP:行显示后沿(或后肩), 单位是 CLK。
- HOZVAL:行有效显示区域,即显示一行数据所需的时间,假如屏幕分辨率为 1024*600,那么 HOZVAL就是 1024,单位为 CLK。
- HFP:行显示前沿(或前肩),单位是 CLK。
当 HSYNC 信号发出以后,需要等待 HSPW+HBP 个 CLK 时间才会接收到真正有效的像素数据。当显示完一行数据以后需要等待 HFP 个 CLK 时间才能发出下一个 HSYNC 信号,所以显示一行所需要的时间就是: HSPW + HBP + HOZVAL + HFP。
重要参数:
- VSYNC:帧(场)同步信号,当此信号有效的时候就表示开始显示新的一帧数据。
- VSPW:帧同步信号宽度,也就是 VSYNC 信号持续时间,单位为 1 行的时间 。
- VBP:帧显示后沿(或后肩),单位为 1 行的时间。
- LINE:帧有效显示区域,即显示一帧数据所需的时间,假如屏幕分辨率为 1024*600,那么 LINE 就是600 行的时间。
- VFP:帧显示前沿(或前肩),单位为 1 行的时间。
显示一帧所需要的时间就是 T = (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP) 。
LCD数据同步模式
**行场同步模式(HV Mode) **
**数据使能同步模式(DE Mode) **
LCD 的 DE 信号作为数据的有效信号。只有同时扫描到帧有效显示区域和行有效显示区域时, DE 信号才有效(高电平)。当选择 DE 同步模式时,此时行场同步信号 VS 和 HS 必须为高电平。
像素时钟
像素时钟就是 RGB-LCD 的时钟信号 ,以 ATK7016 这款屏幕为例,显示一帧图像所需要的时钟数就是: N(CLK) = (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)= (3 + 20 + 600 + 12) * (20 + 140 + 1024 + 160) = 635 * 1344 = 853440。显示一帧图像需要 853440 个时钟数,那么显示 60 帧就是: 853440 * 60 = 51206400≈51.2M,所以像素时钟就是 51.2MHz。
FPGA实现
LCD驱动模块(1024*600分辨率)
名称 | 类型 | 解释 |
---|---|---|
lcd_clk | input | lcd驱动时钟信号 |
sys_rst_n | input | 复位信号 |
[23:0]pixel_data | input | 像素数据 |
[10:0]pixel_xpos | output | 当前点横坐标 |
[10:0]pixel_ypos | output | 当前点纵坐标 |
lcd_bl | output | 背光 |
lcd_de | output | 数据使能 |
lcd_vs | output | 场同步信号 |
lcd_hs | output | 行同步信号 |
lcd_clk | output | LCD时钟 |
lcd_rgb | output | 颜色数据 |
module lcd_drive(
input lcd_clk , //lcd驱动时钟信号
input sys_rst_n,
input [23:0] pixel_data, //像素数据
output [10:0] pixel_xpos, //当前点横坐标
output [10:0] pixel_ypos, //当前点纵坐标
//rgb接口
output lcd_bl, //背光
output lcd_de, //数据使能
output lcd_vs, //场同步信号
output lcd_hs, //行同步信号
output lcd_clk, //LCD时钟
output [23:0] lcd_rgb //颜色数据
);
//parameter define
//reg define
reg [10:0] h_disp = 11'd1024; //水平分辨率
reg [10:0] v_disp = 11'd600; //垂直分辨率
reg [10:0] h_cnt; //行计数器计数
reg [10:0] h_total = 11'd1344; //行显示周期
reg [10:0] v_cnt; //帧计数器计数
reg [10:0] v_total = 11'd635; //帧显示周期
reg [10:0] h_sync = 11'd; //行同步信号宽度
reg [10:0] h_back; //行显示后沿
reg [10:0] v_sync; //帧同步信号宽度
reg [10:0] v_back; //帧显示后沿
assign lcd_bl = 1'b1;
assign lcd_de = lcd_en;
assign lcd_vs = 1'b1; //选择 DE 同步模式时,此时行场同步信号 VS 和 HS 必须为高电平。
assign lcd_hs = 1'b1;
assign lcd_clk = lcd_clk;
assign lcd_rgb = lcd_en ? pixel_data : 24'd0;
//h_cnt 行计数器计数
always @(posedge lcd_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
h_cnt <= 1'b0;
else if(h_cnt == h_total - 1'd1)
h_cnt <= 1'b0;
else
h_cnt <= h_cnt + 1'b1;
end
//v_hnt 帧计数器计数
always @(posedge lcd_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
v_cnt <= 1'b0;
else begin
if(h_cnt == h_total - 1'd1)begin
if(v_cnt == v_total - 1'd1)
v_cnt <= 1'b0;
else
v_cnt <= v_cnt + 1'b1;
end
end
end
//使能数据输入
assign lcd_en = ((h_cnt >= h_sync + h_back) && (h_cnt < h_cnt + 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_cnt + h_back + h_disp)
&& (v_cnt >= v_sync + v_back - 1'b1) && (v_cnt < v_sync + v_back + v_disp))
? 1'b1 : 1'b0;
//像素点坐标
assign pixel_xpos = data_req ? (h_cnt - h_back - h_sync + 1'b1) : 11'd0;
assign pixel_ypos = data_req ? (v_cnt - v_back - v_sync + 1'b1) : 11'd0;
endmodule
ROM显示
ROM 是通过例化 IP 核来实现的只读存储器,它使用 FPGA 的片上存储资源,即BRAM。(达芬奇开发板使用的FPGA 芯片的 BRAM 存储容量为 1.8Mbit)
OM 作为只读存储器,在调用 IP 核时需要指定初始化文件,在这里就是写入存储器中的图片数据,各种格式的图片(bmp、 jpg 等)在 Xilinx 开发软件中都是以 COE 文件或者 HEX 文件的形式导入到 ROM中的
COE 文件格式
使用matlab将图片转化成COE文件
clear %清理命令行窗口
clc %清理工作区
% 使用 imread 函数读取图片,并转化为三维矩阵
image_array = imread('logo.bmp');
% 使用 size 函数计算图片矩阵三个维度的大小
% 第一维为图片的高度,第二维为图片的宽度,第三维为图片维度
[height,width,z]=size(image_array); % 100*100*3
red = image_array(:,:,1); % 提取红色分量,数据类型为 uint8
green = image_array(:,:,2); % 提取绿色分量,数据类型为 uint8
blue = image_array(:,:,3); % 提取蓝色分量,数据类型为 uint8
% 使用 reshape 函数将各个分量重组成一个一维矩阵
%为了避免溢出,将 uint8 类型的数据扩大为 uint32 类型
r = uint32(reshape(red' , 1 ,height*width));
g = uint32(reshape(green' , 1 ,height*width));
b = uint32(reshape(blue' , 1 ,height*width));
% 初始化要写入.COE 文件中的 RGB 颜色矩阵
rgb=zeros(1,height*width);
% 导入的图片为 24bit 真彩色图片,每个像素占用 24bit,RGB888
% 将 RGB888 转换为 RGB565
% 红色分量右移 3 位取出高 5 位,左移 11 位作为 ROM 中 RGB 数据的第 15bit 到第 11bit
% 绿色分量右移 2 位取出高 6 位,左移 5 位作为 ROM 中 RGB 数据的第 10bit 到第 5bit
% 蓝色分量右移 3 位取出高 5 位,左移 0 位作为 ROM 中 RGB 数据的第 4bit 到第 0bit
for i = 1:height*width
rgb(i) = bitshift(bitshift(r(i),-3),11)
+ bitshift(bitshift(g(i),-2),5)
+ bitshift(bitshift(b(i),-3),0);
end
fid = fopen( 'image.coe', 'w+' );
% .mif 文件字符串打印
fprintf( fid, 'MEMORY_INITIALIZATION_RADIX=16;\n');
fprintf( fid, 'MEMORY_INITIALIZATION_VECTOR=\n',height*width);
% 写入图片数据
for i = 1:height*width
if i == height*width
fprintf(fid,'%x;\n',rgb(i)); %最后一个数据后面加分号
else
fprintf(fid,'%x,\n',rgb(i));
end
end
fclose( fid ); % 关闭文件指针
LCD显示模块
module lcd_display (
input lcd_clk,
input sys_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 BACK_COLOR = 24'hE0FFFF; //背景色,浅蓝色
//reg define
reg [13:0] rom_addr ; //ROM 地址
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 - PIC_X_START; //像素点相对于图像区域起始点水平坐标
assign y_cnt = pixel_ypos - PIC_Y_START; //像素点相对于图像区域起始点垂直坐标
assign rom_rd_en = 1'b1; //读使能拉高,即一直读 ROM 数据
//为 LCD 不同显示区域绘制图片、字符和背景色
always @(posedge lcd_pclk or negedge rst_n) begin
if (!sys_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
pixel_data <= BACK_COLOR; //屏幕背景色
end
//根据当前扫描点的横纵坐标为 ROM 地址赋值
always @(posedge lcd_pclk or negedge rst_n) begin
if (!sys_rst_n)
rom_addr <= 14'd0;
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) )
rom_addr <= rom_addr + 1'b1;
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
顶层模块
module lcd_display_top (
input sys_clk, //系统时钟
input sys_rst_n, //系统复位
output lcd_de, //LCD 数据使能信号
output lcd_hs, //LCD 行同步信号
output lcd_vs, //LCD 场同步信号
output lcd_bl, //LCD 背光控制信号
output lcd_clk, //LCD 像素时钟
inout [23:0] lcd_rgb //LCD RGB888 颜色数据
);
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_display u_lcd_display(
.lcd_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.pixel_xpos (pixel_xpos),
.pixel_ypos (pixel_ypos),
.pixel_data (pixel_data)
);
lcd_driver u_lcd_driver(
.lcd_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.pixel_data (pixel_data),
.pixel_xpos (pixel_xpos),
.pixel_ypos (pixel_ypos),
.lcd_de (lcd_de ),
.lcd_hs (lcd_hs ),
.lcd_vs (lcd_vs ),
.lcd_bl (lcd_bl ),
.lcd_clk (lcd_clk ),
.lcd_rgb (lcd_rgb_o)
);
endmodule