Rotary encoder reading

Hi all,

First of all,

I tried searching on the internet to find an answer for my problem but due to lack of experience i cannot find something understanding for me…

in a midi usb project to make a midi controller i want to use some rotary encoders.

I use the Encoder.h lib from the pjrc website. this is a very easy lib to use and so i was able to get some readings on the serial monitor.

I have two questions:

  1. I want to limit the values between 0 and 127 i did this with the following code:
#include <Encoder.h>

// Change these two numbers to the pins connected to your encoder.
//   Best Performance: both pins have interrupt capability
//   Good Performance: only the first pin has interrupt capability
//   Low Performance:  neither pin has interrupt capability
Encoder myEnc(38, 40);
//   avoid using pins with LEDs attached

void setup() {
  Serial.begin(9600);
  Serial.println("Basic Encoder Test:");
}

long oldPosition  = -999;

void loop() {
  long newPosition = myEnc.read();

    if(newPosition > 127){
      newPosition = 127;
    }
    
    if(newPosition < 0) {
       newPosition = 0;
    }
        
  if (newPosition != oldPosition) {
    
    oldPosition = newPosition;
    Serial.println(newPosition);
    
  }


}

So my upper and lower value are limited. but for example I go “10 clicks” below 0 I also have to turn first “10 clicks” before the counting starts again. I would like to eliminate that.

This encoder gives 4 readings per “click” (I hope I say this correct). So for each “click” i get 4 readins in the searial monitor. how can i get the program to send only a reading when the encoder makes a “click”

Thanks in advance for all the help and excuse me for my bad grammar.

regards

Jazzy

Encoder.h does not know anything about what you do with the variable “new position” in your code.

The library command to reset the encoder to some value is ::write(value). In your case with Encoder myEnc

 if(newPosition > 127){
      newPosition = 127;
      myEnc.write(127);
    }
    
    if(newPosition < 0) {
       newPosition = 0;
       myEnc.write(0);
    }

how can i get the program to send only a reading when the encoder makes a “click”

divide by 4

cattledog:
divide by 4

Or use a counter and ignore the first three.

Here's my code for encoders:
I usually use a capacitor for the debounce but I added a timer to save you the trouble since it is only used for user input.

#define SwitchPin 2 // interrupt 0
#define ClockPin  3 // interrupt 1
#define DataPin   4 // this can be any pin

volatile uint8_t EncodeCTR;
volatile uint8_t EncoderChange;
volatile uint8_t SwitchCtr;

void setup() {
  Serial.begin(115200);
  pinMode(SwitchPin , INPUT_PULLUP); // switch is not powered by the + on the Encoder breakout
  pinMode(ClockPin , INPUT);
  pinMode(DataPin , INPUT);
  attachInterrupt(digitalPinToInterrupt(SwitchPin), Switch, FALLING);
  attachInterrupt(digitalPinToInterrupt(ClockPin), Encode, FALLING);
}

void loop() {
  if (EncoderChange || SwitchCtr) {
    EncoderChange = 0;
    Serial.print("EncodeCTR: ");
    Serial.print(EncodeCTR);
    Serial.println();
    Serial.print("Switch Pressed ");
    Serial.println(SwitchCtr);
    SwitchCtr = 0;
  }
  // This is non blocking so other code can go here
}

void Switch() {
  static unsigned long DebounceTimer;
  if ((unsigned long)(millis() - DebounceTimer) >= (100)) {
    DebounceTimer = millis();
    if (!SwitchCtr) {
      SwitchCtr++;
    }
  }
}
void Encode() { // we know the clock pin is low so we only need to see what state the Data pin is and count accordingly
  static unsigned long DebounceTimer;
  if ((unsigned long)(millis() - DebounceTimer) >= (100)) { // standard blink without delay timer
    DebounceTimer = millis();
    if (digitalRead(DataPin) == HIGH) // switch to LOW to reverse direction of Encoder counting
    {
      EncodeCTR++;
    }
    else {
      EncodeCTR--;
    }
    EncoderChange++;
  }
}

Z

I was about to complain about how all these encoder reading codes were way too complicated and post mine but I see @zhomeslice has at some point in his life realized the same simple thing about gray code. The only thing I do differently is that I almost always debounce an encoder with hardware and not software.

The cool thing about it is that if you are only interested in one tick per click, instead of dividing by 4 you can just use one transition on one interrupt. Like what @zhomeslice did there. If you want 2 ticks per click, you use CHANGE and catch both transitions of one pin with one interrupt. And if you want 4 ticks per click you use both transitions on both pins with 2 interrupts.

PS. The trick that I was talking about is this. When you read an encoder look for the edges of the square waves and when you see them on one pin, read the state of the other pin. On one pins transitions if they're the same state then you're going one way and if they are different you are going the other way. On the other pins edges it will be the same rule but reversed directions. If you take advantage of that and a cap to debounce the encoder instead of code you can get the ISR down to just a couple of quick lines to read a pin, do a compare, and increment or decrement a counter.

Read Delta-G's PS and then look at this image:

Image found at:

Thanks @ej. I was just coming back to add a picture. Went to find it after I posted and got distracted.

For one tick per click, this is my ISR. Note that the encoder is hardware debounced.

void ISR_encoder_handler() {
	//  My encoder has a full cycle of 4 transitions per click.
	//  I only want one increment or decrement per click
	//  So we are only looking at one transition
	//  We're only using the falling edge on the interrupt pin.
	//  So we know ENCODER_INTERRUPT_PIN is LOW.
	//  If ENCODER_B_PIN is HIGH then they're different so decrement
	//  if(digitalRead(ENCODER_B_PIN) == HIGH) {
	if (*bReg & bMask) {
		encoderCounter--;
	} else {
		encoderCounter++;
	}
}

The bReg and bMask are just direct port manipulation. I just have the port name and bitmask in variables. In this case the interrupt is on pin 2 and the other pin is on pin 7 so bReg points to PIND and bMask is 0x80. The last line of the comment spells that line out in Arduino language. I just wrote it this way to be fast.

Delta_G:
Thanks @ej. I was just coming back to add a picture. Went to find it after I posted and got distracted.

No problem...your great "PS" description jogged my memory that I had seen your words in an image. Fortunately, I have a directory named Interrupts and I always keep a Resource.txt file with stuff that pertains to the directory name. Your words and the first image I found in my text file were a perfect match!

I was about to complain about how all these encoder reading codes were way too complicated and post mine but I see @zhomeslice has at some point in his life realized the same simple thing about gray code. The only thing I do differently is that I almost always debounce an encoder with hardware and not software.

I always use capacitors with my mechanical encoders and switches. It’s much easier to manage and resolves tons of headaches counting backwards missing counts and more. its amazing what 10uf does to a bouncy switch! :slight_smile:

For those who want to have multiple encoders that have detailed counts I have created the following code which works with interrupts on any pin including all analog pins!

This code works like attachInterrupts() it uses a callback functions to handle the interrupts simplifying the final code.

Please note: this is demonstration code! using Serial.print() in interrupts is unwise. The:
sei(); // re enable other interrupts at this point allows other interrupts to occur such as those needed for Serial.print() otherwise serial print would not work!

*Placing too much code within the interrupted code can cause locking of the arduino so keep the code in the callback functions to a minimum.

#include "Interrupts.h"
InterruptsClass Interrupt;
volatile long EncoderCounter = -2;

void InterruptHandler(uint32_t Time, uint32_t PinsChanged, uint32_t Pins) {
  sei(); // re enable other interrupts at this point,
  Interrupt.PinCallBack(Time, PinsChanged, Pins); // This detects which pin was triggered and calls the proper callback function
  // Every interrupt passes this point
}

void Switch(uint32_t Time, uint32_t PinsChanged, uint32_t Pins) {
  Serial.print("Switch \t");
  Serial.println(Interrupt.Switch(11, Time, 1000, false));

  // Simple basic testing of Pin state at time of interrupt. Checkpin()
  if (Interrupt.CheckPin(11)) { // rising
    Serial.println(" Pin 11 Rising \t");
  } else { // Falling
    Serial.println(" Pin 11 Falling \t");
  }
}

void EncoderCountPlus(uint32_t Time, uint32_t PinsChanged, uint32_t Pins) {
  EncoderCounter += Interrupt.Encoder(12, 13, Pins, true);
  Serial.print("Count ");
  Serial.print(EncoderCounter);
  Serial.print("\t Encoder \t");
  Serial.println(Interrupt.Encoder(12, 13, Pins, true));  // recalculates for display only
}

void EncoderCountMinus(uint32_t Time, uint32_t PinsChanged, uint32_t Pins) {
  EncoderCounter -= Interrupt.Encoder(12, 13, Pins, true);
  Serial.print("Count ");
  Serial.print(EncoderCounter);
  Serial.print("\t Encoder \t");
  Serial.println(-1 * Interrupt.Encoder(12, 13, Pins, true));  // recalculates for display only
}

void setup() {
  Serial.begin(115200); //115200

  Interrupt.onInterrupt(InterruptHandler); // Handles all interrupts
  Interrupt.onPin(11, INPUT_PULLUP, Switch); // Triggered when pin 11 changes
  Interrupt.onPin(12, INPUT, EncoderCountPlus); // Triggered when pin 12 changes
  Interrupt.onPin(13, INPUT, EncoderCountMinus); // Triggered when pin 13 changes

}


void loop() {
}

In addition to switches and encoders, this code currently can handle ultrasonic Ping sensors, RC remote and cycle duration.
I have plans on adding tachometer code.

Interrupts.cpp (5.93 KB)

Interrupts.h (5.3 KB)

There is a potential issue with encoder reading routines which determine only one of the four quadrature changes. That is those algorithms with an interrupt on only one pin and only one edge (Rising or Falling).

There needs to be absolutely no switch bounce, or rock solid debouncing, and no mechanical instability around the edge position. Routines which read both edges, will always settle at the correct value, because the count is incremented and decremented as an edge rises and falls and the other channel is constant. With a one pin, rising edge routine, every rising change will affect the counter, while the falling edge change due to bounce or mechanical jitter is disregarded.

cattledog:
There is a potential issue with encoder reading routines which determine only one of the four quadrature changes. That is those algorithms with an interrupt on only one pin and only one edge (Rising or Falling).

There needs to be absolutely no switch bounce, or rock solid debouncing, and no mechanical instability around the edge position. Routines which read both edges, will always settle at the correct value, because the count is incremented and decremented as an edge rises and falls and the other channel is constant. With a one pin, rising edge routine, every rising change will affect the counter, while the falling edge change due to bounce or mechanical jitter is disregarded.

That's why we keep talking about putting the cap on there to debounce it with hardware.

zhomeslice:
Here's my code for encoders:
I usually use a capacitor for the debounce but I added a timer to save you the trouble since it is only used for user input.

First of all thank you all for your help! some things i get some not :slight_smile:

i tried the code from zhomeslic.

I understand this one the most and isn't to complex I'm thinking make a fucntion for this one so i can easy add more encoders.

It works but not fully 100%. it gives some false readings if i turn the encoder i get readings such as

10
11
12
12
12
13
14
13
14
15
...

I tried to extend the debounce time but that gave no difference.

i added on both data and clock pin a 1uF capacitor but no difference...

somone any suggestions?

regards

Jazzy

I solved my problem,

here for those who are interested… :wink:

When I used the internal pull up resistors I got false readings… so I added 2 external pullup resistors
and all problems were gone.

I also made an RC deboucing filter with R22k and C100nF.

A usefull tip is not to use a breadboard but solder all components together.

Here is my example code which I used for a USB midi controller.

when the encoder turns clockwise a 1 is sent when I turn counter clockwise an 127 is sent according the midi protocol.

#define ClockPin0  42 
#define DataPin0   44

#include "MIDIUSB.h"

int encoder_channel = 2;

int encoder_cc[] = {1,2,3,4};

int encode_ctr0 = 0;
volatile uint8_t encoder0_change;
int encode_ctr_prev0 = 0;

void setup() {
  Serial.begin(9600);
  pinMode(ClockPin0 , INPUT);
  pinMode(DataPin0 , INPUT);
  attachInterrupt(digitalPinToInterrupt(DataPin0), encoder0, FALLING);
}

void loop() {
  
  if (encoder0_change) {
    
    if(encode_ctr0 > encode_ctr_prev0){
      controlChange(encoder_channel, encoder_cc[0], 1);
      MidiUSB.flush(); 
    } 
  
    if(encode_ctr0 < encode_ctr_prev0){
      controlChange(encoder_channel, encoder_cc[0], 127);
      MidiUSB.flush();
    } 
  
  }

  encode_ctr_prev0 = encode_ctr0;

}

void encoder0() {
  
    if (digitalRead(DataPin0) == LOW)
    {
      encode_ctr0++;
    }
    else {
      encode_ctr0--;
    }
    encoder0_change++;
    
}


//________________________________________________________________________________________________

void controlChange(byte channel2, byte control, byte value) {
  midiEventPacket_t event = {0x0B, 0xB0 | channel2, control, value};
  MidiUSB.sendMIDI(event);
}

thank you all for your help!

I want to make a function for this so i can adress multiple encoders without copying all the code I would like
to make something like:

void encoder ( pin to use, midi, channel to use, cc message){

al code for encoder

}

I think this is not possible because of the IRS in the statement to call the interrupt or am I wrong?

anyone a suggestion? :slight_smile:

thanks in advance

zhomeslice, what value of capacitor do you use for debouncing the contacts? And did you build an RC filter, or just a capacitor across the terminals?

And are you setting up the pins as INPUT_PULLUP and connecting them to ground, or do you have them connected to +5V when closed and tied to an external pull-down resistor?

(Sorry, I know this is more of a hardware question post, but this thread is about decoding rotary encoders, so it's relevant to this thread.)

zhomeslice:
Here's my code for encoders:
I usually use a capacitor for the debounce but I added a timer to save you the trouble since it is only used for user input.

#define SwitchPin 2 // interrupt 0

#define ClockPin  3 // interrupt 1
#define DataPin  4 // this can be any pin

volatile uint8_t EncodeCTR;
volatile uint8_t EncoderChange;
volatile uint8_t SwitchCtr;

void setup() {
  Serial.begin(115200);
  pinMode(SwitchPin , INPUT_PULLUP); // switch is not powered by the + on the Encoder breakout
  pinMode(ClockPin , INPUT);
  pinMode(DataPin , INPUT);
  attachInterrupt(digitalPinToInterrupt(SwitchPin), Switch, FALLING);
  attachInterrupt(digitalPinToInterrupt(ClockPin), Encode, FALLING);
}

void loop() {
  if (EncoderChange || SwitchCtr) {
    EncoderChange = 0;
    Serial.print("EncodeCTR: ");
    Serial.print(EncodeCTR);
    Serial.println();
    Serial.print("Switch Pressed ");
    Serial.println(SwitchCtr);
    SwitchCtr = 0;
  }
  // This is non blocking so other code can go here
}

void Switch() {
  static unsigned long DebounceTimer;
  if ((unsigned long)(millis() - DebounceTimer) >= (100)) {
    DebounceTimer = millis();
    if (!SwitchCtr) {
      SwitchCtr++;
    }
  }
}
void Encode() { // we know the clock pin is low so we only need to see what state the Data pin is and count accordingly
  static unsigned long DebounceTimer;
  if ((unsigned long)(millis() - DebounceTimer) >= (100)) { // standard blink without delay timer
    DebounceTimer = millis();
    if (digitalRead(DataPin) == HIGH) // switch to LOW to reverse direction of Encoder counting
    {
      EncodeCTR++;
    }
    else {
      EncodeCTR--;
    }
    EncoderChange++;
  }
}




Z

Seems like encoder algorithms are like belly buttons – everyone has one :slight_smile:

So, I’ll throw in with one I wrote: https://github.com/gfvalvo/NewEncoder

Appropriate Credits:
* It uses the state table technique introduced here: Buxtronix: Rotary encoders, done properly so it handles de-bouncing without delays or hardware filters.
* It uses some interrupt and direct port reading techniques from the PRJC library.

This library only works on pins supporting external interrupts (2 & 3 on Uno).

Some of the improvements I’ve added are settable upper and lower count limits. It essentially “saturates” at the limit but moves off it immediately when the encoder is first rotated in the other direction.

The library also handle both types of commonly available encoder: Those with one full quadrature cycle per detent and those with one full quadrature cycle per every two detents. So, you won’t have multiple counts or need to divide.

DuncanC:
zhomeslice, what value of capacitor do you use for debouncing the contacts? And did you build an RC filter, or just a capacitor across the terminals?

And are you setting up the pins as INPUT_PULLUP and connecting them to ground, or do you have them connected to +5V when closed and tied to an external pull-down resistor?

(Sorry, I know this is more of a hardware question post, but this thread is about decoding rotary encoders, so it’s relevant to this thread.)

Encoder.jpg
I just added a couple .1uf capacitors. these are surface mount and I easily soldered them on the existing terminals. i can turn the encoder by hand as fast as I can without any issues or loss of counts.
Z

*Placing too much code within the interrupted code can cause locking of the arduino so keep the code in the callback functions to a minimum.

this saved my day, thank you

now in the interrupt part, I don't callback any functions, I just set a cw or ccw bool variable to True

then I have a function in the main loop that checks for these cw and ccw variables,
if one is true, it sets it to false, then moves whatever variable in that moment, then I call the subroutine that updates the display.

this is also how I only update the display when things change, so otherwise midi through stuff is fast.

wozlaser:
this saved my day, thank you

now in the interrupt part, I don't callback any functions, I just set a cw or ccw bool variable to True

then I have a function in the main loop that checks for these cw and ccw variables,
if one is true, it sets it to false, then moves whatever variable in that moment, then I call the subroutine that updates the display.

this is also how I only update the display when things change, so otherwise midi through stuff is fast.

#define ClockPin 2 // Must be pin 2 
#define DataPin 3 // Must be pin 3
volatile long _count = 0;
long lastCtr;

void setup() {
  Serial.begin(115200); //115200
  pinMode(ClockPin, INPUT);
  pinMode(DataPin, INPUT);

// To Keep it simple 
// []{ Statements } is what is called an anonomous function or lambda
// since attachInterrupts needs a pointer to a function we can give it this anonomous function 
// We know the Clock pin just went HIGH, so we can now read the datapin (PIND bit 3) PIND shows the states of pins 0 through 7
// if the bit for the Data pin is true them _count++ else _count-- 
// it's that simple

  attachInterrupt(digitalPinToInterrupt(ClockPin), [] {bitRead(PIND,3) ? _count++ : _count--;}, RISING); // We know the pin is true as it just triggered on rise

}

void loop() {
  long Counter;
  noInterrupts ();
  Counter = _count;
  interrupts ();
  if (lastCtr != Counter)  {
    Serial.println(Counter);
    SpareCycles = 0;
  }
  lastCtr = Counter;
}

This will read an encoder with as few lines of code as possible and it is incredibly fast.
Z