Reading a Quad encoder and converting to a function

Hi all,

I have a project that I want incorporate a rotary encoder into for the purpose of making some selections for midi presets. I’m sort of sick of hunting around and trying libraries all the time so I decided to try my hand at writing my own code for reading a quadrature encoder (mechanical, not opto), this one to be specific: LINK

I have sketch that works OK and is nice and simple for what I want to do:

//Manually read quadrature encoder

int ENCA = 2;
int ENCB = 3;

byte  bits;
byte  oldBits;
int value = 0;

void setup() {

  Serial.begin(9600);

  pinMode (ENCA, INPUT_PULLUP);
  pinMode (ENCB, INPUT_PULLUP);

}

void loop() {

  bits = digitalRead(ENCA) << 1 | digitalRead(ENCB); 

  if (bits == B00 && oldBits == B10)
  {
    value++;
    publish();
    while (bits != B11)
    {
      bits = digitalRead(ENCA) << 1 | digitalRead(ENCB);
    }

  }
  else if (bits == B00 && oldBits == B01)
  {
    value--;
    publish();
    while (bits != B11)
    {
      bits = digitalRead(ENCA) << 1 | digitalRead(ENCB);
    }
  }

  oldBits = bits;

} // END LOOP


void publish(){

  Serial.print(value);
  Serial.print("\t");
  Serial.print(bits,BIN);

  Serial.print("\t");
  Serial.println(oldBits,BIN);
}

It works pretty well. I’m reading the 2 inputs and converting them to a nibble and doing some if logic to determine if the shaft is rotating and in which direction. The while statements force the loop to wait for a total 180 degree encoder cycle to finish before proceeding - this acts to debounce the switches and prevent some duplicate values I was getting. I can give details on the encoder behavior if need be. The problem is that I want to call up this functionality in various places and for different variables, so I want to convert this into a self contained function. That is isn’t going so well.

//Manually read quadrature encoder

int ENCA = 2;
int ENCB = 3;
int sum;
int starterVal = 3;

void setup() {

  Serial.begin(9600);

  pinMode (ENCA, INPUT_PULLUP);
  pinMode (ENCB, INPUT_PULLUP);

}

void loop() {

  sum = encoder(starterVal);
  Serial.println(sum);

} 
int encoder(int value){

  byte  bits;
  byte  oldBits;
  int result;

  bits = digitalRead(ENCA) << 1 | digitalRead(ENCB); 

  if (bits == B00 && oldBits == B10)
  {
    value++;
    while (bits != B11)
    {
      bits = digitalRead(ENCA) << 1 | digitalRead(ENCB);
    }

  }
  else if (bits == B00 && oldBits == B01)
  {
    value--;
    while (bits != B11)
    {
      bits = digitalRead(ENCA) << 1 | digitalRead(ENCB);
    }
  }

  oldBits = bits;

  return result;
}

I’ve been trouble shooting all day but I’m getting everything from no return at all, all zeros, to alternating 257 and 514 values! There much be some obvious errors at this point. Can someone help nudge me in the right direction?

Brian

When handling an encoder it is crucial that the inputs are read at least once for every change. This can be done in two ways:

  • Polling the inputs in a tight loop without any blocking code
  • Using interrupts

I normally use this technique for reading an encoder:

const char encTable[16] ={0, 1, -1, -0, -1, 0, -0, 1, 1, -0, 0, -1, -0, -1, 1, 0};//gives -1, 0 or 1 depending on encoder movement
byte encState;//remembering the encoder output and acting as index for encTable[]
byte inp;
long actPos;//encoder position

void loop(){
    //aquire input values
    inp = digitalRead(pinA)|(digitalRead(pinB)<<1);
    encState = ((encState<<2)|((inp)&3))&15;//use encoder bits and last state to form index
    actPos += encTable[encState];//update actual position on encoder movement
}//loop()

The same technique can be used in an interrupt but remember to declare the variable in the interrupt routine as volatile. Also, since the pin numbers are determined you can read the input pins (pin 2&3 equal PIND 2 & 3) directly instead of using digitalRead() which is much faster.

const char encTable[16] ={0, 1, -1, -0, -1, 0, -0, 1, 1, -0, 0, -1, -0, -1, 1, 0};//gives -1, 0 or 1 depending on encoder movement
byte encState;//remembering the encoder output and acting as index for encTable[]
byte inp;
volatile long actPos;//encoder position


void setup(){
    Serial.begin(115200);
    attachInterrupt(0, updateEnc, CHANGE);//monitor pin 2
    attachInterrupt(1, updateEnc, CHANGE);//monitor pin 3
}//setup()

void loop(){
  Serial.println(actPos);//report positioin twice a second
  delay(500);
}//loop()

void updateEnc(){
  encState = ((encState<<2)|((PIND>>2)&3))&15;//use encoder bits and last state to form index
  actPos += encTable[encState];//update actual position on encoder movement
}//updateEnc()

Thanks nilton61,

I tried both of you code examples and they both seemed to work the same for me. I guess with the speed that I can turn the knob by hand interrupts aren't needed for the reads to keep up. Like some other examples I saw this seems to count by 4's so so some for will be needed to make it properly increment another variable. When you run this do you find that after a few turns the actPos variable never lands on zero again?

Did you try mine? Any suggestions for getting any of these into a working function?

Brian

The interrupt is not mainly for speed but convenience, you just read actPos whenever you need. I have tested it with a 1024 p/r optical encoder (4096 counts/r) that spins quite fast, it never missing a count. And i do not have the problems you describe with counting in units of 4 and not going back to zero. This could indicate that you have contact bounces in your encoder but actually it should not. If a contact bounces the count should just alternate between two adjacent values.

I do not really understand your requirement for a function though. What do you want the function to do? Report the position of the encoder? This implies that you either only turn the encoder while the function is called (which is neither realistic nor practical) or you have some processing in the background that keeps track of the encoder movements i.e interrupts. In the case of interrupts you do not a function, reading the position from a variable is fully sufficient.

If contact bounce is the culprit it can be remedied by using a timer interrupt and using a debouncing technique similar to the one i use for reading the encoder. But i must brush up my timer interrupt skills before doing that.

Brian,

There are different ways to count from an encoder so that you register 1,2, or 4 of the possible switch positions. You have chosen to count only one of the four positions 0,0 and determine directions from the previous state. Nilton has given you code which counts all the changes between states, and there are other codes which will increment or decrement based on two of the four changes.

Typically on these mechanical encoders, you will want to increment/decrement the count only in a detent position. I’m not familiar with he encoder you reference, but if there is a detent at each 0,0 position, and no others than you are probably optimized for that encoder. If you can click two or four times between the count change, then you may want to go to one of the other reading routines.

The easisest way to functionalize your code is to leave the shared variables global, and create the function with your existing code. Your code reads the bits every time at the start of the loop and asssigns the value to oldBits at the end. The additional reading while bits != B11 does not seem necessary.

//Manually read quadrature encoder

int ENCA = 2;
int ENCB = 3;

byte  bits;
byte  oldBits;
int value = 0;

void setup() {

  Serial.begin(9600);

  pinMode (ENCA, INPUT_PULLUP);
  pinMode (ENCB, INPUT_PULLUP);

}

void loop() {
  readEncoder();
}

void publish(){

  Serial.print(value);
  Serial.print("\t");
  Serial.print(bits,BIN);

  Serial.print("\t");
  Serial.println(oldBits,BIN);
}

void readEncoder(){
  bits = digitalRead(ENCA) << 1 | digitalRead(ENCB); 

  if (bits == B00 && oldBits == B10)
  {
    value++;
    publish();
  }
  else if (bits == B00 && oldBits == B01)
  {
    value--;
    publish();
  }

  oldBits = bits;
}

Hi cattledog,

I see. Yes, between detents there are four states of the 2 switches and it resolves to 11 at each detent. I was getting weird duplicate values and implementing the while statements (like: 1 2 3 4 5 4 5 6 7). I tried delays to wait until contact bounce ended but it wasn't reliable. Forcing the code to wait to until the 11 condition was met solved this and didn't slow down the code. Is there a better way to deal with switch chatter?

nilton61,

The reason I want to reduce this to a function is because my project has several layers or modes in which I want the encoder to increment/decrement different variable depending on where it's called. Certainly there are lot's of ways to accomplish this but my thinking was that I'd enter a mode; call the encoder function and have it carry in a variable associated with that mode; and have it return that same variable at each loop (sometimes incremented, sometimes not). There are a few different variables that might be manipulated by the encoder. At the macro level the loop monitors a bunch of foot switches and outputs midi when they're pressed. In one mode the midi is program changes, in another is controller changes, and I want the encode to change the content of the midi changes depending on which mode I'm in and which switch was most recently pressed. Maybe I'm overcomplicating things?

Brian

Brian,

I have found it convenient to use the Bounce2 library with mechanical switches.

https://github.com/thomasfredericks/Bounce-Arduino-Wiring/tree/master/Bounce2

//Manually read quadrature encoder

int ENCA = 2;
int ENCB = 3;

byte  bits;
byte  oldBits;
int value = 0;

#include <Bounce2.h>
// Instantiate two Bounce objects for the pins with digitalRead
Bounce debouncerA = Bounce(); 
Bounce debouncerB = Bounce(); 

void setup() {

  Serial.begin(9600);

  pinMode (ENCA, INPUT_PULLUP);
  pinMode (ENCB, INPUT_PULLUP);
  
  debouncerA.attach(ENCA);
  debouncerA.interval(20);//20 ms debounce interval, make longer or shorter as needed
  debouncerB.attach(ENCB);
  debouncerB.interval(20);

}

void loop() {
  readEncoder();
}

void publish(){

  Serial.print(value);
  Serial.print("\t");
  Serial.print(bits,BIN);

  Serial.print("\t");
  Serial.println(oldBits,BIN);
}

void readEncoder(){
  
  debouncerA.update();
  debouncerB.update();
  
  bits = debouncerA.read() << 1 | debouncerB.read(); 

  if (bits == B00 && oldBits == B10)
  {
    value++;
    publish();
  }
  else if (bits == B00 && oldBits == B01)
  {
    value--;
    publish();
  }

  oldBits = bits;
}

The way you have written the code, it looks like 0,0 is the detent position.

The way you have written the code, it looks like 0,0 is the detent position.

Nope, 00 is one of the combinations "mid-detent". It's just one arbitrarily chosen to be the point in time to look back at a previous set of values.

I got my code working as a function, 2 things were working against me. I had the declare the bits and oldbits variables as static (not sure why yet) and I wasn't writing in any point in the loop to have the starting value updated by the function…. so every loop was re-setting to a starting value after incrementing or decrementing. I love this stuff.

But after all that I plugged the function into my main program and the loop time is too long to monitor the encoder so I took some lessons from nilton61's examples and did my first attach interrupt. I guess ISRs can't return values so I reworked it to use only global variables - meaning that if I want to use different variables in different modes I'll need to add in some if or switch case code. But with the interrupts attached the encoder works brilliantly.

Thanks!

Brian

If you want different layers of encoder positions you could have the actPos as a array with a global index. Change the index and you change the layer. Or do you want some kind of HMI system where the user can enter different values and you select which value to read by your function?

Well, I haven't done much with arrays yet so I'd have no idea where to start with that suggestion. To keep it simple, what I have is 2 modes (right now), preset and scene. The preset mode controls program changes and scene controls a specific CC message. When I'm in preset mode I want the encoder to increment/decrement program number and publish a message to change the program through mid; in scene mode I want it to do the same with the specific CC message.

However, after a lot of thought the encoder is really only useful to control program changes (There are 768 programs, a lot to get though by pushing a button with your foot) and not really worth using with other parameters (It needs to be worth bending down to reach the encoder, after all) so I'm satisfied simply with using the encoder for one function.

768 Presets and scenes as well.. sounds like a fractal axefx2xl

int encoder(int value){

  byte  bits;
  byte  oldBits;
  int result;

  bits = digitalRead(ENCA) << 1 | digitalRead(ENCB); 

  if (bits == B00 && oldBits == B10)
  {
    value++;
    while (bits != B11)
    {
      bits = digitalRead(ENCA) << 1 | digitalRead(ENCB);
    }

  }
  else if (bits == B00 && oldBits == B01)
  {
    value--;
    while (bits != B11)
    {
      bits = digitalRead(ENCA) << 1 | digitalRead(ENCB);
    }
  }

  oldBits = bits;

  return result;
}

There’s a problem in this bit of code. You’re set up right to return the result variable, but nowhere in there do you set it equal to anything. So it’s just holding whatever random garbage was in that memory location when the function got called and it got created. You should probably set result equal to value at the very beginning of the function and use result all the rest of the way through and never touch value again.

I found one of those low cost mechanical encoders. It was very ill-behaved and took some effort to get working.
The code works this way: A switch is connected to pin4. When the encoder is turned with the switch high different menu items are selected. When the encoder is turned with the switch low the value of the selected menu item is changed. The TimerOne library used can be downloaded here

#include <TimerOne.h>
const int menuItems = 8;
volatile int menuVal[menuItems];
volatile int menuSel;
volatile byte pin2State;
volatile byte selChange;
volatile byte valChange;

void setup(){
  Serial.begin(115200);
  Timer1.initialize(500);
  Timer1.attachInterrupt(detentEnc);
}//setup()

void loop(){
  if(selChange){
    Serial.print("Menu level:");
    Serial.println(menuSel);
    selChange = 0;
  }//if(selChange)
  if(valChange){
    Serial.print("Menu val:");
    Serial.println(menuVal[menuSel]);
    valChange = 0;
  }//if(valChange)
}//loop()

void detentEnc(){
  pin2State = ((pin2State<<1)|bitRead(PIND, 2))&3;
  if(pin2State == 1){//rising edge on pin2
    if(bitRead(PIND, 4)){//switch is high, change selection
      selChange = 1;//signal selection change
      menuSel = (menuSel+bitRead(PIND, 3)*2-1)%menuItems;
    }else{//switch is low, change value
      valChange = 1;//signal value change
      menuVal[menuSel] += bitRead(PIND, 3)*2-1;
    }//if switch
  }//if(pin2state)
}//detentEnc()

768 Presets and scenes as well.. sounds like a fractal axefx2xl

That is correct!

768 Presets and scenes as well.. sounds like a fractal axefx2xl

New in the XL model is an optical encoder in the Value knob

I would recommend that you too upgrade to an optical rotary encoder as well. You won't be fighting bounce, and interrupt driven code will be simplified. You will need to decide on resolution and detents, and with 768 presets, you know best what the interface needs to be.

You can get get full quadrature resolution with detents at every change with something like the Bourns EM 14 with 32 detents per revolution.

It's even listed in their Pro Audio line 8)