Read quadrature rotary encoder and trigger camera

Hi,
I am reading a quadrature rotary encoder (GHS30-4E360BST5) with my Arduino Uno (for final usage an Arduino Nano will be used). The incoming singals will go to the two external interrupt pins of the Uno/Nano. Every Pin change with rising edge causes a ISR where I detect the rotary direction and so increment/decrement my counter value. Every 220 detected steps I want to trigger a camera for taking a picture. So I put this also in the ISRs, because this ensures the delay between the incoming 220th signal and the triggering of the camera is as small as possible.

One phase of the quadrature encoder has 360 increments. So 4*360 = 1440 steps would be possible to detect while the encoder makes a full 360° round. But I can only use 720 steps when I also want to detect the direction (this might change in between sometimes).
Rotary encoder runs with 76rrpm.

The Arduino digital Pin 10 and GND is directly connected to the trigger (galvanically isolated by an optocoupler) of the camera

Well I changed a lot at the code and I'm a little bit confused now, because sometimes it seems to me that the direction of the encoude where it counts positive changes.
Also I have the problem that suddenly the camera doesn't seem to be triggered, although it worked before. When this happened, I disconnected the Arduino to the camera trigger and triggered it manually by a switching power supply (why was set to 5V). This worked fine. So the problem seems to come from the Arduino.

Does anybody see where these problems could come from?

Here is my code:


const int phaseA = 2;           // pin for green wire of encoder
const int phaseB = 3;           // pin for white wire of encoder
volatile long tmp2 = 0;         // for triggering the camera
volatile long counter = LOW;    // counts the states of phase A and B, should be volatile so this variable (shared between main and ISR) will always be updated correctly
const long toggleStates = 220;  // 220 state changes mean a very exactly tape movement of 24 mm in forward direction
const int triggerPin = 10;      // toggle this digital pin for triggering the camera




void setup() {
  // put your setup code here, to run once:
  pinMode(triggerPin, OUTPUT);
  pinMode(phaseA, INPUT_PULLUP);
  pinMode(phaseB, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(phaseA),detectA, RISING); //enables external Interrupt at pin of phaseA (Arduino Uno can only use pin 2 and 3)
  attachInterrupt(digitalPinToInterrupt(phaseB),detectB, RISING); //enables external Interrupt at pin of phaseB (Arduino Uno can only use pin 2 and 3)
}


void loop() { 
  // put your main code here, to run repeatedly: 
}

//ISR for phase A
void detectA() {
  // bitRead(PIND, 3) is probably the same as direct port manipulation
  //byte p3 = PIND&(1<<3); // read Arduino Uno Digital Pin 3 as fast as possible
  if (bitRead(PIND, 3) == LOW){ // check other phase for determine direction 
    counter++; 
  }else {
    counter--;
  }

  if (counter == tmp2+toggleStates){ 
    tmp2 = counter;
    //digitalWrite(triggerPin, HIGH);
    PORTB |= (1 << PINB2); // camera will be triggered by rising edge, so this sets Arduino Uno Digital Pin 10 to High
  }else {
    //digitalWrite(triggerPin, LOW);
    PORTB &= ~(1 << PINB2); // turn off the camera trigger pin if it wasn't rised before (in the same isr run), so this sets Arduino Uno Digital Pin 10 to Low
  }
}

//ISR for phase B
void detectB() {
  // bitRead(PIND, 2) is probably the same as direct port manipulation
  //byte p2 = PIND&(1<<2); // read Arduino Uno Digital Pin 2 as fast as possible
   if (bitRead(PIND, 2) == HIGH){ // check other phase for determine direction
    counter++; 
  }else {
    counter--;
  }
  
  if (counter == tmp2+toggleStates){ 
    tmp2 = counter;
    //digitalWrite(triggerPin, HIGH);
    PORTB |= (1 << PINB2); // camera will be triggered by rising edge, so this sets Arduino Uno Pin 10 to High
  }else {
    //digitalWrite(triggerPin, LOW);
    PORTB &= ~(1 << PINB2); // turn off the camera trigger pin if it wasn't rised before (in the same isr run), so this sets Arduino Uno Pin 10 to Low
  }
}

Have you validated how much slower it would really be to handle the encoder value in the loop? Do a few hundreds of microsecond really matter? How long does the shutter needs to stay open/camera needs to take a picture?

1 Like

Hi uncle
try to test your 220 steps detection function with a message on the terminal, this will help you to know where the malfunction comes from ? here is a code suggestion
.
.

  // Arduino pro-mini 5V 16 M         *optical* encoder          A & B on pin 2 & 3 
  // if you want to use a mechanical encoder, signals A & B *MUST* be (electricaly) properly debounced !

  #define SignalA          B00000100                          // encoder signal pin 2 (port D)
  #define SignalB          B00001000                          // encoder signal pin 3 (port D)
  #define SignalAB         B00001100                          // both signals
  volatile int             encodPos;                          // encoder position
  volatile byte            LastPortD = SignalA;               // previous A/B state 
  int encodLastPos;                                           // ----- for this demo, previous position

  const long toggleStates = 220;                              // 220 state changes mean a very exactly tape movement of 24 mm in forward direction
  const int  triggerPin = 10;                                 // toggle this digital pin for triggering the camera



void setup(void) {
  PORTD |= SignalAB;                                          // internal pullups on interrupt pins 2, 3
  Serial.begin(115200);                                       // fast fast fast !
  attachInterrupt(digitalPinToInterrupt(2), ExtInt, CHANGE);  // encoder pin 2 interrupt vector
  attachInterrupt(digitalPinToInterrupt(3), ExtInt, CHANGE);  // encoder pin 3 interrupt vector 
}


void ExtInt() {                                               // OPTICAL ENCODER ext. interrupt pin 2, 3
  byte PortD =      PIND  & SignalAB;                         // 
  if ((PortD ^ LastPortD) & SignalA)   encodPos++;            // Rotation ->    {encodPos++; Sense = 1;}
  if ((PortD ^ LastPortD) & SignalB)   encodPos--;            // Rotation <-    {encodPos--; Sense = 0;}
  if ((PortD) &&  (PortD != SignalAB)) PortD ^= SignalAB;     //                              (swap A-B)
   LastPortD  =    PortD;                                     //                  mieux vaut faire court
   
  // to be tested***********************************************
  if (encodPos % toggleStates == 0) {                         // 0, 220, 440, 660  and so on
   PORTB |= (1 << PINB2); // camera will be triggered by rising edge, so this sets Arduino Uno Pin 10 to High
  } else {
    //digitalWrite(triggerPin, LOW);
    PORTB &= ~(1 << PINB2); // turn off the camera trigger pin if it wasn't rised before (in the same isr run), so this sets Arduino Uno Pin 10 to Low
  } // to be tested*********************************************
}


   
  
void loop(void) {                                             // MAIN LOOP

  noInterrupts();                                             //
    int encodPosition = encodPos;                             //
  interrupts();                                               //

  if (encodLastPos != encodPosition) {                        // when the encoder change,
    encodLastPos = encodPosition;                             //
    Serial.println(encodPosition);                            // print encoder position
    
    if (encodPosition % toggleStates ==0)  Serial.println("220 !");  
  }                                                           //
}                                                             //

1 Like

Thanks for your response.
No, I didn't validate it, but I'm also not sure about your point. Why should I cause a delay of a few hundreds of microseconds if it's possible to avoid it?
Another thought of mine: Isn't there the risk that my trigger pin will never be rised if the signals of the rotary encoder are too fast? This should be because interrupts do have higher priority. Well but then I could also argue, that I lose signals because of my ISR needs to much time. And while an ISR is running, other ISRs are blocked. Or is this what you are thinking about: reducing the time needed for the ISRs to avoid losing incoming signals from the rotary encoder?

Shutter is open for 3-7ms. I am trying to look at a moving (actually never ending) object and I don't want to let something out on the images.

The idea at the back of my question was to keep the rotary encoder code as simple as possible (ie use the encoder library) and handle the movement in the loop.

I don't know how you drive your shutter but my experience has been that it's not just the front that triggers the shot but you need to maintain the pin HIGH (or LOW) long enough for this to be taken into account. Here the front stays HIGH until the next encoder interrupt. is that enough for the camera to react? how fast is the system rotating?

Also if the shutter stays open for 3-7ms it probably means the object is not moving significantly during that time or you'd get a blurred image. so the few micro seconds you might lose by handling the triggering in the loop should not make any significant difference.

here is a basic code I frequently use when I've a rotary encoder to deal with (you can get rid of the button/easyRun thingy if you don't care for the rotary button press)

#include <Encoder.h>  // https://www.pjrc.com/teensy/td_libs_Encoder.html
#include <easyRun.h>  // https://github.com/bricoleau/easyRun

const byte encoderCLKPin = 2;    // encoder encoder CLK
const byte encoderDTPin  = 3;    // encoder encoder DT
Encoder encoder(encoderDTPin, encoderCLKPin);

const byte encoderSWPin = 4;     // encoder encoder SW
button encoderSwitch(encoderSWPin);

long currentPosition = 0;

bool encoderChanged() {
  long newPosition = encoder.read() >> 1;   // my encoder generates 2 ticks per click so I divide by 2
  if (newPosition != currentPosition) {
    currentPosition = newPosition;
    return true;
  }
  return false;
}

bool switchPressed() {
  easyRun();
  return encoderSwitch;
}

void setup() {
  Serial.begin(115200); Serial.println();
  Serial.println(F("Ready"));
}

void loop() {
  if (encoderChanged()) {
    Serial.println(currentPosition);
  }

  if (switchPressed()) {
    Serial.println(F("Switch Pressed"));
  }
}

i've found that even moving an encoder by hand requires an interrupt to track changes. the higher the resolution of the encoder, the more frequent the changes.

but the ISR should simply update a position. i don't understand why you have two ISRs and not sure about the other processing in the ISR. can't that be done in loop()

I would try something like this before going to port manipulations

#include <Encoder.h>  // https://www.pjrc.com/teensy/td_libs_Encoder.html

const byte encoderCLKPin = 2;    // encoder encoder CLK
const byte encoderDTPin  = 3;    // encoder encoder DT
Encoder encoder(encoderDTPin, encoderCLKPin);

const byte trigPin = LED_BUILTIN;

long currentPosition = 0;

enum : byte {IDLE, PICTURE} state = IDLE;
const long trigCountThreshold = 220;
const unsigned long trigDuration = 7; // 7ms max for taking a picture
unsigned long trigStart;

bool encoderChanged() {
  long newPosition = encoder.read() >> 1;   // my encoder generates 2 ticks per click so I divide by 2
  if (newPosition != currentPosition) {
    currentPosition = newPosition;
    return true;
  }
  return false;
}

void setup() {
  pinMode(trigPin, OUTPUT);
  digitalWrite(trigPin, LOW); // not really necessary it's the default
  
  Serial.begin(115200); Serial.println();
  Serial.println(F("Ready"));
}

void loop() {

  if (encoderChanged()) {                                                 // did we move?
    if (currentPosition % trigCountThreshold == 0) {                      // is it time to trigger a picture?
      if (state == IDLE) {                                                // is the camera IDLE?
        trigStart = millis();
        digitalWrite(trigPin, HIGH);
        state = PICTURE;
      } else {
        Serial.println(F("ERROR: SYSTEM TURNING TOO FAST. MISSED SHOT"));
      }
    }
  }

  if ((state == PICTURE) && (millis() - trigStart >= trigDuration)) {     // do we need to turn off the trigger pin
    digitalWrite(trigPin, LOW);
    state = IDLE;
  }

}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.