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