Really Small
Stackduino - Arduino DIY focus stacking controller electronics
This is an early prototype of Stackduino.
---------------------------------------------------------------------------------------------------------------------------------
This is a focus stacking controller for the macro setup: www.flickr.com/photos/reallysmall/5649044078/in/photostream.
It uses an arduino, an easydriver and some optocouplers to move a camera a designated distance, take a photo and then repeat the process a chosen number of times until enough image slices have been taken to compile into a stack.
It's housed in an old pata hard drive enclosure for convenience as several of the input ports are already provided: power in, power switch and usb port for future reprogramming with updated code.
It's a prototype and the mass of wiring makes it look a lot more complicated than it is.
1. 12v power in
2. 12v power in on/ off switch
3. Serial breakout port carrying through stepper motor and camera signals
4. USB in - reprogramming port for the arduino
5. Arduino Uno
6. 5v+ and Ground bus
7. Easydriver stepper motor controller
8. Optocoupler camera interface board for triggering focus and shutter remotely
9. Space for battery - future upgrade to run the box outdoors for timelapse footage and similar.
10. Push button - start or stop the stack
11. Rotary encoder - choose step size and number of steps
12. LCD screen
Parts List:
1x Arduino Uno (Any cheaper clone with a similar number of pins would do)
1x Easydriver v4.3 (Powers and controls the stepper motor using signals from the Arduino. Can also power the Arduino)
2x 4n35 Optocoupler (Allow shutter activation signals to go to camera while keeping it electrically isolated)
4x 330 Ohm resistors, 2 for Optocouplers, 2 for push button and rotary encoder
Stripboard for mounting Optocouplers and collecting together +5v and ground wires
Wire
1x push button switch
1x rotary encoder with dial
1x lcd (serial is easier to implement but parallel is much cheaper with some tweaks to the code)
2x serial ports
1x serial cable to carry stepping and camera signals (straight wiring rather than cross-over is easier as the pins go to the same place at either end)
1x stepper motor (get out of an old printer if you can - the one I bought is massive over-kill for the task)
1x enclosure - in this case an old hard drive caddy
1x piece of usb cable with A type plug to connect arduino to caddy's external usb socket for programming (useful but not essential)
1x small electronics project enclosure - acts a junction box on your stacking setup
1x 3.5mm stereo jack - terminates the camera signals from the serial cable
1x stereo lead - carries camera signals from junction box to camera
How the stepper motor is hooked up to the stacking setup would vary a lot I guess, I used timing pulleys and belt to connect it to the fine focus of the microscope block.
You'll also need a soldering iron, solder and a multimeter will come in very handy too to diagnose any problems.
Making it Better:
If I ever get round to doing a second version it will all be consolidated onto one board and probably take up about a third of the space.
These arduino variants with prototyping areas might make a great basis for the project...
Prototino: www.spikenzielabs.com/Catalog/index.php?main_page=product...
MENTA: www.adafruit.com/products/795
Freetronics Eleven: www.freetronics.com/products/eleven
In this version of the controller the serial enabled lcd is connected directly to the arduino's tx pin. This works fine but there is a small risk that when the arduino is being reprogrammed or starting up it could spam the screen with bad instructions and stop it working. Ideally use the arduino 'software serial' library and relocate the screen connection to a standard digital pin or use a parallel screen instead if you don't mind a bit more soldering work.
The Sketch
/*
MacroPhotography Focus Stepping Controller
www.flickr.com/photos/reallysmall/5877378677/ in/photostream
Key parts:
Arduino Uno
Easydriver v4.3
Bipolar stepper motor
Momentary push button
Rotary encoder with push button
16 x 2 serial lcd
2 x 4n35 optocoupler for camera connection
Key resources used, including but not limited to:
SLCD library - www.arduino.cc/playground/Code/SLCD
Easydriver Tutorial - danthompsonsblog.blogspot.com/2010/05/easydri ver-42-tutor...
Rotary encoder code - www.circuitsathome.com/mcu/reading-rotary-enc oder-on-arduino
*/
#include // lcd library
//2x16 char display
#define numRows 2 // Display has two rows
#define numCols 16 // Display has 16 columns
SLCD lcd = SLCD(numRows, numCols);
//Rotary encoder
#define ENC_A A1
#define ENC_B A0
#define ENC_PORT PINC
int steps = 5; // No. microns stepper motor should make between pictures, default 5
int numPictures = 10; // No. pictures to take
int loopCounter = 0; // No. pictures taken so far
int pushButton = 2; // Pin 2 = Start/ Stop button
int rotaryButton = 3;// Pin 3 = Rotary encoder push button
int focus = 6; // Pin 6 = Focus the camera
int shutter = 7; // Pin 7 = Take a picture
int dir = 8; // Pin 5 = Stepper motor direction
int doStep = 9; // Pin 8 = Move stepper motor
int toggleLed = 13; // Pin 13 = Switch onboard LED on/off depending on status of toggle button
//pushButton toggle
volatile int buttonState = HIGH; // the current state of the output pin
volatile int reading; // the current reading from the input pin
volatile int previous = LOW; // the previous reading from the input pin
volatile long time = 0; // the last time the output pin was toggled
volatile long debounce = 200; // the debounce time, increase if the output flickers
//rotaryButton toggle
volatile int rbbuttonState = HIGH; // the current state of the output pin
volatile int rbreading; // the current reading from the input pin
volatile int rbprevious = LOW; // the previous reading from the input pin
volatile long rbdebounce = 200; // the debounce time, increase if the output flickers
void setup()
{
Serial.begin(9600);
delay(100);
setDisplayBaudRate(38400); // increase baud rate for better response from lcd to changes in rotary encoder value
delay(100);
Serial.begin(38400);
lcd.init(); // Start lcd display
attachInterrupt(0, buttonChange, CHANGE); // Button on interrupt 0 - pin 2
attachInterrupt(1, rotaryButtonChange, CHANGE); // Rotary encoder on interrupt 1 - pin 3
pinMode(pushButton, INPUT);
pinMode(ENC_A, INPUT);
pinMode(ENC_B, INPUT);
pinMode(dir, OUTPUT);
pinMode(doStep, OUTPUT);
pinMode(focus, OUTPUT);
pinMode(shutter, OUTPUT);
pinMode(toggleLed, OUTPUT);
digitalWrite(focus, LOW);
digitalWrite(shutter, LOW);
digitalWrite(ENC_A, HIGH);
digitalWrite(ENC_B, HIGH);
}
void loop(){
if (buttonState == HIGH){ //stacking setup section
loopCounter = 0;
if (rbbuttonState == HIGH){ //set number of microns to move (steps)
steps = constrain(steps, 1, 250); //limits input step size between 1 and 250 - increase if desired
steps += read_encoder ();
lcd.print("Step size: ", 1, 0);
if (steps < 10){
Serial.print (00, DEC); //adds two leading zeros to single digit Step size numbers on the display
}
if (steps < 100){
Serial.print (0, DEC); http: //adds one leading zero to double digit Step size numbers on the display
}
Serial.print (steps , DEC);
lcd.print("Num steps: ", 0, 0);
if (numPictures < 10){
Serial.print (00, DEC); //adds two leading zeros to single digit Step numbers on the display
}
if (numPictures < 100){
Serial.print (0, DEC); //adds one leading zero to double digit Step numbers on the display
}
Serial.print (numPictures , DEC);
}
else{
numPictures = constrain(numPictures, 10, 250); //set number of pictures to take. Limits between 10 and 250 - change if desired
numPictures += (read_encoder () * 10); //number of pictures changes in increments of 10 for quick selection of large numbers
lcd.print("Step size: ", 1, 0);
if (steps < 10){
Serial.print (00, DEC); //adds two leading zeros to single digit Step size numbers on the display
}
if (steps < 100){
Serial.print (0, DEC); //adds one leading zero to double digit Step size numbers on the display
}
Serial.print (steps , DEC);
lcd.print("Num steps: ", 0, 0);
if (numPictures < 10){
Serial.print (00, DEC); //adds two leading zeros to single digit Step numbers on the display
}
if (numPictures < 100){
Serial.print (0, DEC); //adds one leading zero to double digit Step numbers on the display
}
Serial.print (numPictures , DEC);
}
} //end of stacking setup section
else{
for (int h = 0; h < numPictures; h++){ // loop the following actions for number of times dictated by var numPictures
loopCounter = loopCounter + 1; // optional count of pictures taken so far if reverse to start on end is used
lcd.clear();
lcd.print("Moving ", 0, 1);
Serial.print (steps);
Serial.print (" mns");
lcd.print("Step ", 1, 1);
Serial.print (loopCounter);
Serial.print (" of ");
Serial.print (numPictures);
delay(1000); // Delay required for text above to have time to appear on the screen
{
digitalWrite(dir, LOW); // Set the stepper direction to clockwise
delay(100);
for (int i = 0; i <= steps * 16; i++) // Iterate doStep for number of steps dictated by var encoder0Pos. Multiply steps by 16 as the default settings on the easydriver are 16 microsteps in each full step of the motor
{
digitalWrite(doStep, LOW); // This LOW to HIGH change is what creates the
digitalWrite(doStep, HIGH); // "Rising Edge" so the easydriver knows to when to step
delayMicroseconds(2000); // Delay time between steps, too fast and motor stalls
}
{
lcd.clear();
lcd.print("Settling", 0, 1);
lcd.print("1.5 secs", 1, 1);
delay(1000); // Allow any vibrations from movement to cease before taking a picture
lcd.clear();
lcd.print("Taking picture", 0, 1);
lcd.print("Image ", 1, 1);
Serial.print (loopCounter);
Serial.print ("/ ");
Serial.print (numPictures);
digitalWrite(focus, HIGH); // Trigger camera autofocus - camera may not take picture in some modes if this is not triggered first
digitalWrite(shutter, HIGH); // Trigger camera shutter
delay(400); // Small delay needed for camera to process above signals
digitalWrite(shutter, LOW); // Switch off camera trigger signal
digitalWrite(focus, LOW); // Switch off camera focus signal
delay(4800); //Pause to allow for camera to take picture with 2 sec mirror lockup and to allow flashes to recharge before next shot
lcd.clear();
}
}
if (buttonState == HIGH){
break;
}
}
lcd.print("Stack finished", 0, 1);
/*delay(1000); // uncomment this section to have camera returned to start position when stack is finished
lcd.clear();
digitalWrite(dir, HIGH); // Set the stepper direction to anti-clockwise
delay(100);
lcd.print("Returning...", 0, 1);
int totalSteps = steps * numPictures;
int partialSteps = steps * loopCounter;
int returnSteps = min(totalSteps, partialSteps);
lcd.print ("< rbdebounce) {
if (rbbuttonState == HIGH)
rbbuttonState = LOW;
else
rbbuttonState = HIGH;
time = millis();
}
digitalWrite(toggleLed, rbbuttonState);
rbprevious = rbreading;
}
/* returns change in encoder state (-1,0,1) */
int8_t read_encoder()
{
static int8_t enc_states[] = {
0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0 };
static uint8_t old_AB = 0;
/**/
old_AB <<= 2; //remember previous state
old_AB |= ( ENC_PORT & 0x03 ); //add current state
return ( enc_states[( old_AB & 0x0f )]);
}
void setDisplayBaudRate(int baudrate) { //function to change baud rate of lcd
Serial.print(0x7C, BYTE); // command byte
switch (baudrate) {
case 2400:
Serial.print(0x0B, BYTE); // Ctrl^K
break;
case 4800:
Serial.print(0x0C, BYTE); // Ctrl^L
break;
case 9600:
Serial.print(0x0D, BYTE); // Ctrl^M
break;
case 14400:
Serial.print(0x0E, BYTE); // Ctrl^N
break;
case 19200:
Serial.print(0x0F, BYTE); // Ctrl^O
break;
case 38400:
Serial.print(0x10, BYTE); // Ctrl^P
break;
default:
Serial.print(0x12, BYTE); // reset to 9600, Ctrl^R
}
}
Stackduino - Arduino DIY focus stacking controller electronics
This is an early prototype of Stackduino.
---------------------------------------------------------------------------------------------------------------------------------
This is a focus stacking controller for the macro setup: www.flickr.com/photos/reallysmall/5649044078/in/photostream.
It uses an arduino, an easydriver and some optocouplers to move a camera a designated distance, take a photo and then repeat the process a chosen number of times until enough image slices have been taken to compile into a stack.
It's housed in an old pata hard drive enclosure for convenience as several of the input ports are already provided: power in, power switch and usb port for future reprogramming with updated code.
It's a prototype and the mass of wiring makes it look a lot more complicated than it is.
1. 12v power in
2. 12v power in on/ off switch
3. Serial breakout port carrying through stepper motor and camera signals
4. USB in - reprogramming port for the arduino
5. Arduino Uno
6. 5v+ and Ground bus
7. Easydriver stepper motor controller
8. Optocoupler camera interface board for triggering focus and shutter remotely
9. Space for battery - future upgrade to run the box outdoors for timelapse footage and similar.
10. Push button - start or stop the stack
11. Rotary encoder - choose step size and number of steps
12. LCD screen
Parts List:
1x Arduino Uno (Any cheaper clone with a similar number of pins would do)
1x Easydriver v4.3 (Powers and controls the stepper motor using signals from the Arduino. Can also power the Arduino)
2x 4n35 Optocoupler (Allow shutter activation signals to go to camera while keeping it electrically isolated)
4x 330 Ohm resistors, 2 for Optocouplers, 2 for push button and rotary encoder
Stripboard for mounting Optocouplers and collecting together +5v and ground wires
Wire
1x push button switch
1x rotary encoder with dial
1x lcd (serial is easier to implement but parallel is much cheaper with some tweaks to the code)
2x serial ports
1x serial cable to carry stepping and camera signals (straight wiring rather than cross-over is easier as the pins go to the same place at either end)
1x stepper motor (get out of an old printer if you can - the one I bought is massive over-kill for the task)
1x enclosure - in this case an old hard drive caddy
1x piece of usb cable with A type plug to connect arduino to caddy's external usb socket for programming (useful but not essential)
1x small electronics project enclosure - acts a junction box on your stacking setup
1x 3.5mm stereo jack - terminates the camera signals from the serial cable
1x stereo lead - carries camera signals from junction box to camera
How the stepper motor is hooked up to the stacking setup would vary a lot I guess, I used timing pulleys and belt to connect it to the fine focus of the microscope block.
You'll also need a soldering iron, solder and a multimeter will come in very handy too to diagnose any problems.
Making it Better:
If I ever get round to doing a second version it will all be consolidated onto one board and probably take up about a third of the space.
These arduino variants with prototyping areas might make a great basis for the project...
Prototino: www.spikenzielabs.com/Catalog/index.php?main_page=product...
MENTA: www.adafruit.com/products/795
Freetronics Eleven: www.freetronics.com/products/eleven
In this version of the controller the serial enabled lcd is connected directly to the arduino's tx pin. This works fine but there is a small risk that when the arduino is being reprogrammed or starting up it could spam the screen with bad instructions and stop it working. Ideally use the arduino 'software serial' library and relocate the screen connection to a standard digital pin or use a parallel screen instead if you don't mind a bit more soldering work.
The Sketch
/*
MacroPhotography Focus Stepping Controller
www.flickr.com/photos/reallysmall/5877378677/ in/photostream
Key parts:
Arduino Uno
Easydriver v4.3
Bipolar stepper motor
Momentary push button
Rotary encoder with push button
16 x 2 serial lcd
2 x 4n35 optocoupler for camera connection
Key resources used, including but not limited to:
SLCD library - www.arduino.cc/playground/Code/SLCD
Easydriver Tutorial - danthompsonsblog.blogspot.com/2010/05/easydri ver-42-tutor...
Rotary encoder code - www.circuitsathome.com/mcu/reading-rotary-enc oder-on-arduino
*/
#include // lcd library
//2x16 char display
#define numRows 2 // Display has two rows
#define numCols 16 // Display has 16 columns
SLCD lcd = SLCD(numRows, numCols);
//Rotary encoder
#define ENC_A A1
#define ENC_B A0
#define ENC_PORT PINC
int steps = 5; // No. microns stepper motor should make between pictures, default 5
int numPictures = 10; // No. pictures to take
int loopCounter = 0; // No. pictures taken so far
int pushButton = 2; // Pin 2 = Start/ Stop button
int rotaryButton = 3;// Pin 3 = Rotary encoder push button
int focus = 6; // Pin 6 = Focus the camera
int shutter = 7; // Pin 7 = Take a picture
int dir = 8; // Pin 5 = Stepper motor direction
int doStep = 9; // Pin 8 = Move stepper motor
int toggleLed = 13; // Pin 13 = Switch onboard LED on/off depending on status of toggle button
//pushButton toggle
volatile int buttonState = HIGH; // the current state of the output pin
volatile int reading; // the current reading from the input pin
volatile int previous = LOW; // the previous reading from the input pin
volatile long time = 0; // the last time the output pin was toggled
volatile long debounce = 200; // the debounce time, increase if the output flickers
//rotaryButton toggle
volatile int rbbuttonState = HIGH; // the current state of the output pin
volatile int rbreading; // the current reading from the input pin
volatile int rbprevious = LOW; // the previous reading from the input pin
volatile long rbdebounce = 200; // the debounce time, increase if the output flickers
void setup()
{
Serial.begin(9600);
delay(100);
setDisplayBaudRate(38400); // increase baud rate for better response from lcd to changes in rotary encoder value
delay(100);
Serial.begin(38400);
lcd.init(); // Start lcd display
attachInterrupt(0, buttonChange, CHANGE); // Button on interrupt 0 - pin 2
attachInterrupt(1, rotaryButtonChange, CHANGE); // Rotary encoder on interrupt 1 - pin 3
pinMode(pushButton, INPUT);
pinMode(ENC_A, INPUT);
pinMode(ENC_B, INPUT);
pinMode(dir, OUTPUT);
pinMode(doStep, OUTPUT);
pinMode(focus, OUTPUT);
pinMode(shutter, OUTPUT);
pinMode(toggleLed, OUTPUT);
digitalWrite(focus, LOW);
digitalWrite(shutter, LOW);
digitalWrite(ENC_A, HIGH);
digitalWrite(ENC_B, HIGH);
}
void loop(){
if (buttonState == HIGH){ //stacking setup section
loopCounter = 0;
if (rbbuttonState == HIGH){ //set number of microns to move (steps)
steps = constrain(steps, 1, 250); //limits input step size between 1 and 250 - increase if desired
steps += read_encoder ();
lcd.print("Step size: ", 1, 0);
if (steps < 10){
Serial.print (00, DEC); //adds two leading zeros to single digit Step size numbers on the display
}
if (steps < 100){
Serial.print (0, DEC); http: //adds one leading zero to double digit Step size numbers on the display
}
Serial.print (steps , DEC);
lcd.print("Num steps: ", 0, 0);
if (numPictures < 10){
Serial.print (00, DEC); //adds two leading zeros to single digit Step numbers on the display
}
if (numPictures < 100){
Serial.print (0, DEC); //adds one leading zero to double digit Step numbers on the display
}
Serial.print (numPictures , DEC);
}
else{
numPictures = constrain(numPictures, 10, 250); //set number of pictures to take. Limits between 10 and 250 - change if desired
numPictures += (read_encoder () * 10); //number of pictures changes in increments of 10 for quick selection of large numbers
lcd.print("Step size: ", 1, 0);
if (steps < 10){
Serial.print (00, DEC); //adds two leading zeros to single digit Step size numbers on the display
}
if (steps < 100){
Serial.print (0, DEC); //adds one leading zero to double digit Step size numbers on the display
}
Serial.print (steps , DEC);
lcd.print("Num steps: ", 0, 0);
if (numPictures < 10){
Serial.print (00, DEC); //adds two leading zeros to single digit Step numbers on the display
}
if (numPictures < 100){
Serial.print (0, DEC); //adds one leading zero to double digit Step numbers on the display
}
Serial.print (numPictures , DEC);
}
} //end of stacking setup section
else{
for (int h = 0; h < numPictures; h++){ // loop the following actions for number of times dictated by var numPictures
loopCounter = loopCounter + 1; // optional count of pictures taken so far if reverse to start on end is used
lcd.clear();
lcd.print("Moving ", 0, 1);
Serial.print (steps);
Serial.print (" mns");
lcd.print("Step ", 1, 1);
Serial.print (loopCounter);
Serial.print (" of ");
Serial.print (numPictures);
delay(1000); // Delay required for text above to have time to appear on the screen
{
digitalWrite(dir, LOW); // Set the stepper direction to clockwise
delay(100);
for (int i = 0; i <= steps * 16; i++) // Iterate doStep for number of steps dictated by var encoder0Pos. Multiply steps by 16 as the default settings on the easydriver are 16 microsteps in each full step of the motor
{
digitalWrite(doStep, LOW); // This LOW to HIGH change is what creates the
digitalWrite(doStep, HIGH); // "Rising Edge" so the easydriver knows to when to step
delayMicroseconds(2000); // Delay time between steps, too fast and motor stalls
}
{
lcd.clear();
lcd.print("Settling", 0, 1);
lcd.print("1.5 secs", 1, 1);
delay(1000); // Allow any vibrations from movement to cease before taking a picture
lcd.clear();
lcd.print("Taking picture", 0, 1);
lcd.print("Image ", 1, 1);
Serial.print (loopCounter);
Serial.print ("/ ");
Serial.print (numPictures);
digitalWrite(focus, HIGH); // Trigger camera autofocus - camera may not take picture in some modes if this is not triggered first
digitalWrite(shutter, HIGH); // Trigger camera shutter
delay(400); // Small delay needed for camera to process above signals
digitalWrite(shutter, LOW); // Switch off camera trigger signal
digitalWrite(focus, LOW); // Switch off camera focus signal
delay(4800); //Pause to allow for camera to take picture with 2 sec mirror lockup and to allow flashes to recharge before next shot
lcd.clear();
}
}
if (buttonState == HIGH){
break;
}
}
lcd.print("Stack finished", 0, 1);
/*delay(1000); // uncomment this section to have camera returned to start position when stack is finished
lcd.clear();
digitalWrite(dir, HIGH); // Set the stepper direction to anti-clockwise
delay(100);
lcd.print("Returning...", 0, 1);
int totalSteps = steps * numPictures;
int partialSteps = steps * loopCounter;
int returnSteps = min(totalSteps, partialSteps);
lcd.print ("< rbdebounce) {
if (rbbuttonState == HIGH)
rbbuttonState = LOW;
else
rbbuttonState = HIGH;
time = millis();
}
digitalWrite(toggleLed, rbbuttonState);
rbprevious = rbreading;
}
/* returns change in encoder state (-1,0,1) */
int8_t read_encoder()
{
static int8_t enc_states[] = {
0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0 };
static uint8_t old_AB = 0;
/**/
old_AB <<= 2; //remember previous state
old_AB |= ( ENC_PORT & 0x03 ); //add current state
return ( enc_states[( old_AB & 0x0f )]);
}
void setDisplayBaudRate(int baudrate) { //function to change baud rate of lcd
Serial.print(0x7C, BYTE); // command byte
switch (baudrate) {
case 2400:
Serial.print(0x0B, BYTE); // Ctrl^K
break;
case 4800:
Serial.print(0x0C, BYTE); // Ctrl^L
break;
case 9600:
Serial.print(0x0D, BYTE); // Ctrl^M
break;
case 14400:
Serial.print(0x0E, BYTE); // Ctrl^N
break;
case 19200:
Serial.print(0x0F, BYTE); // Ctrl^O
break;
case 38400:
Serial.print(0x10, BYTE); // Ctrl^P
break;
default:
Serial.print(0x12, BYTE); // reset to 9600, Ctrl^R
}
}