Equation for magnetic encoder to calculate speed?

I'm trying to write a code for a magnetic encoder that I have installed onto a motor.

I'm using this code written by system here.

I'm having two issues. The first I don't understand where the 2 came from in this equation.

const float konstant = 60.0 * upDatesPerSec / (ppr * 2);

60 is for rate per MINUTE, multiplied by the updates per sec in case the frequency is increased. the PPR is dependent on the encoder. My issue is specifically with that last 2. Where did it come from?

My other issue is probably something in my own hardware. I've inherited this hardware and I'm trying to make things work.
Rover
Encoder

Encoder is 12 PPR (or 20, both numbers are written in the webpage). The motor in the rover is said to have a gear ratio of 30:1.

So supposedly each revolution of the wheel should be 12*30 = 360 pulses. What I'm actually getting is 230 constantly in one direction and 130 constantly in the other direction. Not sure if there's something wrong with the code or are the wheels literally slipping at a constant rate somehow.

I’m guessing that the 2 comes from here. Since it triggers on change, you get 2 pulses per position on the encoder, one for the rising edge and one for the falling edge.

attachInterrupt(0, readEncoder, CHANGE);

Or from here....

  • Provides a resolution of 20 counts per revolution of the motor shaft

Something is very wrong, but you have not given enough information to guess what it might be. You won't get anything to work correctly until you know the actual value of "counts per revolution", and of course it must be the same in both directions.

Yes. Can you show your own code so we can see all the constants.
If your sample time is 500 ms (as the original code appears to be) and your gearing is 1:30 you may not see many (whole) pulses in that period, especially if the motor is rotating slowly

I thought about that. But I think it only counts once if they're both the same or both not the same. Unless I'm confused. From my understanding it only counts once.

I think the website is wrong. I think it's 12. But anyway, the original code with the 2 isn't using the same encoder. The one that you're quoting is my own.

I tried using it with the other motor. Just to clarify they're both connected to belts (like the ones on tanks). And I'm getting around 230 in both directions now. I guess the side I was trying it on was slipping. I'm not sure where the that 230 is coming from though. Assuming the encoder is 12, that means the gear ratio is around 19ish.

Here's the code so we're all on the same page. Only thing I did was I removed the RPM anyway and only counting the pulses. I was trying to see how many pulses I'd get from one full motor revolution. And the next part would be to calculate the RPM, but I'd like to know why the "2". It's not factored in with time because time has been included with the upDatesPerSec constant. Why is there a 2 though. Is it really counting twice per tick? In this case the gear ratio would be 230/12/2? Around 9ish then?

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


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

}

void loop() {
  if(millis() - start > fin)
  {
    start = millis();
    rpm = pulse * konstant; 
    //Serial.println(rpm);
     Serial.println("Pulse value: "); Serial.print(pulse);
    //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
  
}

Let's say the encoder output 16 pulses per rev (PPR) and you were only interrupting on either the rising or falling pulse edge, konstant would be 60.0 * upDatesPerSec / 16, but if you interrupt on BOTH edges (CHANGE), (32 PPR), you would need to DOUBLE PPR (16 * 2) to get the correct count.

The inconsistency in the readings is possibly due to pulse being a 4 byte value updated asynchronously with the print operation (non atomic read on an 8bit MCU).
In the loop(), suspend interrupts, make a local copy of pulse, resume interrupts, then print the copy of pulse.
The upDatesPerSec value is there to control the frequency at which the print to the console happens.

Edit
The code is confusing because upDatesPerSec and interrupts per encoder pulse both have the value 2.

Maybe this helps:

The very basic equation for measuring rotational speed:

revolutions_per_minute = revolutions_per_second * 60 
revolutions_per_second = pulses_counted_in_measuring_period / ( pulses_delivered_in_one_revolution_by_encoder * measuring_period_in_seconds )

The encoder, to your sketch, appears twice as fast because it sees two interrupts per pulse

So, some concrete example figures

the measuring period is 3 seconds. In that 3 seconds you count 240 interrupts. That is you have you have received 120 encoder pulses.
Your encoder delivers 12 pulses per revolution. Therefore in that 3 seconds there have been 10 revolutions. That is 3.34 revolutions per second.
To get revolutions per minute, simply multiply that by 60 to give 200 RPM

Ah I see. Yh so that kinda makes sense! Okay, that covers the reason behind the 2 then. Thanks!

I'm confused about this. Would I be asking too much if I asked for an example?

Thanks for the equation! I guess, the whole rising and falling edge issue being counted twice for each pulse makes sense!

If this works, the reason will have been that the value of pulse was being updated by the ISR simultaneously with your attempt to print it. The variable pulse is 4 bytes long. An 8 bit MCU does operations in units of single bytes, so the 4 byte value could have been in a half updated state.

void loop() {
  if (millis() - start > fin)
  {
    // pulse is updated in ISR
    static long pulseCopy ;
    noInterrupts() ;
    pulseCopy = pulse ;
    interrupts() ;

    start = millis();
    rpm = pulseCopy * konstant;
    //Serial.println(rpm);
    Serial.println("Pulse value: "); Serial.print(pulseCopy);
    //pulse = 0;

  }
}

Accessing a variable or variables within noInterrupts/interrupts like this is called a
"critical section" - the entire code must run to completion without anything else
changing the variable(s) in question. This is particularly important for multi-processing,
but an ISR is effectively a separate process running asynchronously to your code, so
you have to protect against both the main program and ISR running within the critical
section (which is why its critical!)

The other terminology you'll see is "atomic" - if a bunch of code has to run indivisibly,
it should be executed atomically (literally 'without cutting')

The ISR itself is normally run atomically - unless you elect to re-enable interrupts
before the ISR returns.

Hi,
Relevant images of the encoder.
0J7468.230

0J7469.600

0J7470.600

Tom... :grinning: :+1: :coffee: :australia:

I'll try that and will see what I come up with.

I tried the same code on a Raspberry pi because we need to compare their values and I'm getting completely different results for the same setup.

What I'm doing now is trying to figure out how to use a second Arduino to create a hardware in the loop simulator. Have that produce a signal at a preset frequency and see if the arduino and RPi will get the same value.

The problem is I don't trust the motor/encoder/belt setup.

Oh I see. So in layman's terms, the interrupt is breaking the operation it's interrupting? I was under the impression that it just pauses it and continues to do it.

Well in this case, in 6v6gt's code, wouldn't stopping the interrupts for the copying operation force the arduino to miss some of the pulses coming through and hence ruin the readings?

I'm sorry, I'm not sure how these help.

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