LANC camera controller with rotary encoder - debugging

Hi folks,

First time posting here!

I'm working on a controller for my camera. Purpose is to control rec/stop with a button, and the iris ("aperture") with a rotary encoder. In the attachment you can see the code, my setup and what a 'default' LANC circuit looks like. The code is a combination of two sets of code:

and:

The board I'm using is a Beetle v1.1 by DFrobot. I've connected the rotary encoder to A0 and A1. The rotary encoder is also connected to the Vin and Ground pins of the Beetle.

The setup works somewhat. I have two questions for the community I hope to get answered:

  1. When I turn the rotary encoder the iris will open/close most of the times. But sometimes (seemingly random) the iris will suddenly open/close the opposite way, or take multiple steps in one. It seems the 'steps' of the rotary encoder aren't reliably being recognized. Am I doing something wrong coding wise?

  2. I would like the iris to open/close slower. At least two 'steps' of the rotary encoder for one iris stop. Is there an easy way to 'slow' this down? Or do I have to work with a counter or something? (Which feels cumbersome.)

More information about the LANC protocol can be found here: http://www.boehmel.de/lanc .

/*
SIMPLE LANC REMOTE
Version 1.0
Sends LANC commands to the LANC port of a video camera.
Tested with a Canon XF300 camcorder
For the interface circuit interface see 
http://controlyourcamera.blogspot.com/2011/02/arduino-controlled-video-recording-over.html
Feel free to use this code in any way you want.
2011, Martin Koch

"LANC" is a registered trademark of SONY.
CANON calls their LANC compatible port "REMOTE".
*/

#define cmdPin 10 
#define lancPin 9
#define recButton 11
#define irisopen A0
#define irisclose A1
int cmdRepeatCount;
int bitDuration = 104; //Duration of one LANC bit in microseconds. 
int counter = 0; 
int aState;
int aLastState;  

//Start-stop video recording
boolean REC[] = {LOW,LOW,LOW,HIGH,HIGH,LOW,LOW,LOW,   LOW,LOW,HIGH,HIGH,LOW,LOW,HIGH,HIGH}; //18 33

//IRIS open
boolean IRIS_OPEN[] = {LOW,LOW,HIGH,LOW,HIGH,LOW,LOW,LOW, LOW,HIGH,LOW,HIGH,LOW,HIGH,LOW,HIGH}; //28 55

//IRIS close
boolean IRIS_CLOSE[] = {LOW,LOW,HIGH,LOW,HIGH,LOW,LOW,LOW,   LOW,HIGH,LOW,HIGH,LOW,LOW,HIGH,HIGH}; //28 53

void setup() {

 pinMode(lancPin, INPUT); //listens to the LANC line
 pinMode(cmdPin, OUTPUT); //writes to the LANC line

 pinMode(recButton, INPUT); //start-stop recording button
 digitalWrite(recButton, HIGH); //turn on an internal pull up resistor
 pinMode(irisopen, INPUT); 
 pinMode(irisclose, INPUT); 
digitalWrite(cmdPin, LOW); //set LANC line to +5V
delay(5000); //Wait for camera to power up completly
bitDuration = bitDuration - 8; //Writing to the digital port takes about 8 microseconds so only 96 microseconds are left for each bit

Serial.begin (9600);
aLastState = digitalRead(irisopen);   

}

void loop() {
  
   aState = digitalRead(irisopen);
if (aState != aLastState){     
if (digitalRead(irisclose) != aState) { 
       lancCommand(IRIS_OPEN); 
     } else {
       lancCommand(IRIS_CLOSE); 
     }
} 
   aLastState = aState; 

   if (!digitalRead(recButton)) {
    lancCommand(REC); 
  }
}

void lancCommand(boolean lancBit[]) {
       
        cmdRepeatCount = 0;
  
   while (cmdRepeatCount < 5) {  //repeat 5 times to make sure the camera accepts the command

                while (pulseIn(lancPin, HIGH) < 5000) {   
                  //"pulseIn, HIGH" catches any 0V TO +5V TRANSITION and waits until the LANC line goes back to 0V 
                  //"pulseIn" also returns the pulse duration so we can check if the previous +5V duration was long enough (>5ms) to be the pause before a new 8 byte data packet
                  //Loop till pulse duration is >5ms
                }

   //LOW after long pause means the START bit of Byte 0 is here
   delayMicroseconds(bitDuration);  //wait START bit duration

   //Write the 8 bits of byte 0 
                        //Note that the command bits have to be put out in reverse order with the least significant, right-most bit (bit 0) first
                        for (int i=7; i>-1; i--) {
     digitalWrite(cmdPin, lancBit[i]);  //Write bits. 
     delayMicroseconds(bitDuration); 
                        }
   
                        //Byte 0 is written now put LANC line back to +5V
                        digitalWrite(cmdPin, LOW);
                        delayMicroseconds(10); //make sure to be in the stop bit before byte 1
                        
                        while (digitalRead(lancPin)) { 
                          //Loop as long as the LANC line is +5V during the stop bit
                        }

                        //0V after the previous stop bit means the START bit of Byte 1 is here
         delayMicroseconds(bitDuration);  //wait START bit duration
      
         //Write the 8 bits of Byte 1
                        //Note that the command bits have to be put out in reverse order with the least significant, right-most bit (bit 0) first
                        for (int i=15; i>7; i--) {
              digitalWrite(cmdPin,lancBit[i]);  //Write bits 
             delayMicroseconds(bitDuration);
                        }
 
                        //Byte 1 is written now put LANC line back to +5V
                        digitalWrite(cmdPin, LOW); 

   cmdRepeatCount++;  //increase repeat count by 1
   
   /*Control bytes 0 and 1 are written, now don’t care what happens in Bytes 2 to 7
   and just wait for the next start bit after a long pause to send the first two command bytes again.*/
  

 }//While cmdRepeatCount < 5
}

lanc_controller_rotary.ino (4.27 KB)

  1. When I turn the rotary encoder the iris will open/close most of the times.

What part of that code reads the rotary encoder? I don't see anything that does.

Or do I have to work with a counter or something? (Which feels cumbersome.)

Why does counting the number of encoder steps feel cumbersome?

PaulS:
What part of that code reads the rotary encoder? I don't see anything that does.
Why does counting the number of encoder steps feel cumbersome?

Thanks so much for chiming in!
As you might have guessed, I'm still pretty new to the Arduino programming logic but learning quickly.

Allright, this part of the code reads if it turns, right? Or do you mean something else?

  aState = digitalRead(irisopen);
if (aState != aLastState){     
if (digitalRead(irisclose) != aState) { 
       lancCommand(IRIS_OPEN); 
     } else {
       lancCommand(IRIS_CLOSE); 
     }
} 
   aLastState = aState;

Counting the steps feels cumbersome because I don't know how to include it in the code. In the original code for the rotary there's: Counter ++ and Counter -- . I can probably use that part. Can you help me out to write the code so that after 2x Counter ++ the irisopen is triggered, and the same for 2x Counter -- but then irisclose ?

I really hope it makes sense what I am trying to achieve...

Allright, this part of the code reads if it turns, right?

Does it? I wouldn't expect the rotary encoder pins to be named irisopen and irisclose.

Can you help me out to write the code so that after 2x Counter ++ the irisopen is triggered, and the same for 2x Counter -- but then irisclose ?

For any given value of Counter, adding 1 will change the number from odd to even, or from even to odd. Since you want to do something every other time Counter changes, that means that you want to do something when Counter becomes odd (or becomes even). Can you figure out how to determine if a number is odd or even? The worst way is the modulo operator, but that is the easiest way to understand.

PaulS:
Does it? I wouldn't expect the rotary encoder pins to be named irisopen and irisclose.
For any given value of Counter, adding 1 will change the number from odd to even, or from even to odd. Since you want to do something every other time Counter changes, that means that you want to do something when Counter becomes odd (or becomes even). Can you figure out how to determine if a number is odd or even? The worst way is the modulo operator, but that is the easiest way to understand.

Yeah so I named them irisopen and irisclose. That might be confusing.

But to be sure: does the coding look correct, for what I am trying to achieve?

I will read into the modulo operator command. And what would be the best way to work with counters? Besides modulo operator?

And what would be the best way to work with counters?

I'm sorry, but that's too vague a question. Work how?

Determining if a number is a multiple of 37 by looking at it's bit pattern is hard. Determining if a number is a multiple of 2 by looking at it's bit pattern is easy. The last bit is 0 for even numbers and 1 for off numbers.

PaulS:
I'm sorry, but that's too vague a question. Work how?

Determining if a number is a multiple of 37 by looking at it's bit pattern is hard. Determining if a number is a multiple of 2 by looking at it's bit pattern is easy. The last bit is 0 for even numbers and 1 for off numbers.

You stated that modulo is the "worst way" to work with counters. So I was curious what would be a better way.

My primary concern is still the irregular opening/closing of the iris. Do you have any idea what could cause this?

Anonomatos:
My primary concern is still the irregular opening/closing of the iris. Do you have any idea what could cause this?

I would isolate the encoder code (make a sketch with nothing else in it to clutter things) and set it up to increment/decrement a counter and print said counter to the serial monitor. Verify that the counter moves only once for each encoder detent.

Hi,
Welcome to the forum.

Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?

Can you post a link to the encoder you are using.

I think you haven't quite understood how an encoder works and needs to be inputted to your controller.

A "Beetle";

Tom... :slight_smile:

dougp:
I would isolate the encoder code (make a sketch with nothing else in it to clutter things) and set it up to increment/decrement a counter and print said counter to the serial monitor. Verify that the counter moves only once for each encoder detent.

That was actually simple but effective. The code itself wasn't reliable and there was a lot of noise (I think this is called 'grey code').

There a code from Matthias Hertel with a library included A Library for the Arduino environment for using a rotary encoder as an input. and it works very reliably (not a single error). It checks all four states of the encoded so all the noise is filtered out.

Now I have a next problem... the code from Matthias only prints the current position ("newPos"). Now what I need is to check if it turns clockwise or not.

The loop from Matthias is:

void loop()
{
  static int pos = 0;
  encoder.tick();

  int newPos = encoder.getPosition();
  if (pos != newPos) {
    Serial.print(newPos);
    Serial.println();
    pos = newPos;
  } // if
} // loop ()

My idea was to use the bit from the earlier code and write it like this:

void loop()
{
  static int pos = 0;
  encoder.tick();

  int newPos = encoder.getPosition();
  if (pos != newPos) {
    IRIS_OPEN;
  } else {
    IRIS_CLOSE;
  }
    Serial.print(newPos);
    Serial.println();
    pos = newPos;
  } // if
} // loop ()

Where IRIS_OPEN and IRIS_CLOSE are commands to open/close the iris. But now it gives me an error message "SimplePollRotator:53:2: error: expected declaration before '}' token
;} // loop ()"

So it seems I'm doing something wrong. Can someone point me in the right direction to combine the codes? It seems the earlier code from the first post itself works fine (expect that the rotary encoder input wasn't reliable), I only want to replace this part of the loop correctly so it checks clock/counterclockwise.

Thanks again, all the help is much appreciated :slight_smile:

You have the information. When the encoder turns, why is pos != newPos?

Steep learning curve here!

dougp:
You have the information. When the encoder turns, why is pos != newPos?

So the position changes because it takes the new position from "encoder.getPosition()" and a change has occurred.

Now in the library there's "int8_t getDirection()" and it prints out 0,1,-1. So I can use that, I think.

void loop()
{
  static int pos = 0;
  encoder.tick();

  int newPos = encoder.getPosition();
  if (pos != newPos) {
    Serial.print(newPos);
    Serial.println();
    pos = newPos;

int clockwise = getDirection();  
if (clockwise = 1) {
  IRIS_OPEN
}
  if (clockwise = -1) {
  IRIS_close
}

} // if
} // loop ()

Now the error is "getdirection" was not declared... And when I int Getdirection in the setup it states ""int_getDirection() is initialized like a variable". Ehhhh. I feel like I'm getting closes but not quite there yet.

So I can use that, I think.

Not that way, though. You test for equality using ==, not =.

You call functions with () and ;.

If the function only returns -1, 0, and 1, store the value in a int8_t, instead, using half as much memory.

Thanks for the thorough responses!

I will be puzzling some more later on. Will let you folks know later on if I have more questions.

Update: I'm pretty sure I've got it working. Won't be able to test 'in the field' until tomorrow, but it compiles and some tests with the serial monitor are OK.

Final complete code is:

#include <RotaryEncoder.h>
#define cmdPin 10 
#define lancPin 9
#define recButton 11
#define irisopen A0
#define irisclose A1
int cmdRepeatCount;
int bitDuration = 104; //Duration of one LANC bit in microseconds. 
 
//Start-stop video recording
boolean REC[] = {LOW,LOW,LOW,HIGH,HIGH,LOW,LOW,LOW,   LOW,LOW,HIGH,HIGH,LOW,LOW,HIGH,HIGH}; //18 33

//IRIS open
boolean IRIS_OPEN[] = {LOW,LOW,HIGH,LOW,HIGH,LOW,LOW,LOW, LOW,HIGH,LOW,HIGH,LOW,HIGH,LOW,HIGH}; //28 55

//IRIS close
boolean IRIS_CLOSE[] = {LOW,LOW,HIGH,LOW,HIGH,LOW,LOW,LOW,   LOW,HIGH,LOW,HIGH,LOW,LOW,HIGH,HIGH}; //28 53

RotaryEncoder encoder(A0, A1);

void setup() {

 pinMode(lancPin, INPUT); //listens to the LANC line
 pinMode(cmdPin, OUTPUT); //writes to the LANC line

 pinMode(recButton, INPUT); //start-stop recording button
 digitalWrite(recButton, HIGH); //turn on an internal pull up resistor
 pinMode(irisopen, INPUT); 
 pinMode(irisclose, INPUT); 
digitalWrite(cmdPin, LOW); //set LANC line to +5V
delay(5000); //Wait for camera to power up completly
bitDuration = bitDuration - 8; //Writing to the digital port takes about 8 microseconds so only 96 microseconds are left for each bit

Serial.begin (57600);

}

void loop()
{
  encoder.tick();
 
  int8_t clockwise = encoder.getDirection();
  if (clockwise == 1) {
    lancCommand(IRIS_OPEN);

}
  if (clockwise == -1) {
    lancCommand(IRIS_CLOSE);
}

   if (!digitalRead(recButton)) {
    lancCommand(REC); 
  }

}
void lancCommand(boolean lancBit[]) {
       
        cmdRepeatCount = 0;
  
   while (cmdRepeatCount < 5) {  //repeat 5 times to make sure the camera accepts the command

                while (pulseIn(lancPin, HIGH) < 5000) {   
                  //"pulseIn, HIGH" catches any 0V TO +5V TRANSITION and waits until the LANC line goes back to 0V 
                  //"pulseIn" also returns the pulse duration so we can check if the previous +5V duration was long enough (>5ms) to be the pause before a new 8 byte data packet
                  //Loop till pulse duration is >5ms
                }

   //LOW after long pause means the START bit of Byte 0 is here
   delayMicroseconds(bitDuration);  //wait START bit duration

   //Write the 8 bits of byte 0 
                        //Note that the command bits have to be put out in reverse order with the least significant, right-most bit (bit 0) first
                        for (int i=7; i>-1; i--) {
     digitalWrite(cmdPin, lancBit[i]);  //Write bits. 
     delayMicroseconds(bitDuration); 
                        }
   
                        //Byte 0 is written now put LANC line back to +5V
                        digitalWrite(cmdPin, LOW);
                        delayMicroseconds(10); //make sure to be in the stop bit before byte 1
                        
                        while (digitalRead(lancPin)) { 
                          //Loop as long as the LANC line is +5V during the stop bit
                        }

                        //0V after the previous stop bit means the START bit of Byte 1 is here
         delayMicroseconds(bitDuration);  //wait START bit duration
      
         //Write the 8 bits of Byte 1
                        //Note that the command bits have to be put out in reverse order with the least significant, right-most bit (bit 0) first
                        for (int i=15; i>7; i--) {
              digitalWrite(cmdPin,lancBit[i]);  //Write bits 
             delayMicroseconds(bitDuration);
                        }
 
                        //Byte 1 is written now put LANC line back to +5V
                        digitalWrite(cmdPin, LOW); 

   cmdRepeatCount++;  //increase repeat count by 1
   
   /*Control bytes 0 and 1 are written, now don’t care what happens in Bytes 2 to 7
   and just wait for the next start bit after a long pause to send the first two command bytes again.*/
  

 }//While cmdRepeatCount < 5
}

Thanks a bunch :slight_smile:

SOLVED!
Code in the last post is working exactly as I needed. Thanks all :slight_smile: