當前位置:網站首頁>【正點原子FPGA連載】 第三十五章 基於OV7725的PL以太網視頻傳輸實驗-摘自【正點原子】領航者ZYNQ之FPGA開發指南_V2.0

【正點原子FPGA連載】 第三十五章 基於OV7725的PL以太網視頻傳輸實驗-摘自【正點原子】領航者ZYNQ之FPGA開發指南_V2.0

2022-01-27 08:38:35 正點原子

1)實驗平臺:正點原子領航者ZYNQ開發板
2)平臺購買地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套實驗源碼+手册+視頻下載地址:http://www.openedv.com/thread-301505-1-1.html
4)對正點原子FPGA感興趣的同學可以加群討論:994244016
5)關注正點原子公眾號,獲取最新資料更新
在這裏插入圖片描述

第三十五章 基於OV7725的PL以太網視頻傳輸實驗

OV7725是OmniVision(豪威科技)公司生產的一顆CMOS圖像傳感器,該傳感器功耗低、可靠性高以及采集速率快,主要應用在玩具、安防監控、電腦多媒體等領域。本章我們將使用ZYNQ開發板實現對OV7725的數字圖像采集,並通過開發板上的PL以太網接口發送給上比特機實時顯示。
本章分為以下幾個章節:
35.1 簡介
35.2 實驗任務
35.3 硬件設計
35.4 程序設計
35.5 下載驗證
35.1 簡介
OV7725簡介
OV7725是一款1/4英寸單芯片圖像傳感器,其感光陣列達到640*480,能實現最快60fps分辨率的圖像采集。傳感器內部集成了圖像處理的功能,包括自動曝光控制(AEC)、自動增益控制(AGC)和自動白平衡(AWB)等。同時傳感器具有較高的感光靈敏度,適合低照度的應用,下圖為OV7725的功能框圖。
在這裏插入圖片描述

圖 7.5.13.1 OV7725功能框圖
由上圖可知,感光陣列(image array)在XCLK時鐘的驅動下進行圖像采樣,輸出640*480陣列的模擬數據;接著模擬信號處理器在時序發生器(video timing generator)的控制下對模擬數據進行算法處理(analog processing);模擬數據處理完成後分成G(綠色)和R/B(紅色/藍色)兩路通道經過AD轉換器後轉換成數字信號,並且通過DSP進行相關圖像處理,最終輸出所配置格式的10比特視頻數據流。模擬信號處理以及DSP等都可以通過寄存器(registers)來配置,配置寄存器的接口就是SCCB接口,該接口協議兼容IIC協議。
SCCB(Serial Camera Control Bus,串行攝像頭控制總線)是由OV(OmniVision的簡稱)公司定義和發展的三線式串行總線,該總線控制著攝像頭大部分的功能,包括圖像數據格式、分辨率以及圖像處理參數等。OV公司為了减少傳感器引脚的封裝,現在SCCB總線大多采用兩線式接口總線。
OV7725使用的是兩線式接口總線,該接口總線包括SIO_C串行時鐘輸入線和SIO_D串行雙向數據線,分別相當於IIC協議的SCL信號線和SDA信號線。我們在前面提到過SCCB協議兼容IIC協議,是因為SCCB協議和IIC協議非常相似,有關IIC協議的詳細介紹請大家參考“EEPROM讀寫實驗”章節。
SCCB的寫傳輸協議如下圖所示:
在這裏插入圖片描述

圖 7.5.13.2 SCCB寫傳輸協議
上圖中的ID ADDRESS是由7比特器件地址和1比特讀寫控制比特構成(0:寫 1:讀),OV7725的器件地址為7’h21,所以在寫傳輸協議中,ID Address(W) = 8’h42(器件地址左移1比特,低比特補0);Sub-address為8比特寄存器地址,在OV7725的數據手册中定義了0x00~0xAC共173個寄存器,有些寄存器是可改寫的,有些是只讀的,只有可改寫的寄存器才能正確寫入;Write Data為8比特寫數據,每一個寄存器地址對應8比特的配置數據。上圖中的第9比特X錶示Don’t Care(不必關心比特),該比特是由從機(此處指OV7725)發出應答信號來響應主機錶示當前ID Address、Sub-address和Write Data是否傳輸完成,但是從機有可能不發出應答信號,因此主機(此處指FPGA)可不用判斷此處是否有應答,直接默認當前傳輸完成即可。
我們可以發現,SCCB和IIC寫傳輸協議是極為相似的,只是在SCCB寫傳輸協議中,第9比特為不必關心比特,而IIC寫傳輸協議為應答比特。SCCB的讀傳輸協議和IIC有些差异,在IIC讀傳輸協議中,寫完寄存器地址後會有restart即重複開始的操作;而SCCB讀傳輸協議中沒有重複開始的概念,在寫完寄存器地址後,發起總線停止信號,下圖為SCCB的讀傳輸協議。
在這裏插入圖片描述

圖 7.5.13.3 SCCB讀傳輸協議
由上圖可知,SCCB讀傳輸協議分為兩個部分。第一部分是寫器件地址和寄存器地址,即先進行一次虛寫操作,通過這種虛寫操作使地址指針指向虛寫操作中寄存器地址的比特置,當然虛寫操作也可以通過前面介紹的寫傳輸協議來完成。第二部分是讀器件地址和讀數據,此時讀取到的數據才是寄存器地址對應的數據,注意ID Address(R) = 8’h43(器件地址左移1比特,低比特補1)。上圖中的NA比特由主機(這裏指FPGA)產生,由於SCCB總線不支持連續讀寫,因此NA比特必須為高電平。
在OV7725正常工作前,必須先對傳感器進行初始化,即通過配置寄存器使其工作在預期的工作模式,以及得到較好畫質的圖像。因為SCCB的寫傳輸協議和IIC幾乎相同,因此我們可以直接使用IIC的驅動程序來配置攝像頭。當然這麼多寄存器也並非都需要配置,很多寄存器可以采用默認的值。OV公司提供了OV7725的軟件使用手册(OV7725 Software Application Note,比特於開發板所隨附的資料“7_硬件資料/6_OV7725資料/OV7725 Software Application Note.pdf”),如果某些寄存器不知道如何配置可以參考此手册,下錶是本程序用到的關鍵寄存器的配置說明。
錶 35.1.1 OV7725關鍵寄存器配置說明
地址
(HEX) 寄存器 默認值
(HEX) 詳細說明
0x0C COM3 0x10 Bit[7]:保留
Bit[6]:水平鏡像開關
Bit[5]:交換RGB輸出模式B/R比特置
Bit[4]:交換YUV輸出模式Y/UV比特置
Bit[3]:交換MSB/LSB比特置
Bit[2]:電源休眠期間輸出時鐘三態選擇
0:三態
1:非三態
Bit[1]:電源休眠期間輸出數據三態選擇
0:三態
1:非三態
Bit[0]:彩條測試使能
0x0D COM4 0x41 Bit[7:6]:PLL頻率控制
00:旁路PLL(直通)
01: PLL 4x
02: PLL 6x
03: PLL 8x
0x11 CLKRC 0x00 Bit[6]:選擇是否直接使用外部時鐘
Bit[5:0]:內部PLL配置
F(internal clk) = F(input clk) * PLL multiplier/[(CLKRC[5:0]+1)* 2]
0x12 COM7 0x00 Bit[7]:SCCB寄存器複比特
0:保持不變
1:複比特所有的寄存器
Bit[6]:0:VGA分辨率輸出
1:QVGA分辨率輸出
Bit[5]:BT.656協議開關
Bit[4]:Sensor RAW
Bit[3:2]:RGB輸出格式控制
00:GRB4:2:2
01:RGB565
10:RGB555
11: RGB444
Bit[1:0]:輸出格式控制
00:YUV
01:Processed Bayer RAW
10:RGB
11:Bayer RAW
0x13 COM8 0xCF Bit[2]:選擇是否開啟自動增益AGC功能
Bit[1]:選擇是否開啟自動白平衡AWB功能
Bit[0]:選擇是否開啟自動曝光AEC功能
0x15 COM10 0x00 Bit[7]:反轉輸出圖像數據
Bit[6]:切換HREF到HSYNC信號
Bit[5]:PCLK輸出選項
0:PCLK時鐘有效
1:在行無有效信號時PCLK無效
Bit[4]:翻轉PCLK
Bit[3]:翻轉HREF
Bit[1]:翻轉VSYNC
bit[0]:數據輸出範圍選擇
0:10bit圖像數據輸出
1:高8bit圖像數據輸出
0x9B BRIGHT 0x00 亮度值補償,可以通過此值提高像素的亮度
OV7725的寄存器較多,對於其它寄存器的描述可以參OV7725的數據手册。
下圖為OV7725的一些特性。
在這裏插入圖片描述

圖 7.5.13.4 OV7725的特性
從上圖可以看出,OV7725的輸入時鐘頻率的範圍是10Mhz~48Mhz;SCCB總線的SIO_C的時鐘頻率最大為400KHz;配置寄存器軟件複比特(寄存器地址0x12 Bit[7]比特)和硬件複比特(cam_rst_n引脚)後需要等待最大1ms才能配置其它寄存器;每次配置完寄存器後,需要最大300ms時間的延遲,也就是10幀圖像輸出的時間才能輸出穩定的視頻流。
OV7725支持多種不同分辨率圖像的輸出,包括VGA(640480)、QVGA(320240)以及CIF(一種常用的標准化圖像格式,分辨率為352288)到4030等任意尺寸。可通過寄存器地址0x12(COM7)、0x17(HSTART)、0x18(HSIZE)、0x19(VSTRT)、0x1A(VSIZE)、0x32(HREF)、0x29(HoutSize)、0x2C(VOutSize)、0x2A(EXHCH)來配置輸出圖像的分辨率。
OV7725支持多種不同的數據像素格式,包括YUV(亮度參量和色度參量分開錶示的像素格式)、RGB(其中RGB格式包含RGB565、RGB555等)以及8比特的RAW(原始圖像數據)和10比特的RAW,通過寄存器地址0x12(COM7)配置不同的數據像素格式。
一般通過原始像素數據(如RGB565或者RGB888格式)來作為顯示的數據會更加方便,因此我們將OV7725攝像頭輸出的圖像像素數據配置成RGB565格式。本次實驗采用OV7725支持的最大分辨率640*480,下圖為攝像頭輸出的VGA幀模式時序圖。
在這裏插入圖片描述

圖 7.5.13.5 VGA幀模式輸出時序圖
在介紹時序圖之前先了解幾個基本的概念。
VSYNC:場同步信號,由攝像頭輸出,用於標志一幀數據的開始與結束。上圖中VSYNC的高電平作為一幀的同步信號,在低電平時輸出的數據有效。需要注意的是場同步信號是可以通過設置寄存器0x15 Bit[1]比特進行取反的,即低電平同步高電平有效,本次實驗使用的是和上圖一致的默認設置;
HREF/HSYNC:行同步信號,由攝像頭輸出,用於標志一行數據的開始與結束。上圖中的HREF和HSYNC是由同一引脚輸出的,只是數據的同步方式不一樣。本次實驗使用的是HREF格式輸出,當HREF為高電平時,圖像輸出有效,可以通過寄存器0x15 Bit[6]進行配置。本次實驗使用的是HREF格式輸出;
D[9:0]:數據信號,由攝像頭輸出,在RGB格式輸出中,只有高8比特D[9:2]是有效的;
tPCLK:一個像素時鐘周期;
tp:單個數據周期,這裏需要注意的是上圖中左下角紅框標注的部分,在RGB模式中,tp代錶兩個tPCLK(像素時鐘)。以RGB565數據格式為例,RGB565采用16bit數據錶示一個像素點,而OV7725在一個像素周期(tPCLK)內只能傳輸8bit數據,因此需要兩個時鐘周期才能輸出一個RGB565數據;
tLine:攝像頭輸出一行數據的時間,共784個tp,包含640tp個高電平和144tp個低電平,其中640tp為有效像素數據輸出的時間。以RGB565數據格式為例,640tp實際上是6402=1280個tPCLK;
由圖 7.5.13.5可知,VSYNC的上昇沿作為一幀的開始,高電平同步脈沖的時間為4
tLine,緊接著等待20tLine時間後,HREF開始拉高,此時輸出有效數據;HREF由640tp個高電平和144tp個低電平構成;輸出480行數據之後等待6tLine時間一幀數據傳輸結束。所以輸出一幀圖像的時間實際上是tFrame =(4 + 20 + 480 + 6)tLine = 510tLine。
由此我們可以計算出攝像頭的輸出幀率,以PCLK=25Mhz(周期為40ns)為例,計算出OV7725輸出一幀圖像所需的時間如下:
一幀圖像輸出時間:tFrame = 510
tLine = 510784tp = 5107842tPCLK = 79968040ns = 31.9872ms;
攝像頭輸出幀率:1000ms/31.9872ms ≈ 31Hz。
如果把像素時鐘頻率提高到攝像頭的最大時鐘頻率48Mhz,通過上述計算方法,攝像頭的輸出幀率約為60Hz。
下圖為OV7725輸出RGB565格式的時序圖:
在這裏插入圖片描述

圖 7.5.13.6 RGB565模式時序圖
上圖中的PCLK為OV7725輸出的像素時鐘,HREF為行同步信號,D[9:2]為8比特像素數據。OV7725最大可以輸出10比特數據,在RGB565輸出模式中,只有高8比特是有效的。像素數據在HREF為高電平時有效,第一次輸出的數據為RGB565數據的高8比特,第二次輸出的數據為RGB565數據的低8比特,first byte和second byte組成一個16比特RGB565數據。由上圖可知,數據是在像素時鐘的下降沿改變的,為了在數據最穩定的時刻采集圖像數據,所以我們需要在像素時鐘的上昇沿采集數據。
圖像傳輸簡介
隨著圖像技術、監控技術的發展,通信的數據量越來越大,這無疑對數據傳輸系統的實時性、穩定性和高效性都提出了苛刻的要求。對於大量數據的高速傳輸,一般使用以太網或者USB傳輸方案,而以太網相比於USB,有著傳輸距離更遠的優勢。為了能够滿足視頻在高幀率、高分辨率下實時傳輸,傳統的百兆以太網已不能滿足需求,此時需要通過千兆以太網進行傳輸。本章將使用開發板上的千兆以太網接口傳輸視頻,並通過上比特機實時顯示。
以太網實時傳輸圖像采用的傳輸層協議有TCP和UDP兩種。TCP協議能為兩個端點間的數據傳輸提供相對可靠的保障,這種保障是通過一個握手機制實現的。當數據發送給接收者時,接收者要檢查數據的正確性,當接收者接收到正確數據後給發送者一個確認報文信號,發送者只有接收到接收者的確認報文信號後才能發送下一個數據塊。如果沒有接收到確認報文,這個數據塊就必須要重新發送。盡管這種機制對傳輸數據來說是非常合理的,但當用它在以太網視頻實時傳輸時就會引發很多問題。首先就是延遲問題,在傳輸信道丟包率較高時,TCP的傳輸質量下滑嚴重,重傳擁塞導致視頻延時非常大,失去實時互通的意義。而UDP相比於TCP能提供更高的吞吐量和較低的延遲,非常適合低延時的視頻傳輸場合。
UDP性能的提高是以不能保障數據完整性為代價的,它不能對所傳數據提供擔保,有時會出現數據丟包的現象。為了降低丟包對視頻顯示帶來的影響,我們為每幀圖像添加一個幀頭,用於標志一幀圖像的開始。上比特機解析到圖像幀頭之後,接下來將接收到的像素數據重新放到圖像顯示區域的起始比特置,保證了在視頻傳輸過程中,即使出現丟包的現象,視頻也能恢複到正常顯示的畫面。
35.2 實驗任務
本節實驗任務是使用領航者ZYNQ開發板及OV7725攝像頭實現圖像采集,並通過開發板上的PL以太網接口發送給上比特機實時顯示。
35.3 硬件設計
領航者Zynq開發板上有一個攝像頭擴展接口,該接口可以用來連接OV7725/OV5640等攝像頭模塊。由於SCCB接口通信需要接上拉電阻,因此,將CMOS_SCL信號和CMOS_SDA信號連接上拉電阻,原理圖如圖 7.5.13.1所示:
在這裏插入圖片描述

圖 7.5.13.1 攝像頭擴展接口原理圖
ATK-OV7725是正點原子推出的一款高性能30W像素高清攝像頭模塊。該模塊通過2*9排針(2.54mm間距)同外部連接,我們將攝像頭的排針直接插在開發板上的攝像頭接口即可,模塊外觀如圖 7.5.13.2所示:
在這裏插入圖片描述

圖 7.5.13.2 ATK-OV7725攝像頭模塊實物圖
我們在前面說過,OV7725在RGB565模式中只有高8比特數據是有效的即D[9:2],而我們的攝像頭排針上數據引脚的個數是8比特。實際上,攝像頭排針上的8比特數據連接的就是OV7725傳感器的D[9:2],所以我們直接使用攝像頭排針上的8比特數據引脚即可。
需要注意的是,由圖 7.5.13.1可知,攝像頭擴展口的第18個引脚定義為CMOS_PWDN,而我們的OV7725攝像頭模塊的PWDN引脚固定為低電平,也就是一直處於正常工作模式。OV7725攝像頭模塊的第18個引脚定義為SGM_CTRL,這個引脚是攝像頭驅動時鐘的選擇引脚。OV7725攝像頭模塊內部自帶晶振的,當SGM_CTRL引脚為低電平時,選擇使用攝像頭的外部時鐘,也就是FPGA需要輸出時鐘給攝像頭;當SGM_CTRL引脚為高電平時,選擇使用攝像頭的晶振提供時鐘。本次實驗將SGM_CTRL引脚驅動為高電平,這樣就不用為攝像頭提供驅動時鐘,即不用在CMOS_XCLK引脚上輸出時鐘。
由於PL以太網引脚數目較多,且在前面相應的章節中已經給出它們的管脚列錶,這裏只列出攝像頭相關管脚分配, 如下錶所示:
錶 35.3.1 OV7725攝像頭管脚分配
信號名 方向 管脚 端口說明 IO電平
cam_pclk input W14 cmos 數據像素時鐘 LVCMOS33
cam_vsync input U12 cmos 場同步信號 LVCMOS33
cam_href input T12 cmos 行同步信號 LVCMOS33
cam_rst_n output P14 cmos 複比特信號 LVCMOS33
cam_sgm_ctrl output V15 cmos 時鐘選擇信號 LVCMOS33
cam_data[0] input R14 cmos 數據 LVCMOS33
cam_data[1] input U13 cmos 數據 LVCMOS33
cam_data[2] input V13 cmos 數據 LVCMOS33
cam_data[3] input U15 cmos 數據 LVCMOS33
cam_data[4] input U14 cmos 數據 LVCMOS33
cam_data[5] input W13 cmos 數據 LVCMOS33
cam_data[6] input V12 cmos 數據 LVCMOS33
cam_data[7] input Y14 cmos 數據 LVCMOS33
emio_sccb_tri_io[0] output T10 cmos SCCB_SCL線 LVCMOS33
emio_sccb_tri_io[1] inout T11 cmos SCCB_SDA線 LVCMOS33
相關的管脚約束如下所示:
#系統時鐘和複比特

create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
create_clock -period 8.000 -name eth_rxc [get_ports eth_rxc]
set_property -dict {
    PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {
    PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]

set_property -dict {
    PACKAGE_PIN G15 IOSTANDARD LVCMOS33} [get_ports eth_rst_n]
set_property -dict {
    PACKAGE_PIN K17 IOSTANDARD LVCMOS33} [get_ports eth_rxc]
set_property -dict {
    PACKAGE_PIN E17 IOSTANDARD LVCMOS33} [get_ports eth_rx_ctl]
set_property -dict {
    PACKAGE_PIN B19 IOSTANDARD LVCMOS33} [get_ports {
    eth_rxd[0]}]
set_property -dict {
    PACKAGE_PIN A20 IOSTANDARD LVCMOS33} [get_ports {
    eth_rxd[1]}]
set_property -dict {
    PACKAGE_PIN H17 IOSTANDARD LVCMOS33} [get_ports {
    eth_rxd[2]}]
set_property -dict {
    PACKAGE_PIN H16 IOSTANDARD LVCMOS33} [get_ports {
    eth_rxd[3]}]

set_property -dict {
    PACKAGE_PIN B20 IOSTANDARD LVCMOS33} [get_ports eth_txc]
set_property -dict {
    PACKAGE_PIN K18 IOSTANDARD LVCMOS33} [get_ports eth_tx_ctl]
set_property -dict {
    PACKAGE_PIN D18 IOSTANDARD LVCMOS33} [get_ports {
    eth_txd[0]}]
set_property -dict {
    PACKAGE_PIN C20 IOSTANDARD LVCMOS33} [get_ports {
    eth_txd[1]}]
set_property -dict {
    PACKAGE_PIN D19 IOSTANDARD LVCMOS33} [get_ports {
    eth_txd[2]}]
set_property -dict {
    PACKAGE_PIN D20 IOSTANDARD LVCMOS33} [get_ports {
    eth_txd[3]}]

#CAMERA
create_clock -period 40.000 -name cmos_pclk [get_ports cam_pclk]
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_IBUF]
set_property -dict {
    PACKAGE_PIN W14 IOSTANDARD LVCMOS33} [get_ports cam_pclk]
set_property -dict {
    PACKAGE_PIN P14 IOSTANDARD LVCMOS33} [get_ports cam_rst_n]
set_property -dict {
    PACKAGE_PIN V15 IOSTANDARD LVCMOS33} [get_ports cam_sgm_ctrl]
set_property -dict {
    PACKAGE_PIN R14 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {
    cam_data[0]}]
set_property -dict {
    PACKAGE_PIN U13 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {
    cam_data[1]}]
set_property -dict {
    PACKAGE_PIN V13 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {
    cam_data[2]}]
set_property -dict {
    PACKAGE_PIN U15 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {
    cam_data[3]}]
set_property -dict {
    PACKAGE_PIN U14 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {
    cam_data[4]}]
set_property -dict {
    PACKAGE_PIN W13 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {
    cam_data[5]}]
set_property -dict {
    PACKAGE_PIN V12 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {
    cam_data[6]}]
set_property -dict {
    PACKAGE_PIN Y14 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {
    cam_data[7]}]
set_property -dict {
    PACKAGE_PIN U12 IOSTANDARD LVCMOS33} [get_ports cam_vsync]
set_property -dict {
    PACKAGE_PIN T12 IOSTANDARD LVCMOS33} [get_ports cam_href]
set_property -dict {
    PACKAGE_PIN T10 IOSTANDARD LVCMOS33} [get_ports cam_scl]
set_property -dict {
    PACKAGE_PIN T11 IOSTANDARD LVCMOS33} [get_ports cam_sda]

35.4 程序設計
OV7725在VGA(分辨率為640480)幀模式下,以RGB565格式輸出最高幀率可達60Hz,每秒鐘輸出的數據量達到6064048016bit = 294912000bit = 281.25Mbit。我們FPGA開發板上的PHY芯片類型為千兆以太網,理論上最大傳輸速率為1000Mbit/s,加上幀頭、CRC校驗以及幀間隙帶來的額外開銷,實際上能達到的最大傳輸速率比理論上最大傳輸速率低。盡管實際傳輸速率低於1000Mbit/s,但實時傳輸OV7725攝像頭的圖像完全沒有壓力,可以滿足帶寬要求,因此本次實驗不需要通過片外存儲器緩存圖像,僅將圖像數據先經過FIFO進行緩存,然後通過以太網接口進行發送即可。
根據實驗任務,我們可以大致規劃出系統的控制流程:時鐘模塊用於為IIC驅動模塊、以太網頂層模塊和開始傳輸控制模塊提供驅動時鐘。I2C驅動模塊和I2C配置模塊用於初始化OV7725圖像傳感器;攝像頭采集模塊負責采集攝像頭圖像數據,並且把圖像數據連接至圖像數據封裝模塊,圖像數據封裝模塊將輸入的圖像數據進行比特拼接,並添加圖像的幀頭和行場分辨率;以太網頂層模塊實現以太網數據的收發;開始傳輸控制模塊控制以太網頂層模塊開始/停止發送數據。
OV7725的以太網視頻傳輸系統框圖如下圖所示:
在這裏插入圖片描述

圖 7.5.13.1 系統框圖
頂層模塊的原理圖如下圖所示:
在這裏插入圖片描述

圖 7.5.13.2 頂層模塊原理圖
FPGA頂層模塊(ov7725_udp_pc)例化了以下七個模塊:時鐘模塊(clk_wiz_0)、I2C驅動模塊(i2c_dri)、I2C配置模塊(i2c_ov7725_rgb565_cfg)、攝像頭圖像采集模塊(cmos_capture_data)、開始傳輸控制模塊(start_transfer_ctrl)、圖像數據封裝模塊(img_data_pkt)和以太網頂層模塊模塊(eth_top)。
時鐘模塊(clk_wiz_0):時鐘IP核模塊通過調用MMCM IP核來實現,總共輸出2個時鐘,頻率分別為50Mhz和200Mhz時鐘。50Mhz時鐘作為I2C驅動模塊的驅動時鐘;200Mhz時鐘作為IDELAYCTRL源語的參考時鐘。
I2C驅動模塊(i2c_dri):I2C驅動模塊負責驅動OV7725 SCCB接口總線,用戶可根據該模塊提供的用戶接口可以很方便的對OV7725的寄存器進行配置,該模塊和“EEPROM讀寫實驗”章節中用到的I2C驅動模塊為同一個模塊,有關該模塊的詳細介紹請大家參考“EEPROM讀寫實驗”章節。
I2C配置模塊(i2c_ov7725_rgb565_cfg):I2C配置模塊的驅動時鐘是由I2C驅動模塊輸出的時鐘提供的,這樣方便了I2C驅動模塊和I2C配置模塊之間的數據交互。該模塊寄存需要配置的寄存器地址、數據以及控制初始化的開始與結束,同時該模塊輸出OV7725的寄存器地址和數據以及控制I2C驅動模塊開始執行的控制信號,直接連接到I2C驅動模塊的用戶接口,從而完成對OV7725傳感器的初始化。
攝像頭圖像采集模塊(cmos_capture_data):攝像頭采集模塊在像素時鐘的驅動下將傳感器輸出的場同步信號、行同步信號以及8比特數據轉換成16比特數據信號,完成對OV7725傳感器圖像的采集。
開始傳輸控制模塊(start_transfer_ctrl):該模塊解析以太網頂層模塊接收到的數據,如果收到1個字節的ASCII碼“1”,則錶示以太網開始傳輸圖像數據;如果收到1個字節的ASCII碼“0”,則錶示以太網停止傳輸圖像數據。
圖像數據封裝模塊(img_data_pkt):圖像數據封裝模塊負責將輸入16比特的圖像數據,拼接成32比特數據,以及添加圖像數據的幀頭和行場分辨率。該模塊控制著以太網發送模塊發送的字節數,單次發送一行圖像數據的字節數,模塊內部例化了一個异步FIFO模塊,用於緩存待發送的圖像數據。
以太網頂層模塊(eth_top):以太網頂層模塊實現以太網通信的收發功能,有關該模塊的詳細介紹請大家參考“以太網UDP測試實驗”章節。
頂層模塊部分代碼如下:

1   module ov7725_udp_pc(
2       input              sys_clk     , //系統時鐘 
3       input              sys_rst_n   , //系統複比特信號,低電平有效 
4       //以太網接口
5       input              eth_rxc     , //RGMII接收數據時鐘
6       input              eth_rx_ctl  , //RGMII輸入數據有效信號
7       input       [3:0]  eth_rxd     , //RGMII輸入數據
8       output             eth_txc     , //RGMII發送數據時鐘 
9       output             eth_tx_ctl  , //RGMII輸出數據有效信號
10      output      [3:0]  eth_txd     , //RGMII輸出數據 
11      output             eth_rst_n   , //以太網芯片複比特信號,低電平有效 
12  
13      //攝像頭接口
14      input              cam_pclk    , //cmos 數據像素時鐘
15      input              cam_vsync   , //cmos 場同步信號
16      input              cam_href    , //cmos 行同步信號
17      input     [7:0]    cam_data    , //cmos 數據
18      output             cam_rst_n   , //cmos 複比特信號,低電平有效
19      output             cam_sgm_ctrl, //cmos 時鐘選擇信號, 1:使用攝像頭自帶的晶振
20      output             cam_scl     , //cmos SCCB_SCL線
21      inout              cam_sda       //cmos SCCB_SDA線 
22  );
23  
24  //parameter define
25  //開發板MAC地址 00-11-22-33-44-55
26  parameter  BOARD_MAC = 48'h00_11_22_33_44_55;     
27  //開發板IP地址 192.168.1.10
28  parameter  BOARD_IP  = {
    8'd192,8'd168,8'd1,8'd10};  
29  //目的MAC地址 ff_ff_ff_ff_ff_ff
30  parameter  DES_MAC   = 48'hff_ff_ff_ff_ff_ff;    
31  //目的IP地址 192.168.1.102 
32  parameter  DES_IP    = {
    8'd192,8'd168,8'd1,8'd102};
33  
34  parameter  SLAVE_ADDR = 7'h21 ; //OV7725的器件地址7'h21
35  parameter  BIT_CTRL   = 1'b0          ;  //OV7725的字節地址為8比特 0:8比特 1:16比特
36  parameter  CLK_FREQ   = 26'd50_000_000;  //i2c_dri模塊的驅動時鐘頻率
37  parameter  I2C_FREQ   = 18'd250_000   ;  //I2C的SCL時鐘頻率,不超過400KHz

在代碼的第24至32行定義了四個參量:開發板MAC地址BOARD_MAC,開發板IP地址 BOARD_IP,目的MAC地址DES_MAC(這裏指PC MAC地址),目的IP地址 DES_IP(PC IP地址)。開發板的MAC地址和IP地址是我們隨意定義的,只要不和目的MAC 地址和目的IP地址一樣就可以,否則會產生地址沖突。目的MAC地址這裏寫的是公共MAC 地址(48’hff_ff_ff_ff_ff_ff),也可以修改成電腦網口的MAC地址,DES_IP是對應電腦以太網的IP地址,這裏定義的四個參數是向下傳遞的,需要修改MAC地址或者IP地址時直接在這裏修改即可,而不用在以太網頂層模塊裏面修改。
在代碼的第34行定義了OV7725的器件地址,其器件地址為7’h21;第35行定義了寄存器地址的比特寬,BIT_CTRL=0錶示地址比特寬為8比特,BIT_CTRL=1錶示地址比特寬為16比特。因為OV7725的地址比特寬為8比特,所以BIT_CTRL設置為0。第36行和第37行分別定義了i2c_dri模塊的驅動時鐘頻率和I2C的SCL時鐘頻率。

70  assign  rst_n = sys_rst_n & locked;
71  //不對攝像頭硬件複比特,固定高電平
72  assign  cam_rst_n = 1'b1;
73  //cmos 時鐘選擇信號, 1:使用攝像頭自帶的晶振
74  assign  cam_sgm_ctrl = 1'b1;
75  
76  //例化MMCM
77  clk_wiz_0 u_clk_wiz_0
78     (
79      .clk_out1    (clk_50m),  
80      .clk_out2    (clk_200m), 
81      .reset       (~sys_rst_n),  
82      .locked      (locked),       
83      .clk_in1     (sys_clk)
84      );      
85  
86  //I2C配置模塊 
87  i2c_ov7725_rgb565_cfg u_i2c_cfg(
88      .clk           (i2c_dri_clk),
89      .rst_n         (rst_n),
90      .i2c_done      (i2c_done),
91      .i2c_exec      (i2c_exec),
92      .i2c_data      (i2c_data),
93      .init_done     (cam_init_done)
94      );    
95  
96  //I2C驅動模塊
97  i2c_dri 
98     #(
99      .SLAVE_ADDR  (SLAVE_ADDR),               //參數傳遞
100     .CLK_FREQ    (CLK_FREQ  ),              
101     .I2C_FREQ    (I2C_FREQ  )                
102     ) 
103    u_i2c_dri(
104     .clk         (clk_50m   ),   
105     .rst_n       (rst_n     ),   
106     //i2c interface
107     .i2c_exec    (i2c_exec  ),   
108     .bit_ctrl    (BIT_CTRL  ),   
109     .i2c_rh_wl   (1'b0),                     //固定為0,只用到了IIC驅動的寫操作 
110     .i2c_addr    (i2c_data[15:8]),   
111     .i2c_data_w  (i2c_data[7:0]),   
112     .i2c_data_r  (),   
113     .i2c_done    (i2c_done  ),   
114     .scl         (cam_scl   ),   
115     .sda         (cam_sda   ),   
116     //user interface
117     .dri_clk     (i2c_dri_clk)               //I2C操作時鐘
118 );
119 
120 //攝像頭數據采集模塊
121 cmos_capture_data u_cmos_capture_data(
122 
123     .rst_n              (rst_n & cam_init_done),
124     .cam_pclk           (cam_pclk),   
125     .cam_vsync          (cam_vsync),
126     .cam_href           (cam_href),
127     .cam_data           (cam_data),           
128     .cmos_frame_vsync   (cmos_frame_vsync),
129     .cmos_frame_href    (),
130     .cmos_frame_valid   (img_data_en),     
131     .cmos_frame_data    (img_data)             
132     );

OV7725攝像頭配置模塊和IIC驅動模塊實現對OV7725攝像頭的初始化,在初始化完成後拉高cam_init_done信號,此時開始通過攝像頭數據采集模塊接收攝像頭輸出的圖像數據(如程序中第123行代碼所示),將輸入的8比特數據轉換成16比特RGB565數據。

134 //開始傳輸控制模塊 
135 start_transfer_ctrl u_start_transfer_ctrl(
136     .clk                (eth_rx_clk),
137     .rst_n              (rst_n),
138     .udp_rec_pkt_done   (udp_rec_pkt_done),
139     .udp_rec_en         (udp_rec_en      ),
140     .udp_rec_data       (udp_rec_data    ),
141     .udp_rec_byte_num   (udp_rec_byte_num),
142 
143     .transfer_flag      (transfer_flag)      //圖像開始傳輸標志,1:開始傳輸 0:停止傳輸
144     );       
145      
146 //圖像封裝模塊 
147 img_data_pkt u_img_data_pkt(    
148     .rst_n              (rst_n),              
149    
150     .cam_pclk           (cam_pclk),
151     .img_vsync          (cmos_frame_vsync),
152     .img_data_en        (img_data_en),
153     .img_data           (img_data),
154     .transfer_flag      (transfer_flag),            
155     .eth_tx_clk         (eth_tx_clk     ),
156     .udp_tx_req         (udp_tx_req     ),
157     .udp_tx_done        (udp_tx_done    ),
158     .udp_tx_start_en    (udp_tx_start_en),
159     .udp_tx_data        (udp_tx_data    ),
160     .udp_tx_byte_num    (udp_tx_byte_num)
161     );  
162 
163 //以太網頂層模塊 
164 eth_top  #(
165     .BOARD_MAC     (BOARD_MAC),              //參數例化
166     .BOARD_IP      (BOARD_IP ),          
167     .DES_MAC       (DES_MAC  ),          
168     .DES_IP        (DES_IP   )          
169     )          
170     u_eth_top(          
171     .sys_rst_n       (rst_n     ),           //系統複比特信號,低電平有效 
172     .clk_200m        (clk_200m), 
173     //以太網RGMII接口 
174     .eth_rxc         (eth_rxc   ),           //RGMII接收數據時鐘
175     .eth_rx_ctl      (eth_rx_ctl),           //RGMII輸入數據有效信號
176     .eth_rxd         (eth_rxd   ),           //RGMII輸入數據
177     .eth_txc         (eth_txc   ),           //RGMII發送數據時鐘 
178     .eth_tx_ctl      (eth_tx_ctl),           //RGMII輸出數據有效信號
179     .eth_txd         (eth_txd   ),           //RGMII輸出數據 
180     .eth_rst_n       (eth_rst_n ),           //以太網芯片複比特信號,低電平有效 
181 
182     .gmii_rx_clk     (eth_rx_clk),
183     .gmii_tx_clk     (eth_tx_clk),       
184     .udp_tx_start_en (udp_tx_start_en),
185     .tx_data         (udp_tx_data),
186     .tx_byte_num     (udp_tx_byte_num),
187     .udp_tx_done     (udp_tx_done),
188     .tx_req          (udp_tx_req ),
189     .rec_pkt_done    (udp_rec_pkt_done),
190     .rec_en          (udp_rec_en      ),
191     .rec_data        (udp_rec_data    ),
192     .rec_byte_num    (udp_rec_byte_num)
193     );
194 
195 endmodule

在代碼的第135行至144行例化了開始傳輸控制模塊,由該模塊端口可知,輸入端口信號為以太網接收到的數據,而輸出信號為圖像開始傳輸標志(transfer_flag)。transfer_flag信號用於控制以太網發送圖像數據的開始和停止,連接至圖像數據封裝模塊。
在代碼的第147行至161行例化了圖像數據封裝模塊,該模塊輸入的端口信號為攝像頭圖像數據,而輸出的udp_tx_start_en(以太網開始發送信號)和udp_tx_byte_num(發送的字節數)連接至以太網頂層模塊的以太網發送控制端口,從而控制以太網的UDP發送模塊開始發送圖像數據。
I2C配置模塊寄存需要配置的寄存器地址、數據以及控制初始化的開始與結束,代碼如下所示:

1   module i2c_ov7725_rgb565_cfg(  
2       input                clk      ,  //時鐘信號
3       input                rst_n    ,  //複比特信號,低電平有效
4       
5       input                i2c_done ,  //I2C寄存器配置完成信號
6       output  reg          i2c_exec ,  //I2C觸發執行信號 
7       output  reg  [15:0]  i2c_data ,  //I2C要配置的地址與數據(高8比特地址,低8比特數據)
8       output  reg          init_done   //初始化完成信號
9       );
10  
11  //parameter define
12  parameter  REG_NUM = 7'd70   ;       //總共需要配置的寄存器個數
13  
14  //reg define
15  reg    [9:0]   start_init_cnt;       //等待延時計數器
16  reg    [6:0]   init_reg_cnt  ;       //寄存器配置個數計數器
17  
18  //*****************************************************
19  //** main code
20  //*****************************************************
21  
22  //cam_scl配置成250khz,輸入的clk為1Mhz,周期為1us,1023*1us = 1.023ms
23  //寄存器延時配置
24  always @(posedge clk or negedge rst_n) begin
25      if(!rst_n)
26          start_init_cnt <= 10'b0;    
27      else if((init_reg_cnt == 7'd1) && i2c_done)
28          start_init_cnt <= 10'b0;
29      else if(start_init_cnt < 10'd1023) begin
30          start_init_cnt <= start_init_cnt + 1'b1;                    
31      end
32  end
33  
34  //寄存器配置個數計數 
35  always @(posedge clk or negedge rst_n) begin
36      if(!rst_n)
37          init_reg_cnt <= 7'd0;
38      else if(i2c_exec)   
39          init_reg_cnt <= init_reg_cnt + 7'b1;
40  end         
41  
42  //i2c觸發執行信號 
43  always @(posedge clk or negedge rst_n) begin
44      if(!rst_n)
45          i2c_exec <= 1'b0;
46      else if(start_init_cnt == 10'd1022)
47          i2c_exec <= 1'b1;
48      //只有剛上電和配置第一個寄存器增加延時
49      else if(i2c_done && (init_reg_cnt != 7'd1) && (init_reg_cnt < REG_NUM))
50          i2c_exec <= 1'b1;
51      else
52          i2c_exec <= 1'b0;    
53  end 
54  
55  //初始化完成信號
56  always @(posedge clk or negedge rst_n) begin
57      if(!rst_n)
58          init_done <= 1'b0;
59      else if((init_reg_cnt == REG_NUM) && i2c_done)  
60          init_done <= 1'b1;  
61  end        
62     
63  //配置寄存器地址與數據
64  always @(posedge clk or negedge rst_n) begin
65      if(!rst_n)
66          i2c_data <= 16'b0;
67      else begin
68          case(init_reg_cnt)
69              //先對寄存器進行軟件複比特,使寄存器恢複初始值
70              //寄存器軟件複比特後,需要延時1ms才能配置其它寄存器
71              7'd0 : i2c_data <= {8'h12, 8'h80}; //COM7 BIT[7]:複比特所有的寄存器
72              7'd1 : i2c_data <= {8'h3d, 8'h03}; //COM12 模擬過程直流補償
73              7'd2 : i2c_data <= {8'h15, 8'h00}; //COM10 href/vsync/pclk/data信號控制
74              7'd3 : i2c_data <= {8'h17, 8'h23}; //HSTART 水平起始比特置
75              7'd4 : i2c_data <= {8'h18, 8'ha0}; //HSIZE 水平尺寸
76              7'd5 : i2c_data <= {8'h19, 8'h07}; //VSTRT 垂直起始比特置
77              7'd6 : i2c_data <= {8'h1a, 8'hf0}; //VSIZE 垂直尺寸 
78              7'd7 : i2c_data <= {8'h32, 8'h00}; //HREF 圖像開始和尺寸控制,控制低比特
79              7'd8 : i2c_data <= {8'h29, 8'ha0}; //HOutSize 水平輸出尺寸
80              7'd9 : i2c_data <= {8'h2a, 8'h00}; //EXHCH 虛擬像素MSB
81              7'd10 : i2c_data <= {8'h2b, 8'h00}; //EXHCL 虛擬像素LSB
82              7'd11 : i2c_data <= {8'h2c, 8'hf0}; //VOutSize 垂直輸出尺寸
83              7'd12 : i2c_data <= {8'h0d, 8'h41}; //COM4 PLL倍頻設置(multiplier)
84                                                  //Bit[7:6]: 0:1x 1:4x 2:6x 3:8x
85              7'd13 : i2c_data <= {8'h11, 8'h00}; //CLKRC 內部時鐘配置 
86                                        //Freq= input_clk * multiplier/[(CLKRC[5:0]+1)*2]
87              7'd14 : i2c_data <= {8'h12, 8'h06}; //COM7 輸出VGA RGB565格式 
88              7'd15 : i2c_data <= {8'h0c, 8'h10}; //COM3 Bit[0]: 0:圖像數據 1:彩條測試
89              //DSP 控制
90              7'd16 : i2c_data <= {8'h42, 8'h7f}; //TGT_B 黑電平校准藍色通道目標值
91              7'd17 : i2c_data <= {8'h4d, 8'h09}; //FixGain 模擬增益放大器
92              7'd18 : i2c_data <= {8'h63, 8'hf0}; //AWB_Ctrl0 自動白平衡控制字節0
93              7'd19 : i2c_data <= {8'h64, 8'hff}; //DSP_Ctrl1 DSP控制字節1
94              7'd20 : i2c_data <= {8'h65, 8'h00}; //DSP_Ctrl2 DSP控制字節2
95              7'd21 : i2c_data <= {8'h66, 8'h00}; //DSP_Ctrl3 DSP控制字節3
96              7'd22 : i2c_data <= {8'h67, 8'h00}; //DSP_Ctrl4 DSP控制字節4 
97              //AGC AEC AWB 
98              //COM8 Bit[2]:自動增益使能 Bit[1]:自動白平衡使能 Bit[0]:自動曝光功能
99              7'd23 : i2c_data <= {8'h13, 8'hff}; //COM8 
100             7'd24 : i2c_data <= {8'h0f, 8'hc5}; //COM6
101             7'd25 : i2c_data <= {8'h14, 8'h11};  
102             7'd26 : i2c_data <= {8'h22, 8'h98}; 
103             7'd27 : i2c_data <= {8'h23, 8'h03};  
104             7'd28 : i2c_data <= {8'h24, 8'h40}; 
105             7'd29 : i2c_data <= {8'h25, 8'h30};  
106             7'd30: i2c_data <= {8'h26, 8'ha1};      
107             7'd31: i2c_data <= {8'h6b, 8'haa}; 
108             7'd32: i2c_data <= {8'h13, 8'hff};  
109             //matrix sharpness brightness contrast UV
110             7'd33 : i2c_data <= {8'h90, 8'h0a}; //EDGE1 邊緣增强控制1
111             //DNSOff 降噪閾值下限,僅在自動模式下有效
112             7'd34 : i2c_data <= {8'h91, 8'h01}; //DNSOff 
113             7'd35 : i2c_data <= {8'h92, 8'h01}; //EDGE2 銳度(邊緣增强)强度上限
114             7'd36 : i2c_data <= {8'h93, 8'h01}; //EDGE3 銳度(邊緣增强)强度下限
115             7'd37 : i2c_data <= {8'h94, 8'h5f}; //MTX1 矩陣系數1
116             7'd38 : i2c_data <= {8'h95, 8'h53}; //MTX1 矩陣系數2
117             7'd39 : i2c_data <= {8'h96, 8'h11}; //MTX1 矩陣系數3
118             7'd40 : i2c_data <= {8'h97, 8'h1a}; //MTX1 矩陣系數4
119             7'd41 : i2c_data <= {8'h98, 8'h3d}; //MTX1 矩陣系數5
120             7'd42 : i2c_data <= {8'h99, 8'h5a}; //MTX1 矩陣系數6
121             7'd43 : i2c_data <= {8'h9a, 8'h1e}; //MTX_Ctrl 矩陣控制
122             7'd44 : i2c_data <= {8'h9b, 8'h3f}; //BRIGHT 亮度
123             7'd45 : i2c_data <= {8'h9c, 8'h25}; //CNST 對比度 
124             7'd46 : i2c_data <= {8'h9e, 8'h81}; 
125             7'd47 : i2c_data <= {8'ha6, 8'h06}; //SDE 特殊數字效果控制
126             7'd48 : i2c_data <= {8'ha7, 8'h65}; //USAT "U"飽和增益
127             7'd49 : i2c_data <= {8'ha8, 8'h65}; //VSAT "V"飽和增益 
128             7'd50 : i2c_data <= {8'ha9, 8'h80}; //VSAT "V"飽和增益 
129             7'd51 : i2c_data <= {8'haa, 8'h80}; //VSAT "V"飽和增益
130             //伽馬控制 
131             7'd52 : i2c_data <= {8'h7e, 8'h0c}; 
132             7'd53 : i2c_data <= {8'h7f, 8'h16}; 
133             7'd54 : i2c_data <= {8'h80, 8'h2a}; 
134             7'd55 : i2c_data <= {8'h81, 8'h4e}; 
135             7'd56 : i2c_data <= {8'h82, 8'h61}; 
136             7'd57 : i2c_data <= {8'h83, 8'h6f}; 
137             7'd58 : i2c_data <= {8'h84, 8'h7b}; 
138             7'd59 : i2c_data <= {8'h85, 8'h86};   
139             7'd60 : i2c_data <= {8'h86, 8'h8e}; 
140             7'd61 : i2c_data <= {8'h87, 8'h97}; 
141             7'd62 : i2c_data <= {8'h88, 8'ha4}; 
142             7'd63 : i2c_data <= {8'h89, 8'haf}; 
143             7'd64 : i2c_data <= {8'h8a, 8'hc5}; 
144             7'd65 : i2c_data <= {8'h8b, 8'hd7}; 
145             7'd66 : i2c_data <= {8'h8c, 8'he8}; 
146             7'd67 : i2c_data <= {8'h8d, 8'h20}; 
147             
148             7'd68 : i2c_data <= {8'h0e, 8'h65}; //COM5
149             7'd69 : i2c_data <= {8'h09, 8'h00}; //COM2 Bit[1:0] 輸出電流驅動能力
150             //只讀存儲器,防止在case中沒有列舉的情况,之前的寄存器被重複改寫
151             default:i2c_data <= {
    8'h1C, 8'h7F}; //MIDH 制造商ID 高8比特
152         endcase
153     end
154 end
155 
156 endmodule

在代碼的第12行定義了總共需要配置的寄存器的個數,如果增加或者删减了寄存器的配置,需要修改此參數。
圖像傳感器剛開始上電時電壓有可能不够穩定,所以程序中的23行至32行定義了一個延時計數器(start_init_cnt)等待傳感器工作在穩定的狀態。當計數器計數到預設值之後,開始第一次配置傳感器即軟件複比特,目的是讓所有的寄存器複比特到默認的狀態。從前面介紹的OV7725的特性可知,軟件複比特需要等待1ms的時間才能配置其它的寄存器,因此發送完軟件複比特命令後,延時計數器清零,並重新開始計數。當計數器計數到預設值之後,緊接著配置剩下的寄存器。只有軟件複比特命令需要1ms的等待時間,其它寄存器不需要等待時間,直接按照程序中定義的順序發送即可。
在程序的83至86行,說明了關於攝像頭輸出時鐘的寄存器配置,攝像頭的地址0x0d配置成0x41,錶示PLL倍頻設置設為了4倍頻,攝像頭的地址0x11配置成0x00,而攝像頭的輸入時鐘為12M,所以根據第86行的公式可得到攝像頭的輸出時鐘為24M。
CMOS圖像數據采集模塊的代碼如下所示:

1   module cmos_capture_data(
2       input                 rst_n            ,  //複比特信號 
3       //攝像頭接口 
4       input                 cam_pclk         ,  //cmos 數據像素時鐘
5       input                 cam_vsync        ,  //cmos 場同步信號
6       input                 cam_href         ,  //cmos 行同步信號
7       input  [7:0]          cam_data         ,                      
8       //用戶接口 
9       output                cmos_frame_vsync ,  //幀有效信號 
10      output                cmos_frame_href  ,  //行有效信號
11      output                cmos_frame_valid ,  //數據有效使能信號
12      output       [15:0]   cmos_frame_data     //有效數據 
13      );
14  
15  //寄存器全部配置完成後,先等待10幀數據
16  //待寄存器配置生效後再開始采集圖像
17  parameter  WAIT_FRAME = 4'd10    ;            //寄存器數據穩定等待的幀個數 
18                                   
19  //reg define 
20  reg             cam_vsync_d0     ;
21  reg             cam_vsync_d1     ;
22  reg             cam_href_d0      ;
23  reg             cam_href_d1      ;
24  reg    [3:0]    cmos_ps_cnt      ;            //等待幀數穩定計數器
25  reg    [7:0]    cam_data_d0      ;            
26  reg    [15:0]   cmos_data_t      ;            //用於8比特轉16比特的臨時寄存器
27  reg             byte_flag        ;            //16比特RGB數據轉換完成的標志信號
28  reg             byte_flag_d0     ;
29  reg             frame_val_flag   ;            //幀有效的標志 
30  
31  wire            pos_vsync        ;            //采輸入場同步信號的上昇沿
32  
33  //*****************************************************
34  //** main code
35  //*****************************************************
36  
37  //采輸入場同步信號的上昇沿
38  assign pos_vsync = (~cam_vsync_d1) & cam_vsync_d0; 
39  
40  //輸出幀有效信號
41  assign  cmos_frame_vsync = frame_val_flag  ?  cam_vsync_d1  :  1'b0; 
42  
43  //輸出行有效信號
44  assign  cmos_frame_href = frame_val_flag  ?  cam_href_d1   :  1'b0; 
45  
46  //輸出數據使能有效信號
47  assign  cmos_frame_valid = frame_val_flag  ?  byte_flag_d0  :  1'b0; 
48  
49  //輸出數據
50  assign  cmos_frame_data = frame_val_flag  ?  cmos_data_t   :  1'b0; 
51         
52  always @(posedge cam_pclk or negedge rst_n) begin
53      if(!rst_n) begin
54          cam_vsync_d0 <= 1'b0;
55          cam_vsync_d1 <= 1'b0;
56          cam_href_d0 <= 1'b0;
57          cam_href_d1 <= 1'b0;
58      end
59      else begin
60          cam_vsync_d0 <= cam_vsync;
61          cam_vsync_d1 <= cam_vsync_d0;
62          cam_href_d0 <= cam_href;
63          cam_href_d1 <= cam_href_d0;
64      end
65  end
66  
67  //對幀數進行計數
68  always @(posedge cam_pclk or negedge rst_n) begin
69      if(!rst_n)
70          cmos_ps_cnt <= 4'd0;
71      else if(pos_vsync && (cmos_ps_cnt < WAIT_FRAME))
72          cmos_ps_cnt <= cmos_ps_cnt + 4'd1;
73  end
74  
75  //幀有效標志
76  always @(posedge cam_pclk or negedge rst_n) begin
77      if(!rst_n)
78          frame_val_flag <= 1'b0;
79      else if((cmos_ps_cnt == WAIT_FRAME) && pos_vsync)
80          frame_val_flag <= 1'b1;
81      else;    
82  end            
83  
84  //8比特數據轉16比特RGB565數據 
85  always @(posedge cam_pclk or negedge rst_n) begin
86      if(!rst_n) begin
87          cmos_data_t <= 16'd0;
88          cam_data_d0 <= 8'd0;
89          byte_flag <= 1'b0;
90      end
91      else if(cam_href) begin
92          byte_flag <= ~byte_flag;
93          cam_data_d0 <= cam_data;
94          if(byte_flag)
95              cmos_data_t <= {
    cam_data_d0,cam_data};
96          else;   
97      end
98      else begin
99          byte_flag <= 1'b0;
100         cam_data_d0 <= 8'b0;
101     end    
102 end        
103 
104 //產生輸出數據有效信號(cmos_frame_valid)
105 always @(posedge cam_pclk or negedge rst_n) begin
106     if(!rst_n)
107         byte_flag_d0 <= 1'b0;
108     else
109         byte_flag_d0 <= byte_flag;  
110 end 
111        
112 endmodule

CMOS圖像采集模塊第17行定義了參數WAIT_FRAME(寄存器數據穩定等待的幀個數),這個參數是設置攝像頭初始化完成後,等待攝像頭輸出穩定數據的幀數,此處等待10幀,即通過采集場同步信號的上昇沿來統計幀數,計數器計數超過10次之後產生數據有效的標志,開始采集圖像。在程序的第84行開始的always塊實現了8比特數據轉16比特數據的功能。需要注意的是攝像頭的圖像數據是在像素時鐘(cam_pclk)下輸出的,因此攝像頭的圖像數據必須使用像素鐘來采集,否則會造成數據采集錯誤。
開始傳輸控制模塊的代碼如下:

1  module start_transfer_ctrl(
2      input                 clk                ,   //時鐘信號
3      input                 rst_n              ,   //複比特信號,低電平有效
4      input                 udp_rec_pkt_done   ,   //GMII接收時鐘 
5      input                 udp_rec_en         ,   //UDP單包數據接收完成信號
6      input        [31:0]   udp_rec_data       ,   //UDP接收的數據使能信號 
7      input        [15:0]   udp_rec_byte_num   ,   //UDP接收的數據
8                                                   //UDP接收到的字節數
9      output  reg           transfer_flag          //圖像開始傳輸標志,1:開始傳輸 0:停止傳輸
10     );    
11     
12 //parameter define
13 parameter  START = "1";  //開始命令
14 parameter  STOP  = "0";  //停止命令
15 
16 //*****************************************************
17 //** main code
18 //*****************************************************
19 
20 //解析接收到的數據
21 always @(posedge clk or negedge rst_n) begin
22     if(!rst_n) 
23         transfer_flag <= 1'b0;
24     else if(udp_rec_pkt_done && udp_rec_byte_num == 1'b1) begin
25         if(udp_rec_data[31:24] == START)         //開始傳輸
26             transfer_flag <= 1'b1;
27         else if(udp_rec_data[31:24] == STOP)     //停止傳輸
28             transfer_flag <= 1'b0;
29     end
30 end 
31 
32 endmodule

開始傳輸控制模塊的代碼比較簡單,當接收到UDP的數據包,並且這個數據包的有效字節個數為1時,開始對接收到的數據進行判斷。如果這個數據等於ASCII碼的“1”時,此時將transfer_flag賦值為1,錶示以太網開始發送圖像數據;如果這個數據等於ASCII碼的“0”時,此時將transfer_flag賦值為0,錶示以太網停止發送圖像數據。
圖像封裝模塊的代碼如下:

1   module img_data_pkt(
2       input                 rst_n          ,   //複比特信號,低電平有效
3       //圖像相關信號
4       input                 cam_pclk       ,   //像素時鐘
5       input                 img_vsync      ,   //幀同步信號
6       input                 img_data_en    ,   //數據有效使能信號
7       input        [15:0]   img_data       ,   //有效數據 
8       
9       input                 transfer_flag  ,   //圖像開始傳輸標志,1:開始傳輸 0:停止傳輸
10      //以太網相關信號 
11      input                 eth_tx_clk     ,   //以太網發送時鐘
12      input                 udp_tx_req     ,   //udp發送數據請求信號
13      input                 udp_tx_done    ,   //udp發送數據完成信號 
14      output  reg           udp_tx_start_en,   //udp開始發送信號
15      output       [31:0]   udp_tx_data    ,   //udp發送的數據
16      output  reg  [15:0]   udp_tx_byte_num    //udp單包發送的有效字節數
17      );    
18      
19  //parameter define
20  parameter  CMOS_H_PIXEL = 16'd640;  //圖像水平方向分辨率
21  parameter  CMOS_V_PIXEL = 16'd480;  //圖像垂直方向分辨率
22  //圖像幀頭,用於標志一幀數據的開始
23  parameter  IMG_FRAME_HEAD = {
    32'hf0_5a_a5_0f};

程序中第20行至23行定義了三個參數,分別是CMOS_H_PIXEL(圖像水平方向分辨率)、CMOS_V_PIXEL(圖像垂直方向分辨率)和IMG_FRAME_HEAD(圖像幀頭)。我們在用網口傳輸圖像數據時,一次發送一行圖像數據。在發送一幀圖像的第一行數據時,在一行數據的開頭添加圖像的幀頭和圖像的行場分辨率,共8個字節,圖像的幀頭是32’hf0_5a_a5_0f,共占用4個字節;而圖像的行場分辨率占用4個字節,本次實驗傳輸的圖像分辨率為640*480。
這裏我們來詳細說明下在一幀的第一行添加幀頭的原因。在使用UDP傳輸數據時,有時會出現數據丟包的現象。這裏說的丟包現象並不是開發板沒有把數據發送出去,而是電腦接收端系統繁忙或者其它的原因沒有接收到數據。為了降低丟包對視頻顯示帶來的影響,我們為每幀圖像添加一個幀頭,上比特機解析到幀頭之後,接下來將接收到的數據重新放到圖像顯示區域的起始比特置,保證了在視頻傳輸過程中,即使出現丟包的現象,視頻也能恢複到正常顯示的畫面。圖像幀頭的值要盡量選擇圖像數據不容易出現的值,否則很容易把圖像數據當成幀頭,程序中把圖像幀頭設置為{32’hf0_5a_a5_0f},圖像中出現的像素數據和幀頭相同的概率極低。除此之外,上比特機軟件按照幀頭為{32’hf0_5a_a5_0f}來解析數據,所以幀頭的值不可以隨意修改。

25  reg             img_vsync_d0    ;  //幀有效信號打拍
26  reg             img_vsync_d1    ;  //幀有效信號打拍
27  reg             neg_vsync_d0    ;  //幀有效信號下降沿打拍
28                                  
29  reg             wr_sw           ;  //用於比特拼接的標志
30  reg    [15:0]   img_data_d0     ;  //有效圖像數據打拍
31  reg             wr_fifo_en      ;  //寫fifo使能
32  reg    [31:0]   wr_fifo_data    ;  //寫fifo數據
33  
34  reg             img_vsync_txc_d0;  //以太網發送時鐘域下,幀有效信號打拍
35  reg             img_vsync_txc_d1;  //以太網發送時鐘域下,幀有效信號打拍
36  reg             tx_busy_flag    ;  //發送忙信號標志
37                                  
38  //wire define 
39  wire            pos_vsync       ;  //幀有效信號上昇沿
40  wire            neg_vsync       ;  //幀有效信號下降沿
41  wire            neg_vsynt_txc   ;  //以太網發送時鐘域下,幀有效信號下降沿
42  wire   [9:0]    fifo_rdusedw    ;  //當前FIFO緩存的個數
43  
44  //*****************************************************
45  //** main code
46  //*****************************************************
47  
48  //信號采沿
49  assign neg_vsync = img_vsync_d1 & (~img_vsync_d0);
50  assign pos_vsync = ~img_vsync_d1 & img_vsync_d0;
51  assign neg_vsynt_txc = ~img_vsync_txc_d1 & img_vsync_txc_d0;
52  
53  //對img_vsync信號延時兩個時鐘周期,用於采沿
54  always @(posedge cam_pclk or negedge rst_n) begin
55      if(!rst_n) begin
56          img_vsync_d0 <= 1'b0;
57          img_vsync_d1 <= 1'b0;
58      end
59      else begin
60          img_vsync_d0 <= img_vsync;
61          img_vsync_d1 <= img_vsync_d0;
62      end
63  end
64  
65  //寄存neg_vsync信號
66  always @(posedge cam_pclk or negedge rst_n) begin
67      if(!rst_n) 
68          neg_vsync_d0 <= 1'b0;
69      else 
70          neg_vsync_d0 <= neg_vsync;
71  end    

程序中第54行至63行在cam_pclk時鐘下對img_vsync信號延時兩拍,用於采該信號的上昇沿和下降沿。程序中第66行至71行對下降沿延時一拍,我們後面的處理中會用到這些信號。

73  //對wr_sw和img_data_d0信號賦值,用於比特拼接
74  always @(posedge cam_pclk or negedge rst_n) begin
75      if(!rst_n) begin
76          wr_sw <= 1'b0;
77          img_data_d0 <= 1'b0;
78      end
79       else if(neg_vsync)
80          wr_sw <= 1'b0;
81      else if(img_data_en) begin
82          wr_sw <= ~wr_sw;
83          img_data_d0 <= img_data;
84      end    
85  end 

程序中第74行至85行代碼對wr_sw和img_data_d0信號進行賦值,用於比特拼接。這是由於輸入的圖像數據為16比特,而以太網頂層模塊的用戶數據端口是32比特,因此這裏需要對輸入的數據進行寄存,用於將16比特拼接成32比特。

87  //將幀頭和圖像數據寫入FIFO
88  always @(posedge cam_pclk or negedge rst_n) begin
89      if(!rst_n) begin
90          wr_fifo_en <= 1'b0;
91          wr_fifo_data <= 1'b0;
92      end
93      else begin
94          if(neg_vsync) begin
95              wr_fifo_en <= 1'b1;
96              wr_fifo_data <= IMG_FRAME_HEAD;               //幀頭
97          end
98          else if(neg_vsync_d0) begin
99              wr_fifo_en <= 1'b1;
100             wr_fifo_data <= {
    CMOS_H_PIXEL,CMOS_V_PIXEL};  //水平和垂直方向分辨率
101         end
102         else if(img_data_en && wr_sw) begin
103             wr_fifo_en <= 1'b1;
104             wr_fifo_data <= {
    img_data_d0,img_data};       //圖像數據比特拼接,16比特轉32比特
105           end
106         else begin
107             wr_fifo_en <= 1'b0;
108             wr_fifo_data <= 1'b0;        
109         end
110     end
111 end

本次模塊中例化了异步FIFO,我們需要將一幀的幀頭、圖像行場分辨率和拼接後的數據都寫入FIFO中。需要注意的是,只有一幀圖像的第一行圖像才需要添加幀頭和圖像行場分辨率,而其餘行的圖像是不需要添加的,因此我們需要根據img_vsync(幀同步信號)來判斷什麼時候輸入的數據是第一行的圖像。
img_vsync作為幀同步信號,該信號的下降沿之後輸入的數據,就是一幀的第一行數據。因此我們在img_vsync的下降沿將幀頭IMG_FRAME_HEAD寫入FIFO,在下一個時鐘周期將行場分辨率{CMOS_H_PIXEL,CMOS_V_PIXEL}寫入FIFO中。當img_data_en信號和wr_sw同時為高時,將拼接後的32比特數據寫入FIFO中即可。

113 //以太網發送時鐘域下,對img_vsync信號延時兩個時鐘周期,用於采沿
114 always @(posedge eth_tx_clk or negedge rst_n) begin
115     if(!rst_n) begin
116         img_vsync_txc_d0 <= 1'b0;
117         img_vsync_txc_d1 <= 1'b0;
118     end
119     else begin
120         img_vsync_txc_d0 <= img_vsync;
121         img_vsync_txc_d1 <= img_vsync_txc_d0;
122     end
123 end
由於cam_pclk和eth_tx_clk為异步時鐘,因此不能直接在eth_tx_clk時鐘域下直接采集neg_vsync信號(該信號在cam_pclk時鐘域下產生),因此這裏在eth_tx_clk時鐘域下重新對img_vsync信號進行打拍和采沿。
125 //控制以太網發送的字節數
126 always @(posedge eth_tx_clk or negedge rst_n) begin
127     if(!rst_n)
128         udp_tx_byte_num <= 1'b0;
129     else if(neg_vsynt_txc)
130         udp_tx_byte_num <= {
    CMOS_H_PIXEL,1'b0} + 16'd8;
131     else if(udp_tx_done)    
132         udp_tx_byte_num <= {
    CMOS_H_PIXEL,1'b0};
133 end
我們只在一幀的第一行添加了幀頭和行場分辨率,因此只有在發送第一行圖像數據時,發送的UDP字節數為1288640*2+8),而其餘行單包發送的字節數為1280135 //控制以太網發送開始信號
136 always @(posedge eth_tx_clk or negedge rst_n) begin
137     if(!rst_n) begin
138         udp_tx_start_en <= 1'b0;
139         tx_busy_flag <= 1'b0;
140     end
141     //上比特機未發送“開始”命令時,以太網不發送圖像數據
142     else if(transfer_flag == 1'b0) begin
143         udp_tx_start_en <= 1'b0;
144         tx_busy_flag <= 1'b0;        
145     end
146     else begin
147         udp_tx_start_en <= 1'b0;
148         //當FIFO中的個數滿足需要發送的字節數時
149         if(tx_busy_flag == 1'b0 && fifo_rdusedw >= udp_tx_byte_num[15:2]) begin
150             udp_tx_start_en <= 1'b1;                     //開始控制發送一包數據
151             tx_busy_flag <= 1'b1;
152         end
153         else if(udp_tx_done || neg_vsynt_txc) 
154             tx_busy_flag <= 1'b0;
155     end
156 end

當上比特機發送“開始”命令後,transfer_flag信號為高電平,此時以太網開始傳輸圖像數據。當以太網發送空閑時,且FIFO中的字節數達到待發送的字節數時,此時拉高udp_tx_start_en信號,開始控制以太網頂層模塊讀取FIFO,並將FIFO中的數據發送給上比特機。

158 //异步FIFO
159 async_fifo_1024x32b async_fifo_1024x32b_inst (
160   .rst(pos_vsync | (~transfer_flag)), // FIFO複比特控制
161   .wr_clk(cam_pclk),                  // FIFO寫時鐘
162   .rd_clk(eth_tx_clk),                // FIFO讀時鐘
163   .din(wr_fifo_data),                 // FIFO寫數據
164   .wr_en(wr_fifo_en),                 // FIFO寫使能
165   .rd_en(udp_tx_req),                 // FIFO讀使能
166   .dout(udp_tx_data),                 // FIFO讀數據
167   .full(),                       
168   .empty(),                 
169   .rd_data_count(fifo_rdusedw),       // FIFO讀側數據個數
170   .wr_rst_busy(),      
171   .rd_rst_busy()     
172 );   
173 
174 endmodule

程序中第158行至172行代碼例化了异步FIFO,存儲深度為1024,數據比特寬為32比特。值得一提的是,我們幀同步的上昇沿和transfer_flag信號作為FIFO的複比特信號,避免上比特機在傳輸圖像中途發送停止命令,FIFO沒有被清空的情况。
下圖為圖像數據封裝模塊采集過程中ILA抓取的波形圖,在img_vsync的下降沿,依次將圖像幀頭和行場分辨率寫入fifo(如下圖的wr_fifo_en和wr_fifo_data),幀頭為32’hf0_5a_a5_0f,行分辨率為16’h0280(640),場分辨率為32’h01e0(32’h480)。
在這裏插入圖片描述

圖 7.5.13.3 ILA波形圖
35.5 下載驗證
將下載器一端連接電腦,另一端與開發板上的JTAG下載口連接;將網線一端連接開發板的網口,另一端連接電腦的網口;將OV7725攝像頭連接開發板上的攝像頭接口,注意鏡頭方向朝外,如下圖所示。
在這裏插入圖片描述

圖 7.5.13.1 開發板硬件連接示意圖
接下來我們打開電源開關,並下載程序。程序下載完成後,此時開發板上還看不到現象,我們接下來打開正點原子UDP傳輸視頻顯示的上比特機,比特於資料盤(A盤)/6_軟件資料/1_軟件/video_transfer文件夾下,如下圖所示:
在這裏插入圖片描述

圖 7.5.13.2 打開上比特機軟件
雙擊video_transfer.exe即可打開軟件,如果軟件無法打開,先雙擊安裝上圖中的vc_redist.x64.exe,安裝完成後再打開video_transfer.exe。
打開後的界面如下圖所示。
在這裏插入圖片描述

圖 7.5.13.3 上比特機設置界面
按照上圖的界面進行設置,這些設置和程序中定義的參數和代碼是對應的。設置完成後,點擊“打開”按鈕,此時上比特機會通過網口向開發板發送ASCII碼“1”,開發板收到開始命令後,會開始傳輸圖像數據,如下圖所示,如果圖像畫面顯示有模糊不聚焦的現象可以通過旋轉攝像頭鏡頭進行聚焦調試,正常顯示如下圖所示:
在這裏插入圖片描述

圖 7.5.13.4 上比特機實時顯示畫面
由上圖可知,開發板傳輸的圖像分辨率是640*480,幀率約為30fps,如果需要停止顯示畫面的話,只需要點擊上方的“關閉”按鈕即可。需要注意的是,上比特機顯示的畫面受電腦性能的影響,如果電腦性能較差的話,上比特機可能會出現卡頓和崩潰的現象。

版權聲明
本文為[正點原子]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2022/01/202201270838344767.html

隨機推薦