• FPGA图像采集与显示项目(一)带LOGO的VGA显示模块


    带LOGO的VGA显示模块

    1 引言

     项目的背景是采集无人车间现场的工件图像并送往控制间pc端处理,最终实现缺陷检测。项目包括图像采集模块,数据传输模块,上位机,缺陷检测算法等四个部分。其中,图像采集模块又分为摄像头配置及数据采集,输入缓存FIFO,SDRAM读写控制器,输出缓存DIDO,VGA/HDMI显示等部分,共大大小小18个模块,之后我将陆续进行更新讲解每一个子模块。

     在图像采集模块中第一部分要介绍的是VGA显示模块,同时为了更好的兼容性,我们也设置了VGA转HDMI模块供调用。关于VGA的原理教程有很多珠玉在前这里就不进行赘述了。我们只说明一点,就是为了更好的显示车间内图像采集的情况,我们希望能够实时显示画面,因此加入了显示模块,同时在显示的图像上添加了一个LOGO图片。这个LOGO的加入有如下几种添加方式:

     1.摄像头输出数据我们是无法编辑的,但可以选择在存入输入缓存FIFO前将图标与摄像头输出的数据进行融合

     2.可以选择在SDRAM从输入缓存FIFO中读数据时融合,当然这样是很繁琐的

     3.在从SDRAM写入数据至输出缓存FIFO时进行融合,原理与2类似

     4.在VGA从输出缓存FIFO读出数据后,对数据进行融合和显示

     5.保留VGA数据不变,我们在VGA转HDMI模块中对数据进行编辑

     在此处,为了不让LOGO干扰到我们后续的缺陷检测我们不选择123方案,为了保证两种显示方案都能显示LOGO我们不选5,因此最终敲定的是方案4。

    2 实现

    2.1 LOGO数据生成

    首先,我们需要将LOGO的图像数据预先存入ROM中,因此需要生成.coe文件,而此次图标的大小为222x98,位宽16bit,这么大的数据量我们不可能手动生成,在这里我们借用了野火的程序,可以将图片自动转化为coe文件

    clear;
    clc;
    img = imread('image.bmp'); %读取图片
    % 使用size函数计算图片矩阵三个维度的大小
    % 第一维为图片的高度,第二维为图片的宽度,第三维为图片维度
    [height,width,z]=size(img); % 100*100*3
    red = img(:,:,1); % 提取红色分量,数据类型为uint8
    green = img(:,:,2); % 提取绿色分量,数据类型为uint8
    blue = img(:,:,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+'); %创建COE文件
    fprintf(fid, 'memory_initialization_radix=16;\n'); %表明数据进制为16进制
    fprintf(fid, 'memory_initialization_vector=\n');
    % m = size(img); %获取图片尺寸,m(1)为高,m(2)为宽
    % for i = 1:m(1)
    % for j = 1:m(2)
    % % 将RGB数据写在一起
    % fprintf(fid, '%02X%02X%02X,\n', img(i,j,1), ... % R
    % img(i,j,2), ... % G
    % img(i,j,3)); % B
    % end
    % end
    for i = 1:height*width
    fprintf(fid,'%04X,\n',rgb(i));
    end
    fseek(fid, -2, 1); % 将最后一个逗号用分号覆盖
    fprintf(fid, ';');
    fclose(fid); %关闭文件

    2.2 生成ROM的IP

    得到了.coe文件后,我们就可以在VIVADO工程中生成ROM的P核了,配置的参数如下:

    image

    图1 IP核配置页1

    image

    图2 IP核配置页2

    image

    图3 IP核配置页3

    image

    图4 IP核配置页4

    如图2,此处我们选用了带有使能的ROM,因此后续工程需要生成时序对齐的使能和地址信号;同时图4中我们注意到,输出数据的latency是两个clock,我们需要把使能信号打两派和数据对齐。

    2.3 verilog编写

    还有一点需要注意的是,我们在屏幕的左上角保留了50/20个像素的空间,使LOGO显示位置更恰当;同时,去除了空白部分(即值为16'h f7bf的部分,这个值可以在.coe文件中查看),使得显示效果更好,最终模块全部的代码如下:

    //**************************************************************************
    // *** 名称 : vga_ctrl.v
    // *** 作者 : 吃豆熊
    // *** 日期 : 2021-11-22
    // *** 描述 : vga时序控制模块,驱动vga将输入模块的彩色像素的信息扫描至显示器上
    //**************************************************************************
    // *** 版本 : 2022-4-1修改为 V2.0
    // *** 修订 : 添加了logo显示
    //**************************************************************************
    module vga_ctrl
    //========================< 参数 >==========================================
    #(
    parameter H_SYNC = 12'd40 , //行同步
    H_BACK = 12'd220 , //行时序后沿
    H_LEFT = 12'd0 , //行时序左边框
    H_VALID = 12'd1280, //行有效数据
    H_RIGHT = 12'd0 , //行时序右边框
    H_FRONT = 12'd110 , //行时序前沿
    H_TOTAL = 12'd1650, //行扫描周期
    parameter V_SYNC = 12'd5 , //场同步
    V_BACK = 12'd20 , //场时序后沿
    V_TOP = 12'd0 , //场时序左边框
    V_VALID = 12'd720 , //场有效数据
    V_BOTTOM = 12'd0 , //场时序右边框
    V_FRONT = 12'd5 , //场时序前沿
    V_TOTAL = 12'd750 //场扫描周期
    )
    //========================< 端口 >==========================================
    (
    input wire vga_clk,
    input wire sys_rst_n,
    input wire [15:0] pix_data, //数据输入
    output reg pix_data_req,
    output wire hsync, //行同步信号
    output wire vsync, //场同步信号
    output reg [15:0] rgb, //输出数据
    output wire VGA_BLK //输出有效信号
    );
    //========================< 定义 >==========================================
    parameter PIC_LEN = 8'd222; //图像长度
    parameter PIC_HGT = 7'd98; //图像宽度
    parameter PIC_SIZE = 15'd21756; //图像大小
    parameter PIC_LSPACE = 6'd50; //图像左侧预留宽度
    parameter PIC_HSPACE = 5'd20; //图像上方预留宽度
    //========================< 信号 >==========================================
    //vga控制信号
    reg [11:0] hsync_cnt; //行同步信号计数器
    reg [11:0] vsync_cnt; //行同步信号计数器
    reg rgb_vld; //图像显示有效信号
    //lolo数据切换辅助信号
    wire rd_en; //rom数据提前读使能
    reg rd_en_reg; //rom数据提前读使能打牌
    reg data_sel; //logo/摄像头数据选择信号
    wire [15:0] logo_data; //logo数据生成
    reg [14:0] rom_addr; //rom数据读地址
    //==========================================================================
    //== 行同步信号控制
    //==========================================================================
    always @(posedge vga_clk or negedge sys_rst_n) begin
    if (sys_rst_n == 1'b0) begin
    hsync_cnt <= 12'b0;
    end
    else if (hsync_cnt == H_TOTAL - 1'b1) begin
    hsync_cnt <= 12'b0;
    end
    else begin
    hsync_cnt <= hsync_cnt + 1'b1;
    end
    end
    assign hsync = ((hsync_cnt <= H_SYNC - 1'b1)&&(sys_rst_n == 1'b1)) ? 1'b1 : 1'b0;
    //==========================================================================
    //== 场同步信号控制
    //==========================================================================
    always @(posedge vga_clk or negedge sys_rst_n) begin
    if (sys_rst_n == 1'b0) begin
    vsync_cnt <= 12'b0;
    end
    else if (vsync_cnt == V_TOTAL - 1'b1) begin
    vsync_cnt <= 12'b0;
    end
    else if (hsync_cnt == H_TOTAL - 1'b1) begin
    vsync_cnt <= vsync_cnt + 1'b1;
    end
    end
    assign vsync = ((vsync_cnt <= V_SYNC - 1'b1)&&(sys_rst_n == 1'b1)) ? 1'b1 : 1'b0;
    //==========================================================================
    //== 生成图像有效区域
    //==========================================================================
    always @(posedge vga_clk or negedge sys_rst_n) begin
    if (sys_rst_n == 1'b0) begin
    rgb_vld <= 1'b0;
    end
    else if ((hsync_cnt >= H_SYNC+H_BACK+H_LEFT-1'd1)&&(hsync_cnt <= H_TOTAL-H_RIGHT-H_FRONT-2'd2)&&
    (vsync_cnt >= V_SYNC+V_BACK+V_TOP)&&(vsync_cnt <= V_SYNC+V_BACK+V_TOP+V_VALID-1'b1)) begin
    rgb_vld <= 1'b1;
    end
    else begin
    rgb_vld <= 1'b0;
    end
    end
    assign VGA_BLK = rgb_vld;
    //==========================================================================
    //== 准备显示图像的请求信号
    //==========================================================================
    always @(posedge vga_clk or negedge sys_rst_n) begin
    if (sys_rst_n == 1'b0) begin
    pix_data_req <= 1'b0;
    end
    else if ((hsync_cnt >= H_SYNC+H_BACK+H_LEFT-2'd2)&&(hsync_cnt <= H_TOTAL-H_RIGHT-H_FRONT-2'd3)&&
    (vsync_cnt >= V_SYNC+V_BACK+V_TOP)&&(vsync_cnt <= V_SYNC+V_BACK+V_TOP+V_VALID-1'b1)) begin
    pix_data_req <= 1'b1;
    end
    else begin
    pix_data_req <= 1'b0;
    end
    end
    //==========================================================================
    //== 图片数据载入
    //==========================================================================
    //提前读取rom数据
    assign rd_en = ((hsync_cnt > H_SYNC+H_BACK+H_LEFT-1'b1+6'd50)&&(hsync_cnt <= H_SYNC+H_BACK+H_LEFT+PIC_LEN-1'b1+6'd50)&&
    (vsync_cnt > V_SYNC+V_BACK+V_TOP+5'd20)&&(vsync_cnt <= V_SYNC+V_BACK+V_TOP+PIC_HGT+5'd20));
    //logo/摄像头数据切换
    always @(posedge vga_clk or negedge sys_rst_n) begin
    if (sys_rst_n == 1'b0) begin
    rd_en_reg <= 1'b0;
    data_sel <= 1'b0;
    end
    else begin
    rd_en_reg <= rd_en;
    data_sel <= rd_en_reg;
    end
    end
    //rom读地址信号生成
    always @(posedge vga_clk or negedge sys_rst_n) begin
    if (sys_rst_n == 1'b0) begin
    rom_addr <= 15'd0;
    end
    else if (rom_addr == (PIC_SIZE - 1'b1)) begin
    rom_addr <= 15'd0;
    end
    else if (rd_en == 1'b1) begin
    rom_addr <= rom_addr + 1'b1;
    end
    end
    //rom模块例化
    rom_16x21756 ins_rom_16x21756
    (
    .clka (vga_clk ), // input wire clka
    .ena (rd_en ), // input wire ena
    .addra (rom_addr ), // input wire [14 : 0] addra
    .douta (logo_data ) // output wire [15 : 0] douta
    );
    //==========================================================================
    //== 有效数据写入
    //==========================================================================
    always @(*) begin
    if (rgb_vld == 1'b0) begin
    rgb <= 16'd0;
    end
    else if ((data_sel == 1'b1) && (logo_data != 16'hf7bf)) begin
    rgb <= logo_data;
    end
    else begin
    rgb <= pix_data;
    end
    end
    endmodule

    3 展示

    由于模块非常简单,而且在V1.0版本已经进行过了仿真,因此不再进行仿真而是直接上板

    这里,我们选用的板子是小梅哥ACX720开发板,外接了SDRAM模块型号为Winbond W9812G6KH,摄像头选用黑金OV5640摄像头模组,最终实际效果如下

    ---

    原创教程,转载请注明出处吃豆熊-图像采集与显示项目

    参考资料:野火FPGA开发教程

  • 相关阅读:
    【HarmonyOS】鸿蒙入门学习
    LINUX基础 第五次课 10月22日
    TP-Link家用路由器上网与防蹭网
    【SpringBoot笔记24】SpringBoot框架结合Redis实现分布式锁
    Nexu私服安装配置,IDEA打包上传私服
    [CSS]常见布局技巧
    WSL2-ubuntu18.04配置笔记5:基于vnc和xfce4实现远程桌面
    k8s 创建普通用户使用
    【递归、搜索与回溯算法】第七节.257. 二叉树的所有路径和46. 全排列
    C语言文件操作总结
  • 原文地址:https://www.cnblogs.com/chidouxiong/p/16159301.html