Have you ever wanted to build your own wearable spy camera, UAV or other small, camera-enabled gizmo, gadget or device? While Arduino provides a wonderful prototyping platform for creating all sorts of DIY electronic gadgets, experimentations in physical computing, robotic artworks, and the like, it’s slim pickins’ when it comes to finding a tiny, easy-to-use digital camera to pair it up with. Fortunately, a company out of Hong Kong, COMedia Ltd., makes the C328R, a relatively small cell-phone style camera module that includes built-in JPEG image compression and UART serial communication. Couple the camera module with an Arduino and external storage (EEPROM, microSD, etc.) and you can have an instant Arduino-powered digital still image or video capture solution. That is to say, “instant,” once you have a working software driver … Fortunately for you, I’ve already done that work.
While working on a pigeon-based aerial photography solution as part of PigeonBlog for Beatriz da Costa, I wrote a library in C++ that allows the Arduino to communicate with the C328R camera over UART using the camera’s built-in communications protocol. Here’s a sample picture to prove that it works:

Sample C328R picture
The camera can take pictures in a variety of color depths (2- to 8-bit grayscale, 12- to 16-bit color, JPEG) and resolutions (80×64 to 640×480) with serial communication up to 115,200 baud. It’s fairly versatile and not terribly expensive (~$50) given the fact that it does a lot of work for you (i.e. on-board JPEG compression).
The rest of this post assumes that you have the camera and an Arduino Duemilanove (or similar) in hand. If you don’t, I suggest that you get them. You’re also going to need some type of external storage for the pictures, because the Arduino doesn’t provide any sufficient storage on-board. The sample code that follows assumes a 256KB Atmel SPI serial EEPROM or similar SPI EEPROM.
How to Use the Camera
Using the C328R camera involves a fairly straightforward issuance of commands. After power on, you essentially just have to synchronize with the camera, tell it what size and color-depth you want your pictures to be, and optionally set the byte size of the data packets it will send back to you (default 64 bytes), light frequency to use, etc. It looks something like this, assuming you want a JPEG photo:
- Power on camera.
- Issue “sync” command.
- Issue “initial” command (tell the camera the desired color-depth, resolution, etc.).
- Wait about 2 seconds for the camera to settle.
- Issue “snapshot” command. This will tell the camera to take a snapshot and store it in its internal memory.
- Issue “getPicture” command. The camera will then begin sending the JPEG image over serial in a series of data packets (default 64 bytes).
- Take each data packet and store its image data sequentially in the EEPROM, until all image data has been retrieved. (A 640×480 JPEG can be upwards of 20KB. At 64 bytes a packet, this would be 320 individual data packets per photo.)
All of these commands are issued over a proprietary serial communications protocol. The C328R Arduino library takes care of all of this behind the scenes, and reduces the complexity to nothing more than a series of function calls.
Download the Library
You can download the camera library here. Standard Arduino library installation instructions apply.
Making the Connections
The camera should be powered at 3.3V, with the TX (yellow wire) connected to the RX pin on the Arduino, and the RX (green wire) connected to the TX pin on the Arduino. The wiring that comes with the camera is thin and stranded, so you may want to solder on some headers for a more robust electrical connection, especially if you plan on using a breadboard.
The SPI EEPROM, assuming you’re using an Atmel SPI EEPROM or another SPI EEPROM with the same pin layout, should be connected per the instructions found here.
An LED, with appropriate resistor, should be connected to pin 8. This will be used as an indicator LED to let you know when the Arduino is done writing the picture to the EEPROM.
Sample Code and Application
The following sample code operates in two parts: The first part captures a picture with the camera, stores it on an external EEPROM at address 0, then powers off the camera. The second part sends the JPEG image data from the EEPROM to the computer over USB, allowing a simple program written in Processing to read the data and save it to disk for viewing.
IMPORTANT NOTE: This sample code is designed primarily for testing and debugging purposes. Because the Arduino has only one hardware serial port, and the camera and computer (via USB) are both connected to that port, we use the indicator LED to notify us when the Arduino is done saving the data to the EEPROM and has powered off the camera. This way we know that any subsequent data being sent over serial is picture data that should be captured on the computer and saved to disk. By default, the code gives us 5 seconds to MANUALLY tell the Processing sketch to begin listening for and saving the serial data. You do this simply by pressing any key on the keyboard when the Processing sketch is running. After it is done reading the data, press any key again to write it to disk.
First up, the Arduino code. You will need to have the SPI library already installed. Save this as a sketch and upload it to your Arduino:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | #include "CameraC328R.h" #include "Spi.h" #define LED_PIN 8 #define PAGE_SIZE 64 #define BAUD 38400 // EEPROM Opcodes #define WREN 6 #define WRDI 4 #define RDSR 5 #define WRSR 1 #define READ 3 #define WRITE 2 // Buffer for EEPROM data byte pageBuffer[PAGE_SIZE]; CameraC328R camera; static uint16_t pictureSizeCount = 0; static uint16_t pageBufferIndex = 0; static uint16_t currentAddress = 0; bool dirtyBuffer = false; /** * Reads one byte from the EEPROM at the given address. */ byte readByte( uint16_t address ) { byte data; digitalWrite( SS_PIN, LOW ); Spi.transfer( READ ); Spi.transfer( (byte)(address >> 8) ); Spi.transfer( (byte)(address) ); data = Spi.transfer( 0xFF ); digitalWrite( SS_PIN, HIGH ); return data; } /** * Writes the data in the buffer to the EEPROM at the page * starting at the given address. */ void writeBuffer( uint16_t address, uint16_t bufferSize ) { // Write enable digitalWrite( SS_PIN, LOW ); Spi.transfer( WREN ); digitalWrite( SS_PIN, HIGH ); delay( 10 ); // Begin write digitalWrite( SS_PIN, LOW ); Spi.transfer( WRITE ); // Send the address Spi.transfer( (byte)(address>>8) ); // MSB Spi.transfer( (byte)(address) ); // LSB // Send the data for( uint16_t i = 0; i < bufferSize; i++ ) { Spi.transfer( pageBuffer[i] ); } digitalWrite( SS_PIN, HIGH ); } /** * Fills the page buffer for the EEPROM with data. */ void fillPageBuffer( byte* data, uint16_t dataSize ) { for( uint16_t i = 0; i < dataSize; i++ ) { pageBuffer[pageBufferIndex] = data[i]; dirtyBuffer = true; pageBufferIndex++; if( pageBufferIndex == PAGE_SIZE && dirtyBuffer ) { pageBufferIndex = 0; writeBuffer( currentAddress, PAGE_SIZE ); currentAddress += PAGE_SIZE; dirtyBuffer = false; delay( 50 ); } } } /** * Sends a picture to the computer. */ void transferPicture( uint16_t startAddress, uint16_t size ) { uint16_t endAddress = startAddress + size; for( uint16_t i = startAddress; i < endAddress; i++ ) { Serial.print( readByte( i ), BYTE ); } } /** * This callback is called EVERY time a JPEG data packet is received. */ void getJPEGPicture_callback( uint16_t pictureSize, uint16_t packageSize, uint16_t packageCount, byte* package ) { // packageSize is the size of the picture part of the package pictureSizeCount += packageSize; // package contains everything in the package fillPageBuffer( package, packageSize ); if( pictureSizeCount == pictureSize ) { // Is there still stuff in the buffer? if( dirtyBuffer ) { writeBuffer( currentAddress, pageBufferIndex ); } camera.powerOff(); digitalWrite( LED_PIN, HIGH ); // DONE! Serial.flush(); delay( 5000 ); // Give us 5 seconds to hit a key ... transferPicture( 0, pictureSize ); } } void setup() { Serial.begin( BAUD ); digitalWrite( SS_PIN, HIGH ); // disable device pinMode( LED_PIN, OUTPUT ); if( !camera.sync() ) { Serial.println( "Sync failed." ); return; } if( !camera.initial( CameraC328R::CT_JPEG, CameraC328R::PR_160x120, CameraC328R::JR_640x480 ) ) { Serial.println( "Initial failed." ); return; } if( !camera.setPackageSize( 64 ) ) { Serial.println( "Package size failed." ); return; } if( !camera.setLightFrequency( CameraC328R::FT_50Hz ) ) { Serial.println( "Light frequency failed." ); return; } // Let camera settle, per manual delay(2000); if( !camera.snapshot( CameraC328R::ST_COMPRESSED, 0 ) ) { Serial.println( "Snapshot failed." ); return; } if( !camera.getJPEGPicture( CameraC328R::PT_JPEG, PROCESS_DELAY, &getJPEGPicture_callback ) ) { Serial.println( "Get JPEG failed." ); return; } } void loop() { } |
Next up, the Processing code. This code doubles as a serial monitor, so you can run it as soon as you plug in the Arduino to USB, and ignore the built-in Arduino serial monitor. Again, note that you must hit a key within 5 seconds of the LED lighting up, and again once data is done reading to save it to disk. By default, it will be saved as a file called “photo.jpg” in the same folder as your Processing sketch.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | import processing.serial.*; Serial myPort; String filename = "photo.jpg"; byte[] photo = {}; Boolean readData = false; void setup() { println( Serial.list() ); myPort = new Serial( this, Serial.list()[0], 38400 ); } void draw() { byte[] buffer = new byte[64]; if( readData ) { while( myPort.available() > 0 ) { int readBytes = myPort.readBytes( buffer ); print( "Read " ); print( readBytes ); println( " bytes ..." ); for( int i = 0; i < readBytes; i++ ) { photo = append( photo, buffer[i] ); } } } else { while( myPort.available() > 0 ) { print( "COM Data: " ); println( myPort.readString() ); } } } void keyPressed() { if( photo.length > 0 ) { readData = false; print( "Writing to disk " ); print( photo.length ); println( " bytes ..." ); saveBytes( filename, photo ); println( "DONE!" ); } else { readData = true; myPort.clear(); println( "Waiting for data ..." ); } } |
That should be enough to get started. The sample code above does not cover using the camera for RAW image capture, which is considerably faster and more useful as a solution for video (5 frames per second), but the library includes this functionality.
Feel free to post questions, comments or ideas below.
Tags: arduino, c++, camera, photography, processing
-
hi…
thanks for this cool project!
i would like to send a captured picture over the ethernet-shield to a web or ftp-server.
do you think this is possible?
could this work without a memory-card?
greetings..
…eok -
Hi,
Im trying to get this code to work as a demo to further use of this camera…
When I run the sample processing code, the sync command works, but the ‘Initial’ command fails. The ACK I am getting back is AA0F00… instead of AA0E01…(ie AA ‘Ack’ ‘initial’…).Timing issue? A quick look at other commands shows their ACKs to be wrong too…
However when I connect the camera directly to th computer and run the codeproject c# code, this command works fun….
Just getting the logic analyser out now, but any help would be appreciated…
Thanks
-
ok sorry ignore my previous comment it was ill-formed
anyways, using this code, keep getting NAKs with F0h ‘Command Header Error’ back from the C328 for the majority of commands…by changing the code to keep re-sending commands until one actually succeeds (gets ACKd), can generally get the first few commands to work eventually (some commands still always fail…)
Have looked at signals with a logic analyser, sent commands look perfect… only source of error I can guess at is from the shared serial, so I have put delays between any other serial output (to PC) but still getting same problem…
Im using Arduino Duemilanove with ATmega328
Any tips greatly appreciated….
-
Thanks Sean for the initial library and explanation and to others that added helpful comments.
I managed to hook up my C328 via the NewSoftSerial library and connect to the Arduino board using Telnet. My sketch is written such that it will stream back a JPEG’s worth of bytes to the connected client and then force a disconnect.
My purpose for this sketch is to be able to connect remotely at intervals and retrieve an image. The problem is, the retrieved bytes don’t seem to make a valid JPEG. The actual serial monitor I have indicates that everything is in working order, it is purely the returned data that seems to be the problem.
Is there anything I need to do to the returned bytes before they can be classed as a valid JPEG?
Regards,
Whytey -
Just to follow up with a solution, which was really easy really, I just needed to sleep on it and spend a day at work away from it.
Anyways, I created a python script which connects to the socket, receives the data and writes it to a file as follows…
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((“192.168.0.225″, 23))
print “connected”
s.send(“r”)
file = open(“test.jpg”,”wb”)while 1:
data = s.recv(1024)
if not data: break
file.write(data)
print(data)
s.close()
file.close()And so now, the sketch which will take a photo and return to the result via the ethernet shield, and without the need for writing it to local storage is as follows…
/*
* Web Server
*
* A simple web server that shows the value of the analog input pins.
*/#include
#include
#include#define USB_BAUD 115200
#define CAMERA_BAUD 14400#define PAGE_SIZE 256
int LED_PIN = 8;
int BUZZ_PIN = 7;byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = {
192, 168, 0, 225 };uint16_t pictureSizeCount = 0;
Server server(23);
NewSoftSerial mySerial(2, 3);
CameraC328R camera(&mySerial);/**
* This callback is called EVERY time a JPEG data packet is received.
*/
void getJPEGPicture_callback( uint16_t pictureSize, uint16_t packageSize, uint16_t packageCount, byte* package )
{
// packageSize is the size of the picture part of the package
pictureSizeCount += packageSize;server.write(package,packageSize);
if( pictureSizeCount >= pictureSize )
{
digitalWrite( LED_PIN, LOW );
Serial.flush();
}}
void setup()
{
Ethernet.begin(mac, ip);
server.begin();Serial.begin( USB_BAUD );
mySerial.begin(CAMERA_BAUD);pinMode( LED_PIN, OUTPUT );
pinMode( BUZZ_PIN, OUTPUT );digitalWrite( LED_PIN, LOW );
digitalWrite( BUZZ_PIN, LOW );}
void loop()
{
Client client = server.available();if( client ){
digitalWrite( LED_PIN, HIGH );
if( !camera.sync() )
{
Serial.println( “Sync failed.” );
return;
}if( !camera.initial( CameraC328R::CT_JPEG, CameraC328R::PR_160×120, CameraC328R::JR_640×480 ) )
{
Serial.println( “Initial failed.” );
return;
}if( !camera.setPackageSize( 64 ) )
{
Serial.println( “Package size failed.” );
return;
}if( !camera.setLightFrequency( CameraC328R::FT_50Hz ) )
{
Serial.println( “Light frequency failed.” );
return;
}if( !camera.snapshot( CameraC328R::ST_COMPRESSED, 0 ) )
{
Serial.println( “Snapshot failed.” );
return;
}pictureSizeCount = 0;
if( !camera.getJPEGPicture( CameraC328R::PT_JPEG, PROCESS_DELAY, &getJPEGPicture_callback ) )
{
Serial.println( “Get JPEG failed.” );
return;
}client.stop();
}
}Just to confirm, I am using software sockets so I can also debug via USB and also a modified version of Sean’s driver as linked by arms22 http://arms22.blog91.fc2.com/blog-entry-261.html
Lots of polish is required to my code to do what I want it to do, but at least I have pictures now
Regards,
Whytey -
Hi there, your work looks good!
I have found this camera on sparkfun
http://www.sparkfun.com/commerce/product_info.php?products_id=9334will this be suitable or would it require tweaking of the code?
Thanks,
Henry
-
Hi Sean,
I am using your library and is trying to call “getRawPicture” function and is having difficulties. This function accepts 4 inputs, one of the, is PictureType, which I have no problem with, however, for the second input, what exactly do you need to pass?
-
really good project .
can i have the schematics please -
how can i save the contents to my sd card rather than transfering to pc
-
Thanks for this library, Sean. Have you had a chance to look at using the NewSoftSerial library, so the Arduino can talk to the camera on one serial port, and to another device on another serial port at the same time?
‹ Previous · 1 · 2
61 comments
Comments feed for this article
Trackback link: http://gizmologi.st/2009/04/taking-pictures-with-arduino/trackback/