How to write code for shaft encoder RPM counter?

I'm trying to build a simple tachometer using a shaft encoder attached to a DC motor axis. I was able to find a code here in Arduino Forum that allowed me to count the number of revolutions on the encoder shaft. My encoder has 25 steps for every full rotation, so I changed the code just a little bit and also added the DC motor control code. I've thought about using millis() to measure the time between each full rotation and then calculate the RPM, but this just didn't work.
So my question is: how should I adapt this code so I can calculate RPM for my tachometer?

volatile int number = 0;                
                                       
int oldnumber = number;

volatile boolean halfleft = false;      
volatile boolean halfright = false;

int oldtime=0;
int time;
float rev=0, lastmillis=0;
int rpm;

//DC Motor
int enableA = 11;
int motorA1 = 10;
int motorA2 = 9;



void setup(){
  Serial.begin(9600);
  pinMode(2, INPUT);
  digitalWrite(2, HIGH);                
  pinMode(3, INPUT);
  digitalWrite(3, HIGH);                
  attachInterrupt(0, isr_2, FALLING);  
  attachInterrupt(1, isr_3, FALLING);   

//DC Motor Control  
  pinMode(enableA, OUTPUT);
  pinMode(motorA1, OUTPUT);
  pinMode(motorA2, OUTPUT);

  digitalWrite(enableA, HIGH);
  digitalWrite(motorA1, HIGH);
  digitalWrite(motorA2, LOW);
}

void loop(){
  if(number != oldnumber){              
    //Serial.println(number/25);            
         delay(1000);
        detachInterrupt(0); 
        detachInterrupt(1); 
        time = millis() - lastmillis;
        rev = number/25;
        Serial.println(rev);


    rpm = (rev/time)*60000;
        Serial.println(rpm);
    
    oldnumber = number;
    lastmillis = millis();
    rev = 0;

    attachInterrupt(0, isr_2, FALLING);
    attachInterrupt(1, isr_3, FALLING); 
  }
}

void isr_2(){                                             
  delay(1);                                                
  if(digitalRead(2) == LOW){                               
    if(digitalRead(3) == HIGH && halfright == false){     
      halfright = true;                                  
    }  
    if(digitalRead(3) == LOW && halfleft == true){       
      halfleft = false;                                   
      number--;                                            
    }
  }
}
void isr_3(){                                             
  delay(1);                                               
  if(digitalRead(3) == LOW){                              
    if(digitalRead(2) == HIGH && halfleft == false){      
      halfleft = true;                                    
    }                                                     
    if(digitalRead(2) == LOW && halfright == true){       
      halfright = false;                                  
      number++;
    }
  }
}

Usually, it's good to have a sensor that just sends out a pulse every time it rotates 1 revolution. Then have a timer that measures the difference in time that it takes between two measured pulses.

Or if it sends out a number of pulses per revolution, then the timer still needs to accurately measure time differences between pulses....except that some calculations are needed to figure out the angular speed.

So, is your encoder an 'incremental' one, or an 'absolute' one?

If incremental, and if it gives 25 pulses per revolution, then that's one pulse per 0.04 revolution.

If the time between two pulses is M seconds, then:

0.04/M would be the speed in revolution per second.

which would be speed in RPM = [60*0.04]/M revolution per minute

Here’s a simple tach sketch you may be able to adapt for your project, only needs one interrupt pin (pin2) on Uno for encoder channel A, connect encoder channel B to pin 4, its set for 25 PPM & update rate of 2 per second.

unsigned long start;
const byte encoderPinA = 2;//A pin -> interrupt pin 0
const byte encoderPinB = 4;//B pin -> digital pin 4
volatile long pulse;
volatile bool pinB, pinA, dir;
const byte ppr = 25, upDatesPerSec = 2;
const int fin = 1000 / upDatesPerSec;
const float konstant = 60.0 * upDatesPerSec / (ppr * 2);
int rpm;


void setup() {
  Serial.begin(9600);
  attachInterrupt(0, readEncoder, CHANGE);
  pinMode(encoderPinA,INPUT);
  pinMode(encoderPinB,INPUT); 
}

void loop() {
  if(millis() - start > fin)
  {
    start = millis();
    rpm = pulse * konstant;
    Serial.println(rpm);
    pulse = 0;
  }
}

void readEncoder()
{
  pinA = bitRead(PIND,encoderPinA);
  pinB = bitRead(PIND,encoderPinB);
  dir = pinA ^ pinB;          // if pinA & pinB are the same
  dir ? --pulse : ++pulse;    // dir is CW, else CCW
}

Thank you, but I've tried using your code and the output in the monitor serial was just:

0
7
2
0
0
0
4
4
2
0
4
2

My encoder has 3 pins: Signal 1, GND and Signal 2. I've wired Signal 1 to digital pin2, Signal 2 to digital pin4 and GND to GND.

Try the following change:

  pinMode(encoderPinA,INPUT_PULLUP);
  pinMode(encoderPinB,INPUT_PULLUP); [/code

Sorry for the late reply. It's working great now. But could you explain me the code? The void readEncoder part.

  dir = pinA ^ pinB;          // if pinA & pinB are the same
  dir ? --pulse : ++pulse;    // dir is CW, else CCW

could be much cleaner as

  pulse += (pinA == pinB) ? +1 : -1 ;

Avoiding the rather obfuscated use as a side-effecting expression in place of a statement,
and avoiding an unecessary variable and XOR.

The way this test works relies on the interrupt only happening when one of the pins
changes, so it only detects half the transitions, but gets the direction right by snooping
the other pin at the same time. A slightly more complex test will work on every transition
giving twice the angle resolution.

But could you explain me the code? The void readEncoder part.

attachInterrupt(0, readEncoder, CHANGE);
void readEncoder()
{
  pinA = bitRead(PIND,encoderPinA);
  pinB = bitRead(PIND,encoderPinB);
  dir = pinA ^ pinB;          // if pinA & pinB are the same
  dir ? --pulse : ++pulse;    // dir is CW, else CCW
}

The interrupt is triggered when encoderPinA (digital pin 2, external interrupt 0) changes. The design and construction of the rotary encoder is such that knowing the state (HIGH or LOW) of pin B when pin A changes can tell the direction of rotation. If A changes and B is in the same state as A the rotation is in one direction(CW), if B is in a different state from A the direction of rotation is in the opposite direction(CCW).

The bitRead() is taking the place of digitalRead() and is faster.
Without using the XOR (^) operator or the ternary ( ?) operator the code is more frequently written

void readEncoder()
{

 if (digitalRead(PinA) == digitalRead(pinB) )//are the two pins the same
  {
    encoderCount++;
  }
  else //the two pins are in different states
  {
    encoderCount--;
  }
 }

Sorry to be bumping an old thread, I don’t understand the point of the 2 in konstant.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.