Taking Pictures with Arduino

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

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:

  1. Power on camera.
  2. Issue “sync” command.
  3. Issue “initial” command (tell the camera the desired color-depth, resolution, etc.).
  4. Wait about 2 seconds for the camera to settle.
  5. Issue “snapshot” command. This will tell the camera to take a snapshot and store it in its internal memory.
  6. Issue “getPicture” command. The camera will then begin sending the JPEG image over serial in a series of data packets (default 64 bytes).
  7. 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: , , , ,

  1. eok’s avatar

    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

  2. Sean’s avatar

    @JR: Good point. I was using a 3.3V Arduino mini at the time, so it never really crossed my mind.

  3. stu’s avatar

    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

  4. stu’s avatar

    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….

  5. Whytey’s avatar

    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

  6. Whytey’s avatar

    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

  7. henry’s avatar

    Hi there, your work looks good!

    I have found this camera on sparkfun
    http://www.sparkfun.com/commerce/product_info.php?products_id=9334

    will this be suitable or would it require tweaking of the code?

    Thanks,

    Henry

  8. Viktar Tatsiankou’s avatar

    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?

  9. Qasim’s avatar

    really good project .
    can i have the schematics please

  10. Qasim’s avatar

    how can i save the contents to my sd card rather than transfering to pc

  11. Daryl Wilding-McBride’s avatar

    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?

· 1 · 2