102 赞同
82 评论
256 收藏

大家好,我是兔子。嵌入式工程师。

专业角度带你玩转Arduino.

这次教大家用Arduino UNO来驱动摄像头OV7670拍照。

Arduino uno R3 物联网套件RFID入门编程传感器UNO
淘宝
¥88.00
去购买
Arduino uno R3 物联网套件RFID入门编程传感器UNO
淘宝
¥88.00
去购买
Arduino uno R3 物联网套件RFID入门编程传感器UNO
淘宝
¥88.00
去购买

1.摄像头OV7670

ov7670是OmniVision公司生产的一款感光整列,30W像素。

如图所示,就是这个类型芯片的东西。

然后我们给感光整列配上镜头,加上他的外围电路,就成了某宝热卖的OV7670摄像头模块。

模块如图所示:

OV7670待FIFO模块正面
OV7670待FIFO模块背面

我们这次使用的是OV7670带FIFO的模块。(FIFO芯片->背面图,最大的一个28脚的芯片)

为什么要用带FIFO的模块呢?因为Arduino UNO速度比较慢,带FIFO可已将摄像头拍摄的数据暂时存在FIFO里,然后我们的Arduino UNO再慢慢的将拍摄的数据读出来,通过串口发送到串口上位机显示。

FIFO,即first in first out的缩写。在这里,FIFO的速度很快,可以将摄像头的数据暂时存起了。以便我们慢速的CPU将获得的数据慢慢取出来并处理,达到一个类似水库的作用。

2.接线

我们可以看到Arduino UNO的资源有限,摄像头除了串口(管脚0,1)及管脚13没有用到,其余管脚都用上了。

所以数据只能通过串口上传到电脑上显示。

OV7670 D0 ->Arduino 3

OV7670 D1 ->Arduino 4

OV7670 D2 ->Arduino 5

OV7670 D3 ->Arduino 6

OV7670 D4 ->Arduino 7

OV7670 D5 ->Arduino 8

OV7670 D6 ->Arduino 9

OV7670 D7 ->Arduino 10

OV7670 FIFIO_RCK -> Arduino A0

OV7670 FIFIO_OE -> Arduino A1

OV7670 FIFIO_WR -> Arduino A2

OV7670 FIFIO_RRST -> Arduino A3

OV7670 SIO_C -> Arduino A5 (管脚不可调整为其他管脚)

OV7670 SIO_D -> Arduino A4 (管脚不可调整为其他管脚)

OV7670 FIFIO_WRST -> Arduino 11

OV7670 VSYNC -> Arduino 2 (管脚不可调整为其他管脚)

OV7670 RESET -> Arduino 12

OV7670 3.3V -> Arduino 3.3V

OV7670 GND -> Arduino GND

其余摄像头针脚可以不接(悬空)。

3.程序编写

OV7670,Arduino暂无人编写库。所以我们的程序需要带有驱动功能,即具有库的功能。

兔子我找了某宝的OV7670模块的卖家,他们提供的是STM32的库。我已经移植到Arduino UNO上。

后续有机会,兔子我再把它调整为库程序,大家就可以直接调用了。

不废话了,上代码。(由于是驱动程序,有很多操作实际上是对OV7670寄存器的操作,不具有逻辑上的意义)。所以写库就是一件枯燥的事情。必须看厂家的芯片手册你才知道有些操作具有什么含义。


/*
  程序 OV7670驱动程序
  作者:兔子
  效果:驱动摄像头OV7670带FIFO模块,拍摄1帧图片,并将图片数据通过串口上传至电脑
  时间:19.05.10
  联系方式:18910297350  微信:xw2816960
  
  CONNECTIONS:
  OV7670  D0  ->Arduino  3
  OV7670  D1  ->Arduino  4
  OV7670  D2  ->Arduino  5
  OV7670  D3  ->Arduino  6
  OV7670  D4  ->Arduino  7
  OV7670  D5  ->Arduino  8
  OV7670  D6  ->Arduino  9
  OV7670  D7  ->Arduino  10
  OV7670  FIFIO_RCK     ->  Arduino  A0
  OV7670  FIFIO_OE      ->  Arduino  A1
  OV7670  FIFIO_WR      ->  Arduino  A2
  OV7670  FIFIO_RRST    ->  Arduino  A3
  OV7670  SIO_C         ->  Arduino  A5 (管脚不可调整为其他管脚)
  OV7670  SIO_D         ->  Arduino  A4 (管脚不可调整为其他管脚)
  OV7670  FIFIO_WRST    ->  Arduino 11
  OV7670  VSYNC         ->  Arduino  2   (管脚不可调整为其他管脚)
  OV7670  RESET         ->  Arduino  12
  OV7670  3.3V          ->  Arduino  3.3V
  OV7670  GND           ->  Arduino  GND
  其余摄像头针脚可以不接(悬空)。
*/
#include <Wire.h> // I2C 协议必不可少的库
#define OV7670_ADDRESS    0x21
#define VSYNC      2
#define D0         3
#define D1         4
#define D2         5
#define D3         6
#define D4         7
#define D5         8
#define D6         9
#define D7         10
#define RESET      12

#define FIFO_WRST  11
#define FIFO_WR    A2
#define FIFO_RRST  A3
#define FIFO_RCK   A0
#define FIFO_OE    A1


unsigned char WrCmos7670(unsigned char regID, unsigned char regDat);
unsigned char rdCmos7670Reg(unsigned char regID);
unsigned char Cmos7670_init(void);
void set_Cmos7670reg(void);
unsigned char ov7670_data(void);
void IO_Init(void);


/****变量********/
unsigned char tmp;
uint16_t Vsync_Flag,i,j;
unsigned char t1,t2;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(256000);
  Wire.begin();//I2C 要开始了
  
  IO_Init();
  
  digitalWrite(RESET, LOW);
  delay(20);
  digitalWrite(RESET, HIGH);
  while(1!=Cmos7670_init());   //CMOS初始化
  tmp = rdCmos7670Reg(0x3A);
  if(tmp == 0x04)
  {
    }
    else
    {
      while(1);
      }
  attachInterrupt(digitalPinToInterrupt(VSYNC), OV7670, FALLING);//设置触发中断的端口,中断后运行的程序和触发模式
}

void loop() {
  // put your main code here, to run repeatedly:

}

void IO_Init(void)
{
  pinMode(D0, INPUT);
  pinMode(D1, INPUT);
  pinMode(D2, INPUT);
  pinMode(D3, INPUT);
  pinMode(D4, INPUT);
  pinMode(D5, INPUT);
  pinMode(D6, INPUT);
  pinMode(D7, INPUT);

  pinMode(RESET, OUTPUT);
  
  pinMode(FIFO_WRST, OUTPUT);
  pinMode(FIFO_WR, OUTPUT);
  pinMode(FIFO_RRST, OUTPUT);
  pinMode(FIFO_RCK, OUTPUT);
  pinMode(FIFO_OE, OUTPUT);

  pinMode(VSYNC, INPUT_PULLUP);
}

unsigned char WrCmos7670(unsigned char regID, unsigned char regDat)
{
  Wire.beginTransmission(OV7670_ADDRESS); // 设备地址为0x21
  Wire.write(regID);
  Wire.write(regDat);
  Wire.endTransmission(); //结束通信
  return(1);
}   

unsigned char rdCmos7670Reg(unsigned char regID)
{
  unsigned char regDat;
  Wire.beginTransmission(OV7670_ADDRESS); // 设备地址为0x21
  Wire.write(regID);
  Wire.endTransmission(); //结束通信
  Wire.requestFrom(OV7670_ADDRESS, 1);
  regDat = Wire.read();
  return regDat;
}

unsigned char Cmos7670_init(void)
{
  unsigned char mmm;  
  
  mmm=0x80;
  if(0==WrCmos7670(0x12, mmm)) 
  {
    return 0 ;
  }
  delay(10);

  set_Cmos7670reg();

  return 1; 
} 

void set_Cmos7670reg(void)  //OV7670寄存器配置,无逻辑意义,需根据厂家芯片手册了解具体含义
{    
  WrCmos7670(0x3a, 0x04);
  WrCmos7670(0x40, 0xd0);
  WrCmos7670(0x12, 0x14);
  WrCmos7670(0x32, 0x80);
  WrCmos7670(0x17, 0x16);
  WrCmos7670(0x18, 0x04);
  WrCmos7670(0x19, 0x02);
  WrCmos7670(0x1a, 0x7b);
  WrCmos7670(0x03, 0x06);
  WrCmos7670(0x0c, 0x00);
  WrCmos7670(0x3e, 0x00);
  WrCmos7670(0x70, 0x3a);
  WrCmos7670(0x71, 0x35);  //如果调整为WrCmos7670(0x71, 0x80);会显示8条彩色竖条,调试用
  WrCmos7670(0x72, 0x11);
  WrCmos7670(0x73, 0x00);
  WrCmos7670(0xa2, 0x02);
  WrCmos7670(0x11, 0x81);
  
  WrCmos7670(0x7a, 0x20);
  WrCmos7670(0x7b, 0x1c);
  WrCmos7670(0x7c, 0x28);
  WrCmos7670(0x7d, 0x3c);
  WrCmos7670(0x7e, 0x55);
  WrCmos7670(0x7f, 0x68);
  WrCmos7670(0x80, 0x76);
  WrCmos7670(0x81, 0x80);
  WrCmos7670(0x82, 0x88);
  WrCmos7670(0x83, 0x8f);
  WrCmos7670(0x84, 0x96);
  WrCmos7670(0x85, 0xa3);
  WrCmos7670(0x86, 0xaf);
  WrCmos7670(0x87, 0xc4);
  WrCmos7670(0x88, 0xd7);
  WrCmos7670(0x89, 0xe8);
  
  WrCmos7670(0x13, 0xe0);
  WrCmos7670(0x00, 0x00);
  
  WrCmos7670(0x10, 0x00);
  WrCmos7670(0x0d, 0x00);
  WrCmos7670(0x14, 0x28);
  WrCmos7670(0xa5, 0x05);
  WrCmos7670(0xab, 0x07);
  WrCmos7670(0x24, 0x75);
  WrCmos7670(0x25, 0x63);
  WrCmos7670(0x26, 0xA5);
  WrCmos7670(0x9f, 0x78);
  WrCmos7670(0xa0, 0x68);
  WrCmos7670(0xa1, 0x03);
  WrCmos7670(0xa6, 0xdf);
  WrCmos7670(0xa7, 0xdf);
  WrCmos7670(0xa8, 0xf0);
  WrCmos7670(0xa9, 0x90);
  WrCmos7670(0xaa, 0x94);
  WrCmos7670(0x13, 0xe5);

  WrCmos7670(0x0e, 0x61);
  WrCmos7670(0x0f, 0x4b);
  WrCmos7670(0x16, 0x02);
  WrCmos7670(0x1e, 0x37);
  WrCmos7670(0x21, 0x02);
  WrCmos7670(0x22, 0x91);
  WrCmos7670(0x29, 0x07);
  WrCmos7670(0x33, 0x0b);
  WrCmos7670(0x35, 0x0b);
  WrCmos7670(0x37, 0x1d);
  WrCmos7670(0x38, 0x71);
  WrCmos7670(0x39, 0x2a);
  WrCmos7670(0x3c, 0x78);
  WrCmos7670(0x4d, 0x40);
  WrCmos7670(0x4e, 0x20);
  WrCmos7670(0x69, 0x00);
  WrCmos7670(0x6b, 0x60);
  WrCmos7670(0x74, 0x19);
  WrCmos7670(0x8d, 0x4f);
  WrCmos7670(0x8e, 0x00);
  WrCmos7670(0x8f, 0x00);
  WrCmos7670(0x90, 0x00);
  WrCmos7670(0x91, 0x00);
  WrCmos7670(0x92, 0x00);
  WrCmos7670(0x96, 0x00);
  WrCmos7670(0x9a, 0x80);
  WrCmos7670(0xb0, 0x84);
  WrCmos7670(0xb1, 0x0c);
  WrCmos7670(0xb2, 0x0e);
  WrCmos7670(0xb3, 0x82);
  WrCmos7670(0xb8, 0x0a);



  WrCmos7670(0x43, 0x14);
  WrCmos7670(0x44, 0xf0);
  WrCmos7670(0x45, 0x34);
  WrCmos7670(0x46, 0x58);
  WrCmos7670(0x47, 0x28);
  WrCmos7670(0x48, 0x3a);
  WrCmos7670(0x59, 0x88);
  WrCmos7670(0x5a, 0x88);
  WrCmos7670(0x5b, 0x44);
  WrCmos7670(0x5c, 0x67);
  WrCmos7670(0x5d, 0x49);
  WrCmos7670(0x5e, 0x0e);
  WrCmos7670(0x64, 0x04);
  WrCmos7670(0x65, 0x20);
  WrCmos7670(0x66, 0x05);
  WrCmos7670(0x94, 0x04);
  WrCmos7670(0x95, 0x08);
  WrCmos7670(0x6c, 0x0a);
  WrCmos7670(0x6d, 0x55);
  WrCmos7670(0x6e, 0x11);
  WrCmos7670(0x6f, 0x9f);
  WrCmos7670(0x6a, 0x40);
  WrCmos7670(0x01, 0x40);
  WrCmos7670(0x02, 0x40);
  WrCmos7670(0x13, 0xe7);
  WrCmos7670(0x15, 0x00);  
  
  
  WrCmos7670(0x4f, 0x80);
  WrCmos7670(0x50, 0x80);
  WrCmos7670(0x51, 0x00);
  WrCmos7670(0x52, 0x22);
  WrCmos7670(0x53, 0x5e);
  WrCmos7670(0x54, 0x80);
  WrCmos7670(0x58, 0x9e);
  
  WrCmos7670(0x41, 0x08);
  WrCmos7670(0x3f, 0x00);
  WrCmos7670(0x75, 0x05);
  WrCmos7670(0x76, 0xe1);
  WrCmos7670(0x4c, 0x00);
  WrCmos7670(0x77, 0x01);
  WrCmos7670(0x3d, 0xc2); 
  WrCmos7670(0x4b, 0x09);
  WrCmos7670(0xc9, 0x60);
  WrCmos7670(0x41, 0x38);
  WrCmos7670(0x56, 0x40);
  
  WrCmos7670(0x34, 0x11);
  WrCmos7670(0x3b, 0x02); 
                
  WrCmos7670(0xa4, 0x89);
  WrCmos7670(0x96, 0x00);
  WrCmos7670(0x97, 0x30);
  WrCmos7670(0x98, 0x20);
  WrCmos7670(0x99, 0x30);
  WrCmos7670(0x9a, 0x84);
  WrCmos7670(0x9b, 0x29);
  WrCmos7670(0x9c, 0x03);
  WrCmos7670(0x9d, 0x4c);
  WrCmos7670(0x9e, 0x3f);
  WrCmos7670(0x78, 0x04);
  
  WrCmos7670(0x79, 0x01);
  WrCmos7670(0xc8, 0xf0);
  WrCmos7670(0x79, 0x0f);
  WrCmos7670(0xc8, 0x00);
  WrCmos7670(0x79, 0x10);
  WrCmos7670(0xc8, 0x7e);
  WrCmos7670(0x79, 0x0a);
  WrCmos7670(0xc8, 0x80);
  WrCmos7670(0x79, 0x0b);
  WrCmos7670(0xc8, 0x01);
  WrCmos7670(0x79, 0x0c);
  WrCmos7670(0xc8, 0x0f);
  WrCmos7670(0x79, 0x0d);
  WrCmos7670(0xc8, 0x20);
  WrCmos7670(0x79, 0x09);
  WrCmos7670(0xc8, 0x80);
  WrCmos7670(0x79, 0x02);
  WrCmos7670(0xc8, 0xc0);
  WrCmos7670(0x79, 0x03);
  WrCmos7670(0xc8, 0x40);
  WrCmos7670(0x79, 0x05);
  WrCmos7670(0xc8, 0x30);
  WrCmos7670(0x79, 0x26); 
  WrCmos7670(0x09, 0x00);  
}

void OV7670(void)
{
  uint16_t cnt;
  detachInterrupt(digitalPinToInterrupt(VSYNC)); 
        
  Vsync_Flag++;
  if(Vsync_Flag==1)    
  {
    digitalWrite(FIFO_WRST, HIGH);
    digitalWrite(FIFO_WRST, LOW);
    for(i=0;i<100;i++);
    digitalWrite(FIFO_WRST, HIGH); 
    digitalWrite(FIFO_WR, HIGH);    
    
  }
  if(Vsync_Flag==2)
  {
    
    digitalWrite(FIFO_WR, LOW);
    detachInterrupt(digitalPinToInterrupt(VSYNC));
    //EXTI->EMR&=~(1<<4);

    //GPIOA->ODR ^= (1 << 3);

    digitalWrite(FIFO_RRST, LOW);
    digitalWrite(FIFO_RCK, LOW);             
    digitalWrite(FIFO_RCK, HIGH);
    digitalWrite(FIFO_RCK, LOW);
    digitalWrite(FIFO_RCK, HIGH);
    digitalWrite(FIFO_RRST, HIGH);

    digitalWrite(FIFO_OE, LOW);
    Serial.write(0x01);
    Serial.write(0xFE);
    for(i = 0; i < 9600; i ++)  
     {  
      for(j = 0; j < 8; j ++)
      {                   
        digitalWrite(FIFO_RCK, LOW);   
        digitalWrite(FIFO_RCK, HIGH);          
        t1 = ov7670_data();       
        digitalWrite(FIFO_RCK, LOW);          
        digitalWrite(FIFO_RCK, HIGH);          
        t2 = ov7670_data();  
        Serial.write(t2); 
        Serial.write(t1);                         
      }
    }
    digitalWrite(FIFO_OE, HIGH);       
  }  
    if(Vsync_Flag >= 2)
    {
        Serial.write(0xFE);
        Serial.write(0x01);
        pinMode(VSYNC, INPUT_PULLUP);
        detachInterrupt(digitalPinToInterrupt(VSYNC));     
      }
     else
     {
        pinMode(VSYNC, INPUT_PULLUP);
        attachInterrupt(digitalPinToInterrupt(VSYNC), OV7670, FALLING);//设置触发中断的端口,中断后运行的程序和触发模式
      } 
  }

  unsigned char ov7670_data(void)
  {
    unsigned char tmp;
    unsigned char DATA7 = 0;
    unsigned char DATA6 = 0;
    unsigned char DATA5 = 0;
    unsigned char DATA4 = 0;
    unsigned char DATA3 = 0;
    unsigned char DATA2 = 0;
    unsigned char DATA1 = 0;
    unsigned char DATA0 = 0;
    tmp = 0;

    DATA7 = (unsigned char)digitalRead(D7);
    DATA6 = (unsigned char)digitalRead(D6);
    DATA5 = (unsigned char)digitalRead(D5);
    DATA4 = (unsigned char)digitalRead(D4);
    DATA3 = (unsigned char)digitalRead(D3);
    DATA2 = (unsigned char)digitalRead(D2);
    DATA1 = (unsigned char)digitalRead(D1);
    DATA0 = (unsigned char)digitalRead(D0);
    tmp = (DATA7 << 7) | (DATA6 << 6) | (DATA5 << 5) | (DATA4 << 4)\
         |(DATA3 << 3) | (DATA2 << 2) | (DATA1 << 1) | (DATA0 << 0);

    return tmp;
  }
  

驱动程序的大概流程是

1)初始化Arduino的所有IO口。

2)硬件复位OV7670模块

3)检测和模块之间的I2C通信,并通过I2C接口配置模块。(即对模块进行初始化,比如配置模块为彩色数据而非黑白,分辨率为320*240等)。

4)开启中断,等待摄像头1帧数据的开始。第一帧数据放弃,仅用来对齐FIFO的地址。第二帧数据开始,打开FIFO,将摄像头拍摄的数据取出并通过串口上传到电脑。

5)程序结束

4.效果

先放上视频

Arduino UNO 摄像头OV7670效果https://www.zhihu.com/video/1109978486093078528
实际上,图片拍的不好,有些地方出现噪点及图片错位等问题。
兔子估计可能是接的杜邦线太长以及接触不良,信号速度又比较快,相互之间有干扰导致。如果直接使用PCB板走线而不是用杜邦线接线,效果会好很多。
兔子我也是拍了好几次,才有几幅看起来比较OK的图片。

5.操作流程

我们Arduino程序,最终目的是将图像数据通过串口传输到电脑上显示。这就需要我们电脑上有对应的软件。

兔子我找了很久,找到一款名叫"山外多功能调试助手的软件"。

首先,我们打开它。

将Arduino UNO板卡通过USB数据线插到我们电脑上。

1)选择智能车助手

2)串口配置,端口选择下拉框里的唯一的一个接口。(接上Arduino板卡后,会自动显示端口号。不插Arduino板卡则不会显示)

波特率调整为256000

数据位为8

校验无

停止位为1

3)选择为摄像头

4)图片配置为彩色图像,设置宽和高分别为320,240。RGB565小端显示。

5)最后点击 打开串口。

Arduino板卡就会开始自动上传数据,等待大约10秒,图片就显示在软件的右侧。

Arduino UNO 摄像头驱动显示


如果想再次刷新图片,点击软件的关闭串口按钮。再点击打开串口。等待10秒左右,新的图片就会覆盖旧图片。

6.资料

资料已经整理好,为电脑端的山外调试助手软件及OV7670摄像头源代码。

可以在百度云链接下载

链接:pan.baidu.com/s/1YReSw1

提取码:cpbh