Skip to content

摄像头

摄像头使用的VOFF25摄像头图像采集芯片

和AL422B的FIFO

摄像头的作用就是输出一个图像, 按照输出的模式分为数字摄像头和模拟摄像头, 按照传感构成分为CCD和CMOS, 绝大部分的都是CMOS

数字摄像头可以使用常见的接口进行传输, 模拟摄像头多使用AV视频端子之类线传输

模拟摄像头一般直接把信号输入到显示器

  • CCD和CMOS区别

主要是使用的材料有区别, CCD使用的是MOS管构成像素, 需要比较大的电压, 一般有多个电源, 消耗的能量比较小, 但是起步早, 噪声低, 成像质量好, 但是如今的CMOS经过不断发展解决了大部分的问题, 占据了大部分市场

摄像头包括一个镜头座, 一个可以旋转调节的凸透镜, 通过旋转可以调节焦距, 正常使用的时候光线只能通过镜头传输到传感器, 他采集光线信号之后储存到后面的FIFO里面, 使用VGA时序输出图像, 可以输出YUV(422/420), YCbCr422以及RGB565

image-20230914120258792

image-20230914120315929

  • 通过HREF, VSYNC, PCLK组成一个VGA时序, 再加上D0-9进行数据的输出
  • PCLK: 时序时钟用于传输信号
  • XCLK: 是一个外界时钟的输入, 用于内部的时序, 摄像头自带有一个晶振
  • SCL: SCCB总线的时钟线
  • SDA: SCCB总线的数据线, 主要用来修改内部的寄存器

image-20230914120741160

  1. 控制模块, 使用SCCB时序, 实际上可以使用I2C进行控制
  2. 通讯, 控制信号以及时钟, PCLK, HREF, VSYNC是像素同步时钟, 行同步信号以及帧同步信号, 还有复位引脚, 外部时钟等
  3. 感光矩阵, 模块的核心, 首先产生模拟信号, 之后通过A/D转换器转换为数字信号
  4. 数字输出信号, 根据寄存器配置, 处理之后转换为其他的信号, 从数据线进行输出

SCCB时序

和I2C时序非常相似, 区别在于SCCB每一次只能读取或者写入一个字节数据, 但是测试之后发现还是支持的

这一个摄像头的硬件文档不完整, 直接使用提供的软件配置就行了

像素输出时序

使用VGA时序或者是QVGA, 区别主要是分别率区别, VGA输出的是480*640, QVGA输出的是320*240

可以设置为输出RGB565, 每一次输出完一行的时候会产生一个HREF的信号, 结束一幅画之后HREF会产生VSYNC信号

image-20230914122808760

HREF在高位的时候传输的数据是有效的, 这个是在每一行进行一次跳变, 在时钟每一次高电平的时候进行数据的采样, 低电平的时候会进行改变

image-20230914123046234

HSYNC和HREF都用于输出信号, HREF产生之后马上输出, HSYNC则是在一段时间后再进行输出, ,当 VSYNC 为低电平时,各行的像素数据依次传输,每传输完一帧图像时,VSYNC 会 输出一个电平跳变信号。

FIFO

实际上就是一个缓冲, 一个RAM芯片, 为的是适应这一个性能比较差的芯片, 如果使用的是F4系列的芯片, 一般会外部扩展SRAM, SDRAM等存储器, 并且有DCMI外设, 可以直接处理VGA时序, 存储数据

一帧图像的大小是150kb大于内部的SRAM, 需要一个外部SRAM来存储图像, 扩展使用的FIFO可以缓存两帧的图像

image-20230914192931248

image-20230914192943344

image-20230914194003346

驱动原理

需要协调好FIFO和OV7725之间的关系, 首先存储数据, 之后进行读取

实际控制, stm32在接受到第一个VSYNC信号时候使能写入, 写入一帧之后控制不再使能, 之后进行读取, 读取之后再重复之前的过程, 在使用480*640模式的时候由于FIFO的内存不够需要确保在覆盖数据之前把数据读出来

image-20230914195042494

image-20230914195107623

WEN是一个普通的GPIO进行控制的引脚, 只有在数据有效以及WEN使能的时候才会使能FIFO的写入功能

image-20230914195356040

实际上的软件实现

在这里使用的GPIO视同一组的GPIO, 所以可以直接读取GPIO_IDR寄存器进行获取读取到的状态

image-20230914225329956

PA8由于摄像头使用的是自己的晶振, 所以没有使用

编程要点

  • 初始化SCCB通讯使用的引脚
  • 初始化OV7725和VGA和FIFO的控制相关的引脚以及时钟
  • 使用SCCB协议向其写入初始化配置
  • 编写测试程序
  • 使用SCCB的时候的程序基本和I2C相同
c
/**
  ******************************************************************************
  * @file    bsp_sccb.c
  * @version V1.0
  * @date    2013-xx-xx
  * @brief   模拟I2C SCCB协议驱动
  ******************************************************************************
  * @attention
  *
  * 实验平台:野火 F103-指南者 STM32 开发板 
  * 论坛    :http://www.firebbs.cn
  * 淘宝    :https://fire-stm32.taobao.com
  *
  ******************************************************************************
  */ 

#include "./sccb/bsp_sccb.h"

#define DEV_ADR  ADDR_OV7725 			 /*设备地址定义*/

/********************************************************************
 * 函数名:SCCB_Configuration
 * 描述  :SCCB管脚配置
 * 输入  :无
 * 输出  :无
 * 注意  :无        
 ********************************************************************/
void SCCB_GPIO_Config(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure; 
	
	
  /* SCL(PC6)、SDA(PC7)管脚配置 */
	OV7725_SIO_C_SCK_APBxClock_FUN ( OV7725_SIO_C_GPIO_CLK, ENABLE );
  GPIO_InitStructure.GPIO_Pin =  OV7725_SIO_C_GPIO_PIN ;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  
  GPIO_Init(OV7725_SIO_C_GPIO_PORT, &GPIO_InitStructure);
	
	OV7725_SIO_D_SCK_APBxClock_FUN ( OV7725_SIO_D_GPIO_CLK, ENABLE );
  GPIO_InitStructure.GPIO_Pin =  OV7725_SIO_D_GPIO_PIN ;
  GPIO_Init(OV7725_SIO_D_GPIO_PORT, &GPIO_InitStructure);
	
}

/********************************************************************
 * 函数名:SCCB_delay
 * 描述  :延迟时间
 * 输入  :无
 * 输出  :无
 * 注意  :内部调用        
 ********************************************************************/
static void SCCB_delay(void)
{	
   uint16_t i = 400; 
   while(i) 
   { 
     i--; 
   } 
}

/********************************************************************
 * 函数名:SCCB_Start
 * 描述  :SCCB起始信号
 * 输入  :无
 * 输出  :无
 * 注意  :内部调用        
 ********************************************************************/
static int SCCB_Start(void)
{
	SDA_H;
	SCL_H;
	SCCB_delay();
	if(!SDA_read)
	return DISABLE;	/* SDA线为低电平则总线忙,退出 */
	SDA_L;
	SCCB_delay();
	if(SDA_read) 
	return DISABLE;	/* SDA线为高电平则总线出错,退出 */
	SDA_L;
	SCCB_delay();
	return ENABLE;
}



/********************************************************************
 * 函数名:SCCB_Stop
 * 描述  :SCCB停止信号
 * 输入  :无
 * 输出  :无
 * 注意  :内部调用        
 ********************************************************************/
static void SCCB_Stop(void)
{
	SCL_L;
	SCCB_delay();
	SDA_L;
	SCCB_delay();
	SCL_H;
	SCCB_delay();
	SDA_H;
	SCCB_delay();
}



/********************************************************************
 * 函数名:SCCB_Ack
 * 描述  :SCCB应答方式
 * 输入  :无
 * 输出  :无
 * 注意  :内部调用        
 ********************************************************************/
static void SCCB_Ack(void)
{	
	SCL_L;
	SCCB_delay();
	SDA_L;
	SCCB_delay();
	SCL_H;
	SCCB_delay();
	SCL_L;
	SCCB_delay();
}



/********************************************************************
 * 函数名:SCCB_NoAck
 * 描述  :SCCB 无应答方式
 * 输入  :无
 * 输出  :无
 * 注意  :内部调用        
 ********************************************************************/
static void SCCB_NoAck(void)
{	
	SCL_L;
	SCCB_delay();
	SDA_H;
	SCCB_delay();
	SCL_H;
	SCCB_delay();
	SCL_L;
	SCCB_delay();
}

/********************************************************************
 * 函数名:SCCB_WaitAck
 * 描述  :SCCB 等待应答
 * 输入  :无
 * 输出  :返回为:=1有ACK,=0无ACK
 * 注意  :内部调用        
 ********************************************************************/
static int SCCB_WaitAck(void) 	
{
	SCL_L;
	SCCB_delay();
	SDA_H;			
	SCCB_delay();
	SCL_H;
	SCCB_delay();
	if(SDA_read)
	{
	 //没有获取到读信号
      SCL_L;
      return DISABLE;
	}
	SCL_L;
	return ENABLE;
}



 /*******************************************************************
 * 函数名:SCCB_SendByte
 * 描述  :数据从高位到低位
 * 输入  :SendByte: 发送的数据
 * 输出  :无
 * 注意  :内部调用        
 *********************************************************************/
static void SCCB_SendByte(uint8_t SendByte) 
{
    uint8_t i=8;
    while(i--)
    {
		//在时钟信号为低电平的时候进行信号线的选择
        SCL_L;
        SCCB_delay();
      if(SendByte&0x80)
        SDA_H;  
      else 
        SDA_L;
        SendByte<<=1;
        SCCB_delay();
		SCL_H;
        SCCB_delay();
    }
    SCL_L;
}


 /******************************************************************
 * 函数名:SCCB_ReceiveByte
 * 描述  :数据从高位到低位
 * 输入  :无
 * 输出  :SCCB总线返回的数据
 * 注意  :内部调用        
 *******************************************************************/
static int SCCB_ReceiveByte(void)  
{ 
    uint8_t i=8;
    uint8_t ReceiveByte=0;
	//释放数据线
    SDA_H;				
    while(i--)
    {
      ReceiveByte<<=1;      
      SCL_L;
      SCCB_delay();
	  SCL_H;
      SCCB_delay();	
      if(SDA_read)
      {
        ReceiveByte|=0x01;
      }
    }
    SCL_L;
    return ReceiveByte;
}





 /*****************************************************************************************
 * 函数名:SCCB_WriteByte
 * 描述  :写一字节数据
 * 输入  :- WriteAddress: 待写入地址 	- SendByte: 待写入数据	- DeviceAddress: 器件类型
 * 输出  :返回为:=1成功写入,=0失败
 * 注意  :无        
 *****************************************************************************************/           
int SCCB_WriteByte( uint16_t WriteAddress , uint8_t SendByte )
{		
    if(!SCCB_Start())
	{
	    return DISABLE;
	}
    SCCB_SendByte( DEV_ADR );                    /* 器件地址 */
    
    if( !SCCB_WaitAck() )
	{
		SCCB_Stop(); 
		return DISABLE;
	}
    SCCB_SendByte((uint8_t)(WriteAddress & 0x00FF));   /* 设置低起始地址 */      
    SCCB_WaitAck();	
    SCCB_SendByte(SendByte);
    SCCB_WaitAck();   
    SCCB_Stop(); 
    return ENABLE;
}

/******************************************************************************************************************
 * 函数名:SCCB_ReadByte
 * 描述  :读取一串数据
 * 输入  :- pBuffer: 存放读出数据 	- length: 待读出长度	- ReadAddress: 待读出地址		 - DeviceAddress: 器件类型
 * 输出  :返回为:=1成功读入,=0失败
 * 注意  :无        
 **********************************************************************************************************************/           
int SCCB_ReadByte(uint8_t* pBuffer, uint16_t length, uint8_t ReadAddress)
{	
    if(!SCCB_Start())
	{
	    return DISABLE;
	}
    SCCB_SendByte( DEV_ADR );         /* 器件地址 */
    if( !SCCB_WaitAck() )
	{
		SCCB_Stop(); 
		return DISABLE;
	}
    SCCB_SendByte( ReadAddress );     /* 设置低起始地址 */      
    SCCB_WaitAck();	
    SCCB_Stop(); 
	
    if(!SCCB_Start())
	{
		return DISABLE;
	}
    SCCB_SendByte( DEV_ADR + 1 );     /* 器件地址 */ 
    if(!SCCB_WaitAck())
	{
		SCCB_Stop(); 
		return DISABLE;
	}
    while(length)
    {
      *pBuffer = SCCB_ReceiveByte();
      if(length == 1)
	  {
		  SCCB_NoAck();
	  }
      else
	  {
		SCCB_Ack(); 
	  }
      pBuffer++;
      length--;
    }
    SCCB_Stop();
    return ENABLE;
}
/*********************************************END OF FILE**********************/
  • 初始化各种引脚

在初始FIFO的时候使得允许读但是允许写, 这里的WE不是FIFO芯片的WE在之前还有一个与非门

在初始化VSYNC的时候设置一个VSYNC信号的时候会产生一个中断, 也就是每一幅画结束的时候会产生一个中断

使用的是EXTI_LINE3

  • 初始化OV7725
c
/************************************************
 * 函数名:Sensor_Init
 * 描述  :Sensor初始化, 写入寄存机, 重复五次
 * 输入  :无
 * 输出  :返回1成功,返回0失败
 * 注意  :无
 ************************************************/
ErrorStatus OV7725_Init(void)
{
	uint16_t i = 0;
	uint8_t Sensor_IDCode = 0;	
	
	//DEBUG("ov7725 Register Config Start......");
	
	if( 0 == SCCB_WriteByte ( 0x12, 0x80 ) ) /*复位sensor */
	{
		//DEBUG("sccb write data error");		
		return ERROR ;
	}	

	if( 0 == SCCB_ReadByte( &Sensor_IDCode, 1, 0x0b ) )	 /* 读取sensor ID号*/
	{
		//DEBUG("read id faild");		
		return ERROR;
	}
	//DEBUG("Sensor ID is 0x%x", Sensor_IDCode);	
	
	if(Sensor_IDCode == OV7725_ID)
	{
		for( i = 0 ; i < OV7725_REG_NUM ; i++ )
		{
            //这一个结构体数组, 里面的每一项保存有地址和数据两个数据
			if( 0 == SCCB_WriteByte(Sensor_Config[i].Address, Sensor_Config[i].Value) )
			{                
				//DEBUG("write reg faild", Sensor_Config[i].Address);
				return ERROR;
			}
		}
	}
	else
	{
		return ERROR;
	}
	//DEBUG("ov7725 Register Config Success");
	
	return SUCCESS;
}
c
typedef struct Reg
{
	uint8_t Address;			       /*寄存器地址*/
	uint8_t Value;		           /*寄存器值*/
}Reg_Info;

存放寄存器数据的寄存器

  • 实现中断函数
c
/* ov7725 场中断 服务程序 */
void OV7725_VSYNC_EXTI_INT_FUNCTION ( void )
{
    if ( EXTI_GetITStatus(OV7725_VSYNC_EXTI_LINE) != RESET ) 	//检查EXTI_Line0线路上的中断请求是否发送到了NVIC 
    {
        //判断进来多少次了
        if( Ov7725_vsync == 0 )
        {
            FIFO_WRST_L(); 	                      //拉低使FIFO写(数据from摄像头)指针复位
            FIFO_WE_H();	                        //拉高使FIFO写允许
            
            Ov7725_vsync = 1;	   	
            FIFO_WE_H();                          //使FIFO写允许
            FIFO_WRST_H();                        //允许使FIFO写(数据from摄像头)指针运动
        }
        else if( Ov7725_vsync == 1 )
        {
            //写入一次以后,这时候已经记录完成一次图像了
            FIFO_WE_L();                          //拉低使FIFO写暂停
            Ov7725_vsync = 2;
        }        
        EXTI_ClearITPendingBit(OV7725_VSYNC_EXTI_LINE);		    //清除EXTI_Line0线路挂起标志位        
    }    
}
c
while(1)
	{
		/*接收到新图像进行显示, 在中断里面获取到的*/
		if( Ov7725_vsync == 2 )
		{
			frame_count++;	//记录每一秒钟采样的数量, 使用系统时钟完成
			FIFO_PREPARE;  			/*FIFO准备*/					
			ImagDisp(cam_mode.lcd_sx,
								cam_mode.lcd_sy,
								cam_mode.cam_width,
								cam_mode.cam_height);			/*采集并显示*/
			
			Ov7725_vsync = 0;			
			LED1_TOGGLE;

		}
		
		/*检测按键*/
		if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON  )
		{
			/*LED反转*/
			LED2_TOGGLE;

		} 
		/*检测按键*/
		if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON  )
		{
			/*LED反转*/
			LED3_TOGGLE;			
			
				/*动态配置摄像头的模式,
			    有需要可以添加使用串口、用户界面下拉选择框等方式修改这些变量,
			    达到程序运行时更改摄像头模式的目的*/
			
				cam_mode.QVGA_VGA = 0,	//QVGA模式
				cam_mode.cam_sx = 0,
				cam_mode.cam_sy = 0,	

				cam_mode.cam_width = 320,
				cam_mode.cam_height = 240,

				cam_mode.lcd_sx = 0,
				cam_mode.lcd_sy = 0,
				cam_mode.lcd_scan = 3, //LCD扫描模式,本横屏配置可用1、3、5、7模式

				//以下可根据自己的需要调整,参数范围见结构体类型定义	
				cam_mode.light_mode = 0,//自动光照模式
				cam_mode.saturation = 0,	
				cam_mode.brightness = 0,
				cam_mode.contrast = 0,
				cam_mode.effect = 1,		//黑白模式
			
			/*根据摄像头参数写入配置*/
			OV7725_Special_Effect(cam_mode.effect);
			/*光照模式*/
			OV7725_Light_Mode(cam_mode.light_mode);
			/*饱和度*/
			OV7725_Color_Saturation(cam_mode.saturation);
			/*光照度*/
			OV7725_Brightness(cam_mode.brightness);
			/*对比度*/
			OV7725_Contrast(cam_mode.contrast);
			/*特殊效果*/
			OV7725_Special_Effect(cam_mode.effect);
			
			/*设置图像采样及模式大小*/
			OV7725_Window_Set(cam_mode.cam_sx,
																cam_mode.cam_sy,
																cam_mode.cam_width,
																cam_mode.cam_height,
																cam_mode.QVGA_VGA);

			/* 设置液晶扫描模式 */
			ILI9341_GramScan( cam_mode.lcd_scan );
		}
		
		/*每隔一段时间计算一次帧率*/
		if(Task_Delay[0] == 0)  
		{			
			printf("\r\nframe_ate = %.2f fps\r\n",frame_count/10);
			frame_count = 0;
			Task_Delay[0] = 10000;
		}
		
	}
c
#define FIFO_PREPARE                do{\
	                                  FIFO_RRST_L();\
	                                  FIFO_RCLK_L();\
	                                  FIFO_RCLK_H();\
	                                  FIFO_RRST_H();\
	                                  FIFO_RCLK_L();\
	                                  FIFO_RCLK_H();\
                                    }while(0)

进行读指针复位, 经历两个时钟, 之后才可以进行读取

c
	/*根据摄像头参数组配置模式*/
	OV7725_Special_Effect(cam_mode.effect);
	/*光照模式*/
	OV7725_Light_Mode(cam_mode.light_mode);
	/*饱和度*/
	OV7725_Color_Saturation(cam_mode.saturation);
	/*光照度*/
	OV7725_Brightness(cam_mode.brightness);
	/*对比度*/
	OV7725_Contrast(cam_mode.contrast);
	/*特殊效果*/
	OV7725_Special_Effect(cam_mode.effect);
	
	/*设置图像采样及模式大小,设置显示的位置, 需要在设置的模式范围*/
	OV7725_Window_Set(cam_mode.cam_sx,
														cam_mode.cam_sy,
														cam_mode.cam_width,
														cam_mode.cam_height,
														cam_mode.QVGA_VGA);