/**********************************************************************
OV7670_camera_mono.ino
Code by lingib
https://www.instructables.com/member/lingib/instructables/
Last update 6 December 2018
This program samples the YUV (4:2:2) output from an OV7670 camera chip
and sends the luminance data bytes to a frame-grabber via the Arduino USB cable.
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;
// ====================
// 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();
/*
Debug use only ...
Comment out the main loop() when using.
*/
//read_registers(); // Display register contents
}
// =============
// main loop
// =============
void loop()
{
/* Odd frame */
if (Start)
{
Start = false; // Run once
capture(640, 480); // Capture luminance data
}
}
// ====================
// serial event()
// ====================
void serialEvent()
{
while (Serial.available())
{
char inChar = (char) Serial.read();
if ((inChar == 'c') | (inChar == 'C'))
{
Start = true; // Toggle start flag
}
}
}
// =============================
// capture(width, height)
// =============================
/*
+-+
VSYNC: | |
---+ +--------------------------------------//------------------------------------//----
+-----------------------------//--------------------------------+
HREF: blanking | |
---------//---+ Skip Sample Skip Sample Skip Sample Skip Sample +---//----
---------//---+ +---+ +---+ +---+ +-//+ +---+ +---+ +---+ +---+---//----
PCLK: blanking | U | Y | V | Y | U | Y | V | Y
+---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+.
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) The luminance (Y) data bytes are 16uS apart which leaves aprox 6uS spare for data transfer.
(4) Theoretically HREF is not requred if the PCLK pulses are disabled during horizontal blanking but
I find the left hand edge of the image is cleaner when HREF is used to define the start of each line.
*/
void capture(int width, int height)
/*
For efficient image capture the PC requires a termination character at the end of each data stream. I have
chosen to use the linefeed character which is close to black and therefore not visible. Using a terminator,
however, means that we can not have unwanted termination characters in the data stream. The simple solution
is to change the value of any unwanted linefeed characters before that character is sent.
*/
{
noInterrupts(); // Disable interrupts
/* Locals */
int column_counter = width; // Column counter
int row_counter = height; // Row counter
byte data; // OV7670 output data
/* 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
interrupts(); // 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
// ----- manual gain
/*
Uncomment the following code lines for manual control.
The gain is set by writing a '1' into any bit position shown with an 'x'.
write_register(0x07, Bxxxxxxxx); // AECHH AEC[15:10] Change AECHH[5:0] to suit
write_register(0x10, Bxxxxxxxx); // AECH AEC[9:2] Change AECH[7:0] to suit
write_register(0x04, B000000xx); // COM1 AEC[1:0] Change COM1[1:0} to suit
*/
write_register(0x13, B00001000); // COM8 Disable AGC/AWB/AEC (was 0x8F)
write_register(0x07, B00000000); // AECHH AEC[15:10] (was 0x00)
write_register(0x10, B00000000); // AECH AEC[9:2] (was 0x40)
write_register(0x04, B00000000); // COM1 AEC[1:0] (was 0x00)
}
// ====================
// 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); // The datasheet says 1mS ... at least 3mS needed in practice
// ----- 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; // Meast 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);
}