/**********************************************************************
OV7670_camera_color.ino
Code by lingib
https://www.instructables.com/member/lingib/instructables/
Last update 6 December 2018
This program samples output from an OV7670 camera chip.
Luminance and chroma data are sent on alternate scans to a PC frame-grabber.
The camera gain has been set to manual.
A neutral density filter in fromt of the OV7670 lens helps in bright light.
----------
COPYRIGHT
----------
This code is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License. If
not, see .
***************************************************************************/
/*
Arduino Uno R3 OV7670
+----------------+ +----------------+
| | | |
| 3v3 |-----------+----+----- -+------| 3v3 |
| | | | | | |
| | | | +------| Reset |
| | +++ +++ | |
| | 2*4K7 | | | | | |
| | +++ +++ | |
| | | | | |
| (SCL) A4 |----------------+--------------| SIOB |
| (SDA) A5 |-----------+-------------------| SIOC |
| | | |
| D8 |----<--------------------------| PCLK |
| D9 |----<--------------------------| HREF |
| D10|----<--------------------------| VSYNC |
| D11|---->-----------+ | |
| | | | |
| | +++ | |
| | 4K7 | | | |
| | +++ 8Mhz | |
| | +------->------| XCLK |
| | +++ | |
| | 4K7 | | | |
| | +++ | |
| | | | |
| | /// | |
| | | |
| A0 |----<--------------------------| D0 |
| A1 |----<--------------------------| D1 |
| A2 |----<--------------------------| D2 |
| A3 |----<--------------------------| D3 |
| D4 |----<--------------------------| D4 |
| D5 |----<--------------------------| D5 |
| D6 |----<--------------------------| D6 |
| D7 |----<--------------------------| D7 |
| | | |
| | +------| PWDN |
| | | | |
| GND |----------------+-------+------| GND |
| | | | |
+----------------+ /// +----------------+
Note: HREF is not required if we supress the PCLK pulses when HREF is LOW
*/
// =============
// globals
// =============
/* libraries */
#include
/* Arduino */
#define D0 A0 //data lines from OV7670
#define D1 A1
#define D2 A2
#define D3 A3
#define D4 4
#define D5 5
#define D6 6
#define D7 7
/* OV7670 */
#define OV7670 0x21 // OV7670 address
#define PCLK 8 //pixel clock from OV7670
#define HREF 9 //horizontal sync from 0V7670 (not used)
#define VSYNC 10 //vertical sync from OV7670
#define XCLK 11 //8MHz output to OV7670
/* Flags */
boolean Start = false;
boolean Odd_frame = true;
// ====================
// setup()
// ====================
void setup()
{
/* Disable Arduino's 5 volt internal pullup resistors */
MCUCR |= (1 << PUD); // ATmega328P datasheet 14.4.1
/* configure Arduino inputs */
pinMode(VSYNC, INPUT); // OV7670 timing
pinMode(HREF, INPUT);
pinMode(PCLK, INPUT);
pinMode(D0, INPUT); //OV7670 data
pinMode(D1, INPUT);
pinMode(D2, INPUT);
pinMode(D3, INPUT);
pinMode(D4, INPUT);
pinMode(D5, INPUT);
pinMode(D6, INPUT);
pinMode(D7, INPUT);
/* Generate 8MHz XCLK on pin 11 */
noInterrupts();
pinMode(XCLK, OUTPUT);
TCCR2A = (1 << COM2A0) | (1 << WGM21) | (1 << WGM20);
TCCR2B = (1 << WGM22) | (1 << CS10);
OCR2A = 0;
interrupts();
/* Configure serial port */
Serial.begin(1000000);
/* Initialise the I2C bus */
Wire.begin();
/* Restore default register values */
reset_registers();
/* Initialise OV7670 camera chip */
initialise_OV7670();
/*
For debug use only ... comment out the
main loop() when using this routine.
*/
//read_registers(); // Display register contents
}
// =============
// main loop
// =============
void loop()
{
/* Odd frame */
if (Start & Odd_frame)
{
capture_odd_bytes(640, 480); // Capture odd numbered bytes (i.e 1,3,5,7,9...)
Odd_frame = false; // Select even frame
}
/* Even frame */
if (Start & !Odd_frame)
{
capture_even_bytes(640, 480); // Capture even numbered bytes (i.e. 2,4,6,8,10,12...)
Odd_frame = true; // Select odd frame
Start = false; // Only perform tasks once
}
}
// ====================
// serial event()
// ====================
void serialEvent()
{
while (Serial.available())
{
char inChar = (char) Serial.read();
if ((inChar == 'c') | (inChar == 'C'))
{
Start = true; // Toggle start flag
}
}
}
// ================================
// capture_odd_bytes(width, height)
// ================================
/*
+-+
VSYNC: | |
---+ +--------------------------------------//------------------------------------//----
+-----------------------------//--------------------------------+
HREF: blanking | |
---------//---+ Sample Skip Sample Skip Sample Skip Sample Skip +---//----
---------//---+ +---+ +---+ +---+ +-//+ +---+ +---+ +---+ +---+---//----
PCLK: blanking | U | Y | V | Y | U | Y | V | Y
+---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+.
Odd bytes 1 3 5 7
Notes:
(1) The PCLK pulses are 8uS apart
(2) It takes at least 10uS to transmit one data byte across a 1Mbps link (8-data, 1-start, 1-stop)
(3) If we only sample every second byte the sample interval is 16uS which leaves approx. 6uS to spare.
(4 A monochrome image may be obtained by sampling the luminance (Y) bytes.
(5) A color image may be obtained by sampling the chroma bytes on alternate frames and combining the two.
*/
void capture_odd_bytes(int width, int height)
{
/* Locals */
int column_counter = width; // Column counter
int row_counter = height; // Row counter
byte data; // OV7670 output data
/* Capture image */
cli(); // Disable interrupts
/* Wait for the frame to start */
while (!(PINB & B00000100)); // Wait until VSYNC pin 10 goes high
/* For each row */
while (row_counter--) // Row down-counter
{
column_counter = width; // Reset column down-counter
/* For each column */
while (column_counter--) // Column down-counter
{
/* Read first byte */
while (PINB & B00000001); // Wait until PCLK pin 8 is low
data = (PIND & B11110000) | (PINC & B00001111); // Read data
if (data == '\n') data--; // '\n' characters not allowed
Serial.write(data); // Send data to PC
while (!(PINB & B00000001)); // Wait until PCLK pin 8 is high
/* Skip second byte */
while (PINB & B00000001); // Wait until PCLK pin 8 is low
while (!(PINB & B00000101)); // Wait until PCLK pin 8 is high
}
}
/* Send '\n' terminator */
Serial.write('\n'); // Send linefeed to PC
sei(); // Enable interrupts
}
// =============================
// capture_even_bytes(width, height)
// =============================
/*
+-+
VSYNC: | |
---+ +--------------------------------------//------------------------------------//----
+-----------------------------//--------------------------------+
HREF: blanking | |
---------//---+ Skip Sample Skip Sample Skip Sample Skip Sample +---//----
---------//---+ +---+ +---+ +---+ +-//+ +---+ +---+ +---+ +---+---//----
PCLK: blanking | U | Y | V | Y | U | Y | V | Y
+---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+.
Even bytes 2 4 6 8
Notes:
(1) The PCLK pulses are 8uS apart
(2) It takes at least 10uS to transmit one data byte across a 1Mbps link (8-data, 1-start, 1-stop)
(3) If we only sample every second byte the sample interval is 16uS which leaves approx. 6uS to spare.
(4 A monochrome image may be obtained by sampling the luminance (Y) bytes.
(5) A color image may be obtained by sampling the chroma bytes on alternate frames and combining the two.
*/
void capture_even_bytes(int width, int height)
{
/* Locals */
int column_counter = width; // Column counter
int row_counter = height; // Row counter
byte data; // OV7670 output data
/* Capture image */
cli(); // Disable interrupts
/* Wait for the frame to start */
while (!(PINB & B00000100)); // Wait until VSYNC pin 10 goes high
/* For each row */
while (row_counter--) // Row down-counter
{
column_counter = width; // Reset column down-counter
/* For each column */
while (column_counter--) // Column down-counter
{
/* Skip first byte */
while (PINB & B00000001); // Wait until PCLK pin 8 is low
while (!(PINB & B00000001)); // Wait until PCLK pin 8 is high
/* Read second byte */
while (PINB & B00000001); // Wait until PCLK pin 8 is low
data = (PIND & B11110000) | (PINC & B00001111); // Read data
if (data == '\n') data--; // '\n' characters not allowed
Serial.write(data); // Send data to PC
while (!(PINB & B00000001)); // Wait until PCLK pin 8 is high
}
}
/* Send '\n' terminator */
Serial.write('\n'); // Send 'linefeed' to PC
sei(); // Enable interrupts
}
// ====================
// initialise_OV7670()
// ====================
void initialise_OV7670()
{
/* PCLK */
write_register(0x11, B10111111); // CLCKRC Finternal=XCLK/(Bits[5:0]+1)=8MHz/64=0.125MHz (8uS)
write_register(0x15, B00100000); // COM10 Bit[5]=PCLK does not toggle during blanking
/*
Horizontal positioning.
Each frame is equivalent to 784*510 pixels of which only 640*480 pixels are dislayed.
With the default HSTART, HSTOP, & HREF values, the image has a black band down left
hand side. The image moves left when we increase the HSTART value.
0 784 784
0 +----------------------------+----------------------------+------//--
| | |
| 158 14 158 14
10 | +---------------------+ +---------------------+
| | | | |
| | | | |
| | Image 1 | | Image 2 |
| | 640*480 | | 640*480 | (158+640-784=14)
| | | | |
| | | | |
490 | +---------------------+ +---------------------+
| | |
510 +----------------------------+----------------------------+------//--
Frame 1 Frame 2
The image sensor array is 656*488 pixels
*/
write_register(0x17, 0x13); // HSTART B00010011110=158 HSTART[7:0],HREF[2:0]
write_register(0x18, 0x01); // HSTOP B00000001110=14 HSTOP[7:0],HREF[5:3]
write_register(0x32, 0xB6); // HREF B10110110
// /* Disable AGC|AWB|AEC */
// write_register(0x13, B00001000); // COM8 (was 0x8F)
//
// /* AEC/AGC gain ...
// In automatic mode these values get overridden.
// In manual mode we write our own values.
// AEC currently set to zero as exposure time is long.
// */
// write_register(0x07, B00000000); // AECHH AEC[15:10] (was 0x00) Change AECHH[5:0] to suit
// write_register(0x10, B00100000); // AECH AEC[9:2] (was 0x40) Change AECH[7:0] to suit
// write_register(0x04, B00000000); // COM1 AEC[1:0] (was 0x00) Change COM1[1:0} to suit
}
// ====================
// read_register()
// ====================
void read_register(byte regID)
{
// ----- point to register
Wire.beginTransmission(OV7670); //I2C address
Wire.write(regID); //identify the register
Wire.endTransmission();
delay(10);
// ----- read data
Wire.requestFrom(OV7670, 1); //request one byte
if (Wire.available())
{
while (!(UCSR0A & (1 << UDRE0))); //wait for TX shift register to empty
show_hex(regID); //display regID
while (!(UCSR0A & (1 << UDRE0))); //wait for TX shift register to empty
UDR0 = ':'; //display ':' delimiter
while (!(UCSR0A & (1 << UDRE0))); //wait for TX shift register to empty
show_hex(Wire.read()); //display data
while (!(UCSR0A & (1 << UDRE0))); //wait for TX shift register to empty
UDR0 = '\n'; //send linefeed
}
}
// ====================
// read_registers()
// ====================
void read_registers()
{
for (byte regID = 0x00; regID < 0xCA; regID++)
{
read_register(regID);
}
}
// ====================
// reset_registers()
// ====================
void reset_registers()
{
write_register(0x12, 0x80); //see section "8.1.1 Register Reset"
delay(100); //allow registers to settle
}
// ====================
// show_hex()
// ====================
void show_hex(byte data)
{
/* separate byte into nibbles */
byte MSN = data >> 4; //most significant nibble
byte LSN = data & B00001111; //least significant nibble
/* display MSN */
while (!(UCSR0A & (1 << UDRE0))); //wait for TX shift register to empty
if (MSN == B00000000) UDR0 = '0';
if (MSN == B00000001) UDR0 = '1';
if (MSN == B00000010) UDR0 = '2';
if (MSN == B00000011) UDR0 = '3';
if (MSN == B00000100) UDR0 = '4';
if (MSN == B00000101) UDR0 = '5';
if (MSN == B00000110) UDR0 = '6';
if (MSN == B00000111) UDR0 = '7';
if (MSN == B00001000) UDR0 = '8';
if (MSN == B00001001) UDR0 = '9';
if (MSN == B00001010) UDR0 = 'A';
if (MSN == B00001011) UDR0 = 'B';
if (MSN == B00001100) UDR0 = 'C';
if (MSN == B00001101) UDR0 = 'D';
if (MSN == B00001110) UDR0 = 'E';
if (MSN == B00001111) UDR0 = 'F';
/* display LSN */
while (!(UCSR0A & (1 << UDRE0))); //wait for TX shift register to empty
if (LSN == B00000000) UDR0 = '0';
if (LSN == B00000001) UDR0 = '1';
if (LSN == B00000010) UDR0 = '2';
if (LSN == B00000011) UDR0 = '3';
if (LSN == B00000100) UDR0 = '4';
if (LSN == B00000101) UDR0 = '5';
if (LSN == B00000110) UDR0 = '6';
if (LSN == B00000111) UDR0 = '7';
if (LSN == B00001000) UDR0 = '8';
if (LSN == B00001001) UDR0 = '9';
if (LSN == B00001010) UDR0 = 'A';
if (LSN == B00001011) UDR0 = 'B';
if (LSN == B00001100) UDR0 = 'C';
if (LSN == B00001101) UDR0 = 'D';
if (LSN == B00001110) UDR0 = 'E';
if (LSN == B00001111) UDR0 = 'F';
}
// ====================
// write_register()
// ====================
void write_register(byte regID, byte regVal)
{
// ----- point to register
Wire.beginTransmission(OV7670); //I2C address
Wire.write(regID); //identify the register
delay(10);
// ----- write data to register
Wire.write(regVal); //write the data
Wire.endTransmission();
delay(10);
}