帶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核了,配置的引數如下:
圖1 IP核配置頁1
圖2 IP核配置頁2
圖3 IP核配置頁3
圖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開發教程