轉載請注明出處:http://blog.csdn.net/lxk7280
有人會奇怪為什麼使用OV系列的攝像頭每次都要進行SCCB的操作呢?難道它自己不會儲存上次的操作結果嗎?
原因是:OV系列的攝像頭的寄存器是EEPROM,不穩定,資料很容易丢失,是以程式每次初始化時我們都要重新寫入寄存器設定。
PS:常見需要修改的寄存器有,PCLK速率,幀率、圖像亮度、對比度、色飽和度、鏡像等功能。
智能車攝像頭組的初期學習中,雖然有不少攝像頭優于OV7620,但是相信大部分的車友第一個接觸的都是OV7620。下面從其特性和性能等角度,剖析攝像頭的特點。
攝像頭的輸出格式有RGB565,YUY422等格式,我所接觸的第一個攝像頭OV7620的輸出格式是YUV422。下面給大家介紹一下YUV422。
什麼是YUV422?
人的眼睛對低頻信号比對高頻信号具有更高的敏感度,事實上,人的眼睛對明視度的改變比對色彩的改變要敏感的多。是以,人們将RGB三色信号改為YUV來表示,其中Y為灰階,UV為色差。如果是表示一副彩色圖像,同樣的道理,YUV444是無損的存儲方式,但是需要3個位元組,存儲空間開銷很大。由于Y分量比UV分量重要的多,是以人們用YUV422來表示。這樣一來圖像被壓縮了很多,一個位元組就可以表示其彩色的資訊。
對于OV7620,它有2 組并行的資料口Y[7..0]和UV[7..0],其中對于資料口Y[7..0],輸出的是灰階值Y,對于UV[7..0]輸出的色度信号UV。下圖給出了k 個像素(K 個位元組)輸出的格式。
OV762的控制采用SCCB(Serial Camera ControlBus)協定。SCCB的簡化的I2C協定,SIO-I是串行時鐘輸入線,SIO-O是串行雙向資料線,分别相當于I2C協定的SCL和SDA。SCCB的總線時序與I2C基本相同,他的響應信号ACK被陳偉一個傳輸單元的第9位,分别Do not care和NA.Do not care位由從機産生;NA位由主機産生,由于SCCB不支援多位元組的讀寫,NA位必須為高電平。另外SCCB沒有重複起始的概念,是以在SCCB的讀周期中,當主機發送讀指令時,從機将不能産生Do not care響應信号。
由于I2C和SCCB的一些細微差别,是以采用GPIO模拟SCCB總線的方式,SCL所連接配接的引腳始終設為輸出方式,而SDA所連接配接的引腳在資料傳輸過程中,通過設定IODIR的值,動态改變引腳的輸入/輸出方式。SCCB的寫周期直接使用I2C總線協定的寫周期時序;而SC-CB的讀周期,則增加一個總線停止條件。
OV7620的幾個優點:
第一,OV7620的電平相容3.3V和5V。目前智能車使用者用到的處理器基本上可以分為XS128和K60和KL25三種控制器,而這三種控制器的工作電平分别是5V和3.3V和3.3V。OV7620可以完全适應這兩種電平,XS128和K60和KL25可以随性切換,無需做電平比對。(要注意的是當OV7620接5v和3.3v的時候,輸出的效果是不同的,建議在5v的電壓下使用,因為在3.3v的電壓下使用比較難調,輸出的16進制資料清一色偏小。)
同樣的情況下:
3.3V下: 5v下:
第二,OV7620的幀率是60幀/s。新手學習攝像頭的時候,誤以為攝像頭幀率越快越好,其實不然。就拿OV7620來說,其PCLK(像素中斷)的周期是73ns,該頻率下的PCLK很容易被K60的IO捕捉,如果幀率更快的攝像頭,其PCLK的周期就會更小,該頻率下PCLK不易被K60的IO捕捉到。(但是鷹眼攝像頭不然,火哥的鷹眼攝像頭理論上宣傳的是150幀每秒,但是他并不是通過PCLK的周期減小進而獲得效果的,鷹眼攝像頭的高明之處在于它在硬體二值化之後,每一次PCLK中斷對外輸出了8個像素,而不是1個像素。鷹眼攝像頭已經買來了,以後有機會會試試效果。)
第三:OV7620的分辨率也是非常合适的,在第三篇也提到OV7620是隔行掃描,采集VSYN的話,其輸出分辨率是640*240。如果改為QVGA格式,預設輸出分辨率是320*120,該分辨率下非常适合采集賽道,資料容量有限又不會失真圖像。(OV7620的分辨率可以通過SCCB修改,有興趣修改的可以去檢視OV7620的寄存器配置,然後通過SCCB修改。)
隻有掌握了OV7620的時序,才能靈活得使用OV7620。下面開始本篇的重點:OV7620時序分析。
對于OV7620,我們隻關心場中斷信号VSYN、行中斷信号HREF、像素中斷信号PCLK的波形。用示波器去監控這三個波形,可以看到一下關系。
VSYN 的周期是16.64ms,高電平時間為換場時間,約80us;低電平時間内像素輸出。我們在采集VSYN脈沖時,既可以采集上升沿,也可以采集下降沿,采集下降沿更準确些,這也是一場的開始。從VSYN的周期可以算出,1s/16.64ms=60幀,OV7620的幀率是60幀/s。
HREF的周期63.6us,高電平時間為像素輸出時間,約47us;低電平時間為換行時間,是以采集HREF一定要采集其上升沿,下降沿後的資料是無效的。從HREF的周期可以算出,16.64ms/63.6us≈261,除去期間的間隙時間,可以算出每場圖像有240行。
PCLK的周期是73ns,高電平輸出像素,低電平像素無效。PCLK是一直輸出的,是以一定要在觸發VSYN并且觸發HREF以後,再去捕捉PCLK才能捕捉到像素資料。從PCLK的周期可以算出,47us/73ns≈640,可以算出每行圖像中有640個像素點。
介紹完基本知識之後,下面開始寫程式了(Keil--K60--C語言):
在這我分成兩部分着重介紹7620的時序程式和貼上SCCB的協定程式(其實原理和處理情況和I2C差不多):
First :
首先要對使用到的一些IO口進行初始化處理,四個部分的初始化,
A.像素中斷PCLK
B.行中斷HREF
C.場中斷VSYNC
D.DMA
程式如下:
//初始化OV7620子產品
void OV7620_Init()
{
//像素中斷 PCLK
GPIO_InitStruct1.GPIO_Pin = OV7620_PCLK_PIN;
GPIO_InitStruct1.GPIO_InitState = Bit_SET;
GPIO_InitStruct1.GPIO_IRQMode = GPIO_IT_DMA_RISING;
GPIO_InitStruct1.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStruct1.GPIOx = OV7620_PCLK_PORT;
GPIO_Init(&GPIO_InitStruct1);
//行中斷 HREF
GPIO_InitStruct1.GPIO_Pin = OV7620_HREF_PIN;
GPIO_InitStruct1.GPIO_InitState = Bit_SET;
GPIO_InitStruct1.GPIO_IRQMode = GPIO_IT_RISING;
GPIO_InitStruct1.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStruct1.GPIOx = OV7620_HREF_PORT;
GPIO_Init(&GPIO_InitStruct1);
// 場中斷 VSYNC
GPIO_InitStruct1.GPIO_Pin = OV7620_VSYNC_PIN;
GPIO_InitStruct1.GPIO_InitState = Bit_SET;
GPIO_InitStruct1.GPIO_IRQMode = GPIO_IT_RISING; //GPIO_IT_RISING
GPIO_InitStruct1.GPIO_Mode = GPIO_Mode_IPD; //GPIO_Mode_IPD
GPIO_InitStruct1.GPIOx = OV7620_VSYNC_PORT;
GPIO_Init(&GPIO_InitStruct1);
//配置DMA
DMA_InitStruct1.Channelx = DMA_CH1; //DMA 1通道
DMA_InitStruct1.PeripheralDMAReq =PORTC_DMAREQ; //C端口(PCLK) 上升呀觸發
DMA_InitStruct1.MinorLoopLength = 170; //傳輸次數 超過攝像頭每行像素數即可
DMA_InitStruct1.TransferBytes = 1; //每次傳輸1個位元組
DMA_InitStruct1.DMAAutoClose = ENABLE; //連續采集
DMA_InitStruct1.EnableState = ENABLE; //初始化後立即采集
DMA_InitStruct1.SourceBaseAddr =(uint32_t)&PTD->PDIR;//攝像頭端口接D0-D7
DMA_InitStruct1.SourceMajorInc = 0; //位址不增加
DMA_InitStruct1.SourceDataSize = DMA_SRC_8BIT; //8BIT資料
DMA_InitStruct1.SourceMinorInc = 0;
DMA_InitStruct1.DestBaseAddr =(uint32_t)DMABuffer; //DMA 記憶體 //uint8_t DMABuffer[400];
DMA_InitStruct1.DestMajorInc = 0;
DMA_InitStruct1.DestDataSize = DMA_DST_8BIT;
DMA_InitStruct1.DestMinorInc = 1; //每次傳輸 +1個位元組
DMA_Init(&DMA_InitStruct1);
}
然後開始編寫場中斷函數,編寫之前我們需要在心裡理一下思緒,在場中斷函數裡我們要按照順序,做以下幾件事情:
A.确認是否是場中斷,确認之後進入處理。
B.清除标志位Flag。(Flag是用來觀察是否處理完一場圖像的标志)
C.清除中斷标志。
D.計數全部清零。(因為新的一場已經開始)
E.打開行中斷,關閉場中斷。
void PORTB_IRQHandler(void)//功 能:PORTB 外部中斷服務 //V
{
u8 i=9;
if((PORTB->ISFR>>i)==1)
{
Flag = 0;
PORTB->ISFR|=(1<<9);
Row = 0;
Row_Num = 0;
NVIC_EnableIRQ(PORTA_IRQn);//行
NVIC_DisableIRQ(PORTB_IRQn);//場
}
接着編寫行中斷函數,在行中斷中,我們要做以下幾件事情:
A.确認是否是行中斷。
B.關閉DMA中斷,防止提前進入PCLK的采集。
C.跳過消隐區。(消隐區:消隐區的出現,在電視機原理上,是因為電子束結束一行掃描,從一行尾換到另一行頭,期間的空閑期,這叫做行消隐信号;同理,從一場尾換到另一場尾,期間也會有空閑期,這叫做場消隐信号。)
D.進入行采集處理。
E.配置DMA,并且打開DMA中斷。
F.行計數加1,表示已經采集完了一行。(因為PCLK的中斷周期遠遠小于HREF的中斷周期,是以不需要杞人憂天,擔心中斷搞得混亂。)
G.當采集完了自己的目标行數之後,标志位Flag修改。并關閉行中斷,打開場中斷,等待下一次的場中斷。
void PORTA_IRQHandler(void)//功 能:PORTA 外部中斷服務//Herf
{
u8 i=14;
DMA_SetEnableReq(DMA_CH1,DISABLE); //close DMA ISr
if((PORTA->ISFR>>i)==1);
{
PORTA->ISFR|=(1<<14);
if(Row_Num++ > 15) //消隐區啦
{
if(Row_Num%5) //進入行采集
{
//配置DMA
DMA_InitStruct1.Channelx = DMA_CH1; //DMA 1通道
DMA_InitStruct1.PeripheralDMAReq =PORTC_DMAREQ; //C端口(PCLK) 上升呀觸發
DMA_InitStruct1.MinorLoopLength = 170; //傳輸次數 超過攝像頭每行像素數即可
DMA_InitStruct1.TransferBytes = 1; //每次傳輸1個位元組
DMA_InitStruct1.DMAAutoClose = ENABLE; //連續采集
DMA_InitStruct1.EnableState = ENABLE; //初始化後立即采集
DMA_InitStruct1.SourceBaseAddr =(uint32_t)&PTD->PDIR;//攝像頭端口接D0-D7
DMA_InitStruct1.SourceMajorInc = 0; //位址不增加
DMA_InitStruct1.SourceDataSize = DMA_SRC_8BIT; //8BIT資料
DMA_InitStruct1.SourceMinorInc = 0;
DMA_InitStruct1.DestBaseAddr =(uint32_t)Image[Row]; //DMA 記憶體 //uint8_t DMABuffer[400];
DMA_InitStruct1.DestMajorInc = 0;
DMA_InitStruct1.DestDataSize = DMA_DST_8BIT;
DMA_InitStruct1.DestMinorInc = 1; //每次傳輸 +1個位元組
DMA_Init(&DMA_InitStruct1);
///
Row ++;
if(Row==MAX_ROW)
{
Flag = 1;
NVIC_DisableIRQ(PORTA_IRQn);//行
NVIC_EnableIRQ(PORTB_IRQn);//場
}
}
}
}
}
最後給大家看一下,DMA的初始化函數,這個函數是超核的庫裡面的,不是我寫的,但是上面的解釋很詳細了,相信都能看懂。
void DMA_Init(DMA_InitTypeDef *DMA_InitStruct)
{
//參數檢查
assert_param(IS_DMA_REQ(DMA_InitStruct->PeripheralDMAReq));
assert_param(IS_DMA_ATTR_SSIZE(DMA_InitStruct->SourceDataSize));
assert_param(IS_DMA_ATTR_DSIZE(DMA_InitStruct->DestDataSize));
assert_param(IS_DMA_CH(DMA_InitStruct->Channelx));
assert_param(IS_DMA_MINOR_LOOP(DMA_InitStruct->MinorLoopLength));
//打開DMA0和DMAMUX時鐘源
SIM->SCGC6 |= SIM_SCGC6_DMAMUX_MASK;
SIM->SCGC7 |= SIM_SCGC7_DMA_MASK;
//配置DMA觸發源
DMAMUX->CHCFG[DMA_InitStruct->Channelx] = DMAMUX_CHCFG_SOURCE(DMA_InitStruct->PeripheralDMAReq);
//設定源位址資訊
DMA0->TCD[DMA_InitStruct->Channelx].SADDR = DMA_InitStruct->SourceBaseAddr;
//執行完源位址操作後,是否在源位址基礎上累加
DMA0->TCD[DMA_InitStruct->Channelx].SOFF = DMA_SOFF_SOFF(DMA_InitStruct->SourceMinorInc);
//設定源位址傳輸寬度
DMA0->TCD[DMA_InitStruct->Channelx].ATTR = 0;
DMA0->TCD[DMA_InitStruct->Channelx].ATTR |= DMA_ATTR_SSIZE(DMA_InitStruct->SourceDataSize);
//主循環進行完後 是否更改源位址
DMA0->TCD[DMA_InitStruct->Channelx].SLAST = DMA_InitStruct->SourceMajorInc;
//設定目的位址資訊
DMA0->TCD[DMA_InitStruct->Channelx].DADDR = DMA_InitStruct->DestBaseAddr;
//執行完源位址操作後,是否在源位址基礎上累加
DMA0->TCD[DMA_InitStruct->Channelx].DOFF = DMA_DOFF_DOFF(DMA_InitStruct->DestMinorInc);
//設定目的位址傳輸寬度
DMA0->TCD[DMA_InitStruct->Channelx].ATTR |= DMA_ATTR_DSIZE(DMA_InitStruct->DestDataSize);
//主循環進行完後 是否更改源位址
DMA0->TCD[DMA_InitStruct->Channelx].DLAST_SGA = DMA_InitStruct->DestMajorInc;
//設定計數器長度 循環次數
//設定資料長度 長度每次遞減 也被稱作目前主循環計數 current major loop count
DMA0->TCD[DMA_InitStruct->Channelx].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(DMA_InitStruct->MinorLoopLength );
//起始循環計數器 當主循環計數器為0 時候 将裝載起始循環計數器的值
DMA0->TCD[DMA_InitStruct->Channelx].BITER_ELINKNO = DMA_BITER_ELINKNO_BITER(DMA_InitStruct->MinorLoopLength);
//設定每一次傳輸位元組的個數 個數到達上限時 DMA便将資料存入RAM
DMA0->TCD[DMA_InitStruct->Channelx].NBYTES_MLNO = DMA_NBYTES_MLNO_NBYTES(DMA_InitStruct->TransferBytes);
//設定DMA TCD控制寄存器
DMA0->TCD[DMA_InitStruct->Channelx].CSR = 0;
if(DMA_InitStruct->DMAAutoClose == ENABLE)
{
DMA0->TCD[DMA_InitStruct->Channelx].CSR |=DMA_CSR_DREQ_MASK;
}
else
{
DMA0->TCD[DMA_InitStruct->Channelx].CSR &=(~DMA_CSR_DREQ_MASK);
}
//使能此寄存器DMA開始工作
DMA_SetEnableReq(DMA_InitStruct->Channelx,DMA_InitStruct->EnableState);
//DMA 通道使能
DMAMUX->CHCFG[DMA_InitStruct->Channelx] |= DMAMUX_CHCFG_ENBL_MASK;
}
Second:
講完OV7620的一些中斷處理函數之後,我們來看看SCCB的庫程式,這個庫可以通用,需要的車友可以直接添加,隻需要對照自己使用的庫,在IO口初始化裡面做出相應的修改即可。
#ifndef __SCCB_H
#define __SCCB_H
#define SCL_HIGH PEout(1) = 1 //設定為輸出後輸出1
#define SCL_LOW PEout(1) = 0 //設定為輸出後輸出0
#define SCL_OUT PTE->PDDR|=(1<<1) //設定為輸出
//#define SCL_DDR_IN() PTE->PDDR&=~(1<<1)//輸入
#define SDA_HIGH PEout(0)= 1 //設定為輸出後輸出1
#define SDA_LOW PEout(0)= 0 //設定為輸出後輸出0
#define SDA_DATA PEin(0)
#define SDA_OUT PTE->PDDR|=(1<<0) //設定為輸出
#define SDA_IN PTE->PDDR&=~(1<<0) //設定為輸入
#define u8 unsigned char
#define u16 unsigned short
//#define ADDR_OV7725 0x42
void sccb_init(void); //初始化SCCB端口為GPIO
void sccb_wait(void); //SCCB時序延時
void sccb_start(void); //起始标志
void sccb_stop(void); //停止标志
u8 sccb_sendByte(u8 data);
void sccb_regWrite(u8 device,u8 address,u8 data);
#endif
#include "sys.h"
#include "gpio.h"
#include "sccb.h"
#include "delay.h"
#include "stdio.h"
/*************************************************************************
* 函數名稱:sccb_init
* 功能說明:初始化SCCB 其中SCL接PE1 SDA接PTE0
*************************************************************************/
void sccb_init(void)
{
int i ;
GPIO_InitTypeDef GPIO_InitStruct1;
for(i=0;i<8;i++)
{
GPIO_InitStruct1.GPIO_Pin = i;
GPIO_InitStruct1.GPIO_InitState = Bit_RESET; //change as Bit_Set , it will shut.
GPIO_InitStruct1.GPIO_IRQMode = GPIO_IT_DISABLE;
GPIO_InitStruct1.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct1.GPIOx = PTD;
GPIO_Init(&GPIO_InitStruct1);
}
GPIO_InitStruct1.GPIO_Pin = 0;
GPIO_InitStruct1.GPIO_InitState = Bit_RESET;
GPIO_InitStruct1.GPIO_IRQMode = GPIO_IT_DISABLE;
GPIO_InitStruct1.GPIO_Mode = GPIO_Mode_OPP;
GPIO_InitStruct1.GPIOx = PTE;
GPIO_Init(&GPIO_InitStruct1);
GPIO_InitStruct1.GPIO_Pin = 1;
GPIO_InitStruct1.GPIO_InitState = Bit_RESET;
GPIO_InitStruct1.GPIO_IRQMode = GPIO_IT_DISABLE;
GPIO_InitStruct1.GPIO_Mode = GPIO_Mode_OPP;
GPIO_InitStruct1.GPIOx = PTE;
GPIO_Init(&GPIO_InitStruct1);
}
/************************************************************************
* 函數名稱:sccb_wait
* 功能說明:SCCB延時,不應太小
*************************************************************************/
void sccb_wait(void)
{
u8 i;
u16 j;
for( i=0; i<100; i++)
{
j++;
}
}
/************************************************************************
* 函數名稱:sccb_start
* 功能說明:SCCB啟動位
*************************************************************************/
void sccb_start(void)
{
SCL_OUT;
SDA_OUT;
SDA_HIGH;
//sccb_wait();
SCL_HIGH;
sccb_wait();
SDA_LOW;
sccb_wait();
SCL_LOW;
}
/************************************************************************
* 函數名稱:sccb_stop
* 功能說明:SCCB停止位
*************************************************************************/
void sccb_stop(void)
{
SCL_OUT;
SDA_OUT;
SDA_LOW;
sccb_wait();
SCL_HIGH;
sccb_wait();
SDA_HIGH;
sccb_wait();
}
/************************************************************************
* 函數名稱:sccb_sendByte
* 功能說明:在SCCB總線上發送一個位元組
* 參數說明:data 要發送的位元組内容
*************************************************************************/
u8 sccb_sendByte(u8 data)
{
u8 i;
u8 ack;
SDA_OUT;
for( i=0; i<8; i++)
{
if(data & 0x80)
SDA_HIGH;
else
SDA_LOW;
data <<= 1;
sccb_wait();
SCL_HIGH;
sccb_wait();
SCL_LOW;
sccb_wait();
}
SDA_HIGH;
SDA_IN;
sccb_wait();
SCL_HIGH;
sccb_wait();
ack = SDA_DATA;
SCL_LOW;
sccb_wait();
return ack;
}
/************************************************************************
* 函數名稱:sccb_regWrite
* 功能說明:通過SCCB總線向指定裝置的指定位址發送指定内容
* 參數說明:device---裝置号 讀寫有差別 42是寫,43是寫
* address---寫資料的寄存器
* data---寫的内容
* 函數傳回:ack=1未收到應答(失敗) ack=0收到應答(成功)
*************************************************************************/
void sccb_regWrite(u8 device,u8 address,u8 data)
{
// u8 i;
u8 ack;
// for( i=0; i<20; i++)
// {
sccb_start();
ack = sccb_sendByte(device);
while( ack )
{
ack = sccb_sendByte(device);
// printf("device\n\r");
}
ack = sccb_sendByte(address);
while( ack )
{
ack = sccb_sendByte(address);;
// printf("address\n\r");
}
ack = sccb_sendByte(data);
while( ack )
{
ack = sccb_sendByte(data);
// printf("data\n\r");
}
sccb_stop();
// if( ack == 0 ) break;
// }
}
貼上使用的SCCB的庫之後,給大家看一下對SCCB的一段執行個體操作程式。程式上有詳細的解釋,我就不贅述了。
sccb_init();
sccb_regWrite(0x42,0x11,0x01); //位址0X11-中斷四分頻(1280*480) PCLK:166ns HREF:254.6us VSYN:133.6ms
sccb_regWrite(0x42,0x14,0x24); //位址0X14-QVGA(320*240) PCLK:332ns HREF:509.6us VSYN:133.6ms
sccb_regWrite(0x42,0x28,0x40); //位址0X28-黑白模式(320*240 PCLK:332ns HREF:127us VSYN:33.6ms
sccb_wait();
以上就是關于OV7620的使用了,看完之後大家是不是會使用了呢。關于後期圖像的處理和調試,我目前正在使用一款智能車調試助手,感覺非常好用,完全免費,并且可以配合Visual Studio,在Visual Studio裡面用C#編寫一些圖像處理的算法,生成dll檔案,然後在調試助手的界面裡面直接觀察。非常好非常好。給大家看看圖。
如果有需要關于OV7620資料或者調試軟體或者有什麼賜教,請留言共同探讨。.
End。2014、03、26