第45章 DCMI—OV2640攝像頭
全套200集視頻教程和1000頁PDF教程請到秉火論壇下載:www.firebbs.cn
野火視頻教程優酷觀看網址:http://i.youku.com/firege
本章參考資料:《STM32F4xx參考手冊》、《STM32F4xx規格書》、庫幫助文檔《stm32f4xx_dsp_stdperiph_lib_um.chm》。
關於開發板配套的OV2640攝像頭參數可查閱《ov2640datasheet》配套資料獲知。
STM32F4芯片具有浮點運算單元,適合對圖像信息使用DSP進行基本的圖像處理,其處理速度比傳統的8、16位機快得多,而且它還具有與攝像頭通訊的專用DCMI接口,所以使用它驅動攝像頭采集圖像信息並進行基本的加工處理非常適合。本章講解如何使用STM32驅動OV2640型號的攝像頭。
45.1 攝像頭簡介
在各類信息中,圖像含有最豐富的信息,作為機器視覺領域的核心部件,攝像頭被廣泛地應用在安防、探險以及車牌檢測等場合。攝像頭按輸出信號的類型來看可以分為數字攝像頭和模擬攝像頭,按照攝像頭圖像傳感器材料構成來看可以分為CCD和CMOS。現在智能手機的攝像頭絕大部分都是CMOS類型的數字攝像頭。
45.1.1 數字攝像頭跟模擬攝像頭區別
輸出信號類型
數字攝像頭輸出信號為數字信號,模擬攝像頭輸出信號為標准的模擬信號。
接口類型
數字攝像頭有USB接口(比如常見的PC端免驅攝像頭)、IEE1394火線接口(由蘋果公司領導的開發聯盟開發的一種高速度傳送接口,數據傳輸率高達800Mbps)、千兆網接口(網絡攝像頭)。模擬攝像頭多采用AV視頻端子(信號線+地線)或S-VIDEO(即蓮花頭--SUPER VIDEO,是一種五芯的接口,由兩路視頻亮度信號、兩路視頻色度信號和一路公共屏蔽地線共五條芯線組成)。
分辨率
模擬攝像頭的感光器件,其像素指標一般維持在752(H)*582(V)左右的水平,像素數一般情況下維持在41萬左右。現在的數字攝像頭分辨率一般從數十萬到數千萬。但這並不能說明數字攝像頭的成像分辨率就比模擬攝像頭的高,原因在於模擬攝像頭輸出的是模擬視頻信號,一般直接輸入至電視或監視器,其感光器件的分辨率與電視信號的掃描數呈一定的換算關系,圖像的顯示介質已經確定,因此模擬攝像頭的感光器件分辨率不是不能做高,而是依據於實際情況沒必要做這么高。
45.1.2 CCD與CMOS的區別
攝像頭的圖像傳感器CCD與CMOS傳感器主要區別如下:
成像材料
CCD與CMOS的名稱跟它們成像使用的材料有關,CCD是"電荷耦合器件"(Charge Coupled Device)的簡稱,而CMOS是"互補金屬氧化物半導體"(Complementary Metal Oxide Semiconductor)的簡稱。
功耗
由於CCD的像素由MOS電容構成,讀取電荷信號時需使用電壓相當大(至少12V)的二相或三相或四相時序脈沖信號,才能有效地傳輸電荷。因此CCD的取像系統除了要有多個電源外,其外設電路也會消耗相當大的功率。有的CCD取像系統需消耗2~5W的功率。而CMOS光電傳感器件只需使用一個單電源5V或3V,耗電量非常小,僅為CCD的1/8~1/10,有的CMOS取像系統只消耗20~50mW的功率。
成像質量
CCD傳感器件制作技術起步早,技術成熟,采用PN結或二氧化硅(sio2)隔離層隔離噪聲,所以噪聲低,成像質量好。與CCD相比,CMOS的主要缺點是噪聲高及靈敏度低,不過現在隨着CMOS電路消噪技術的不斷發展,為生產高密度優質的CMOS傳感器件提供了良好的條件,現在的CMOS傳感器已經占領了大部分的市場,主流的單反相機、智能手機都已普遍采用CMOS傳感器。
45.2 OV2640攝像頭
本章主要講解實驗板配套的攝像頭,它的實物見圖 451,該攝像頭主要由鏡頭、圖像傳感器、板載電路及下方的信號引腳組成。
圖 451 實驗板配套的OV2640攝像頭
鏡頭部件包含一個鏡頭座和一個可旋轉調節距離的凸透鏡,通過旋轉可以調節焦距,正常使用時,鏡頭座覆蓋在電路板上遮光,光線只能經過鏡頭傳輸到正中央的圖像傳感器,它采集光線信號,然后把采集得的數據通過下方的信號引腳輸出數據到外部器件。
45.2.1 OV2640傳感器簡介
圖像傳感器是攝像頭的核心部件,上述攝像頭中的圖像傳感器是一款型號為OV2640的CMOS類型數字圖像傳感器。該傳感器支持輸出最大為200萬像素的圖像 (1600x1200分辨率),支持使用VGA時序輸出圖像數據,輸出圖像的數據格式支持YUV(422/420)、YCbCr422、RGB565以及JPEG格式,若直接輸出JPEG格式的圖像時可大大減少數據量,方便網絡傳輸。它還可以對采集得的圖像進行補償,支持伽瑪曲線、白平衡、飽和度、色度等基礎處理。根據不同的分辨率配置,傳感器輸出圖像數據的幀率從15-60幀可調,工作時功率在125mW-140mW之間。
45.2.2 OV2640引腳及功能框圖
OV2640傳感器采用BGA封裝,它的前端是采光窗口,引腳都在背面引出,引腳的分布見圖 452。
圖 452 OV2640傳感器引腳分布圖
圖中的非彩色部分是電源相關的引腳,彩色部分是主要的信號引腳,介紹如下表 451。
表 451 OV7670管腳
管腳名稱 |
管腳類型 |
管腳描述 |
SIO_C |
輸入 |
SCCB總線的時鍾線,可類比I2C的SCL |
SIO_D |
I/O |
SCCB總線的數據線,可類比I2C的SDA |
RESETB |
輸入 |
系統復位管腳,低電平有效 |
PWDN |
輸入 |
掉電/省電模式,高電平有效 |
HREF |
輸出 |
行同步信號 |
VSYNC |
輸出 |
幀同步信號 |
PCLK |
輸出 |
像素同步時鍾輸出信號 |
XCLK |
輸入 |
外部時鍾輸入端口,可接外部晶振 |
Y0…Y9 |
輸出 |
像素數據輸出端口 |
下面我們配合圖 453中的OV2640功能框圖講解這些信號引腳。
圖 453 OV2640功能框圖
(1) 控制寄存器
標號處的是OV2640的控制寄存器,它根據這些寄存器配置的參數來運行,而這些參數是由外部控制器通過SIO_C和SIO_D引腳寫入的,SIO_C與SIO_D使用的通訊協議跟I2C十分類似,在STM32中我們完全可以直接用I2C硬件外設來控制。
(2) 通信、控制信號及時鍾
標號處包含了OV2640的通信、控制信號及外部時鍾,其中PCLK、HREF及VSYNC分別是像素同步時鍾、行同步信號以及幀同步信號,這與液晶屏控制中的信號是很類似的。RESETB引腳為低電平時,用於復位整個傳感器芯片,PWDN用於控制芯片進入低功耗模式。注意最后的一個XCLK引腳,它跟PCLK是完全不同的,XCLK是用於驅動整個傳感器芯片的時鍾信號,是外部輸入到OV2640的信號;而PCLK是OV2640輸出數據時的同步信號,它是由OV2640輸出的信號。XCLK可以外接晶振或由外部控制器提供,若要類比XCLK之於OV2640就相當於HSE時鍾輸入引腳與STM32芯片的關系,PCLK引腳可類比STM32的I2C外設的SCL引腳。
(3) 感光矩陣
標號處的是感光矩陣,光信號在這里轉化成電信號,經過各種處理,這些信號存儲成由一個個像素點表示的數字圖像。
(4) 數據輸出信號
標號處包含了DSP處理單元,它會根據控制寄存器的配置做一些基本的圖像處理運算。這部分還包含了圖像格式轉換單元及壓縮單元,轉換出的數據最終通過Y0-Y9引腳輸出,一般來說我們使用8根據數據線來傳輸,這時僅使用Y2-Y9引腳,OV2640與外部器件的連接方式見圖 454。
圖 454 8位數據線接法
45.2.3 SCCB時序
外部控制器對OV2640寄存器的配置參數是通過SCCB總線傳輸過去的,而SCCB總線跟I2C十分類似,所以在STM32驅動中我們直接使用片上I2C外設與它通訊。SCCB與標准的I2C協議的區別是它每次傳輸只能寫入或讀取一個字節的數據,而I2C協議是支持突發讀寫的,即在一次傳輸中可以寫入多個字節的數據(EEPROM中的頁寫入時序即突發寫)。關於SCCB協議的完整內容可查看配套資料里的《SCCB協議》文檔,下面我們簡單介紹下。
SCCB的起始、停止信號及數據有效性
SCCB的起始信號、停止信號及數據有效性與I2C完全一樣,見圖 455及圖 456。
起始信號:在SIO_C為高電平時,SIO_D出現一個下降沿,則SCCB開始傳輸。
停止信號:在SIO_C為高電平時,SIO_D出現一個上升沿,則SCCB停止傳輸。
數據有效性:除了開始和停止狀態,在數據傳輸過程中,當SIO_C為高電平時,必須保證SIO_D上的數據穩定,也就是說,SIO_D上的電平變換只能發生在SIO_C為低電平的時候,SIO_D的信號在SIO_C為高電平時被采集。
圖 455 SCCB停止信號
圖 456 SCCB的數據有效性
SCCB數據讀寫過程
在SCCB協議中定義的讀寫操作與I2C也是一樣的,只是換了一種說法。它定義了兩種寫操作,即三步寫操作和兩步寫操作。三步寫操作可向從設備的一個目的寄存器中寫入數據,見圖 457。在三步寫操作中,第一階段發送從設備的ID地址+W標志(等於I2C的設備地址:7位設備地址+讀寫方向標志),第二階段發送從設備目標寄存器的8位地址,第三階段發送要寫入寄存器的8位數據。圖中的"X"數據位可寫入1或0,對通訊無影響。
圖 457 SCCB的三步寫操作
而兩步寫操作沒有第三階段,即只向從器件傳輸了設備ID+W標志和目的寄存器的地址,見圖 458。兩步寫操作是用來配合后面的讀寄存器數據操作的,它與讀操作一起使用,實現I2C的復合過程。
圖 458 SCCB的兩步寫操作
兩步讀操作,它用於讀取從設備目的寄存器中的數據,見圖 459。在第一階段中發送從設備的設備ID+R標志(設備地址+讀方向標志)和自由位,在第二階段中讀取寄存器中的8位數據和寫NA 位(非應答信號)。由於兩步讀操作沒有確定目的寄存器的地址,所以在讀操作前,必需有一個兩步寫操作,以提供讀操作中的寄存器地址。
圖 459 SCCB的兩步讀操作
可以看到,以上介紹的SCCB特性都與I2C無區別,而I2C比SCCB還多出了突發讀寫的功能,所以SCCB可以看作是I2C的子集,我們完全可以使用STM32的I2C外設來與OV2640進行SCCB通訊。
45.2.4 OV2640的寄存器
控制OV2640涉及到它很多的寄存器,可直接查詢《ov2640datasheet》了解,通過這些寄存器的配置,可以控制它輸出圖像的分辨率大小、圖像格式及圖像方向等。要注意的是OV2640有兩組寄存器,這兩組寄存器有部分地址重合,通過設置地址為0xFF的RA_DLMT寄存器可以切換寄存器組,當RA_DLMT寄存器為0時,通過SCCB發送的寄存器地址在DSP相關的寄存器組尋址,見圖 4510;RA_DLMT寄存器為1時,在Sensor相關的寄存器組尋址,圖 4510。
圖 4510 0xFF=0時的DSP相關寄存器說明(部分)
圖 4511 0xFF=1時的Sensor相關寄存器說明(部分)
官方還提供了一個《OV2640_Camera_app》的文檔,它針對不同的配置需求,提供了配置范例,見圖 4512。其中write_SCCB是一個利用SCCB向寄存器寫入數據的函數,第一個參數為要寫入的寄存器的地址,第二個參數為要寫入的內容。
圖 4512 調節幀率的寄存器配置范例
45.2.5 像素數據輸出時序
主控器控制OV2640時采用SCCB協議讀寫其寄存器,而它輸出圖像時則使用VGA時序(還可用SVGA、UXGA,這些時序都差不多),這跟控制液晶屏輸入圖像時很類似。OV2640輸出圖像時,一幀幀地輸出,在幀內的數據一般從左到右,從上到下,一個像素一個像素地輸出(也可通過寄存器修改方向),見圖 4513。
圖 4513 攝像頭數據輸出
例如,見圖 4514和圖 4515,若我們使用Y2-Y9數據線,圖像格式設置為RGB565,進行數據輸出時,Y2-Y9數據線會在1個像素同步時鍾PCLK的驅動下發送1字節的數據信號,所以2個PCLK時鍾可發送1個RGB565格式的像素數據。像素數據依次傳輸,每傳輸完一行數據時,行同步信號HREF會輸出一個電平跳變信號,每傳輸完一幀圖像時,VSYNC會輸出一個電平跳變信號。
圖 4514像素同步時序
圖 4515 幀圖像同步時序
45.3 STM32的DCMI接口簡介
STM32F4系列的控制器包含了DCMI數字攝像頭接口(Digital camera Interface),它支持使用上述類似VGA的時序獲取圖像數據流,支持原始的按行、幀格式來組織的圖像數據,如YUV、RGB,也支持接收JPEG格式壓縮的數據流。接收數據時,主要使用HSYNC及VSYNC信號來同步。
45.3.1 DCMI整體框圖
STM32的DCMI接口整體框圖見圖 4516。
圖 4516 DCMI接口整體框圖
外部接口及時序
上圖標號處的是DCMI向外部引出的信號線。DCMI提供的外部接口的方向都是輸入的,接口的各個信號線說明見表 452。
表 452 DCMI的信號線說明
引腳名稱 |
說明 |
DCMI_D[0:13] |
數據線 |
DCMI_PIXCLK |
像素同步時鍾 |
DCMI_HSYNC |
行同步信號(水平同步信號) |
DCMI_VSYNC |
幀同步信號(垂直同步信號) |
其中DCMI_D數據線的數量可選8、10、12或14位,各個同步信號的有效極性都可編程控制。它使用的通訊時序與OV2640的圖像數據輸出接口時序一致,見圖 4517。
圖 4517 DCMI時序圖
內部信號及PIXCLK的時鍾頻率
圖 4516的標號處表示DCMI與內部的信號線。在STM32的內部,使用HCLK作為時鍾源提供給DCMI外設。從DCMI引出有DCMI_IT信號至中斷控制器,並可通過DMA_REQ信號發送DMA請求。
DCMI從外部接收數據時,在HCLK的上升沿時對PIXCLK同步的信號進行采樣,它限制了PIXCLK的最小時鍾周期要大於2.5個HCLK時鍾周期,即最高頻率為HCLK的1/4。
45.3.2 DCMI接口內部結構
DCMI接口的內部結構見圖 4518。
圖 4518 DCMI接口內部結構
(1) 同步器
同步器主要用於管理DCMI接收數據的時序,它根據外部的信號提取輸入的數據。
(2) FIFO/數據格式化器
為了對數據傳輸加以管理,STM32在DCMI接口上實現了 4 個字(32bit x4)深度的 FIFO,用以緩沖接收到的數據。
(3) AHB接口
DCMI接口掛載在AHB總線上,在AHB總線中有一個DCMI接口的數據寄存器,當我們讀取該寄存器時,它會從FIFO中獲取數據,並且FIFO中的數據指針會自動進行偏移,使得我們每次讀取該寄存器都可獲得一個新的數據。
(4) 控制/狀態寄存器
DCMI的控制寄存器協調圖中的各個結構運行,程序中可通過檢測狀態寄存器來獲DCMI的當前運行狀態。
(5) DMA接口
由於DCMI采集的數據量很大,我們一般使用DMA來把采集得的數據搬運至內存。
45.3.3 同步方式
DCMI接口支持硬件同步或內嵌碼同步方式,硬件同步方式即使用HSYNC和VSYNC作為同步信號的方式,OV2640就是使用這種同步時序。
而內嵌碼同步的方式是使用數據信號線傳輸中的特定編碼來表示同步信息,由於需要用0x00和0xFF來表示編碼,所以表示圖像的數據中不能包含有這兩個值。利用這兩個值,它擴展到4個字節,定義出了2種模式的同步碼,每種模式包含4個編碼,編碼格式為0xFF0000XY,其中XY的值可通過寄存器設置。當DCMI接收到這樣的編碼時,它不會把這些當成圖像數據,而是按照表 453中的編碼來解釋,作為同步信號。
表 453兩種模式的內嵌碼
模式2的內嵌碼 |
模式1的內嵌碼 |
幀開始(FS) |
有效行開始(SAV) |
幀結束(FE) |
有效行結束(EAV) |
行開始(LS) |
幀間消隱期內的行開始(SAV),其中消隱期內的即為無效數據 |
行結束(LS) |
幀間消隱期內的行結束(EAV),其中消隱期內的即為無效數據 |
45.3.4 捕獲模式及捕獲率
DCMI還支持兩種數據捕獲模式,分別為快照模式和連續采集模式。快照模式時只采集一幀的圖像數據,連續采集模式會一直采集多個幀的數據,並且可以通過配置捕獲率來控制采集多少數據,如可配置為采集所有數據或隔1幀采集一次數據或隔3幀采集一次數據。
45.4 DCMI初始化結構體
與其它外設一樣,STM32的DCMI外設也可以使用庫函數來控制,其中最主要的配置項都封裝到了DCMI_InitTypeDef結構體,來這些內容都定義在庫文件"stm32f4xx_dcmi.h"及"stm32f4xx_ dcmi.c"中,編程時我們可以結合這兩個文件內的注釋使用或參考庫幫助文檔。
DCMI_InitTypeDef初始化結構體的內容見錯誤!未找到引用源。。
代碼清單 451 DCMI初始化結構體
1 /**
2 * @brief DCMI 初始化結構體
3 */
4 typedef struct
5 {
6 uint16_t DCMI_CaptureMode; /*選擇連續模式或拍照模式 */
7 uint16_t DCMI_SynchroMode; /*選擇硬件同步模式還是內嵌碼模式 */
8 uint16_t DCMI_PCKPolarity; /*設置像素時鍾的有效邊沿*/
9 uint16_t DCMI_VSPolarity; /*設置VSYNC的有效電平*/
10 uint16_t DCMI_HSPolarity; /*設置HSYNC的有效邊沿*/
11 uint16_t DCMI_CaptureRate; /*設置圖像的采集間隔 */
12 uint16_t DCMI_ExtendedDataMode; /*設置數據線的寬度 */
13 } DCMI_InitTypeDef;
這些結構體成員說明如下,其中括號內的文字是對應參數在STM32標准庫中定義的宏:
(1) DCMI_CaptureMode
本成員設置DCMI的捕獲模式,可以選擇為連續攝像(DCMI_CaptureMode_Continuous)或單張拍照DCMI_CaptureMode_SnapShot。
(2) DCMI_SynchroMode
本成員設置DCMI數據的同步模式,可以選擇為硬件同步方式(DCMI_SynchroMode_Hardware)或內嵌碼方式(DCMI_SynchroMode_Embedded)。
(3) DCMI_PCKPolarity
本成員用於配置DCMI接口像素時鍾的有效邊沿,即在該時鍾邊沿時,DCMI會對數據線上的信號進行采樣,它可以被設置為上升沿有效(DCMI_PCKPolarity_Rising)或下降沿有效(DCMI_PCKPolarity_Falling)。
(4) DCMI_VSPolarity
本成員用於設置VSYNC的有效電平,當VSYNC信號線表示為有效電平時,表示新的一幀數據傳輸完成,它可以被設置為高電平有效(DCMI_VSPolarity_High)或低電平有效(DCMI_VSPolarity_Low)。
(5) DCMI_HSPolarity
類似地,本成員用於設置HSYNC的有效電平,當HSYNC信號線表示為有效電平時,表示新的一行數據傳輸完成,它可以被設置為高電平有效(DCMI_HSPolarity_High)或低電平有效(DCMI_HSPolarity_Low)。
(6) DCMI_CaptureRate
本成員可以用於設置DCMI捕獲數據的頻率,可以設置為全采集、半采集或1/4采集(DCMI_CaptureRate_All_Frame/ 1of2_Frame/ 1of4_Frame),在間隔采集的情況下,STM32的DCMI外設會直接按間隔丟棄數據。
(7) DCMI_ExtendedDataMode
本成員用於設置DCMI的數據線寬度,可配置為8/10/12及14位數據線寬(DCMI_ExtendedDataMode_8b/10b/12b/14b)。
配置完這些結構體成員后,我們調用庫函數DCMI_Init即可把這些參數寫入到DCMI的控制寄存器中,實現DCMI的初始化。
45.5 DCMI—OV2640攝像頭實驗
本小節講解如何使用DCMI接口從OV2640攝像頭輸出的RGB565格式的圖像數據,並把這些數據實時顯示到液晶屏上。
學習本小節內容時,請打開配套的"DCMI—OV2640攝像頭"工程配合閱讀。
45.5.1 硬件設計
攝像頭原理圖
本實驗采用的OV2640攝像頭實物見圖 451,其原理圖見錯誤!未找到引用源。。
圖 4519 OV2640攝像頭原理圖
圖 2719標號處的是OV2640芯片的主電路,在這部分中已對SCCB使用的信號線接了上拉電阻,外部電路可以省略上拉;標號處的是一個24MHz的有源晶振,它為OV2640提供系統時鍾,如果不想使用外部晶振提供時鍾源,可以參考圖中的R6處貼上0歐電阻,XCLK引腳引出至外部,由外部控制器提供時鍾;標號處的是攝像頭引腳集中引出的排針接口,使用它可以方便地與STM32實驗板中的排母連接。
攝像頭與實驗板的連接
通過排母,OV2640與STM32引腳的連接關系見錯誤!未找到引用源。。控制攝像頭的部分引腳與實驗板上的RGB彩燈共用,使用時會互相影響。
圖 4520 STM32實驗板引出的DCMI接口
以上原理圖可查閱《ov2640—黑白原理圖》及《秉火F429開發板黑白原理圖》文檔獲知,若您使用的攝像頭或實驗板不一樣,請根據實際連接的引腳修改程序。
45.5.2 軟件設計
為了使工程更加有條理,我們把攝像頭控制相關的代碼獨立分開存儲,方便以后移植。在"LTDC—液晶顯示"工程的基礎上新建"bsp_ov2640.c"及"bsp_ov2640.h"文件,這些文件也可根據您的喜好命名,它們不屬於STM32標准庫的內容,是由我們自己根據應用需要編寫的。
1. 編程要點
(1) 初始化DCMI時鍾,I2C時鍾;
(2) 使用I2C接口向OV2640寫入寄存器配置;
(3) 初始化DCMI工作模式;
(4) 初始化DMA,用於搬運DCMI的數據到顯存空間進行顯示;
(5) 編寫測試程序,控制采集圖像數據並顯示到液晶屏。
2. 代碼分析
攝像頭硬件相關宏定義
我們把攝像頭控制硬件相關的配置都以宏的形式定義到"bsp_ov2640.h"文件中,其中包括I2C及DCMI接口的,見錯誤!未找到引用源。。
代碼清單 452 攝像頭硬件配置相關的宏(省略了部分數據線)
1
2 /*攝像頭接口 */
3 //IIC SCCB
4 #define CAMERA_I2C I2C1
5 #define CAMERA_I2C_CLK RCC_APB1Periph_I2C1
6
7 #define CAMERA_I2C_SCL_PIN GPIO_Pin_6
8 #define CAMERA_I2C_SCL_GPIO_PORT GPIOB
9 #define CAMERA_I2C_SCL_GPIO_CLK RCC_AHB1Periph_GPIOB
10 #define CAMERA_I2C_SCL_SOURCE GPIO_PinSource6
11 #define CAMERA_I2C_SCL_AF GPIO_AF_I2C1
12
13 #define CAMERA_I2C_SDA_PIN GPIO_Pin_7
14 #define CAMERA_I2C_SDA_GPIO_PORT GPIOB
15 #define CAMERA_I2C_SDA_GPIO_CLK RCC_AHB1Periph_GPIOB
16 #define CAMERA_I2C_SDA_SOURCE GPIO_PinSource7
17 #define CAMERA_I2C_SDA_AF GPIO_AF_I2C1
18
19 //VSYNC
20 #define DCMI_VSYNC_GPIO_PORT GPIOI
21 #define DCMI_VSYNC_GPIO_CLK RCC_AHB1Periph_GPIOI
22 #define DCMI_VSYNC_GPIO_PIN GPIO_Pin_5
23 #define DCMI_VSYNC_PINSOURCE GPIO_PinSource5
24 #define DCMI_VSYNC_AF GPIO_AF_DCMI
25 // HSYNC
26 #define DCMI_HSYNC_GPIO_PORT GPIOA
27 #define DCMI_HSYNC_GPIO_CLK RCC_AHB1Periph_GPIOA
28 #define DCMI_HSYNC_GPIO_PIN GPIO_Pin_4
29 #define DCMI_HSYNC_PINSOURCE GPIO_PinSource4
30 #define DCMI_HSYNC_AF GPIO_AF_DCMI
31 //PIXCLK
32 #define DCMI_PIXCLK_GPIO_PORT GPIOA
33 #define DCMI_PIXCLK_GPIO_CLK RCC_AHB1Periph_GPIOA
34 #define DCMI_PIXCLK_GPIO_PIN GPIO_Pin_6
35 #define DCMI_PIXCLK_PINSOURCE GPIO_PinSource6
36 #define DCMI_PIXCLK_AF GPIO_AF_DCMI
37 //PWDN
38 #define DCMI_PWDN_GPIO_PORT GPIOG
39 #define DCMI_PWDN_GPIO_CLK RCC_AHB1Periph_GPIOG
40 #define DCMI_PWDN_GPIO_PIN GPIO_Pin_3
41
42 //數據信號線
43 #define DCMI_D0_GPIO_PORT GPIOH
44 #define DCMI_D0_GPIO_CLK RCC_AHB1Periph_GPIOH
45 #define DCMI_D0_GPIO_PIN GPIO_Pin_9
46 #define DCMI_D0_PINSOURCE GPIO_PinSource9
47 #define DCMI_D0_AF GPIO_AF_DCMI
48 /*....省略部分數據線*/
以上代碼根據硬件的連接,把與DCMI、I2C接口與攝像頭通訊使用的引腳號、引腳源以及復用功能映射都以宏封裝起來。
初始化DCMI的 GPIO及I2C
利用上面的宏,初始化DCMI的GPIO引腳及I2C,見錯誤!未找到引用源。。
代碼清單 453 初始化DCMI的GPIO及I2C(省略了部分數據線)
1
2 /**
3 * @brief 初始化控制攝像頭使用的GPIO(I2C/DCMI)
4 * @param None
5 * @retval None
6 */
7 void OV2640_HW_Init(void)
8 {
9 GPIO_InitTypeDef GPIO_InitStructure;
10 I2C_InitTypeDef I2C_InitStruct;
11
12 /***DCMI引腳配置***/
13 /* 使能DCMI時鍾 */
14 RCC_AHB1PeriphClockCmd(DCMI_PWDN_GPIO_CLK|DCMI_VSYNC_GPIO_CLK |
15 DCMI_HSYNC_GPIO_CLK | DCMI_PIXCLK_GPIO_CLK|
16 DCMI_D0_GPIO_CLK| DCMI_D1_GPIO_CLK|, ENABLE);
17
18 /*控制/同步信號線*/
19 GPIO_InitStructure.GPIO_Pin = DCMI_VSYNC_GPIO_PIN;
20 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
21 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
22 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
23 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
24 GPIO_Init(DCMI_VSYNC_GPIO_PORT, &GPIO_InitStructure);
25 GPIO_PinAFConfig(DCMI_VSYNC_GPIO_PORT, DCMI_VSYNC_PINSOURCE, DCMI_VSYNC_AF);
26
27 GPIO_InitStructure.GPIO_Pin = DCMI_HSYNC_GPIO_PIN ;
28 GPIO_Init(DCMI_HSYNC_GPIO_PORT, &GPIO_InitStructure);
29 GPIO_PinAFConfig(DCMI_HSYNC_GPIO_PORT, DCMI_HSYNC_PINSOURCE, DCMI_HSYNC_AF);
30
31 GPIO_InitStructure.GPIO_Pin = DCMI_PIXCLK_GPIO_PIN ;
32 GPIO_Init(DCMI_PIXCLK_GPIO_PORT, &GPIO_InitStructure);
33 GPIO_PinAFConfig(DCMI_PIXCLK_GPIO_PORT, DCMI_PIXCLK_PINSOURCE, DCMI_PIXCLK_AF);
34
35 GPIO_InitStructure.GPIO_Pin = DCMI_PWDN_GPIO_PIN ;
36 GPIO_Init(DCMI_PWDN_GPIO_PORT, &GPIO_InitStructure);
37 /*PWDN引腳,高電平關閉電源,低電平供電*/
38 GPIO_ResetBits(DCMI_PWDN_GPIO_PORT,DCMI_PWDN_GPIO_PIN);
39
40 /*數據信號*/
41 GPIO_InitStructure.GPIO_Pin = DCMI_D0_GPIO_PIN ;
42 GPIO_Init(DCMI_D0_GPIO_PORT, &GPIO_InitStructure);
43 GPIO_PinAFConfig(DCMI_D0_GPIO_PORT, DCMI_D0_PINSOURCE, DCMI_D0_AF);
44 /*...省略部分數據信號線*/
45
46 /****** 配置I2C,使用I2C與攝像頭的SCCB接口通訊*****/
47 /* 使能I2C時鍾 */
48 RCC_APB1PeriphClockCmd(CAMERA_I2C_CLK, ENABLE);
49 /* 使能I2C使用的GPIO時鍾 */
50RCC_AHB1PeriphClockCmd(CAMERA_I2C_SCL_GPIO_CLK|CAMERA_I2C_SDA_GPIO_CLK, ENABLE);
51 /* 配置引腳源 */
52GPIO_PinAFConfig(CAMERA_I2C_SCL_GPIO_PORT, CAMERA_I2C_SCL_SOURCE, CAMERA_I2C_SCL_AF);
53GPIO_PinAFConfig(CAMERA_I2C_SDA_GPIO_PORT, CAMERA_I2C_SDA_SOURCE, CAMERA_I2C_SDA_AF);
54
55 /* 初始化GPIO */
56 GPIO_InitStructure.GPIO_Pin = CAMERA_I2C_SCL_PIN ;
57 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
58 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
59 GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
60 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
61 GPIO_Init(CAMERA_I2C_SCL_GPIO_PORT, &GPIO_InitStructure);
62GPIO_PinAFConfig(CAMERA_I2C_SCL_GPIO_PORT, CAMERA_I2C_SCL_SOURCE, CAMERA_I2C_SCL_AF);
63
64 GPIO_InitStructure.GPIO_Pin = CAMERA_I2C_SDA_PIN ;
65 GPIO_Init(CAMERA_I2C_SDA_GPIO_PORT, &GPIO_InitStructure);
66
67 /*初始化I2C模式 */
68 I2C_DeInit(CAMERA_I2C);
69
70 I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
71 I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
72 I2C_InitStruct.I2C_OwnAddress1 = 0xFE;
73 I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
74 I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
75 I2C_InitStruct.I2C_ClockSpeed = 40000;
76
77 /* 寫入配置 */
78 I2C_Init(CAMERA_I2C, &I2C_InitStruct);
79
80 /* 使能I2C */
81 I2C_Cmd(CAMERA_I2C, ENABLE);
82 }
與所有使用到GPIO的外設一樣,都要先把使用到的GPIO引腳模式初始化,以上代碼把DCMI接口的信號線全都初始化為DCMI復用功能,而PWDN則被初始化成普通的推挽輸出模式,並且在初始化完畢后直接控制它為低電平,使能給攝像頭供電。
函數中還包含了I2C的初始化配置,使用I2C與OV2640的SCCB接口通訊,這里的I2C模式配置與標准的I2C無異。
配置DCMI的模式
接下來需要配置DCMI的工作模式,我們通過編寫OV2640_Init函數完成該功能,見錯誤!未找到引用源。。
代碼清單 454 配置DCMI的模式(bsp_ov2640.c文件)
1 #define FSMC_LCD_ADDRESS ((uint32_t)0xD0000000)
2
3 /*液晶屏的分辨率,用來計算地址偏移*/
4 uint16_t lcd_width=800, lcd_height=480;
5
6 /*攝像頭采集圖像的大小,改變這兩個值可以改變數據量,
7 但不會加快采集速度,要加快采集速度需要改成SVGA模式*/
8 uint16_t img_width=800, img_height=480;
9 /**
10 * @brief 配置 DCMI/DMA 以捕獲攝像頭數據
11 * @param None
12 * @retval None
13 */
14 void OV2640_Init(void)
15 {
16 DCMI_InitTypeDef DCMI_InitStructure;
17 NVIC_InitTypeDef NVIC_InitStructure;
18
19 /*** 配置DCMI接口 ***/
20 /* 使能DCMI時鍾 */
21 RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_DCMI, ENABLE);
22
23 /* DCMI 配置*/
24 DCMI_InitStructure.DCMI_CaptureMode = DCMI_CaptureMode_Continuous;
25 DCMI_InitStructure.DCMI_SynchroMode = DCMI_SynchroMode_Hardware;
26 DCMI_InitStructure.DCMI_PCKPolarity = DCMI_PCKPolarity_Rising;
27 DCMI_InitStructure.DCMI_VSPolarity = DCMI_VSPolarity_Low;
28 DCMI_InitStructure.DCMI_HSPolarity = DCMI_HSPolarity_Low;
29 DCMI_InitStructure.DCMI_CaptureRate = DCMI_CaptureRate_All_Frame;
30 DCMI_InitStructure.DCMI_ExtendedDataMode = DCMI_ExtendedDataMode_8b;
31 DCMI_Init(&DCMI_InitStructure);
32
33 //開始傳輸,從后面開始一行行掃描上來,實現數據翻轉
34 //dma_memory 以16位數據為單位, dma_bufsize以32位數據為單位(即像素個數/2)
35 OV2640_DMA_Config(FSMC_LCD_ADDRESS+(lcd_height-1)*(lcd_width)*2,img_width*2/4);
36
37 /* 配置中斷 */
38 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
39
40 /* 配置中斷源 */
41 NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream1_IRQn ;//DMA數據流中斷
42 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
43 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
44 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
45 NVIC_Init(&NVIC_InitStructure);
46 DMA_ITConfig(DMA2_Stream1,DMA_IT_TC,ENABLE);
47
48 /* 配置幀中斷,接收到幀同步信號就進入中斷 */
49 NVIC_InitStructure.NVIC_IRQChannel = DCMI_IRQn ; //幀中斷
50 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0;
51 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
52 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
53 NVIC_Init(&NVIC_InitStructure);
54 DCMI_ITConfig (DCMI_IT_FRAME,ENABLE);
55 }
該函數的執行流程如下:
(1) 使能DCMI外設的時鍾,它是掛載在AHB2總線上的;
(2) 根據攝像頭的時序和硬件連接的要求,配置DCMI工作模式為:使用硬件同步,連續采集所有幀數據,采集時使用8根數據線,PIXCLK被設置為上升沿有效,VSYNC和HSYNC都被設置成低電平有效;
(3) 調用OV2640_DMA_Config函數開始DMA數據傳輸,每傳輸完一行數據需要調用一次,它包含本次傳輸的目的首地址及傳輸的數據量,后面我們再詳細解釋;
(4) 配置DMA中斷,DMA每次傳輸完畢會引起中斷,以便我們在中斷服務函數配置DMA傳輸下一行數據;
(5) 配置DCMI的幀傳輸中斷,為了防止有時DMA出現傳輸錯誤或傳輸速度跟不上導致數據錯位、偏移等問題,每次DCMI接收到攝像頭的一幀數據,得到新的幀同步信號后(VSYNC),就進入中斷,復位DMA,使它重新開始一幀的數據傳輸。
配置DMA數據傳輸
上面的DCMI配置函數中調用了OV2640_DMA_Config函數開始了DMA傳輸,該函數的定義見代碼清單 455。
代碼清單 455 配置DMA數據傳輸(bsp_ov2640.c文件)
1 /**
2 * @brief 配置 DCMI/DMA 以捕獲攝像頭數據
3 * @param DMA_Memory0BaseAddr:本次傳輸的目的首地址
4 * @param DMA_BufferSize:本次傳輸的數據量(單位為字,即4字節)
5 */
6 void OV2640_DMA_Config(uint32_t DMA_Memory0BaseAddr,uint16_t DMA_BufferSize)
7 {
8
9 DMA_InitTypeDef DMA_InitStructure;
10
11 /* 配置DMA從DCMI中獲取數據*/
12 /* 使能DMA*/
13 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
14 DMA_Cmd(DMA2_Stream1,DISABLE);
15 while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE) {}
16
17 DMA_InitStructure.DMA_Channel = DMA_Channel_1;
18 DMA_InitStructure.DMA_PeripheralBaseAddr = DCMI_DR_ADDRESS; //DCMI數據寄存器地址
19 DMA_InitStructure.DMA_Memory0BaseAddr = DMA_Memory0BaseAddr;//傳輸的目的地址(傳入的參數)
20 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
21 DMA_InitStructure.DMA_BufferSize =DMA_BufferSize;//傳輸的數據大小(傳入的參數)
22 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
23 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //寄存器地址自增
24 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
25 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
26 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循環模式
27 DMA_InitStructure.DMA_Priority = DMA_Priority_High;
28 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
29 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
30 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC8;
31 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
32
33 /*DMA中斷配置 */
34 DMA_Init(DMA2_Stream1, &DMA_InitStructure);
35
36 DMA_Cmd(DMA2_Stream1,ENABLE);
37 while (DMA_GetCmdStatus(DMA2_Stream1) != ENABLE) {}
38 }
39
該函數跟普通的DMA配置無異,它把DCMI接收到的數據從它的數據寄存器搬運到SDRAM顯存中,從而直接使用液晶屏顯示攝像頭采集得的圖像。它包含2個輸入參數DMA_Memory0BaseAddr和DMA_BufferSize,其中DMA_Memory0BaseAddr用於設置本次DMA傳輸的目的首地址,該參數會被賦值到結構體成員DMA_InitStructure.DMA_Memory0BaseAddr中。DMA_BufferSize則用於指示本次DMA傳輸的數據量,它會被賦值到結構體成員DMA_InitStructure.DMA_BufferSize中,要注意它的單位是一個字,即4字節,如我們要傳輸60字節的數據時,它應配置為15。在前面的OV2640_Init函數中,對這個函數有如下調用:
1
2 #define FSMC_LCD_ADDRESS ((uint32_t)0xD0000000)
3
4 /*液晶屏的分辨率,用來計算地址偏移*/
5 uint16_t lcd_width=800, lcd_height=480;
6
7 /*攝像頭采集圖像的大小,改變這兩個值可以改變數據量,
8 但不會加快采集速度,要加快采集速度需要改成SVGA械*/
9 uint16_t img_width=800, img_height=480;
10
11
12 //開始傳輸,從后面開始一行行掃描上來,實現數據翻轉
13 //dma_memory 以16位數據為單位, dma_bufsize以32位數據為單位(即像素個數/2)
14 OV2640_DMA_Config(FSMC_LCD_ADDRESS+(lcd_height-1)*(lcd_width)*2,img_width*2/4);
15
其中的lcd_width和lcd_height是液晶屏的分辨率,img_width和img_heigh表示攝像頭輸出的圖像的分辨率,FSMC_LCD_ADDRESS是液晶層的首個顯存地址。另外,本工程中顯示攝像頭數據的這個液晶層采用RGB565的像素格式,每個像素點占據2個字節。
所以在上面的函數調用中,第一個輸入參數:
【FSMC_LCD_ADDRESS+(lcd_height-1)*(lcd_width)*2】
它表示的是液晶屏最后一行的第一個像素的地址。
而第二個輸入參數:
【img_width*2/4】
它表示表示攝像頭一行圖像的數據量,單位為字,即用一行圖像數據的像素個數除以2即可。注意這里使用的變量是"img_width"而不是的"lcd_width"。
由於這里配置的是第一次DMA傳輸,它把DCMI接收到的第一行攝像頭數據傳輸至液晶屏的最后一行,見圖 4521,再配合在后面分析的中斷函數里的多次DMA配置,攝像頭輸出的數據會一行一行地"由下至上"顯示到液晶屏上。
圖 4521 DMA傳輸過程
把攝像頭輸出的第一行數據顯示到液晶屏的最后一行,是因為攝像頭輸出的原圖像是顛倒的,這樣處理可方便觀看實驗現象(實際上OV2640可配置寄存器來直接輸出鏡像數據,但不知為何配置該功能后采集得的圖像失真,所以我們就采用這樣顛倒的方法處理了)。
DMA傳輸完成中斷及幀中斷
OV2640_Init函數初始化了DCMI,使能了幀中斷、DMA傳輸完成中斷,並使能了第一次DMA傳輸,當這一行數據傳輸完成時,會進入DMA中斷服務函數,見代碼清單 456中的DMA2_Stream1_IRQHandler。
代碼清單 456 DMA傳輸完成中斷與幀中斷(stm32f4xx_it.c文件)
1 extern uint16_t lcd_width, lcd_height;
2 extern uint16_t img_width, img_height;
3
4 //記錄傳輸了多少行
5 static uint16_t line_num =0;
6 //DMA傳輸完成中斷服務函數
7 void DMA2_Stream1_IRQHandler(void)
8 {
9 if ( DMA_GetITStatus(DMA2_Stream1,DMA_IT_TCIF1) == SET )
10 {
11 /*行計數*/
12 line_num++;
13 if (line_num==img_height)
14 {
15 /*傳輸完一幀,計數復位*/
16 line_num=0;
17 }
18 /*DMA 一行一行傳輸*/
19 OV2640_DMA_Config(FSMC_LCD_ADDRESS+(lcd_width*2*(lcd_height-line_num-1)),img_width*2/4);
20 DMA_ClearITPendingBit(DMA2_Stream1,DMA_IT_TCIF1);
21 }
22 }
23
24
25 //幀中斷服務函數,使用幀中斷重置line_num,可防止有時掉數據的時候DMA傳送行數出現偏移
26 void DCMI_IRQHandler(void)
27 {
28 if ( DCMI_GetITStatus (DCMI_IT_FRAME) == SET )
29 {
30 /*傳輸完一幀,計數復位*/
31 line_num=0;
32 DCMI_ClearITPendingBit(DCMI_IT_FRAME);
33 }
34 }
35
DMA中斷服務函數中主要是使用了一個靜態變量line_num來記錄已傳輸了多少行數據,每進一次DMA中斷時自加1,由於進入一次中斷就代表傳輸完一行數據,所以line_num的值等於lcd_height時(攝像頭輸出的數據行數),表示傳輸完一幀圖像,line_num復位為0,開始另一幀數據的傳輸。line_num計數完畢后利用前面定義的OV2640_DMA_Config函數配置新的一行DMA數據傳輸,它利用line_num變量計算顯存地址的行偏移,控制DCMI數據被傳送到正確的位置,每次傳輸的都是一行像素的數據量。
當DCMI接口檢測到攝像頭傳輸的幀同步信號時,會進入DCMI_IRQHandler中斷服務函數,在這個函數中不管line_num原來的值是什么,它都把line_num直接復位為0,這樣下次再進入DMA中斷服務函數的時候,它會開始新一幀數據的傳輸。這樣可以利用DCMI的硬件同步信號,而不只是依靠DMA自己的傳輸計數,這樣可以避免有時STM32內部DMA傳輸受到阻塞而跟不上外部攝像頭信號導致的數據錯誤。
使能DCMI采集
以上是我們使用DCMI的傳輸配置,但它還沒有使能DCMI采集,在實際使用中還需要調用下面兩個庫函數開始采集數據。
1 //使能DCMI采集數據
2 DCMI_Cmd(ENABLE);
3 DCMI_CaptureCmd(ENABLE);
讀取OV2640芯片ID
配置完了STM32的DCMI,還需要控制攝像頭,它有很多寄存器用於配置工作模式。利用STM32的I2C接口,可向OV2640的寄存器寫入控制參數,我們先寫個讀取芯片ID的函數測試一下,見代碼清單 457。
代碼清單 457 讀取OV2640的芯片ID(bsp_ov2640.c文件)
1
2 //存儲攝像頭ID的結構體
3 typedef struct
4 {
5 uint8_t Manufacturer_ID1;
6 uint8_t Manufacturer_ID2;
7 uint8_t PIDH;
8 uint8_t PIDL;
9 } OV2640_IDTypeDef;
10 /*寄存器地址*/
11 #define OV2640_DSP_RA_DLMT 0xFF
12 #define OV2640_SENSOR_MIDH 0x1C
13 #define OV2640_SENSOR_MIDL 0x1D
14 #define OV2640_SENSOR_PIDH 0x0A
15 #define OV2640_SENSOR_PIDL 0x0B
16 /**
17 * @brief 讀取攝像頭的ID.
18 * @param OV2640ID: 存儲ID的結構體
19 * @retval None
20 */
21 void OV2640_ReadID(OV2640_IDTypeDef *OV2640ID)
22 {
23 /*OV2640有兩組寄存器,設置0xFF寄存器的值為0或為1時可選擇使用不同組的寄存器*/
24 OV2640_WriteReg(OV2640_DSP_RA_DLMT, 0x01);
25
26 /*讀取寄存芯片ID*/
27 OV2640ID->Manufacturer_ID1 = OV2640_ReadReg(OV2640_SENSOR_MIDH);
28 OV2640ID->Manufacturer_ID2 = OV2640_ReadReg(OV2640_SENSOR_MIDL);
29 OV2640ID->PIDH = OV2640_ReadReg(OV2640_SENSOR_PIDH);
30 OV2640ID->PIDL = OV2640_ReadReg(OV2640_SENSOR_PIDL);
31 }
在OV2640的MIDH及MIDL寄存器中存儲了它的廠商ID,默認值為0x7F和0xA2;而PIDH及PIDL寄存器存儲了產品ID,PIDH的默認值為0x26,PIDL的默認值不定,可能的值為0x40、0x41及0x42。在代碼中我們定義了一個結構體OV2640_IDTypeDef專門存儲這些讀取得的ID信息。
由於這些寄存器都是屬於Sensor組的,所以在讀取這些寄存器的內容前,需要先把OV2640中0xFF地址的DLMT寄存器值設置為1。
OV2640_ReadID函數中使用的OV2640_ReadReg及OV2640_WriteReg函數是使用STM32的I2C外設向某寄存器讀寫單個字節數據的底層函數,它與我們前面章節中用到的I2C函數大同小異,就不展開分析了。
向OV2640寫入寄存器配置
檢測到OV2640的存在后,向它寫入配置參數,見代碼清單 458。
代碼清單 458向OV2640寫入寄存器配置
1 /* OV2640的 SVGA是 600列*800行的,在800列*480行的液晶屏上不能全屏*/
2 /* 所以直接用 UXGA 模式,再根據所需的圖像窗口裁剪 */
3 const unsigned char OV2640_UXGA[][2]=
4 {
5 0xff, 0x00,
6 0x2c, 0xff,
7 0x2e, 0xdf,
8 0xff, 0x01,
9 0x3c, 0x32,
10 0x11, 0x00,
11 0x09, 0x02,
12 0x04, 0x20|0x80, //水平翻轉
13 0x13, 0xe5,
14
15 /*....以下省略*/
16 }
17
18
19 /**
20 * @brief 配置OV2640為UXGA模式,並設置輸出圖像大小
21 * @param None
22 * @retval None
23 */
24 void OV2640_UXGAConfig(void)
25 {
26 uint32_t i;
27
28 /* 寫入寄存器配置 */
29 for (i=0; i<(sizeof(OV2640_UXGA)/2); i++)
30 {
31 OV2640_WriteReg(OV2640_UXGA[i][0], OV2640_UXGA[i][1]);
32 }
33 /*設置輸出的圖像大小*/
34 OV2640_OutSize_Set(img_width,img_height);
35 }
這個OV2640_UXGAConfig函數簡單粗暴,它直接把一個二維數組OV2640_UXGA使用I2C傳輸到OV2640中,該二維數組的第一維存儲的是寄存器地址,第二維存儲的是對應寄存器要寫入的控制參數。
如果您對這些寄存器配置感興趣,可以一個個對着OV2640的寄存器說明來閱讀,閱讀時要注意區分DLMT寄存器(地址0xFF)為1或為0時的寄存器組。總的來部,這些配置主要是把OV2640配置成了UXGA時序模式,並使用8根數據線輸出格式為RGB565的圖像數據。
其中UXGA時序是指它最大可輸出1600x1200分辨率的圖像,OV2640還支持使用SVGA時序輸出最大分辨率為800x600的圖像,相對於UXGA,它可使用更高的幀率輸出,但由於它規定好了數據是800行、600列,而我們的液晶屏在橫屏狀態下,無法直接全屏顯示(800列、480行),所以就把OV2640配置成UXGA模式了。通過修改COM7寄存器(地址0x12)可改變時序模式。
main函數
最后我們來編寫main函數,利用前面講解的函數,控制采集圖像,見代碼清單 2414。
代碼清單 459 main函數
1 /**
2 * @brief 主函數
3 * @param 無
4 * @retval 無
5 */
6 int main(void)
7 {
8
9 /*攝像頭與RGB LED燈共用引腳,不要同時使用LED和攝像頭*/
10 Debug_USART_Config();
11
12 /*初始化液晶屏*/
13 LCD_Init();
14 LCD_LayerInit();
15 LTDC_Cmd(ENABLE);
16
17 /*把背景層刷黑色*/
18 LCD_SetLayer(LCD_BACKGROUND_LAYER);
19 LCD_SetTransparency(0xFF);
20 LCD_Clear(LCD_COLOR_BLACK);
21
22 /*初始化后默認使用前景層*/
23 LCD_SetLayer(LCD_FOREGROUND_LAYER);
24 /*默認設置不透明,該函數參數為不透明度,范圍 0-0xff ,
25 0為全透明,0xff為不透明*/
26 LCD_SetTransparency(0xFF);
27 LCD_Clear(TRANSPARENCY);
28
29 LCD_SetColors(LCD_COLOR_RED,TRANSPARENCY);
30
31 LCD_ClearLine(LINE(18));
32 LCD_DisplayStringLine_EN_CH(LINE(18),(uint8_t* )" 模式:UXGA 800x480");
33
34 CAMERA_DEBUG("STM32F429 DCMI 驅動OV2640例程");
35
36 /* 初始化攝像頭GPIO及IIC */
37 OV2640_HW_Init();
38
39 /* 讀取攝像頭芯片ID,確定攝像頭正常連接 */
40 OV2640_ReadID(&OV2640_Camera_ID);
41
42 if (OV2640_Camera_ID.PIDH == 0x26)
43 {
44 CAMERA_DEBUG("%x %x",OV2640_Camera_ID.Manufacturer_ID1 ,
45 OV2640_Camera_ID.Manufacturer_ID2);
46 }
47 else
48 {
49 LCD_SetTextColor(LCD_COLOR_RED);
50 LCD_DisplayStringLine_EN_CH(LINE(0),(uint8_t*) "沒有檢測到OV2640,請重新檢查連接。");
51 CAMERA_DEBUG("沒有檢測到OV2640攝像頭,請重新檢查連接。");
52 while (1);
53 }
54
55 OV2640_Init();
56 OV2640_UXGAConfig();
57
58 //使能DCMI采集數據
59 DCMI_Cmd(ENABLE);
60 DCMI_CaptureCmd(ENABLE);
61
62 /*DMA直接傳輸攝像頭數據到LCD屏幕顯示*/
63 while (1)
64 {
65 }
66 }
67
在main函數中,首先初始化了液晶屏,注意它是把攝像頭使用的液晶層初始化成RGB565格式了,可直接在工程的液晶底層驅動解這方面的內容。
攝像頭控制部分,首先調用了OV2640_HW_Init函數初始化DCMI及I2C,然后調用OV2640_ReadID函數檢測攝像頭與實驗板是否正常連接,若連接正常則調用OV2640_Init函數初始化DCMI的工作模式及DMA,再調用OV2640_UXGAConfig函數向OV2640寫入寄存器配置,最后,一定要記住調用庫函數DCMI_Cmd及DCMI_CaptureCmd函數使能DCMI開始捕獲數據,這樣才能正常開始工作。
3. 下載驗證
把OV2640接到實驗板的攝像頭接口中,用USB線連接開發板,編譯程序下載到實驗板,並上電復位,液晶屏會顯示攝像頭采集得的圖像,通過旋轉鏡頭可以調焦。
45.6 每課一問
14. DMA轉運DCMI數據到SDRAM顯存中時,不考慮圖像顛倒的問題,為什么不直接一次傳輸一整幀圖像而是一行一行地傳輸?
答:因為一整幀圖像的數據超過了DMA單次傳輸的最大數據量,所以就拆分成一行行傳輸了。
15. 運輸DCMI的數據時是否可以使用其它的DMA通道?如果可以,嘗試修改程序使用該通道進行傳輸。
16. 嘗試修改例程中的img_width及img_height變量,觀察實驗現象。