Skip to main content

Camera mirror lock up and shutter activation timing

Posted in

This section doesn't get a lot of traffic.

Hi. As posted in the introduction forum, I hope to solve the issues I have with a small project that I've been working on for some time now.

The equatorial mount that I use for astrophotography is beautifully made with excellent mechanical properties for the task. The problen is that it is low tech and requires a lot of intervention. Button pushing between shots for hours on end.

I designed an Arduino shield that can be used for stepper control, in two axis if required, or in this case an optocoupler and stepper for camera control and control of a small robot to push the hand controller buttons. The bot works well as does the camera electronics. The problem I cannot solve is timing in the Arduino environment, partly because my programming skills are amateur level, although I've learned quite a bit through this project.

The requirements of the system are quite simple; as follows

Shutter lock up - wait for 3 seconds, then fire the shutter.
Shutter and mirror remain open for exposure time.
Close shutter (and mirror)
Activate robot to manipulate hand controller buttons... and start all over again...

The issue is, whether using delay(); millis(); or MsTimer2.h the program adds the mirror lockup time to the shutter time. This means that the delay between mirror lock up and shutter activation is several minutes, whereas the mirror is required to lock up 3 seconds before the shutter opens and remain open until the shutter closes.

My setup is through an optocoupler, pin 8 shutter and pin 11 mirror, appropriately earthed. Independently both functions work fine. The problem as mentioned is getting the timing right.

Referring to the MsTimer2.h code posted by shutterdrone I cannot see how the code might be changed so that the mirror and shutter times are not added, but operate independently. I would need to add the stepper function also, but that should be fairly easy once the timing issues are resolved.

Thanks for taking the time to look. Hoping to resolve this soon and get on top of the programming.

#include <MsTimer2.h>

  // shot interval time
#define INTERVAL_TM 3000 //mirror settle time

  // exposure length (how long to fire camera)
#define EXPOSURE_TM 6000 //this would be longer, but 6 seconds is OK for testing

  // our pin that will trigger the camera
#define CAMERA_PIN 8 //changed this to suit setup. Mirror pin 11 - need to implement this?

  // last time our camera fired
unsigned long last_tm = 0;

  // whether or not we're currently exposing
bool exposing = false;

void setup() {
 
    // prepare camera pin as an output
  pinMode(CAMERA_PIN, OUTPUT);
 
    // for debugging purposes
  Serial.begin(19200);
}

void loop() {

 
  if( exposing == false && millis() - last_tm > INTERVAL_TM ) {
   
      // if the camera is not currently exposing, and our
      // timer has elapsed, fire camera...
     
   
      // set timer interrupt to call our
      // function to disengage the camera
    MsTimer2::set( EXPOSURE_TM, camera_off );
   
      // enable timer
    MsTimer2::start();
   
      // set flag to indicate that we're currently
      // exposing
     
    exposing = true;
   
      // enable optocoupler
    digitalWrite(CAMERA_PIN, HIGH);
   
  } // end if not exposing and timer has elapsed
   
     
}

void camera_off() {
 
     // disable optocoupler
  digitalWrite(CAMERA_PIN, LOW);

     // disable timer
  MsTimer2::stop();
 
     // we set this now to ensure that our
     // interval time is measured from the
     // time an image is completed until the
     // time the next one is triggered
     //
     // if you want the interval time to be
     // measured between the time the image
     // is triggered and the next image is
     // triggered, move this to before
     // bringing the camera pin high
     
  last_tm = millis();
   
     // reset exposing flag
  exposing = false;
}

Hi Rowland, Generally, you

Hi Rowland,

Generally, you want to get as close to your interval time as possible, without going under.

To avoid adding times, etc. consider going through a series of state changes, e.g. in your main loop:

 // starts in "ok to MLU mode"

byte current_state = 1;

...

void loop() {

  switch(current_state) {

   case 1:

     if( millis() - last_tm > INTERVAL_TM ) {
        // ok to fire camera, but we want MLU first
        trigger_mlu(); // function should set current_state = 2
                       // when ready to trigger shutter
        current_state = 0;
        last_tm = millis();
     }

     break;

   case 2:

     // ok to fire camera now
     fire_camera(); // function should set current_state = 3
                    // when done exposing
     current_state = 0;
     break;

   case 3:

    // ok to push robot button
    activate_robot(); // function should set current_state = 1
                      // when done operating robot
    current_state = 0;
    break;

   default:
    // do nothing when in state 0, or any unknown state
    break;

  }
}

Now, you simply control the value of current_state in the functions called by MsTimer2 when the timers go off, so that, say, "done_mlu()" sets state to 2, "done_camera()" sets state to 3, and "done_robot()" sets state to 1. (Back to beginning) Until the functions are triggered by MsTimer2, current_state stays at 0, so nothing happens in the main loop. If the total time spent in mlu, exposing, and robot activation exceeds your interval, mlu will start immediately as the robot operation finishes. This is the way you want the timing to work, as you don't want the camera to expose while the robot is working, if you accidentally set your interval too low.

!c

Hi !c. Thank you. This level

Hi !c. Thank you. This level of coding is beyond me at the moment and this very helpful. Switch was a method that I had considered as a possibility. I will post the draft sketch in the near future. An aspect is the box spiral dither pattern that the button bot must follow.

Rowland

Hi. This is the latest.

Hi. This is the latest. Timing and switching between cases is a breeze with MsTimer2. The only problem I've encountered is the stepper not stepping? This is the first time I have seen this. The coils on pins 5 and 6 light up (using leds for testing) and remain so - motor stationary. Tried many different methods to overcome, but no resolution? Close, but not close enough.

EDIT: It works with both or one stepper line commented out.

#include <Stepper.h>
#include <MsTimer2.h>

//------variables---------//

#define sessiontime 60000

#define exposuretime 6000

#define motorSteps 200

int steps = 50;

int motorspeed = 5;

//---------fixed---------//

#define interval 5000

#define delaytime 3000

unsigned long time = millis();

bool dithering = false;

int ledPin = 13;

byte current_state = 1;

//stepper - ditherbot
#define motorPin1 4
#define motorPin2 5
#define motorPin3 6
#define motorPin4 7

Stepper stepper(motorSteps, motorPin1,motorPin2,motorPin3,motorPin4);

void trigger_mlu() {
  digitalWrite(12, HIGH);
  MsTimer2::set(delaytime, fire_camera);
  MsTimer2::start();
}

void fire_camera() {
  digitalWrite(8, HIGH);
  MsTimer2::set(exposuretime, camera_off);
  MsTimer2::start();
}

void camera_off() {
  digitalWrite(8, LOW);
  digitalWrite(12, LOW);
  MsTimer2::set(delaytime, activate_robot);
  MsTimer2::start();
}

void activate_robot() {
  digitalWrite(ledPin, HIGH);
  stepper.setSpeed(motorspeed);
  stepper.step(steps);
  digitalWrite(ledPin, LOW);
  MsTimer2::set(interval, trigger_mlu);
  MsTimer2::start();
  dithering = false;
}

void setup() {

  pinMode(8, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);

  Serial.begin(19200);
}  

void loop() {

  switch(current_state) {

  case 1:

    if (time <= sessiontime && dithering == false) {

      trigger_mlu(); // function should set current_state = 2
      // ready to fire_camera
      current_state = 0;
    }
    else
    {
      digitalWrite(8, LOW);
      digitalWrite(12, LOW);
      {
        //remain in loop doing nothing
        while(1){
        }
        break;

      case 2:

        fire_camera(); // function should set current_state = 3
        // ready to camera_off
        current_state = 0;
        break;

      case 3:

        camera_off(); // current_state = 4
        // ready to dither
        current_state = 0;
        break;

      case 4:

        activate_robot(); // function should set current_state = 1
        // ready to start again
        current_state = 0;
        break;

      default: // do nothing when in state 0, or any unknown state
        break;
      }  
    }
  }
}

Rowland, I don't actually use

Rowland,

I don't actually use the stepper library, as I prefer using an external driver that uses just step/dir instead of wasting valuable clock cycles timing interchanges between pole states on the stepper. So, I'm not sure I can help you with that...

This is no bueno:

//remain in loop doing nothing
        while(1){
        }
        break;

It will never exit, for any reason. If you're just using cascading MsTimer2 calls, then get rid of the rest of your main loop - it's worthless once you hit here, although I suggest not doing this, as it makes your code difficult to decipher.

But, it doesn't matter any how, as you'll never hit that code, AFAICT, given that:

  if (time <= sessiontime && dithering == false) {

'time' never seems to be updated.

I also don't see any MsTimer2::stop()s, so do know that it keeps triggering on cycle until you stop it =)

I'm pretty sure that is -not- what you want:

void trigger_mlu() {
  digitalWrite(12, HIGH);
  MsTimer2::set(delaytime, fire_camera);
  MsTimer2::start();
}

void fire_camera() {
  digitalWrite(8, HIGH);
  MsTimer2::set(exposuretime, camera_off);
  MsTimer2::start();
}

void camera_off() {
  digitalWrite(8, LOW);
  digitalWrite(12, LOW);
  MsTimer2::set(delaytime, activate_robot);
  MsTimer2::start();
}

void activate_robot() {
  digitalWrite(ledPin, HIGH);
  stepper.setSpeed(motorspeed);
  stepper.step(steps);
  digitalWrite(ledPin, LOW);
  MsTimer2::set(interval, trigger_mlu);
  MsTimer2::start();
  dithering = false;
}

activate_robot() and the main loop both call trigger_mlu(), and so on.

Choose one or the other method to handling timing: either transition through states and check the state in the main loop, or trigger cascading MsTimer2 interrupts, and get rid of your main loop (not recommended). My preference is to use the main loop to check state, as it makes your code exceedingly obvious when you need to debug a problem later, whereas chasing cascading triggers from MsTimer2 is brutal (ask me how I know =).

e.g., what I meant when I was talking earlier, consider the following:

void loop() {

  switch(current_state) {

   case 1:

     if( millis() - last_tm > INTERVAL_TM ) {
        // ok to fire camera, but we want MLU first
        trigger_mlu(); // function should set current_state = 2
                       // when ready to trigger shutter
        current_state = 0;
        last_tm = millis();
     }

     break;

   case 2:

     // ok to fire camera now
     fire_camera(); // function should set current_state = 3
                    // when done exposing
     current_state = 0;
     break;

   case 3:

...

}

...

void trigger_mlu() {
  digitalWrite(12, HIGH);
  MsTimer2::set(mlu_delaytime, close_mlu);
  MsTimer2::start();
}

void close_mlu() {

 digitalWrite(12, LOW);
 MsTimer2::stop();
 current_state = 2;
}

void fire_camera() {

 digitalWrite(camera_pin, HIGH);
 MsTimer2::set(exposure_tm, stop_firing);
 MsTimer2::start();
}

void stop_firing() {

 digitalWrite(camera_pin, LOW);
 MsTimer2::stop();
 current_state = 3;
}

HTH!

!chris

Thanks again Chris. This is

Thanks again Chris. This is the latest. The timing appears perfect using led's to test the sequence, but once the camera is attached, the code below is the only configuration that works - sort of...

The sequence is;

mirror lockup
robot activates
shutter fires
robot activates
mirror lockup
robot
shutter
robot
mirror etc...

Consequently, the mirror is locked up for the equivalent of the exposure time on the first and every other iteration.

I have tried many other configurations - too many to mention, and this is the closest I can get. Maybe the mirror/shutter timing is a hardware issue and the equivalent 'first press' for mirror lock up and 'second press' for shutter is not being emulated correctly in this case. Or is it just a timing issue?

EDIT: Taking a look at the All in One canon wiring I should check this out first.

#include <Stepper.h>
#include <MsTimer2.h>

//------variables---------//
//set for testing//

#define session_tm 600000

#define exposure_tm 5000

#define motorSteps 200

int steps = 50;

int motorspeed = 5;

//---------fixed---------//
//set for testing //

#define interval_tm 1000

#define mlu_delaytime 1000

#define shutter_pin 8

#define mirror_pin 11

long time = millis();
long last_tm = 0;

int ledPin = 13;

byte current_state = 1;

//stepper - ditherbot
#define motorPin1 4
#define motorPin2 5
#define motorPin3 6
#define motorPin4 7

Stepper stepper(motorSteps, motorPin1,motorPin2,motorPin3,motorPin4);

void trigger_mlu() {
  digitalWrite(mirror_pin, HIGH);
  MsTimer2::set(mlu_delaytime, close_mlu);
  MsTimer2::start();
}

void close_mlu() {
 digitalWrite(mirror_pin, LOW);
  MsTimer2::stop();
  current_state = 2;
}

void fire_camera() {
  digitalWrite(shutter_pin, HIGH);
  MsTimer2::set(exposure_tm, stop_firing);
  MsTimer2::start();
}

void stop_firing() {
 digitalWrite(shutter_pin, LOW);
  MsTimer2::stop();
  current_state = 3;
}

void  activate_robot() {
  digitalWrite(13, HIGH);  
  stepper.setSpeed(motorspeed);
  stepper.step(steps);
  digitalWrite(13, LOW);
// THIS IS THE ONLY CONFIGURATION THAT WORKS - CURRENTLY
  MsTimer2::set(interval_tm, fire_camera);
  MsTimer2::start();
}

void end_session() {
  digitalWrite(shutter_pin, LOW);
  digitalWrite(mirror_pin, LOW);
  MsTimer2::stop();
}

void setup() {

  pinMode(shutter_pin, OUTPUT);
  pinMode(mirror_pin, OUTPUT);
  pinMode(13, OUTPUT);
  Serial.begin(19200);
}  

void loop() {

  if (millis() >= session_tm) {
    end_session();
  }

  switch(current_state) {

  case 1:

    if( millis() - last_tm > interval_tm ) {
      // ok to fire camera, but we want MLU first
      trigger_mlu(); // function should set current_state = 2
      // when ready to trigger shutter
      current_state = 0;
      last_tm = millis();
    }

    break;

  case 2:

    // ok to fire camera now
    fire_camera(); // function should set current_state = 3
    // when done exposing
    current_state = 0;
    break;

  case 3:

    activate_robot(); // function should set current_state = 1
    // ready to start again
    current_state = 0;
    break;

  default: // do nothing when in state 0, or any unknown state
    break;
  }      
}

When you say "is the only

When you say "is the only code that works" - what does not working mean?

I'm quite confused that the MLU pin is -not- the shutter pin? On all of my cameras, you tap the shutter to engage the MLU, and then after a period of time, the camera triggers the exposure, or you trigger the exposure (using the same line, there are, after all, only two control lines to the camera: shutter and focus). That is to say, only need to press the shutter and wait when in that mode - but I guess Canons are different?

It's fairly obvious to see why your program goes into a loop and just loops back and forth between robot and shutter =) activate_robot() calls fire_camera() which means you will forever flip back and forth between states 0 and 3, and never call your MLU function after the first time.

I fear a big part of what you're experiencing has everything to do with the fact that there is no delay between any of these steps. Have you tried a blocking, straight-forward implementation first, and then ensured that everything worked correctly before moving to non-blocking timing?

e.g.:

Quote:Shutter lock up - wait for 3 seconds, then fire the shutter.
Shutter and mirror remain open for exposure time.
Close shutter (and mirror)
Activate robot to manipulate hand controller buttons... and start all over again...
Quote:
The issue is, whether using delay(); millis(); or MsTimer2.h the program adds the mirror lockup time to the shutter time. This means that the delay between mirror lock up and shutter activation is several minutes, whereas the mirror is required to lock up 3 seconds before the shutter opens and remain open until the shutter closes.

That second paragraph is hard for me to decipher, there's no way a blocking mode would result in this unless you wrote the wrong values somewhere, consider:

void loop() {

  pinMode(mirror_pin, HIGH);
   // hold open for 3 seconds
  delay(3000);
  pinMode(shutter_pin, HIGH);
  delay(exposure_time);
  pinMode(shutter_pin, LOW);
  pinMode(mirror_pin, LOW);
  delay(100);

  digitalWrite(13, HIGH);  
  stepper.setSpeed(motorspeed);
  stepper.step(steps);
  digitalWrite(13, LOW);
 
  delay(interval_time);
 
}

This bring the MLU high for exactly three seconds, and then triggers the shutter, it does not add exposure time to the MLU time.

It's unlikely there's a distinct "mlu control line", unless you're doing something quite interesting with a robot to press a button or something! =)

What camera are you using, and how are you engaging MLU - through the remote port, or through some external entity that presses a button?

!c

Thanks Chris. The problem was

Thanks Chris. The problem was hardware, as you suggested. The way in which the firing of the shutter was implemented was incorrect. Setting the shutter pin HIGH then delay(); and then setting it LOW, locks the mirror up and prepares to fire the shutter again on the same pin - as you know.

Ending the sequence is still a bit of a hack - working on that.

The only other addition is the stepping sequence for dithering so that movement in Right Ascension and Declination follows a box shaped spiral, repositioning between shots. I will look at step/dir as an alternative to the stepper library to implement this.

Many thanks for your time. This exercise has been very informative and has developed my programming know how 1000%.

Rowland.