Encoder drift. optical relative encoder

i'm using this encoder:

brand: BERM

B E6B2-CWCWZ6C

with Arduino Nano Every

and this code:

/**
 *  Encoder example code 
 * 
 * This is a code intended to test the encoder connections and to demonstrate the encoder setup.
 * 
 */

#include <SimpleFOC.h>


Encoder encoder = Encoder(2, 3, 2500);
// interrupt routine intialisation
void doA() {
  encoder.handleA();
}
void doB() {
  encoder.handleB();
}

void setup() {
  // monitoring port
  Serial.begin(115200);

  // enable/disable quadrature mode
  encoder.quadrature = Quadrature::ON;

  // check if you need internal pullups
  encoder.pullup = Pullup::USE_INTERN;

  // initialise encoder hardware
  encoder.init();
  // hardware interrupt enable
  encoder.enableInterrupts(doA, doB);

  Serial.println("Encoder ready");
  _delay(1000);
}

void loop() {
  // iterative function updating the sensor internal variables
  // it is usually called in motor.loopFOC()
  // not doing much for the encoder though
  encoder.update();
  // display the angle and the angular velocity to the terminal
  if (encoder.getAngle() == 0) {
    Serial.println("zero");
  };
  //Serial.print(encoder.getAngle());
  //Serial.print("\r\n");
  //Serial.println(encoder.getVelocity());
}

it's the angle read example from Simple.FOC, that i modified to output "zero" every time the shaft is back to the origin.
the idea is to test repeatability.
It drifts. After going a couple of full turns in one direction and back, the point at which i get "zero" message shifts by a considerable angle (a visible angle i mean). I can easily make it drift by as much as 30 degrees after a minute of twitching the knob.
Any ideas why? and how to fix it?

i don't have to use simpleFOC -- i can use a simple code with the same result. for example this:


volatile long temp, counter = 0;  //This variable will increase or decrease depending on the rotation of encoder

void setup() {
  Serial.begin(115200);

  pinMode(2, INPUT_PULLUP);  // internal pullup input pin 2

  pinMode(3, INPUT_PULLUP);  // internalเป็น pullup input pin 3
                             //Setting up interrupt
  //A rising pulse from encodenren activated ai0(). AttachInterrupt 0 is DigitalPin nr 2 on moust Arduino.
  attachInterrupt(digitalPinToInterrupt(2), ai0, RISING);

  //B rising pulse from encodenren activated ai1(). AttachInterrupt 1 is DigitalPin nr 3 on moust Arduino.
  attachInterrupt(digitalPinToInterrupt(3), ai1, RISING);
}

void loop() {
  // Send the value of counter
  if (counter != temp) {
    Serial.println(counter);
    temp = counter;
  }
}

void ai0() {
  // ai0 is activated if DigitalPin nr 2 is going from LOW to HIGH
  // Check pin 3 to determine the direction
  if (digitalRead(3) == LOW) {
    counter++;
  } else {
    counter--;
  }
}

void ai1() {
  // ai0 is activated if DigitalPin nr 3 is going from LOW to HIGH
  // Check with pin 2 to determine the direction
  if (digitalRead(2) == LOW) {
    counter--;
  } else {
    counter++;
  }
}

The drift clearly indicates either code errors or line noise interfering with the counts, or both.

  1. Try the Arduino Encoder library.

  2. The INPUT_PULLUP resistors are far too weak for open collector outputs on long leads. Use external pullups of 5K or even lower to avoid line noise errors.

Is there a data sheet for that encoder?

thank you. will do

  1. could you expand on "Arduino Encoder library," please. Which one?

could you give the exact name, please

By the way, you make the classical mistake of failing to protect a shared variable from being corrupted by the interrupt, while being accessed by the main program.

This frequently fails:

  if (counter != temp) {
    Serial.println(counter);
    temp = counter;
  }

This is the correct approach:

 noInterrupts();
 long counter_copy = count;
interrupts();
  if (counter_copy != temp) {
    Serial.println(counter_copy);
    temp = counter_copy;
  }
1 Like

thank you

pull-up resistors didn't help.
Arduino library -- issue remains
i used this code:

/* Encoder Library - Basic Example
 * http://www.pjrc.com/teensy/td_libs_Encoder.html
 *
 * This example code is in the public domain.
 */

#include <Encoder.h>

// Change these two numbers to the pins connected to your encoder.
//   Best Performance: both pins have interrupt capability
//   Good Performance: only the first pin has interrupt capability
//   Low Performance:  neither pin has interrupt capability
Encoder myEnc(2, 3);
//   avoid using pins with LEDs attached

void setup() {
  Serial.begin(9600);
  Serial.println("Basic Encoder Test:");
}

long oldPosition  = -999;

void loop() {
  long newPosition = myEnc.read();
  if (newPosition != oldPosition) {
    oldPosition = newPosition;
    Serial.println(newPosition);
  }
}

You missed the last part of reply #6, "This is the correct way"

1 Like

could you expand a bit, please. I understand that you second that the code:

 noInterrupts();
 long counter_copy = count;
interrupts();
  if (counter_copy != temp) {
    Serial.println(counter_copy);
    temp = counter_copy;
  }

has to go in place of

 if (counter != temp) {
    Serial.println(counter);
    temp = counter;
  }

But I didn't use the latter.

i tried this code

volatile long temp, counter = 0;  //This variable will increase or decrease depending on the rotation of encoder

int i = 0;  // just to make 0 lines differ

void setup() {
  Serial.begin(115200);

  pinMode(2, INPUT_PULLUP);  // internal pullup input pin 2

  pinMode(3, INPUT_PULLUP);  // internalเป็น pullup input pin 3

  //pinMode(4, INPUT_PULLUP);  // pin 4

  //Setting up interrupt
  //A rising pulse from encodenren activated ai0(). AttachInterrupt 0 is DigitalPin nr 2 on moust Arduino.
  attachInterrupt(digitalPinToInterrupt(2), ai0, RISING);

  //B rising pulse from encodenren activated ai1(). AttachInterrupt 1 is DigitalPin nr 3 on moust Arduino.
  attachInterrupt(digitalPinToInterrupt(3), ai1, RISING);

  //Z
  //attachInterrupt(digitalPinToInterrupt(4), aiZ, RISING);
}

void loop() {
  // Send the value of counter


  noInterrupts();
  long counter_copy = counter;
  interrupts();
  if (counter_copy != temp) {
    Serial.println(counter_copy);
    temp = counter_copy;
  }



  if (counter == 0) {
    Serial.println(counter);
    Serial.println(i);
    i++;
  }
}

void ai0() {
  // ai0 is activated if DigitalPin nr 2 is going from LOW to HIGH
  // Check pin 3 to determine the direction
  if (digitalRead(3) == LOW) {
    counter++;
  } else {
    counter--;
  }
}


void ai1() {
  // ai0 is activated if DigitalPin nr 3 is going from LOW to HIGH
  // Check with pin 2 to determine the direction
  if (digitalRead(2) == LOW) {
    counter--;
  } else {
    counter++;
  }
}

/*
void aiZ() {
  if (digitalRead(2) == LOW) {
    Serial.println("Z");
    Serial.println(counter);
  } else {
    //
  }
}
*/


the issue remains

Draw the pulse train, the quadrature signals. Then look for the point where it makes ++ to the count variable. Do the same for the --.If You switch the direction in an unfortunate moment the rewerse count comes to early.
I saw this problem in an industrial sawing machine some 35 - 40 years ago. Sadly I don't remember the details that fixed it, how I fixed it.

Not a very scientific method of determining repeatability. You may be exceeding the maximum frequency of the encoder and/or processing speed of the nano every. At 2500 PPR, count will increment 5000 times/revolution.

To test repeatability you should have a means of rotating the encoder at a controlled rate.

That's because you only trigger an ISR on the RISING edges.

//                           _______         _______       
//               Pin1 ______|       |_______|       |______ Pin1
// negative <---         _______         _______         __      --> positive
//               Pin2 __|       |_______|       |_______|   Pin2
//                    0   1   2   3   0   1   2   3   0

Imagine what happens with this sequence : 0 1 2 3 2 1 0
Counter = 0
0  1: Pin2 RISING - counter = 1
1  2: Pin1 RISING - counter = 2
2  3: Pin2 FALLING - counter = 2
3  2: Pin2 RISING - counter = 1
2  1: Pin1 FALLING - counter = 1
1  0: Pin2 FALLING - counter = 1
The knob is at starting position again but counter is now 1 !

EDIT :
Even the simple 0 1 0 is sufficient to show the drift !

it's a sound suggestion. But on further analysis it cannot be the reason. Here's why. it's 2500 ppr. 10000 pulses per rotation. 0.036 degree between changes. If this were the reason, the error would be in the single numbers, which gives fraction of a degree. something like a tenth of a degree. I see 30 degrees after a dozen of turns. that's 1000 times higher (in the ballpark). Do you still think the explanation you gave is likely?

could you explain a bit. I don't understand. I thought the best way to check repeatability of the result is to put the system in unfavorable conditions. it's ability to do the milage in a continuous rotation for example is of no use to me. I want to see if it behaves reliably in after a few random rotations.

same point as i made to post#14

Your code and/or circuitry is obviously not working correctly, but you haven't provided enough information to confidently identify all the actual problems.

A complete schematic diagram, with links to all the system components, would be a good place to start. Have a look at the posting instructions in the "How to get the best out of this forum" post.

Then you have proved it does not.
Time to look for a different encoder.

the encoder is rated at 5000RPM, and i'm turning it with my fingers -- so, no. Also, i go at most 1revolution per second (60 RPM), which results in a pulse frequency of 1khz. Nano works at 20Mhz, which is 2000 times higher. Do you still think it may be the case?