Encoder readings not correct

Hello,

i have a very important project i am working on currently. The ide is to rewire an existing rollforming machine in a simple way. I just input the length of the piece and the quantity of the pieces. Arduino then starts the motor of the machine, once it gets the desired length data from the rotary encoder it stops the machine, triggers the cutters and then restarts the motor…
Everything works (look at the VIDEO) with the exception of one problem. Each piece is a bit different in length. Meaning the encoder and arduino are not comunicating properly.

I have tried using the following solutions:

  1. 2 × 10 kΩ resistors (pull-ups)
  2. 2 × 220 Ω series resistors - on encoder signal wires
  3. 0.1 µF ceramic + 100 µF electrolytic capacitors (power decoupling)

Here is my code:

#include <LiquidCrystal.h>
#include <Encoder.h>
Encoder myEnc(2, 3); // (CLK, DT)
// =======================
// LCD KEYPAD Shield 
// =======================
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
const int buttonPin = A0;

// =======================
// Releji
// =======================
const int relayPin = 22; // start the škarje
const int relayPin2 = 20; // start/stop da machin
// =======================
// Rotary Encoder (KY-040)
// =======================
const int encoderPinA = 2;  // 
const int encoderPinB = 3;  // 
volatile long encoderCount = 0;
long lastEncoderCount = 0;

// =======================
// Spremenljivke
// =======================
float mmPerTick = 0.25;     // kalibracija enkodarja : mm per encoder tick
int pieceLength = 1250;     // mm
int pieceQuantity = 20;     
int producedPieces = 0;

int menuStage = 0;         // 0 = set length, 1 = set quantity, 2 = calibration
bool running = false;

// =======================
// Encoder Interrupt
// =======================
void readEncoder() {
  int b = digitalRead(encoderPinB);
  if (b == HIGH) {
    encoderCount++;
  } else {
    encoderCount--;
  }
}

// =======================
// Beri - LCD Keypad Button
// =======================
int readLCDKey() {
  int x = analogRead(buttonPin);
  if (x < 50)   return 0; // Right
  if (x < 110)  return 1; // Up
  if (x < 270)  return 2; // Down
  if (x < 420)  return 3; // Left
  if (x < 660)  return 4; // Select
  return 5; // None
}

// =======================
// Setup
// =======================
void setup() {
  Serial.begin(9600);
  pinMode(relayPin, OUTPUT);
  pinMode(relayPin2, OUTPUT);
  digitalWrite(relayPin2, LOW); // 
  digitalWrite(relayPin, HIGH);

  pinMode(encoderPinA, INPUT_PULLUP);
  pinMode(encoderPinB, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(encoderPinA), readEncoder, CHANGE);

  lcd.begin(16, 2);
  lcd.print("Cutting Machine");
  delay(1500);
  lcd.clear();
}

// =======================
// Loop
// =======================
void loop() {

    long position = myEnc.read();

  if (!running) {
    // Menu navigation
    lcd.setCursor(0,0);
    if (menuStage == 0) {
      lcd.print("Len(mm):");
      lcd.print(pieceLength);
      lcd.print("    ");
      lcd.setCursor(0,1);
      lcd.print("Set piece length ");
    } 
    else if (menuStage == 1) {
      lcd.print("Qty:");
      lcd.print(pieceQuantity);
      lcd.print("    ");
      lcd.setCursor(0,1);
      lcd.print("Set total pieces ");
    }
    else if (menuStage == 2) {
      lcd.print("mm/tick:");
      lcd.print(mmPerTick, 2); // 
      lcd.print("   ");
      lcd.setCursor(0,1);
      lcd.print("Calibrate encoder");
    }

    int key = readLCDKey();
    if (key == 1) { // Up
      if (menuStage == 0) pieceLength += 10;
      else if (menuStage == 1) pieceQuantity++;
      else if (menuStage == 2) mmPerTick += 0.01;
    } 
    else if (key == 2) { // Down
      if (menuStage == 0 && pieceLength > 10) pieceLength -= 10;
      else if (menuStage == 1 && pieceQuantity > 1) pieceQuantity--;
      else if (menuStage == 2 && mmPerTick > 0.1) mmPerTick -= 0.01;
    } 
    else if (key == 0 || key == 3) { // Right/Left switch menu
      menuStage = (menuStage + 1) % 3;
      lcd.clear();
    } 
    else if (key == 4) { // Select = Start job
      lcd.clear();
      lcd.print("Starting..");
      delay(2000);
      digitalWrite(relayPin2, HIGH);
      running = true;
      producedPieces = 0;
      encoderCount = 0;
      lastEncoderCount = 0;
      lcd.clear();
    }
    delay(200);
  } 
  else {
    // Running cutting process
    lcd.setCursor(0,0);
    lcd.print("Cutting ");
    lcd.print(producedPieces);
    lcd.print("/");
    lcd.print(pieceQuantity);

    float traveled = abs(encoderCount - lastEncoderCount) * mmPerTick;

lcd.setCursor(0,1);
lcd.print("                "); // clear line
lcd.setCursor(0,1);
lcd.print("Len:");
lcd.print(traveled,1);
lcd.print("/");
lcd.print(pieceLength);

    if (traveled >= pieceLength) {
                  // Trigger relay2
      digitalWrite(relayPin2, LOW);
      delay(1000); // relay pulse
      
      
      // Trigger relay
      digitalWrite(relayPin, LOW);
      delay(500); // relay pulse
      digitalWrite(relayPin, HIGH);
      delay (1000);
      digitalWrite(relayPin2, HIGH);

      producedPieces++;
      lastEncoderCount = encoderCount; // reset distance 

      if (producedPieces >= pieceQuantity) {
        running = false;
        digitalWrite(relayPin2, LOW);
        lcd.clear();
        lcd.print("Job Finished!");
          // Stop motor
        
        
        lcd.clear();
      }
    }
  }
}

I am using arduino mega and keyestudio 1602 LCD Keypad, rotary encoder is 12volt 5 wire encoder (ground, +, -, signal1, signal 2)

Anything you guys might come up with?

Please post a link to the encoder datasheet.

Please also post schematics.

How do you know this?
Did you monitor the signals with a scope?
Maybe the encoder is bad?
Maybe your program is wrong?

this is the only data i can find on it..

The result was the same with 2 brand new encoders so encoder should be fine..the code you are more than welcome to check. I am no expert so it most certanly could be wrong but the way i see it the most likely problem is in the wiring (wrong capacitors, something missing or something like that)

The problem is likely to do with how rotary encoders work. They can only measure how much rotation has taken place, not there absolute position.

You need to ensure that you know the starting point for the movement. If there is some kind of backstop you could reverse the motor back to the backstop and then start measuring from there, and then record that position as zero.

HTH

Im not much of a sketch artist so bare with me but this is rougly it.. the only thing missing here is the relays but its really just two basic relays triggering the machine drive and the cutter. That part works fine. I tried with and without the capacitors..result is the same so i am guessing they are not much of use here at all.. or are they?

Should i try optocoplers?

The delay() s bother me, as the way your encoders are coded, nothing will be counted during those periods.

Also, you instantiate the encoder library, but manually manipulate the count values outside of the encoder object ?

Just my first quick observations.

The information on that webpage is somewhat confusing. They say "consider" using pull-up not that they are required but also say the output is open-collector

Normally I would recommend trying a smaller pullup value like 2.2K but they do not mention the current handling capability of the encoder.

Also, you may try connecting the shield ground in only one place at the Arduino ground and not the machine ground.

Also, the code does need some fixing.

How was this determined?
float mmPerTick = 0.25; // kalibracija enkodarja : mm per encoder tick

Is there a ground connection between processor and encoder?

not at the moment..i will try it

this is just for calibrating..i added this because i had to make a new wheel which rotates as the steel sheet passes into the machine. Because the size of the wheel isnt identical to the old one (the one on the photo) i added this to adjust rotations into mm..and yes i understand its the speed that matters but still it came in handy. Should i try something different?

Not yet but as @dougp pointed out it won't work right without a ground.

Encoder (input) side — 12 V domain (isolated)

  • Encoder +12 V → encoder Vcc (red).

  • Encoder GND → encoder GND (black).

  • Encoder A (green) → 1k resistor → 6N137 LED anode (pin 2).

  • 6N137 LED cathode (pin 3) → encoder GND.

6N137 output side — Arduino / 5 V domain (isolated)

  • 6N137 VCC (pin 8) → +5 V (Arduino 5 V supply).

  • 6N137 GND (pin 5) → Arduino GND.

  • 6N137 VO (pin 6) → pull-up resistor to 5 V (10 kΩ). Then VO also goes to the input of a 74HC14 gate.

  • 6N137 VE (enable, pin 7) → tied to +5 V

  • 0.1 µF bypass capacitor close between VCC and GND on the 6N137 output side.

  • 6N137 VO → 74HC14 input

  • 74HC14 output → Arduino digital input pin (e.g. 2 and 3 — INT0, INT1)

  • 74HC14 VCC → 5 V, GND → Arduino GND

  • Added 0.1 µF decoupling at 74HC14 VCC→GND.

new code:

#include <LiquidCrystal.h>

// =======================
// LCD KEYPAD Shield 
// =======================
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
const int buttonPin = A0;

// =======================
// Releji
// =======================
const int relayPin = 22;   // start the škarje
const int relayPin2 = 20;  // start/stop da machin

// =======================
// Rotary Encoder (KY-040) - ISR-only
// =======================
const int encoderPinA = 2;
const int encoderPinB = 3;
volatile long encoderCount = 0;   // updated in ISR
long lastEncoderCount = 0;        // read in main loop (non-volatile copy)

// =======================
// Spremenljivke
// =======================
float mmPerTick = 0.25;     // kalibracija enkodarja : mm per encoder tick
int pieceLength = 1250;     // mm
int pieceQuantity = 20;
int producedPieces = 0;

int menuStage = 0;         // 0 = set length, 1 = set quantity, 2 = calibration
bool running = false;

// =======================
// Encoder Interrupt (fast & minimal)
// =======================
void readEncoder() {
  // Read the B pin to determine direction when A changes
  // Keep ISR minimal
  if (digitalRead(encoderPinB) == HIGH) {
    encoderCount++;
  } else {
    encoderCount--;
  }
}

// =======================
// Beri - LCD Keypad Button
// =======================
int readLCDKey() {
  int x = analogRead(buttonPin);
  if (x < 50)   return 0; // Right
  if (x < 110)  return 1; // Up
  if (x < 270)  return 2; // Down
  if (x < 420)  return 3; // Left
  if (x < 660)  return 4; // Select
  return 5; // None
}

// Helper to safely read volatile encoderCount
long safeReadEncoder() {
  noInterrupts();
  long val = encoderCount;
  interrupts();
  return val;
}

// Small helper to clear a 16-char LCD line
void clearLine(int row) {
  lcd.setCursor(0, row);
  for (int i = 0; i < 16; ++i) lcd.print(' ');
  lcd.setCursor(0, row);
}

// =======================
// Setup
// =======================
void setup() {
  Serial.begin(9600);

  pinMode(relayPin, OUTPUT);
  pinMode(relayPin2, OUTPUT);
  digitalWrite(relayPin2, LOW);
  digitalWrite(relayPin, HIGH);

  pinMode(encoderPinA, INPUT_PULLUP);
  pinMode(encoderPinB, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(encoderPinA), readEncoder, CHANGE);

  lcd.begin(16, 2);
  lcd.print("Cutting Machine");
  delay(1500);
  lcd.clear();
}

// =======================
// Loop
// =======================
void loop() {
  // Print encoder value occasionally for debug
  static unsigned long lastSerial = 0;
  if (millis() - lastSerial > 500) {
    lastSerial = millis();
    Serial.println(safeReadEncoder());
  }

  // Use an atomic read for logic
  long currentEncoder = safeReadEncoder();

  if (!running) {
    // MENU
    lcd.setCursor(0, 0);
    clearLine(0); // ensure top line is clean
    if (menuStage == 0) {
      lcd.setCursor(0,0);
      lcd.print("Len(mm):");
      lcd.print(pieceLength);
      // fill to avoid remnants
      int used = 8 + String(pieceLength).length();
      for (int i = used; i < 16; ++i) lcd.print(' ');
      lcd.setCursor(0,1);
      clearLine(1);
      lcd.setCursor(0,1);
      lcd.print("Set piece length");
    } 
    else if (menuStage == 1) {
      lcd.setCursor(0,0);
      lcd.print("Qty:");
      lcd.print(pieceQuantity);
      for (int i = 4 + String(pieceQuantity).length(); i < 16; ++i) lcd.print(' ');
      lcd.setCursor(0,1);
      clearLine(1);
      lcd.setCursor(0,1);
      lcd.print("Set total pieces");
    }
    else if (menuStage == 2) {
      lcd.setCursor(0,0);
      lcd.print("mm/tick:");
      lcd.print(mmPerTick, 2);
      for (int i = 8 + 4; i < 16; ++i) lcd.print(' ');
      lcd.setCursor(0,1);
      clearLine(1);
      lcd.setCursor(0,1);
      lcd.print("Calibrate encoder");
    }

    int key = readLCDKey();
    if (key == 1) { // Up
      if (menuStage == 0) pieceLength += 10;
      else if (menuStage == 1) pieceQuantity++;
      else if (menuStage == 2) mmPerTick += 0.01;
      delay(150);
    } 
    else if (key == 2) { // Down
      if (menuStage == 0 && pieceLength > 10) pieceLength -= 10;
      else if (menuStage == 1 && pieceQuantity > 1) pieceQuantity--;
      else if (menuStage == 2 && mmPerTick > 0.01) mmPerTick -= 0.01;
      delay(150);
    } 
    else if (key == 0 || key == 3) { // Right/Left switch menu
      menuStage = (menuStage + 1) % 3;
      lcd.clear();
      delay(150);
    } 
    else if (key == 4) { // Select = Start job
      lcd.clear();
      lcd.print("Starting..");
      delay(800);

      // Safe reset of counters while interrupts are disabled
      noInterrupts();
      encoderCount = 0;
      lastEncoderCount = 0;
      interrupts();

      digitalWrite(relayPin2, HIGH); // start machine
      running = true;
      producedPieces = 0;
      lcd.clear();
      delay(200);
    }
    // small loop delay
    delay(100);
  } 
  else {
    // RUNNING
    // Read encoder atomically
    currentEncoder = safeReadEncoder();

    lcd.setCursor(0,0);
    clearLine(0);
    lcd.setCursor(0,0);
    lcd.print("Cutting ");
    lcd.print(producedPieces);
    lcd.print("/");
    lcd.print(pieceQuantity);

    // compute traveled distance since last piece
    long deltaTicks = currentEncoder - lastEncoderCount;
    float traveled = fabs((float)deltaTicks) * mmPerTick;

    lcd.setCursor(0,1);
    clearLine(1);
    lcd.setCursor(0,1);
    lcd.print("Len:");
    lcd.print(traveled, 1);
    lcd.print("/");
    lcd.print(pieceLength);

    // check if we've reached the length for a piece
    if (traveled >= pieceLength) {
      // Trigger relay2 (stop/start motor) and relay (cut)
      digitalWrite(relayPin2, LOW);      // pause motor
      delay(1000);                        // small pause
      // cutting relay pulse
      digitalWrite(relayPin, LOW);
      delay(500);
      digitalWrite(relayPin, HIGH);
      delay(1000);

      // resume motor
      digitalWrite(relayPin2, HIGH);
      delay(100);

      producedPieces++;

      // set lastEncoderCount to current encoder reading (atomic)
      noInterrupts();
      lastEncoderCount = encoderCount;
      interrupts();

      if (producedPieces >= pieceQuantity) {
        // job finished
        running = false;
        digitalWrite(relayPin2, LOW); // stop motor
        lcd.clear();
        lcd.print("Job Finished!");
        delay(1500);
        lcd.clear();
      }
    }
    // small loop delay to avoid LCD flooding
    delay(100);
  }
}

Result still the same..first piece is the longest, second and third are the same length but quite shorter than the first), fourth comes a bit longer than second and third..and so on..pretty much the same pattern everytime..my best guess now is a coding problem.

Why are you trying to use an optoisolator and an inverter?
That won't solve anything.

Your main problem is that you have no idea what the encoder actually outputs and how much current the outputs can handle if any.

First you need to see if the encoder is outputting a voltage on the A/B pins. Connect up the 12V and ground and monitor one of the outputs with a scope. Turn the encoder and see if you get any pulses. Measure the pulse amplitude.

SOLVEDD!!! :grinning_face: to anyone who might try something similar someday..i tested with a small encoder (from arduino starter kit) and i noticed that the values (milimeters) on my LCD are not adding up the way they should. Sometimes one rotation increased the length by 5mm sometimes by 25mm for example. So i added some debouncing in my code:

volatile unsigned long lastEncoderTime = 0;
const unsigned long debounceTime = 74 
 if (now - lastEncoderTime < debounceTime) return;
  lastEncoderTime = now;

Played around with the debounce time a bit and the pieces started to fall nice and even and most importantly of the same length :smiley:

Certanly an interesting and unexpected solution but now the system works like a charm.

Thank you for your time and responses.

Best of luck in your future endeavours guys!

The only issue is that optical encoders don't bounce.
So it's not clear what you solved.