Rotary Encoder to Linear Translation

Hello all,

I am writing an Arduino program to track rotary motion and translate to linear distance.

I am taking this linear measurement and storing it on an SD card and printing it to a 16x2 LCD display.

Everything seems to be working fine, except that if I rotate the encoder semi-fast, the value doesnt update.

Im assuming this works on some type of hall effect sensor, so I can understand why this would happen, however it seems like it isnt very constant. When rotating the sensor clockwise, for example, it seems to catch at lower rotational speeds as opposed to ccw motion.

I’m not sure if there is an error in my code causing it to catch, or if I have a bad encoder, wiring setup, or loose connections.

All connections SEEM fine, sometimes the seems encoder works better if I push it harder into the breadboard (could be my imagination, though - and the trend with cw being worse then ccw still continues).

Can anyone help confirm if it is my code, something hardware related, or if this is non avoidable using a rotary encoder?

My wiring: CLK and DT are plugged into digital pins, BTN (SW) is not wired.

CODE:

#include <SD.h>
#include <LiquidCrystal_I2C.h>

#define outputA 6
#define outputB 7
#define pitchDiameter 0.56229722 //Inches

float counter = 0.0; 
int aState;
int aLastState; 

LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display

//Constant for sd card
const int chipSelect = 53;

void setup() {
  //Initialize LCD
  lcd.init();
  lcd.backlight();
  
  //Begins SD Card Output
  pinMode(53, OUTPUT);
  digitalWrite(53, HIGH);

  //Establish pins for encoder
  pinMode (outputA,INPUT);
  pinMode (outputB,INPUT);

  aLastState = digitalRead(outputA);  
  
  Serial.begin(9600);   

  lcd.setCursor(0,0);
  lcd.print("Position: ");

  // SD initialization
  if (!SD.begin(chipSelect))
  {
    Serial.println("Card failed, or not present.");
  } 
  else
  {
    Serial.println("Card initialized");
  }
}

void loop() {
  aState = digitalRead(outputA); // Reads the "current" state of the outputA
  // If the previous and the current state of the outputA are different, that means a Pulse has occured
  if (aState != aLastState)
  {     
    // If the outputB state is different to the outputA state, that means the encoder is rotating clockwise
    if (digitalRead(outputB) != aState)
    { 
      counter += 0.07853981633 * pitchDiameter;
    } 
    else 
    {
      counter -= 0.07853981633 * pitchDiameter;
    }
    Serial.print("Position: ");
    Serial.println(counter);

    //Print position to LCD
    lcd.setCursor(0,0);
    lcd.print("Position: ");
    lcd.print("       ");
    lcd.setCursor(10,0);
    lcd.print(counter);
    lcd.setCursor(0,1);
    lcd.print("Inches");
  } 
  aLastState = aState; // Updates the previous state of the outputA with the current state
  //Add position to data string
  String dataString = ""; 
  dataString += String("Position: ");
  dataString += String(counter);
  
  //Open datafile 
  File dataFile = SD.open("DataLog.txt", FILE_WRITE);

  //If the datafile is available, write to it:
  if (dataFile) 
  {
    dataFile.println(dataString);
    dataFile.close();
  } 
  //If the file isn't open, pop up an error:
  else 
  {

  }
}

Can anyone help confirm if it is my code, something hardware related, or if this is non avoidable using a rotary encoder?

It is possible to read encoders that are rotating very fast, but NOT by polling the encoder pins. You need to connect the encoder to external interrupt pins, and define an interrupt service routine to be called when the pin(s) change state. Or, use the Encoder library which already does that.

Hello PaulS!

First off, thank you for your time! I am kind of a noob, and not sure how I would go about the first option you had listed, how easy would the second option be, just different function/object calls?

What calls from the library could I use?

Thanks again!
Matt

The Encoder library comes with examples to get you started.

It would be helpful if you posted a click-able link to the encoder you’re using.

What calls from the library could I use?

Look at the examples. The only ones that really are useful are the one that tells you that a change has happened since you last asked, or not, and the one that tells you the current encoder position/value.

Im not sure exactly what library to include - it appears there are no examples on my board for an encoder by default, either.

I found an example online of using interrupts as PaulS had suggested, I have it wired as described in the example, and have ensured that I am wired to the PIND array (2 and 3) of my mega.

I run this code and literally nothing happens (Im assuming the if case of the loop is not triggering).

Not sure what is going wrong - the people who commented on this post raved about the code.

Any thoughts? And thank you guys, again!

PS (Encoder from this kit): https://amzn.to/2Xri6gW

#include <SD.h>
#include <LiquidCrystal_I2C.h>

static int pinA = 2; // Our first hardware interrupt pin is digital pin 2
static int pinB = 3; // Our second hardware interrupt pin is digital pin 3
volatile byte aFlag = 0; // let's us know when we're expecting a rising edge on pinA to signal that the encoder has arrived at a detent
volatile byte bFlag = 0; // let's us know when we're expecting a rising edge on pinB to signal that the encoder has arrived at a detent (opposite direction to when aFlag is set)
volatile byte encoderPos = 0; //this variable stores our current value of encoder position. Change to int or uin16_t instead of byte if you want to record a larger range than 0-255
volatile byte oldEncPos = 0; //stores the last encoder position value so we can compare to the current reading and see if it has changed (so we know when to print to the serial monitor)
volatile byte reading = 0; //somewhere to store the direct values we read from our interrupt pins before checking to see if we have moved a whole detent

void setup() 
{
 pinMode(pinA, INPUT_PULLUP); // set pinA as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
 pinMode(pinB, INPUT_PULLUP); // set pinB as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
 attachInterrupt(0,PinA,RISING); // set an interrupt on PinA, looking for a rising edge signal and executing the "PinA" Interrupt Service Routine (below)
 attachInterrupt(1,PinB,RISING); // set an interrupt on PinB, looking for a rising edge signal and executing the "PinB" Interrupt Service Routine (below)
 Serial.begin(115200); // start the serial monitor link
}

void PinA()
{
 cli(); //stop interrupts happening before we read pin values
 reading = PIND & 0xC; // read all eight pin values then strip away all but pinA and pinB's values
 if(reading == B00001100 && aFlag)
 { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
   encoderPos --; //decrement the encoder's position count
   bFlag = 0; //reset flags for the next turn
   aFlag = 0; //reset flags for the next turn
 }
 else if (reading == B00000100) bFlag = 1; //signal that we're expecting pinB to signal the transition to detent from free rotation
 sei(); //restart interrupts
}

void PinB()
{
 cli(); //stop interrupts happening before we read pin values
 reading = PIND & 0xC; //read all eight pin values then strip away all but pinA and pinB's values
 if (reading == B00001100 && bFlag) 
 { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
   encoderPos ++; //increment the encoder's position count
   bFlag = 0; //reset flags for the next turn
   aFlag = 0; //reset flags for the next turn
 }
 else if (reading == B00001000) aFlag = 1; //signal that we're expecting pinA to signal the transition to detent from free rotation
 sei(); //restart interrupts
}

void loop()
{
 if(oldEncPos != encoderPos) 
 {
   Serial.println(encoderPos);
   oldEncPos = encoderPos;
 }
}

Did you try googling "encoder library arduino"? The first hit is a foolproof library for dealing with encoders, written by someone that knows far more about writing code than whoever wrote that mess you found.

Paul,

Thank you for your advice - I found an encoder library that worked wonderfully, and I agree - much neater then that mess.

Thank you very much!
Matt