Issues reading rotary encoder

Hello,
I am having trouble reading a basic rotary encoder and cannot find anything on the Web.
I need to rotate a stepper motor in a 1:1 ratio with some sort of knob and have chosen a 20 detent rotary encoder because I have tried a 10k potentiometer (as in https://www.arduino.cc/en/Tutorial/MotorKnob ) and have failed. The pot was a Bourns 82A1AC28BA0728 (https://www.allelectronics.com/item/ptw-10k/10k-bourns-pot-pre-wired/1.html)
I am using an Uno for testing but plan to ultimately put it on a Pro Mini (5v, 16MHz).
I have tried using it with Paul Stoffregen’s Encoder library and it did nothing, as opposed to rotating the stepper motor.

#include <Stepper.h>
#include <Encoder.h>
#define STEPS 200

int myStep;
int previous = 0;
int myFactor = 10;                           //# of steppper steps to one encoder step
int encoderSteps = 20;                       //# of steps in encoder
int lethalDistance = 40;                     //distance in encoder steps that is "too far"
int overturnCounter = 0;                     //counter to prevent more than two rotations
                                             //that would rip the coax.
Stepper stepper(STEPS, 8, 9, 10, 11);
Encoder encoder(2, 3);

void setup() {
  // put your setup code here, to run once:
  stepper.setSpeed(90);                       //slow down once working right

}

void loop() {
  // put your main code here, to run repeatedly:
  myStep = encoder.read();
  if(myStep =! previous){
    stepper.step(myStep - previous);
    overturnCounter = myStep;
    previous = myStep;
  }
  if(overturnCounter >= lethalDistance){
    stepper.step(0 - STEPS);
    previous = previous - STEPS;
  }
  if(overturnCounter <= (0 - lethalDistance)){
    stepper.step(STEPS);
    previous = previous + STEPS;
  }
  

}

Since that failed, I looked at the disc that came with the encoder. The encoder I have is one with a push button that came in a RobotLinking 37 sensor kit (https://www.ebay.com/i/222173477724?chn=ps&dispItem=1, I don't know where else to find it) and has 5 pins, namely GND, VCC, CLK, DT, and SW. I have wired it according to the instructions: GND-GND VCC-5V CLK-2 DT-3 SW-4.

const int interruptA = 0;       // Interrupt 0 
const int interruptB = 1;       // Interrupt 1 

int CLK = 2;     // PIN2
int DAT = 3;     // PIN3
int BUTTON = 4;  // PIN4
int LED1 = 5;    // PIN5
int LED2 = 6;    // PIN6
int COUNT = 0;

void setup()
{
  attachInterrupt(interruptA, RoteStateChanged, FALLING);
  // attachInterrupt(interruptB, buttonState, FALLING);
  pinMode(CLK, INPUT);
  digitalWrite(2, HIGH);  // Pull High Restance
  pinMode(DAT, INPUT);
  digitalWrite(3, HIGH);  // Pull High Restance
  pinMode(BUTTON, INPUT);
  digitalWrite(4, HIGH);  // Pull High Restance
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  Serial.begin(9600);
}

void loop()
{
  if  (!(digitalRead(BUTTON)))
  {
    COUNT = 0;
    Serial.println("STOP COUNT = 0");
    digitalWrite(LED1, LOW);
    digitalWrite(LED2, LOW);
    delay (2000);
  }
  Serial.println(COUNT);
}

//-------------------------------------------
void RoteStateChanged() //When CLK  FALLING READ DAT
{
  if  (digitalRead(DAT)) // When DAT = HIGH IS FORWARD
  {
    COUNT++;
    digitalWrite(LED1, HIGH);
    digitalWrite(LED2, LOW);
    delay(20);
  }
  else                   // When DAT = LOW IS BackRote
  {
    COUNT--;
    digitalWrite(LED2, HIGH);
    digitalWrite(LED1, LOW);
    delay(20);
  }
}

It compiles, but returns a solid string of “COUNT = 0” to the Serial Monitor. COUNT was supposed to change with the rotation of the encoder.

Does anybody see what I am doing wrong or a better way to do this?

Or, do I simply need a different rotary encoder?

Ass-uming you have verified that the encoder is putting out the desired signals there are a few things you could improve - and one is imperative. Namely, add the 'volatile' qualifier to your COUNT declaration.

Next, there are two violations of the principle that an ISR (Interrupt Service Routine) should be short and sweet. First, consider moving the digitalWrites in the ISR to their own function which is called by a *change * in COUNT. Second, why the delay() in the ISR?

Thank you for replying,

I added the volatile qualifier and commented out the delays. I also commented out the digitalWrites as they apply to LEDs that I see as an alternative way of telling me that it is working. It still does a constant string of zeros.

const int interruptA = 0;       // Interrupt 0 
const int interruptB = 1;       // Interrupt 1 

int CLK = 2;     // PIN2
int DAT = 3;     // PIN3
int BUTTON = 4;  // PIN4
//int LED1 = 5;    // PIN5
//int LED2 = 6;    // PIN6
volatile int COUNT = 0;

void setup()
{
  attachInterrupt(digitalPinToInterrupt(interruptA), RoteStateChanged, FALLING);
  //attachInterrupt(digitalPinToInterrupt(interruptB), buttonState, FALLING);
  pinMode(CLK, INPUT);
  digitalWrite(2, HIGH);  // Pull High Restance
  pinMode(DAT, INPUT);
  digitalWrite(3, HIGH);  // Pull High Restance
  pinMode(BUTTON, INPUT);
  digitalWrite(4, HIGH);  // Pull High Restance
  //pinMode(LED1, OUTPUT);
  //pinMode(LED2, OUTPUT);
  Serial.begin(9600);
}

void loop()
{
  if  (!(digitalRead(BUTTON)))
  {
    COUNT = 0;
    Serial.println("STOP COUNT = 0");
    //digitalWrite(LED1, LOW);
    //digitalWrite(LED2, LOW);
    delay (2000);
  }
  Serial.println(COUNT);
}

//-------------------------------------------
void RoteStateChanged() //When CLK  FALLING READ DAT
{
  if  (digitalRead(DAT)) // When DAT = HIGH IS FORWARD
  {
    COUNT++;
    //digitalWrite(LED1, HIGH);
    //digitalWrite(LED2, LOW);
    //delay(20);
  }
  else                   // When DAT = LOW IS BackRote
  {
    COUNT--;
    //digitalWrite(LED2, HIGH);
    //digitalWrite(LED1, LOW);
    //delay(20);
  }
}

Doug, you said

dougp:
Ass-uming you have verified that the encoder is putting out the desired signals.

I haven't. How do you do that?

A first check would be to determine which pins on the encoder are common, chA, and chB. Verify that chA and chB go on and off as the shaft is turned. Then, to see the phase relationship between chA and chB connect each separately to an LED/limiting resistor and apply power. You should see the LEDs light in a quadrature sequence as the shaft is turned.

Thank you. The encoder is a 20 detent encoder, and when it is stopped (at a detent) it is stable, with both LEDs off: (0,0). However, when I am between detents, it goes through the quadrature sequence: (1,0); (1,1); (0,1); depending on where I am, and then I'm at the next detent with (0,0).

Should it be at 0 at the detent or should it be in a stage of the sequence at the detent? I can't tell from the video because his encoder doesn't look like it does clicks or detents.

This.

Delta_G:
Some encoders have all four transitions between detents, some have 2 and some only have 1. It just depends on the encoder.

The essential thing is that you see the correct sequence on the LEDs.

You show interrupts A (clk) and B (dat) attached to pins 2 and 3 - standard UNO connections from what I've seen. This would have both encoder pins providing interrupts.

However, attach interruptB is commented out. Is interruptB required for the encoder code you're using? Even if it were not disabled, the ISR named doesn't exist in your sketch. Confusing.

Here is a basic Encoder with tachometer bonus Let me know if this works I can show you how to increase the resolution by 4 if you need it.

#define ClockPin 2 // Must be pin 2 or 3
#define DataPin 3 // can be any other pin
      // My Encoder has 400 Clock pulses per revolution
      // note that 150000.0 = (60 seonds * 1000000 microseconds)microseconds in a minute / 400 pulses in 1 revolution)
      // change the math to get the proper multiplier for RPM for your encoder
#define Multiplier 150000.0 // don't forget a decimal place to make this number a floating point number
volatile long count = 0;
volatile long EncoderCounter = 0;
volatile float SpeedInRPM = 0;

void onPin2CHANGECallBackFunction(){ 
    static uint32_t lTime; // Saved Last Time of Last Pulse
    uint32_t cTime; // Current Time
    cTime = micros(); // Store the time for RPM Calculations
    int32_t dTime; // Delt in time

// Encoder Code
    bool DataPinVal = digitalRead(DataPin);
// We know pin 2 just went high to trigger the interrupt
// depending on direction the data pin will either be high or low
    EncoderCounter += (DataPinVal) ? 1 : -1; // Should we step up or down?
// End Encoder Code

// calculate the DeltaT between pulses
    dTime = cTime - lTime; 
    lTime = cTime;
    SpeedInRPM = Multiplier / ((DataPinVal) ? dTime: (-1 * dTime)); // Calculate the RPM Switch DeltaT to either positive or negative to represent Forward or reverse RPM
}



void eCode(bool x){
  if(x) {
    count ++;
  } else {
    count --;
  }
}
void setup() {
  Serial.begin(115200); //115200
  // put your setup code here, to run once:
  pinMode(ClockPin, INPUT);  
  pinMode(DataPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(ClockPin),onPin2CHANGECallBackFunction,RISING);
}

void loop() {
  long Counter;
  float Speed;
  noInterrupts (); 
// Because when the interrupt occurs the EncoderCounter and SpeedInRPM could be interrupted while they 
// are being used we need to say hold for a split second while we copy these values down. This doesn't keep the 
// interrupt from occurring it just slightly delays it while we maneuver values.
// if we don't do this we could be interrupted in the middle of copying a value and the result get a corrupted value.
  Counter = EncoderCounter;
  Speed = SpeedInRPM;
  interrupts ();

// use the speed and counter values for whatever you need to do.

  static unsigned long SpamTimer;
  if ( (unsigned long)(millis() - SpamTimer) >= (100)) {
    SpamTimer = millis();
    Serial.print(Counter );
    Serial.print("\t");
    Serial.print(Speed , 3);
    Serial.print(" RPM");
    Serial.println();
    SpeedInRPM = 0; // if no pulses occure in the next 100 miliseconds then we must assume that the motor has stopped
  }
}

Also note that you may experience switch bouncing on the mechanical switch adding simple Capacitors across the clock and data pins to ground solve this
Encoder.jpg
Z

Thank you everyone,

Doug: I agree that it is confusing. It is the code that came with the encoder, apparently written by a monkey at a typewriter. I was hopeful I could salvage it.

Zhomeslice: Thank you very much for the code, it is much better than what I had to start with. I didn't understand what was meant by "clock pulses" regarding Multiplier, and the Serial commands were telling me that the encoder was moving when it was not. Should I have put capacitors between the Arduino and the encoder?

I have taken the code and messed with it:

#include <Stepper.h>

#define ClockPin 2 // Must be pin 2 or 3

#define DataPin 3 // can be any other pin

      // My Encoder has 400 Clock pulses per revolution

      // note that 150000.0 = (60 seonds * 1000000 microseconds)microseconds in a minute / 400 pulses in 1 revolution)

      // change the math to get the proper multiplier for RPM for your encoder

//#define Multiplier 1.0 // don't forget a decimal place to make this number a floating point number

#define SSTEPS 200                    //Stepper steps

#define ESTEPS 20                     //Encoder steps

float stepCompensation = SSTEPS / ESTEPS;

volatile long count = 0;

volatile long EncoderCounter = 0;

volatile float SpeedInRPM = 0;

volatile long oldCounter = 0;

volatile long var;


Stepper stepper(SSTEPS, 8, 9, 10, 11);


void onPin2CHANGECallBackFunction(){

    static uint32_t lTime; // Saved Last Time of Last Pulse

    uint32_t cTime; // Current Time

    cTime = micros(); // Store the time for RPM Calculations

    int32_t dTime; // Delt in time


// Encoder Code

    bool DataPinVal = digitalRead(DataPin);

// We know pin 2 just went high to trigger the interrupt

// depending on direction the data pin will either be high or low

    EncoderCounter += (DataPinVal) ? 1 : -1; // Should we step up or down?

// End Encoder Code

/*
// calculate the DeltaT between pulses

    dTime = cTime - lTime;

    lTime = cTime;

    SpeedInRPM = Multiplier / ((DataPinVal) ? dTime: (-1 * dTime)); // Calculate the RPM Switch DeltaT to either positive or negative to represent Forward or reverse RPM
*/
}




void eCode(bool x){

  if(x) {

    count ++;

  } else {

    count --;

  }

}

void setup() {

  Serial.begin(115200); //115200

  // put your setup code here, to run once:

  pinMode(ClockPin, INPUT);  

  pinMode(DataPin, INPUT);

  attachInterrupt(digitalPinToInterrupt(ClockPin),onPin2CHANGECallBackFunction,RISING);

  stepper.setSpeed(90);                           //drop lower for actual use

}


void loop() {

  long Counter;

  float Speed;

  noInterrupts ();

// Because when the interrupt occurs the EncoderCounter and SpeedInRPM could be interrupted while they

// are being used we need to say hold for a split second while we copy these values down. This doesn't keep the

// interrupt from occurring it just slightly delays it while we maneuver values.

// if we don't do this we could be interrupted in the middle of copying a value and the result get a corrupted value.

  Counter = EncoderCounter;

  //Speed = SpeedInRPM;

  interrupts ();


// use the speed and counter values for whatever you need to do.


  static unsigned long SpamTimer;

  if ( (unsigned long)(millis() - SpamTimer) >= (100)) {

    SpamTimer = millis();

    Serial.print(Counter );

    Serial.print("t");

    Serial.print(Speed , 3);

    Serial.print(" RPM");

    Serial.println();

    SpeedInRPM = 0; // if no pulses occure in the next 100 miliseconds then we must assume that the motor has stopped

    oldCounter = Counter;

  }

  if (oldCounter =! Counter) {
    var = Counter - oldCounter;
    stepper.step(var * stepCompensation);
    oldCounter = Counter;
  }

}

This doesn't work either. The stepper motor spins for a little while and then either I turn it off or it turns itself off, either way it stops moving. I don't know if this has to do with the fact that I commented out Multiplier and everything that has to do with SpeedInRPM. Does anyone know what I did to the code that messed it up?