第46章 DCMI—OV5640攝像頭—零死角玩轉STM32-F429系列


 

第46章     DCMI—OV5640攝像頭

全套200集視頻教程和1000PDF教程請到秉火論壇下載:www.firebbs.cn

野火視頻教程優酷觀看網址:http://i.youku.com/firege

 

 

 

本章參考資料:《STM32F4xx參考手冊》、《STM32F4xx規格書》、庫幫助文檔《stm32f4xx_dsp_stdperiph_lib_um.chm》。

關於開發板配套的OV5640攝像頭參數可查閱《ov5640datasheet》配套資料獲知。

STM32F4芯片具有浮點運算單元,適合對圖像信息使用DSP進行基本的圖像處理,其處理速度比傳統的816位機快得多,而且它還具有與攝像頭通訊的專用DCMI接口,所以使用它驅動攝像頭采集圖像信息並進行基本的加工處理非常適合。本章講解如何使用STM32驅動OV5640型號的攝像頭。

46.1 攝像頭簡介

在各類信息中,圖像含有最豐富的信息,作為機器視覺領域的核心部件,攝像頭被廣泛地應用在安防、探險以及車牌檢測等場合。攝像頭按輸出信號的類型來看可以分為數字攝像頭和模擬攝像頭,按照攝像頭圖像傳感器材料構成來看可以分為CCDCMOS。現在智能手機的攝像頭絕大部分都是CMOS類型的數字攝像頭。

46.1.1 數字攝像頭跟模擬攝像頭區別

    輸出信號類型

數字攝像頭輸出信號為數字信號,模擬攝像頭輸出信號為標准的模擬信號。

    接口類型

數字攝像頭有USB接口(比如常見的PC端免驅攝像頭)IEE1394火線接口(由蘋果公司領導的開發聯盟開發的一種高速度傳送接口,數據傳輸率高達800Mbps)、千兆網接口(網絡攝像頭)。模擬攝像頭多采用AV視頻端子(信號線+地線)或S-VIDEO(即蓮花頭--SUPER VIDEO,是一種五芯的接口,由兩路視頻亮度信號、兩路視頻色度信號和一路公共屏蔽地線共五條芯線組成)。

    分辨率

模擬攝像頭的感光器件,其像素指標一般維持在752(H)*582(V)左右的水平,像素數一般情況下維持在41萬左右。現在的數字攝像頭分辨率一般從數十萬到數千萬。但這並不能說明數字攝像頭的成像分辨率就比模擬攝像頭的高,原因在於模擬攝像頭輸出的是模擬視頻信號,一般直接輸入至電視或監視器,其感光器件的分辨率與電視信號的掃描數呈一定的換算關系,圖像的顯示介質已經確定,因此模擬攝像頭的感光器件分辨率不是不能做高,而是依據於實際情況沒必要做這么高。

46.1.2 CCD與CMOS的區別

攝像頭的圖像傳感器CCDCMOS傳感器主要區別如下:

    成像材料

CCDCMOS的名稱跟它們成像使用的材料有關,CCD"電荷耦合器件"(Charge Coupled Device)的簡稱,而CMOS"互補金屬氧化物半導體"(Complementary Metal Oxide Semiconductor)的簡稱。

    功耗

由於CCD的像素由MOS電容構成,讀取電荷信號時需使用電壓相當大(至少12V)的二相或三相或四相時序脈沖信號,才能有效地傳輸電荷。因此CCD的取像系統除了要有多個電源外,其外設電路也會消耗相當大的功率。有的CCD取像系統需消耗2~5W的功率。而CMOS光電傳感器件只需使用一個單電源5V3V,耗電量非常小,僅為CCD1/8~1/10,有的CMOS取像系統只消耗20~50mW的功率。

    成像質量

CCD傳感器件制作技術起步早,技術成熟,采用PN結或二氧化硅(sio2)隔離層隔離噪聲,所以噪聲低,成像質量好。與CCD相比,CMOS的主要缺點是噪聲高及靈敏度低,不過現在隨着CMOS電路消噪技術的不斷發展,為生產高密度優質的CMOS傳感器件提供了良好的條件,現在的CMOS傳感器已經占領了大部分的市場,主流的單反相機、智能手機都已普遍采用CMOS傳感器。

46.2 OV5640攝像頭

本章主要講解實驗板配套的攝像頭,它的實物見圖 461,該攝像頭主要由鏡頭、圖像傳感器、板載電路及下方的信號引腳組成。

461 實驗板配套的OV5640攝像頭

鏡頭部件包含一個鏡頭座和一個可旋轉調節距離的凸透鏡,通過旋轉可以調節焦距,正常使用時,鏡頭座覆蓋在電路板上遮光,光線只能經過鏡頭傳輸到正中央的圖像傳感器,它采集光線信號,然后把采集得的數據通過下方的信號引腳輸出數據到外部器件。

46.2.1 OV5640傳感器簡介

圖像傳感器是攝像頭的核心部件,上述攝像頭中的圖像傳感器是一款型號為OV5640CMOS類型數字圖像傳感器。該傳感器支持輸出最大為500萬像素的圖像 (2592x1944分辨率),支持使用VGA時序輸出圖像數據,輸出圖像的數據格式支持YUV(422/420)YCbCr422RGB565以及JPEG格式,若直接輸出JPEG格式的圖像時可大大減少數據量,方便網絡傳輸。它還可以對采集得的圖像進行補償,支持伽瑪曲線、白平衡、飽和度、色度等基礎處理。根據不同的分辨率配置,傳感器輸出圖像數據的幀率從15-60幀可調,工作時功率在150mW-200mW之間。

46.2.2 OV5640引腳及功能框圖

OV5640模組帶有自動對焦功能,引腳的定義見圖 462

462 OV5640傳感器引腳分布圖

信號引腳功能介紹如下,介紹如下表 461

461 OV5640管腳

管腳名稱

管腳類型

管腳描述

SIO_C

輸入

SCCB總線的時鍾線,可類比I2CSCL

SIO_D

I/O

SCCB總線的數據線,可類比I2CSDA

RESET

輸入

系統復位管腳,低電平有效

PWDN

輸入

掉電/省電模式,高電平有效

HREF

輸出

行同步信號

VSYNC

輸出

幀同步信號

PCLK

輸出

像素同步時鍾輸出信號

XCLK

輸入

外部時鍾輸入端口,可接外部晶振

Y2Y9

輸出

像素數據輸出端口

下面我們配合圖 463中的OV5640功能框圖講解這些信號引腳。

463 OV5640功能框圖

(5)    控制寄存器

標號處的是OV5640的控制寄存器,它根據這些寄存器配置的參數來運行,而這些參數是由外部控制器通過SIO_CSIO_D引腳寫入的,SIO_CSIO_D使用的通訊協議跟I2C十分類似,在STM32中我們完全可以直接用I2C硬件外設來控制。

(6)    通信、控制信號及時鍾

標號‚處包含了OV5640的通信、控制信號及外部時鍾,其中PCLKHREFVSYNC分別是像素同步時鍾、行同步信號以及幀同步信號,這與液晶屏控制中的信號是很類似的。RESETB引腳為低電平時,用於復位整個傳感器芯片,PWDN用於控制芯片進入低功耗模式。注意最后的一個XCLK引腳,它跟PCLK是完全不同的,XCLK是用於驅動整個傳感器芯片的時鍾信號,是外部輸入到OV5640的信號;而PCLKOV5640輸出數據時的同步信號,它是由OV5640輸出的信號。XCLK可以外接晶振或由外部控制器提供,若要類比XCLK之於OV5640就相當於HSE時鍾輸入引腳與STM32芯片的關系,PCLK引腳可類比STM32I2C外設的SCL引腳。

(7)    感光矩陣

標號ƒ處的是感光矩陣,光信號在這里轉化成電信號,經過各種處理,這些信號存儲成由一個個像素點表示的數字圖像。

(8)    數據輸出信號

標號„處包含了DSP處理單元,它會根據控制寄存器的配置做一些基本的圖像處理運算。這部分還包含了圖像格式轉換單元及壓縮單元,轉換出的數據最終通過Y0-Y9引腳輸出,一般來說我們使用8根據數據線來傳輸,這時僅使用Y2-Y9引腳,OV5640與外部器件的連接方式見圖 464

464     8位數據線接法

(9)    數據輸出信號

標號⑤處為VCM處理單元,他會通過圖像分析來實現圖像的自動對焦功能。要實現自動對焦還需要下載自動對焦固件到模組,后面攝像頭實驗詳細介紹這個功能。

46.2.3 SCCB時序

外部控制器對OV5640寄存器的配置參數是通過SCCB總線傳輸過去的,而SCCB總線跟I2C十分類似,所以在STM32驅動中我們直接使用片上I2C外設與它通訊。SCCB與標准的I2C協議的區別是它每次傳輸只能寫入或讀取一個字節的數據,而I2C協議是支持突發讀寫的,即在一次傳輸中可以寫入多個字節的數據(EEPROM中的頁寫入時序即突發寫)。關於SCCB協議的完整內容可查看配套資料里的《SCCB協議》文檔,下面我們簡單介紹下。

SCCB的起始、停止信號及數據有效性

SCCB的起始信號、停止信號及數據有效性與I2C完全一樣,見圖 465及圖 466

    起始信號:在SIO_C為高電平時,SIO_D出現一個下降沿,則SCCB開始傳輸。

    停止信號:在SIO_C為高電平時,SIO_D出現一個上升沿,則SCCB停止傳輸。

    數據有效性:除了開始和停止狀態,在數據傳輸過程中,當SIO_C為高電平時,必須保證SIO_D上的數據穩定,也就是說,SIO_D上的電平變換只能發生在SIO_C為低電平的時候,SIO_D的信號在SIO_C為高電平時被采集。

465 SCCB停止信號

466 SCCB的數據有效性

SCCB數據讀寫過程

SCCB協議中定義的讀寫操作與I2C也是一樣的,只是換了一種說法。它定義了兩種寫操作,即三步寫操作和兩步寫操作。三步寫操作可向從設備的一個目的寄存器中寫入數據,見圖 467。在三步寫操作中,第一階段發送從設備的ID地址+W標志(等於I2C的設備地址:7位設備地址+讀寫方向標志),第二階段發送從設備目標寄存器的16位地址,第三階段發送要寫入寄存器的8位數據。圖中的"X"數據位可寫入10,對通訊無影響。

467 SCCB的三步寫操作

而兩步寫操作沒有第三階段,即只向從器件傳輸了設備ID+W標志和目的寄存器的地址,見圖 468。兩步寫操作是用來配合后面的讀寄存器數據操作的,它與讀操作一起使用,實現I2C的復合過程。

468 SCCB的兩步寫操作

兩步讀操作,它用於讀取從設備目的寄存器中的數據,見圖 469。在第一階段中發送從設備的設備ID+R標志(設備地址+讀方向標志)和自由位,在第二階段中讀取寄存器中的8位數據和寫NA (非應答信號)。由於兩步讀操作沒有確定目的寄存器的地址,所以在讀操作前,必需有一個兩步寫操作,以提供讀操作中的寄存器地址。

469 SCCB的兩步讀操作

可以看到,以上介紹的SCCB特性都與I2C無區別,而I2CSCCB還多出了突發讀寫的功能,所以SCCB可以看作是I2C的子集,我們完全可以使用STM32I2C外設來與OV5640進行SCCB通訊。

46.2.4 OV5640的寄存器

控制OV5640涉及到它很多的寄存器,可直接查詢《ov5640datasheet》了解,通過這些寄存器的配置,可以控制它輸出圖像的分辨率大小、圖像格式及圖像方向等。要注意的是OV5640寄存器地址為16位。

官方還提供了一個《OV5640_自動對焦照相模組應用指南(DVP_接口)__R2.13C.pdf》的文檔,它針對不同的配置需求,提供了配置范例,見圖 4610。其中write_SCCB是一個利用SCCB向寄存器寫入數據的函數,第一個參數為要寫入的寄存器的地址,第二個參數為要寫入的內容。

4610 調節幀率的寄存器配置范例

46.2.5 像素數據輸出時序

OV5640采用SCCB協議進行控制,而它輸出圖像時則使用VGA時序(還可用SVGAUXGA,這些時序都差不多),這跟控制液晶屏輸入圖像時很類似。OV5640輸出圖像時,一幀幀地輸出,在幀內的數據一般從左到右,從上到下,一個像素一個像素地輸出(也可通過寄存器修改方向),見圖 4611

4611 攝像頭數據輸出

例如,圖 4612,若我們使用Y2-Y9數據線,圖像格式設置為RGB565,進行數據輸出時,Y2-Y9數據線會在1個像素同步時鍾PCLK的驅動下發送1字節的數據信號,所以2PCLK時鍾可發送1RGB565格式的像素數據。像素數據依次傳輸,每傳輸完一行數據時,行同步信號HREF會輸出一個電平跳變信號,每傳輸完一幀圖像時,VSYNC會輸出一個電平跳變信號。

4612 DVP接口時序

46.3 STM32的DCMI接口簡介

STM32F4系列的控制器包含了DCMI數字攝像頭接口(Digital camera Interface),它支持使用上述類似VGA的時序獲取圖像數據流,支持原始的按行、幀格式來組織的圖像數據,如YUVRGB,也支持接收JPEG格式壓縮的數據流。接收數據時,主要使用HSYNCVSYNC信號來同步。

46.3.1 DCMI整體框圖

STM32DCMI接口整體框圖見圖 4613

4613 DCMI接口整體框圖

外部接口及時序

上圖標號處的是DCMI向外部引出的信號線。DCMI提供的外部接口的方向都是輸入的,接口的各個信號線說明見表 462

462 DCMI的信號線說明

引腳名稱

說明

DCMI_D[0:13]

數據線

DCMI_PIXCLK

像素同步時鍾

DCMI_HSYNC

行同步信號(水平同步信號)

DCMI_VSYNC

幀同步信號(垂直同步信號)

其中DCMI_D數據線的數量可選8101214位,各個同步信號的有效極性都可編程控制。它使用的通訊時序與OV5640的圖像數據輸出接口時序一致,見圖 4614

4614 DCMI時序圖

內部信號及PIXCLK的時鍾頻率

4613的標號‚處表示DCMI與內部的信號線。在STM32的內部,使用HCLK作為時鍾源提供給DCMI外設。從DCMI引出有DCMI_IT信號至中斷控制器,並可通過DMA_REQ信號發送DMA請求。

DCMI從外部接收數據時,在HCLK的上升沿時對PIXCLK同步的信號進行采樣,它限制了PIXCLK的最小時鍾周期要大於2.5HCLK時鍾周期,即最高頻率為HCLK1/4

46.3.2 DCMI接口內部結構

DCMI接口的內部結構見圖 4615

4615 DCMI接口內部結構

(6)    同步器

同步器主要用於管理DCMI接收數據的時序,它根據外部的信號提取輸入的數據。

(7)    FIFO/數據格式化器

為了對數據傳輸加以管理,STM32DCMI接口上實現了 4 個字(32bit x4)深度的 FIFO,用以緩沖接收到的數據。

(8)    AHB接口

DCMI接口掛載在AHB總線上,在AHB總線中有一個DCMI接口的數據寄存器,當我們讀取該寄存器時,它會從FIFO中獲取數據,並且FIFO中的數據指針會自動進行偏移,使得我們每次讀取該寄存器都可獲得一個新的數據。

(9)    控制/狀態寄存器

DCMI的控制寄存器協調圖中的各個結構運行,程序中可通過檢測狀態寄存器來獲DCMI的當前運行狀態。

(10)    DMA接口

由於DCMI采集的數據量很大,我們一般使用DMA來把采集得的數據搬運至內存。

46.3.3 同步方式

DCMI接口支持硬件同步或內嵌碼同步方式,硬件同步方式即使用HSYNCVSYNC作為同步信號的方式,OV5640就是使用這種同步時序。

而內嵌碼同步的方式是使用數據信號線傳輸中的特定編碼來表示同步信息,由於需要用0x000xFF來表示編碼,所以表示圖像的數據中不能包含有這兩個值。利用這兩個值,它擴展到4個字節,定義出了2種模式的同步碼,每種模式包含4個編碼,編碼格式為0xFF0000XY,其中XY的值可通過寄存器設置。當DCMI接收到這樣的編碼時,它不會把這些當成圖像數據,而是按照表 463中的編碼來解釋,作為同步信號。

463兩種模式的內嵌碼

模式2的內嵌碼

模式1的內嵌碼

幀開始(FS)

有效行開始(SAV)

幀結束(FE)

有效行結束(EAV)

行開始(LS)

幀間消隱期內的行開始(SAV),其中消隱期內的即為無效數據

行結束(LS)

幀間消隱期內的行結束(EAV),其中消隱期內的即為無效數據

46.3.4 捕獲模式及捕獲率

DCMI還支持兩種數據捕獲模式,分別為快照模式和連續采集模式。快照模式時只采集一幀的圖像數據,連續采集模式會一直采集多個幀的數據,並且可以通過配置捕獲率來控制采集多少數據,如可配置為采集所有數據或隔1幀采集一次數據或隔3幀采集一次數據。

46.4 DCMI初始化結構體

與其它外設一樣,STM32DCMI外設也可以使用庫函數來控制,其中最主要的配置項都封裝到了DCMI_InitTypeDef結構體,來這些內容都定義在庫文件"stm32f4xx_dcmi.h"及"stm32f4xx_ dcmi.c"中,編程時我們可以結合這兩個文件內的注釋使用或參考庫幫助文檔。

DCMI_InitTypeDef初始化結構體的內容見代碼清單 461

代碼清單 461 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標准庫中定義的宏:

(8)    DCMI_CaptureMode

本成員設置DCMI的捕獲模式,可以選擇為連續攝像(DCMI_CaptureMode_Continuous)或單張拍照DCMI_CaptureMode_SnapShot

(9)    DCMI_SynchroMode

本成員設置DCMI數據的同步模式,可以選擇為硬件同步方式(DCMI_SynchroMode_Hardware)或內嵌碼方式(DCMI_SynchroMode_Embedded)

(10)    DCMI_PCKPolarity

本成員用於配置DCMI接口像素時鍾的有效邊沿,即在該時鍾邊沿時,DCMI會對數據線上的信號進行采樣,它可以被設置為上升沿有效(DCMI_PCKPolarity_Rising)或下降沿有效(DCMI_PCKPolarity_Falling)

(11)    DCMI_VSPolarity

本成員用於設置VSYNC的有效電平,當VSYNC信號線表示為有效電平時,表示新的一幀數據傳輸完成,它可以被設置為高電平有效(DCMI_VSPolarity_High)或低電平有效(DCMI_VSPolarity_Low)

(12)    DCMI_HSPolarity

類似地,本成員用於設置HSYNC的有效電平,當HSYNC信號線表示為有效電平時,表示新的一行數據傳輸完成,它可以被設置為高電平有效(DCMI_HSPolarity_High)或低電平有效(DCMI_HSPolarity_Low)

(13)    DCMI_CaptureRate

本成員可以用於設置DCMI捕獲數據的頻率,可以設置為全采集、半采集或1/4采集(DCMI_CaptureRate_All_Frame/ 1of2_Frame/ 1of4_Frame),在間隔采集的情況下,STM32DCMI外設會直接按間隔丟棄數據。

(14)    DCMI_ExtendedDataMode

本成員用於設置DCMI的數據線寬度,可配置為8/10/1214位數據線寬(DCMI_ExtendedDataMode_8b/10b/12b/14b)

配置完這些結構體成員后,我們調用庫函數DCMI_Init即可把這些參數寫入到DCMI的控制寄存器中,實現DCMI的初始化。

46.5 DCMI—OV5640攝像頭實驗

本小節講解如何使用DCMI接口從OV5640攝像頭輸出的RGB565格式的圖像數據,並把這些數據實時顯示到液晶屏上。

學習本小節內容時,請打開配套的"DCMIOV5640攝像頭"工程配合閱讀。

46.5.1 硬件設計

攝像頭原理圖

本實驗采用的OV5640攝像頭實物見圖 4616,其原理圖見圖 4617

4616 OV5640攝像頭原理圖

4616標號處的是OV5640模組接口電路,在這部分中已對SCCB使用的信號線接了上拉電阻,外部電路可以省略上拉;標號‚處的是一個24MHz的有源晶振,它為OV5640提供系統時鍾,如果不想使用外部晶振提供時鍾源,可以參考圖中的R6處貼上0歐電阻,XCLK引腳引出至外部,由外部控制器提供時鍾;標號ƒ處的是電源轉換模塊,可以從5V2.8V1.5V供給模組使用;標號④處的是攝像頭引腳集中引出的排針接口,使用它可以方便地與STM32實驗板中的排母連接。標號⑤處的是電源指示燈。

攝像頭與實驗板的連接

通過排母,OV5640STM32引腳的連接關系見圖 4617控制攝像頭的部分引腳與實驗板上的RGB彩燈共用,使用時會互相影響。

4617 STM32實驗板引出的DCMI接口

以上原理圖可查閱《ov5640—黑白原理圖》及《秉火F429開發板黑白原理圖》文檔獲知,若您使用的攝像頭或實驗板不一樣,請根據實際連接的引腳修改程序。

46.5.2 軟件設計

為了使工程更加有條理,我們把攝像頭控制相關的代碼獨立分開存儲,方便以后移植。在"LTDC—液晶顯示"工程的基礎上新建"bsp_ov5640.c","ov5640_AF.c","bsp_ov5640.h", "ov5640_AF.h"文件,這些文件也可根據您的喜好命名,它們不屬於STM32標准庫的內容,是由我們自己根據應用需要編寫的。

1.    編程要點

(1)    初始化DCMI時鍾,I2C時鍾;

(2)    使用I2C接口向OV5640寫入寄存器配置;

(3)    初始化DCMI工作模式;

(4)    初始化DMA,用於搬運DCMI的數據到顯存空間進行顯示;

(5)    編寫測試程序,控制采集圖像數據並顯示到液晶屏。

2.    代碼分析
攝像頭硬件相關宏定義

我們把攝像頭控制硬件相關的配置都以宏的形式定義到"bsp_ov5640.h"文件中,其中包括I2CDCMI接口的,見代碼清單 462

代碼清單 462 攝像頭硬件配置相關的宏(省略了部分數據線)

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 /*....省略部分數據線*/

以上代碼根據硬件的連接,把與DCMII2C接口與攝像頭通訊使用的引腳號、引腳源以及復用功能映射都以宏封裝起來。

初始化DCMI的 GPIO及I2C

利用上面的宏,初始化DCMIGPIO引腳及I2C,見代碼清單 463

代碼清單 463 初始化DCMIGPIOI2C(省略了部分數據線)

 1 /** 

2 * @brief 初始化控制攝像頭使用的GPIO(I2C/DCMI)

3 * @param None

4 * @retval None

5 */

6 void OV5640_HW_Init(void)

7 {

8 GPIO_InitTypeDef GPIO_InitStructure;

9 I2C_InitTypeDef I2C_InitStruct;

10

11 /***DCMI引腳配置***/

12 /* 使能DCMI時鍾 */

13 RCC_AHB1PeriphClockCmd(DCMI_PWDN_GPIO_CLK|DCMI_RST_GPIO_CLK|DCMI_VS

14 YNC_GPIO_CLK | DCMI_HSYNC_GPIO_CLK |

15 DCMI_PIXCLK_GPIO_CLK|

16 DCMI_D0_GPIO_CLK| DCMI_D1_GPIO_CLK|

17 DCMI_D2_GPIO_CLK| DCMI_D3_GPIO_CLK|

18 DCMI_D4_GPIO_CLK| DCMI_D5_GPIO_CLK|

19 DCMI_D6_GPIO_CLK| DCMI_D7_GPIO_CLK, ENABLE);

20

21 /*控制/同步信號線*/

22 GPIO_InitStructure.GPIO_Pin = DCMI_VSYNC_GPIO_PIN;

23 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

24 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;

25 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

26 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;

27 GPIO_Init(DCMI_VSYNC_GPIO_PORT, &GPIO_InitStructure);

28 GPIO_PinAFConfig(DCMI_VSYNC_GPIO_PORT, DCMI_VSYNC_PINSOURCE,

29 DCMI_VSYNC_AF);

30

31 GPIO_InitStructure.GPIO_Pin = DCMI_HSYNC_GPIO_PIN ;

32 GPIO_Init(DCMI_HSYNC_GPIO_PORT, &GPIO_InitStructure);

33 GPIO_PinAFConfig(DCMI_HSYNC_GPIO_PORT, DCMI_HSYNC_PINSOURCE,

34 DCMI_HSYNC_AF);

35

36 GPIO_InitStructure.GPIO_Pin = DCMI_PIXCLK_GPIO_PIN ;

37 GPIO_Init(DCMI_PIXCLK_GPIO_PORT, &GPIO_InitStructure);

38 GPIO_PinAFConfig(DCMI_PIXCLK_GPIO_PORT, DCMI_PIXCLK_PINSOURCE,

39 DCMI_PIXCLK_AF);

40

41 /*數據信號*/

42 GPIO_InitStructure.GPIO_Pin = DCMI_D0_GPIO_PIN ;

43 GPIO_Init(DCMI_D0_GPIO_PORT, &GPIO_InitStructure);

44 GPIO_PinAFConfig(DCMI_D0_GPIO_PORT, DCMI_D0_PINSOURCE, DCMI_D0_AF);

45 /*...省略部分數據信號線*/

46

47 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;

48 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;

49 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

50 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;

51 GPIO_InitStructure.GPIO_Pin = DCMI_PWDN_GPIO_PIN ;

52 GPIO_Init(DCMI_PWDN_GPIO_PORT, &GPIO_InitStructure);

53 GPIO_InitStructure.GPIO_Pin = DCMI_RST_GPIO_PIN ;

54 GPIO_Init(DCMI_RST_GPIO_PORT, &GPIO_InitStructure);

55 /*PWDN引腳,高電平關閉電源,低電平供電*/

56

57 GPIO_ResetBits(DCMI_RST_GPIO_PORT,DCMI_RST_GPIO_PIN);

58 GPIO_SetBits(DCMI_PWDN_GPIO_PORT,DCMI_PWDN_GPIO_PIN);

59

60 Delay(10);//延時10ms

61

62 GPIO_ResetBits(DCMI_PWDN_GPIO_PORT,DCMI_PWDN_GPIO_PIN);

63

64 Delay(10);//延時10ms

65

66 GPIO_SetBits(DCMI_RST_GPIO_PORT,DCMI_RST_GPIO_PIN);

67

68 /****** 配置I2C,使用I2C與攝像頭的SCCB接口通訊*****/

69 /* 使能I2C時鍾 */

70 RCC_APB1PeriphClockCmd(CAMERA_I2C_CLK, ENABLE);

71 /* 使能I2C使用的GPIO時鍾 */

72 RCC_AHB1PeriphClockCmd(CAMERA_I2C_SCL_GPIO_CLK|CAMERA_I2C_SDA_GPIO_

73 CLK, ENABLE);

74 /* 配置引腳源 */

75 GPIO_PinAFConfig(CAMERA_I2C_SCL_GPIO_PORT, CAMERA_I2C_SCL_SOURCE,

76 CAMERA_I2C_SCL_AF);

77 GPIO_PinAFConfig(CAMERA_I2C_SDA_GPIO_PORT, CAMERA_I2C_SDA_SOURCE,

78 CAMERA_I2C_SDA_AF);

79

80 /* 初始化GPIO */

81 GPIO_InitStructure.GPIO_Pin = CAMERA_I2C_SCL_PIN ;

82 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

83 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;

84 GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;

85 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

86 GPIO_Init(CAMERA_I2C_SCL_GPIO_PORT, &GPIO_InitStructure);

87 GPIO_PinAFConfig(CAMERA_I2C_SCL_GPIO_PORT, CAMERA_I2C_SCL_SOURCE,

88 CAMERA_I2C_SCL_AF);

89

90 GPIO_InitStructure.GPIO_Pin = CAMERA_I2C_SDA_PIN ;

91 GPIO_Init(CAMERA_I2C_SDA_GPIO_PORT, &GPIO_InitStructure);

92

93 /*初始化I2C模式 */

94 I2C_DeInit(CAMERA_I2C);

95

96 I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;

97 I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;

98 I2C_InitStruct.I2C_OwnAddress1 = 0xFE;

99 I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;

100 I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7b

101 it;

102 I2C_InitStruct.I2C_ClockSpeed = 400000;

103

104 /* 寫入配置 */

105 I2C_Init(CAMERA_I2C, &I2C_InitStruct);

106

107 /* 使能I2C */

108 I2C_Cmd(CAMERA_I2C, ENABLE);

109

110 Delay(50);//延時50ms

111 }

 

與所有使用到GPIO的外設一樣,都要先把使用到的GPIO引腳模式初始化,以上代碼把DCMI接口的信號線全都初始化為DCMI復用功能,這里需要特別注意的地方是OV5640的上電時序比較特殊,我們初始化PWDNRST應該特別小心,先初始化成普通的推挽輸出模式,並且在初始化完畢后直接控制RST為低電平,PWDN為高電平,使攝像頭處於待機模式,延時10ms后控制PWDN為低電平,再延時10ms后控制RST為高電平,OV5640模組啟動。

函數中還包含了I2C的初始化配置,使用I2COV5640SCCB接口通訊,這里的I2C模式配置與標准的I2C無異。特別注意:I2C初始化完必須延時50ms,再進行對OV5640寄存器的讀寫操作。

配置DCMI的模式

接下來需要配置DCMI的工作模式,我們通過編寫OV5640_Init函數完成該功能,見代碼清單 464

代碼清單 464 配置DCMI的模式(bsp_ov5640.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 OV5640_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_High;

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_bufsize32位數據為單位(即像素個數/2)

35 OV5640_DMA_Config(FSMC_LCD_ADDRESS,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 }

該函數的執行流程如下:

(6)    使能DCMI外設的時鍾,它是掛載在AHB2總線上的;

(7)    根據攝像頭的時序和硬件連接的要求,配置DCMI工作模式為:使用硬件同步,連續采集所有幀數據,采集時使用8根數據線,PIXCLK被設置為上升沿有效,VSYNC被設置成高電平有效, HSYNC被設置成低電平有效;

(8)    調用OV5640_DMA_Config函數開始DMA數據傳輸,每傳輸完一行數據需要調用一次,它包含本次傳輸的目的首地址及傳輸的數據量,后面我們再詳細解釋

(9)    配置DMA中斷,DMA每次傳輸完畢會引起中斷,以便我們在中斷服務函數配置DMA傳輸下一行數據;

(10)    配置DCMI的幀傳輸中斷,為了防止有時DMA出現傳輸錯誤或傳輸速度跟不上導致數據錯位、偏移等問題,每次DCMI接收到攝像頭的一幀數據,得到新的幀同步信號后(VSYNC),就進入中斷,復位DMA,使它重新開始一幀的數據傳輸。

配置DMA數據傳輸

上面的DCMI配置函數中調用了OV5640_DMA_Config函數開始了DMA傳輸,該函數的定義見代碼清單 465

代碼清單 465 配置DMA數據傳輸(bsp_ov5640.c文件)

1 /**

2 * @brief 配置 DCMI/DMA 以捕獲攝像頭數據

3     * @param DMA_Memory0BaseAddr:本次傳輸的目的首地址

4 * @param DMA_BufferSize:本次傳輸的數據量(單位為字,

5 4字節)

6 */

7 void OV5640_DMA_Config(uint32_t DMA_Memory0BaseAddr,uint16_t

8 DMA_BufferSize)

9 {

10

11 DMA_InitTypeDef DMA_InitStructure;

12

13 /* 配置DMADCMI中獲取數據*/

14 /* 使能DMA*/

15 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);

16 DMA_Cmd(DMA2_Stream1,DISABLE);

17 while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE) {}

18

19 DMA_InitStructure.DMA_Channel = DMA_Channel_1;

20 DMA_InitStructure.DMA_PeripheralBaseAddr = DCMI_DR_ADDRESS;    

21 //DCMI數據寄存器地

22

23 DMA_InitStructure.DMA_Memory0BaseAddr = DMA_Memory0BaseAddr;

24 //DMA傳輸的目的地址(

25 傳入的參數)

26 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;

27 DMA_InitStructure.DMA_BufferSize =DMA_BufferSize;                         

28 //傳輸的數據大小(

29 傳入的參數)

30 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

31 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;            

32 //寄存器地址自增

33 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_W

34 ord;

35 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

36 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                                

37 //循環模式

38 DMA_InitStructure.DMA_Priority = DMA_Priority_High;

39 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;

40 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;

41 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC8;

42 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

43

44 /*DMA中斷配置 */

45 DMA_Init(DMA2_Stream1, &DMA_InitStructure);

46

47 DMA_Cmd(DMA2_Stream1,ENABLE);

48 while (DMA_GetCmdStatus(DMA2_Stream1) != ENABLE) {}

49 }

 

該函數跟普通的DMA配置無異,它把DCMI接收到的數據從它的數據寄存器搬運到SDRAM顯存中,從而直接使用液晶屏顯示攝像頭采集得的圖像。它包含2個輸入參數DMA_Memory0BaseAddrDMA_BufferSize,其中DMA_Memory0BaseAddr用於設置本次DMA傳輸的目的首地址,該參數會被賦值到結構體成員DMA_InitStructure.DMA_Memory0BaseAddr中。DMA_BufferSize則用於指示本次DMA傳輸的數據量,它會被賦值到結構體成員DMA_InitStructure.DMA_BufferSize中,要注意它的單位是一個字,即4字節,如我們要傳輸60字節的數據時,它應配置為15。在前面的OV5640_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_bufsize32位數據為單位(即像素個數/2)

14 OV5640_DMA_Config(FSMC_LCD_ADDRESS,img_width*2/4);

15

其中的lcd_widthlcd_height是液晶屏的分辨率,img_widthimg_heigh表示攝像頭輸出的圖像的分辨率,FSMC_LCD_ADDRESS是液晶層的首個顯存地址。另外,本工程中顯示攝像頭數據的這個液晶層采用RGB565的像素格式,每個像素點占據2個字節。

所以在上面的函數調用中,第一個輸入參數:

FSMC_LCD_ADDRESS

它表示的是液晶屏第一行的第一個像素的地址。

而第二個輸入參數:

img_width*2/4

它表示表示攝像頭一行圖像的數據量,單位為字,即用一行圖像數據的像素個數除以2即可。注意這里使用的變量是"img_width"而不是的"lcd_width"。

由於這里配置的是第一次DMA傳輸,它把DCMI接收到的第一行攝像頭數據傳輸至液晶屏的最后一行,見圖 4618,再配合在后面分析的中斷函數里的多次DMA配置,攝像頭輸出的數據會一行一行地"由下至上"顯示到液晶屏上。

4618 DMA傳輸過程

DMA傳輸完成中斷及幀中斷

OV5640_Init函數初始化了DCMI,使能了幀中斷、DMA傳輸完成中斷,並使能了第一次DMA傳輸,當這一行數據傳輸完成時,會進入DMA中斷服務函數,見代碼清單 466中的DMA2_Stream1_IRQHandler

代碼清單 466 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 OV5640_DMA_Config(FSMC_LCD_ADDRESS+(lcd_width*2*line_num)),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計數完畢后利用前面定義的OV5640_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);

讀取OV5640芯片ID

配置完了STM32DCMI,還需要控制攝像頭,它有很多寄存器用於配置工作模式。利用STM32I2C接口,可向OV5640的寄存器寫入控制參數,我們先寫個讀取芯片ID的函數測試一下,見代碼清單 467

代碼清單 467 讀取OV5640的芯片ID(bsp_ov5640.c文件)

1 //存儲攝像頭ID的結構體

2 typedef struct {

3 uint8_t PIDH;

4 uint8_t PIDL;

5 } OV5640_IDTypeDef;

6 #define OV5640_SENSOR_PIDH 0x300A

7 #define OV5640_SENSOR_PIDL 0x300B

8 /**

9 * @brief 讀取攝像頭的ID.

10 * @param OV5640ID: 存儲ID的結構體

11 * @retval None

12 */

13 void OV5640_ReadID(OV5640_IDTypeDef *OV5640ID)

14 {

15

16 /*讀取寄存芯片ID*/

17 OV5640ID->PIDH = OV5640_ReadReg(OV5640_SENSOR_PIDH);

18 OV5640ID->PIDL = OV5640_ReadReg(OV5640_SENSOR_PIDL);

19 }

20 /**

21 * @brief OV5640寄存器中讀取一個字節的數據

22 * @param Addr: 寄存器地址

23 * @retval 返回讀取得的數據

24 */

25 uint8_t OV5640_ReadReg(uint16_t Addr)

26 {

27 uint32_t timeout = DCMI_TIMEOUT_MAX;

28 uint8_t Data = 0;

29

30 /* Generate the Start Condition */

31 I2C_GenerateSTART(CAMERA_I2C, ENABLE);

32

33 /* Test on CAMERA_I2C EV5 and clear it */

34 timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */

35 while (!I2C_CheckEvent(CAMERA_I2C, I2C_EVENT_MASTER_MODE_SELECT)) {

36 /* If the timeout delay is exeeded, exit with error code */

37 if ((timeout--) == 0) return 0xFF;

38 }

39

40 /* Send DCMI selcted device slave Address for write */

41 I2C_Send7bitAddress(CAMERA_I2C, OV5640_DEVICE_ADDRESS,

42 I2C_Direction_Transmitter);

43

44 /* Test on I2C1 EV6 and clear it */

45 timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */

46 while (!I2C_CheckEvent(CAMERA_I2C,

47 I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) {

48 /* If the timeout delay is exeeded, exit with error code */

49 if ((timeout--) == 0) return 0xFF;

50 }

51

52 /* Send I2C1 location address MSB */

53 I2C_SendData( CAMERA_I2C, (uint8_t)((Addr>>8) & 0xFF) );

54

55 /* Test on I2C1 EV8 and clear it */

56 timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */

57 while (!I2C_CheckEvent(CAMERA_I2C,

58 I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {

59 /* If the timeout delay is exeeded, exit with error code */

60 if ((timeout--) == 0) return 0xFF;

61 }

62

63 /* Clear AF flag if arised */

64 CAMERA_I2C->SR1 |= (uint16_t)0x0400;

65

66 //--------------------------------------------------------

67 /* Send I2C1 location address LSB */

68 I2C_SendData( CAMERA_I2C, (uint8_t)(Addr & 0xFF) );

69 /* Test on I2C1 EV8 and clear it */

70 timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */

71 while (!I2C_CheckEvent(CAMERA_I2C,

72 I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {

73 /* If the timeout delay is exeeded, exit with error code */

74 if ((timeout--) == 0) return 0xFF;

75 }

76

77 /* Clear AF flag if arised */

78 CAMERA_I2C->SR1 |= (uint16_t)0x0400;

79 //--------------------------------------------------------

80

81 /* Generate the Start Condition */

82 I2C_GenerateSTART(CAMERA_I2C, ENABLE);

83

84 /* Test on I2C1 EV6 and clear it */

85 timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */

86 while (!I2C_CheckEvent(CAMERA_I2C, I2C_EVENT_MASTER_MODE_SELECT)) {

87 /* If the timeout delay is exeeded, exit with error code */

88 if ((timeout--) == 0) return 0xFF;

89 }

90

91 /* Send DCMI selcted device slave Address for write */

92 I2C_Send7bitAddress(CAMERA_I2C, OV5640_DEVICE_ADDRESS,

93 I2C_Direction_Receiver);

94

95 /* Test on I2C1 EV6 and clear it */

96 timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */

97 while (!I2C_CheckEvent(CAMERA_I2C,

98 I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) {

99 /* If the timeout delay is exeeded, exit with error code */

100 if ((timeout--) == 0) return 0xFF;

101 }

102

103 /* Prepare an NACK for the next data received */

104 I2C_AcknowledgeConfig(CAMERA_I2C, DISABLE);

105

106 /* Test on I2C1 EV7 and clear it */

107 timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */

108 while (!I2C_CheckEvent(CAMERA_I2C, I2C_EVENT_MASTER_BYTE_RECEIVED))

109 {

110 /* If the timeout delay is exeeded, exit with error code */

111 if ((timeout--) == 0) return 0xFF;

112 }

113

114 /* Prepare Stop after receiving data */

115 I2C_GenerateSTOP(CAMERA_I2C, ENABLE);

116

117 /* Receive the Data */

118 Data = I2C_ReceiveData(CAMERA_I2C);

119

120 /* return the read data */

121 return Data;

122 }

 

OV5640PIDHPIDL寄存器存儲了產品IDPIDH的默認值為0x56PIDL的默認值為0x40。在代碼中我們定義了一個結構體OV5640_IDTypeDef專門存儲這些讀取得的ID信息。

OV5640_ReadID函數中使用的OV5640_ReadReg函數是使用STM32I2C外設向某寄存器讀寫單個字節數據的底層函數,它與我們前面章節中用到的I2C函數差異是OV5640的寄存器地址是16位的。程序中是先發高8位地址接着發低8位地址,再讀取寄存器的值。

向OV5640寫入寄存器配置

檢測到OV5640的存在后,向它寫入配置參數,見代碼清單 468

代碼清單 468OV5640寫入寄存器配置

1 /**

2 * @brief Configures the OV5640 camera in BMP mode.

3 * @param BMP ImageSize: BMP image size

4 * @retval None

5 */

6 void OV5640_RGB565Config(void)

7 {

8 uint32_t i;

9

10 /*攝像頭復位*/

11 OV5640_Reset();

12 /* 寫入寄存器配置 */

13 /* Initialize OV5640 Set to output RGB565 */

14 for (i=0; i<(sizeof(RGB565_Init)/4); i++) {

15 OV5640_WriteReg(RGB565_Init[i][0], RGB565_Init[i][1]);

16 }

17

18 Delay(500);

19

20 if (img_width == 320)

21

22 ImageFormat=BMP_320x240;

23

24 else if (img_width == 640)

25

26 ImageFormat=BMP_640x480;

27

28 else if (img_width == 800)

29

30 ImageFormat=BMP_800x480;

31

32 switch (ImageFormat) {

33 case BMP_320x240: {

34 for (i=0; i<(sizeof(RGB565_QVGA)/4); i++) {

35 OV5640_WriteReg(RGB565_QVGA[i][0], RGB565_QVGA[i][1]);

36 }

37 break;

38 }

39 case BMP_640x480: {

40 for (i=0; i<(sizeof(RGB565_VGA)/4); i++) {

41 OV5640_WriteReg(RGB565_VGA[i][0], RGB565_VGA[i][1]);

42 }

43 break;

44 }

45 case BMP_800x480: {

46 for (i=0; i<(sizeof(RGB565_WVGA)/4); i++) {

47 OV5640_WriteReg(RGB565_WVGA[i][0], RGB565_WVGA[i][1]);

48 }

49 break;

50 }

51 default: {

52 for (i=0; i<(sizeof(RGB565_WVGA)/4); i++) {

53 OV5640_WriteReg(RGB565_WVGA[i][0], RGB565_WVGA[i][1]);

54 }

55 break;

56 }

57 }

58 }

59 /**

60 * @brief 寫一字節數據到OV5640寄存器

61 * @param Addr: OV5640 的寄存器地址

62 * @param Data: 要寫入的數據

63 * @retval 返回0表示寫入正常,0xFF表示錯誤

64 */

65 uint8_t OV5640_WriteReg(uint16_t Addr, uint8_t Data)

66 {

67 uint32_t timeout = DCMI_TIMEOUT_MAX;

68

69 /* Generate the Start Condition */

70 I2C_GenerateSTART(CAMERA_I2C, ENABLE);

71

72 /* Test on CAMERA_I2C EV5 and clear it */

73 timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */

74 while (!I2C_CheckEvent(CAMERA_I2C, I2C_EVENT_MASTER_MODE_SELECT)) {

75 /* If the timeout delay is exeeded, exit with error code */

76 if ((timeout--) == 0) return 0xFF;

77 }

78

79 /* Send DCMI selcted device slave Address for write */

80 I2C_Send7bitAddress(CAMERA_I2C, OV5640_DEVICE_ADDRESS,

81 I2C_Direction_Transmitter);

82

83 /* Test on CAMERA_I2C EV6 and clear it */

84 timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */

85 while (!I2C_CheckEvent(CAMERA_I2C,

86 I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) {

87 /* If the timeout delay is exeeded, exit with error code */

88 if ((timeout--) == 0) return 0xFF;

89 }

90

91 /* Send CAMERA_I2C location address MSB */

92 I2C_SendData(CAMERA_I2C, (uint8_t)( (Addr >> 8) & 0xFF) );

93

94 /* Test on CAMERA_I2C EV8 and clear it */

95 timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */

96 while (!I2C_CheckEvent(CAMERA_I2C,

97 I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {

98 /* If the timeout delay is exeeded, exit with error code */

99 if ((timeout--) == 0) return 0xFF;

100 }

101 //--------------------------------------------------------

102 /* Send I2C1 location address LSB */

103 I2C_SendData( CAMERA_I2C, (uint8_t)(Addr & 0xFF) );

104 /* Test on I2C1 EV8 and clear it */

105 timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */

106 while (!I2C_CheckEvent(CAMERA_I2C,

107 I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {

108 /* If the timeout delay is exeeded, exit with error code */

109 if ((timeout--) == 0) return 0xFF;

110 }

111

112

113 //--------------------------------------------------------

114

115 /* Send Data */

116 I2C_SendData(CAMERA_I2C, Data);

117

118 /* Test on CAMERA_I2C EV8 and clear it */

119 timeout = DCMI_TIMEOUT_MAX; /* Initialize timeout value */

120 while (!I2C_CheckEvent(CAMERA_I2C,

121 I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {

122 /* If the timeout delay is exeeded, exit with error code */

123 if ((timeout--) == 0) return 0xFF;

124 }

125

126 /* Send I2C1 STOP Condition */

127 I2C_GenerateSTOP(CAMERA_I2C, ENABLE);

128

129 /* If operation is OK, return 0 */

130 return 0;

131 }

這個OV5640_RGB565Config函數直接把一個初始化的二維數組RGB565_Init和一個分辨率設置的二維數組RGB565_WVGA(分辨率決定)使用I2C傳輸到OV5640中,二維數組的第一維存儲的是寄存器地址,第二維存儲的是對應寄存器要寫入的控制參數。OV5640_WriteReg 函數中,因為OV5640的寄存器地址為16位,所以寫寄存器的時候會先寫入高8位的地址接着寫入低8位的地址,然后再寫入寄存器的值,這個是有別於普通的I2C設備的寫入方式,需要特別注意。

如果您對這些寄存器配置感興趣,可以一個個對着OV5640的寄存器說明來閱讀,這些配置主要是把OV5640配置成了WVGA時序模式,並使用8根數據線輸出格式為RGB565的圖像數據。我們參考《OV5640_自動對焦照相模組應用指南(DVP_接口)__R2.13C.pdf》文檔中第204.1.3節的800x480預覽的寄存器參數進行配置。使攝像頭輸出為WVGA模式。

初始化OV5640自動對焦功能

寫入OV5640的配置參數后,需要向它寫入自動對焦固件,初始化自動對焦功能,才能使用自動對焦功能,見代碼清單 469

代碼清單 469 初始化OV5640自動對焦功能

1 void OV5640_AUTO_FOCUS(void)

2 {

3 OV5640_FOCUS_AD5820_Init();

4 OV5640_FOCUS_AD5820_Constant_Focus();

5 }

6 static void OV5640_FOCUS_AD5820_Init(void)

7 {

8 u8 state=0x8F;

9 u32 iteration = 100;

10 u16 totalCnt = 0;

11

12 CAMERA_DEBUG("OV5640_FOCUS_AD5820_Init\n");

13

14 OV5640_WriteReg(0x3000, 0x20);

15 totalCnt = sizeof(OV5640_AF_FW);

16 CAMERA_DEBUG("Total Count = %d\n", totalCnt);

17

18 // 寫入自動對焦固件 Brust mode

19 OV5640_WriteFW(OV5640_AF_FW,totalCnt);

20

21 OV5640_WriteReg(0x3022, 0x00);

22 OV5640_WriteReg(0x3023, 0x00);

23 OV5640_WriteReg(0x3024, 0x00);

24 OV5640_WriteReg(0x3025, 0x00);

25 OV5640_WriteReg(0x3026, 0x00);

26 OV5640_WriteReg(0x3027, 0x00);

27 OV5640_WriteReg(0x3028, 0x00);

28 OV5640_WriteReg(0x3029, 0xFF);

29 OV5640_WriteReg(0x3000, 0x00);

30 OV5640_WriteReg(0x3004, 0xFF);

31 OV5640_WriteReg(0x0000, 0x00);

32 OV5640_WriteReg(0x0000, 0x00);

33 OV5640_WriteReg(0x0000, 0x00);

34 OV5640_WriteReg(0x0000, 0x00);

35

36 do {

37 state = (u8)OV5640_ReadReg(0x3029);

38 CAMERA_DEBUG("when init af, state=0x%x\n",state);

39

40 Delay(10);

41 if (iteration-- == 0) {

42 CAMERA_DEBUG("[OV5640]STA_FOCUS state check ERROR!!,

43 state=0x%x\n",state);

44 break;

45 }

46 } while (state!=0x70);

47

48 OV5640_FOCUS_AD5820_Check_MCU();

49 return;

50 } /* OV5640_FOCUS_AD5820_Init */

51

52 //set constant focus

53 void OV5640_FOCUS_AD5820_Constant_Focus(void)

54 {

55 u8 state = 0x8F;

56 u32 iteration = 300;

57 //send constant focus mode command to firmware

58 OV5640_WriteReg(0x3023,0x01);

59 OV5640_WriteReg(0x3022,0x04);

60

61 iteration = 5000;

62 do {

63 state = (u8)OV5640_ReadReg(0x3023);

64 if (iteration-- == 0) {

65 CAMERA_DEBUG("[OV5640]AD5820_Single_Focus time out !!

66 %x\n",state);

67 return ;

68 }

69 Delay(10);

70 } while (state!=0x00); //0x0 : focused 0x01: is focusing

71 return;

72 }

OV5640_AUTO_FOCUS函數調用了OV5640_FOCUS_AD5820_Init函數和OV5640_FOCUS_

AD5820_Constant_Focus函數,我們先來介紹OV5640_FOCUS_AD5820_Init函數,首先復位OV5640內部的MCU,然后通過I2C的突發模式寫入自動對焦固件,突發模式就是只需要寫入首地址,接着就一直寫數據,這個過程地址會自增,直接寫完數據位置,對於連續地址寫入相當方便。寫入固件之后OV5640內部MCU開始初始化,最后檢查初始化完成的狀態是否為0x70,如果是就代表固件已經寫入成功,並初始化成功。接着,我們需要OV5640_FOCUS_AD5820_Constant_Focus函數來調用自動對焦固件中的持續對焦指令,完成以上步驟后,攝像頭就已經初始化完畢。

main函數

最后我們來編寫main函數,利用前面講解的函數,控制采集圖像,見代碼清單 4610

代碼清單 4610 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 驅動OV5640例程");

35

36 /* 初始化攝像頭GPIOIIC */

37 OV5640_HW_Init();

38

39 /* 讀取攝像頭芯片ID,確定攝像頭正常連接 */

40 OV5640_ReadID(&OV5640_Camera_ID);

41

42 if (OV5640_Camera_ID.PIDH == 0x56)

43 {

44 CAMERA_DEBUG("%x %x",OV5640_Camera_ID.PIDH,OV5640_Camera_ID.PIDL);

45

46 }

47 else

48 {

49 LCD_SetTextColor(LCD_COLOR_RED);

50 LCD_DisplayStringLine_EN_CH(LINE(0),(uint8_t*) "沒有檢測到OV5640,請重新檢查連接。");

51 CAMERA_DEBUG("沒有檢測到OV5640攝像頭,請重新檢查連接。");

52 while (1);

53 }

54

55 OV5640_Init();

56 OV5640_RGB565Config();

57     OV5640_AUTO_FOCUS();

58 //使能DCMI采集數據

59 DCMI_Cmd(ENABLE);

60 DCMI_CaptureCmd(ENABLE);

61

62 /*DMA直接傳輸攝像頭數據到LCD屏幕顯示*/

63 while (1)

64 {

65 }

66 }

67

main函數中,首先初始化了液晶屏,注意它是把攝像頭使用的液晶層初始化成RGB565格式了,可直接在工程的液晶底層驅動解這方面的內容。

攝像頭控制部分,首先調用了OV5640_HW_Init函數初始化DCMII2C,然后調用OV5640_ReadID函數檢測攝像頭與實驗板是否正常連接,若連接正常則調用OV5640_Init函數初始化DCMI的工作模式及DMA,再調用OV5640_RGB565Config函數向OV5640寫入寄存器配置,再調用OV5640_AUTO_FOCUS函數初始化OV5640自動對焦功能,最后,一定要記住調用庫函數DCMI_CmdDCMI_CaptureCmd函數使能DCMI開始捕獲數據,這樣才能正常開始工作。

3.    下載驗證

把OV5640接到實驗板的攝像頭接口中,用USB線連接開發板,編譯程序下載到實驗板,並上電復位,液晶屏會顯示攝像頭采集得的圖像,通過旋轉鏡頭可以調焦。

46.6 每課一問

1.    DMA轉運DCMI數據到SDRAM顯存中時,不考慮圖像顛倒的問題,為什么不直接一次傳輸一整幀圖像而是一行一行地傳輸?

答:因為一整幀圖像的數據超過了DMA單次傳輸的最大數據量,所以就拆分成一行行傳輸了。

2.    運輸DCMI的數據時是否可以使用其它的DMA通道?如果可以,嘗試修改程序使用該通道進行傳輸。

3.    嘗試修改例程中的img_width及img_height變量,觀察實驗現象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM