FPGA影像採集與顯示專案(一)帶LOGO的VGA顯示模組

吃豆熊發表於2022-04-18

帶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開發教程

相關文章