Help with rotary encoder for focus stacking

Hi,

New user and new to arduino. I have an issue which is probably really simple but I seem to have cooked my brain learning all this and the answer isn't clear to me. I'm building a controller to drive a stepper connected to a microscope fine focus for use for focus stacking macro images. Here's the setup if of interest (sorry it's taken on a phone):

With the way it's geared, one step should equal a movement of the camera of about one micron. What I want to be able to do with the controller is choose the number of pictures I want to take and how many steps to move between each picture. Then I want to push a button and leave it to get on with it.

I'm using the following hardware:
Arduino Uno
easydriver v4.3
stepper motor with timing belt
serial lcd 16x2
momentary push button switch
sparkfun rotary encoder with button
opocouplers for camera control

I patched the code together from various sources (thank you everyone I've borrowed from). Here it is:

#include <SLCD.h> // 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 A0
#define ENC_B A1
#define ENC_PORT PINC

static uint8_t steps = 0;
static uint8_t numPictures = 0;

int pushButton = 2; // Pin 2 = Start/ Stop Button
int rotaryButton = 3;
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 sleep = 10; // Pin 12 = Cut power to stepper motor when not in use
int ms1 = 11; // Pin 9 = Use with MS1 to enable/ disable microstepping (default disabled)
int ms2 = 12; // Pin 10 = Use with MS2 to enable/ disable microstepping (default disabled)
int toggleLed = 13; // Pin 13 = Switch onboard LED on/off depending on status of toggle button

//pushButton toggle
int buttonState = HIGH; // the current state of the output pin
int reading; // the current reading from the input pin
int previous = LOW; // the previous reading from the input pin
long time = 0; // the last time the output pin was toggled
long debounce = 200; // the debounce time, increase if the output flickers

//rotaryButton toggle
int rbbuttonState = HIGH; // the current state of the output pin
int rbreading; // the current reading from the input pin
int rbprevious = LOW; // the previous reading from the input pin
long rbtime = 0; // the last time the output pin was toggled
long rbdebounce = 200; // the debounce time, increase if the output flickers

void setup()

{

lcd.init(); // Start lcd display

Serial.begin (9600);

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(ms1, OUTPUT);
pinMode(ms2, OUTPUT);
pinMode(focus, OUTPUT);
pinMode(shutter, OUTPUT);
pinMode(toggleLed, OUTPUT);

digitalWrite(ms1, LOW); // Set states of MS1 and MS2 to LOW to disable microstepping
digitalWrite(ms2, LOW); // Set states of MS1 and MS2 to LOW to disable microstepping
digitalWrite(focus, LOW);
digitalWrite(shutter, LOW);
digitalWrite(ENC_A, HIGH);
digitalWrite(ENC_B, HIGH);

}

void loop(){

if (buttonState == HIGH){
if (rbbuttonState == HIGH){

static uint8_t steps = 0; //this variable will be changed by encoder input
int8_t tmpdata;
/**/
tmpdata = read_encoder();
if( tmpdata ) {
steps += tmpdata;
}

lcd.print("Num steps: ", 0, 1);
Serial.print (numPictures, DEC);
lcd.print("Step size: ", 1, 1);
Serial.print (steps, DEC);

}

else{

static uint8_t numPictures = 0; //this variable will be changed by encoder input
int8_t tmpdata;
/**/
tmpdata = read_encoder();
if( tmpdata ) {
numPictures += tmpdata;
}

lcd.print("Num steps: ", 0, 1);
Serial.print (numPictures, DEC);
lcd.print("Step size: ", 1, 1);
Serial.print (steps, DEC);

}
}

else{

for (int h = 0; h < numPictures; h++){ // loop for number of times dictated by var numPictures

lcd.clear();
lcd.print("Moving forward", 0, 1);
lcd.print(">> ", 1, 1);
Serial.print (steps);
Serial.print (" microns");
delay(1500); // 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; i++) // Iterate doStep for number of steps dictated by var encoder0Pos

{

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(1500); // Allow any vibrations from movement to cease before taking a picture

lcd.clear();
lcd.print("Taking picture", 0, 1);
lcd.print("5 secs", 1, 1);

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(500); // 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(4500); //Pause to allow for camera to take picture with 2 sec mirror lockup and to allow flashes to recharge before next shot

lcd.clear();

}
}
}

lcd.print("Stack finished", 0, 1);
delay(2000);
buttonState = HIGH;
}

}

void buttonChange(){

reading = digitalRead(pushButton);

if (reading == HIGH && previous == LOW && millis() - time > debounce) {
if (buttonState == HIGH)
buttonState = LOW;
else
buttonState = HIGH;

time = millis();
}

digitalWrite(toggleLed, buttonState);

previous = reading;
}

void rotaryButtonChange(){

rbreading = digitalRead(rotaryButton);

if (rbreading == HIGH && rbprevious == LOW && millis() - rbtime > 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 )]);
}

The idea is the loop defaults to the 'setup area' where you select your variables and then when pushButton is toggled it then runs the main part of the loop before returning to setup.
Within setup the rotary encoder pushbutton (rotaryButton) toggles between selecting numPictures and steps.

This bit isn't working properly though -

1/ when the toggle button is clicked it does switch inputs but it often makes the previous number 'jump' to another random figure.
2/ the numbers input also don't seem to get passed to the main part of the loop as it finishes instantly, suggesting numPictures and steps are still set to 0 - I'm misundertanding how to pass information up to global variables?
3/ rotary encoder feedback is not very good, numbers go up erratically but don't seem keen to come down again. The code (http://www.circuitsathome.com/mcu/reading-rotary-encoder-on-arduino) worked brilliantly by itself in a test sketch but the way I've implemented it seems to have damaged its functionality.

Can anyone point out the places where I've gone completely wrong please and if possible suggest a better way of implementing the rotary encoder code? :slight_smile:

 int8_t tmpdata;
      /**/
      tmpdata = read_encoder();
      if( tmpdata ) {
        steps += tmpdata;
      }

is more simply written

steps += read_encoder ();

You've got a lot of things with global scope that don't need to be.

I'd add volatlile qualifiers to the stuff changed within the ISRs.

You've got two static "steps" and two "numPictures"

"rbtime" - there's an interesting bug, highlighting the dangers of cut-and-paste programming.

Suggest a code tidy-up.

Thanks for this feedback, really appreciated!

The issues seem fairly obvious now you've pointed them out, but as a novice at this I would have gone round in a lot of circles before I realised.

Point taken about copying and pasting, seems it's a good way of making the situation worse without realising.

The code now picks up the values of steps and numPictures and runs as it should.

My rotary encoder input is still a bit unresponsive but it's a step closer... :slight_smile:

My rotary encoder input is still a bit unresponsive but it's a step closer

Those things can be pretty tricky.
I have some cheap mechanical ones, and the contact bounce is appalling - very difficult to read fast.

Nice-looking mechanics, BTW.
Please post some pics when you get going.

I've got an old Pentax bellows set somewhere...I wonder if I've got any steppers spare.

Will do! Spent so long building it I haven't had much of a chance to take any pictures yet :slight_smile:

I followed the suggestion that goes with the rotary encoder code and upped the baud rate, which seems to have helped the feedback on the encoder quite a bit. I do have another problem to fix though - because the lcd doesn't clear between loops in the setup menu it writes the new values over the old - which leads to values getting left on the screen when the number goes over a factor of ten and then under again. e.g. If I go from 9 to 10 and then back to 9 again, it displays as 90. Not ideal.

This is probably also a stupid question but can I avoid this issue by converting my numbers to three figures? How can I display for example 1 as 001, 10 as 010 etc?

Did you find a way to format the string to 3 chars? I'm using the code from here: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1205038401

iamrichard
Nice setup.
I have a project in which part of the display was displaying the time.
I used to check if it was below 10, and then print a space, then the number.
Only one or two extra lines.
Basically you need to write three chars to that area each time. Either they are three valid numbers, or you pad it with 0 (or space).

Checked my listing, but ended up using a new time library, and it automatically gives me leading zero.
I do have a routine for scrolling a message that is variable in length in the middle of the display, without writing the whole display, if you ever get stuck.

Encoders need to be on an interrupt, but you should check to see you aren't introducing delays somewhere else.
I used the millis() and check if the difference was equal to the delay I wanted.
It lets the program run faster, but has the necessary delay where it counts.

Cheers
Mark