OV2640是OV (OmniVision)公司生产的- -颗1/4 寸的CMOS UXGA (1632*1232) 图像 传感器。传感器体积小,工作电压低,提供单片UXGA相机和图像处理器的所有功能。 过SCCB总线控制,各种分辨率8/10的图像,如输出整帧、子采样、缩放和取窗等 数据UXGA最高图像15帧/秒(SVGA可达30帧,CIF 可达60帧)。用户可以完成 全面控制图像质量、数据格式和传输模式。所有图像处理功能包括伽玛曲线、白平衡、对比 度、色度等都可以通过SCCB接口编程。OmmiVision 图像传感器采用独特的传感器技术 过度减少或消除固定图案噪声、拖尾、浮散等光学或电子缺陷,提高图像质量,获得清晰稳定 定色图像。
● 高灵 低电压适用于嵌入式应用 ● 标准的SCCB接口,兼容IIC接口 ● 支持RawRGB、RGB(RGB565/RGB555). GRB422、YUV(422/420)和YCbCr (422) 输出格式 ● 支持UXGA、SXGA、SVGA并按比例缩小到从SXGA到40*30的任何尺寸 ● 支持自动曝光控制、自动增益控制、自动白平衡、自动消除灯光条纹、自动黑电平校准 准等自动控制功能。支持色饱和度、色相、伽马、锐度等设置。 ● 支持闪光灯 ● 支持图像缩放、平移和窗口设置 ● 支持图像压缩输出JPEG图像数据. ● 自带嵌入式微处理器
OV2640传感器采用BGA包装,前端是照明窗,引脚在背面,引脚的分布如下图所示
图中的非彩色部分是与电源相关的引脚,颜色部分是主要的信号引脚,如下表所示451
下面我们配合图453中的OV2640功能框图解释这些信号引脚。
标号处的是OV根据这些寄存器配置的参数运行2640控制寄存器,这些参数由外部控制器通过SIO_C和SIO_D引脚写入,SIO_C与SIO_D跟踪使用的通信协议I2C十分类似,在STM我们可以直接在32中学I2C控制硬件外设。
包括标号OV2640通信、控制信号和外部时钟PCLK、HREF及VSYNC它们是像素同步时钟、行同步信号和帧同步信号,与液晶屏控制中的信号非常相似。RESETB引脚为低电平时复位整个传感器芯片,PWDN控制芯片进入低功耗模式。注意最后一个XCLK引脚,它跟PCLK完全不同,XCLK时钟信号用于驱动整个传感器芯片,外部输入OV2640的信号;而PCLK是OV2640输出数据时的同步信号,它是由OV2640输出信号。XCLK可由外部控制器提供外部晶振或类比XCLK之于OV2640就相当于HSE时钟输入引脚和STM32芯片关系,PCLK引脚可类比STM32的I2C外设的SCL引脚。
标号是感光矩阵,光信号在这里转换成电信号,经过各种处理,这些信号存储成由像素点表示的数字图像。
包括标号DSP根据控制寄存器的配置,对处理单元进行一些基本的图像处理操作。这部分还包括图像格式转换单元和压缩单元,转换数据最终通过Y0-Y9引脚输出,一般来说,我们使用8根据数据线传输,此时只使用Y2-Y9引脚,OV2640与外部设备的连接见上图
一、SCCB总线
对外部控制器OV通过2640寄存器的配置参数SCCB总线传输过去的,而SCCB总线跟I2C非常相似,所以在STM我们在32驱动器上直接使用电影I2C外设与它通信。SCCB与标准的I2C协议的区别在于,它每次只能写入或读取字节数据,因为SCCB最重要的是阉割IIC连续读写功能,即主机每读写一个字节后必须发送一个NA信号,而I2C该协议支持突然读写,即多个字节的数据可以写在一次传输中(EEPROM中页写入时序即突然写入)。关于SCCB协议的完整内容可自行百度搜索查看,下面我们简单介绍下。
1.1、SCCB简介
SCCB欧姆尼图像技术公司(OmniVision)应用于开发的总线OV因此,一般使用系列图像传感器OV图像传感器离不开SCCB总线协议。通俗地说SCCB有两种工作模式,一主多从,一主一从。
即三线操作:(通过控制使能端SCCB_E控制选定的从机)
也就是说,二线操作:(默认:SCCB_E被拉低)
1.2、SCCB管脚定义
SCCB | IIC | 方向 | 描述 |
SIO_E | \ | 主机发出 | 低电平有效,主机在总线空闲时将此引脚驱动为1,驱动为0时开始传输或悬挂模式 |
SIO_C | SCL | 主机发出 | 总线空闲时主机驱动此引脚为1; 当驱动SIO_E为0时,主机驱动此引脚为0或1;当挂起时主机驱动SIO_C为0; SIO_D只能在SIO_C为0时发生变化。 |
SIO_D | SDA | 双向传输 | 当总线空闲时保持浮动,状态不固定(0、1或高阻态)相当于数据位! |
1.3、SCCB时序
SCCB_E为低电平时传输有效,SIO_C为高电平时SIO_D读取数据(SIO_C为低电平时SIO_D改变)
1.3.1、开始传输的时序
开始传输开始于SCCB_E下降沿。在SCCB_E下降沿前,主机必须将SIO_D置1,这样可以避免读取传送之前产生的未知总线状态。 在SCCB_E下降沿之前,SIO_D必须有tPRC时长的高电平,tPRC至少要15ns tPRA是SCCB_E下降沿之后SIO_D保持高电平的时间,tPRA至少要1.25us
//SCCB起始信号
//当时钟为高的时候,数据线的高到低,为SCCB起始信号
//在激活状态下,SDA和SCL均为低电平
void SCCB_Start(void)
{
SCCB_SDA_OUT(); //数据线为输出模式
SCCB_SDA=1; //数据线高电平
SCCB_SCL=1; //在时钟高的时候数据线由高至低
delay_us(50);
SCCB_SDA=0; //在时钟高的时候数据线由高至低
delay_us(50);
SCCB_SCL=0; //数据线恢复低电平,单操作函数必要
delay_us(50);
}
1.3.2、结束传输的时序
SCCB_E上升沿时表示结束传输,两个时间规定为tPSA和tPSC tPSC为SCCB_E上升沿之后SIO_D维持高电平的时间,tPSC至少15ns tPSA为SCCB_E之前SIO_D高电平的时间,tPSA至少0ns
//SCCB停止信号
//当时钟为高的时候,数据线的低到高,为SCCB停止信号
//空闲状况下,SDA,SCL均为高电平
void SCCB_Stop(void)
{
SCCB_SDA_OUT(); //数据线为输出模式
SCCB_SDA=0; //数据线低电平
delay_us(50);
SCCB_SCL=1;
delay_us(50);
SCCB_SDA=1;
delay_us(50);
}
1.3.3、产生NA时序(用于读数据)
//SCCB_D先拉高,再把SCCB_C拉高,后把SCCB_C拉低,最后把SCCB_D拉低
void SCCB_No_Ack(void)
{
SCCB_SDA_OUT();
delay_us(50);
SCCB_SDA=1;
SCCB_SCL=1;
delay_us(50);
SCCB_SCL=0;
delay_us(50);
SCCB_SDA=0;
delay_us(50);
}
1.3.4、读时序
//SCCB 读取一个字节
//在SCL的上升沿,数据锁存
//返回值:读到的数据
u8 SCCB_RD_Byte(void)
{
u8 temp=0,j;
SCCB_SDA_IN(); //设置SDA为输入
for(j=8;j>0;j--) //循环8次接收数据
{
delay_us(50);
SCCB_SCL=1;
temp=temp<<1;
if(SCCB_READ_SDA)temp++;
delay_us(50);
SCCB_SCL=0;
}
SCCB_SDA_OUT(); //设置SDA为输出
return temp;
}
1.3.5、写时序
//SCCB,写入一个字节
//返回值:0,成功;1,失败.
u8 SCCB_WR_Byte(u8 dat)
{
u8 j,res;
for(j=0;j<8;j++) //循环8次发送数据
{
if(dat&0x80)SCCB_SDA=1; //高位先发送
else SCCB_SDA=0;
dat<<=1;
delay_us(50);
SCCB_SCL=1; //高电平时写入
delay_us(50);
SCCB_SCL=0; //低电平时 改变SDA
}
SCCB_SDA_IN(); //设置SDA为输入
delay_us(50);
SCCB_SCL=1; //接收第九位,以判断是否发送成功
delay_us(50);
if(SCCB_READ_SDA)res=1; //SDA=1发送失败,返回1
else res=0; //SDA=0发送成功,返回0
SCCB_SCL=0;
SCCB_SDA_OUT(); //设置SDA为输出
return res;
}
1.4、SCCB传输规则
一个基本传输单元称作一个相 一个相包含总共9比特,前8比特为数据,第9比特为 Don’t-Care bit (不关心比特),该第9比特的数据取决于传输任务是读还是写如果是写操作则don’t care,如果为读操作为NA。一个传输任务的最大相个数是3。 总结如下: 每一个单元组成:8位数据+don’t care/NA 如果是主机发送数据,即进行写操作,第九位就为don’t care(不关心比特) 如果是从机发送数据,即为读操作,第九位就为NA.
相1:主机向从机发送从机的ID号,SCCB协议支持一个主机和多个从机,因此这一个相目的是区分不同的从机,但如果我们只连接了一个从机时,也必须执行这样一个流程。实际上ID Address有8bit,其中bit7-bit1为从机的ID号,大小为0-127,一共能区分128个从机。OV2640的ID号为0x60。而bit0是用来区分对从机是写数据还是读数据,bit0=0代表写数据,bit0=1代表读数据,由于我们要向从机写数据,因此bit0应为0(0x60=01100000写入数据)。而相1中紧跟在ID Address这8位数据后的第九位是一个Don’t care bit(图中打X的位)。对于OV2640来说,从机在接收到主机送来的8bit数据后,将在SCL=1的期间,在SDA引脚输出低电平。在这期间,主机就可以读取SDA上的电平并进行判断,如果读取到低电平,表示从机已经顺利接收到了相1中的前8bit数据。说明数据传输成功,否则说明传输失败。
相2:主机向从机发送将要写入数据的寄存器的编号,寄存器的编号在OV传感器的数据手册上都能找到。寄存器的编号是一个8bit的数据。同样地,相2的第9bit也是一个Don’t care bit(图中打X的位),对该位的说明与相1相同 相3:前面两个相指定了数据传输的从机ID以及要写入数据的寄存器的编号,这时候在第三个相就可以向前面指定的寄存器写入数据了。bit7-bit0是我们希望写入寄存器的数据。而第9bit也是一个Don’t care bit(图中打X的位),对该位的说明与相1相同。 虽然每个相写入的数据不同,但其时序都是相同的,并且第九bit都是Don’t care bit
1.4.1、写寄存器值
写寄存器分三个阶段:写器件地址,写寄存器地址,写数据
//写寄存器
//返回值:0,成功;1,失败.
u8 SCCB_WR_Reg(u8 reg,u8 data)
{
u8 res=0;
SCCB_Start(); //启动SCCB传输
if(SCCB_WR_Byte(SCCB_ID))res=1; //写器件ID
delay_us(100);
if(SCCB_WR_Byte(reg))res=1; //写寄存器地址
delay_us(100);
if(SCCB_WR_Byte(data))res=1; //写数据
SCCB_Stop();
return res;
}
1.4.2、读寄存器值
读寄存器分两次两个阶段
第一个阶段:写器件地址,写要读的寄存器地址 第二个阶段:写器件地址+1(表示读命令),读取数据,最后在发送NA信号
//读寄存器
//返回值:读到的寄存器值
u8 SCCB_RD_Reg(u8 reg)
{
u8 val=0;
SCCB_Start(); //启动SCCB传输
SCCB_WR_Byte(SCCB_ID); //写器件ID
delay_us(100);
SCCB_WR_Byte(reg); //写寄存器地址
delay_us(100);
SCCB_Stop();
delay_us(100);
//设置寄存器地址后,才是读
SCCB_Start();
SCCB_WR_Byte(SCCB_ID|0X01); //发送读命令
delay_us(100);
val=SCCB_RD_Byte(); //读取数据
SCCB_No_Ack();
SCCB_Stop();
return val;
}
二、SCCB代码部分
2.1、sccb.c
#include "sys.h"
#include "sccb.h"
#include "delay.h"
//初始化SCCB接口
void SCCB_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);//使能GPIOD时钟
//GPIOF9,F10初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;//PD6,7 推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //PD6,7 推挽输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化
GPIO_SetBits(GPIOD,GPIO_Pin_6|GPIO_Pin_7);
}
//SCCB起始信号
//当时钟为高的时候,数据线的高到低,为SCCB起始信号
//在激活状态下,SDA和SCL均为低电平
void SCCB_Start(void)
{
SCCB_SDA_OUT();
SCCB_SDA=1; //数据线高电平
SCCB_SCL=1; //在时钟线高的时候数据线由高至低
delay_us(50);
SCCB_SDA=0;
delay_us(50);
SCCB_SCL=0; //数据线恢复低电平,单操作函数必要
}
//SCCB停止信号
//当时钟为高的时候,数据线的低到高,为SCCB停止信号
//空闲状况下,SDA,SCL均为高电平
void SCCB_Stop(void)
{
SCCB_SDA_OUT();
SCCB_SDA=0;
delay_us(50);
SCCB_SCL=1;
delay_us(50);
SCCB_SDA=1;
delay_us(50);
}
//产生NA信号
void SCCB_No_Ack(void)
{
delay_us(50);
SCCB_SDA=1;
SCCB_SCL=1;
delay_us(50);
SCCB_SCL=0;
delay_us(50);
SCCB_SDA=0;
delay_us(50);
}
//SCCB,写入一个字节
//返回值:0,成功;1,失败.
u8 SCCB_WR_Byte(u8 dat)
{
u8 j,res;
for(j=0;j<8;j++) //循环8次发送数据
{
if(dat&0x80)SCCB_SDA=1;
else SCCB_SDA=0;
dat<<=1;
delay_us(50);
SCCB_SCL=1;
delay_us(50);
SCCB_SCL=0;
}
SCCB_SDA_IN(); //设置SDA为输入
delay_us(50);
SCCB_SCL=1; //接收第九位,以判断是否发送成功
delay_us(50);
if(SCCB_READ_SDA)res=1; //SDA=1发送失败,返回1
else res=0; //SDA=0发送成功,返回0
SCCB_SCL=0;
SCCB_SDA_OUT(); //设置SDA为输出
return res;
}
//SCCB 读取一个字节
//在SCL的上升沿,数据锁存
//返回值:读到的数据
u8 SCCB_RD_Byte(void)
{
u8 temp=0,j;
SCCB_SDA_IN(); //设置SDA为输入
for(j=8;j>0;j--) //循环8次接收数据
{
delay_us(50);
SCCB_SCL=1;
temp=temp<<1;
if(SCCB_READ_SDA)temp++;
delay_us(50);
SCCB_SCL=0;
}
SCCB_SDA_OUT(); //设置SDA为输出
return temp;
}
//写寄存器
//返回值:0,成功;1,失败.
u8 SCCB_WR_Reg(u8 reg,u8 data)
{
u8 res=0;
SCCB_Start(); //启动SCCB传输
if(SCCB_WR_Byte(SCCB_ID))res=1; //写器件ID
delay_us(100);
if(SCCB_WR_Byte(reg))res=1; //写寄存器地址
delay_us(100);
if(SCCB_WR_Byte(data))res=1; //写数据
SCCB_Stop();
return res;
}
//读寄存器
//返回值:读到的寄存器值
u8 SCCB_RD_Reg(u8 reg)
{
u8 val=0;
SCCB_Start(); //启动SCCB传输
SCCB_WR_Byte(SCCB_ID); //写器件ID
delay_us(100);
SCCB_WR_Byte(reg); //写寄存器地址
delay_us(100);
SCCB_Stop();
delay_us(100);
//设置寄存器地址后,才是读
SCCB_Start();
SCCB_WR_Byte(SCCB_ID|0X01); //发送读命令
delay_us(100);
val=SCCB_RD_Byte(); //读取数据
SCCB_No_Ack();
SCCB_Stop();
return val;
}
2.1、sccb.h
#ifndef __SCCB_H_
#define __SCCB_H_
#include "sys.h"
//IO方向设置
#define SCCB_SDA_IN() {GPIOD->MODER&=~(3<<(7*2));GPIOD->MODER|=0<<7*2;} //PD7 输入
#define SCCB_SDA_OUT() {GPIOD->MODER&=~(3<<(7*2));GPIOD->MODER|=1<<7*2;} //PD7 输出
//IO操作函数
#define SCCB_SCL PDout(6) //SCL
#define SCCB_SDA PDout(7) //SDA
#define SCCB_READ_SDA PDin(7) //输入SDA
#define SCCB_ID 0X60 //OV2640的ID
///
void SCCB_Init(void);
void SCCB_Start(void);
void SCCB_Stop(void);
void SCCB_No_Ack(void);
u8 SCCB_WR_Byte(u8 dat);
u8 SCCB_RD_Byte(void);
u8 SCCB_WR_Reg(u8 reg,u8 data);
u8 SCCB_RD_Reg(u8 reg);
#endif