Control box for a focus rail, and other stepping motor-driven camera moving devices. The program.

With some reluctance, I have decided to upload the Arduino code that I use to drive the new electronics that I use with my focus rail. As I said in the previous post, it can be used with up to five stepping motor-driven devices. Thus, it can be used to move a camera, or a specimen one is photographing, along X, Y and Z axes as well as providing the facility to rotate the camera or specimen around the A & B axes. The control box incorporates 8 outputs (configured as 4 pairs) that can be used to trip the shutters of cameras, prefocus, or operate strobes. The box also has provision for up to 8 analogue inputs. However, these are at the present time, unused. Currently I have only the one focus rail and a turntable with which to test the box. Other bits are in the process of construction. The control box utilises an Arduino Mega, which with its provision for lots of digital outs and analogue ins, provides almost infinite scope for expansion.

The hardware consists of the aforementioned Mega, a 4 line LED display driven by an I2C module via the SDA and SLC pins on the Arduino, EasyDriver boards to drive the stepping motors, a rotary encoder with a push-button switch, and the little optocoupler boards pictured below to trigger the cameras etc. The project is housed in a die-caste aluminium box and all the connections bar those to external switches and jacks are made using DuPont connectors.  I will at some point, if I can find the time, draw up a wiring diagram. However, in many ways, this should not be necessary because the programme contains a list of all the pin connections necessary to make the different circuits work together apart from the SDA and SLC connections mentioned above.

The menu system is scrolled by the rotary encoder and operating the integral push button activates the selected menu item. The  short video below (a new one!) shows this system in operation. I brought the Arduino’s reset pin out to an external pushbutton to make it easy to resent the system should anything go wrong. I added a switch to turn off the power supply to the motors so that one can test the programme without disconnecting them. I also cut a hole in the side of the box to allow me to program (reprogram, reprogram and reprogam again and again!)  the board in situ via its USB socket.

My reluctance in publishing the code is two-fold. Firstly, I do not consider myself in any way to be an Arduino expert – my competence has grown, but I remain a complete novice by comparison to the gurus! Secondly, the code is a work in prgress and likely always will be! I am sure there are lots of errors, however, I have been using it to take pictures so, in practice it works for me. If anyone finds the code at all useful then I will be very happy. However, I do ask that should you pass it on, or use chunks, you acknowledge the source (this blog) so there is a chance that I can keep a handle on the development of the code and put right any errors  etc. Unfortunately, WordPress do not make it that easy to publish your code exactly as one would see it in the Arduino Editor. However, I am hopeful people should be able to cut and past the code presented on this page into an editor – look out for lines that have scrolled, and other formatting errors that have been produced by incorporating the code into these WordPress pages. It looks OK but beware of potential problems. I am thinking of placing the code on Github or elsewhere – news on this another time. There are about 1000 lines altogether.

Before the code here are a few pictures to help explain the components I have used.

img_3018

The box with its four line display. The red button is an external reset. The RHS carries the 4 stereo jack sockets for the outputs to the camera and flashes and also sockets for the as yet  unused sensor inputs. 4-pin aviation sockets are used for connecting the motors. There is a motor power socket and switch on the LHS, and an access hole for a USB plug.

img_3017

The two types of circuit board used inside the box other than the Arduino Mega. The little optocoupler boards are available from China for next to nothing on eBay. The EasyDriver boards are also very cheap. You will need to solder on some header pins to connect the boards to the Mega using DuPont connectors. My box currently contains three EasyDrivers and two quad optocoupler boards.

This short video shows the options currently available from the rotary encoder-based menu.

Here is the program – don’t forget there is a sideways scroll bar at the bottom of the page!

//STEPPER BOX 2 Begun 04/October 2016
// Global variables to have capital letters
//#define ENCODER_OPTIMIZE_INTERUPTS // Use if time overhead is crucial
#include <Encoder.h>
#include <LiquidCrystal_I2C.h>
Encoder myencoder(2, 3); // both of these are interupt pins - gives highest fidelity
int Button_pin = 13;// pin that connects to encoder switch/button
int Upper_limit = 8; // initial max for encoder
int Lower_limit = 1; // initial min for encoder
int Increment = 10; // initial value for multilier re: durations, distance etc
int Direction; // intial direct rail will move 0 = forward
long int Distance;
int Number_photos;
int Delay_1;// Time over which railmovement settles down
int Delay_2;// Exposure time during which rail halts
int Dir_pin = 11; // Set initial pins to those for Z axes
int Drive_pin = 12; // initial pin for Z stepper motion
int Enable_pin = 10; // initial pin to enable Z stepper motor
int Camera_pin = 16;// connected to camera remote socket - camera may need another pin for prefocus - say 15 on same jack socket
int Flash_pin = 17;// connects to flash socket - rename and initialise if using more than one flash - ie Flash_2_pin = 19
int Motor_selected = 3; //start with Z motor selected
int Number_Photos_hold;
int Direction_hold;
int Distance_hold;
int Delay_1_hold;
int Delay_2_hold;
int Delay_shutter_lag = 100; // sets period required to activate camera
byte Reuse_Z_values;
byte Button_flag; // flag for button press
byte Old_button_flag = HIGH; // carry old value of button forward

String Main_Menu_Title = " --MAIN MENU-- ";
// 12345678901234567890
String Option_1 = " Stack ";
String Option_2 = " Jog ";
String Option_3 = " Rotate ";
String Option_4 = " Shoot ";
String Option_5 = " Flash ";
String Option_6 = " Shoot & flash ";
String Option_7 = " Motor - X,Y,Z,A,B ";
String Option_8 = "Distance multiplier ";
String Explain_1 = " (Create Z stack) ";
String Explain_2 = " (Jog back/forward)";
String Explain_3 = " (Control A,B Axes)";
String Explain_4 = " (Fire w/out flash)";
String Explain_5 = " (Fire flash) ";
String Explain_6 = " (Fire and flash) ";
String Explain_7 = " (Select motor) ";
String Explain_8 = "(Click sensitivity)";
LiquidCrystal_I2C lcd(0x3F, 20, 4); // set address for LCD on I2C
void setup()
{
pinMode(10, OUTPUT);// set all motor enable pins as outputs
pinMode(20, OUTPUT);
pinMode(23, OUTPUT);
pinMode(29, OUTPUT);
pinMode(32, OUTPUT);
digitalWrite(10, HIGH); // set all motor enable pins to off = HIGH
digitalWrite(20, HIGH);
digitalWrite(23, HIGH);
digitalWrite(29, HIGH);
digitalWrite(32, HIGH);
pinMode(15, OUTPUT);//configure current flash and camera pins as outputs
pinMode(16, OUTPUT);
pinMode(17, OUTPUT);
pinMode(18, OUTPUT);
// others as one wishes
pinMode (Button_pin, INPUT_PULLUP);
lcd.init();
lcd.clear();
lcd.backlight();
lcd.setCursor(0, 0);
// 12345678901234567890
lcd.print(" M A D B O F F I N ");
lcd.setCursor(0, 1);
lcd.print(" L A B S ");
lcd.setCursor(0, 2);
lcd.print("Software version 1.3");
lcd.setCursor(0, 3);
lcd.print(" 28th October 2016 ");
delay(4000);
lcd.clear();
}

void loop()

{
lcd.setCursor(0, 0);
lcd.print(Main_Menu_Title);
int k = encode_return(Upper_limit, Lower_limit); // call subroutine with upper and lower limits, k holds value returned from encoder
// Spell out menu options using encoder to select
switch (k) {
case 1:
lcd.setCursor(0, 1);
lcd.print(Option_1);
lcd.setCursor(0, 2);
lcd.print(Explain_1);
break;
case 2:
lcd.setCursor(0, 1);
lcd.print(Option_2);
lcd.setCursor(0, 2);
lcd.print(Explain_2);
break;
case 3:
lcd.setCursor(0, 1);
lcd.print(Option_3);
lcd.setCursor(0, 2);
lcd.print(Explain_3);
break;
case 4:
lcd.setCursor(0, 1);
lcd.print(Option_4);
lcd.setCursor(0, 2);
lcd.print(Explain_4);
break;
case 5:
lcd.setCursor(0, 1);
lcd.print(Option_5);
lcd.setCursor(0, 2);
lcd.print(Explain_5);
break;
case 6:
lcd.setCursor(0, 1);
lcd.print(Option_6);
lcd.setCursor(0, 2);
lcd.print(Explain_6);
break;
case 7:
lcd.setCursor(0, 1);
lcd.print(Option_7);
lcd.setCursor(0, 2);
lcd.print(Explain_7);
break;
case 8:
lcd.setCursor(0, 1);
lcd.print(Option_8);
lcd.setCursor(0, 2);
lcd.print(Explain_8);
break;
default:
;
}
Button_flag = digitalRead(Button_pin);
if (Button_flag != Old_button_flag)
{
Old_button_flag = Button_flag;
delay(100);
}
if (Button_flag == LOW && k == 1)
{
stacker();
}
else if (Button_flag == LOW && k == 2)
{
jogger();
}
else if (Button_flag == LOW && k == 3)
{
rotate();
}
else if (Button_flag == LOW && k == 4)
{
shoot();
}
else if (Button_flag == LOW && k == 5)
{
flash();
}
else if (Button_flag == LOW && k == 6)
{
shoot_flash();
}
else if (Button_flag == LOW && k == 7)
{
select_motor();
}
else if (Button_flag == LOW && k == 8)
{
click_Increment();
}
}

//
// SUBROUTINE encode_return () is sent upper and lower limits and returns encoder_value
//

int encode_return (int Upper_limit, int Lower_limit) {
int new_encoder_value = -999;
int mult_enc;
new_encoder_value = myencoder.read();
new_encoder_value = new_encoder_value / 4; // because each click increases encoder value by 4
// now test to see if limits exceeded
if (new_encoder_value >= Upper_limit) {
new_encoder_value = Upper_limit;
mult_enc = (Upper_limit * 4); //not clear why "if" not blocking code here????? add a 2...take it out or not??!
myencoder.write(mult_enc);
}
if (new_encoder_value <= Lower_limit) { new_encoder_value = Lower_limit; mult_enc = (Lower_limit * 4) + 2; //add 2 to stop "if" blocking code myencoder.write(mult_enc); } return new_encoder_value; } // // SUBROUTINE stacker () creates a stack of photos // void stacker() { Direction = 1; Number_photos = 1; Distance = 0; Delay_1 = 0; Delay_2 = 0; byte Old_button_flag = HIGH; byte Button_flag = HIGH; if (Motor_selected > 3)
{
lcd.clear();
// 01234567890123456789
lcd.setCursor(0,0);
lcd.print("--------------------");
lcd.setCursor(0,1);
lcd.print("Use X, Y or Z motor ");
lcd.setCursor(0,2);
lcd.print("Choose when offered!");
lcd.setCursor(0,3);
lcd.print("--------------------");
delay(1500);
lcd.clear();
select_motor();
}
lcd.clear();
lcd.setCursor(0, 0);
// 12345678901234567890
lcd.print("--STACKER SUB-MENU--");
//
// do while loop here for direction to run
//
lcd.setCursor(0, 1);
// 12345678901234567890
lcd.print(" (Direction) ");
if (Reuse_Z_values == HIGH)
{
myencoder.write(Direction_hold * 4);
}
do
{
Direction = encode_return(2, 1);
if (Direction == 1)
{
lcd.setCursor(0, 2);
lcd.print(" **Forward** ");
}
if (Direction == 2)
{
lcd.setCursor(0, 2);
lcd.print(" **Reverse** ");
}
Button_flag = digitalRead(Button_pin);
if (Button_flag != Old_button_flag)
{
Old_button_flag = Button_flag;
delay(30);
}
} while (Button_flag == HIGH);
Direction_hold = Direction;
myencoder.write(4); // return to a value of 1 for encoder - divide by four!
delay(200); // inserting this delay ends problems with 'bouncing by' setting number of photos
// 12345678901234567890
// do while loop here for number of photos
Old_button_flag = HIGH;
Button_flag = HIGH;
lcd.setCursor(0, 1);
// 12345678901234567890
lcd.print(" Number of Photos: ");
lcd.setCursor(0, 2);
lcd.print(" ");
if (Reuse_Z_values == HIGH)
{
myencoder.write(Number_Photos_hold * 4);
}
do
{
Number_photos = encode_return(1000, 1);
lcd.setCursor(10, 2);
lcd.print(Number_photos);
lcd.print(" ");
Button_flag = digitalRead(Button_pin);
if (Button_flag != Old_button_flag)
{
Old_button_flag = Button_flag;
delay(30);
}
} while (Button_flag == HIGH);
Number_Photos_hold = Number_photos;
myencoder.write(4); // return to a value of 1 for encoder - divide by four!
delay(200); // inserting this delay ends problems with 'bouncing by' setting distance to run
//
// do while loop here for distance to run
//
lcd.setCursor(0, 1);
// 12345678901234567890
lcd.print(" Distance (um): ");
if (Reuse_Z_values == HIGH)
{
myencoder.write(Distance_hold * 4);
}
do
{
Distance = encode_return(10000, 1); // um could be a problem for longer distances!!
Distance = Distance * Increment;
//lcd.print(" ");
lcd.setCursor(9, 2);
lcd.print(Distance);
lcd.print(" ");
Button_flag = digitalRead(Button_pin);
if (Button_flag != Old_button_flag)
{
Old_button_flag = Button_flag;
delay(30);
}
} while (Button_flag == HIGH);
Distance_hold = Distance / Increment;
myencoder.write(4);
delay(200); // inserting this delay ends problems with 'bouncing by'
// 12345678901234567890
// do while loop here for delay to settle rail

lcd.setCursor(0, 1);
// 12345678901234567890
lcd.print("Delay - settle (ms):");
if (Reuse_Z_values == HIGH)
{
myencoder.write(Delay_1_hold * 4);
}
do
{
Delay_1 = encode_return(9999, 1);
Delay_1 = Delay_1 * Increment;
lcd.setCursor(9, 2);
lcd.print(Delay_1);
lcd.print(" ");
Button_flag = digitalRead(Button_pin);
if (Button_flag != Old_button_flag)
{
Old_button_flag = Button_flag;
delay(30);
}
} while (Button_flag == HIGH);
Delay_1_hold = Delay_1 / Increment;
myencoder.write(4); // return to a value of 1 for encoder - divide by four!
delay(200); // inserting this delay ends problems with 'bouncing by'

// 12345678901234567890
// do while loop here for delay for exposure i.e. after tripping shutter

lcd.setCursor(0, 1);
lcd.print("Delay - expose (ms):");
if (Reuse_Z_values == HIGH)
{
myencoder.write(Delay_2_hold * 4);
}
do
{
Delay_2 = encode_return(9999, 1);
Delay_2 = Delay_2 * Increment;
lcd.setCursor(9, 2);
lcd.print(Delay_2);
lcd.print(" ");
Button_flag = digitalRead(Button_pin);
if (Button_flag != Old_button_flag)
{
Old_button_flag = Button_flag;
delay(30);
}
} while (Button_flag == HIGH);
Delay_2_hold = Delay_2 / Increment;
myencoder.write(4); // return to a value of 1 for encoder - divide by four!
lcd.clear();
lcd.setCursor(0, 0);
// 12345678901234567890
lcd.print("Photos: ");
lcd.print(Number_photos);
lcd.setCursor(0, 1);
lcd.print("Distance: ");
lcd.print(Distance);
lcd.setCursor(0, 2);
lcd.print("Delay (settle): ");
lcd.print(Delay_1);
lcd.setCursor(0, 3);
lcd.print("Delay (expose): ");
lcd.print(Delay_2);
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("--------------------");
lcd.setCursor(0, 1);
// 12345678901234567890
lcd.print(" ACCEPT VALUES? ");
lcd.setCursor(0, 2);
lcd.print("Press in 2 sec = YES");
lcd.setCursor(0, 3);
lcd.print("--------------------");
Button_flag = HIGH;
for (int i = 0; i <= 1000; i++) {
Button_flag = digitalRead(Button_pin);
delay(20);
if (Button_flag == LOW)
{
break;
}
}
if (Button_flag == LOW)
{
motor_driver();
}
lcd.clear();
lcd.setCursor(0, 0);
// 01234567890123456789
lcd.print("--------------------");
lcd.setCursor(0, 1);
lcd.print(" Finished Z stack! ");
lcd.setCursor(0, 2);
lcd.print("--------------------");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("--------------------");
lcd.setCursor(0, 1);
// 12345678901234567890
lcd.print("REUSE STACK VALUES? ");
lcd.setCursor(0, 2);
lcd.print("Press in 2 sec = YES");
lcd.setCursor(0, 3);
lcd.print("--------------------");
Reuse_Z_values = LOW;
Button_flag = HIGH;
for (int i = 0; i <= 200; i++) {
Button_flag = digitalRead(Button_pin);
delay(20);
if (Button_flag == LOW)
{
Reuse_Z_values = HIGH;
break;
}
}
lcd.clear();
return;
}

//
//
// SUBROUTINE motor_driver () moves the motor via EasyDriver and activates camera to produce a photo stack
//
//

int motor_driver()
{
// This value is crucial for absolute accuracy and needs to be set by moving the rail and seeing how far it actually travels.
float steps_per_micron = 0.78824; //1.25mm is 1600 steps (with 8 microsteps per step) so 0.78125 steps/micron!
long int steps_between_photos;
long int distance_between_photos;
long int total_steps;
int carry_direction;
pinMode(Dir_pin, OUTPUT);
pinMode(Drive_pin, OUTPUT);
pinMode(Camera_pin, OUTPUT);
pinMode(Flash_pin, OUTPUT);
pinMode(Enable_pin, OUTPUT);
digitalWrite(Dir_pin, LOW);
digitalWrite(Drive_pin, LOW);
digitalWrite(Enable_pin, LOW);
distance_between_photos = Distance / (Number_photos - 1);
steps_between_photos = (float)distance_between_photos / steps_per_micron; // check loss of precision!
lcd.clear();
lcd.setCursor(0, 0);
lcd.clear();
lcd.setCursor(0, 0);
// 12345678901234567890
lcd.print("Caution motor active");
lcd.setCursor(0, 1);
lcd.print("Creating Z stack of:");
lcd.setCursor(9, 2);
lcd.print(Number_photos);
lcd.setCursor(7, 3);
lcd.print("Photos");
delay(1500);

//head off in the right direction!
if (Direction == 2) {
digitalWrite(Dir_pin, LOW);
}
if (Direction == 1) {
digitalWrite(Dir_pin, HIGH);
}
//set Camera_pin high - first photo is where the rail is now
//tell operator taking first photo
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Creating Z stack! ");
lcd.setCursor(0, 1);
// 1234567890123456890
lcd.print("Photo #: 1 ");
lcd.setCursor(0, 2);
lcd.print("In stack of: ");
lcd.print(Number_photos);
digitalWrite(Camera_pin, HIGH);
delay (Delay_shutter_lag);// wait for shutter to open plus a bit - 52ms for Nikon plus a copule of ms before flash
digitalWrite(Flash_pin, HIGH);
delay(10);// trigger flash
digitalWrite(Camera_pin, LOW);
digitalWrite(Flash_pin, LOW);
delay(Delay_2);
for (int x_count_photos = 1; x_count_photos <= Number_photos - 1; x_count_photos++) {
//move rail position by steps_between_photos
//motor speed is set by delays....going slowly to avoid lost steps.....
for (int y_count_steps = 1; y_count_steps <= steps_between_photos; y_count_steps++) {
digitalWrite(Drive_pin, LOW);
delayMicroseconds(200);
digitalWrite(Drive_pin, HIGH);
delayMicroseconds(200);
}
//Tell user which photo is being taken
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Creating Z stack! ");
lcd.setCursor(0, 1);
// 12345678901234567890
lcd.print("Photo #: ");
lcd.print(x_count_photos + 1);
lcd.setCursor(0, 2);
lcd.print("In stack of: ");
lcd.print(Number_photos);
delay(Delay_1); // settle dealy to steady camera and rail
// take the photo and set Flash_pin high with Camera_pin long enough to trigger camera
digitalWrite(Camera_pin, HIGH);
delay (Delay_shutter_lag);// delay for shutter to open - ~1/10th
// Neeeds work here to set delay so flash occurs with correct timing - not relevant if camera triggers flashes
digitalWrite(Flash_pin, HIGH);
delay(10);// long enough pulse to trigger flash
digitalWrite(Camera_pin, LOW);
digitalWrite(Flash_pin, LOW);
delay(Delay_2); // allow for extended exposure times greater than 1 second provided by message delay
}
delay(1000);// so you can see the last value for photo number
//
//Rewind the slider to the starting point by reversing rail_direction and going back total number of steps in stack
//
//Warn that rewind about to take place..
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("--------------------");
lcd.setCursor(0, 1);
// 01234567890123456789
lcd.print(" Caution rewinding! ");
lcd.setCursor(0, 2);
lcd.print("<<<<<<<<<<>>>>>>>>>>");
lcd.setCursor(0, 3);
lcd.print("--------------------");
delay (1500);
carry_direction = Direction;
if (carry_direction == 2) {
digitalWrite(Dir_pin, HIGH);
}
if (carry_direction == 1) {
digitalWrite(Dir_pin, LOW);
}
//digitalWrite(Dir_pin, rail_direction);
total_steps = (Number_photos - 1) * steps_between_photos;
for (int x_count_rewind = 1; x_count_rewind <= total_steps; x_count_rewind ++) { digitalWrite(Drive_pin, LOW); delayMicroseconds(200); digitalWrite(Drive_pin, HIGH); delayMicroseconds(200); } digitalWrite(Enable_pin, HIGH); myencoder.write(4); lcd.clear(); } // /// SUBROUTINE jogger() jogs a motor via encoder // void jogger () { long int j = 0; int i = 0; int old_encoder_value; int step_size = 10; float jog_Increment = 0; Old_button_flag = HIGH; Button_flag = HIGH; if (Motor_selected > 3)
{
lcd.clear();
// 01234567890123456789
lcd.setCursor(0,0);
lcd.print("--------------------");
lcd.setCursor(0,1);
lcd.print("Use X, Y or Z motor ");
lcd.setCursor(0,2);
lcd.print("Choose when offered!");
lcd.setCursor(0,3);
lcd.print("--------------------");
delay(1500);
lcd.clear();
select_motor();
}
pinMode(Dir_pin, OUTPUT);
pinMode(Drive_pin, OUTPUT);
pinMode(Enable_pin, OUTPUT);
digitalWrite(Dir_pin, HIGH);
digitalWrite(Drive_pin, LOW);
digitalWrite(Enable_pin, LOW);
lcd.clear();
lcd.setCursor(0, 0);
// 01234567890123456789
lcd.print(" ---JOG SUB-MENU--- ");
lcd.setCursor(0, 1);
lcd.print(" Ready to move! ");
lcd.setCursor(0, 2);
lcd.print("Jog Increment (um): ");
jog_Increment = float(Increment) * float(step_size) * 0.78824;
lcd.setCursor(0, 3);
lcd.print(" ");
lcd.print(int(jog_Increment));
// 12345678901234567890
step_size = step_size * Increment; // scale step_size with value from Increment subroutine
old_encoder_value = myencoder.read();
do
{
j = myencoder.read();
if (j > old_encoder_value)
{
i = 1;
do {// not at all clear why a for loop will not work...but it doesn't!
digitalWrite(Dir_pin, HIGH);
digitalWrite(Drive_pin, HIGH);
delayMicroseconds(600);
digitalWrite(Drive_pin, LOW);
delayMicroseconds(600);
i = i + 1;
} while (i <= step_size);
}
if (j < old_encoder_value)
{
i = 1;
do {
digitalWrite(Dir_pin, LOW);
digitalWrite(Drive_pin, HIGH);
delayMicroseconds(600);
digitalWrite(Drive_pin, LOW);
delayMicroseconds(600);
i = i + 1;
} while (i <= step_size);
}
old_encoder_value = myencoder.read();
Button_flag = digitalRead(Button_pin);
if (Old_button_flag != Button_flag)
{
Old_button_flag = Button_flag;
delay(70);
}
} while (Button_flag == HIGH);
lcd.clear();
digitalWrite(Enable_pin, HIGH);
myencoder.write(4);
}

//
// SUBROUTINE rotate () rotates a rotary axis motor
//

void rotate () {
//probably needs to check that a rotary axis motor has been selected in the 'select motor' subroutine
//and then gets instructions re: clockwise or anticlockwise and the number of degrees (limits?)
int turn_clock = 0;
int turn_clock_old = 0;
if (Motor_selected <= 3) { lcd.clear(); // 01234567890123456789 lcd.setCursor(0,0); lcd.print("--------------------"); lcd.setCursor(0,1); lcd.print(" Use A or B motor "); lcd.setCursor(0,2); lcd.print("Choose when offered!"); lcd.setCursor(0,3); lcd.print("--------------------"); delay(1500); lcd.clear(); select_motor(); } Old_button_flag = HIGH; Button_flag = HIGH; pinMode (Dir_pin, OUTPUT); pinMode(Drive_pin, OUTPUT); pinMode(Enable_pin, OUTPUT); digitalWrite(Enable_pin, LOW); // been problem when I defined enable pin as output????? lcd.clear(); lcd.setCursor(0, 0); lcd.print("--ROTATE SUB-MENU---"); // 12345678901234567890 lcd.setCursor(0, 1); lcd.print("Jog step 0.22 degree"); lcd.setCursor(0, 2); lcd.print("Degrees of rotation:"); lcd.setCursor(0, 3); myencoder.write(0); // cahnged to zero -ok?? do { turn_clock = myencoder.read(); turn_clock = turn_clock / 4; if (turn_clock > turn_clock_old) {
digitalWrite(Dir_pin, LOW);
digitalWrite(Drive_pin , HIGH);
delayMicroseconds(900);
digitalWrite(Drive_pin, LOW);
delayMicroseconds(900);
}
else if (turn_clock < turn_clock_old) {
digitalWrite(Dir_pin, HIGH);
digitalWrite(Drive_pin, HIGH);
delayMicroseconds(900);
digitalWrite(Drive_pin, LOW);
delayMicroseconds(900);
}
turn_clock_old = turn_clock;
lcd.setCursor(8, 3);
lcd.print(float(turn_clock) * 0.225);
lcd.print(" ");
Button_flag = digitalRead(Button_pin);
if (Old_button_flag != Button_flag)
{
delay(100);
Button_flag = digitalRead(Button_pin);
Old_button_flag = Button_flag;
}
} while (Button_flag == HIGH);
myencoder.write(4);
lcd.clear();
digitalWrite(Enable_pin, HIGH);
}

//
// SUBROUTINE shoot () takes a picture without flash, just fires shutter on press of rotary encoder knob
//

void shoot () {
// just fires shutter on press of rotary encoder knob
int k;
int j;
byte Old_button_flag = HIGH;
byte Button_flag = HIGH;
lcd.clear();
lcd.setCursor(0, 0);
// 12345678901234567890
lcd.print("---SHOOT SUB-MENU---");
lcd.setCursor(0, 1);
lcd.print("Click to take photo!");
// 12345678901234567890
// need to get clockwise or anticlockwise and degrees
while (Button_flag == HIGH)
{
Button_flag = digitalRead(Button_pin);
if (Old_button_flag != Button_flag)
{
delay(30);
Button_flag = digitalRead(Button_pin);
Old_button_flag = Button_flag;
}
if (Button_flag == LOW)
{
digitalWrite(Camera_pin, HIGH);
delay(Delay_shutter_lag);// no need for lag here
digitalWrite(Camera_pin, LOW);
delay(Delay_2);
// 1234567890123456789
lcd.setCursor(0, 2);
lcd.print(" Photo taken! ");
delay(500);
lcd.clear();
return;
}
}
}

//
// SUBROUTINE flash () just fires the flashes on press of rotary encoder knob
//

void flash () {
// just fires flashes on press of rotary encoder knob
int k;
int j;
byte Old_button_flag = HIGH;
byte Button_flag = HIGH;
lcd.clear();
lcd.setCursor(0, 0);
// 12345678901234567890
lcd.print("---FLASH SUB-MENU---");
lcd.setCursor(0, 1);
lcd.print("Click to fire flash ");
// 12345678901234567890
// need to get clockwise or anticlockwise and degrees
while (Button_flag == HIGH)
{
Button_flag = digitalRead(Button_pin);
if (Old_button_flag != Button_flag)
{
delay(30);
Button_flag = digitalRead(Button_pin);
Old_button_flag = Button_flag;
}
if (Button_flag == LOW)
{
digitalWrite(Flash_pin, HIGH);
delay(10);
digitalWrite(Flash_pin, LOW);
delay(Delay_2);
// 1234567890123456789
lcd.setCursor(0, 2);
lcd.print(" Flash fired! ");
delay(500);
lcd.clear();
return;
}
}
}

//
// SUBROUTINE shoot_flash () just takes a single picture with flash on press of rotary encoder knob
// this and the routines shoot() & shoot_flash() could all be choices within one subroutine??
//

void shoot_flash () {
// just fires flashes on press of rotary encoder knob
int k;
int j;
byte Old_button_flag = HIGH;
byte Button_flag = HIGH;
lcd.clear();
lcd.setCursor(0, 0);
// 12345678901234567890
lcd.print("SHOOT&FLASH SUB-MENU");
lcd.setCursor(0, 1);
lcd.print("Click = flash photo ");
// 12345678901234567890
// need to get clockwise or anticlockwise and degrees
while (Button_flag == HIGH)
{
Button_flag = digitalRead(Button_pin);
if (Old_button_flag != Button_flag)
{
delay(30);
Button_flag = digitalRead(Button_pin);
Old_button_flag = Button_flag;
}
if (Button_flag == LOW)
{
digitalWrite(Camera_pin, HIGH);
delay(Delay_shutter_lag);
digitalWrite(Flash_pin, HIGH);
delay(10);
digitalWrite(Camera_pin, LOW);
digitalWrite(Flash_pin, LOW);
delay(Delay_2);
// 1234567890123456789
lcd.setCursor(0, 2);
lcd.print(" Flash photo taken!");
delay(500);
lcd.clear();
return;
}
}
}

//
// SUBROUTINE select_motor() allows choice of which motor to control
//

int select_motor () {
char motor = 'Z';
Old_button_flag = HIGH;
Button_flag = HIGH;
myencoder.write(12);
lcd.clear();
lcd.setCursor(0, 0);
// 12345678901234567890
lcd.print("---MOTOR SUB-MENU---");
// 12345678901234567890
lcd.setCursor(0, 1);
lcd.print(" A, B for rotations!");
lcd.setCursor(0, 2);
lcd.print("Current motor: ");
lcd.print(motor);
while (Button_flag == HIGH)
{
Motor_selected = encode_return(5, 1);
switch (Motor_selected) {
case 1:
motor = 'X';
break;
case 2:
motor = 'Y';
break;
case 3:
motor = 'Z';
break;
case 4:
motor = 'A';
break;
case 5:
motor = 'B';
break;
default:
;
}
lcd.setCursor(0, 2);
lcd.print(" Current motor: ");
lcd.print(motor);
Button_flag = digitalRead(Button_pin);
if (Old_button_flag != Button_flag)
{
Old_button_flag = Button_flag;
delay(70);
}
}
switch (motor) { //set pins up for the motor selected
case 'X':
Enable_pin = 20;
Dir_pin = 21;
Drive_pin = 22;
break;
case 'Y':
Enable_pin = 23;
Dir_pin = 24;
Drive_pin = 25;
break;
case 'Z': // reset these to 26, 27, 28 when rewiring done!
Enable_pin = 10;
Dir_pin = 11;
Drive_pin = 12;
break;
case 'A':
Enable_pin = 29;
Dir_pin = 30;
Drive_pin = 31;
break;
case 'B':
Enable_pin = 32;
Dir_pin = 33;
Drive_pin = 34;
break;
default:
;
}
myencoder.write(4);
//return; //Motor_selected......no need to return this variable it's global
}

//
// SUBROUTINE click_Increment() adjusts the 'sensitivity' of the rotary encoder making
// it easier to dial up big numbers
//

int click_Increment() {
byte Old_button_flag = HIGH;
byte Button_flag = HIGH;
myencoder.write(40);
lcd.clear();
lcd.setCursor(0, 0);
// 12345678901234567890
lcd.print("-CLICK INC SUB-MENU-");
lcd.setCursor(0, 1);
lcd.print(" For each encoder ");
lcd.setCursor(0, 2);
lcd.print(" click inc will be: ");
// 12345678901234567890
while (Button_flag == HIGH)
{
Increment = encode_return(50, 1);
lcd.setCursor(9, 3);
lcd.print(Increment);
// 1234567890
lcd.print(" ");
Button_flag = digitalRead(Button_pin);
if (Old_button_flag != Button_flag)
{
delay(100);
Button_flag = digitalRead(Button_pin);
Old_button_flag = Button_flag;
}
if (Button_flag == LOW)
{
lcd.clear();
myencoder.write(4); // return encoder value to 1
return Increment;
}
}
}

Advertisements
Posted in Photography and electronics | Tagged , , , , , , , | 20 Comments

New control box for a focus rail with many other options

I have now completed the new control box for the focus rail that I described how to build in a post I made a year or two ago. Actually, the new control box is capable of a great deal more than controlling a focus rail. The current version is set up to control three motors so that the X, Y and Z position of the camera/object can be set in 3D space. Thus, the control box can be used for both Z stacking and stitching images. Further, the number of motors can easily be expanded to 5 allowing control of rotation in 2 planes with the object of making macro stereo photographs. The box includes 8 opto-coupled outputs arranged to allow the triggering of 4 cameras/flashes (only 4 because 2 outputs are used per camera to activate pre-focus and the shutter). Unused at the moment, but already incorporated, are 8 analogue/digital  inputs that could either be used to trigger motor movements, or allow cameras and flashes to respond to external events, or one or more could be used to stop the motors when a limit is reached.

img_2951

Rotary encoder controlled camera mover controller with the focus rail described in an earlier post

The menu system is driven by a rotary encoder and incorporates a ‘click sensitivity’ option that allows the encoder to increase any variable by between 1 and X for each rotational click. The use of the encoder provides for a much more responsive and versatile interface than a button pad. There is a switch to turn off power to the motors that is useful both when testing, to prevent heating of the motor, and also as an emergency stop! An LED indicates when the motor is active and there is an external button to reset the microprocessor. I have used an Arduino Mega 2560 because it is significantly more capable than an Uno. Further expansion would not be a problem bar finding room in the control box and having the abilities of a spider where routing the wiring is concerned. EasyDriver boards (http://www.schmalzhaus.com/EasyDriver/) control the motors and commercially available opto-coupler boards are used for the flash and camera output control signals. In theory, the design  could easily be used to control a pan and tilt system for hyper-resolution photography and/or virtual 3D. The programming leans heavily on an interrupt-based encoder library that enables the encoder to be read while the sketch is busy doing other things (see http://www.pjrc.com/teensy/td_libs_Encoder.html). The current sketch is about  600 lines including comments etc.

The approximate cost of all the hardware, assuming a Mega clone such as a Funduino is employed, is about £35. Because all the components are available as finished boards, the only difficult things are the point-to-point wiring between them, and between the boards and the sockets etc. Mostly, the wiring can be done using Dupont  jumper cables.

The little video of the prototype below shows the bare bones of the project in action. The mechanics of the X&Y axes have yet to be constructed – they could simply be repeats of the design I published earlier but my intention is to motorize an old microscope mechanical stage and incorporate on it a rotary table rather like the one I also described in a previous post. When I am happy with the code, I will publish the sketch that will control the mechanical gubbins. Meantime, it functions very well as an alternative to the push-button control box that I made for my focus rail.

By way of cheering up those, including myself, that make stupid mistakes, the reason the control box is shown powered from both a USB input and an external power supply, is because I blew the Arduino’s external psu circuitry by connecting its 5V output to the 5V output on an EasyDriver!

Posted in electronics, Photography and electronics | Tagged , , , , , , , , , , , , , , | 11 Comments

How to take ‘bad macro’ photos

I was somewhat reluctant to do a blog post about the process of taking pictures. Whenever I read anything about this on internet forums, there is lots of flaming and trolling. However, I want to start out by freely admitting that I take ‘bad macro’ pictures. My definition of ‘bad macro’ goes something like this – most people carry lots of gear out into the wild and often take stunningly beautiful shots of the animals and plants they encounter. While I sometimes do the same, apart from taking the stunning photos that is (!),  generally I am interested in seeing things and my camera acts as both an eye capable of resolving what mine cannot, and a kind of nature notebook that I can take home as a record of what was about and where, and that I can use to identify an insect or plant that I don’t immediately recognise. I carry a camera with a macro lens and nothing else….no tripod, no monopod, no flash, just a lens hood and a plastic bag to protect the camera if it starts to rain.

dsc_4522_hf

Shooting dragonflies is fun. They often have beautiful colours and they return to same perch making it much easier to photograph them.

It seems to me that there are three ways to take macro photos in the wild. The one I describe above requires only a camera and lens and thus can only make use of natural light. The method has quite a few advantages, choose a light camera and the right lens and you can walk all day taking photos that look natural. It is easier to be stealthy with a lightweight setup. Indeed, while I can understand how on a cool and still morning or evening you might be able to use a tripod to get pin sharp pictures by natural light, I find it vanishingly unlikely you’d find many butterflies or other insects that would sit still long enough for you to get set up. Adding a flash will provide for more light and allow you take photos where natural light would not but adds significantly to the weight you need to carry around, and reduces your mobility. Plus, most insects are gone once the flash fires. Add enough flash power and put an ND8 filer on the front of your lens, and daylight can be rendered almost superfluous (though not completely irrelevant – the subject of another post). The built in flash on your camera is not usually terribly useful for  macro flash, it’s in the wrong place, and most often one adds either a ring flash or a pair of dedicated macro flash guns. More of such setups another time.

OLYMPUS DIGITAL CAMERA

A Clouded Yellow butterfly. Sometimes you can get really close and rattle off several shots.

I have two cameras and macro lenses, an aging Nikon D7000 with a wonderfully sharp Nikon 105mm macro lens with vibration reduction, and an Olympus OM-D EM-1 with a great macro lens, the Olympus 60mm macro. The Olympus body has built in 3-axis stabilisation. Now for a truly controversial statement – the Nikon setup is nowhere near as good as the Olympus one! Why? First, the Nikon is way too heavy for my liking. The Nikon and lens weigh in at 1571g; the Olympus at 601g. While I think the Nikon 105mm is sharper than the Olympus 60mm, there really isn’t that much in it for ‘bad macro’. Secondly, I find the focus-peaking system available in the electronic viewfinder (EVF) of the Olympus to be a fantastic boon. As soon as you hit perfect focus, the white peaking pixels light up and in a trice, I can see the antenna and eyes of my subject are in focus. Really important if you wear varifocal glasses as I need to! However, the single most important factor is simply weight and manoeuvrability, and on that front, the Olympus micro 4/3rds wins hands down. Indeed, so taken am I with it that I intend to invest in an E-M1 Mk 2.

img_2933

The size and weight differences between a D SLR and a micro 4/3rds are quite marked. The former weighs 2.5 times as much as the latter!

So, a few words on my ‘bad technique’.  Outdoors there are three things that move; the wind blows everything around, the thing you want to photograph may (will) move or fly away, and you, the photographer, shake and wobble around. While the latter may not be obvious when you take a picture of a landscape, it becomes very obvious when you are trying to take photos around 1:1 magnification and the object has tiny feature that will betray your slightest movements as blur. Unless you are photographing plants, where it is possible to clamp the stem, or even use a light tent to shade them from the wind, there is generally almost nothing you can do about the first two sources of movement described above other than aim for a reasonably high shutter speed, which helps a bit with movement in the plane of focus, but not at all if the movement takes the object out of focus. A small aperture will provide a bit more depth of field (DoF) but will add blur through diffraction if the lens is stopped down too far. The best solution by far is to work on a still day! A still, sunny day before the heat comes on is the best time for ‘bad macro’ shots of insects.  Not only do the things like butterflies move more slowly when they are cool, but also you stay cool – getting up and down from your knees, or lying on the ground and getting up again, maybe a hundred times in a day when its 37oC is a recipe for heatstroke. The other thing that I find helps a great deal is good camera holding technique. A good macro photo doesn’t generally result from adopting a position that is much higher than the object you are trying to photograph. So, usually it is best to crouch, kneel or lie down to get on the right level. On one knee you can rest an elbow on one knee and tuck the other one into your side, focus,  – hold your breath and rock slightly to get perfect focus and then hit the shutter button.  Continuous shooting with a tiny rocking motion can also be used though you then have later to sort out which frame represents the perfect shot and it can be a pain to do this if you go for many frames per second. I reserve lying down for things that are likely to fly off pretty much as soon as I adopt that position – much better suited to plants than butterflies etc. If you do adopt lying down then propping your elbows on the ground will give you a lot of stability. Standing up, keeping your elbows tucked in is essential. The image stabiliser is your friend and I have it on at all times. On a bright day, when shooting insects, I usually choose an ASA that allows me to shoot at a 500th with an aperture of f8 to f16 – about 500ASA in bright sunlight. At that setting, noise is tolerable though if conditions get dimmer, I sometimes have to shoot at higher ASAs. However, the DoF will always be a small number of millimetres and it is this that means a great deal of attention to camera holding technique is necessary. With insects and other small creatures, always aim to have the eyes in focus – it always looks best.

There is ‘cheat’ that can be useful if you need a little more stability and you walk with a pole. Hold the camera as shown below, you can slide your hand down the stick to get to the right height, rock forward or backwards to get a bit closer or further away and in focus. It can really help and is far quicker than messing with a tripod or a monopod, and sticks are to be found everywhere for free!. If in doubt always hit the shutter button….the worst photo is the one you didn’t quite take.

img_2942

Sorry about the socks! Down on one knee, one elbow pushed against the knee, the other tucked in. The right hand holds the camera against the stick with a lttle downward force applied to the pole.

My only other tips for ‘bad macro’ involve knowing your prey. Many people do not realise that insects are creatures of habit. Butterflies have ‘leks’ and often return to the same spot at a particular time of day, flying off and returning to the exact same spot many times. As an example, a Red Admiral we named ‘Red’ returned to more or less the same spot in our garden every evening at around 6pm for a month! Dragonflies will perch on the same twig over-and-over again. So, if at first you don’t succeed, try just waiting for your prey to return. On a bright day, shadowing an insect will often cause it to move away so get used to walking such that your shadow doesn’t pass over them. Move slowly and without sudden movement. I am convinced that once your body fills the field of view of an insect’s eye, you can get very close because you no longer cause sudden changes from say full sunlight to shadowed. Mating insects often give up caring about most other things and butterflies chasing one another are often quite easy to shoot when they alight. And yes, you are right, surely there is an advantage to using a flash but, it isn’t absolutely necessary, and taking ‘bad macro’ pictures is surprisingly satisfying – sometimes, they even look quite good!

 

Posted in Photography and electronics | Tagged , , , , , , , , , , , , , , , , , | Leave a comment

DIY Printed Circuit Boards – Part 2: Isolation Milling

Isolation milling is not an option available to everyone! It requires that you have a small CNC mill that has the required accuracy to mill tracks of the thickness that your design requires, and alsothe  necessary software. I have a Sieg X1LP mini mill (ArcEurotrade) that I converted to CNC using my own parts. You can see a picture of it below. My mill does not have ball screws and the Z axis is driven directly via the bevel gear that used to be operated by a hand-wheel. Thus, I think it would be fair to describe the whole system as pretty crude. However, through the wonders of Mach3, the software that takes G code and converts it into the signals required to drive the stepping motors that operate the XY table and the Z axis, it is possible to tune the mill to be surprisingly accurate. The key to accuracy when using the ACME lead-screws that come with the Sieg mill is to regularly check the mill’s accuracy and set the backlash compensation so that its movements are both accurate and repeatable. The backlash compensation in Mach3 works extremely well – in essence, it does exactly what the operator would do were they operating the mill manually. When used manually, one works with the mill to take up the backlash before dialling in a further real movement of the table. So, for example when making an X axis movement to the left after the mill has made a movement to the right, on first turning the hand wheel one encounters no resistance as the lead-screw’s thread moves to take up the slack within the drive nut. It is only once that slack has been taken up that the hand wheel begins to drive the table. In Mach3 you can tell the programme to make the movement that removes the backlash and then drive the table the required distance. At the end of this blog entry, I provide a reference to a video that shows you how to do this. Suffice it to say, that with the backlash compensation correctly set, the X1LP mill is very accurate – without it, you will not be able to mill a PCB. Obviously, the head of the mill needs properly ‘trammed’ – set to be absolutely orthogonal to the surface of the table. My Sieg came very well trammed and no shimming of the column carrying the mill head was required. I suspect the advice given above will apply to almost any small mill regardless of make.

 

blog_mill

My CNC converted Sieg X1LP mill. The MPG handwheel is a very useful accessory enabling you to operate the mill while standing right in front of it!

The first step in the production of a PCB is to use design software to create the files you require to produce the G code to drive the mill. I use EasyEDA. This is a web-based system. It is very easy to use and has the option to either send the files off to have EasyEDA manufacture your boards or to download the files you need to make them yourself. Thus, you have a way of making both a prototype board for yourself, and a way of ordering as many copies as you like! There are tutorials showing you how to use EasyEDA so I will not explain here (https://easyeda.com/Doc/Tutorial/ ). The design process consists of drawing your schematic by placing components or packages on a canvas. You can use SPICE to simulate the circuit if you wish. Following this, you can convert the schematic into a PCB. It is important to bear in mind the separation distance between tracks – this needs to be more than twice the tip-diameter of the cutter used in milling the board.

schematic

Simple schematic of an Arduino button pad created using EasyEDA

pcb_blog

The PCB design from the schematic above

Pressing the ‘Fabrication Output’ button at the top of the EasyEDA PCB design screen will open a new browser window giving you the option to download the Gerber files to your computer. The files are download to a zipped directory which contains the following types of file all with the name Gerber_drill with the file extensions GTL, GBL, GTS, GBS, GTO,GBO, DRL, GKO, GTP.

These apparently bewildering file suffixes have the following meanings:

GTL = top copper

GBL = bottom copper

GTS = top solder mask

GBS = bottom solder mask

GTO = top silkscreen

GBO = bottom silkscreen

DRL = NC drill

GKO = board outline

GTP = top paste

Only two or three of these files are relevant to milling your board. The DRL file, which contains the points at which the board needs to be drilled for component leads etc., and the GBL and/or GTL files which contain the information concerning the track layouts on the top and bottom copper surfaces of the PCB. To make use of these files you will require a piece of software like CamBam which is an application to create CAM files (G-code) from CAD source files or its own internal geometry editor. It like other programs of its kind, can import Gerber files and turn them into the G-code that Mach3 requires to drive your mill in order to cut your PCB design. Let’s consider the procedure for utilising the Gerber files produced from a PCB design program such as EasyEDA for a single-sided design.

Since Gerber files record everything in Imperial units, in CamBam (or equivalent) set everything up for ‘inches’. Then, open the GBL file and you will see something like this:

blog_cambam_tracks

The Gerber GTL file imported into CamBam

blog_cambam_holes

Gerber DRL file imported into CamBam and superimposed on the GTL file

Select all the tracks and hit ‘Copy’. Now open the DRL file without saving the tracks and hit ‘Paste’. You now have the tracks and the holes superimposed. I now convert the units metric. I do this because all my tooling is metric. After selecting all the components of the design, you can drag the design to a convenient location relative the XY origin. Now, select all the tracks in the design and specify how you would like these to be milled. Select ‘Profile’  as the milling operation and set the parameters to mill outside the profile. I use a 0.2mm 30 degree V-cutter of the kind used for engraving and run the mill at maximum RPM. This means that the minimum tool diameter you should set is 0.2mm. However, this assumes that it is the tip of the cutter that will do all the cutting when in fact the diameter of the cutter at the chosen cutting depth will be greater than this so you will need to lie about the tool diameter! The depth of copper depends on the board you buy. For 0.5oz board the copper is 0.7mils thick or about 17.5 microns, for 1oz board it is double this and double again for 2oz board. So for 1oz board, using a piece of scrap, try something about 0.25mm for the tool size along with a cutting depth of about 50 microns. If you get the depth or diameter wrong, the tracks you cut will either not be isolated or be cut to be narrower than you wanted. During the milling process you can use any manual Z fine tuning adjustment your mill have to get the the depth of cut just right. Also, if the cuts are not deep enough, you can mill the same piece of board a second time setting the Z zero point a little deeper. However, before setting off to mill a board you will need to convert the design to G-code and save it. I save the ‘track G-code’ and the ‘hole G-code’ to separate files and mill them sequentially. So after selecting the tracks and specifying how they are to be milled, you can save the G-code file for them and then deselect (or delete) them. You can then select the holes and specify drilling operations for them and create a separate G-code file for them. If in the end you want lots of different sized holes then I suggest you may find it easier to first drill them all to the same size and then drill the larger holes out by hand using a Dremel or, even just use the V-cutter to ‘dink’ the centres of all the through-the-hole pads and drill them by hand. If you don’t do this, you will need to edit the G-code to stop the mill and make the necessary tool changes – too difficult for me! Don’t be tempted to drill on the copper without a guiding ‘dink’ or pilot hole – your drill will skid all over the place!

img_2663

The mill all set up and ready to go

Holding down a piece of FR4 for milling can be problematic. The technique I employ is to use double-sided tape to stick the board to a piece of MDF and then clamp the MDF to the mill table. Not all boards are equal! The flatness of the board is critical so any variation in the thickness of it will cause problems. For this reason it is probably a good idea to steer clear of the cheaper board you see advertised on eBay. Because of the shallow depth of cut, it is crucial to accurately zero the Z axis. To do this you can use an electrical method, lowering the mill until you get the first electrical contact between the tool and the copper surface of the board, or you can employ a magnifying glass and watch for first contact (I do the latter with the mill running). During the cutting process I usually sray a little WD40 onto the surface – it seems to result in a slightly better cut. Using the technique described above I can easily achieve tracks of 0.2mm thickness.

blog_pcb_tools

Tungsten carbide V-cutter used for milling PCBs and a tungsten carbide micro-drill for drilling the holes for component leads etc.

img_2664

The PCB held on double-sided tape

img_2669

PCB after milling both the ‘tracks’ and ‘holes’ files

Links:

Setting up a mill to do backlash compensation in Mach3: https://www.youtube.com/watch?v=dJ6eadoJJqo

EasyEDA: https://easyeda.com/

CamBam: http://www.cambam.info/

Posted in electronics, Photography and electronics | Tagged , , , , , , , , , , , , | Leave a comment

A new web site for macro and insect lovers!

Having spent a year getting the house here in the Tarn ready, and building a new workshop, I finally got round to  building a website with my own domain name – www.petermobbs.com. The subject of the web site is, “Musings on the French countryside, photography, science, and the meaning of life”. It has some of my ‘bad macro’ pictures of some of the beautiful things to be found here in the Tarn, France. By ‘bad macro’, I mean handheld photos taken without a flash of things that move about and that thus are never absolutely pin sharp! So, ‘bad macro’ is a phrase I use to describe the photography I do aimed at catching as many images as I can of the creatures and plants that fascinate me – a sort of nature notebook. I can put my wonderful micro 4/3rds camera in my pocket along with my 60mm macro lens and wander and wonder without the encumbrences of tripods, flashes etc. I save photography with a strobe and other gubbins for other occassions – I love both sorts of photos but a pocketable setup is hard to beat sometimes. Take a look!

OLYMPUS DIGITAL CAMERA

OLYMPUS DIGITAL CAMERA

Posted in Photography and electronics | Tagged , , , , , , , , , , | Leave a comment

DIY printed circuit boards – printing and milling. Part 1: photo-etching.

There are several ways that an amateur can go about making their own printed circuit boards. They include: the toner transfer method, photo-etching and isolation milling. Their is another alternative – sending your design off to a commercial supplier. For a long time, I have used very basic software (Circuit Wizard) for designing my own circuit boards. More recently, I have used KiCAD and EasyEDA. Of these two, I have found EasyEDA very easy to learn and use. It also has, should you need it, the facility to send a design off and have the boards made at a very reasonable cost. For that reason, and because it also incorporates a simulation package, I have now concentrated on using it rather than any other package. The only ‘down-side’ to EasyEDA is that it is web-based and your designs are created and remain on the EasyEDA server. However, you can download Gerber files and PDFs to enable photo-etching and isolation milling.

I am not going to explain how to use it here, you can find everything you need at: www.easyeda.com. Here, I am just going to explain how I used to make and how I now make PCBs at home. I have no experience of the toner transfer method so it won’t be discussed. Suffice it to say that it involves transferring the plastic toner used by laser printers from glazed paper to the surface of a copper-clad board where it protects the copper from the etchant during the etching process. This, the first of two posts, explains how I photo-etch PCBs. A later post will explain how to use a milling machine to do the same.

Photo-etching:

What you need

To make your own photo-etched PCBs you will need an inkjet photoprinter – I have used both an Epson R300 and a Canon Pixmar 4950 – both worked very well. In addition, you will need a hairdryer, some pre-sensitized photoboard  (Maplin can supply this), a UV light box – I made my own (see below), some OHP film designed for use with an ink jet printer (I use the stuff you can buy from WH Smiths), some developing trays, a pair of broad plastic forceps, a measuring cylinder or jug, something to weigh out the chemicals, and some software in which to create PCB designs (see above). To drill the boards, you will need a Dremel or other small hobby drill.

Printing your design

I have found that ink jet printers can do a first rate job.  However, the settings are crucial. Print your design onto inkjet OHP film. You can buy this in WH Smiths or get it on the web. Results may vary between manufacturers. With WH Smith’s film I print using the ‘Glossy photopaper’, ‘high quality’, and ‘photo printing’ options. Print your design to the film using these settings and remember to use the rough side of the plastic sheets. The rough side carries a coating designed to absorb the ink. Once you have printed your design, dry the film with a hairdryer holding the dryer about 45 cms away. Don’t let the film get too hot – it will curl and distort. Drying takes about 2 minutes. Return the film to the printer and overprint your design and then dry it again with the hairdryer. Remarkably, with my Canon printer, the two prints line up perfectly. If you hold up the film to bright light after the first printing it will appear slightly transparent, but after the second printing, it should be jet black. Tiny details can be reproduced and I have produced boards with tracks only a fraction of a millimeter wide.

A caution, the OHP prints may appear totally dry but actually they are slightly hygroscopic and will forever by slightly sticky to the touch. They can be stored for a few months but eventually blur as the ink diffuses through the paper’s coating. I haven’t found this to be a problem – you can always print another mask.

Mask printed on a photo-printer.

Exposing the printed circuit board

This step requires some experimentation. However, it will not take long to establish for exactly how long the board needs to be exposed. I have a homemade UV light box. It consists of two Sylvania blacklight 350 tubes mounted in a wooden storage box bought from Homebase. It has a 5mm glass platen and a switch. I used some aluminiumized radiator reflector material to make a reflector for the tubes. The whole thing cost about £20 to make. When in use, it is covered with sheet of card and a black cloth. NEVER let any UV light enter your eye! If I were making the box today, I would probably use half a dozen 3W UV LEDs.

To expose the board I attach the mask to the copper surface using a tab of sellotape and place it on the platen. I put a heavy book on the board to make sure there is a good contact between the mask and the board. I cover the whole thing with a black cloth and then switch the tubes on. For me, the exposure times are about one minute and fifteen seconds. To establish the best exposure times you need to make a test strip – i.e. expose a piece of board through a photo-mask while using a piece of card to block the light from a portion of the board. Move the card along and expose it again etc. I used intervals of 15 seconds for this and then, when I had checked out how well this had worked by developing the board, I made a second strip using 5 second intervals. Exposure time is crucial and I have found that you need to be within 10% of the correct time to get a perfect board. My lights may be a little bright or too close to the board because ideally one might like exposures to be around five minutes. However, it works perfectly for me so I’ll stick with it.

My DIY UV light-box – you really don’t need anything more complicated!

Once the board has been exposed, you will be able to see your design on the photoboard’s surface.

Developing the PCB

Probably the wise thing to do is to buy some purpose made PCB developer from a supplier like Maplin. I made my own. It consists of 2.5 grams of sodium metasilicate (anhydrous) and 5 grams of sodium hydroxide in 375 ml of water (see below for a link to the origins of this formulation). Both these chemicals are dangerous and need to be handled with care. Add the sodium metasilicate slowly to the water and protect yourself appropriately from splashes. Then add the sodium hydroxide again taking great care. Shake until everything has dissolved. The developer keeps for a few weeks. If in doubt, make it up fresh every time.

To develop the board, immerse it in the developer with the design side uppermost. I use a discarded plastic tray from supermarket food packaging for this. Gently rock the dish. After about a minute you will see the tracks remain green and the exposed areas floating away in swirls of black. When the design is fully developed, remove the board from the developer using plastic tongs and place it a tray full of water. Rinse it off and then rinse it some more under a tap. A light stroke with some wet tissue will remove any remaining etch resist from the parts of the design exposed to light.

Top – Board in process of development. Bottom – Board after development and washing.

 

 

Etching the board

There are two etchants in common use – ferric chloride and ammonium persulphate. Both are nasty chemicals but ferric chloride is the nastiest! Be very careful using it. You can buy ferric chloride already made up and I suggest that you do. You can also buy it as granules; cheaper but nastier. Most people use a solution at about 40% concentration. The solution takes about 30 minutes to etch a board at room temperature. It will corrode everything in sight, burn you, and splashes will stain and rot your clothing and also anything else they touch! You have been warned.  Ammonium persulphate is cleaner to use and less nasty. A 20 – 25% weight to volume solution works best at about 40 oC. You can keep the solution at this temperature by placing the tray you use for etching in a water-bath.  Etching time will be about 15 minutes at this temperature. With both ferric chloride and ammonium persulphate you should rock the bath to keep the etchant moving over the surface of the board.

When all the copper has been removed from the spaces between the traces you can remove the board and wash it with copious amounts of water. When dry, you can remove the etch resist from the tracks either by exposing the whole board to UV, developing it again, washing and drying it, or by rubbing it with propyl alcohol. Actually, on the boards I have used the resist can stay there and you can solder through it.

Finished boards ready for drilling and cutting out

Drilling the board

I use small diameter tungsten carbide drills and my Dremel for drilling the pads.  You can do it without a drill press but it is a bit fiddly. The most important thing is that your board has nice holes etched in each and every pad. If it doesn’t, it is hell’s own job to get the drill to go through the pad without skidding all over the place!

 

Useful links

A rapid etching method using ferric chloride – looks amazing, haven’t tried it:

http://www.instructables.com/id/Sponge-Ferric-Chloride-Method-Etch-Circuit-Bo/

A different etchant; copper chloride and hydrochloric acid:

http://www.instructables.com/id/Stop-using-Ferric-Chloride-etchant!–A-better-etc/

Using ammonium persulphate:

http://www.youtube.com/watch?v=TLf4w1zTpkw

A survey of methods using an inkjet printer to print directly(!) to a pcb – haven’t tried it but looks fantastic:

http://www.pabr.org/pcbprt/pcbprt.en.html

Someone using the same technique described here with excellent results:

http://projects.dimension-x.net/archives/77

Some further info for those wishing to make their own developer solutions:

http://forum.allaboutcircuits.com/showthread.php?t=16675

 

 

Posted in electronics, Photography and electronics | Tagged , , , , , , , , , | Leave a comment

End to the silence – building a new workshop

It is nearly a year since I made the last entry on this blog. The reason for this is that my wife and I bought a new house in France close to the village of Penne. It is an old farmhouse and as is inevitable with old buildings, it needed some work. Mostly, it has been painting the walls and installing new furniture, and other such minor works. However, having spent the last several years working out of a small shed in the garden, I wanted to have a decent workshop. To this end, I decided to convert the old ‘cistern’ here into a workshop. Cisterns are a feature of isolated farmhouses in this part of France – they store water captured from the roof. In this case, a barn at one end of the farmhouse houses a second building within it. This interior structure has a ‘barrel roof’ and waterproof render on the floors and walls up to a height of about 2 metres. This ‘tank’ is about 4 x 5 metres and offered plenty of room for benching. Since the floor and the walls were designed to contain water without leaking, my instincts were that they should also keep out the damp. There is a pantile roof over the brick barrel roof and I my hope was, and it seems to have proven to be so, that if one roof didn’t stop the rain then the next one would. So far the whole thing seems entirely dry. The walls are nearly a metre thick – I presume this is because the walls of the tanks inside the barn were built up against the original barn walls. A great side effect of the massive nature of the structure is that it stays cool 22oC inside when it’s 38oC outside, perfect for when making something involves a little exertion! The way in to the cistern was via a set of large stone steps that led down into a pit. The pit was there so that the farmer or his wife could stand below the water level in the cistern and draw water from it via a brass stop cock. The previous owner had cut a hole in the wall just above the pit. Even with a pneumatic drill, it must have taken quite a bit of work to penetrate so much stone and cement.

IMG_2623

The way in over the drawbridge

So, as I came to it, the cistern was bare concrete and a hole about a metre above the level of the floor of the pit. I bought a low-pressure high-volume paint sprayer in the UK (Screwfix) and used this to paint the walls with white masonry paint. I bought 10 litres of grey floor paint and roller-painted the floor with it. With these things done, the place began to look habitable! I built a door to close across the opening and closed up what I think may have been an inspection window through which to check to the water level. Then, I built benches along two sides and put clip-together shelving along another. I ran electricity into the cistern from a panel in the barn, and installed 6 13A sockets and fluorescent lights. The final step was to install a drawbridge (!) that folds down from the wall of the cistern below the door and that folds down to meet the steps on the other side of the pit. The door can be locked and the drawbridge also raised and locked in front of the door giving two levels of security.

IMG_2620

Shelving – note the cistern’s barell roof

IMG_2621

Benching

IMG_2622

The inner door

I brought my machines from the UK; a tiny CNC mill and a small CNC mill, a bench drill and a small lathe. These are now installed along with draws etc to store all the bits and pieces one needs when making things.

IMG_2616

Small mill – Seig X1-LP diy conversion to CNC

IMG_2617

Tiny mill – diy conversion of Proxxon MF70 to CNC

IMG_2618

Cheap bench drill – it works

IMG_2619

Small Amadeal lathe

The cistern is my mechanical workshop. My electronic workshop is in one of the spare bedrooms in the main house and has all the stuff I need for building and testing circuits; oscilloscopes, signal generators, meters etc. So it is that after nearly a year, I am back in a position to start making things again. I have plans for a general purpose 5 axis camera positioning device that will allow stacking, structure-from-motion and the like, though the first step has been simply to rebuild my focus rail controller using a rotary encoder to drive the menus and the setting of values. That venture has been my first encounter with interupts…..took me a while but I think I have the measure of them. I’ll post the new design as soon as I have it working to my satisfaction. I have also been getting my mill to so isolation milling for prototyping PCBs and I will post some stuff on how I managed that very soon!

 

Posted in Photography and electronics | Tagged , , , , | Leave a comment