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);
}
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
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.
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?
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);
}
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:
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.
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).