FPGA驱动RGB888屏幕——基于正点原子达芬奇FPGA开发板

发布时间 2023-12-11 11:59:01作者: carpe--diem

RGB888简介

重要提示:由于我没有RGB888屏幕,所以代码未经验证,但是我将其在HDMI中使用了,证明代码还是有一定的正确性

一个像素点由三种颜色控制,每个颜色8bit,共24bit,三个字节,这就是RGB888。同样的还有RGB565等。
RGB888 数据格式

LCD屏幕介绍

LCD时间参数
1、HSYNC(水平同步信号、行同步信号):产生此信号,说明开始显示新的一行。
2、VSYNC(垂直同步信号、帧同步信号):当产生此信号的话就表示开始显示新的一帧图像。
3、LCD 屏幕中继续存在HBP、 HFP、 VPB 和 VFP 这四个参数的主要目的是为了锁定有效的像素数据。 (白色区域为显示区域)

LCD屏幕时序

LCD 行显示时序
重要参数:

  1. HSYNC:行同步信号,当此信号有效的时候就表示开始显示新的一行数据, 图中低电平有效。
  2. HSPW:行同步信号宽度,也就是 HSYNC 信号持续时间。 HSYNC 信号不是一个脉冲,而是需要持续一段时间才是有效的,单位为 CLK。
  3. HBP:行显示后沿(或后肩), 单位是 CLK。
  4. HOZVAL:行有效显示区域,即显示一行数据所需的时间,假如屏幕分辨率为 1024*600,那么 HOZVAL就是 1024,单位为 CLK。
  5. HFP:行显示前沿(或前肩),单位是 CLK。

当 HSYNC 信号发出以后,需要等待 HSPW+HBP 个 CLK 时间才会接收到真正有效的像素数据。当显示完一行数据以后需要等待 HFP 个 CLK 时间才能发出下一个 HSYNC 信号,所以显示一行所需要的时间就是: HSPW + HBP + HOZVAL + HFP。
LCD 帧显示时序
重要参数:

  1. VSYNC:帧(场)同步信号,当此信号有效的时候就表示开始显示新的一帧数据。
  2. VSPW:帧同步信号宽度,也就是 VSYNC 信号持续时间,单位为 1 行的时间 。
  3. VBP:帧显示后沿(或后肩),单位为 1 行的时间。
  4. LINE:帧有效显示区域,即显示一帧数据所需的时间,假如屏幕分辨率为 1024*600,那么 LINE 就是600 行的时间。
  5. 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。

不同分辨率的LCD时序参数
接口图

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中的
image.png
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