Issues Reading an encoder

Hello,

I have looked through the web and the forum in particular but was able to fine a solution to the issue I am having. I am trying to read an industrial quadrature encoder with an Arduino DUE board and so some math an print the value to the Serial interface. Below I attach the code.

The issue I am having is inconsistent reading of the encoder count. I am using a precise stepper motor powered system to pull the encoder, so I am able to command precise movements to test the program performance and accuracy. For some reason, as it seems to me, not all the encoder pulses are being caught by the Arduino DUE. Sometimes it is spot on and fairly repeatable to within 1 encoder count (I am printing enocder counts as well, for debugging purposes), other times it is starting to accumulate missed pulses with each movement.

By the way, before any suggestions are made regarding noise - I am pretty confident there is no noise causing the described behavior. My wiring from the encoder to the DUE is short, additionally to confirm there is no noise induced to the DUE's input pins, I ran the stepper without mechanical engagement with the encoder indefinite amount of repetitions without any change in the printed encoder count.

I believe it could be fixed with better coding. I have tried several things (adding a debounce being the most recent) including assigning interrupts to both input pins, which did not help. Would appreciate any insight.

Thanks,
Dan

/* 
This program is reading an encoder with an Arduino DUE, calculates the corresponding distance
 its cirumference travels and prints to serial interface. The code has a debounce which is added
 to deal with possible imperfections of the input signal. Only if the signal stays there longer
 than the set debounceDelay it is counted by the program.
 */
// Define the pins for the encoder
const int encoderPinA = 3;
const int encoderPinB = 4;

//Read the pines
#define readA digitalRead(encoderPinA)//faster than digitalRead()
#define readB digitalRead(encoderPinB)//faster than digitalRead()


// Variables for encoder state and distance
volatile bool lastStateA = LOW;
volatile bool lastStateB = LOW;
volatile long encoderCount = 0;
const int countsPerRevolution = 2000; // Counts per revolution of the encoder
const float encoderDiameter = 11.021;//11.07; // Diameter of the encoder in millimeters
float lastDistanceTraveled = 0; // Last distance traveled by the encoder's circumference
float totalDistanceTraveled = 0; // Total distance traveled by the encoder's circumference
const unsigned long debounceDelay = 10; // Adjust as needed (microseconds)

// Variables for printing to serial port conditions
unsigned long lastEncoderChangeTime = 0; // Time of the last encoder count change
unsigned long lastPrintTime = 200; // Time of the last print to serial monitor

// Setup function
void setup() {
  // Initialize serial communication
  Serial.begin(9600);

  // Set encoder pins as inputs
  pinMode(encoderPinA, INPUT);
  pinMode(encoderPinB, INPUT);

  // Enable internal pullup resistors for encoder pins
  digitalWrite(encoderPinA, HIGH);
  digitalWrite(encoderPinB, HIGH);

  // Attach interrupts to the encoder pins
  attachInterrupt(digitalPinToInterrupt(encoderPinA), readEncoder, CHANGE);
  
}
void readEncoder() {
  // Read encoder state after debounce delay
  bool currentStateA = digitalRead(encoderPinA);
  bool currentStateB = digitalRead(encoderPinB);
  delayMicroseconds(debounceDelay); // Debounce delay

  // Check if the state has changed
  if (currentStateA != lastStateA || currentStateB != lastStateB) {
    // Update encoder count based on direction of rotation
    if (currentStateA != currentStateB) {
      encoderCount++;
    } else {
      encoderCount--;
    }
    // Update last state
    lastStateA = currentStateA;
    lastStateB = currentStateB;
  }
  lastEncoderChangeTime = millis();
}

/*

// Main loop
void loop() {
  //Reset the distance upon any input from the serial port
  if (Serial.available()){
    Serial.read();
    encoderCount = 0;
  }

  // Calculate distance traveled based on encoder counts and diameter
  float circumference = PI * encoderDiameter; // Calculate circumference
  float distance = ((encoderCount) * circumference) / (countsPerRevolution/2); // Calculate distance
  

  // Update total distance traveled
  totalDistanceTraveled = distance;
  

  // Check conditions for printing to serial port
  unsigned long currentTime = millis();
  if ((encoderCount != 0) && (currentTime - lastEncoderChangeTime >= 500) && (totalDistanceTraveled != lastDistanceTraveled) && (currentTime - lastPrintTime >= 100)) {
    // Print distance traveled to serial monitor
    Serial.print(totalDistanceTraveled);
    Serial.print(", EncoderCount: ");
    Serial.println(encoderCount);
    // Update last printed distance and time values
    lastDistanceTraveled = totalDistanceTraveled;
    lastPrintTime = currentTime;
  }

  delay(1);
}

What is the maximum revolutions/second?
If it more than about 12 your code may miss one or more pulses

1 Like

Have you added pullup resistors to the encoder outputs?

1 Like

Don’t delay in an interrupt.

Sorry, forgot to mention that the speed is very slow - fastest that I ran it was 2 rev/sec.

Nope, using the DUE's internal pull-up resistors.

As I mentioned, this addition was the most recent thing I tried, after not being able to find a solution. It didn't seem to make any difference.

These may be too high. Try adding 1k to each signal wire.

Let's see.
If you are rotating at 1 rev/second, you would get 2000 pulses/second or 1 pulse every 500us.
So if your precise stepper is actually spinning at 1.0005 revs/sec you would get a count of 2001.
Can your precise stepper run with that accuracy

Perhaps it would be best if you tested your code with an accurate function generator set to 2000.0Hz

Your commented out main code doesn't look like it protects against encoderCount changing during use.

Maybe make a copy within a noInterrupts();myCount=encoderCount; interrupts(); bracket and use that.

Hmm... I didn't even bother to check the value of the internal ones. May try to add external pull-ups. Thanks.

Well, actually, I only print the value when the motor is still. Also, a stepper motor is a fairly repeatable device when operated under the right conditions. A full step accuracy is within 5%. In my application a full step of the motor corresponds to 1.333 encoder counts. Meaning it should be repeatable to, roughly, 1 encoder count, while what I am seeing is an accumulating error far beyond the stepper's precision.

A function generator is not something I have access to, unfortunately.

I am afraid I don't understand what you mean by this.

Maybe true but many times they are not, that's why we use encoders.
They can certainly skip steps like you are seeing.

A full step accuracy is within 5%

Really has nothing to do with speed accuracy.
If it running fast you will get more counts.

You could use an arduino to generate a 1kHz square wave. The frequency may not be accurate but it will be stable.

First, your posted code does not compile.

Ah, I missed that you were on a Due -- on 8-bit arduinos at least, reading and writing between multiple bytes can be interrupted and get corrupted. Im not sure of the size of words and longs on a Due.

As, is the commented code looks like interrupts could happen between the calculations and the printing. If you save a copy once per loop, and use the copy for the calcs and printing, they can't get out of sync.

Could it be that the stepper is actually the component skipping steps? How fast is the stepping, and how big are the accelerations?

Edit: ...and how big are the errors?

1 Like

Right, sorry. Line 68 should be deleted. Below I attach the updated code with some minor changes, like using 2 interrupts (one for each input) instead of one and disabling internal pull-ups.

How would you go about saving a copy of the encoderCount without it being changed by an interrupt?

No, this is not the case and I am certain about this. I have a home switch, which I use to home and confirm the stepper position in between the tests. It is extremely repeatable with no lost steps or noticeable position errors.

Now, I have done some extensive testing with the below code and I discovered some consistency. In the tests I run the motor a given amount of revolutions in one direction then the exact same amount in the opposite direction. What I found was that the encoderCount looses counts in one direction only AND only for moves larger than around 1.5 to 1.7 revolutions of the encoder. Moves that are shorter than that are extremely precise and consistent.

I believe there is something wrong in my code and how it handles the interrupts, but I cannot see what could it be.

/* 
This program is reading an encoder with an Arduino DUE, calculates the corresponding distance
 its cirumference travels and prints to serial interface. The code has a debounce which is added
 to deal with possible imperfections of the input signal. Only if the signal stays there longer
 than the set debounceDelay it is counted by the program.
 */
// Define the pins for the encoder
const int encoderPinA = 3;
const int encoderPinB = 4;

//Read the pines
#define readA digitalRead(encoderPinA)//faster than digitalRead()
#define readB digitalRead(encoderPinB)//faster than digitalRead()


// Variables for encoder state and distance
volatile bool lastStateA = LOW;
volatile bool lastStateB = LOW;
volatile long encoderCount = 0;
const int countsPerRevolution = 2000; // Counts per revolution of the encoder
const float encoderDiameter = 11.021;//11.07; // Diameter of the encoder in millimeters
float lastDistanceTraveled = 0; // Last distance traveled by the encoder's circumference
float totalDistanceTraveled = 0; // Total distance traveled by the encoder's circumference
const unsigned long debounceDelay = 10; // Adjust as needed (microseconds)

// Variables for printing to serial port conditions
unsigned long lastEncoderChangeTime = 0; // Time of the last encoder count change
unsigned long lastPrintTime = 200; // Time of the last print to serial monitor

// Setup function
void setup() {
  // Initialize serial communication
  Serial.begin(9600);

  // Set encoder pins as inputs
  pinMode(encoderPinA, INPUT);
  pinMode(encoderPinB, INPUT);

  // Attach interrupts to the encoder pins
  attachInterrupt(digitalPinToInterrupt(encoderPinA), readEncoder1, CHANGE);
  attachInterrupt(digitalPinToInterrupt(encoderPinB), readEncoder2, CHANGE);
}

// Functions to read the encoder
void readEncoder1() {
  // Update the encoder count based on the direction of rotation
  if (readB != readA) {
    encoderCount++;
  } else {
    encoderCount--;
  }
  // Update the time of the last encoder count change
  lastEncoderChangeTime = millis();
}

void readEncoder2() {
  // Update the encoder count based on the direction of rotation
  if (readA == readB) {
    encoderCount++;
  } else {
    encoderCount--;
  }
  // Update the time of the last encoder count change
  lastEncoderChangeTime = millis();
}


// Main loop
void loop() {
  //Reset the distance upon any input from the serial port
  if (Serial.available()){
    Serial.read();
    encoderCount = 0;
  }

  // Calculate distance traveled based on encoder counts and diameter
  float circumference = PI * encoderDiameter; // Calculate circumference
  float distance = ((encoderCount) * circumference) / (countsPerRevolution); // Calculate distance
  

  // Update total distance traveled
  totalDistanceTraveled = distance;
  

  // Check conditions for printing to serial port
  unsigned long currentTime = millis();
  if ((encoderCount != 0) && (currentTime - lastEncoderChangeTime >= 500) && (totalDistanceTraveled != lastDistanceTraveled) && (currentTime - lastPrintTime >= 100)) {
    // Print distance traveled to serial monitor
    Serial.print(totalDistanceTraveled);
    Serial.print(", EncoderCount: ");
    Serial.println(encoderCount);
    // Update last printed distance and time values
    lastDistanceTraveled = totalDistanceTraveled;
    lastPrintTime = currentTime;
  }

  delay(1);
}

By the way, ran it this time at 0.3 rev/sec. Same results

Added 2k resistors. That didn't make any change. I don't think I need pull-ups anyway as I am using an industrial encoder (not one of those rotary switches) which should have a circuit of its own to handle that:

https://www.sensata.com/products/position-sensors-encoders/h25-incremental-encoder-01002-10023

Are you sure it was not 0.30005rev/sec?
How do you know the precise speed of the motor.
You are testing your code with an input of unknown accuracy.

It may not be a software or interrupt problem at all.

Read the Voltage/Output spec in that data sheet. Only the 28/O version is compatible with the DUE, else you may have killed the inputs already. The NPN output requires pullup resistors.

What is your encoder's exact, full part number?

Yes, I seem to have given a link to a wrong model. The correct one is this:
http://www.galardy.com/files/1848031/uploaded/BEI_H20_Incremental_Encoder.pdf

And the exact part number is:
H20EA-37-F28-SS-500-ABZC-26LS31-SM18-5V-S

It is 5V TTL output. Sorry for the confusion caused.

I still do see why should I bother about speed? I am running the stepper in position mode, so only thing that matters to me should be the start position and end position. I don't care what it did and how it got from A to B as long as it didn't exceed the optimal operating conditions of the stepper that could lead to lost steps (which it didn't).