Awesome Optical encoder works on UNO but not LEO

Hi there!

I'm experimenting with an optical encoder from a HP printer:

It works perfectly on the UNO, giving me about 7400 values per rotation, with this code:

const byte encoderPinA = 2;
const byte encoderPinB = 3;
volatile int count = 0;
int protectedCount = 0;
int previousCount = 0;
int val1 = 1280;
int val2 = 1280;

#define readA bitRead(PIND,2)
#define readB bitRead(PIND,3)
void setup() {
  Serial.begin (57600); // I use 9600 for the UNO though

  pinMode(encoderPinA, INPUT);
  pinMode(encoderPinB, INPUT);
 
  attachInterrupt(digitalPinToInterrupt(encoderPinA), isrA, CHANGE);
  attachInterrupt(digitalPinToInterrupt(encoderPinB), isrB, CHANGE);
}

void loop() {
  noInterrupts();
  protectedCount = count;
  
 interrupts();
 
  if(protectedCount != previousCount) {
    Serial.print("prtcnt:");
    Serial.println(protectedCount);

  }
  previousCount = protectedCount;
}

void isrA() {
  if(readB != readA) {
    count ++;
  } else {
    count --;
  }
}
void isrB() {
  if (readA == readB) {
    count ++;
  } else {
    count --;
  }
}

But when write the exact same code to the LEO, it does not seem to react at all until I rotate the disc in a particular way. And it counts only up (or down if I switch the cables on the input).
From what I can find, pin 0 and 1 or 2 and 3 should work the same way on both units, right?

What can be the problem? Cheapo LEO?

//Chris

bitRead(PIND,2)
Should be right too, right?
Can’t find anything that indicates that it is mismatch with the code.. Then again, I am new to this and ruthlessly copy paste stuff...

help...

Chris_Beeves:
bitRead(PIND,2)
Should be right too, right?

No. The pin mapping on the Leonardo is different from the Uno:
https://www.arduino.cc/en/Hacking/PinMapping32u4
PD2 is Arduino pin 0 on the Leonardo and PD3 is pin 1 on the Leonardo.

So (PIND,2) refers to PD2, and not bank d digital in 2?

Ok, so I tried changing the code to this:

const byte encoderPinA = 0;// Pin 0 on Leonardo is PD2
const byte encoderPinB = 1;// Pin 1 on Leonardo is PD3
volatile int count = 0;
int protectedCount = 0;
int previousCount = 0;

#define readA bitRead(PIND,2)// Pin 0 on Leonardo is PD2
#define readB bitRead(PIND,3)// Pin 1 on Leonardo is PD3

void setup() {
  Serial.begin (57600);

  pinMode(encoderPinA, INPUT);
  pinMode(encoderPinB, INPUT);
 
  attachInterrupt(digitalPinToInterrupt(encoderPinA), isrA, CHANGE);
  attachInterrupt(digitalPinToInterrupt(encoderPinB), isrB, CHANGE);
}

void loop() {
  noInterrupts();
  protectedCount = count;
  
 interrupts();
 
  if(protectedCount != previousCount) {
    Serial.print("prtcnt:");
    Serial.println(protectedCount);

  }
  previousCount = protectedCount;
}

void isrA() {
  if(readB != readA) {
    count ++;
  } else {
    count --;
  }
}
void isrB() {
  if (readA == readB) {
    count ++;
  } else {
    count --;
  }
}

After removing the _PULLUP I now get consistent readings, but instead of counting up to 23000-someting I just get this:

prtcnt:-1
prtcnt:-2
prtcnt:-1
prtcnt:-2
prtcnt:-1
prtcnt:0
prtcnt:-1
prtcnt:0
prtcnt:-1
prtcnt:0
prtcnt:-1
prtcnt:0
prtcnt:-1
prtcnt:0
prtcnt:-1

What is up with that?

Does anyone have an idea what more differences there can be between the Uno and the Leo?

Edit:
Also tried it on a cheap Uno clone, works perfect..

Tried it in a cheap Nano clone too and it works perfect there too..

For one thing, ext int pins are reversed on Leo, PIND,2 is pin 0 (RX) on Leo.


Ext int # 0 is PIND,0. Ext int #1 is PIND,1.

I can't see anything obviously wrong with your code. Are the grounds connected between the encoder and the Leonardo? Are you using Serial1 for anything on the Leonardo? Have you moved the encoder connections to 0 and 1?

Let's go back to basics, and see if you are actually seeing the encoder transitions on the Leonardo. Experiment with this code and confirm that you can see the pattern of the encoder
AB. 10>11>01>00 in one direction and 01>11>10>00 in the other. Try both sets of pins 0/1 and 2/3. Try replacing the digital reads with the bitRead() syntax. If all the basics are confirmed, then we can move on to the interrupt context. There may be an issue with this test code and the resolution of the encoder and picking up all the transitions. I've put a delay() in the code but the output will fly by.

#define encoderPinA 0 
#define encoderPinB 1

void setup() {
 Serial.begin (115200);

 pinMode(encoderPinA, INPUT_PULLUP);
 pinMode(encoderPinB, INPUT_PULLUP);

}

void loop() {
 byte a = digitalRead(encoderPinA);
 byte b = digitalRead(encoderPinB);
 if (a == 1)
   Serial.print("1");
 else
   Serial.print("0");
 if (b == 1)
   Serial.print("1");
 else
   Serial.print("0");
 Serial.println();
 delay(100);
}

Chris_Beeves:
So (PIND,2) refers to PD2, and not bank d digital in 2?

It’s referring to bit 2 of the PIND register. Low level code used to control the I/O pins on the microcontroller uses a system of port and bit. Each pin is identified by which port letter it’s on and which bit of that port is assigned to it. The shorthand for this on AVR chips looks like PD2, where “P” stands for “port”, “D” indicates port D, and “2” indicates bit 2.

Arduino decided that system was too confusing for beginners so they assign a number to each pin. The mapping between Arduino pin numbers and the low level PORT/bit notation is completely arbitrary. Don’t get mislead by the Uno’s pin mapping matching Arduino pin n to PDn for values of n 0-7. As you discovered, there is no guarantee that convention will be followed on other boards.

The code you’re using makes things especially confusing because you are using Arduino pin numbers for attachInterrupt() but then you’re also using low level code to read directly from the registers, which can not be done using Arduino pin numbers.

cattledog:
I can't see anything obviously wrong with your code. Are the grounds connected between the encoder and the Leonardo? Are you using Serial1 for anything on the Leonardo? Have you moved the encoder connections to 0 and 1?

The encoder is directly connected to the Arduino. 5v-5v, gnd-gnd, A-D0 & B-D1. Should I use a separate power supply?

I’m not using serial1 that I am aware of..

cattledog:
Let's go back to basics, and see if you are actually seeing the encoder transitions on the Leonardo. Experiment with this code and confirm that you can see the pattern of the encoder
AB. 10>11>01>00 in one direction and 01>11>10>00 in the other. Try both sets of pins 0/1 and 2/3. Try replacing the digital reads with the bitRead() syntax. If all the basics are confirmed, then we can move on to the interrupt context. There may be an issue with this test code and the resolution of the encoder and picking up all the transitions. I've put a delay() in the code but the output will fly by.

#define encoderPinA 0 

#define encoderPinB 1

void setup() {
Serial.begin (115200);

pinMode(encoderPinA, INPUT_PULLUP);
pinMode(encoderPinB, INPUT_PULLUP);

}

void loop() {
byte a = digitalRead(encoderPinA);
byte b = digitalRead(encoderPinB);
if (a == 1)
  Serial.print("1");
else
  Serial.print("0");
if (b == 1)
  Serial.print("1");
else
  Serial.print("0");
Serial.println();
delay(100);
}

Excellent, I’ll try it and get back to you!

pert:
It's referring to bit 2 of the PIND register. Low level code used to control the I/O pins on the microcontroller uses a system of port and bit. Each pin is identified by which port letter it's on and which bit of that port is assigned to it. The shorthand for this on AVR chips looks like PD2, where "P" stands for "port", "D" indicates port D, and "2" indicates bit 2.

Arduino decided that system was too confusing for beginners so they assign a number to each pin. The mapping between Arduino pin numbers and the low level PORT/bit notation is completely arbitrary. Don't get mislead by the Uno's pin mapping matching Arduino pin n to PDn for values of n 0-7. As you discovered, there is no guarantee that convention will be followed on other boards.

The code you're using makes things especially confusing because you are using Arduino pin numbers for attachInterrupt() but then you're also using low level code to read directly from the registers, which can not be done using Arduino pin numbers.

Thanks for clearing that up for me! :smiley:

cattledog:
Let's go back to basics, and see if you are actually seeing the encoder transitions on the Leonardo. Experiment with this code and confirm that you can see the pattern of the encoder
AB. 10>11>01>00 in one direction and 01>11>10>00 in the other. Try both sets of pins 0/1 and 2/3. Try replacing the digital reads with the bitRead() syntax. If all the basics are confirmed, then we can move on to the interrupt context. There may be an issue with this test code and the resolution of the encoder and picking up all the transitions. I've put a delay() in the code but the output will fly by.

I tried this with:
Genuino Uno - Works just the way you described
Fakeunio Onu - Works perfect
Fakeunio Nano - Works perfect

Velleman Leonardo:

11
11
11
11
10
11
11
11
11
01
11
11
11
11
10
10
01
11
11
11

and so on, in no apparent order except that 11 is the "standard state"

I tested the encoder with an oscilloscope, and noticed that the signals made much more sense when I ran the encoder on 3,3V. Hooked it up to the Leo, and VOILA!!
:smiley:

Fingers crossed!

Special thanks to cattledog!

Good news on finding the issue with the Leonardo.

There appear to be some differences in HIGH/LOW digital voltage transitions between the AT328 and 32U4. See this thread Leonardo Digital Pins Flip at 1.5/1.6 volts, not 2.4/2.5 like the uno! - Microcontrollers - Arduino Forum

Do you know if the encoder output is open collector? I so it requires pullups and I would expect to find them on the board. Do you know their value?

This may be a case, where there is some resistance in the ground path, and the voltage was not being pulled low enough.

cattledog:
Good news on finding the issue with the Leonardo.

There appear to be some differences in HIGH/LOW digital voltage transitions between the AT328 and 32U4. See this thread Leonardo Digital Pins Flip at 1.5/1.6 volts, not 2.4/2.5 like the uno! - Microcontrollers - Arduino Forum

Do you know if the encoder output is open collector? I so it requires pullups and I would expect to find them on the board. Do you know their value?

This may be a case, where there is some resistance in the ground path, and the voltage was not being pulled low enough.

That is some interesting reading indeed! Would explain a lot.
Thanks again :smiley:

I found this amusing:

elmerfudd:
Let's make sure these discrepancies are clearly advertised, or a beginners project could lead to frustration instead of joyous discovery. I can see a program relying on an interrupt failing miserably without this knowledge.

:grinning:

The output is from what I can gather just the IR receiver diode.
The "Oscilloscope" I used to read them was actually a Nano printing AnalogRead to serial port into a windows app. (The famous 5$ Oscilloscope) So I wouldn't really trust it for reading voltage, just give a hint about what the signals are up to.

There are a few resistors, but I think they are for the "index" channel in some way. I don't use that one.