multipurpose rotarty encoder

I need help with a rotary encoder problem. Is it possible to get two separate counts from a single rotary encoder? In the code below I have three physical encoders setup and a forth "virtual" encoder set to count if a button is pushed. The problem is that the count for the physical and virtual encoder stays sequential. I need to make them count only if activated by the button.

I'm working on a camera RCP which may require as many as 12 rotary encoders to control various parts of the camera vision. The simplest solutions would probably be to run a couple of microcontrollers (teensy 3.1) and have them talk to each other. Before I head down that route I wanted to see if I can make 1 rotary encoder perform two functions.

/*
TESTING MULTI USE OF ENCODER FOR BOTH WB AND BB CONTROL. BUTTON WORKS
BUT ONLY WITH MOMENTARY ACTION. CONTROL WORKS BUT THERE IS NO SEPARATION
BETWEEN CONTROLS. (SETTINGS CONTINUE TO COUNT WHEN THEY SHOULDN'T)
4/5/15
 */

#include <Encoder.h>
//#define HWSERIAL Serial3      //used later to send commands to camera

const int buttonAWB = 7;
int bstateAWB = 0;

Encoder knobLeft(0, 1);
Encoder knobCentre(2, 3);
Encoder knobRight(4, 5);

//   avoid using pins with LEDs attached

void setup() {
  Serial.begin(9600);
  delay(2000);
  Serial.println("TwoKnobs Encoder Test:");
  knobLeft.write(513);
  knobCentre.write(500);
  knobRight.write(450);

  pinMode(buttonAWB, INPUT);            //testing push button for BB control of encoders
  digitalWrite(7, HIGH);
}

long positionLeft  = -999;
long positionCentre  = -999;
long positionRight = -999;
long positionBBRed = -999;      //test to try and separate control counts

void loop() {
  bstateAWB = digitalRead(buttonAWB);
  if (bstateAWB == HIGH)  {
    encoderOneChk();
    encoderTwoChk();
    encoderThreeChk();
  }
  else  {
    encoderFourChk();
  }

}
void encoderOneChk() {
  int newLeft = knobLeft.read();
  // delay(500);      //used to minimize the times a serial command is sent
  if (newLeft != positionLeft) {
    Serial.print("Left = ");
    Serial.println(newLeft);
    positionLeft = newLeft;
  }
}
void encoderTwoChk() {
  int newCentre = knobCentre.read();
  if (newCentre != positionCentre) {
    Serial.print("Centre = ");
    Serial.println(newCentre);
    positionCentre = newCentre;
  }
}
void encoderThreeChk() {
  int newRight = knobRight.read();
  if (newRight != positionRight) {
    Serial.print("Right = ");
    Serial.println(newRight);
    positionRight = newRight;
  }
}
void encoderFourChk() {      //test to try and separate control counts
  int bbRed = knobLeft.read();
  // delay(500);      //used to minimize the times a serial command is sent
  if (bbRed != positionBBRed) {
    Serial.print("bbRed = ");
    Serial.println(bbRed);
    positionBBRed = bbRed;
  }
}

EncoderFourChk function reads the left knob, why?

EncoderFourChk is the "virtual" encoder that I was trying to describe. In the sketch above knobLeft, knobCentre and knobRight are physical encoders.

To simplify what I am trying to describe I have altered the code below to only include 1 physical encoder. Using that encoder I need to store two independent values (functionOne and functionTwo). Rotating the encoder should only change the value of the active function. Hopefully this code will be a bit more clear.

/*
TESTING MULTI USE OF ENCODER FOR BOTH WB AND BB CONTROL. BUTTON WORKS
BUT ONLY WITH MOMENTARY ACTION. CONTROL WORKS BUT THERE IS NO SEPARATION
BETWEEN CONTROLS. (SETTINGS CONTINUE TO COUNT WHEN THEY SHOULDN'T)
4/5/15

STRIPPED DOWN TO ONE PHYSICAL ENCODER FOR EASIER ILLUSTRATION
8/5/15
 */

#include <Encoder.h>
//#define HWSERIAL Serial3      //used later to send commands to camera

const int buttonAWB = 7;
int bstateAWB = 0;

Encoder knobLeft(0, 1);

//   avoid using pins with LEDs attached

void setup() {
  Serial.begin(9600);
  delay(2000);
  Serial.println("One Encoder Two functions Test:");
 // knobLeft.write(513);        //eventually used to set a default value for functionOne

  pinMode(buttonAWB, INPUT);            //testing push button for functionTwo count from encoder
  digitalWrite(7, HIGH);
}

long positionOne  = -999;
long positionTwo = -999;      //test to try and separate function counts

void loop() {
  bstateAWB = digitalRead(buttonAWB);
  if (bstateAWB == HIGH)  {
    checkFunctionOne();      //this function is active when button is NOT pushed - should not effect count for functionTwo
  }
  else  {
    checkFunctionTwo();      //this function is active when button IS pushed - should not effect count for functionOne
  }

}
void checkFunctionOne() {
  int functionOne = knobLeft.read();
  if (functionOne != positionOne) {
    Serial.print("Function 1 = ");
    Serial.println(functionOne);
    positionOne = functionOne;
  }
}

void checkFunctionTwo() {      //test to try and separate control counts
  int functionTwo = knobLeft.read();
  if (functionTwo != positionTwo) {
    Serial.print("Function 2 = ");
    Serial.println(functionTwo);
    positionTwo = functionTwo;
  }
}

void checkFunctionOne() {
int functionOne = knobLeft.read();
if (functionOne != positionOne) {
Serial.print("Function 1 = ");
Serial.println(functionOne);
positionOne = functionOne;
}
}

void checkFunctionTwo() { //test to try and separate control counts
int functionTwo = knobLeft.read();
if (functionTwo != positionTwo) {
Serial.print("Function 2 = ");
Serial.println(functionTwo);
positionTwo = functionTwo;
}
}

So what is the difference between these two functions? I can't see why there should be any difference between the values positionTwo and positionOne.

One of us is missing something.

Don't rely on the encoder library keeping count for you. Keep your own variables to control the thing that you actually want to control. Then if the button is pushed, increments measured on one encoder (or all?) can be applied to your fourth variable.

Mike,
I'm still pretty new at programming so I'm most likely not explaining myself the correct manner. The individual function code was left out as it is beyond the scope of this part of the trial. To give an example of what two functions could be, lets say functionOne will control Red (val 0000-27FF) levels in video while functionTwo will control Blue (val 0000-00FF) levels - remembering that this project is for a camera RCP. I need one encoder to control both values independently. The problem with the code in Post 2 is that each function is counting sequentially after the other.

Current Results:
Function 1 = 12
Function 1 = 13
Function 1 = 14
Function 1 = 15
Function 1 = 16
Function 2 = 16
Function 2 = 17
Function 2 = 18
Function 2 = 19
Function 2 = 20
Function 1 = 20
Function 1 = 19
Function 1 = 20

The individual function code was left out as it is beyond the scope of this part of the trial.

Sorry I have no idea what you are talking about.

The results you get are the results I would expect from your code.

How about this. The above code does get me this

Function 1 = 12
Function 1 = 13
Function 1 = 14
Function 1 = 15
Function 1 = 16
Function 2 = 16
Function 2 = 17
Function 2 = 18
Function 2 = 19
Function 2 = 20
Function 1 = 20
Function 1 = 19
Function 1 = 20

What I would like it to get me is this (two independent counts from one encoder)

Function 1 = 12
Function 1 = 13
Function 1 = 14
Function 1 = 15
Function 1 = 16
Function 2 = 1
Function 2 = 2
Function 2 = 3
Function 2 = 4
Function 2 = 5
Function 1 = 17
Function 1 = 18
Function 1 = 19

You're using the serial monitor already... Usually when I'm trying to solve unexpected behaviour I print out all the variables that are both likely relevant to the issue, in scope and at the points I'm suspicious of.

If that doesn't make it clear, sometimes you have to widen the search - at this point it's often a sign you're about to learn something 'weird' (that will make more sense in the future).

:wink:

What I would like it to get me is this (two independent counts from one encoder)

You can't get two independent counts from one encoder.

That library uses interrupts to count pulses from one encoder. When ever you use the knobLeft.read() it returns the value that is automatically updated in the background by the interrupt routines in the library. There is no way to tell it to use two counts. Those two functions are BOTH calling the knobLeft.read() function so they will not return different values but the value accumulated under the knobLeft method.

So what you are trying to do is not possible, that is why you are failing to do it.

davros:
I'm working on a camera RCP which may require as many as 12 rotary encoders to control various parts of the camera vision. The simplest solutions would probably be to run a couple of microcontrollers (teensy 3.1) and have them talk to each other. Before I head down that route I wanted to see if I can make 1 rotary encoder perform two functions.

A couple of weeks ago I posted some code for Atmega based boards, that can easily handle 6 rotary encoders at the same time. If you want to have a look on the code, I might search for it.

If your application doesn't need reacting on low-latency interrupt actions, you surely can increase the number of rotary encoders using the exactly same code with 12 encoders. But if you need to have low-latency interrupt actions in your program like controlling servo positions while reading encoders, some speed optimizing for replacement of "digitalRead()" with some AVR low-level code would be recommended.

BTW: You can use just ONE ENCODER counting an theoretically unlimited number of variables when using an encoder with a pushbutton, i.e. one encoder for counting 10 variables:

  • default: counting variable 1
  • button once pressed: counting variable 2
  • button once more pressed: counting variable 3
  • button once more pressed: counting variable 4
  • button once more pressed: counting variable 5
  • button once more pressed: counting variable 6
  • button once more pressed: counting variable 7
  • button once more pressed: counting variable 8
  • button once more pressed: counting variable 9
  • button once more pressed: back to 'default'

Would be nice to have a display though, not only displaying the count but also the variable which is currently active for counting.

You can't get two independent counts from one encoder.

That is what I was guessing, but wanted to double check with those who know more than me. Thanks Mike

A couple of weeks ago I posted some code for Atmega based boards, that can easily handle 6 rotary encoders at the same time. If you want to have a look on the code, I might search for it.

jurs, I'd love to have a look at that code if you have it handy. The full code for my project, at the moment, has 3 encoders that send values to both and LCD screen and a RS232 chip for the camera. I've got enough pins on the teensy3.1 to add all of the encoders I need, but then I'd be short on resources for future expansion. That is why I was looking to get 1 encoder to do 2 things.

You can't get two independent counts from one encoder

while that's true . its what you do with that count that matters ,it can be used to independently adjust any number of variables . you just have to control which var has "focus" when the re counter changes value

davros:
jurs, I'd love to have a look at that code if you have it handy.

Here is the code for reading 4 rotary encoders with ATMEGA based boards like UNO and MEGA2560:

// rotary encoder demo by 'jurs' for Arduino Forum
struct rotary_t {byte pinA; byte pinB; int count;};

rotary_t encoder[]={ // define 2 pins for each rotary encoder
  {2,3},  // encoder[0].pinA, encoder[0].pinB
  {4,5},  // encoder[1].pinA, encoder[1].pinB
  {6,7},  // encoder[2].pinA, encoder[2].pinB
  {8,9},  // encoder[3].pinA, encoder[3].pinB
};
#define NUMENCODERS (sizeof(encoder)/sizeof(encoder[0]))

volatile byte state_ISR[NUMENCODERS];
volatile int8_t count_ISR[NUMENCODERS];


void beginEncoders()
{ // active internal pullup resistors on each encoder pin and start timer2
  for (int i=0; i<NUMENCODERS; i++)
  {
    pinMode(encoder[i].pinA, INPUT_PULLUP);
    pinMode(encoder[i].pinB, INPUT_PULLUP);
    readEncoder(i); // Initialize start condition
  }
  startTimer2();
}

boolean updateEncoders()
{ // read all the 'volatile' ISR variables and copy them into normal variables
  boolean changeState=false;
  for (int i=0; i<NUMENCODERS; i++)
  {
    if (count_ISR[i]!=0)
    {
      changeState=true;
      noInterrupts();
      encoder[i].count+= count_ISR[i];
      count_ISR[i]=0;
      interrupts();
    }
  }
  return changeState;
}

void printEncoders()
{ // print current count of each encoder to Serial
  for (int i=0; i<NUMENCODERS; i++)
  {
    Serial.print(encoder[i].count);
    Serial.print('\t');
  }
  Serial.println();
}

int8_t readEncoder(byte i)
{ // this function is called within timer interrupt to read one encoder!
  int8_t result=0;
  byte state=state_ISR[i];
  state= state<<2 | (byte)digitalRead(encoder[i].pinA)<<1 | (byte)digitalRead(encoder[i].pinB); 
  state= state & 0xF;   // keep only the lower 4 bits
  /* // next two lines would be code to read 'quarter steps'
  if (state==0b0001 || state==0b0111 || state==0b1110 || state==0b1000) result= -1;
  else if (state==0b0010 || state==0b1011 || state==0b1101 || state==0b0100) result= 1;
  */
  // next two lines is code to read 'full steps'
  if (state==0b0001) result= -1;
  else if (state==0b0010) result= 1;
  state_ISR[i]= state;
  return result;
}


void startTimer2()  // start TIMER2 interrupts
{
  noInterrupts();
  // Timer 2 CTC mode
  TCCR2B = (1<<WGM22) | (1<<CS22)  | (1<<CS20);
  TCCR2A = (1<<WGM21);
  OCR2A = 124;   // 249==500,  124==1000 interrupts per second
                 // 63 ==2000,  31==4000
                 // 15 ==8000,   7==16000
  TIMSK2 = (1<<OCIE2A); // enable Timer 2 interrupts
  interrupts();
}

void stopTimer2() // stop TIMER2 interrupts
{
  noInterrupts();
  TIMSK2 = 0;
  interrupts();
}


ISR(TIMER2_COMPA_vect)  // handling of TIMER2 interrupts
{
  for (int i=0; i<NUMENCODERS; i++) 
  {
    count_ISR[i]+= readEncoder(i); 
  }
}


#define BAUDRATE 115200L // serial baud rate

void setup() {
  Serial.begin(BAUDRATE);
  Serial.println();
  Serial.println("Good night and good luck!"); // print some Test-Message at beginning
  beginEncoders();
}

void loop() {
  if (updateEncoders()) printEncoders();
}

If you need more encoders, just add them to the "encoder[]" array as you need.

davros:
I've got enough pins on the teensy3.1 to add all of the encoders I need, but then I'd be short on resources for future expansion.

"Teensy 3.1", that's not an Arduino, isn't it?
With boards that are not based on 8-Bit Atmega controllers, you would have to change the timer interrupt code and set up timer interrupts in your system. Timer programming is controller dependent code.

The code works like that: A timer interrupt fires 1000 timer interrupts per second. Within the timer interrupts, all encoders are sampled in a for-loop. Within your loop function you just call the "updateEncoders()" function, which then copies the volatile counts (which are set within the timer interrupts) to a normal count variable that you can use in your application code.

The code would need changes for different (non-Atmega) controller platforms.
The code would also need changes if every physical encoder has to act as a virtual encoder for different count variables, activated by pressing pushbuttons.

racpi:
while that's true . its what you do with that count that matters ,it can be used to independently adjust any number of variables . you just have to control which var has "focus" when the re counter changes value

Yes, the conditional in the code acts like the first derivative - ie looking for change.

Although, it works like ABS in its current form.

Anyway, point is, I agree it can be done.

Anyway, point is, I agree it can be done.

you just have to control which var has "focus" when the re counter changes value

Just sticking to using this library and without writing your own code you can not do what you suggest. Or maybe YOU can if you want to put your money where your collective mouths are?

The OP already has started down that path.

I don't have have an arduino or encoder on me to test this, but my psuedo code would have a similar backbone.

I'll have a proper look in, hrrrm - 37hrs...

I bet if you had the time to spare before then you could get something going along these lines yourself :slight_smile:

try this
edited to remove useless comments

/*
TESTING MULTI USE OF ENCODER 
STRIPPED DOWN TO ONE PHYSICAL ENCODER FOR EASIER ILLUSTRATION

 */
#include <Encoder.h>

const int buttonAWB = 7;
short bstateAWB = 1;
boolean focus = 1;
int count, old_count;
Encoder knobLeft(2, 3);

void setup() {
  Serial.begin(9600);
  delay(2000);
  Serial.println("One Encoder Two functions Test:");
 
  pinMode(buttonAWB, INPUT);            //testing push button to change focus
  digitalWrite(7, HIGH);
  old_count = count = knobLeft.read();
}
long positionOne  = -999;
long positionTwo = -999;      //test to try and separate function counts
void loop() {
  bstateAWB = digitalRead(buttonAWB);
  delay(20);
  if (bstateAWB == digitalRead(buttonAWB)) {
    if (bstateAWB == 0) {
      focus = !focus;
      old_count = knobLeft.read();
      Serial.print("Focus now = ");
      if (focus)
        Serial.println("one");
      else
        Serial.println("two");
    }
    while (digitalRead(buttonAWB) == 0);
  }
  count = knobLeft.read();
  if (old_count != count)
    checkFunction(focus);    
}
void checkFunction(boolean funx) {

  if (funx) {
    if (count > old_count)
      positionOne++;
    else
      positionOne--;
    Serial.print("Function 1 = ");
    Serial.println(positionOne);
  }
  else {
    if (count > old_count)
      positionTwo++;
    else
      positionTwo--;
    Serial.print("Function 2 = ");
    Serial.println(positionTwo);
  }
  old_count = count;
}

Grumpy_Mike:
Just sticking to using this library and without writing your own code you can not do what you suggest.

Read post #12 again to get the context of our quotes - post #4 might be due some attention also ;).

I haven't looked at #17's code fully - but at a glance it appears to express in concept what we (OP incl. once you read past the first sentence of post #1) are taking about.

the results

One Encoder Two functions Test:
Function 1 = -1000
Function 1 = -1001
Function 1 = -1002
Function 1 = -1003
Focus now = two
Function 2 = -1000
Function 2 = -1001
Function 2 = -1002
Function 2 = -1003
Function 2 = -1004
Function 2 = -1005
Function 2 = -1006
Function 2 = -1007
Focus now = one
Function 1 = -1002
Function 1 = -1001
Function 1 = -1000
Function 1 = -999
Function 1 = -998
Function 1 = -997
Function 1 = -996
Function 1 = -995
Focus now = two
Function 2 = -1006
Function 2 = -1005
Function 2 = -1004
Function 2 = -1003
Focus now = one
Function 1 = -996
Function 1 = -997
Function 1 = -998
Function 1 = -999
Function 1 = -1000
Function 1 = -1001
Function 1 = -1002
Function 1 = -1003
Focus now = two
Function 2 = -1004
Function 2 = -1005
Function 2 = -1006
Function 2 = -1007
Function 2 = -1008
Function 2 = -1009
Function 2 = -1010