stm32 OV7670摄像头模块的介绍以及应用(SCCB的使用)
今天学习摄像头模块,使用该模块进行拍摄,照相等功能进行对stm32 控制板的结合,了解他的原理以及应用。
主角: ov7670摄像头模块。(大概了解一下产品背景)
OV7670 是 OV( OmniVision)公司生产的一颗 1/6 寸的 CMOS VGA 图像传 感器。该传感器体积小、工作电压低,提供单片 VGA 摄像头和影像处理器的所 有功能。通过 SCCB 总线控制,可以输出整帧、子采样、取窗口等方式的各种分 辨率 8 位影像数据。该产品 VGA 图像最高达到 30 帧/秒。用户可以完全控制 图像质量、数据格式和传输方式。所有图像处理功能过程包括伽玛曲线、白平衡、 度、色度等都可以通过 SCCB 接口编程。 OmmiVision 图像传感器应用独有的传 感器技术,通过减少或消除光学或电子缺陷如固定图案噪声、托尾、浮散等,提 高图像质量,得到清晰的稳定的彩色图像。(官方说明请进一步查看说明文档,部分 中文版翻译有出入,有能力的可以看英文版的)
SCCB?(后面会用到这里简单讲解一下)
SCCB是欧姆尼图像技术公司(OmniVision)开发的一种总线,并广泛的应用于OV系列图像传感器上,所以一般使用OV的图像传感器都离不开SCCB总线协议。可以通俗地讲SCCB有两种工作模式,一主多从,一主一从模式。
参考正反面接线柱以及元器件(如下图)
实物图如下
OV7670 的特点有:
(1)高灵敏度、低电压适合嵌入式应用
(2)标准的 SCCB 接口,兼容 IIC 接口
(3)支持 RawRGB、RGB(GBR4:2:2,RGB565/RGB555/RGB444),YUV(4:2:2) 和 YCbCr( 4:2:2)输出格式
(4)支持 VGA、 CIF,和从 CIF 到 40*30 的各种尺寸输出
(5)支持自动曝光控制、自动增益控制、自动白平衡、自动消除灯光条纹、 自动黑电平校准等自动控制功能。同时支持色饱和度、色相、伽马、锐度等设置。
(6)支持闪光灯
(7)支持图像缩放
应用,进行拍摄,画面传输在控制板的TFT 屏幕上,下期更新拍照。
OV7670 传感器 功能模块:
1.感光整列( Image Array) OV7670 总共有 656488 个像素,其中 640480 个有效(即有效像素为 30W)。
2.时序发生器( Video Timing Generator) 时序发生器具有的功能包括:整列控制和帧率发生( 7 种不同格式输出)、 内部信号发生器和分布、帧率时序、自动曝光控制、输出外部时序( VSYNC帧同步信号、 HREF/HSYNC同步信号 和 PCLK像素时钟)。
3.模拟信号处理( Analog Processing) 模拟信号处理所有模拟功能,并包括:自动增益( AGC)和自动白平衡( AWB)。
4.A/D 转换( A/D) 原始的信号经过模拟处理器模块之后 ,分 G 和 BR 两路进入一个 10 位的 A/D 转换器,A/D 转换器工作在 12M 频率,与像素频率完全同步(转换的频率 和帧率有关)。 除 A/D 转换器外,该模块还有以下三个功能: ①黑电平校正( BLC) ②U/V 通道延迟 ③A/D 范围控制 A/D 范围乘积和 A/D 的范围控制共同设置 A/D 的范围和最大值,允许用户 2根据应用调整图片的亮度。
5.测试图案发生器( Test Pattern Generator) 测试图案发生器功能包括:八色彩色条图案、渐变至黑白彩色条图案和输出 脚移位“ 1”。
6.数字处理器( DSP) 这个部分控制由原始信号插值到 RGB 信号的过程,并控制一些图像质量: ①边缘锐化(二维高通滤波器) ②颜色空间转换(原始信号到 RGB 或者 YUV/YCbYCr) ③RGB 色彩矩阵以消除串扰 ④色相和饱和度的控制 ⑤黑/白点补偿 ⑥降噪 ⑦镜头补偿 ⑧可编程的伽玛 ⑨十位到八位数据转换
7.缩放功能( Image Scaler) 这个模块按照预先设置的要求输出数据格式,能将 YUV/RGB 信号从 VGA 缩 小到 CIF 以下的任何尺寸。
8.数字视频接口( Digital Video Port) 通过寄存器 COM2[1:0],调节 IOL/IOH 的驱动电流,以适应用户的负载。
9.SCCB 接口( SCCB Interface) SCCB 接 口 控 制 图 像 传 感 器 芯 片 的 运 行 , 详 细 使 用 方 法 参 照 《 OmniVisionTechnologies Seril Camera Control Bus(SCCB) Specification》 这个文档。
10.LED 和闪光灯的输出控制( LED and Storbe Flash Control Output) OV7670 有闪光灯模式,可以控制外接闪光灯或闪光 LED 的工作。 OV7670 的寄存器通过 SCCB 时序访问并设置,SCCB 时序和 IIC 时序十分 类似,在这里我们不做介绍,请大家参考模块的相关文档。
重点定义:
VGA,即分辨率为 640480 的输出模式;
QVGA,即分辨率为 320240 的输出格式,也就是本手册我们需要用到的格 式;
QQVGA,即分辨率为 160*120 的输出格式;
PCLK,即像素时钟,一个 PCLK 时钟,输出一个像素(或半个像素)。
VSYNC,即帧同步信号。
HREF /HSYNC,即行同步信号。
图像输出的控制条件
OV7670 的图像数据输出(通过 D[7:0])就是在 PCLK, VSYNC 和 HREF/ HSYNC 的控制下进行的。
首先看看行输出时序,如图所示:
-----上属已经初步了解了0V7670的原理和背景(下面市重点),下面学习OV7670 模块使用方法-------------------------
下面图像数据的存储于读取的过程分析
如何存储图像数据?
-OV7670 摄像头模块存储图像数据的过程为:等待 OV7670 同步信号→ FIFO 写指针复位→FIFO 写使能→等待第二个 OV7670 同步信号→FIFO 写禁 止。通过以上 5 个步骤,我们就完成了 1 帧图像数据的存储
如何读取图像数据?
在存储完一帧图像以后,我们就可以开始读取图像数据了。读取过程为: FIFO 读指针复位→给 FIFO 读时钟( FIFO_RCLK)→读取第一个像素高字节→ 给 FIFO 读时钟→读取第一个像素低字节→给 FIFO 读时钟→读取第二个像素 高字节→循环读取剩余像素→结束。
原理图如下所示
由上图可以看出0v7670(图片信号) ------传输数据到--------->FIFO------------------------>经过输出模块------------------->mcu (我们拿到的图片信息)
下面直接开始分析代码函数的使用
初始化SCCB接口(前面讲到使用sccb 进行控制图像)/
初始化OV7670
设置图像输出窗口
帧中断标记
更新LCD显示
//初始化SCCB接口 //CHECK OK void SCCB_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOG, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; // 端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //输入 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOG, &GPIO_InitStructure); GPIO_SetBits(GPIOG,GPIO_Pin_13); // 输出高 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // 端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //输输出 GPIO_Init(GPIOD, &GPIO_InitStructure); GPIO_SetBits(GPIOD,GPIO_Pin_3); // 输出高 SCCB_SDA_OUT(); }
//初始化OV7670 //返回0:成功 //返回其他值:错误代码 u8 OV7670_Init(void) { u8 temp; u16 i=0; //设置IO GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOG|RCC_APB2Periph_AFIO, ENABLE); //使能相关端口时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //PA8 输入 上拉 **VSYNC** GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA,GPIO_Pin_8); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4;// 端口配置SWD 禁止swj 因为默认的PB3和pb4 是Jtag下载占用 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_SetBits(GPIOB,GPIO_Pin_3|GPIO_Pin_4); GPIO_InitStructure.GPIO_Pin = 0xff; //PC0~7 输入 上拉 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOD, &GPIO_InitStructure); GPIO_SetBits(GPIOD,GPIO_Pin_6); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14|GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOG, &GPIO_InitStructure); GPIO_SetBits(GPIOG,GPIO_Pin_14|GPIO_Pin_15); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); //SWD 禁止swj 因为默认的PB3和pb4 是Jtag下载占用的 SCCB_Init(); //初始化SCCB 的IO口 if(SCCB_WR_Reg(0x12,0x80))return 1; //复位SCCB delay_ms(50); //读取产品型号 temp=SCCB_RD_Reg(0x0b); if(temp!=0x73)return 2; temp=SCCB_RD_Reg(0x0a); if(temp!=0x76)return 2; //初始化序列 for(i=0;i //初始化寄存器序列及其对应的值 SCCB_WR_Reg(ov7670_init_reg_tbl[i][0],ov7670_init_reg_tbl[i][1]); //SCCB_WR_Reg对ov7670_init_reg_tbl的写入完成初始化 } return 0x00; //ok } u16 endx; u16 endy; u8 temp; endx=sx+width*2; //V*2 endy=sy+height*2; if(endy784)endy-=784; temp=SCCB_RD_Reg(0X03); //读取Vref之前的值 temp&=0XF0; temp|=((endx&0X03)2); //设置Vref的start高8位 SCCB_WR_Reg(0X1A,endx>>2); //设置Vref的end的高8位 temp=SCCB_RD_Reg(0X32); //读取Href之前的值 temp&=0XC0; temp|=((endy&0X07)3); //设置Href的start高8位 SCCB_WR_Reg(0X18,endy>>3); //设置Href的end的高8位 }
//等待ov7670 帧同步信号(帧中断标记是否开始) u8 ov_sta; //帧中断标记 void EXTI9_5_IRQHandler(void) //红外遥控外部中断 { if(EXTI_GetITStatus(EXTI_Line8)==SET) //是8线的中断 { if(ov_sta if(ov_sta==0) { OV7670_WRST=0; //复位写指针 OV7670_WRST=1; OV7670_WREN=1; //允许写入FIFO } else { OV7670_WREN=0;//禁止写入FIFO OV7670_WRST=0; //复位写指针 OV7670_WRST=1; } ov_sta++; //帧中断加1 } } EXTI_ClearITPendingBit(EXTI_Line8); //清除EXTI8线路挂起位 } u32 j; u16 color; if(ov_sta)//有帧中断更新? { LCD_Display_Dir(1); LCD_Set_Window(0,0,320-1,240-1);//将显示区域设置到屏幕中央 OV7670_RRST=0; //开始复位读指针 OV7670_RCK_L; OV7670_RCK_H; OV7670_RCK_L; OV7670_RRST=1; //复位读指针结束 OV7670_RCK_H; for(j=0;j OV7670_RCK_L; color=GPIOC-IDR&0XFF; //读数据 OV7670_RCK_H; colorCRH|=0X00800000;} //#define SCCB_SDA_OUT() {GPIOG->CRH&=0XFF0FFFFF;GPIOG->CRH|=0X00300000;} //IO操作函数 #define SCCB_SCL PDout(3) //SCL #define SCCB_SDA PGout(13) //SDA #define SCCB_READ_SDA PGin(13) //输入SDA #define SCCB_ID 0X42 //OV7670的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 sccb。c ```c #include "system.h" #include "sccb.h" #include "SysTick.h" void SCCB_SDA_OUT(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; // 端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOG, &GPIO_InitStructure); } void SCCB_SDA_IN(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; // 端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //输入 GPIO_Init(GPIOG, &GPIO_InitStructure); } //初始化SCCB接口 //CHECK OK void SCCB_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOG, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; // 端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //输入 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOG, &GPIO_InitStructure); GPIO_SetBits(GPIOG,GPIO_Pin_13); // 输出高 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // 端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //输输出 GPIO_Init(GPIOD, &GPIO_InitStructure); GPIO_SetBits(GPIOD,GPIO_Pin_3); // 输出高 SCCB_SDA_OUT(); } //SCCB起始信号 //当时钟为高的时候,数据线的高到低,为SCCB起始信号 //在激活状态下,SDA和SCL均为低电平 void SCCB_Start(void) { 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=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 if(dat&0x80)SCCB_SDA=1; else SCCB_SDA=0; dat u8 temp=0,j; SCCB_SDA_IN(); //设置SDA为输入 for(j=8;j0;j--) //循环8次接收数据 { delay_us(50); SCCB_SCL=1; temp=temp 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; } u32 j; u16 color; if(ov_sta)//有帧中断更新? { LCD_Display_Dir(1); LCD_Set_Window(0,0,320-1,240-1);//将显示区域设置到屏幕中央 OV7670_RRST=0; //开始复位读指针 OV7670_RCK_L; OV7670_RCK_H; OV7670_RCK_L; OV7670_RRST=1; //复位读指针结束 OV7670_RCK_H; for(j=0;j OV7670_RCK_L; color=GPIOC-IDR&0XFF; //读数据 OV7670_RCK_H; color LCD_DrawRectangle( result.x-result.w/2, result.y-result.h/2, result.x-result.w/2+result.w, result.y-result.h/2+result.h); LCD_DrawRectangle( result.x-2, result.y-2,result.x+2, result.y+2); } } } const u8*LMODE_TBL[5]={"Auto","Sunny","Cloudy","Office","Home"}; const u8*EFFECTS_TBL[7]={"Normal","Negative","B&W","Redish","Greenish","Bluish","Antique"}; //7种特效 int main() { u8 i=0; u8 key; u8 lightmode=0,saturation=2,brightness=2,contrast=2; u8 effect=0; u8 sbuf[15]; u8 count; SysTick_Init(72); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2组 LED_Init(); USART1_Init(115200); TFTLCD_Init(); //LCD初始化 KEY_Init(); EN25QXX_Init(); //初始化EN25Q128 my_mem_init(SRAMIN); //初始化内部内存池 FRONT_COLOR=RED;//设置字体为红色 LCD_ShowString(10,10,tftlcd_data.width,tftlcd_data.height,16,"PRECHIN STM32F1"); LCD_ShowString(10,30,tftlcd_data.width,tftlcd_data.height,16,"www.prechin.net"); LCD_ShowString(10,50,tftlcd_data.width,tftlcd_data.height,16,"OV7670 Test"); while(OV7670_Init())//初始化OV7670 { LCD_ShowString(10,80,tftlcd_data.width,tftlcd_data.height,16,"OV7670 Error!"); delay_ms(200); LCD_Fill(10,80,239,206,WHITE); delay_ms(200); } LCD_ShowString(10,80,tftlcd_data.width,tftlcd_data.height,16,"OV7670 OK! "); delay_ms(1500); OV7670_Light_Mode(0); OV7670_Color_Saturation(2); OV7670_Brightness(2); OV7670_Contrast(2); OV7670_Special_Effects(0); TIM4_Init(10000,7199); //10Khz计数频率,1秒钟中断 EXTI8_Init(); OV7670_Window_Set(12,176,240,320); //设置窗口 OV7670_CS=0; LCD_Clear(BLACK); while(1) { key=KEY_Scan(0); if(key)count=20; switch(key) { case KEY_UP_PRESS: //灯光模式设置 lightmode++; if(lightmode4)lightmode=0; OV7670_Light_Mode(lightmode); sprintf((char*)sbuf,"%s",LMODE_TBL[lightmode]); break; case KEY1_PRESS: //饱和度 saturation++; if(saturation4)saturation=0; OV7670_Color_Saturation(saturation); sprintf((char*)sbuf,"Saturation:%d",(char)saturation-2); break; case KEY2_PRESS: //亮度 brightness++; if(brightness4)brightness=0; OV7670_Brightness(brightness); sprintf((char*)sbuf,"Brightness:%d",(char)brightness-2); break; case KEY0_PRESS: //对比度 contrast++; if(contrast4)contrast=0; OV7670_Contrast(contrast); sprintf((char*)sbuf,"Contrast:%d",(char)contrast-2); break; } if(count) { count--; LCD_ShowString((tftlcd_data.width-240)/2+30,(tftlcd_data.height-320)/2+60,200,16,16,sbuf); } camera_refresh();//更新显示 i++; if(i%20==0) { LED1=!LED1; } // delay_ms(5); } }