I have an optical encoder with PPR of 320, and I want to calculate the RPM of the shaft as it rotates.
From my understanding there is a couple of ways to do this, either with a period or instantaneously as the encoders pulses.
I have tried to implement it, my code below.
My questions are:
1 - I don't know if what I did is correct?
2- what happens when if I make the interval smaller?
3- is there a way to do this to get more accurate speeds? As I want to use this speed and input it into a PID controller
4- is using the BlinkwithoutDelay approach for period timing going to be okay if I have multiple encoders?
#define ENCODER0PINA 32 // this pin needs to support interrupts
#define ENCODER0PINB 33 // no interrupt required
#define CPR 320 // encoder cycles per revolution
#define CLOCKWISE 1 // direction constant
#define COUNTER_CLOCKWISE 2 // direction constant
// variables modified by interrupt handler must be declared as volatile
volatile long encoder0Position = 0;
volatile float angle=0.00;
volatile long interruptsReceived = 0;
volatile long interruptsReceivedOld =0;
volatile float speed=0.00;
// track direction: 0 = counter-clockwise; 1 = clockwise
short currentDirection = CLOCKWISE;
// track last position so we know whether it's worth printing new output
long previousPosition = 0;
unsigned long previousMillis = 0;
const long interval = 1000;
// interrupt function needs to do as little as possible
void IRAM_ATTR onInterrupt()
{
// read both inputs
//int a = digitalRead(ENCODER0PINA);
int b = digitalRead(ENCODER0PINB);
if (b== HIGH )
{
// b is leading a (counter-clockwise)
encoder0Position--;
currentDirection = COUNTER_CLOCKWISE;
}
else
{
// a is leading b (clockwise)
encoder0Position++;
currentDirection = CLOCKWISE;
}
//encoder0Position = encoder0Position % CPR;
//encoder0Position = (encoder0Position / CPR)*360;
// track the number of interrupts
interruptsReceived++;
}
void setup()
{
// inputs
pinMode(ENCODER0PINA, INPUT_PULLUP);
pinMode(ENCODER0PINB, INPUT_PULLUP);
// interrupts
attachInterrupt(32, onInterrupt, RISING);
// enable diagnostic output
Serial.begin (115200);
Serial.println("\n\n\n");
Serial.println("Ready.");
}
void loop()
{
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
speed = ((interruptsReceived - interruptsReceivedOld)*60.000)/320.000;
interruptsReceivedOld=interruptsReceived;
}
// only display position info if has changed
if (encoder0Position != previousPosition )
{
Serial.print("Speed= ");
Serial.print(speed,3);
Serial.print("\t");
angle = (encoder0Position / 320.00)*360.00;
Serial.print(angle);
Serial.print("\t");
Serial.print(encoder0Position);
Serial.print("\t");
Serial.print(currentDirection == CLOCKWISE ? "clockwise" : "counter-clockwise");
Serial.print("\t");
Serial.println(interruptsReceived);
previousPosition = encoder0Position;
}
}
Thank you so much for the help!
PS. Im using an ESP32, and I don't think I have the data sheet of the encoder, I can try to get information about it if needed.
It very much matters what range of RPMs you want to detect/measure/count/control. For slow RPMs you'll have poor accuracy with counting ticks in an interval.
Also how often will you need to know the speed to make your PID adjustment?
if (Millis >= interval) { ///if time elapsed is greater than interval?
Flag = false; ///save interruptsReceived which was updated in the interrupt function?
long count = interruptsReceived;
interruptsReceived = 0; //// why make it zero?
Flag = true;
float speed = (60000.000 / Millis) * interruptsReceived / CPR; //// this means we are multiplying with zero??
previousMillis += interval;
Serial.print("Speed= ");
Serial.print(speed, 3);
Serial.print("\t");
Serial.println(count);
}
I put the comment next to the lines Im confused about. Sorry for extra questions.
And for the calculation if I change the interval is it like this:
(60 * interval * Millis) / (CPR * interruptsReceived)?
I thought the equation was
RPM = frequency * 60 / CPR -- where frequency equals (# of pulses / interval)
Why are we doing Millis as (Current time elapsed - previous time), where previous time is an addition of the interval? this means that Millis value will not always be equal between each calculation right?
Best I appreciate this, seems like there is something I am missing.
if (Millis >= interval) { // if time elapsed is greater than interval? or equal
noInterrupts();
long count = interruptsReceived; // save interruptsReceived which was updated in the interrupt function?
interruptsReceived = 0; // why make it zero? bc why not, so it is no need to store "oldinterruptsReceived" and substract every interval with treat if overflow. in this case no big deal, but we want good programing skill
interrupts();
float TimeMultiplicator = 1000 / Millis; // take care of real elapsed milliseconds
float speed = 60 * TimeMultiplicator * count / CPR; //multiplying with zero? of course not, my error.
previousMillis += interval;
Serial.print("Speed= ");
Serial.print(speed, 3);
Serial.print("\t");
Serial.println(count);
}
I don't think you have tried to implement both of those ways, only the first of those 2.
You are correct to say there are 2 main approaches. Counting pulses over a fixed period, or measuring the period of a fixed number of pulses. Neither method is better than the other in general. Both have advantages and disadvantages.
If you use a fixed period, you are guaranteed to get an updated RPM measurement at that rate, but at lower RPM the measurement will be less accurate/precise. Also, the longer the period, the more accurate/precise the measurement will be, at the cost of updates being less frequent.
If you use a fixed number of pulses, you get a measurement which is always the same accuracy/precision, but the updates will be more frequent at higher RPM and less frequent updates at lower RPM. The higher the fixed number of pulses, the more precise/accurate the measurement but they will come less frequently.
What you need to consider is not only the PPR, but the expected range of RPM of the shaft, and the minimum frequency of updates you need.
1- Is this equation to calculate RPM with a fixed period correct?
RPM = ((# of pulses / period) * 60 ) / PPR
What would be the equation if I was changing periods and keeping number of pulses fixed? I guess I would just use the same equation...
2- Why is the fixed period approach less accurate at lower RPM? Is it because I might end up getting zeros? if the frequency of the pulses is less than the frequency of the period I choose? So for example if the shaft is rotating at an RPM that means only one pulse measured every 400ms and the period was 100ms then I would be getting a tiny RPM value for the first period then 3 zero RPM, until the next pulse arrives? Is my assumptions correct?
3- For some reason when I made the period large and flicked the shaft really quick I would get zero RPM even though it's obviously moving and I register maybe 75 pulses. But when I made the period shorter it started showing me some peak RPM that are really high when I would flick the shaft very quickly. Is there a reason for this? How does changing the period time affect the registering of low/high RPMS?
4- How would I implement the second approach? Instantaneous measurement? And what are the benefits and drawbacks of that?
Really really appreciate your support with explaining the theory and concept behind this.
The “count pulses in a fixed period” approach has more issues than zeros at low speeds. If you use it above speeds of 1000 counts/period you can measure speeds at 0.1% precision. If you are down at 10 counts/period you get 10% precision. Around 1 count per period you get really poor 100% precision and can’t reliably tell the difference 60 and 120 rpm or any speed in between.
At speeds below 1000counts/ period, measuring the time between pulses can be higher resolution than counting pulses.
Yes. Provided that 'period' is in seconds. It's likely to be in milliseconds if you are using millis() to time the period, so you may need to add a 1000 factor in also.
Correct, same equation.
Because one more or one less pulse during the fixed period would make a big difference to the answer.
Yes, of course. Probably an error in your code. It looks like you copied code written by someone else, and attempted to modify it, without really understanding how it works. For example:
attachInterrupt(32, onInterrupt, RISING);
...
void IRAM_ATTR onInterrupt()
{
// read both inputs
//int a = digitalRead(ENCODER0PINA);
int b = digitalRead(ENCODER0PINB);
if (b== HIGH )
the interrupt is triggered by the rising edge of the signal. So it must be HIGH when your code reads it. No point reading the pin or testing if it is HIGH.
I would suggest changing your interrupt routine so that after the pulse count is increased, it checks if the desired pulse count had been reached. If it has, reset the pulse counter to zero and calculate the period using the latest value of millis() or micros() and the saved value of millis()/micros() from when the counter was previously reset.