Appearance
摄像头
摄像头使用的VOFF25摄像头图像采集芯片
和AL422B的FIFO
摄像头的作用就是输出一个图像, 按照输出的模式分为数字摄像头和模拟摄像头, 按照传感构成分为CCD和CMOS, 绝大部分的都是CMOS
数字摄像头可以使用常见的接口进行传输, 模拟摄像头多使用AV视频端子之类线传输
模拟摄像头一般直接把信号输入到显示器
- CCD和CMOS区别
主要是使用的材料有区别, CCD使用的是MOS管构成像素, 需要比较大的电压, 一般有多个电源, 消耗的能量比较小, 但是起步早, 噪声低, 成像质量好, 但是如今的CMOS经过不断发展解决了大部分的问题, 占据了大部分市场
摄像头包括一个镜头座, 一个可以旋转调节的凸透镜, 通过旋转可以调节焦距, 正常使用的时候光线只能通过镜头传输到传感器, 他采集光线信号之后储存到后面的FIFO里面, 使用VGA时序输出图像, 可以输出YUV(422/420), YCbCr422以及RGB565
- 通过HREF, VSYNC, PCLK组成一个VGA时序, 再加上D0-9进行数据的输出
- PCLK: 时序时钟用于传输信号
- XCLK: 是一个外界时钟的输入, 用于内部的时序, 摄像头自带有一个晶振
- SCL: SCCB总线的时钟线
- SDA: SCCB总线的数据线, 主要用来修改内部的寄存器
- 控制模块, 使用SCCB时序, 实际上可以使用I2C进行控制
- 通讯, 控制信号以及时钟, PCLK, HREF, VSYNC是像素同步时钟, 行同步信号以及帧同步信号, 还有复位引脚, 外部时钟等
- 感光矩阵, 模块的核心, 首先产生模拟信号, 之后通过A/D转换器转换为数字信号
- 数字输出信号, 根据寄存器配置, 处理之后转换为其他的信号, 从数据线进行输出
SCCB时序
和I2C时序非常相似, 区别在于SCCB每一次只能读取或者写入一个字节数据, 但是测试之后发现还是支持的
这一个摄像头的硬件文档不完整, 直接使用提供的软件配置就行了
像素输出时序
使用VGA时序或者是QVGA, 区别主要是分别率区别, VGA输出的是480*640, QVGA输出的是320*240
可以设置为输出RGB565, 每一次输出完一行的时候会产生一个HREF的信号, 结束一幅画之后HREF会产生VSYNC信号
HREF在高位的时候传输的数据是有效的, 这个是在每一行进行一次跳变, 在时钟每一次高电平的时候进行数据的采样, 低电平的时候会进行改变
HSYNC和HREF都用于输出信号, HREF产生之后马上输出, HSYNC则是在一段时间后再进行输出, ,当 VSYNC 为低电平时,各行的像素数据依次传输,每传输完一帧图像时,VSYNC 会 输出一个电平跳变信号。
FIFO
实际上就是一个缓冲, 一个RAM芯片, 为的是适应这一个性能比较差的芯片, 如果使用的是F4系列的芯片, 一般会外部扩展SRAM, SDRAM等存储器, 并且有DCMI外设, 可以直接处理VGA时序, 存储数据
一帧图像的大小是150kb大于内部的SRAM, 需要一个外部SRAM来存储图像, 扩展使用的FIFO可以缓存两帧的图像
驱动原理
需要协调好FIFO和OV7725之间的关系, 首先存储数据, 之后进行读取
实际控制, stm32在接受到第一个VSYNC信号时候使能写入, 写入一帧之后控制不再使能, 之后进行读取, 读取之后再重复之前的过程, 在使用480*640模式的时候由于FIFO的内存不够需要确保在覆盖数据之前把数据读出来
WEN是一个普通的GPIO进行控制的引脚, 只有在数据有效以及WEN使能的时候才会使能FIFO的写入功能
实际上的软件实现
在这里使用的GPIO视同一组的GPIO, 所以可以直接读取GPIO_IDR寄存器进行获取读取到的状态
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);