大家好,我是兔子。嵌入式工程师。
专业角度带你玩转Arduino.
这次教大家用Arduino UNO来驱动摄像头OV7670拍照。
ov7670是OmniVision公司生产的一款感光整列,30W像素。
如图所示,就是这个类型芯片的东西。
然后我们给感光整列配上镜头,加上他的外围电路,就成了某宝热卖的OV7670摄像头模块。
模块如图所示:
我们这次使用的是OV7670带FIFO的模块。(FIFO芯片->背面图,最大的一个28脚的芯片)
为什么要用带FIFO的模块呢?因为Arduino UNO速度比较慢,带FIFO可已将摄像头拍摄的数据暂时存在FIFO里,然后我们的Arduino UNO再慢慢的将拍摄的数据读出来,通过串口发送到串口上位机显示。
FIFO,即first in first out的缩写。在这里,FIFO的速度很快,可以将摄像头的数据暂时存起了。以便我们慢速的CPU将获得的数据慢慢取出来并处理,达到一个类似水库的作用。
我们可以看到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
其余摄像头针脚可以不接(悬空)。
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)程序结束
先放上视频
实际上,图片拍的不好,有些地方出现噪点及图片错位等问题。
兔子估计可能是接的杜邦线太长以及接触不良,信号速度又比较快,相互之间有干扰导致。如果直接使用PCB板走线而不是用杜邦线接线,效果会好很多。
兔子我也是拍了好几次,才有几幅看起来比较OK的图片。
我们Arduino程序,最终目的是将图像数据通过串口传输到电脑上显示。这就需要我们电脑上有对应的软件。
兔子我找了很久,找到一款名叫"山外多功能调试助手的软件"。
首先,我们打开它。
将Arduino UNO板卡通过USB数据线插到我们电脑上。
1)选择智能车助手
2)串口配置,端口选择下拉框里的唯一的一个接口。(接上Arduino板卡后,会自动显示端口号。不插Arduino板卡则不会显示)
波特率调整为256000
数据位为8
校验无
停止位为1
3)选择为摄像头
4)图片配置为彩色图像,设置宽和高分别为320,240。RGB565小端显示。
5)最后点击 打开串口。
Arduino板卡就会开始自动上传数据,等待大约10秒,图片就显示在软件的右侧。
如果想再次刷新图片,点击软件的关闭串口按钮。再点击打开串口。等待10秒左右,新的图片就会覆盖旧图片。
资料已经整理好,为电脑端的山外调试助手软件及OV7670摄像头源代码。
可以在百度云链接下载
链接:https://pan.baidu.com/s/1YReSw1vC_NoVkTPntGPfyQ
提取码:cpbh