Encoder as rpm sensor

Hello!
I am using Arduino Uno and an E50S8-1000-3-T-24 encoder. Arduino should read pulses from encoder, calculate RPM and then trigger stepper code functions, when encoder is turning above 20RPM and stop stepper, when encoder RPM is less than 20.
Stepper code is working properly if tested seperately.

I have trouble getting my functions to work properly. I don't really understand it, maybe encoder reading isn't stable? Or there is some obvious problems with my code?
Here's the code:

#include <AccelStepper.h>

AccelStepper stepper(AccelStepper::DRIVER, 4, 5);
int StepCounter = 0;
int i = 0;
int Home = true;
int WorkSequence = false;
int CW;


// Define our analog pot input pin
#define  SPEED_PIN 0
// Define our maximum and minimum speed in steps per second (scale pot to these)
#define  MAX_SPEED 2000
#define  MIN_SPEED 100

// read RPM .
const int numreadings = 10;
int readings[numreadings];
unsigned long average = 0;
int index = 0;
unsigned long total; 
const byte interruptPin = 2;

volatile int rpmcount = 0;
int rpm = 0;
unsigned long lastmillis = 0;

void setup() {
  pinMode(5, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(7, OUTPUT);
  digitalWrite(5, HIGH);
  digitalWrite(4, LOW);
  stepper.setMaxSpeed(3000);
  pinMode(9, INPUT_PULLUP);
  pinMode(10, INPUT_PULLUP);
  pinMode(6,INPUT_PULLUP);
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), rpm_shaft, FALLING);

}

   
void loop() {
  Homing();
  BackAndForth();

 if (millis() - lastmillis >= 1000){  /*Uptade every one second, this will be equal to reading frecuency (Hz).*/
 
 detachInterrupt(digitalPinToInterrupt(interruptPin));    //Disable interrupt when calculating
 total = 0;  
 readings[index] = (rpmcount * 60)/1000;  /* Convert frecuency to RPM */
 
 for (int x=0; x<=9; x++){
   total = total + readings[x];
 }
 
 average = total / numreadings;
 rpm = average;
 
 rpmcount = 0; // Restart the RPM counter
 index++;
 if(index >= numreadings){
  index=0; 
 } 
 
 
 
 lastmillis = millis(); // Uptade lasmillis
 attachInterrupt(digitalPinToInterrupt(interruptPin), rpm_shaft, FALLING); //enable interrupt
  }



 
  }
void rpm_shaft(){ /* this code will be executed every time the interrupt (pin2) gets low.*/
  rpmcount++;
}

There's the encoder datasheet:

Rather than use detachInterrupt() just get the value from the ISR like this

prevRpmCount = latestRpmCount; // save the current value
noInterrupts();
  latestRpmCount = rpmcount; // get the new value
interrupts()
countsThisPeriod = latestRpmCount - prevRpmCount;

That way the interrupts will be disabled for the shortest possible time.

Note that by using subtraction there is no problem if the value in rpmcount overflows and there is no need to zero it. However to make that work you must change this

volatile int rpmcount = 0;

to

volatile unsigned int rpmcount = 0;

Having said all that the timing will almost certainly be more accurate if you get the number of microseconds for a specific number of encoder pulses - perhaps with an ISR like this

void rpm_shaft() {
   static byte pulseCount = 0;
   pulseCount ++;
   if (pulseCount == 8) { // if 8 is not suitable use a more appropriate number
      latestPulseMicros = micros();
      pulseCount = 0;
      newTimeValue = true;
   }
}

and your main code will include

if (newTimeValue == true) {
   prevTimeMicros = latestTimeMicros;
   noInterrupts();
     latestTimeMicros = latestPulseMicros;
     newTimeValue = false;
   interrupts();
   // etc
}

...R

Thanks!

I still can't get rpm or even latestRpmCount values. Serial monitor just shows "0" , when turning the encoder. But, when I try Serial.println(rpmcount); , it shows all the pulses perfectly.
I am a beginner at programming, but I should really get this project done as soon as possible.
Here's the loop:

void loop() {
  Homing();  
  BackAndForth();

 if (millis() - lastmillis >= 1000){  /*Uptade every one second, this will be equal to reading frecuency (Hz).*/

prevRpmCount = latestRpmCount; // save the current value
noInterrupts();
  latestRpmCount = rpmcount; // get the new value
interrupts();
countsThisPeriod = latestRpmCount - prevRpmCount;

rpm = (countsThisPeriod/1000)*60; //Encoder gives 1000pulses per sec
 
 } 
 
 
if (millis() > 10000){  // wait for RPMs average to get stable

  Serial.println(latestRpmCount); 
   Serial.println(rpm); 

}
 
 lastmillis = millis(); // Uptade lasmillis
 
  }
void rpm_shaft(){ /* this code will be executed every time the interrupt 0 (pin2) gets low.*/
  rpmcount++;
}

Also, what variables should I use for prevRpmCount , latestRpmCount, countsThisPeriod?

unsigned long prevRpmCount = 0;
unsigned long latestRpmCount = 0;
unsigned long countsThisPeriod ;

ArthurK6:
I still can't get rpm or even latestRpmCount values.

Post the latest version of your complete program.

unsigned long variables should be fine

...R

In serial monitor, rpm value is "0" even when turning the encoder.

#include <AccelStepper.h>

AccelStepper stepper(AccelStepper::DRIVER, 4, 5);
int StepCounter = 0;
int i = 0;
int Home = true;
int WorkSequence = false;
int CW;
int motor_speed = 1500;


// Define our analog pot input pin
#define  SPEED_PIN 0
// Define our maximum and minimum speed in steps per second (scale pot to these)
#define  MAX_SPEED 4000
#define  MIN_SPEED 100

// read RPM and calculate average every then readings.
const int numreadings = 10;
int readings[numreadings];
unsigned long average = 0;
int index = 0;
unsigned long total; 

const byte interruptPin = 2;


volatile unsigned int rpmcount = 0;
int rpm = 0;
unsigned long lastmillis = 0;


unsigned long prevRpmCount = 0;
unsigned long latestRpmCount = 0;
unsigned long countsThisPeriod ;


void setup() {
  pinMode(5, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(7, OUTPUT);
  digitalWrite(5, HIGH);
  digitalWrite(4, LOW);
  stepper.setMaxSpeed(3000);
  pinMode(9, INPUT_PULLUP);
  pinMode(10, INPUT_PULLUP);
  pinMode(6,INPUT_PULLUP);
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), rpm_shaft, FALLING);
 Serial.begin(9600); 
}

   
void loop() {
  Homing();  
  BackAndForth();

 if (millis() - lastmillis >= 1000){  /*Uptade every one second, this will be equal to reading frecuency (Hz).*/

prevRpmCount = latestRpmCount; // save the current value
noInterrupts();
  latestRpmCount = rpmcount; // get the new value
interrupts();
countsThisPeriod = latestRpmCount - prevRpmCount;

rpm = (countsThisPeriod/1000)*60; // Encoder has 1000pulses per rev
 
 } 
 
 
if (millis() > 10000){  // wait for RPMs average to get stable
   Serial.println(rpm);
   Serial.println(latestRpmCount); 

}
 
 lastmillis = millis(); // Uptade lasmillis
 
  }
void rpm_shaft(){ /* this code will be executed every time the interrupt 0 (pin2) gets low.*/
  rpmcount++;
}

Hi,

Do you have code that JUST reads the encoder and calculates RPM. (NOTHING ELSE)

You should have if you developed your code in stages, making sure each stage worked before combining them.

If not, then I suggest you just write some code to get readings and display RPM.

Tom... :slight_smile:

Well, this is the code, that just reads encoder, calculates rpm and should display RPM, but it only shows "0"in serial monitor.
Encoder is working, if I try to Serial.println(rpmcount); , it shows all pulses precisely. There must be a problem in getting the interrupt value and calculation, i guess?

const byte interruptPin = 2;

volatile unsigned int rpmcount = 0;
int rpm = 0;
unsigned long lastmillis = 0;

unsigned long prevRpmCount = 0;
unsigned long latestRpmCount = 0;
unsigned long countsThisPeriod ;

void setup() {
 pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), rpm_shaft, FALLING);
 Serial.begin(9600); 
}

void loop() {
  if (millis() - lastmillis >= 1000){  /*Uptade every one second, this will be equal to reading frecuency (Hz).*/

prevRpmCount = latestRpmCount; // save the current value
noInterrupts();
  latestRpmCount = rpmcount; // get the new value
interrupts();
countsThisPeriod = latestRpmCount - prevRpmCount;

rpm = (countsThisPeriod/1000)*60; // Encoder has 1000pulses per rev
 
 } 
 
 
if (millis() > 10000){  // wait for RPMs average to get stable
   Serial.println(rpm); 

}
 
 lastmillis = millis(); // Uptade lasmillis
 
  }
void rpm_shaft(){ /* this code will be executed every time the interrupt 0 (pin2) gets low.*/
  rpmcount++;
}

Add an LED. In the (stupidly named) rpm_shaft() function, toggle the LED. Does the ISR even get called?

Referring to the code in Reply #6 ...

This is very unlikely to work

rpm = (countsThisPeriod/1000)*60;

if countsThisPeriod is less than 1000 it will give a zero answer as you are doing integer maths. Things may be better if you do the multiplication first - but probably still not adequate. Do you really need to know the speed in RPM?

Try printing the variable countsThisPeriod

And this will print in every iteration of loop() once the Arduino has been running for 10 seconds. There does not seem to be any need to print the value more often than it is updated.

if (millis() > 10000){  // wait for RPMs average to get stable
  Serial.println(rpm);
}

...R

PaulS:
Add an LED. In the (stupidly named) rpm_shaft() function, toggle the LED. Does the ISR even get called?

Sorry for stupid naming, english is not my first language :confused:
Yes, checked with a LED too, ISR gets called.
Also, if I try to print rpmcount from , it shows all the pulses correctly in serial monitor.

Robin2:
This is very unlikely to work

rpm = (countsThisPeriod/1000)*60;

if countsThisPeriod is less than 1000 it will give a zero answer as you are doing integer maths. Things may be better if you do the multiplication first - but probably still not adequate. Do you really need to know the speed in RPM?

Try printing the variable countsThisPeriod

Tried printing countsThisPeriod, still shows only "0". Got it with integer maths, I guess I don't really need speed in RPM, but still, if I get only zero values for countsThisPeriod, it doesn't really help.

Hi,
Can you try this order of statements;

   if (millis() - lastmillis >= 1000) { /*Uptade every one second, this will be equal to reading frecuency (Hz).*/
//    prevRpmCount = latestRpmCount; // save the current value
    noInterrupts();
    latestRpmCount = rpmcount; // get the new value
    interrupts();
    countsThisPeriod = latestRpmCount - prevRpmCount;

    rpm = (countsThisPeriod / 1000) * 60; // Encoder has 1000pulses per rev
    prevRpmCount = latestRpmCount; // save the current value
  }

Tom... :slight_smile:

TomGeorge:
Hi,
Can you try this order of statements;

   if (millis() - lastmillis >= 1000) { /*Uptade every one second, this will be equal to reading frecuency (Hz).*/

//    prevRpmCount = latestRpmCount; // save the current value
    noInterrupts();
    latestRpmCount = rpmcount; // get the new value
    interrupts();
    countsThisPeriod = latestRpmCount - prevRpmCount;

rpm = (countsThisPeriod / 1000) * 60; // Encoder has 1000pulses per rev
    prevRpmCount = latestRpmCount; // save the current value
  }



Tom... :)

Tried, nothing changed.
Also, latestRpmCount print shows only zero values. It should be equal to rpmcount value, right?

Also, latestRpmCount print shows only zero values. It should be equal to rpmcount value, right?

Depends on where you print()ed the value.

Huge thanks to you, guys, just had to put lastmillis = millis(); in the if loop, and it worked!

    if (millis() - lastmillis >= 1000) { /*Uptade every one second, this will be equal to reading frecuency (Hz).*/

    noInterrupts();
    latestRpmCount = rpmcount; // get the new value
    interrupts();
    countsThisPeriod = latestRpmCount - prevRpmCount;

    rpm = (countsThisPeriod) * 60/1000; // Encoder has 1000pulses per rev
    prevRpmCount = latestRpmCount; // save the current value
  Serial.println(rpm); 
  lastmillis = millis(); // Update lastmillis
}

Huge thanks to you, guys, just had to put lastmillis = millis(); in the if loop, and it worked!

Now, explain why the variable to hold the last time the RPM was calculated is called lastMillis.

That variable does not hold the last time you Millised.

ArthurK6:
Tried printing countsThisPeriod, still shows only "0". Got it with integer maths, I guess I don't really need speed in RPM, but still, if I get only zero values for countsThisPeriod, it doesn't really help.

Never stop at the first test. Test everything possible.

Have you tried printing latestRpmCount ?

Have you tried changing

volatile unsigned int rpmcount = 0;

to

volatile unsigned long rpmcount = 0;

in case your timing interval is exactly the duration to cause the romcount variable to overflow back to 0?

To be honest, my guess is that your ISR is not detecting anything. How is everything connected? Make a pencil drawing showing all the connections and post a photo of the drawing.

...R

Hi,
What are you using to power your E50S8-1000-3-T-24 encoder?

It has totem pole output, so you do not need an output pullup resistor.
BUT it is the 24Vdc model.

So the totem pole output will swing from 0V to 24V.
Have you got a potential divider on the Arduino inputs to bring the output of the encoder down to 0V to 5V?

Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?

Thanks.. Tom.. :slight_smile:

TomGeorge:
What are you using to power your E50S8-1000-3-T-24 encoder?

It has totem pole output, so you do not need an output pullup resistor.
BUT it is the 24Vdc model.

So the totem pole output will swing from 0V to 24V.
Have you got a potential divider on the Arduino inputs to bring the output of the encoder down to 0V to 5V?

I made a voltage divider with resistors to get the output down to 5V, and I power the encoder from Arduino Vin pin, which gives about 12V (because I power Arduino with 12V and encoder works with 12-24V).
I have it connected to Arduino pin2 without pullup resistor