Rotary encoder using interrupts

Hi! I have a 400ppr, max 330 rpm, 2 phase, rotary encoder. (2200 points per secound)

I found that most of the example codes are using digitalWrite, which seems to be too slow. So i tried using interrupts without digitalread, but i have some problems with it.

volatile int A = 0;
volatile int B = 0;
volatile unsigned int count = 0;
 
void setup() {
  Serial.begin (115200);
 
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  digitalWrite(2, HIGH);
  digitalWrite(3, HIGH);
  
  attachInterrupt(0, Arising, RISING);
  attachInterrupt(1, Brising, RISING);
}
 
void loop() {
    if (count != count) {
      Serial.println(count);
    }
  }
 
void Arising() {
  attachInterrupt(0, Afalling, FALLING);
  A = 1;
  if (B = 1) {
    count ++;
  } else {
    count --;
  }
}
 
void Afalling() {
  attachInterrupt(0, Arising, RISING);
  A = 0;
  if (B = 1) {
    count --;
  } else {
    count ++;
  }
}


void Brising() {
  attachInterrupt(1, Bfalling, FALLING);
  B = 1;
  if (A = 1) {
    count --;
  } else {
    count ++;
  }
}
 
void Bfalling() {
  attachInterrupt(1, Brising, RISING);
  B = 0;
  if (A = 1) {
    count ++;
  } else {
    count --;
  }
}

All i get when I'm turning the encoder any way is:

0
65535
0
1
0
0
65535
0
0
65535
65535
0
65535
1
65535
0
1

I've tried changing the phase shift, but the closest I get to function, is disabling one of the interrupts, and I get it to work, but then it only counts upwards either way.

Here is an image of the phase shift:

Can anyone help me with my code?
And what is the fastest way to read a 2 phase shift using arduino?

I'm not an expert on this but I wonder if you just need a RISING interrupt on (say) phase A. In the Interrupt Service Routine (ISR) use digitalRead() or PINx to check the other phase. If phase B is LOW it is turning CW and if phase B is HIGH it is turning CCW. Something like this

void setup() {
   // other stuff
   attachInterrupt(aPin, myISR, RISING);
}


void myISR() {
  phaseB = digitalRead(bPin);
  newPulse = true;
}

...R

Good job with the code tags on your first post.

Hi! I have a 400ppr, max 330 rpm, 2 phase, rotary encoder. (2200 points per second)

You can read the encoder to count 1,2, or 4 of the available quadrature transitions. The resolution is a design choice for you to make. Robin2 suggests a routine to read 1 of the 4 transitions. Your original code was to count all 4 transitions so I have kept with that approach although the resolution sounds high(but achievable) for 330rpm. What is your application, and what are you going to do with the count?

There are many things wrong with your code. The switching of interrupt modes(RISING/FALLING) is wrong and can lead to problems. Use CHANGE and read the pin.

if (B = 1) {

You have several statements like this, which are wrong for two reasons. You must use == for the comparison, and you need to read the value of the B pin with digitalRead(B); There are faster routines than digitalRead() and I have used one of them in the code below.

 if (count != count)

To control you output, you need to use count and previous count, and assign the value of count to previousCount after the comparison. You also need to briefly stop the interrupts to grab a value of count which will not change as it is being read.

Here’s a modified version of your code which addresses these issues.

const byte encoderPinA = 2;//outputA digital pin2
const byte encoderPinB = 3;//outoutB digital pin3
volatile int count = 0;
int protectedCount = 0;
int previousCount = 0;

#define readA bitRead(PIND,2)//faster than digitalRead()
#define readB bitRead(PIND,3)//faster than digitalRead()


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

  pinMode(encoderPinA, INPUT_PULLUP);
  pinMode(encoderPinB, INPUT_PULLUP);
  
  attachInterrupt(digitalPinToInterrupt(encoderPinA), isrA, CHANGE);
  attachInterrupt(digitalPinToInterrupt(encoderPinB), isrB, CHANGE);
}

void loop() {
  noInterrupts();
  protectedCount = count;
  interrupts();
  
  if(protectedCount != previousCount) {
    Serial.println(protectedCount);
  }
  previousCount = protectedCount;
}

void isrA() {
  if(readB != readA) {
    count ++;
  } else {
    count --;
  }
}
void isrB() {
  if (readA == readB) {
    count ++;
  } else {
    count --;
  }
}

Heine:
Hi! I have a 400ppr, max 330 rpm, 2 phase, rotary encoder. (2200 points per secound)

I found that most of the example codes are using digitalWrite, which seems to be too slow. So i tried using interrupts without digitalread, but i have some problems with it.

volatile int A = 0;

volatile int B = 0;
volatile unsigned int count = 0;

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

pinMode(2, INPUT);
  pinMode(3, INPUT);
  digitalWrite(2, HIGH);
  digitalWrite(3, HIGH);
 
  attachInterrupt(0, Arising, RISING);
  attachInterrupt(1, Brising, RISING);
}

void loop() {
    if (count != count) {
      Serial.println(count);
    }
  }

void Arising() {
  attachInterrupt(0, Afalling, FALLING);
  A = 1;
  if (B = 1) {
    count ++;
  } else {
    count --;
  }
}

void Afalling() {
  attachInterrupt(0, Arising, RISING);
  A = 0;
  if (B = 1) {
    count --;
  } else {
    count ++;
  }
}

void Brising() {
  attachInterrupt(1, Bfalling, FALLING);
  B = 1;
  if (A = 1) {
    count --;
  } else {
    count ++;
  }
}

void Bfalling() {
  attachInterrupt(1, Brising, RISING);
  B = 0;
  if (A = 1) {
    count ++;
  } else {
    count --;
  }
}





All i get when I'm turning the encoder any way is:



0
65535
0
1
0
0
65535
0
0
65535
65535
0
65535
1
65535
0
1




I've tried changing the phase shift, but the closest I get to function, is disabling one of the interrupts, and I get it to work, but then it only counts upwards either way.


Here is an image of the phase shift:
![|500x115](http://i66.tinypic.com/2eauxit.png)


Can anyone help me with my code?
And what is the fastest way to read a 2 phase shift using arduino?

I've messed with encoders and have come up with this code that has most everything you might want.

I've documented it quite a bit to help you understand what is happening.
Features:
Step counter (Low resolution) counts once per sequence.
Speed in RPM both forward and reverse (-)

#define ClockPin 2 // Must be pin 2 or 3
#define DataPin 9 // can be any other pin
      // My Encoder has 400 Clock pulses per revolution
      // note that 150000.0 = (60 seonds * 1000000 microseconds)microseconds in a minute / 400 pulses in 1 revolution)
      // change the math to get the proper multiplier for RPM for your encoder
#define Multiplier 150000.0 // don't forget a decimal place to make this number a floating point number
volatile long EncoderCounter = 0;
volatile float SpeedInRPM = 0;

void onPin2CHANGECallBackFunction(uint32_t Time, uint32_t PinsChanged, uint32_t Pins){ 
    static uint32_t lTime; // Saved Last Time of Last Pulse
    uint32_t cTime; // Current Time
    cTime = micros(); // Store the time for RPM Calculations
    int32_t dTime; // Delt in time

// Encoder Code
    bool DataPinVal = digitalRead(DataPin);
// We know pin 2 just went high to trigger the interrupt
// depending on direction the data pin will either be high or low
    EncoderCounter += (DataPinVal) ? 1 : -1; // Should we step up or down?
// End Encoder Code

// calculate the DeltaT between pulses
    dTime = cTime - lTime; 
    lTime = cTime;
    SpeedInRPM = Multiplier / ((DataPinVal) ? dTime: (-1 * dTime)); // Calculate the RPM Switch DeltaT to either positive or negative to represent Forward or reverse RPM
}

void setup() {
  Serial.begin(115200); //115200
  // put your setup code here, to run once:
  pinMode(ClockPin, INPUT);  
  pinMode(DataPin, INPUT);
  attachInterrupt(0,onPin2CHANGECallBackFunction,RISING);
}

void loop() {
  static unsigned long SpamTimer;
  if ( (unsigned long)(millis() - SpamTimer) >= (100)) {
    SpamTimer = millis();
    Serial.print(EncoderCounter);
    Serial.print("\t");
    Serial.print(SpeedInRPM, 3);
    Serial.print(" RPM");
    Serial.println();
    SpeedInRPM = 0; // if no pulses occure in the next 100 miliseconds then we must assume that the motor has stopped
  }
}

Not much to it :slight_smile:

Z

@zhomeslice

volatile long EncoderCounter = 0;
volatile float SpeedInRPM = 0;

if ( (unsigned long)(millis() - SpamTimer) >= (100)) {
    SpamTimer = millis();
    Serial.print(EncoderCounter);
    Serial.print("\t");
    Serial.print(SpeedInRPM, 3);
    Serial.print(" RPM");
    Serial.println();
    SpeedInRPM = 0; // if no pulses occure in the next 100 miliseconds then we must assume that the motor has stopped
  }

Both variables (EncoderCounter and SpeedInRPM) are multi- byte variables and an interrupt can occur while they are being accessed. Interrupts should be temporarily disabled when reading them.

From Nick Gammon’s interrupt tutorial

Also if multi-byte fields are being updated by an ISR then you may need to disable interrupts so that you get the data “atomically”. Otherwise one byte may be updated by the ISR while you are reading the other one.

For example:

noInterrupts ();

long myCounter = isrCounter;  // get value set by ISR
interrupts (



);


Temporarily turning off interrupts ensures that isrCounter (a counter set inside an ISR) does not change while we are obtaining its value.

cattledog:
@zhomeslice

volatile long EncoderCounter = 0;

volatile float SpeedInRPM = 0;

if ( (unsigned long)(millis() - SpamTimer) >= (100)) {
   SpamTimer = millis();
   Serial.print(EncoderCounter);
   Serial.print("\t");
   Serial.print(SpeedInRPM, 3);
   Serial.print(" RPM");
   Serial.println();
   SpeedInRPM = 0; // if no pulses occure in the next 100 miliseconds then we must assume that the motor has stopped
 }




Both variables (EncoderCounter and SpeedInRPM) are multi- byte variables and an interrupt can occur while they are being accessed. Interrupts should be temporarily disabled when reading them.

From [Nick Gammon's interrupt tutorial](http://gammon.com.au/interrupts)

Excellent catch @cattledog

So you are Recommending:

#define ClockPin 2 // Must be pin 2 or 3
#define DataPin 9 // can be any other pin
      // My Encoder has 400 Clock pulses per revolution
      // note that 150000.0 = (60 seonds * 1000000 microseconds)microseconds in a minute / 400 pulses in 1 revolution)
      // change the math to get the proper multiplier for RPM for your encoder
#define Multiplier 150000.0 // don't forget a decimal place to make this number a floating point number
volatile long EncoderCounter = 0;
volatile float SpeedInRPM = 0;

void onPin2CHANGECallBackFunction(uint32_t Time, uint32_t PinsChanged, uint32_t Pins){ 
    static uint32_t lTime; // Saved Last Time of Last Pulse
    uint32_t cTime; // Current Time
    cTime = micros(); // Store the time for RPM Calculations
    int32_t dTime; // Delt in time

// Encoder Code
    bool DataPinVal = digitalRead(DataPin);
// We know pin 2 just went high to trigger the interrupt
// depending on direction the data pin will either be high or low
    EncoderCounter += (DataPinVal) ? 1 : -1; // Should we step up or down?
// End Encoder Code

// calculate the DeltaT between pulses
    dTime = cTime - lTime; 
    lTime = cTime;
    SpeedInRPM = Multiplier / ((DataPinVal) ? dTime: (-1 * dTime)); // Calculate the RPM Switch DeltaT to either positive or negative to represent Forward or reverse RPM
}

void setup() {
  Serial.begin(115200); //115200
  // put your setup code here, to run once:
  pinMode(ClockPin, INPUT);  
  pinMode(DataPin, INPUT);
  attachInterrupt(0,onPin2CHANGECallBackFunction,RISING);
}

void loop() {
  long Counter;
  float Speed;
  noInterrupts (); 
// Because when the interrupt occurs the EncoderCounter and SpeedInRPM could be interrupted while they 
// are being used we need to say hold for a split second while we copy these values down. This doesn't keep the 
// interrupt from occurring it just slightly delays it while we maneuver values.
// if we don't do this we could be interrupted in the middle of copying a value and the result get a corrupted value.
  Counter = EncoderCounter;
  Speed = SpeedInRPM;
  interrupts ();

// use the speed and counter values for whatever you need to do.

  static unsigned long SpamTimer;
  if ( (unsigned long)(millis() - SpamTimer) >= (100)) {
    SpamTimer = millis();
    Serial.print(Counter );
    Serial.print("\t");
    Serial.print(Speed , 3);
    Serial.print(" RPM");
    Serial.println();
    SpeedInRPM = 0; // if no pulses occure in the next 100 miliseconds then we must assume that the motor has stopped
  }
}

Thanks again **@cattledog ** I've update my code to represent your catch :slight_smile:
z

I'm not sure that you can attach two separate interrupts to a single pin. You may need to have a single one that listens to CHANGING.

65535 is -1. That is, the bit pattern 0xFFFF interpreted as an unsigned integer is 65535, and interpreted as a signed integer it is -1.

cattledog:
Robin2 suggests a routine to read 1 of the 4 transitions. Your original code was to count all 4 transitions so I have kept with that approach

I had been assuming that when it says 400 ppr that that would be 400 rising transitions on phase A - is that not correct?

...R

Heine:
Can anyone help me with my code?
And what is the fastest way to read a 2 phase shift using arduino?

Your encoder is a quadrature encoder, right?

Maybe use 2 interrupt channels.

1 channel for the first quadrature pair (A1, A2). This means the waveform of A1 can be connected to a arduino pin, and that pin can be linked to in interrupt.

and 1 channel for the second quadrature pair (B1, B2). That is, assign signal B1 to an arduino pin, and link that pin to a different interrupt.

Detect falling edge of A1 waveform with interrupt. When the interrupt occurs, do a digital read on that pin (just for the heck of it.....to make sure it is low). And then do a digital read on the pin assigned to waveform A2. If A1 is high, and A2 is low.... then that will just mean 1 particular motor direction. If A1 is low and A2 is high....then that'll be other 'other' direction.

Motor speed could be estimated from measured times between interrupts of A1. Or over several counts of A1 interrupts.

A similar thing can be done for waveform B1 and B2.

If the motor is stopped during time different measurements....then have a bit of code that just sets the time difference value to something relatively large (which could be a codeword for motor has virtually stopped, or is going really really slowly).

Thank you very much guys! I learnt alot from your examples, i specially liked cattledog's version by using bitread, it was fast enough for my aplication, which was reading the position of the steering wheel on my full scale 3.3l V6 1,8 ton RC car :slight_smile: The optical encoder is HN3806.

cattledog:
Good job with the code tags on your first post.
You can read the encoder to count 1,2, or 4 of the available quadrature transitions. The resolution is a design choice for you to make. Robin2 suggests a routine to read 1 of the 4 transitions. Your original code was to count all 4 transitions so I have kept with that approach although the resolution sounds high(but achievable) for 330rpm. What is your application, and what are you going to do with the count?

There are many things wrong with your code. The switching of interrupt modes(RISING/FALLING) is wrong and can lead to problems. Use CHANGE and read the pin.

if (B = 1) {

You have several statements like this, which are wrong for two reasons. You must use == for the comparison, and you need to read the value of the B pin with digitalRead(B); There are faster routines than digitalRead() and I have used one of them in the code below.

 if (count != count)

To control you output, you need to use count and previous count, and assign the value of count to previousCount after the comparison. You also need to briefly stop the interrupts to grab a value of count which will not change as it is being read.

Here's a modified version of your code which addresses these issues.

const byte encoderPinA = 2;//outputA digital pin2

const byte encoderPinB = 3;//outoutB digital pin3
volatile int count = 0;
int protectedCount = 0;
int previousCount = 0;

#define readA bitRead(PIND,2)//faster than digitalRead()
#define readB bitRead(PIND,3)//faster than digitalRead()

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

pinMode(encoderPinA, INPUT_PULLUP);
  pinMode(encoderPinB, INPUT_PULLUP);
 
  attachInterrupt(digitalPinToInterrupt(encoderPinA), isrA, CHANGE);
  attachInterrupt(digitalPinToInterrupt(encoderPinB), isrB, CHANGE);
}

void loop() {
  noInterrupts();
  protectedCount = count;
  interrupts();
 
  if(protectedCount != previousCount) {
    Serial.println(protectedCount);
  }
  previousCount = protectedCount;
}

void isrA() {
  if(readB != readA) {
    count ++;
  } else {
    count --;
  }
}
void isrB() {
  if (readA == readB) {
    count ++;
  } else {
    count --;
  }
}

this is probably an old post but I want to share this option in the code

 volatile int MSB = PIND & B00000100 ;// digitalRead(encoderPin1); //MSB = most significant bit
 volatile int LSB = PIND & B00001000; //LSB = least significant bit

 volatile int encoded = (MSB >> 1) |(LSB >> 3); //converting the 2 pin value to single number
 volatile int sum  = (lastEncoded << 2) | encoded; //adding it to the previous encoded value

  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011){
    encoderValue ++;
    encoderValuet ++;
  }
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) {
    encoderValue --;
    encoderValuet --;
  }
  lastEncoded = encoded; //store this value for next tim

it’s work fine with high speed encoders