Canon Camera Controller – nickname C-Cubed
The system contains the Arduino Mega micro processor, a 20 x 4 LCD display, a TSL230R light to frequency convertor, the DT1307 real time clock and a SHT10 temperature and humidity sensor. Please note that the SHT10 sensor is not fully compliant with the I2C protocol, it will be replaced by the SHT21 sensor which is compliant.
The following arduino software code has been developed to control most digital cameras. It allows you to define the interval between photographs and the the length of the pulse to trigger the camera. If the camera is in Auto or Manual modes then the pulse length should be around 50-60ms; in Bulb mode the pulse length can be increased to accurately control the bulb exposure time or used for bulb ramping in a time lapse sequence.
The code has been developed to avoid the use of delay() functions, except in the setup loop, this means that the buttons and LCD display remain fully functional and exposure times can be changed between photographs.
Mechanical and electrical details of the project can be seen here
ccc_main.pde is the main code, the remaining blocks should be put into individual tabs inside the Arduino interface.
History
v1.5 – first release
v1.6 – added routine that saves Interval and Exposure values to EEPROM and reloads them next time the system is used. Press Red button on startup to load default values.
v1.7 – minor bug fixes relating to the LCD display, zip file updated.
The Arduino code can be downloaded from here as a zip file. Updated to v1.7 13/06/10
Please be aware that the code blocks shown below may not be the latest version but the zip file will always be the latest version.
ccc_main.pde
/*
Canon Camera Controller (nickname C-Cubed)
version 1.6 - 03/06/10
Author - K Lewis www.photosbykev.com
The system uses the normal camera pulses to trigger the camera in auto/manual modes.
The pulse length can be adjusted so that it can also control the bulb exposures.
All timing loops are non blocking so the buttons and lcd remain functional.
Tested on Canon 7D and 40D cameras but will work on most systems with minor changes
History
v1.5 - first release
v1.6 - added routine that saves Interval and Exposure values and reloads them next time the system is used. Press Red button on startup to load default values
Things to do:
1. programme up a decent clock setting routine
2. interface the system to control DSLR Remote Pro to control ISO ramping
This program 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 program 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.
The core of the non blocking software is based on code
supplied by Shutterdrone on the OpenMoco website under
the terms of the GNU General Public License.
*/
// Libraries used
#include <LiquidCrystal.h> // library from LCD display
#include <MsTimer2.h> // library for interrupt control
#include "Wire.h" // used with DS1307
#include <stdlib.h> // string manipulation etc
#include <EEPROM.h> // used to save variables to eeprom
// Camera pin definitions
#define SHUTTER_PIN 22 // digital output for shutter
#define FOCUS_PIN 23 // digital output for focus tap
#define PCSYNC_PIN 24 // digital input for pcsync confirmation - not implimented yet
#define CAMLED_PIN 25 // digital output for "shutter fired" LED
// Button definitions
#define B_LT 49 // Left button
#define B_RT 50 // Right button
#define B_UP 51 // Up button
#define B_DN 52 // Down button
#define B_CT 53 // Centre button
unsigned long button_hit = 0; // time last button was hit
// define battery status variables
int batteryPin = 0; // analogue pin 0 hard wired to battery status line
float batteryValue; // true battery voltage
int batteryPercent; // % full against max reference voltage
float batteryMaxVolts = 4.15; // max reference voltage (full charge)
float batteryMinVolts = 3.35; // min battery voltage (low alarm at batteryMinVolts + 0.1v)
float batteryAverage = 0; // the average voltage
// variables used to smooth the voltage reading
const int numReadings = 20; // no. of samples for voltage smoothing
int readings[numReadings]; // the readings from the analog input
int index = 0; // the index of the current reading
int total = 0; // the running total
// assign other variables
char* clearline = " "; // empty line string
// buffer for string conversion
char buffer_Msg[] = "123456578901234567890";
// main display strings
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(31, 33, 35, 37, 39, 41);
#define lcd_LIGHT 29 // digital pin controlling LCD backlight
boolean lcd_SW; // flag for controlling backlight status
int lcd_TIMEOUT = 30000; // timeout delay before screen turns off
float lcd_TIMER; // Backlight timer used with millis()
int lcd_LAST_UPDATE = 200; // Only update display every 200 ms
float lcd_UPDATE_TM = 0; // variable to check against millis
// arrays used to build custom characters
byte UP[8] = // array to make an arrow pointing UP
{
B00000,
B00100,
B01110,
B11111,
B00100,
B00100,
B00100,
B00000
};
byte DOWN[8] = // array to make an arrow pointing DOWN
{
B00000,
B00100,
B00100,
B00100,
B11111,
B01110,
B00100,
B00000
};
byte LEFT[8] = // array to make an arrow pointing LEFT
{
B00000,
B00000,
B00100,
B01100,
B11111,
B01100,
B00100,
B00000
};
byte RIGHT[8] = // array to make an arrow pointing RIGHT
{
B00000,
B00000,
B00100,
B00110,
B11111,
B00110,
B00100,
B00000
};
/*
LiquidCrystal KEYWORDS
#######################################
# Methods and Functions
#######################################
begin
clear
home
print
setCursor
cursor
noCursor
blink
noBlink
display
noDisplay
autoscroll
noAutoscroll
leftToRight
rightToLeft
scrollDisplayLeft
scrollDisplayRight
createChar
*/
// setup RTC
#define DS1307_I2C_ADDRESS 0x68
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
return ( (val/10*16) + (val%10) );
}
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
return ( (val/16*10) + (val%16) );
}
// 1) Sets the date and time on the ds1307
// 2) Starts the clock
// 3) Sets hour mode to 24 hour clock
// Assumes you're passing in valid numbers
void setDateDs1307 (
byte second, // 0-59
byte minute, // 0-59
byte hour, // 1-23
byte dayOfWeek, // 1-7
byte dayOfMonth, // 1-28/29/30/31
byte month, // 1-12
byte year) // 0-99
{
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.send(0);
Wire.send(decToBcd(second)); // 0 to bit 7 starts the clock
Wire.send(decToBcd(minute));
Wire.send(decToBcd(hour)); // If you want 12 hour am/pm you need to set
// bit 6 (also need to change readDateDs1307)
Wire.send(decToBcd(dayOfWeek));
Wire.send(decToBcd(dayOfMonth));
Wire.send(decToBcd(month));
Wire.send(decToBcd(year));
Wire.endTransmission();
}
// Gets the date and time from the ds1307
void getDateDs1307(byte *second,
byte *minute,
byte *hour,
byte *dayOfWeek,
byte *dayOfMonth,
byte *month,
byte *year)
{
// Reset the register pointer
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.send(0);
Wire.endTransmission();
Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
// A few of these need masks because certain bits are control bits
*second = bcdToDec(Wire.receive() & 0x7f);
*minute = bcdToDec(Wire.receive());
*hour = bcdToDec(Wire.receive() & 0x3f); // Need to change this if 12 hour am/pm
*dayOfWeek = bcdToDec(Wire.receive());
*dayOfMonth = bcdToDec(Wire.receive());
*month = bcdToDec(Wire.receive());
*year = bcdToDec(Wire.receive());
}
// functions for EEPROM memory read/write
template <class T> int EEPROM_writeAnything(int ee, const T& value)
{
const byte* p = (const byte*)(const void*)&value;
int mem;
for (mem = 0; mem < sizeof(value); mem++)
EEPROM.write(ee++, *p++);
return mem;
}
template <class T> int EEPROM_readAnything(int ee, T& value)
{
byte* p = (byte*)(void*)&value;
int mem;
for (mem= 0; mem < sizeof(value); mem++)
*p++ = EEPROM.read(ee++);
return mem;
}
// Camera function variables
long INTERVAL_TM = 1200; // default interval delay between exposures
int MIN_INTERVAL_TM = 100; // minimum interval delay
long EXPOSURE_TM = 60; // default exposure length (how long to fire camera)
int MIN_EXPOSURE_TM = 60; // minimum exposure (pulse length)
float EXPOSURE_INC = 1.05; // percentage to increase or decrease exposure
float EXPOSURE_DEC = 0.95;
unsigned long LAST_TM = 0; // last time camera fired
long SHOT_COUNT = 0; // running total of photographs taken
boolean SEQ_RUNNING = false; // is the camera running a sequence
boolean EXPOSING = false; // whether or not we're currently exposing
// values to read/write to Eeprom, rememeber they are "configuration.INTERVAL_TM" etc.
// they are not the same as INTERVAL_TM etc
struct config_t
{
long INTERVAL_TM;
long EXPOSURE_TM;
}
configuration;
void setup() {
// initialise all battery array readings to 0
for (int thisReading = 0; thisReading < numReadings; thisReading++)
readings[thisReading] = 0;
// preload battery arrays with a cycle through the analogue readings
// to avoid silly numbers appearing on the initial display
int temp;
for (temp =0; temp <= numReadings; temp++){
get_Battery();
}
// setup pins for input/output
pinMode(SHUTTER_PIN, OUTPUT);
pinMode(FOCUS_PIN, OUTPUT);
pinMode(PCSYNC_PIN, INPUT);
pinMode(CAMLED_PIN, OUTPUT);
// set input button pins
pinMode(B_LT, INPUT); // Left
pinMode(B_RT, INPUT); // Right
pinMode(B_UP, INPUT); // Up
pinMode(B_DN, INPUT); // Down
pinMode(B_CT, INPUT); // Centre
// setup display
pinMode (lcd_LIGHT, OUTPUT);
digitalWrite(lcd_LIGHT, HIGH);
lcd_SW = HIGH; // turn on LCD backlight
lcd_TIMER = millis(); // set timer start
// set up the LCD's number of rows and columns:
// Note 4 rows from 0 to 3
lcd.begin(20, 4);
// creates custom characters from the arrays storing them to
// one of 8 (0-7) available custom character slots.
lcd.createChar(0, UP);
lcd.createChar(1, DOWN);
lcd.createChar(2, LEFT);
lcd.createChar(3, RIGHT);
/* use lcd.write() for custom Arrow characters
lcd.write(0); UP
lcd.write(1); DOWN
lcd.write(2); LEFT
lcd.write(3); RIGHT
*/
// display intro screen
lcd.clear();
lcd.setCursor(4, 0);
lcd.print("Canon Camera");
lcd.setCursor(5, 1);
lcd.print("Controller");
lcd.setCursor(8, 2);
lcd.print("C^3");
lcd.setCursor(4, 3);
lcd.print("version 1.6");
delay(2000);
lcd.clear();
// check if Centre button is pressed on startup.
// If LOW load values from memory (default is to load memory values)
// if HIGH load default values
if( digitalRead(B_CT) == LOW)
{
EEPROM_readAnything(0, configuration);
INTERVAL_TM = configuration.INTERVAL_TM;
EXPOSURE_TM = configuration.EXPOSURE_TM;
lcd.setCursor(0, 1);
lcd.print(" Saved data loaded");
lcd.setCursor(0, 2);
lcd.print("Press FIRE to reset");
delay(1000);
lcd.clear();
}
else
{
lcd.setCursor(0, 1);
lcd.print("Default data loaded");
delay(1000);
lcd.clear();
}
// display main screen
lcd.setCursor(0, 0);
lcd.print("Interval = ");
lcd.setCursor(18, 0);
lcd.write(0); // UP arrow
lcd.write(1); // DOWN arrow
lcd.setCursor(0, 1);
lcd.print("Exposure = ");
lcd.setCursor(18, 1);
lcd.write(2); // LEFT arrow
lcd.write(3); // RIGHT arrow
lcd.setCursor(0,2);
lcd.print("Shot = ");
// setup DS1307 (need to convert this into a proper clocking setting routine)
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
Wire.begin();
// Change these values to what you want to set your clock to.
// You probably only want to set your clock once and then remove
// the setDateDs1307 call.
// unrem next lines to set clock
/*
second = 0;
minute = 25;
hour = 12;
dayOfWeek = 4;
dayOfMonth = 20;
month = 5;
year = 10;
setDateDs1307(second, minute, hour, dayOfWeek, dayOfMonth, month, year);
*/
}
void loop() {
if (SEQ_RUNNING == true) {
shutter_control();
}
user_input();
}
battery_status.pde
/*
check battery status and work out percentage life left in the lithium battery
*/
// Battery Status
void get_Battery() {
// read the value from the battery sensor analogue pin (full charge voltage = batteryMaxVolts)
// create a rolling average over 20 samples
// subtract the last reading:
total = total - readings[index];
// read from the sensor:
readings[index] = analogRead(batteryPin);
// add the reading to the total:
total = total + readings[index];
// advance to the next position in the array:
index = index + 1;
// if we're at the end of the array...
if (index >= numReadings)
// ...wrap around to the beginning:
index = 0;
// calculate the average:
batteryAverage = total / numReadings;
// convert to volts
batteryValue = (batteryAverage/1024)*5; // 1024 = 5 volts
batteryPercent = ((batteryValue-batteryMinVolts)/(batteryMaxVolts-batteryMinVolts))*100;
}
lcd_display.pde
/*
Update display every lcd_UPDATE_TM ms
*/
void update_LCD() {
// only update every lcd_UPDATE_TM ms
if (millis() - lcd_UPDATE_TM > lcd_LAST_UPDATE) {
// update display
lcd.setCursor(11, 0);
lcd.print(" ");
lcd.setCursor(11, 0);
lcd.print(INTERVAL_TM);
lcd.setCursor(11, 1);
lcd.print(" ");
lcd.setCursor(11, 1);
lcd.print(EXPOSURE_TM);
lcd.setCursor(11, 2);
lcd.print(" ");
lcd.setCursor(11, 2);
lcd.print(SHOT_COUNT);
// update status line with time and battery
// Update clock on status line
get_Clock();
// update Battery on status line
get_Battery();
lcd.setCursor(11, 3);
lcd.print("B ");
lcd.setCursor(12, 3);
lcd.print(batteryPercent);
lcd.print("%");
// check battery status and display label
if (batteryPercent <= 5){
lcd.setCursor(17,3);
lcd.print("LOW");
}
else
{
lcd.setCursor(17,3);
lcd.print(" OK");
}
// update update timer
lcd_UPDATE_TM = millis();
}
}
rtc_status.pde
/*
get update from RTC and display it
*/
void get_Clock()
{
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
getDateDs1307(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
lcd.setCursor(0,3);
if (hour < 10){
lcd.print("0");
}
lcd.print(hour, DEC);
lcd.print(":");
if (minute < 10){
lcd.print("0");
}
lcd.print(minute, DEC);
lcd.print(":");
if (second < 10){
lcd.print("0");
}
lcd.print(second, DEC);
}
shutter_control.pde
/*
control the camera and the interrupt
*/
void shutter_control() {
// if the camera is not currently exposing, and our
// timer has elapsed, fire camera...
if( EXPOSING == false && millis() - LAST_TM > INTERVAL_TM ) {
// set timer interrupt to call our
// function to disengage the camera
MsTimer2::set( EXPOSURE_TM, shutter_off );
// enable timer
MsTimer2::start();
// set flag to indicate that we're currently
// exposing
EXPOSING = true;
// enable optocoupler and LED
digitalWrite(SHUTTER_PIN, HIGH);
digitalWrite(CAMLED_PIN, HIGH);
SHOT_COUNT++;
} // end if not exposing and timer has elapsed
}
void shutter_off() {
// disable optocoupler and LED
digitalWrite(SHUTTER_PIN, LOW);
digitalWrite(CAMLED_PIN, LOW);
// disable timer
MsTimer2::stop();
// we set this now to ensure that our
// interval time is measured from the
// time an image is completed until the
// time the next one is triggered
//
// if you want the interval time to be
// measured between the time the image
// is triggered and the next image is
// triggered, move this to before
// bringing the camera pin high
LAST_TM = millis();
// reset exposing flag
EXPOSING = false;
}
user_input.pde
/*
user input functions
*/
void user_input() {
// this function determines if any button has been hit,
// and what to do about it
// up and down do not require a HIGH (unpressed) reading
// so that you can hold them down and quickly cycle through
// values (about 3 changes per second)
update_LCD();
// check to turn off backlight
if (lcd_SW == HIGH){
if (lcd_TIMER + lcd_TIMEOUT < millis()) {
lcd_SW = LOW;
digitalWrite(lcd_LIGHT, LOW);
}
}
// check for button press
if( digitalRead(B_UP) == HIGH && millis() - button_hit > 300) {
if (lcd_SW == LOW && millis() - button_hit > 300) {
lcd_ON();
button_hit = millis();
return;
}
else
{
take_button_action(0);
button_hit = millis();
return;
}
}
if( digitalRead(B_DN) == HIGH && millis() - button_hit > 300 ) {
//pre_dn = true;
lcd_ON();
take_button_action(1);
button_hit = millis();
return;
}
if( digitalRead(B_LT) == HIGH && millis() - button_hit > 300 ) {
if (lcd_SW == LOW && millis() - button_hit > 300) {
lcd_ON();
button_hit = millis();
return;
}
else
{
take_button_action(2);
button_hit = millis();
return;
}
}
if( digitalRead(B_RT) == HIGH && millis() - button_hit > 300) {
if (lcd_SW == LOW && millis() - button_hit > 300) {
lcd_ON();
button_hit = millis();
return;
}
else
{
take_button_action(3);
button_hit = millis();
return;
}
}
if( digitalRead(B_CT) == HIGH && millis() - button_hit > 300) {
if (lcd_SW == LOW && millis() - button_hit > 300) {
lcd_ON();
button_hit = millis();
return;
}
else
{
take_button_action(4);
button_hit = millis();
return;
}
}
}
void take_button_action( byte button ) {
switch(button) {
case 0:
// up hit
INTERVAL_TM = INTERVAL_TM + 100;
// update memory
configuration.INTERVAL_TM = INTERVAL_TM;
EEPROM_writeAnything(0, configuration);
update_LCD();
return;
break;
case 1:
// down hit
INTERVAL_TM = INTERVAL_TM - 100;
if (INTERVAL_TM <= MIN_INTERVAL_TM) {
INTERVAL_TM = MIN_INTERVAL_TM;
}
update_LCD();
// update memory
configuration.INTERVAL_TM = INTERVAL_TM;
EEPROM_writeAnything(0, configuration);
return;
break;
case 2:
// left hit - reduce exposure
EXPOSURE_TM = int(EXPOSURE_TM * EXPOSURE_DEC);
if (EXPOSURE_TM <= MIN_EXPOSURE_TM) {
EXPOSURE_TM = MIN_EXPOSURE_TM;
}
update_LCD();
// update memory
configuration.EXPOSURE_TM = EXPOSURE_TM;
EEPROM_writeAnything(0, configuration);
return;
break;
case 3:
// right hit - increase exposure
EXPOSURE_TM = int(EXPOSURE_TM * EXPOSURE_INC);
// update memory
configuration.EXPOSURE_TM = EXPOSURE_TM;
EEPROM_writeAnything(0, configuration);
update_LCD();
return;
break;
case 4:
// centre hit
if (SEQ_RUNNING == true) {
SEQ_RUNNING = false;
update_LCD();
}
else {
SEQ_RUNNING = true;
update_LCD();
}
return;
break;
}
}
// function to turn on lcd_LIGHT and reset timer
void lcd_ON(){
lcd_TIMER = millis(); // reset lcd timer
digitalWrite(lcd_LIGHT, HIGH);
lcd_SW = HIGH;
}
Pingback: C-Cubed – the Arduino-based camera intervalometer « freetronicsblog
Kev to Kev
Any plans on selling built units? Now that Little Bramper is sold out, I’m sure there would be a market for these in the timelapse community. Not everyone has the knowledge, patience and time to do this themselves. I would be willing to pay several hundred dollars for a unit that I knew worked.
I just don’t have the free time to put together systems for other people I’m afraid.
First off thank you so much for documenting this, I’m going to try and build a version of this but I’m not very good with code. When trying to upload I get an error, it revolves around defining the battery status variables. The code looks like this;
unsigned long button_hit = 0; // time last button was hit
// define battery status variables
int batteryPin = 0; // analogue pin 0 hard wired to battery status line
float batteryValue; // true battery voltage
int batteryPercent; // % full against max reference voltage
float batteryMaxVolts = 4.15; // max reference voltage (full charge)
float batteryMinVolts = 3.35; // min battery voltage (low alarm at batteryMinVolts + 0.1v)
float batteryAverage = 0; // the average voltage
I’m thinking that I need to enter the specs on the battery I’m using. Two problems, I don’t know how and I’m currently powering off of USB until the system is much further along. PLEASE HELP!!!
Hello everybody,
I want to use SHT10 sensor to read temperature and humidity and the display them on LCD 2*16 via PIC18F452.
Anybody that have the code,please put it on this page,i will pick up that.
Many thanks,
Kayvan
i was wondering if you could get into contact with me on your arduino controller, i wish this or something like this to be my first project, and i was wondering if i could just ask you some questions.
its more theory rather than arduino code…. thanks
Martieuk [at] hotmail [dot] com
Hi,
Great looking device you have there. I am getting a bit jealous.
I am taking too the challenge, but I have some question concerning the light measurement. I notice that you used the TSL230R as a incident light meter. Did you change Church’s initial code?
Also, I am having some issue on where to aim the sensor for sunset. Do you have any advice?
Thanks again.
T
Hi,
the main core of the original code is unchanged, all I’ve done is play around at the edges of it to suit the devices I was using. It’s possible to apply an Ev offset in the programme so you can use the light sensor for incident or reflective readings. Measuring off a card or through a diffuser would give you the best average for metering and the direction of the reading becomes less important. i.e I visually correct the base value to give me a good exposure histogram and then let the system run.
regards
Kev
Pingback: Arduino intervalometer and bulb ramping for Digital Cameras | PhotosbyKev