01概述
本文主要是為了驗證之前設計的以太網(wǎng)發(fā)送模塊,確保之前的設計沒有問題,或者找到并修改存在的問題。工程的系統(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ù),傳輸速率也遠大于281Mbit,所以不需要添加DDR3等外部存儲器存儲數(shù)據(jù),只需要一個FIFO暫存小部分數(shù)據(jù)即可。
本次使用原子的上位機對以太網(wǎng)接收的數(shù)據(jù)進行顯示,實測當上位機點擊打開按鈕后,上位機會通過以太網(wǎng)向FPGA發(fā)送一個長度為1的數(shù)據(jù)報,報文數(shù)據(jù)為8’h31,當FPGA接收到數(shù)據(jù)后,即可向上位機傳輸數(shù)據(jù)。上位機對傳輸?shù)臄?shù)據(jù)有格式要求,一幀數(shù)據(jù)的開始需要傳輸固定長度為4字節(jié)的幀頭數(shù)據(jù)32’h,然后需要傳輸一幀圖像的水平像素和垂直像素個數(shù)。
上位機才能夠在接收數(shù)據(jù)后正確顯示圖像。所以每幀圖像的開始需要多傳輸8字節(jié)數(shù)據(jù),一般規(guī)定每次傳輸一行數(shù)據(jù),由于OV7725一行有640個像素,每個像素16位,而以太網(wǎng)每個時鐘傳輸8位數(shù)據(jù),因此需要1280個時鐘才能傳輸完一次數(shù)據(jù)。第一行需要1288個時鐘才能全部傳輸。按理說FIFO的深度設置為2048即可,因為有時候上位機可能會通過ARP獲取FPGA的MAC地址,導致FIFO中的數(shù)據(jù)不能及時被讀取發(fā)送,所以把FIFO的深度設置得稍微大一點,畢竟2048深度能夠充分利用構成FIFO的RAM地址線吧。
整體設計思路是當以太網(wǎng)接收到上位機發(fā)送的8’h31數(shù)據(jù)后,當檢測到場同步信號的上升沿之后,當FIFO中數(shù)據(jù)個數(shù)大于等于一次傳輸?shù)臄?shù)據(jù)個數(shù)時,且以太網(wǎng)發(fā)送模塊處于空閑,將UDP發(fā)送使能信號拉高,之后讀取FIFO中的數(shù)據(jù)進行發(fā)送。OV7725攝像頭初始化和以太網(wǎng)發(fā)送模塊在前文均已經(jīng)做過詳細講解,本文需要著重設計的其實只有攝像頭的數(shù)據(jù)封裝模塊。
02圖像封裝處理模塊
上位機是原子開發(fā)的,據(jù)說點擊關閉后會向開發(fā)板發(fā)送8’h30,但是實測點擊關閉后并沒有向開發(fā)板發(fā)出指令,本處就默認會發(fā)結束傳輸?shù)闹噶畎?。上位機通過以太網(wǎng)向開發(fā)板發(fā)送8’h31后,F(xiàn)PGA開始通過以太網(wǎng)向上位機傳輸以太網(wǎng)數(shù)據(jù),開發(fā)板接收到上位機發(fā)送的8’h30后,F(xiàn)PGA停止向上位機傳輸圖片數(shù)據(jù)。上位機在檢測到4字節(jié)的幀頭數(shù)據(jù)后,才會接收數(shù)據(jù)并顯示圖像。上位機還要知道需要顯示圖像尺寸,因此在傳輸四字節(jié)的幀頭后,需要傳輸2字節(jié)的水平像素個數(shù)和2字節(jié)的垂直像素點個數(shù)。下面通過代碼講解具體設計思路,首先當FPGA接收到UDP數(shù)據(jù)報文長度為1,如果數(shù)據(jù)為8’h31,則將發(fā)送數(shù)據(jù)標志信號拉高,如果數(shù)據(jù)為8’h30,則將發(fā)送數(shù)據(jù)標志信號拉低,其余時間保持不變。由于以太網(wǎng)發(fā)送時鐘和以太網(wǎng)接收時鐘在FPGA內部是同一個時鐘,因此后文以太網(wǎng)發(fā)送時鐘可以直接使用該信號,不屬于異步信號。
//解析接收以太網(wǎng)傳輸?shù)闹噶顢?shù)據(jù),其實以太網(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的復位,因為xilinx的FIFO復位需要多個時鐘周期,因此使用了一個計數(shù)器,將復位脈沖拉高多個時鐘周期,調節(jié)計數(shù)器的位寬就可更改復位脈沖長度。當上位機不接收數(shù)據(jù)時,F(xiàn)IFO一直處于復位狀態(tài)。每次檢測到場同步信號的上升沿,也會對FIFO進行一次復位,清除FIFO中殘留數(shù)據(jù),保證上次傳輸過程可能出現(xiàn)的錯誤不會影響下次傳輸,對應代碼如下:
//后文主要思路:首先為了確保每幀數(shù)據(jù)的正確顯示,當檢測到場同步信號的上升沿時,表示后面就是下一幀圖像數(shù)據(jù)了,此時把FIFO復位。
//xilinx的FIFO復位一般需要多個時鐘周期,當FIFO復位完成之后,就需要把幀頭和水平垂直的像素個數(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復位完成信號的下降沿;
//因為xilinx FIFO復位需要持續(xù)多個時鐘周期才能有效,所以需要一個計數(shù)器來輔助復位;
always@(posedge cam_pclk)begin
if(rst_n==1’b0)begin//初始值為0;
vsync_rst 《= 1‘b0;
end
else if(&rst_cnt)begin//復位計數(shù)器所有位均為高電平時拉低復位信號;
vsync_rst 《= 1’b0;
end//當檢測到場同步信號上升沿時把FIFO復位信號拉高;
else if(cam_vsync_pos)begin
vsync_rst 《= 1‘b1;
end
end
//復位計數(shù)器,對復位脈沖進行計數(shù),改變該計數(shù)器位寬即可更改復位脈沖持續(xù)時間;
always@(posedge cam_pclk)begin
if(rst_n==1’b0)begin//初始值為0;
rst_cnt 《= 3‘d0;
end//對復位脈沖寬度進行計數(shù)。
else if(vsync_rst)begin
rst_cnt 《= rst_cnt + 1;
end
end
assign fifo_rst = (~transfer_flag) || vsync_rst;//FIFO復位信號;
當FIFO復位完成之后,就將4字節(jié)幀頭數(shù)據(jù)、2字節(jié)水平和垂直像素數(shù)據(jù)寫入FIFO中。因此需要一個標志信號和一個計數(shù)器,對應代碼如下。為了保證確保數(shù)據(jù)正確,標志信號和計數(shù)器增加了清零的邏輯。
//寫入幀頭數(shù)據(jù)標志信號,初始值為0,當FIFO復位或者寫入全部幀頭后清零,當FIFO復位完成后拉高;
always@(posedge cam_pclk)begin
if(rst_n==1’b0)begin//初始值為0;
head_flag 《= 1‘b0;
end//當FIFO復位或者寫入全部幀頭后清零;
else if(vsync_rst || (&head_cnt))begin
head_flag 《= 1’b0;
end
else if(fifo_wrrst_busy_neg)begin//FIFO復位完成,開始向FIFO中寫入幀頭數(shù)據(jù);
head_flag 《= 1‘b1;
end
end
//幀頭計數(shù)器,對幀頭標志信號進行計數(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復位的時候將幀頭計數(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//當幀頭寫入標志有效或者輸入有效數(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ù)器的值輸出對應的數(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中,當寫入幀頭標志信號或者輸入圖像數(shù)據(jù)有效且FIFO不處于空閑狀態(tài)時,寫使能拉高。寫數(shù)據(jù)根據(jù)幀頭計數(shù)器寫入對應幀頭數(shù)據(jù),否則如果輸入像素數(shù)據(jù)有效,則將對應數(shù)據(jù)寫入FIFO。之后再來查看FIFO讀側邏輯,由于每幀數(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ù)為水平像素點*2+8,乘2是因為一個水平像素點包含16位數(shù)據(jù),而千兆網(wǎng)一個時鐘只能發(fā)送8位。當檢測到以太網(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ù),由于像素點為16位數(shù)據(jù),以太網(wǎng)每次傳輸8位數(shù)據(jù),所以實際發(fā)送數(shù)據(jù)是水平像素點的2倍;
udp_tx_data_num 《= {CMOS_H_PIXEL,1‘b0};
end
最后是以太網(wǎng)發(fā)送使能信號,當以太網(wǎng)發(fā)送模塊處于空閑且FIFO不處于復位狀態(tài)且FIFO中的數(shù)據(jù)個數(shù)大于一次傳輸?shù)臄?shù)據(jù)個數(shù)且發(fā)送標志信號有效時才能進行發(fā)送。
//生成UDP發(fā)送使能信號;
always@(posedge gmii_tx_clk)begin
if(rst_n==1’b0)begin//初始值為0;
udp_tx_en 《= 1‘b0;
end
else begin//當UDP發(fā)送模塊處于空閑且FIFO中的數(shù)據(jù)大于一次發(fā)送所需數(shù)據(jù)且FIFO不處于復位狀態(tài),則將使能信號拉高,其余時間使能信號均為低電平;
udp_tx_en 《= (udp_tx_rdy && (fifo_rdusedw 》= udp_tx_data_num) && (~fifo_rdrst_busy) && transfer_flag);
end
end
03頂層模塊
頂層模塊跟以前一樣,主要就是對各個模塊的引腳進行連接,對應的RTL視圖如下所示。

圖2 頂層模塊RTL視圖注意power_en信號只是控制模塊開關電源工作的信號,只與固定模塊有關,不使用該模塊可以不考慮。04上板實測
將上述工程的ILA注釋取消,然后進行綜合,下載到開發(fā)板上,開發(fā)板環(huán)境如下所示,連接攝像頭和網(wǎng)線。

圖3 硬件開發(fā)環(huán)境之后打開Wireshark工具,打開原子的上位機軟件,進行如下設置。接收圖像格式為RGB565的圖像數(shù)據(jù)進行顯示,目的IP、UDP端口地址等設置需要與工程頂層模塊的對應參數(shù)保持一致。

圖4 上位機設置然后點擊上位機的打開按鈕,通過Wireshark軟件抓取數(shù)據(jù)如下圖所示,首先上位機會先向FPGA開發(fā)板發(fā)送2個長度為1的UDP報文。如果上位機沒有綁定開發(fā)板的MAC地址和IP地址,還會發(fā)送ARP請求,由于篩選的關系,此處看不見ARP請求數(shù)據(jù)報文。

圖5 wireshark抓取數(shù)據(jù)之后FPGA開始向上位機發(fā)送圖像數(shù)據(jù),第一幀數(shù)據(jù)長度為1288,之后數(shù)據(jù)長度均為1280。如下圖所示,第一幀雖然包含幀頭,但是數(shù)據(jù)其實是錯誤的。錯誤的原因在于上位機可能在任何時候發(fā)起開始信號,有可能在一幀圖像的中間開始傳輸數(shù)據(jù),這樣導致FIFO中的數(shù)據(jù)其實是溢出了很多的,最終導致錯誤。

圖6 wireshark抓取錯誤數(shù)據(jù)但是第二幀開始時FIFO會復位清空,然后就能正常傳輸數(shù)據(jù)了,因此沒有去做修改。在wireshark中可以通過frame.len》=1330去篩選報文長度,進而可以查看全部長度為1288的報文,點擊第二幀數(shù)據(jù)的第一個報文。發(fā)現(xiàn)幀頭和分辨率都顯示正確,沒有問題。

圖7 wireshark第二幀第一行報文之后將ILA設置抓取第一行數(shù)據(jù)報文的時序,當FIFO中數(shù)據(jù)等于1288時,產(chǎn)生UDP使能信號,下個時鐘周期需要發(fā)送報文長度變?yōu)?280。

圖8 ILA抓取第一行數(shù)據(jù)讀出數(shù)據(jù)如下圖所示,幀頭數(shù)據(jù)和像素尺寸均與設置保持一致,傳輸?shù)臄?shù)據(jù)沒有問題。

圖9 ILA抓取幀頭數(shù)據(jù)還可以抓一下FIFO復位之后的時序,如下圖所示,當檢測到場同步信號上升沿后,將FIFO復位信號拉高幾個時鐘周期,等FIFO復位結束之后,將第一行需要發(fā)送的幀頭數(shù)據(jù)寫入FIFO中,之后就等待需要寫入FIFO的像素數(shù)據(jù)到來即可。

圖10 ILA抓取復位時序最后來查看一下攝像頭傳輸?shù)男Ч?,如下面視頻所示,由上位機顯示結果可知,幀率維持在30左右,與前文的計算能夠對應。

圖11 上板結果及幀率如果想要提高幀率,只需要修改PCLK時鐘頻率就行,目前為24MHz,如果將PCLK頻率更改為48MHz,那么幀率可以達到60幀。修改方式如下圖所示,在初始化OV7725攝像頭寄存器時,只需要將地址為8’h0d的高兩位改為2’b11即可。

圖12 修改攝像頭芯片鎖相環(huán)倍頻系數(shù)
只是需要注意一個問題,就是原子的這個上位機軟件點擊“關閉”之后,并不會向開發(fā)板發(fā)送以太網(wǎng)報文,這個可以通過wireshrak軟件去抓,實際上抓不到任何報文。只是上位機軟件不會接收圖像數(shù)據(jù)了而已,可能是開發(fā)者忘記了這個功能吧。05總結
本文將OV7725圖像數(shù)據(jù)通過以太網(wǎng)傳輸?shù)絇C端上位機進行顯示,由于前文實現(xiàn)了攝像頭數(shù)據(jù)采集和以太網(wǎng)收發(fā)模塊的設計,本文只需要將攝像頭采集的圖像封裝成上位機顯示圖像的格式即可,總體比較簡單。為了確保上一幀圖像的錯誤傳輸不會影響下一幀數(shù)據(jù),每次檢測到場同步信號之后,需要復位FIFO,將FIFO清空,然后寫入幀頭數(shù)據(jù),之后將圖像數(shù)據(jù)寫入FIFO。每當FIFO中的數(shù)據(jù)超過一行圖像數(shù)據(jù)時,向以太網(wǎng)發(fā)送模塊的使能信號拉高,然后讀取FIFO中的數(shù)據(jù)通過以太網(wǎng)傳輸給上位機。
審核編輯:黃飛
電子發(fā)燒友App









評論