”WinForm上位机+OV7670摄像头+STM32+蓝牙“图像采集系统(一)STM32驱动CMOS摄像头OV7670

初衷;将摄像头放在防盗门猫眼位置,访客到来时,给访客拍个照,然后传到房主端显示。 现在只完成了蓝牙传输,和WinForm窗体显示,后面时间来得及的话会陆续完成WiFi传输,和手机端APK显示。


常规思路:图像采集->图像处理->图像传输->图像显示


首先要做的就是图像采集:

系统采用32位单片机STM32F103ZET6,自己LayOut的PCB,中间可以直插FSMC驱动的TFT屏。 摄像头模块用的带FIFO的OV7670,最高输出是640*480@25Hz_16RGB,由于系统采用的是串口蓝牙传输,所以为了节省传输时间,决定只取320*240_4bits灰度图像传输。

OV要把一帧图像存储在FIFO中,等待MCU慢慢地读取,MCU读完一帧图片后,OV才会向FIFO写入新的一帧图片(摄像头VYSNC做FIFO读写中断即可),硬件装置如下图所示。

”WinForm上位机+OV7670摄像头+STM32+蓝牙“图像采集系统(一)STM32驱动CMOS摄像头OV7670_第1张图片


1、SCCB接口

初始化OV摄像头,需要通过SCCB接口配置其内部的一百多个寄存器。 SCCB就是个两线的接口协议,类似于IIC。SCL是时钟线,SDA是双向数据线。

2、调试心酸经验

刚开始调OV的时候,找了一大堆网友的程序,挨个试验,结果没一个管用的,于是耽搁了一段时间没搞,很是不甘心。 后来想着要理解理解代码,从SCCB协议开始,对照着芯片手册上的Register Map理解。

1) 先确保SCCB的时序正确,对照着DataSheet上的SCCB Timing Diagram检查

2) SCCB时序无误后,检查是否可以正常读写寄存器。先读下图的两个寄存器,地址为0X0A和0X0B,若分别返回0X76和0X73则说明可以正常读寄存器。

然后再去写一个寄存器(不要写成了ReadOnly的寄存器),写完去读它,检查是否和自己写入的值相等。若相等的话,就表示可以正常写了。

3) 在调试过程中,我遇到过几次这样的情况:读出来的数据始终是0XFF,也就是说,每次按位读的时候,SDA总为高电平。很让我苦恼,同样的程序在STM32上可以用,为什么到其他平台上就不能用了呢。后来偶然发现了原因:因为我将SCCB时序程序中的delay_us(50)改成了delay_ms(1)。也就是说,延时变长了就不能正常读写了,切记!

4) 另外,介绍两个需要注意的寄存器,地址分别是0x12 (用来配置输出格式:RGB YUV QVGA等), 0x1e (用来配置输出水平垂直镜像)

”WinForm上位机+OV7670摄像头+STM32+蓝牙“图像采集系统(一)STM32驱动CMOS摄像头OV7670_第2张图片

5)一帧图像在FIFO内读写的控制,程序如下,作简单解读

 //ov_sta:0,开始一帧数据采集
u8 ov_sta;
 //外部中断5~9服务程序  , 接OV7670_VSYNC
void EXTI9_5_IRQHandler(void)
{ 
 
if(EXTI_GetITStatus(EXTI_Line8)==SET)//是8线的中断
{     
if(ov_sta<2)
{
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++;
}
}
EXTI_ClearITPendingBit(EXTI_Line8);  //清除EXTI8线路挂起位 
} 


此IO中断连接OV7670_VSYNC,状态变量ov_sta初始为0,第一个OV7670_VSYNC上升沿来到,表示OV开始输出一帧图像,此时复位FIFO写指针,允许写入FIFO,ov_sta自增至1。第二个OV7670_VSYNC上升沿来到,表示OV此帧图像输出完毕,开始输出下一帧图像,此时进制写入FIFO,ov_sta自增至2。 这样就把一帧图像锁存在FIFO中等待MCU慢慢读取了,ov_sta为2的时候,OV7670_VSYNC的中断服务是不会有任何操作的,因为此时要等待MCU读完FIFO,并将状态变量ov_sta复位,程序如下:

 extern u8 ov_sta;
//更新LCD显示
void camera_refresh(void)
{
u32 i,j;
  u16 color;  
//  ili9320_SetCursor(0,0); 
//  LCD_WriteRAM_Prepare(); 
if(ov_sta==2)
{ 
OV7670_RRST_0;//开始复位读指针 
OV7670_RCK_0;
OV7670_RCK_1;
OV7670_RCK_0;
OV7670_RRST_1;//复位读指针结束 
OV7670_RCK_1;  


for(i=0;i<240;i++)
for(j=0;j<320;j++)
{
OV7670_RCK_0;
color=GPIOC->IDR&0XFF;//读数据
usart1_send_char(color);
delay_ms(20);//留时间给zigbee传输数据
OV7670_RCK_1; 
color<<=8;  
OV7670_RCK_0;
color|=GPIOC->IDR&0XFF;//读数据
usart1_send_char(color);
delay_ms(20);//留时间给zigbee传输数据
OV7670_RCK_1; 


// LCD_WriteRAM(color);
LCD_DrawPoint(239-i,j,color);
}    
EXTI_ClearITPendingBit(EXTI_Line8);  //清除LINE8上的中断标志位
ov_sta=0; //开始下一次采集
} 
} 


camera_refresh()函数负责从FIFO中读取一帧图片,读取320*240个像素,每个像素16位,分两次读取,每次8位。读完之后将ov_sta复位至0,此时OV就可以向FIFO写入新的一帧图像。camera_refresh()函数可以放在main函数的while(1)中连续读取并显示(摄像机),也可以放在外部中断或串口中断中,来一次中断需求,读取一次数据(照相机)。


To include..附上ov7670的驱动程序:

sccb.h文件

********************************************************************************************************************************************************************************

#ifndef __SCCB_H
#define __SCCB_H
#include "stm32f10x.h"
#include "delay.h"


#define SCCB_SDA_IN()  {GPIOG->CRH&=0XFF0FFFFF;GPIOG->CRH|=0X00800000;}
#define SCCB_SDA_OUT() {GPIOG->CRH&=0XFF0FFFFF;GPIOG->CRH|=0X00300000;}


//IO操作函数  
#define SCCB_SCL_0 GPIO_ResetBits(GPIOD,GPIO_Pin_3)//SCL=0
#define SCCB_SCL_1   GPIO_SetBits(GPIOD,GPIO_Pin_3) //SCL=1
#define SCCB_SDA_0 GPIO_ResetBits(GPIOG,GPIO_Pin_13)//SDA=0
#define SCCB_SDA_1   GPIO_SetBits(GPIOG,GPIO_Pin_13)//SDA=1
#define SCCB_READ_SDA GPIO_ReadInputDataBit(GPIOG,GPIO_Pin_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文件

********************************************************************************************************************************************************************************

#include "sccb.h"
//初始化SCCB接口
//CHECK OK
void SCCB_Init(void)
{   
RCC->APB2ENR|=1<<8;//先使能外设PORTG时钟  
  RCC->APB2ENR|=1<<5; //先使能外设PORTD时钟
//PORTG13 IN 
GPIOG->CRH&=0XFF0FFFFF;
GPIOG->CRH|=0X00800000;    
GPIOG->ODR|=1<<13; 
//PD3 OUT  
GPIOD->CRL&=0XFFFF0FFF;
GPIOD->CRL|=0X00003000;    
GPIOD->ODR|=1<<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<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;
}






ov7670.h文件

********************************************************************************************************************************************************************************

#ifndef _OV7670_H
#define _OV7670_H
#include "stm32f10x.h"
#include "delay.h" 
#include "ili9320.h"
#include "usart.h"
#include "sccb.h"


#define OV7670_VSYNC  GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8)//同步信号检测IO  
#define OV7670_WRST_0 GPIO_ResetBits(GPIOD,GPIO_Pin_6)//写指针复位
#define OV7670_WRST_1 GPIO_SetBits(GPIOD,GPIO_Pin_6)
#define OV7670_WREN_0 GPIO_ResetBits(GPIOB,GPIO_Pin_3)//写入FIFO使能 
#define OV7670_WREN_1 GPIO_SetBits(GPIOB,GPIO_Pin_3)




#define OV7670_RCK_0 GPIO_ResetBits(GPIOB,GPIO_Pin_4)//读数据时钟
#define OV7670_RCK_1 GPIO_SetBits(GPIOB,GPIO_Pin_4)
#define OV7670_RRST_0 GPIO_ResetBits(GPIOG,GPIO_Pin_14)//读指针复位
#define OV7670_RRST_1 GPIO_SetBits(GPIOG,GPIO_Pin_14)
#define OV7670_CS_0 GPIO_ResetBits(GPIOG,GPIO_Pin_15)//片选信号(OE)  直接接地就行
#define OV7670_CS_1 GPIO_SetBits(GPIOG,GPIO_Pin_15) 
   
#define OV7670_DATA   GPIO_ReadInputData(GPIOC,0x00FF) //数据输入端口
///////////////////////////////////////// 
     
u8   OV7670_Init(void); 
void camera_refresh(void);
void OV7670_Light_Mode(u8 mode);
void OV7670_Color_Saturation(u8 sat);
void OV7670_Brightness(u8 bright);
void OV7670_Contrast(u8 contrast);
void OV7670_Special_Effects(u8 eft);
void OV7670_Window_Set(u16 sx,u16 sy,u16 width,u16 height);


#endif






ov7670.c文件

********************************************************************************************************************************************************************************


#include "ov7670.h"




//初始化寄存器序列及其对应的值
u8 ov7670_init_reg_tbl[][2]= 
{   
  /*以下为OV7670 QVGA RGB565参数  */
  {0x3a, 0x04},//
{0x40, 0x10},
{0x12, 0x14},//QVGA,RGB输出


//输出窗口设置
{0x32, 0x80},
{0x17, 0x16},         
{0x18, 0x04},//5
{0x19, 0x02},
{0x1a, 0x7a},//0x7a,
  {0x03, 0x0a},//0x0a,




{0x0c, 0x0c},
{0x15, 0x00},
{0x3e, 0x00},//10
{0x70, 0x00},
{0x71, 0x01},
{0x72, 0x11},
{0x73, 0x09},//
        
{0xa2, 0x02},//15
{0x11, 0x00},//时钟分频设置,0,不分频.
{0x7a, 0x20},
{0x7b, 0x1c},
{0x7c, 0x28},
        
{0x7d, 0x3c},//20
{0x7e, 0x55},
{0x7f, 0x68},
{0x80, 0x76},
{0x81, 0x80},
        
{0x82, 0x88},
{0x83, 0x8f},
{0x84, 0x96},
{0x85, 0xa3},
{0x86, 0xaf},
        
{0x87, 0xc4},//30
{0x88, 0xd7},
{0x89, 0xe8},
{0x13, 0xe0},
{0x00, 0x00},//AGC
        
{0x10, 0x00},
{0x0d, 0x00}, 
{0x14, 0x20},//0x38, limit the max gain
{0xa5, 0x05},
{0xab, 0x07},
        
{0x24, 0x75},//40
{0x25, 0x63},
{0x26, 0xA5},
{0x9f, 0x78},
{0xa0, 0x68},
        
{0xa1, 0x03},//0x0b,
{0xa6, 0xdf},//0xd8,
{0xa7, 0xdf},//0xd8,
{0xa8, 0xf0},
{0xa9, 0x90},
        
{0xaa, 0x94},//50
{0x13, 0xe5},
{0x0e, 0x61},
{0x0f, 0x4b},
{0x16, 0x02},
        
{0x1e, 0x27},//图像输出镜像控制.0x07,
{0x21, 0x02},
{0x22, 0x91},
{0x29, 0x07},
{0x33, 0x0b},
        
{0x35, 0x0b},//60
{0x37, 0x1d},
{0x38, 0x71},
{0x39, 0x2a},
{0x3c, 0x78},
        
{0x4d, 0x40},
{0x4e, 0x20},
{0x69, 0x5d},
{0x6b, 0x40},//PLL*4=48Mhz
{0x74, 0x19},
{0x8d, 0x4f},
        
{0x8e, 0x00},//70
{0x8f, 0x00},
{0x90, 0x00},
{0x91, 0x00},
{0x92, 0x00},//0x19,//0x66
        
{0x96, 0x00},
{0x9a, 0x80},
{0xb0, 0x84},
{0xb1, 0x0c},
{0xb2, 0x0e},
        
{0xb3, 0x82},//80
{0xb8, 0x0a},
{0x43, 0x14},
{0x44, 0xf0},
{0x45, 0x34},
        
{0x46, 0x58},
{0x47, 0x28},
{0x48, 0x3a},
{0x59, 0x88},
{0x5a, 0x88},
        
{0x5b, 0x44},//90
{0x5c, 0x67},
{0x5d, 0x49},
{0x5e, 0x0e},
{0x64, 0x04},
{0x65, 0x20},
        
{0x66, 0x05},
{0x94, 0x04},
{0x95, 0x08},
{0x6c, 0x0a},
{0x6d, 0x55},
        
        
{0x4f, 0x80},
{0x50, 0x80},
{0x51, 0x00},
{0x52, 0x22},
{0x53, 0x5e},
{0x54, 0x80},
        
//{0x54, 0x40},//110
        
        
{0x09, 0x03},//驱动能力最大


{0x6e, 0x11},//100
{0x6f, 0x9f},//0x9e for advance AWB
  {0x55, 0x00},//亮度
  {0x56, 0x40},//对比度
  {0x57, 0x80},//0x40,  change according to Jim's request
}; 




//初始化OV7670
//返回0:成功
//返回其他值:错误代码
u8 OV7670_Init(void)
{
u8 temp;
u16 i=0;
 
  GPIO_InitTypeDef  GPIO_InitStructure;
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOG, ENABLE);//使能相关端口时钟
 


GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_8; //PA8 输入 上拉
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &GPIO_InitStructure);





  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4;// 端口配置
  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


  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;i784)endy-=784;
temp=SCCB_RD_Reg(0X03);//读取Vref之前的值
temp&=0XF0;
temp|=((endx&0X03)<<2)|(sx&0X03);
SCCB_WR_Reg(0X03,temp);//设置Vref的start和end的最低2位
SCCB_WR_Reg(0X19,sx>>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)|(sy&0X07);
SCCB_WR_Reg(0X17,sy>>3);//设置Href的start高8位
SCCB_WR_Reg(0X18,endy>>3);//设置Href的end的高8位
}


extern u8 ov_sta;
//更新LCD显示
void camera_refresh(void)
{
u32 i,j;
  u16 color;  
//  ili9320_SetCursor(0,0); 
//  LCD_WriteRAM_Prepare(); 
if(ov_sta==2)
{ 
OV7670_RRST_0;//开始复位读指针 
OV7670_RCK_0;
OV7670_RCK_1;
OV7670_RCK_0;
OV7670_RRST_1;//复位读指针结束 
OV7670_RCK_1;  


for(i=0;i<240;i++)
for(j=0;j<320;j++)
{
OV7670_RCK_0;
color=GPIOC->IDR&0XFF;//读数据
usart1_send_char(color);
delay_ms(20);//留时间给zigbee传输数据
OV7670_RCK_1; 
color<<=8;  
OV7670_RCK_0;
color|=GPIOC->IDR&0XFF;//读数据
usart1_send_char(color);
delay_ms(20);//留时间给zigbee传输数据
OV7670_RCK_1; 


// LCD_WriteRAM(color);
LCD_DrawPoint(239-i,j,color);
}    
EXTI_ClearITPendingBit(EXTI_Line8);  //清除LINE8上的中断标志位
ov_sta=0; //开始下一次采集
} 
} 










































你可能感兴趣的:(嵌入式)