01概述
本文主要是為了驗證之前設(shè)計的以太網(wǎng)發(fā)送模塊,確保之前的設(shè)計沒有問題,或者找到并修改存在的問題。工程的系統(tǒng)框圖如下所示,主要包含OV7725初始化模塊、像素數(shù)據(jù)封裝處理模塊、FIFO、以太網(wǎng)模塊、鎖相環(huán)模塊。
圖1 系統(tǒng)框圖OV7725最多只能輸出640*480的60幀圖像數(shù)據(jù),即每秒傳輸640*480*60*16bit=294912000bit=281Mb it數(shù)據(jù),千兆以太網(wǎng)即使存在幀頭、幀間隙、校驗碼等數(shù)據(jù),傳輸速率也遠(yuǎn)大于281Mbit,所以不需要添加DDR3等外部存儲器存儲數(shù)據(jù),只需要一個FIFO暫存小部分?jǐn)?shù)據(jù)即可。
本次使用原子的上位機(jī)對以太網(wǎng)接收的數(shù)據(jù)進(jìn)行顯示,實(shí)測當(dāng)上位機(jī)點(diǎn)擊打開按鈕后,上位機(jī)會通過以太網(wǎng)向FPGA發(fā)送一個長度為1的數(shù)據(jù)報,報文數(shù)據(jù)為8’h31,當(dāng)FPGA接收到數(shù)據(jù)后,即可向上位機(jī)傳輸數(shù)據(jù)。上位機(jī)對傳輸?shù)臄?shù)據(jù)有格式要求,一幀數(shù)據(jù)的開始需要傳輸固定長度為4字節(jié)的幀頭數(shù)據(jù)32’h,然后需要傳輸一幀圖像的水平像素和垂直像素個數(shù)。
上位機(jī)才能夠在接收數(shù)據(jù)后正確顯示圖像。所以每幀圖像的開始需要多傳輸8字節(jié)數(shù)據(jù),一般規(guī)定每次傳輸一行數(shù)據(jù),由于OV7725一行有640個像素,每個像素16位,而以太網(wǎng)每個時鐘傳輸8位數(shù)據(jù),因此需要1280個時鐘才能傳輸完一次數(shù)據(jù)。第一行需要1288個時鐘才能全部傳輸。按理說FIFO的深度設(shè)置為2048即可,因為有時候上位機(jī)可能會通過ARP獲取FPGA的MAC地址,導(dǎo)致FIFO中的數(shù)據(jù)不能及時被讀取發(fā)送,所以把FIFO的深度設(shè)置得稍微大一點(diǎn),畢竟2048深度能夠充分利用構(gòu)成FIFO的RAM地址線吧。
整體設(shè)計思路是當(dāng)以太網(wǎng)接收到上位機(jī)發(fā)送的8’h31數(shù)據(jù)后,當(dāng)檢測到場同步信號的上升沿之后,當(dāng)FIFO中數(shù)據(jù)個數(shù)大于等于一次傳輸?shù)臄?shù)據(jù)個數(shù)時,且以太網(wǎng)發(fā)送模塊處于空閑,將UDP發(fā)送使能信號拉高,之后讀取FIFO中的數(shù)據(jù)進(jìn)行發(fā)送。OV7725攝像頭初始化和以太網(wǎng)發(fā)送模塊在前文均已經(jīng)做過詳細(xì)講解,本文需要著重設(shè)計的其實(shí)只有攝像頭的數(shù)據(jù)封裝模塊。
02圖像封裝處理模塊
上位機(jī)是原子開發(fā)的,據(jù)說點(diǎn)擊關(guān)閉后會向開發(fā)板發(fā)送8’h30,但是實(shí)測點(diǎn)擊關(guān)閉后并沒有向開發(fā)板發(fā)出指令,本處就默認(rèn)會發(fā)結(jié)束傳輸?shù)闹噶畎伞I衔粰C(jī)通過以太網(wǎng)向開發(fā)板發(fā)送8’h31后,F(xiàn)PGA開始通過以太網(wǎng)向上位機(jī)傳輸以太網(wǎng)數(shù)據(jù),開發(fā)板接收到上位機(jī)發(fā)送的8’h30后,F(xiàn)PGA停止向上位機(jī)傳輸圖片數(shù)據(jù)。上位機(jī)在檢測到4字節(jié)的幀頭數(shù)據(jù)后,才會接收數(shù)據(jù)并顯示圖像。上位機(jī)還要知道需要顯示圖像尺寸,因此在傳輸四字節(jié)的幀頭后,需要傳輸2字節(jié)的水平像素個數(shù)和2字節(jié)的垂直像素點(diǎn)個數(shù)。下面通過代碼講解具體設(shè)計思路,首先當(dāng)FPGA接收到UDP數(shù)據(jù)報文長度為1,如果數(shù)據(jù)為8’h31,則將發(fā)送數(shù)據(jù)標(biāo)志信號拉高,如果數(shù)據(jù)為8’h30,則將發(fā)送數(shù)據(jù)標(biāo)志信號拉低,其余時間保持不變。由于以太網(wǎng)發(fā)送時鐘和以太網(wǎng)接收時鐘在FPGA內(nèi)部是同一個時鐘,因此后文以太網(wǎng)發(fā)送時鐘可以直接使用該信號,不屬于異步信號。
//解析接收以太網(wǎng)傳輸?shù)闹噶顢?shù)據(jù),其實(shí)以太網(wǎng)發(fā)送時鐘和接收端的時鐘時同一個時鐘;
always@(posedge gmii_rx_clk)begin
if(rst_n==1‘b0)begin//初始值為0;
transfer_flag 《= 1’b0;
end
else if(udp_rx_data_vld && (udp_rx_data_num == 16‘d1))begin
if(udp_rx_data == 8’h31)//開始傳輸;
transfer_flag 《= 1‘b1;
else if(udp_rx_data == 8’h30)//停止傳輸;
transfer_flag 《= 1‘b0;
end
end
首先考慮FIFO的復(fù)位,因為xilinx的FIFO復(fù)位需要多個時鐘周期,因此使用了一個計數(shù)器,將復(fù)位脈沖拉高多個時鐘周期,調(diào)節(jié)計數(shù)器的位寬就可更改復(fù)位脈沖長度。當(dāng)上位機(jī)不接收數(shù)據(jù)時,F(xiàn)IFO一直處于復(fù)位狀態(tài)。每次檢測到場同步信號的上升沿,也會對FIFO進(jìn)行一次復(fù)位,清除FIFO中殘留數(shù)據(jù),保證上次傳輸過程可能出現(xiàn)的錯誤不會影響下次傳輸,對應(yīng)代碼如下:
//后文主要思路:首先為了確保每幀數(shù)據(jù)的正確顯示,當(dāng)檢測到場同步信號的上升沿時,表示后面就是下一幀圖像數(shù)據(jù)了,此時把FIFO復(fù)位。
//xilinx的FIFO復(fù)位一般需要多個時鐘周期,當(dāng)FIFO復(fù)位完成之后,就需要把幀頭和水平垂直的像素個數(shù)數(shù)據(jù)寫入FIFO中;
//過段時間就會出現(xiàn)圖像數(shù)據(jù),就把圖像數(shù)據(jù)寫入FIFO中。
//讀FIFO數(shù)據(jù)時需要注意,第一行數(shù)據(jù)包含幀頭等信息,會多8字節(jié)數(shù)據(jù),從第二行開始就是正常的數(shù)據(jù)個數(shù)。
//把場同步信號打兩拍,用于檢測場同步信號上升沿;
always@(posedge cam_pclk)begin
cam_vsync_r 《= {cam_vsync_r[0],cam_vsync};
fifo_wrrst_busy_r 《= fifo_wrrst_busy;
end
assign cam_vsync_pos = cam_vsync_r[0] & (~cam_vsync_r[1]);//檢測場同步信號上升沿;
assign fifo_wrrst_busy_neg = fifo_wrrst_busy_r & (~fifo_wrrst_busy);//檢測FIFO復(fù)位完成信號的下降沿;
//因為xilinx FIFO復(fù)位需要持續(xù)多個時鐘周期才能有效,所以需要一個計數(shù)器來輔助復(fù)位;
always@(posedge cam_pclk)begin
if(rst_n==1’b0)begin//初始值為0;
vsync_rst 《= 1‘b0;
end
else if(&rst_cnt)begin//復(fù)位計數(shù)器所有位均為高電平時拉低復(fù)位信號;
vsync_rst 《= 1’b0;
end//當(dāng)檢測到場同步信號上升沿時把FIFO復(fù)位信號拉高;
else if(cam_vsync_pos)begin
vsync_rst 《= 1‘b1;
end
end
//復(fù)位計數(shù)器,對復(fù)位脈沖進(jìn)行計數(shù),改變該計數(shù)器位寬即可更改復(fù)位脈沖持續(xù)時間;
always@(posedge cam_pclk)begin
if(rst_n==1’b0)begin//初始值為0;
rst_cnt 《= 3‘d0;
end//對復(fù)位脈沖寬度進(jìn)行計數(shù)。
else if(vsync_rst)begin
rst_cnt 《= rst_cnt + 1;
end
end
assign fifo_rst = (~transfer_flag) || vsync_rst;//FIFO復(fù)位信號;
當(dāng)FIFO復(fù)位完成之后,就將4字節(jié)幀頭數(shù)據(jù)、2字節(jié)水平和垂直像素數(shù)據(jù)寫入FIFO中。因此需要一個標(biāo)志信號和一個計數(shù)器,對應(yīng)代碼如下。為了保證確保數(shù)據(jù)正確,標(biāo)志信號和計數(shù)器增加了清零的邏輯。
//寫入幀頭數(shù)據(jù)標(biāo)志信號,初始值為0,當(dāng)FIFO復(fù)位或者寫入全部幀頭后清零,當(dāng)FIFO復(fù)位完成后拉高;
always@(posedge cam_pclk)begin
if(rst_n==1’b0)begin//初始值為0;
head_flag 《= 1‘b0;
end//當(dāng)FIFO復(fù)位或者寫入全部幀頭后清零;
else if(vsync_rst || (&head_cnt))begin
head_flag 《= 1’b0;
end
else if(fifo_wrrst_busy_neg)begin//FIFO復(fù)位完成,開始向FIFO中寫入幀頭數(shù)據(jù);
head_flag 《= 1‘b1;
end
end
//幀頭計數(shù)器,對幀頭標(biāo)志信號進(jìn)行計數(shù),因為需要8個數(shù)據(jù),所以計數(shù)到7之后可以通過溢出清零;
always@(posedge cam_pclk)begin
if(rst_n==1’b0)begin//初始值為0;
head_cnt 《= 3‘d0;
end
else if(fifo_rst)begin//FIFO復(fù)位的時候?qū)^計數(shù)器清零;
head_cnt 《= 3’d0;
end
else if(head_flag)begin
head_cnt 《= head_cnt + 1;
end
end
//FIFO寫使能信號。
always@(posedge cam_pclk)begin
if(rst_n==1‘b0)begin//初始值為0;
fifo_wr_en 《= 1’b0;
end//當(dāng)幀頭寫入標(biāo)志有效或者輸入有效數(shù)據(jù)時拉高,其余時間均為低電平;
else begin
fifo_wr_en 《= (head_flag || cam_href) && (~fifo_wrrst_busy);
end
end
//FIFO寫數(shù)據(jù)信號,向FIFO中寫入有效數(shù)據(jù);
always@(posedge cam_pclk)begin
if(rst_n==1‘b0)begin//初始值為0;
fifo_wdata 《= 8’d0;
end
else if(head_flag)begin
case (head_cnt)//在輸出幀頭數(shù)據(jù)時,根據(jù)計數(shù)器的值輸出對應(yīng)的數(shù)據(jù);
3‘d0 : fifo_wdata 《= IMG_FRAME_HEAD[31:24];//幀頭;
3’d1 : fifo_wdata 《= IMG_FRAME_HEAD[23:16];//幀頭;
3‘d2 : fifo_wdata 《= IMG_FRAME_HEAD[15: 8];//幀頭;
3’d3 : fifo_wdata 《= IMG_FRAME_HEAD[ 7: 0];//幀頭;
3‘d4 : fifo_wdata 《= {6’d0,CMOS_H_PIXEL[9: 8]};//水平方向分辨率;
3‘d5 : fifo_wdata 《= CMOS_H_PIXEL[7: 0];//水平方向分辨率;
3’d6 : fifo_wdata 《= {7‘d0,CMOS_V_PIXEL[8]};//垂直方向分辨率;
3’d7 : fifo_wdata 《= CMOS_V_PIXEL[7: 0];//垂直方向分辨率;
default : ;
endcase
end//像素數(shù)據(jù)有效時,將像素數(shù)據(jù)輸出;
else if(cam_href)begin
fifo_wdata 《= cam_data;
end
end
之后就是將接收的攝像頭數(shù)據(jù)寫入FIFO中,當(dāng)寫入幀頭標(biāo)志信號或者輸入圖像數(shù)據(jù)有效且FIFO不處于空閑狀態(tài)時,寫使能拉高。寫數(shù)據(jù)根據(jù)幀頭計數(shù)器寫入對應(yīng)幀頭數(shù)據(jù),否則如果輸入像素數(shù)據(jù)有效,則將對應(yīng)數(shù)據(jù)寫入FIFO。之后再來查看FIFO讀側(cè)邏輯,由于每幀數(shù)據(jù)開頭需要多傳輸8字節(jié)數(shù)據(jù),所以也需要檢測一幀的開始。因此把場同步信號同步到千兆網(wǎng)發(fā)送時鐘域下,并且檢測其上升沿。
//把場同步信號同步到以太網(wǎng)發(fā)送時鐘域下,然后檢測其上升沿,所以需要將場同步信號延遲三個時鐘周期。
//延遲的前兩個時鐘周期用于同步,后一個時鐘周期用于檢測上升沿;
always@(posedge gmii_tx_clk)begin
cam_vsync_txc_r 《= {cam_vsync_txc_r[1:0],cam_vsync};
end
//在以太網(wǎng)發(fā)送時鐘域下檢測cam_vsync信號上升沿;
assign cam_vsync_txc_pos = cam_vsync_txc_r[1] & (~cam_vsync_txc_r[2]);
如果檢測到場同步信號上升沿,表示一幀圖像傳輸?shù)拈_始,那么需要發(fā)送的數(shù)據(jù)個數(shù)為水平像素點(diǎn)*2+8,乘2是因為一個水平像素點(diǎn)包含16位數(shù)據(jù),而千兆網(wǎng)一個時鐘只能發(fā)送8位。當(dāng)檢測到以太網(wǎng)發(fā)送使能信號有效時,表示已經(jīng)發(fā)送過一次數(shù)據(jù)了,即幀頭被發(fā)送,那么后續(xù)發(fā)送的數(shù)據(jù)就不包含幀頭,會少8字節(jié)數(shù)據(jù)。
//以太網(wǎng)每次發(fā)送數(shù)據(jù)的報文長度,單位字節(jié)。
always@(posedge gmii_tx_clk)begin
if(rst_n==1‘b0)begin//初始值為0;
udp_tx_data_num 《= {CMOS_H_PIXEL,1’b0};
end
else if(cam_vsync_txc_pos)begin//第一行數(shù)據(jù)需要多傳輸8個字節(jié)的幀頭數(shù)據(jù);
udp_tx_data_num 《= {CMOS_H_PIXEL,1‘b0} + 16’d8;
end
else if(udp_tx_en)//其余行正常傳輸數(shù)據(jù),由于像素點(diǎn)為16位數(shù)據(jù),以太網(wǎng)每次傳輸8位數(shù)據(jù),所以實(shí)際發(fā)送數(shù)據(jù)是水平像素點(diǎn)的2倍;
udp_tx_data_num 《= {CMOS_H_PIXEL,1‘b0};
end
最后是以太網(wǎng)發(fā)送使能信號,當(dāng)以太網(wǎng)發(fā)送模塊處于空閑且FIFO不處于復(fù)位狀態(tài)且FIFO中的數(shù)據(jù)個數(shù)大于一次傳輸?shù)臄?shù)據(jù)個數(shù)且發(fā)送標(biāo)志信號有效時才能進(jìn)行發(fā)送。
//生成UDP發(fā)送使能信號;
always@(posedge gmii_tx_clk)begin
if(rst_n==1’b0)begin//初始值為0;
udp_tx_en 《= 1‘b0;
end
else begin//當(dāng)UDP發(fā)送模塊處于空閑且FIFO中的數(shù)據(jù)大于一次發(fā)送所需數(shù)據(jù)且FIFO不處于復(fù)位狀態(tài),則將使能信號拉高,其余時間使能信號均為低電平;
udp_tx_en 《= (udp_tx_rdy && (fifo_rdusedw 》= udp_tx_data_num) && (~fifo_rdrst_busy) && transfer_flag);
end
end
03頂層模塊
頂層模塊跟以前一樣,主要就是對各個模塊的引腳進(jìn)行連接,對應(yīng)的RTL視圖如下所示。
圖2 頂層模塊RTL視圖注意power_en信號只是控制模塊開關(guān)電源工作的信號,只與固定模塊有關(guān),不使用該模塊可以不考慮。04上板實(shí)測
將上述工程的ILA注釋取消,然后進(jìn)行綜合,下載到開發(fā)板上,開發(fā)板環(huán)境如下所示,連接攝像頭和網(wǎng)線。
圖3 硬件開發(fā)環(huán)境之后打開Wireshark工具,打開原子的上位機(jī)軟件,進(jìn)行如下設(shè)置。接收圖像格式為RGB565的圖像數(shù)據(jù)進(jìn)行顯示,目的IP、UDP端口地址等設(shè)置需要與工程頂層模塊的對應(yīng)參數(shù)保持一致。
圖4 上位機(jī)設(shè)置然后點(diǎn)擊上位機(jī)的打開按鈕,通過Wireshark軟件抓取數(shù)據(jù)如下圖所示,首先上位機(jī)會先向FPGA開發(fā)板發(fā)送2個長度為1的UDP報文。如果上位機(jī)沒有綁定開發(fā)板的MAC地址和IP地址,還會發(fā)送ARP請求,由于篩選的關(guān)系,此處看不見ARP請求數(shù)據(jù)報文。
圖5 wireshark抓取數(shù)據(jù)之后FPGA開始向上位機(jī)發(fā)送圖像數(shù)據(jù),第一幀數(shù)據(jù)長度為1288,之后數(shù)據(jù)長度均為1280。如下圖所示,第一幀雖然包含幀頭,但是數(shù)據(jù)其實(shí)是錯誤的。錯誤的原因在于上位機(jī)可能在任何時候發(fā)起開始信號,有可能在一幀圖像的中間開始傳輸數(shù)據(jù),這樣導(dǎo)致FIFO中的數(shù)據(jù)其實(shí)是溢出了很多的,最終導(dǎo)致錯誤。
圖6 wireshark抓取錯誤數(shù)據(jù)但是第二幀開始時FIFO會復(fù)位清空,然后就能正常傳輸數(shù)據(jù)了,因此沒有去做修改。在wireshark中可以通過frame.len》=1330去篩選報文長度,進(jìn)而可以查看全部長度為1288的報文,點(diǎn)擊第二幀數(shù)據(jù)的第一個報文。發(fā)現(xiàn)幀頭和分辨率都顯示正確,沒有問題。
圖7 wireshark第二幀第一行報文之后將ILA設(shè)置抓取第一行數(shù)據(jù)報文的時序,當(dāng)FIFO中數(shù)據(jù)等于1288時,產(chǎn)生UDP使能信號,下個時鐘周期需要發(fā)送報文長度變?yōu)?280。
圖8 ILA抓取第一行數(shù)據(jù)讀出數(shù)據(jù)如下圖所示,幀頭數(shù)據(jù)和像素尺寸均與設(shè)置保持一致,傳輸?shù)臄?shù)據(jù)沒有問題。
圖9 ILA抓取幀頭數(shù)據(jù)還可以抓一下FIFO復(fù)位之后的時序,如下圖所示,當(dāng)檢測到場同步信號上升沿后,將FIFO復(fù)位信號拉高幾個時鐘周期,等FIFO復(fù)位結(jié)束之后,將第一行需要發(fā)送的幀頭數(shù)據(jù)寫入FIFO中,之后就等待需要寫入FIFO的像素數(shù)據(jù)到來即可。
圖10 ILA抓取復(fù)位時序最后來查看一下攝像頭傳輸?shù)男Ч?,如下?a href="http://www.brongaenegriffin.com/v/" target="_blank">視頻所示,由上位機(jī)顯示結(jié)果可知,幀率維持在30左右,與前文的計算能夠?qū)?yīng)。
圖11 上板結(jié)果及幀率如果想要提高幀率,只需要修改PCLK時鐘頻率就行,目前為24MHz,如果將PCLK頻率更改為48MHz,那么幀率可以達(dá)到60幀。修改方式如下圖所示,在初始化OV7725攝像頭寄存器時,只需要將地址為8’h0d的高兩位改為2’b11即可。
圖12 修改攝像頭芯片鎖相環(huán)倍頻系數(shù)
只是需要注意一個問題,就是原子的這個上位機(jī)軟件點(diǎn)擊“關(guān)閉”之后,并不會向開發(fā)板發(fā)送以太網(wǎng)報文,這個可以通過wireshrak軟件去抓,實(shí)際上抓不到任何報文。只是上位機(jī)軟件不會接收圖像數(shù)據(jù)了而已,可能是開發(fā)者忘記了這個功能吧。05總結(jié)
本文將OV7725圖像數(shù)據(jù)通過以太網(wǎng)傳輸?shù)絇C端上位機(jī)進(jìn)行顯示,由于前文實(shí)現(xiàn)了攝像頭數(shù)據(jù)采集和以太網(wǎng)收發(fā)模塊的設(shè)計,本文只需要將攝像頭采集的圖像封裝成上位機(jī)顯示圖像的格式即可,總體比較簡單。為了確保上一幀圖像的錯誤傳輸不會影響下一幀數(shù)據(jù),每次檢測到場同步信號之后,需要復(fù)位FIFO,將FIFO清空,然后寫入幀頭數(shù)據(jù),之后將圖像數(shù)據(jù)寫入FIFO。每當(dāng)FIFO中的數(shù)據(jù)超過一行圖像數(shù)據(jù)時,向以太網(wǎng)發(fā)送模塊的使能信號拉高,然后讀取FIFO中的數(shù)據(jù)通過以太網(wǎng)傳輸給上位機(jī)。
審核編輯:黃飛
評論