What could be causing my Encoder Algorithm to be slightly off?

Hello all. Happy 2020!

I have a JGY371 12v Worm Gear motor with a built-in quadrature encoder.

The motor supposedly has a PPR of 11, and a gear ratio of 1/40.

I am using a high amperage motor controller to signal it’s rotation, which is powered through an external power supply. Under full load, it can supposedly pull as much as 4-5 amps, but I have not fully tested this yet since I am waiting on more hardware.

I need to calculate the position of the motor with a fairly high degree of accuracy using the built-in encoder. I already know the direction of the motor by using the motor controller, so I don’t need to use a more complicated algorithm to determine the direction state. I only need to count the cycles/pulses from each revolution to determine it’s position in degrees.

The encoder includes two feedback channels which are being sent to PIN 3 & 2 on an Arduino UNO. These pins are interrupts so it should stop the main loop and run a subroutine whenever a CHANGE is detected. In my code, I am only polling 1 interrupt from the A-channel since a second interrupt is not needed to calculate the PPR.

In theory there are 4 cycles in 1 pulse. Therefore when an interrupt occurs, there should be 1/4 pulses. Our counter will take this ratio and multiply it by the total 11 pulses within a single revolution. Then, we also have to take in account the gear ratio so we also multiply the number by a factor 40.

Now we can take the resulting number (110) and divide by 360 degrees, and we get 0.3055555555555556‬ degrees of accuracy from the encoder. We can keep adding this number each interrupt cycle to determine the overall position of the motor within a revolution.

This result seems to be almost perfect. However, there seems to be a slight degree inaccuracy. Each revolution, the motor’s position drags behind somewhere between a factor of 1-3 degrees and I cannot figure out why.

As someone else mentioned from a previous post, if it were a brushless D/C motor or something of alike, I would need to account for the delay it takes to signal the motor to turn ON/OFF as well as the overshoot you would get from the leftover momentum afterward.

There are two reasons why I do not believe this is the case for me. The first reason, is that I am using a worm geared motor which is supposed to stop any leftover momentum. The second, is that if I were to have any leftover speed after signaling the motor off, it would be overshooting a revolution instead of undershooting it.

I am not really sure what I could be doing wrong. Maybe it is a limitation from performing double float calculations on a 32 bit architecture. If I am adding roughly 0.305`recurring degrees over a total of 1,760 cycles, then I would imagine there would be some minor truncation happening from the calculations. I am not sure how to get around this.

Other than that, this simplified quadrature algorithm seems to work fairly well. There has to be some way to fix the minor 1-3 degree offset issue. I would appreciate any feedback on the matter!

Source Code:

#include <Arduino.h>

// Motor Controller channels.
int R_PWM = 11;
int L_PWM = 10;

// Feedback channels (A/B) labeled by color.
int Y_FEEDBACK = 3;
int G_FEEDBACK = 2;

// Flags for motor status and direction.
bool CLOCKWISE = false;
bool COUNTER_CLOCKWISE = false;
bool MOTOR_ON = false;

// Counter for motor's position.
volatile double counter = 0;

// Forward declaration of subroutine.
void readSensor();

void setup() {

    // Init Outputs
    pinMode(R_PWM, OUTPUT);
    pinMode(L_PWM, OUTPUT);

    // Init Inputs
    pinMode(Y_FEEDBACK, INPUT_PULLUP);
    pinMode(G_FEEDBACK, INPUT_PULLUP);

    // Init Interrupts
    attachInterrupt(digitalPinToInterrupt(Y_FEEDBACK), readSensor, CHANGE);

    // Start serial
    Serial.begin(230400);
}

void loop() {

    Serial.println("Waiting for input");

    do
    {
        // Reserved space.
    }while (Serial.available() == 0);

    // Parse incoming input.
    int direction = Serial.parseInt();

    // This could be changed to a switch statement, but it's fine as is for now.
    if (direction == 1)
    {
        CLOCKWISE = false;
        COUNTER_CLOCKWISE = true;
        MOTOR_ON = true;

        Serial.println("Counter Clockwise");
        digitalWrite(R_PWM, HIGH);
        digitalWrite(L_PWM, LOW);
    }
    else if (direction == 2)
    {
        CLOCKWISE = true;
        COUNTER_CLOCKWISE = false;
        MOTOR_ON = true;

        Serial.println("Clockwise");
        digitalWrite(R_PWM, LOW);
        digitalWrite(L_PWM, HIGH);
    }
    else if (direction == 0)
    {
        CLOCKWISE = false;
        COUNTER_CLOCKWISE = false;
        MOTOR_ON = false;

        Serial.println("Power off");
        digitalWrite(R_PWM, LOW);
        digitalWrite(L_PWM, LOW);
    }

    // Clear the input buffer.
    Serial.read();

}

void readSensor(){

    // Only begin counting the position if Motor is running.
    if (MOTOR_ON)

        // Gear Ratio (40) x Pulse Per Revolution (11) x Single interrupt Cycle (1/4) / Degrees in a revolution (360)
        counter += ( 40 * 11 * (static_cast<double>(1) / 4) / static_cast<double>(360));

    if (counter > 360)
    {
        counter -= 360.00;  // This is only for debugging. I want to see where it is within a range of 0-360 degrees.

        // Flag the motor off and power it down after a revolution is complete.
        MOTOR_ON = false;   
        Serial.println("Power off");
        digitalWrite(R_PWM, LOW);
        digitalWrite(L_PWM, LOW);
    }

    // Display the counter (Motor's position)
    Serial.println(counter);
}

You should not have any Serial.print() commands in an ISR - they are very slow and they require interrupts to be enabled and they are off inside an ISR.

You should do the absolute minimum inside your ISR so that it can return control to the rest of the program as quickly as possible. Do complicated calculations in the main program.

If it was mine I think my ISR code would simply be

void readSensor(){
   counter ++;
   newPulse = true;
}

and then the main code can check for the newPulse and take a copy of the counter value like this

if (newPulse == true) {
   noInterrupts();
     copyOfCounter = counter;
     newPulse = false;
  interrupts();
  // now do the calculations with copyOfCounter
}

Don't use a float for counter as floating point code is slow on an Arduino. If you really need to you could use a float for copyOfCounter - but you risk losing precision.

...R

aspen1135:
In theory there are 4 cycles in 1 pulse. Therefore when an interrupt occurs, there should be 1/4 pulses. Our counter will take this ratio and multiply it by the total 11 pulses within a single revolution

Since you're catching rising and falling edges, isn't that 22 counts per rev?

Maybe I'm missing something.

I highly recommend to use the PJRC encoder library, and to fully decode the encoder outputs. That way you don't miss any direction changes.

All of the above, plus the following introduces errors. The motor continues to rotate for a while even if the power is turned off:

    // Only begin counting the position if Motor is running.
    if (MOTOR_ON)

I'm a fan of the State Transition method for handling encoders.

Everyone,

I recently discovered that there is a library called “digitalWriteFast”. When implemented, this ironed out some of the data inconsistencies I was experiencing with my interrupt routines.

This did not solve all the issues. When counting the cycles up to 1760 (the anticipated number according to the motor’s specs), it signals the motor OFF but overshooting occurs.

I have completely scrapped the flag which checks to see if the motor is on, in order to count the additional overshoot offset in cycles.

I am getting a reported delta between 0-50 cycles of overshoot after turning off the motor (usually around 40). This data does not seem to reflect how much offset is actually occurring after turning off the motor however.

We know that if there are 360 deg/1760 cycles, then is 0.2045 degrees turn per cycle.

This should mean that if there are 40 additional cycles detected after turning off the motor, then there should be around 8 degrees of overshoot. However, I am experiencing about 95 degrees of overshoot after turning off the motor.

I am not sure what is going on at this point. I have request from the buyer to exchange the motor for a new one. The new one I received had a poor connection on the encoder’s PCB and would not maintain power unless it was in a precise physical position. When turning the motor on, it would usually disconnect so I am returning this one as well.

I am becoming very frustrated with this motor, but it’s one of the very few ones out there that meet my project requirements. I have tried a few different quadrature encoder arduino libraries now without any further progress. Most of them performed worse than the most simple of algorithms, which is to simply count the number of interrupts like I have been trying so far.

I thought that maybe there could be a problem with the initial-voltage being sent to the motor. I believe this generally has to be a bit higher than the constant-voltage after the motor is in rotation. However, this should not make a difference in the encoders accuracy and what is actually being reported.

Please let me know if you have any other ideas. I am eager to get this working correctly.

#include <Arduino.h>
#include <digitalWriteFast.h>
#include <Encoder.h>

bool MOTOR_OFF = true;

// IO VARIABLES
int PWM_R = 11;
int PWM_L = 10;
int CHANNEL_A = 3;
int CHANNEL_B = 2;

int userInput = 0;

// INTERRUPT VARIABLES
int readChannelA = 0;
int readChannelB = 0;
int encoderState = 0;
int cycleCounter = 0;
int pulseCounter = 0;
int cycleArray[4];

int motorPosition = 0;

int cycleTotal = 0;

// New Algorithm Variables.
// http://cdn.sparkfun.com/datasheets/Robotics/How%20to%20use%20a%20quadrature%20encoder.pdf
int QEM [16] = {0,-1,1,2,1,0,2,-1,-1,2,0,1,2,1,-1,0};
int OLD = 0;
int NEW = 0;
int OUT = 0;

// Encoder myEnc(readChannelA, readChannelB);
// long oldPosition  = -999;

void encoderInterrupt();

void setup() {

  pinModeFast(PWM_L, OUTPUT);
  pinModeFast(PWM_R, OUTPUT);

  pinModeFast(CHANNEL_A, INPUT_PULLUP);
  pinModeFast(CHANNEL_B, INPUT_PULLUP);

  // Schedule Interrupts.
  attachInterrupt(digitalPinToInterrupt(CHANNEL_A), encoderInterrupt, CHANGE);
  attachInterrupt(digitalPinToInterrupt(CHANNEL_B), encoderInterrupt, CHANGE);

  // Start the Serial port.
  Serial.begin(230400);

}

void loop() {
  do
  {
      // Display the cycles in current pulse.
      Serial.print("Cycle Pattern: ");
      for (int i=0; i<4; ++i)
        Serial.print(cycleArray[i]);

      // Display pulse number.
      Serial.print(" Pulses: ");
      Serial.print(pulseCounter);

      Serial.print(" Cycles: ");
      Serial.print(cycleTotal);
      Serial.print("\n");

  } while (Serial.available() == 0);

  userInput = Serial.parseInt();

  switch(userInput)
  {
    // Turn motor OFF
    case 0:
    {
      MOTOR_OFF = true;
      Serial.println("Motor turned off.");
      digitalWriteFast(PWM_R, LOW);
      digitalWriteFast(PWM_L, LOW);
      break;
    }
    // Rotate counter-clockwise
    case 1:
    {
      MOTOR_OFF = false;
      Serial.println("Motor rotating counterclockwise.");
      digitalWriteFast(PWM_R, HIGH);
      digitalWriteFast(PWM_L, LOW);
      break;
    }
    // Rotate clockwise
    case 2:
    {
      MOTOR_OFF = false;
      Serial.println("Motor rotating clockwise.");
      digitalWriteFast(PWM_R, LOW);
      digitalWriteFast(PWM_L, HIGH);
      break;
    }
  }

  // Clear the Serial Input Buffer
  Serial.read();

}

void encoderInterrupt()
{
  // Read the channels.
  readChannelA = digitalReadFast(CHANNEL_A);
  readChannelB = digitalReadFast(CHANNEL_B);

  // Determine the Cycle's state.
  if (readChannelA == 0 && readChannelB == 0)
    encoderState = 0;
  if (readChannelA == 0 && readChannelB == 1)
    encoderState = 1;
  if (readChannelA == 1 && readChannelB == 0)
    encoderState = 2;
  if (readChannelA == 1 && readChannelB == 1)
    encoderState = 3;

  // Insert the State Value into the corresponding cycle in the pulse array.
  cycleArray[cycleCounter] = encoderState;

  // Update the Cycle/Pulse Count.
  cycleCounter ++;

  // Update if a quadrature pulse completes and incriment the total pulse count.
  if (cycleCounter == 4)
  {
    cycleCounter = 0;
    pulseCounter ++;
  }

  // Update total Cycle/Rev Count.
  cycleTotal ++;

  // Final condition: TURN OFF MOTOR
  if (cycleTotal == 1760)
  {
    MOTOR_OFF = true;
    digitalWriteFast(PWM_R, LOW);
    digitalWriteFast(PWM_L, LOW);
    pulseCounter = 0;
    cycleTotal = 0;
  }

  // Update our motors position each cycle.
  // motorPosition += (40 * 11 * (static_cast<double>(1) / 4) / 360 );
}

How many encoder pulses are being produced per revolution?
How many revolutions per seconds? And how many pulses per second?

Do you really need to have an interrupt triggered by both encoder pins?
Do you really need to use CHANGE for the interrupts?
Triggering with RISING (or FALLING) on a single encoder pin would reduce the number of interrupts by a factor of 4.

What is the purpose of cycleArray - it does not seem to be used for anything.

When I read back over this Thread I don't really know what you actually need to do - as opposed to what you are trying, and having problems with. If you describe the project you are creating it will be much easier to give useful advice. Maybe there is another way.

...R

Look at the table near the bottom of this page, there is no 40:1 ratio, there is a 37.3:1, might that be what you have?
https://www.alibaba.com/product-detail/JGY-371B-Mini-DC-Worm-Gear_60770796833.html

OK so I was playing with code thresholds more this evening and I found that the number of 1374 to be the exact threshold of need cycles to get a perfect revolution.

The first revolution always overshoots, but then the encoder accounts for it afterwards. The next revolution aims again for the 1374 target cycle threshold and reaches it sooner due to the leftover cycles from the previous revolution. This seems to be a very accurate way of getting a precise revolution, with the exception of the very first time it is turned on. I just can't for the life of me figure out where the number "1374" is coming from.

1374 is a fairly close CPR if the motor's rated PPR were 9 instead of 11 (91140 == 1440), but even then there is a 66 cycle difference which kind of debunks this theory.

When performing the tests over again, it seems fairly consistent too. Overshoots by 40ish cycles (probably due to the speed/power of 100 RPM motor), and then second time I trigger a rotation it compensates perfectly.

Faulty encoder or motor? What do you think?

I will have to hookup the replacement motor I received tomorrow. As I mentioned, it has a finicky encoder PCB so it may not be the most scientific comparison. If I clamp it down and get the encoder power to stay on once the motor's started, I'll have to compare the two units against each other. It'll be interesting to see if the replacement motor shows the same results or not.

JCA34F:
Look at the table near the bottom of this page, there is no 40:1 ratio, there is a 37.3:1, might that be what you have?
Jgy-371b Mini Dc Worm Gear Motor With Encoder In Auto -lock Function - Buy Dc Micro Worm Gear Motor,Dc Planetary Gear Motor With Encoder,12v Dc Worm Gear Motor Product on Alibaba.com

Just updated the thread with some news. The CPR need for a revolution seems to be 1374. If the gear ratio were 37.3, then the theoretical CPR should be 1641.2 so this kind of debunks this theory. I appreciate you digging and finding this though. It seems this version you found is the "B" model. The sticker on my motor and the listing on amazon say this is simply the "JGY-371" with no Letter afterwards.

I was hoping you were right though, because I currently have no way of explaining why 1374 is the CPR. It does not match the manufacturers specs.

Robin2:
How many encoder pulses are being produced per revolution?
How many revolutions per seconds? And how many pulses per second?

Do you really need to have an interrupt triggered by both encoder pins?
Do you really need to use CHANGE for the interrupts?
Triggering with RISING (or FALLING) on a single encoder pin would reduce the number of interrupts by a factor of 4.

What is the purpose of cycleArray - it does not seem to be used for anything.
...R

When I was first trying to determine the 4 quadratic states, the cycleArray variable was used to store the last cycles so I could print them over the serial port later for debugging.

I also wanted to get the highest resolution and make sure I am counting all cycles to make sure they meet the manufacturers specifications. Although now that you mention it, I may want to try changing the interrupt states. I should get a PPR of 440 from checking a pulse which is still a resolution with a high enough accuracy to a single degree and plenty for my application.

The motor is 100 RPM, although I have not clocked this yet. It seems to check out on first glance. I am using a 12v 6 AMP power supply into a 40 AMP rated motor controller module. These were simply precautionary steps for when I put the motor under load and I haven't even gotten that far yet. I also don't have any probing tools to see how much power is coming through since I am new to hobbyist electronics, I'll have to buy one soon... especially if the motor is not rotating up to full speed.

i trying to use an encoder to position a model railroad turntable and have similar found unexpected encoder events. Still haven’t resolved all my problems.

i minimize ISR events by triggering on the RISING edge of one encoder output (blue). The state of the other encoder input determines whether the position is incremented or decremented. Code outside the ISR monitors the position and controls the motor.

in order to debug, within the ISR I capture a timestamp (micros()), encoder values, position and speed (PWM). The following plot of the captured shows the position (white) encoder states (red and blue near bottom) and time between encoder events (red). samples are marked.

i was surprised to see a LOW encoder value for the input triggering the ISR on the RISING edge. If you look closely, you would see the position decremented as well as the event occurring sooner than expected. invalid events

wire between the motor, encoder and Arduino is about 20". I found fewer invalid events if i keep the motor and encoder wires separated. ignoring events that occurs < 50% of the previous event helps but should be unnecessary.

i don’t believe the encoder salvaged from a printed should have such long wire lengths. I’m considering using twisted pair for each encoder, or coax, or possibly a smitt trigger driver near the encoder.

you could try capturing data w/in the ISR to help you debug your system. I’m using an encoder with 30 vanes. ISR events should be 200 usec apart at 10k rpm.

aspen1135:
a PPR of 440

[.....]

The motor is 100 RPM,

That suggests that there are 733.33 pulses per second, or a pulse every 1363.6 microsecs. Is that correct?

That is very comfortably within the capability of a 16MHz Arduino and there should be no problem capturing every pulse.

...R

I received an email back from the vendor, who said:

dear customer

thank you for your email

our engineer said "The motor speed is 12V100RPM, the motor speed is 6000, the speed ratio is 62.4, the final speed is 96 plus or minus 5%, the motor rotates 62.4 turns equal to the output speed 1 turn, the output pulse signal is 686.4 output shafts, and the motor has 11 pulse signals per turn."

686.4 * 4 = 2745.6 CPR. Which was pretty close to what I was getting for 1 revolution. It turns out one of the channels on my motor previously was shorted, so there was only 1 channel reporting cycles (hence the "1374" CPR I reported earlier).

I am still very confused where this engineer is coming up with these numbers, as these numbers are not indicated on any of the specifications sheets I could find for the motor. I also do not understand the math for the speed ratio and where he came up with 686.4 output shafts.

Could someone here help explain this with a little more detail?

Thank you!

It reads as if the 11 PPR encoder is on the motor shaft and there is a 62.4 :1 reduction gear between the motor shaft and the output shaft.

One way to verify things would be to remove the gearbox from the motor and to rotate the motor shaft slowly by hand while monitoring the encoder output - but dismantling may not be possible or sensible.

...R

PS ... It is possible that 62.4 is an approximation - gearboxes are often designed with fractional ratios to reduce the amount of times the same pairs of teeth meet.

It turns out one of the channels on my motor previously was shorted, so there was only 1 channel reporting cycles (hence the "1374" CPR I reported earlier).

96 rpm = 6000 rpm / 62.4

each turn of the output shaft requires 62.4 turns of the motor.
the output shaft rpm is 1/62.4 of the motor rpm

with multiple gears, the ratio may be some fraction

62.45 = 1374 / 11 / 2

1374 encoder events / output shaft rotation
11 encoder cycles / motor revolution
2 encoder events / encoder cycle

each turn of the motor generates 11 encoder cycles
an encoder cycle is each encoder output going high then low in sequence, 4 possible events / cycle
if one encoder output were shorted, then there are only 2 encoder events per cycle