議原理到Verilog實(shí)現(xiàn))
OV7670FPGA這個(gè)組合我估計(jì)是不少高校實(shí)驗(yàn)室和智能車隊(duì)伍的標(biāo)配入門項(xiàng)目。一顆三十萬像素的CMOS傳感器一個(gè)主控邏輯芯片兩者之間除了像素?cái)?shù)據(jù)流還有一條容易被忽略的控制通道——SCCB。很多人第一次調(diào)OV7670對著攝像頭模組一臉懵明明接了線上電就是不輸出圖像查了半天才發(fā)現(xiàn)是寄存器根本沒配進(jìn)去。這篇就把這條控制通道從協(xié)議原理到FPGA實(shí)現(xiàn)掰開揉碎講一遍。這篇文章適合剛接觸FPGA圖像采集的初學(xué)者也適合智能車隊(duì)的同學(xué)——只要你的板子上是一顆純FPGA做圖像采集SCCB這條配置鏈路遲早要過一遍。就算你用的是Zynq或者帶硬核的FPGA只要想自己掌控?cái)z像頭初始化流程這篇的控制器設(shè)計(jì)和調(diào)試思路同樣能用上。我會先把協(xié)議本身講清楚再給出一套完整的Verilog實(shí)現(xiàn)方案最后聊聊我上板調(diào)試時(shí)踩過的坑。保證你能拿著代碼跑通也知道每一步為什么這么做。1. SCCB協(xié)議到底是什么——先把它和I2C分清楚1.1 兩線制總線和基本時(shí)序SCCB的全稱是Serial Camera Control Bus串行攝像頭控制總線是OmniVision專門為攝像頭傳感器定義的一套兩線制配置協(xié)議??偩€只有兩根線SIOC串行時(shí)鐘和SIOD串行數(shù)據(jù)接傳感器時(shí)通常也標(biāo)注為SCL和SDA。它和I2C長得確實(shí)很像7位設(shè)備地址、MSB先發(fā)、每8位數(shù)據(jù)后跟一個(gè)第9時(shí)鐘作為應(yīng)答位。OV7670的SCCB設(shè)備地址是固定的寫地址是0x42讀地址是0x43這個(gè)地址寫死在芯片里不需要像EEPROM那樣靠外部引腳配置地址。時(shí)序上幾個(gè)關(guān)鍵點(diǎn)起始條件SIOC為高電平時(shí)SIOD產(chǎn)生一個(gè)下降沿停止條件SIOC為高電平時(shí)SIOD產(chǎn)生一個(gè)上升沿?cái)?shù)據(jù)有效性SIOC高電平期間SIOD必須保持穩(wěn)定數(shù)據(jù)變化只能發(fā)生在SIOC低電平期間這里說個(gè)小細(xì)節(jié)起始和停止都要求SIOC保持高電平很多人寫狀態(tài)機(jī)時(shí)容易忽略這個(gè)前提直接在SIOC低電平時(shí)去拉SIOD結(jié)果是傳感器根本不識別波形看起來又有“沿”又沒“沿”特別迷惑。如果你用邏輯分析儀抓過總線應(yīng)該見過那種看著像起始又不像起始的波形多半就是這個(gè)問題。數(shù)據(jù)有效性這個(gè)規(guī)則是總線協(xié)議的通識I2C、SMBus、SCCB全都一樣。時(shí)鐘高電平期間數(shù)據(jù)必須穩(wěn)定是因?yàn)閺臋C(jī)是在時(shí)鐘高電平期間采樣數(shù)據(jù)線的如果你在此時(shí)刻改變數(shù)據(jù)從機(jī)采到的就是不確定值。反過來數(shù)據(jù)只能在時(shí)鐘低電平期間切換為下一次采樣做準(zhǔn)備。1.2 讀操作的特殊性別跟I2C混為一談SCCB和I2C最大的區(qū)別不在單字節(jié)時(shí)序而在讀操作流程。標(biāo)準(zhǔn)I2C讀EEPROM的流程是起始→發(fā)送設(shè)備地址寫位→寄存器地址→重復(fù)起始→發(fā)送設(shè)備地址讀位→讀取數(shù)據(jù)→主機(jī)回NACK→停止。SCCB的規(guī)范文檔里把讀操作拆成了兩個(gè)階段先做一次“兩相寫傳輸”設(shè)備地址加寄存器地址再發(fā)起一次讀傳輸。實(shí)際物理行為上兩者等價(jià)所以工程上直接用I2C風(fēng)格的重復(fù)起始去讀OV7670也是能讀通的。不過位級層面有一個(gè)容易忽略的細(xì)節(jié)SCCB規(guī)范里寫操作第9個(gè)時(shí)鐘的應(yīng)答位被稱作“Dont Care”位從機(jī)回不回、回什么都無所謂主機(jī)不應(yīng)該因?yàn)樗K止事務(wù)。但實(shí)際在FPGA實(shí)現(xiàn)時(shí)我強(qiáng)烈建議你還是去檢測這個(gè)應(yīng)答位理由很簡單如果一個(gè)寄存器地址寫錯(cuò)了或者傳感器沒起來從機(jī)不會回ACK你就能立刻發(fā)現(xiàn)通信鏈路有問題而不是等到后面圖像一片黑才回頭排查。把ACK檢測當(dāng)成一種廉價(jià)的調(diào)試手段能省很多時(shí)間。讀操作最后一個(gè)字節(jié)主機(jī)必須主動回一個(gè)NACK否則從機(jī)可能不釋放SIOD。這一點(diǎn)在FPGA實(shí)現(xiàn)時(shí)最容易漏——漏了之后第一次讀正常第二次以后就卡死或者讀到全1。原因是從機(jī)以為還有下一個(gè)字節(jié)要發(fā)一直占著數(shù)據(jù)線不放主機(jī)又以為從機(jī)還在響應(yīng)雙向就僵住了。1.3 工程上怎么看SCCB和I2C的兼容性我見過很多人在論壇上爭論“OV7670到底該用SCCB還是I2C配置”其實(shí)在做FPGA方案時(shí)不用太糾結(jié)這個(gè)把時(shí)序做規(guī)范、讀寫順序做對設(shè)備就能正常工作。給你一張對比表更直觀對比項(xiàng)I2CSCCB總線定義標(biāo)準(zhǔn)協(xié)議支持多主機(jī)/多從機(jī)面向攝像頭控制單主機(jī)為主起始/停止條件時(shí)鐘高電平時(shí)數(shù)據(jù)線跳變與I2C一致讀流程支持重復(fù)起始規(guī)范文檔分兩段實(shí)際等價(jià)應(yīng)答位主機(jī)必須檢測ACK第9位為“不關(guān)心”但檢測更穩(wěn)電氣兼容性—OV系列傳感器基本兼容I2C讀寫結(jié)論很直接只要照著I2C的主機(jī)時(shí)序去寫一個(gè)控制器把設(shè)備地址、寄存器地址、讀寫位都安排對OV7670就能正常工作。我自己測試過用標(biāo)準(zhǔn)的I2C主機(jī)邏輯去配置OV7670完全沒有問題。SCCB文檔里那些特殊的叫法更多是OmniVision想體現(xiàn)自己的協(xié)議特色底層電氣行為和時(shí)序規(guī)則并沒有本質(zhì)變化。2. 為什么OV7670需要SCCB——寄存器配置是圖像質(zhì)量的命門2.1 OV7670能輸出什么配置了什么OV7670是一顆30萬像素級別的CMOS傳感器最大輸出VGA640×480也可以工作在QVGA320×240、QQVGA160×120等模式。它能輸出RGB565、RGB555、YUV422、YCbCr422這些格式具體走哪種格式完全由內(nèi)部寄存器決定。沒錯(cuò)這顆傳感器本身是不帶視頻編碼器的它只是把感光區(qū)域的數(shù)據(jù)按配置好的格式和時(shí)序送出來怎么解釋這些數(shù)據(jù)是FPGA或者后續(xù)處理器的事。寄存器總共有上百個(gè)分布在0x00到0x7F地址空間。上電之后OV7670內(nèi)部的默認(rèn)值是一個(gè)能工作的狀態(tài)但這個(gè)狀態(tài)并不一定適合你的項(xiàng)目分辨率可能不是你要的、顏色空間可能不對、幀率可能偏低、畫面可能偏暗偏色。所以上板第一步就是通過SCCB把關(guān)鍵寄存器配置成項(xiàng)目需要的值。2.2 關(guān)鍵寄存器組和配置順序先說幾個(gè)最常動的寄存器這些都是我實(shí)際項(xiàng)目中驗(yàn)證過的寄存器功能常用配置0x12 (COM7)分辨率/格式總開關(guān)bit7軟復(fù)位bit3選QVGAbit2選RGB0x11 (CLKRC)時(shí)鐘分頻常用0x00或0x01可配合PLL倍頻0x40 (COM15)輸出格式高2位RGB565時(shí)置0xD00x3D (COM13)格式細(xì)節(jié)配合RGB565配置注意bit30x0C/0x0D/0x0E曝光時(shí)間默認(rèn)即可必要時(shí)手動調(diào)0x10 (AECHH)曝光高位與0x0D組合成16位曝光0x24/0x25AGC增益低/高位光照差時(shí)調(diào)大0x55/0x56/0x57AWB白平衡自動收斂異常時(shí)手動寫配置順序有講究見過不少朋友直接把網(wǎng)上抄的一段寄存器表從頭寫到尾結(jié)果花屏。比較穩(wěn)妥的順序是先軟復(fù)位往0x12寫0x80延時(shí)幾個(gè)毫秒再寫輸出格式和分辨率相關(guān)寄存器然后寫顏色、曝光、增益這些最后把0x12重新覆寫成目標(biāo)值。核心邏輯是復(fù)位之后芯片處于一個(gè)已知初始狀態(tài)再逐塊配置避免某些寄存器依賴關(guān)系沒建立就寫入被后續(xù)設(shè)置意外覆蓋。舉個(gè)例子0x40的bit2如果是1表示輸出RGB格式但這個(gè)bit只定義了高兩位的編碼具體的RGB位數(shù)排列還要看0x3D的bit1和bit2。如果你只寫了0x40沒管0x3D那輸出的數(shù)據(jù)排列可能跟你預(yù)期的不一樣顏色自然不對。這類寄存器之間的耦合關(guān)系在OV7670里特別多所以我建議你手邊放一份完整的寄存器手冊網(wǎng)上搜“OV7670 register set”就能找到別只看代碼片段。2.3 為什么堅(jiān)持用FPGA自己寫SCCB控制器可能有人要問用STM32寫I2C初始化OV7670不是更簡單嗎確實(shí)在很多雙芯片方案里MCU通過I2C配置攝像頭FPGA只管采集圖像數(shù)據(jù)。但如果你做的是純FPGA方案或者板子上只有FPGA一顆主控那就必須自己把SCCB時(shí)序在邏輯里實(shí)現(xiàn)出來。退一步說就算系統(tǒng)里有MCU把SCCB用FPGA實(shí)現(xiàn)也有幾個(gè)實(shí)際好處。上電后攝像頭配置不依賴外部固件的加載節(jié)奏FPGA配置鏈路自己就能完成初始化減少跨芯片協(xié)調(diào)的麻煩。數(shù)據(jù)通路和配置通路都在一張邏輯設(shè)計(jì)里調(diào)試定位更快——圖像出問題先查SCCB寄存器有沒有配進(jìn)去再查數(shù)據(jù)通路不用在兩個(gè)芯片之間來回猜。而且這個(gè)控制器狀態(tài)機(jī)寫好了以后配OV5640、OV2640甚至其他I2C外設(shè)改改地址位寬和寄存器表就能復(fù)用成本其實(shí)很低。3. FPGA端SCCB控制器的完整設(shè)計(jì)3.1 整體架構(gòu)分頻模塊、主狀態(tài)機(jī)、三態(tài)IO一個(gè)標(biāo)準(zhǔn)的SCCB控制器我建議分成兩個(gè)層面來做。層面一是bit級的協(xié)議引擎負(fù)責(zé)SCL/SDA時(shí)序、起始停止條件、逐位收發(fā)層面二是byte級的主狀態(tài)機(jī)負(fù)責(zé)組織設(shè)備地址、寄存器地址和數(shù)據(jù)的完整事務(wù)。兩者分開的好處是協(xié)議引擎可以單獨(dú)仿真驗(yàn)證狀態(tài)機(jī)邏輯也清楚出現(xiàn)問題容易定位是在時(shí)序?qū)舆€是事務(wù)層。模塊接口上我用參數(shù)化設(shè)計(jì)把主頻和目標(biāo)SCL頻率留成參數(shù)這樣從50MHz換到100MHz主頻或者想調(diào)快調(diào)慢總線速度只改參數(shù)就行。module sccb_master #( parameter CLK_FREQ 50_000_000, // 輸入時(shí)鐘頻率 parameter SCL_FREQ 100_000 // 目標(biāo)SIOC頻率 )( input wire clk, input wire rst_n, input wire start, // 觸發(fā)一次傳輸 input wire rw, // 0:寫 1:讀 input wire [7:0] dev_addr, // 設(shè)備地址已左移1位的8位地址 input wire [7:0] reg_addr, // 寄存器地址 input wire [7:0] wdata, // 寫數(shù)據(jù) output reg [7:0] rdata, // 讀數(shù)據(jù) output reg busy, // 總線忙指示 output reg done, // 傳輸完成脈沖 output wire scl, // 時(shí)鐘線 inout wire sda // 數(shù)據(jù)線 );SCL頻率的分頻系數(shù)計(jì)算很簡單半周期計(jì)數(shù)值 CLK_FREQ / (2 × SCL_FREQ)。比如50MHz主頻、100kHz的SCL半周期就是250個(gè)主時(shí)鐘周期一個(gè)完整SCL周期是500個(gè)主時(shí)鐘周期。這個(gè)值用參數(shù)算出來代碼里加個(gè)計(jì)數(shù)器就能產(chǎn)生占空比接近50%的時(shí)鐘。3.2 核心狀態(tài)機(jī)設(shè)計(jì)狀態(tài)機(jī)是整個(gè)控制器的靈魂。我按照讀寫兩種完整事務(wù)設(shè)計(jì)狀態(tài)跳轉(zhuǎn)如下寫流程IDLE → START → SEND_DEV(8bitACK) → SEND_REG(8bitACK) → WR_DATA(8bitACK) → STOP → IDLE讀流程IDLE → START → SEND_DEV(8bitACK) → SEND_REG(8bitACK) → RESTART → SEND_RDEV(8bitACK) → RD_DATA(8bitNACK) → STOP → IDLE每個(gè)狀態(tài)內(nèi)部再細(xì)分發(fā)送8位數(shù)據(jù)的階段用bit_cnt做0到7的子循環(huán)每bit產(chǎn)生一個(gè)完整的SCL周期第8位發(fā)完后再進(jìn)入ACK狀態(tài)這是第9個(gè)時(shí)鐘周期。ACK狀態(tài)里有兩件事要做如果是發(fā)送階段主機(jī)要把SDA釋放為高阻輸入在第9個(gè)時(shí)鐘高電平期間采樣SDA判斷從機(jī)是否應(yīng)答如果是讀數(shù)據(jù)階段主機(jī)要在第9個(gè)時(shí)鐘低電平期間主動把SDA拉低輸出一個(gè)NACK告訴從機(jī)不要再發(fā)了。這里給你一段關(guān)鍵的結(jié)構(gòu)示意代碼先理解框架細(xì)節(jié)可以根據(jù)自己的狀態(tài)編碼填充localparam IDLE 5d0; localparam START 5d1; localparam SEND_DEV 5d2; localparam DEV_ACK 5d3; localparam SEND_REG 5d4; localparam REG_ACK 5d5; localparam WR_DATA 5d6; localparam WR_ACK 5d7; localparam RESTART 5d8; localparam SEND_RDEV 5d9; localparam RDEV_ACK 5d10; localparam RD_DATA 5d11; localparam NACK_BIT 5d12; localparam STOP 5d13; reg [4:0] state, next_state; reg [2:0] bit_cnt; // 當(dāng)前發(fā)送/接收的bit序號 reg [7:0] tx_shift; // 發(fā)送移位寄存器 reg [7:0] rx_shift; // 接收移位寄存器 reg sda_out_en; // SDA輸出使能三態(tài)控制核心 reg sda_out; // SDA輸出數(shù)據(jù) reg scl_sync; // 同步后的SCL內(nèi)部信號 assign scl scl_sync; assign sda sda_out_en ? sda_out : 1bz;移位寄存器的工作方式每個(gè)SCL低電平期間更新一位數(shù)據(jù)到SDA上SCL高電平期間保持穩(wěn)定接收時(shí)在SCL高電平期間采樣SDA存入rx_shift。這個(gè)過程建議寫在always塊里用half_cnt的下降沿來定位SCL低電平區(qū)間避免數(shù)據(jù)變化點(diǎn)落在SCL高電平內(nèi)。3.3 分頻和主時(shí)鐘生成的具體實(shí)現(xiàn)時(shí)鐘分頻這塊我用一個(gè)半周期計(jì)數(shù)器來生成SCL。計(jì)數(shù)器從0加到CLK_DIV-1然后翻轉(zhuǎn)SCL同時(shí)計(jì)數(shù)器清零重新開始。每次翻轉(zhuǎn)對應(yīng)SCL的一個(gè)半周期翻轉(zhuǎn)兩次就是一個(gè)完整周期。把SCL的頻率參數(shù)化之后如果想兼容不同傳感器對總線速度的要求直接改參數(shù)重新綜合就行。reg [15:0] half_cnt; reg scl_reg; always (posedge clk or negedge rst_n) begin if (!rst_n) begin half_cnt 16d0; scl_reg 1b1; end else if (half_cnt CLK_DIV - 1) begin half_cnt 16d0; scl_reg ~scl_reg; end else begin half_cnt half_cnt 1b1; end endCLK_DIV的計(jì)算CLK_DIV CLK_FREQ / (2 * SCL_FREQ)。比如50MHz主頻、100kHz的SIOCCLK_DIV就是250。需要注意CLK_DIV必須是個(gè)整數(shù)如果你的主頻和SCL頻率湊不出整數(shù)分頻就取整并微調(diào)實(shí)際SCL頻率控制在標(biāo)稱值的正負(fù)10%以內(nèi)一般沒問題。OV7670的SCCB接口對時(shí)鐘頻率不算苛刻100kHz到400kHz都能跑我習(xí)慣用100kHz圖個(gè)穩(wěn)妥。3.4 設(shè)計(jì)里幾個(gè)必須注意的細(xì)節(jié)應(yīng)答位的處理是SCCB控制器最容易出錯(cuò)的地方單獨(dú)展開說。設(shè)備地址和寄存器地址發(fā)送的時(shí)候主機(jī)在第9個(gè)時(shí)鐘必須釋放SDA轉(zhuǎn)為輸入方向去采樣ACK。很多入門代碼把SDA一直當(dāng)輸出口用到第9個(gè)時(shí)鐘還在試圖驅(qū)動SDA結(jié)果就是總線沖突讀回來的ACK永遠(yuǎn)是高電平。三態(tài)門的寫法是assign sda sda_out_en ? sda_out : 1bzsda_out_en在發(fā)送數(shù)據(jù)時(shí)為1在檢測ACK和讀數(shù)據(jù)時(shí)為0。SCL低電平期間切換數(shù)據(jù)的規(guī)則必須在代碼里嚴(yán)格執(zhí)行。起始條件產(chǎn)生時(shí)SCL要從高拉低然后SDA才能變化停止條件產(chǎn)生前SDA要在SCL低電平時(shí)先準(zhǔn)備好等SCL拉高后再讓SDA上升。如果順序反了波形上是看不出問題但傳感器就會莫名丟數(shù)據(jù)而且這種問題用示波器都很難一眼發(fā)現(xiàn)得拿邏輯分析儀對照協(xié)議規(guī)范逐拍核對??偩€釋放問題也值得一提。STOP之后的空閑狀態(tài)SDA和SCL都必須保持高電平這是從機(jī)識別總線空閑的唯一依據(jù)。很多新手寫完STOP就回IDLE忽略了在IDLE狀態(tài)把SDA和SCL都拉高結(jié)果下一次傳輸開始時(shí)總線狀態(tài)不對傳感器誤判成錯(cuò)誤起始。做法很簡單進(jìn)入IDLE時(shí)sda_out_en清零、scl_reg置高讓上拉電阻把SDA維持在高電平。補(bǔ)充一個(gè)仿真和上板的坑仿真里如果端口沒有外接上拉電阻模型SDA讀到的數(shù)據(jù)會一直是高阻態(tài)Z或者0導(dǎo)致仿真波形完全不對。解決方法是加一句仿真專用的賦值在testbench里給sda端口加pullup(sda)或者在頂層模塊里條件編譯加上ifdef SIMULATION pullup(sda);endif。這個(gè)坑我踩過一次當(dāng)時(shí)以為三態(tài)門寫錯(cuò)了排查了一整天才發(fā)現(xiàn)是仿真模型的問題。4. 上板調(diào)試實(shí)錄配置寫不進(jìn)、圖像全灰怎么辦4.1 現(xiàn)象一寫寄存器沒ACK讀ID讀不出先說最常見的坑。寫完控制器上板往OV7670寫寄存器忙等半天看不到done中斷抓出來一看總線狀態(tài)停在設(shè)備地址那個(gè)狀態(tài)。用邏輯分析儀抓SIOC和SIOD發(fā)現(xiàn)波形是出來了但從機(jī)就是不高低電平應(yīng)答。遇到這個(gè)現(xiàn)象先檢查硬件再看代碼邏輯。硬件方面SIOC和SIOD有沒有接上拉電阻。SCCB總線的SDA是開漏輸出必須靠外部上拉電阻才能拉高。很多OV7670模塊板載了上拉電阻但如果你是自己畫的板子或者用的轉(zhuǎn)接板漏了上拉電阻SDA就只能在低電平和三態(tài)之間跳從機(jī)根本沒法應(yīng)答。上拉電阻選2.2k到10k都行我習(xí)慣用4.7k兼容性比較好。代碼方面先檢查設(shè)備地址。寫地址0x42、讀地址0x43這是8位表示法。如果你把設(shè)備地址寫成0x21那就是7位地址忘了左移一位傳感器收不到任何有效地址。還有朋友會把地址搞成0x21左移變成0x42寫進(jìn)去又把讀地址寫成0x43——這兩個(gè)都是對的但一定要區(qū)分8位地址和7位地址的表示方式別混著用。再就是時(shí)鐘頻率。如果分頻系數(shù)算錯(cuò)SIOC跑到幾兆赫茲傳感器跟不上時(shí)鐘自然不響應(yīng)。用示波器量一下SIOC的實(shí)際頻率對一對代碼里的SCL_FREQ參數(shù)很快就能確認(rèn)是不是這個(gè)問題。4.2 現(xiàn)象二寄存器能寫進(jìn)去但圖像黑屏或者花屏寄存器能寫進(jìn)去說明SCCB鏈路已經(jīng)通了問題大概率出在圖像數(shù)據(jù)通路。OV7670輸出端的幾個(gè)信號PCLK像素時(shí)鐘、VSYNC幀同步、HREF行同步、D7~D0像素?cái)?shù)據(jù)。這些信號任何一個(gè)接錯(cuò)或者時(shí)序沒對上圖像都出不來。這里特別說一下熱詞里很多人搜的“OV7670不帶FIFO”。市面上的OV7670模塊分兩種帶FIFO版本和裸傳感器版本。帶FIFO版本常見的是板載AL422B內(nèi)部有存儲緩沖FPGA只需要按FIFO的時(shí)序去讀數(shù)據(jù)就行接口相對簡單不帶FIFO的版本PCLK、VSYNC、HREF全都要接到FPGA上而且數(shù)據(jù)是連續(xù)流出的FPGA必須實(shí)時(shí)把數(shù)據(jù)接收下來存到自己的RAM或者FIFO里時(shí)序一個(gè)都不能漏。如果你買的是裸傳感器就得把同步信號全部接對并且FPGA內(nèi)部要有緩存數(shù)據(jù)的機(jī)制否則很容易出現(xiàn)錯(cuò)位花屏。PCLK極性問題也特別常見。OV7670默認(rèn)輸出時(shí)PCLK有效數(shù)據(jù)在上升沿還是下降沿穩(wěn)定實(shí)際上和寄存器配置有關(guān)系比如0x15COM10的bit4可以改變PCLK極性。上板以后先用示波器同時(shí)抓PCLK和HREF確認(rèn)數(shù)據(jù)在哪個(gè)沿確實(shí)穩(wěn)定再去寫采集邏輯。別想當(dāng)然用上升沿采一旦極性反了采到的數(shù)據(jù)全是亂的但你又看不出來接線問題。像素位序問題也一樣隱蔽。RGB565輸出時(shí)高字節(jié)順序是R[4:0]G[5:3]低字節(jié)是G[2:0]B[4:0]。如果你把D7~D0接反了或者FPGA側(cè)對字節(jié)序做了錯(cuò)誤的拆分圖像顏色就全亂了。建議先把攝像頭在寄存器里配成RGB565輸出一個(gè)純色場景比如對著白墻在FPGA里抓一幀數(shù)據(jù)看具體值再倒推位序是否正確。4.3 現(xiàn)象三圖像偏暗、偏色或者劇烈閃爍寄存器配置完畢圖像也有輸出了但畫質(zhì)不理想這屬于調(diào)試的進(jìn)階問題。偏暗最直接的原因是AGC自動增益和AEC自動曝光的默認(rèn)參數(shù)在某些光照環(huán)境下收斂不到合適的值。你可以手動把0x25增益高位調(diào)大一點(diǎn)或者通過0x13COM8的bit0關(guān)閉自動增益手動寫入固定的增益值對比效果。偏色問題一般是白平衡沒收斂。OV7670的自動白平衡默認(rèn)是開啟的但開始運(yùn)行的最初幾秒畫面會偏色等它自己收斂。如果等了很久還是偏可以手動寫0x55/0x56/0x57這三個(gè)白平衡寄存器。當(dāng)然前提是你得確定鏡頭前面沒有顏色濾鏡或者偏色光源——這個(gè)我見過有人調(diào)了半天寄存器最后發(fā)現(xiàn)是用的紅色外殼轉(zhuǎn)接板透光影響了白平衡。畫面閃爍在室內(nèi)熒光燈環(huán)境下比較多見本質(zhì)是曝光時(shí)間和市電工頻的節(jié)拍沒對齊以及供電紋波太大。排查思路是先看電源攝像頭模塊的AVDD和DVDD要干凈電源紋波大的話先加電容濾波再看曝光時(shí)間設(shè)置盡量讓曝光和光環(huán)境節(jié)拍對齊或者改成固定曝光值試試。4.4 用寄存器回讀快速定位問題一個(gè)值得養(yǎng)成的習(xí)慣配置完某個(gè)寄存器之后回讀一下寄存器值核對寫進(jìn)去的是不是讀出來一樣。SCCB讀流程一旦跑通這個(gè)驗(yàn)證很暴力也很有效——硬件鏈路、狀態(tài)機(jī)、時(shí)序?qū)Σ粚追昼娋湍懿槌鰜?。我一般?x0APID和0x0BVER這兩個(gè)產(chǎn)品ID寄存器作為第一個(gè)回讀目標(biāo)OV7670的PID應(yīng)該是0x76VER應(yīng)該是0x73。第一次上板調(diào)試控制器寫完先別急著配一堆寄存器就做一件事讀這兩個(gè)ID。讀出來對說明通信鏈路完全正常剩下的就是寄存器配置邏輯的事讀不出來就回去查通信鏈路不要往下走。這個(gè)習(xí)慣幫我節(jié)省了大量無效排查時(shí)間。4.5 常見問題速查表現(xiàn)象可能原因解決辦法寫寄存器無ACK上拉電阻缺失、設(shè)備地址錯(cuò)、時(shí)鐘過快檢查硬件上拉、核對0x42/0x43、示波器測SIOC頻率讀ID全是1讀流程N(yùn)ACK沒處理好、SDA三態(tài)控制錯(cuò)在讀數(shù)據(jù)第9拍主動拉低SDA、檢查sda_out_en時(shí)序圖像黑屏寄存器沒配進(jìn)去、VSYNC/HREF接錯(cuò)先回讀PID/VER確認(rèn)鏈路、核對同步信號接線圖像花屏PCLK極性錯(cuò)誤、數(shù)據(jù)位序不匹配示波器確認(rèn)數(shù)據(jù)穩(wěn)定沿、抓一幀純色圖排查位序圖像偏暗A(chǔ)GC/AEC收斂慢、曝光設(shè)置低手動調(diào)0x25增益、檢查0x13自動控制開關(guān)畫面閃爍電源紋波大、曝光節(jié)拍不對增加濾波電容、固定曝光值對比測試5. 從SCCB到通用配置總線——讓控制器別再打補(bǔ)丁5.1 改造成通用I2C主機(jī)控制器SCCB控制器寫完別急著丟進(jìn)倉庫稍微改一下就能變成通用主機(jī)控制器后面配其他傳感器和外設(shè)都能用。改造方向設(shè)備地址參數(shù)化不再固定寫死寄存器地址位寬從8位擴(kuò)展到16位OV5640這類傳感器就是16位寄存器地址SCL頻率通過上層的寄存器配置接口動態(tài)調(diào)整增加一個(gè)配置隊(duì)列FIFO主機(jī)側(cè)連續(xù)下發(fā)多條配置命令控制器按順序完成省去每次等done再發(fā)下一條的時(shí)間。我后來在一個(gè)Zynq工程里驗(yàn)證過這個(gè)思路。把改造后的控制器封裝成AXI-Lite從機(jī)掛在PS端的總線上PS側(cè)通過寫寄存器下發(fā)配置表和觸發(fā)命令PL側(cè)負(fù)責(zé)具體時(shí)序。上層軟件只關(guān)心配置表內(nèi)容完全不用管底層怎么翻轉(zhuǎn)SDA和SCL。調(diào)試的時(shí)候改一行配置表代碼就能調(diào)整攝像頭參數(shù)不用重新綜合整個(gè)FPGA工程效率高了很多。5.2 從OV7670到圖像處理應(yīng)用配置鏈路跑通之后OV7670才能真正的開始干活。在智能車場景里FPGA通過SCCB把OV7670設(shè)置為QVGA灰度輸出然后做二值化、邊緣提取、十字補(bǔ)線這些圖像處理這些都是很成熟的應(yīng)用核心就是先把圖像數(shù)據(jù)穩(wěn)定采進(jìn)來再做算法。車牌識別方向大家搜得也多雖然OV7670只有30萬像素但用來做近距離的車牌定位和字符識別驗(yàn)證完全夠用。FPGA做圖像預(yù)處理比如灰度化、二值化、邊緣檢測然后把精簡后的數(shù)據(jù)交給軟核或者上位機(jī)做字符識別這套架構(gòu)在不少課程設(shè)計(jì)里都能跑通。它的價(jià)值在于讓你把圖像采集、預(yù)處理、算法識別整條鏈路走一遍等你換到更高分辨率的傳感器時(shí)前面這些經(jīng)驗(yàn)全部復(fù)用得上。從SCCB遷移到其他協(xié)議的思路也值得一提。不管是I3C、BISS-C還是MIPI CSI-2它們本質(zhì)上都是“時(shí)鐘數(shù)據(jù)狀態(tài)機(jī)”的組合理解了SCCB怎么用狀態(tài)機(jī)產(chǎn)生精確時(shí)序再學(xué)這些新協(xié)議會快很多。尤其是FPGA開發(fā)里接觸的大量串行協(xié)議底層邏輯都是相通的一個(gè)SCCB控制器就是你理解整個(gè)串行協(xié)議家族的第一塊敲門磚。做SCCB控制器這件事我前后折騰了三個(gè)晚上第一版代碼看著沒問題但上板就是不應(yīng)答。后來用邏輯分析儀一點(diǎn)點(diǎn)抓波形才發(fā)現(xiàn)是讀操作里NACK沒處理好第二次讀就卡死。從那以后我的習(xí)慣就是任何總線協(xié)議先規(guī)規(guī)矩矩畫出時(shí)序圖再寫狀態(tài)機(jī)最后仿真驗(yàn)證。把這個(gè)流程走順SCCB只是開始后面接觸MIPI、PCIe、DDR這類復(fù)雜總線方法論是一樣的。希望這篇能幫你少走點(diǎn)彎路一次點(diǎn)亮OV7670。