sensor研究(一)——基础知识

文章推荐:见附录一

http://www.tuicool.com/articles/mYnqeuv

行消隐和场消隐

在将光信号转换为电信号的扫描过程中,扫描总是从图像的左上角开始,水平向前行进,同时扫描点也以较慢的速率向下移动。当扫描点到达图像右侧边缘时,扫描点快速返回左侧,重新开始在第1行的起点下面进行第2行扫描,行与行之间的返回过程称为水平消隐。一幅完整的图像扫描信号,由水平消隐间隔分开的行信号序列构成,称为一帧。扫描点扫描完一帧后,要从图像的右下角返回到图像的左上角,开始新一帧的扫描,这一时间间隔,叫做垂直消隐,也称场消隐(VBlank)。




附录一:

转载请注明出处:http://blog.csdn.net/lxk7280

智能车摄像头组的初期学习中,虽然有不少摄像头优于OV7620,但是相信大部分的车友第一个接触的都是OV7620。下面 从其特性和性能等角度,剖析摄像头的特点。

摄像头的输出格 式有R GB565,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 个字节)输出的格式。

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时序分析。 
。 


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资料或者调试软件或者有什么赐教的,可以留言或者联系我的QQ416815882.

End。2014、03、26


  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值