Uno vs Mega with a rotary encoder

Morning programmers,
I have been playing around with a piece of code to use a rotary encoder with the Uno utilizing interrupts. With the uno it works perfect. When I transition the same code to the Mega it doesn't work. I looked up the interrupt pins that are available on the mega and have rotated through all three sets. (2/3, 18/19 and 20/21) I even tried 52/53 and that didnt work either. Is there something with the Mega and interrupts that I don't know to enable them somehow. Not sure how to phrase the question.
Here is the code unaltered from elsewhere on the net that works on Uno but not Mega:

const int pinA = 2; // Our first hardware interrupt pin is digital pin 2
const 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(9600); // start the serial monitor link
  Serial.println("-------------rotaryEncoderWorkup_v1.1-------------------");
  Serial.println(encoderPos);
}

void PinA(){ //from interrupt above
  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
    if  (encoderPos>18){encoderPos=18;}
    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(){  //from interrupt above
  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
    if (encoderPos>18){encoderPos=0;}
    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;
  }
}

Thank you for your expertise
amachinetech

update: l also tried a known second mega board just to make sure that the board wasn't damaged somehow so it isn't that

If you are only changing these values, you have not done enough to move the code to new pins.

The code uses the ports directly and is essentially hard coded to use pins 2 and 3 of port D.

To actually move to new pins, you need to change those constants and

  • read the correct port and
  • mask and test the right bits of that port
  reading = PIND & 0xC; // read all eight pin values then strip away all but pinA and pinB's values

That's bits 2 and 3 of port D.

Figure out or wait for someone to tell you which bits of which port are your alternate pins on the MEGA.

See port pin mapping for the MEGA

HTH

a7

Why use cli and sei ?

2023-02-26_12-46-40

2023-02-26_12-44-57

A bit more:
When I tried the other interrupt ports on the mega I changed the pin labels to reflect the pins that I attached the rotary encoder to.

I looked at the information that you provided and have found that pins 2 and 3 are connected to:
(OC3B/INT4) PE4 (pin 2)
&
(OC3C/INT5) PE5 (pin 3)

How do I alter [ reading = PIND & 0xC; ] to the new pin locations?

my guess: readiing = PINE & 0xC; although I think the last hex value is wrong
amachinetech

@ larryD
the way that it was explained to me was that the interrupts have to be stopped to allow the function to read the variables. Also I didn't write this one, I shamelessly cut and pasted it from a tutorial and attempted to bend it to my will. And as it works just fine with Uno, why would I fix something that isn't broken.
amachinetech

Interrupts are turn off when you go to your ISR.

Yeah, not quite as you guess.

PINE & B00110000

would be port E with everything stripped off except bits 4 and 5 (count starting with 0 from the left).

That's also

PINE & 0x30

Using binary looks easiest, you'll have to check other uses of hex or binary constants and adjust them accordianly, and look closley at the attach statements.

Srsly I'd do it for ya, but cannot test it anyway, so.

You on the right track. Looks like enough "common" sense should get you there if no one does in the meantime.

a7

image

Since Im not well traveled in this area of the IDE, the above line means "read the entire port but ignore everything except pins 4 and 5" correct? Or do I have it backwards and it should be

image

in which case it would read:
"read the entire port but ignore everything except pins 2 and 3"

or

i didn't understand it at all
Amachinetech

update: the first drawing is the correct one

Ouch! My bad as some might say.

Start counting from the right. First diagram in #8 is correct.

I owe you one. At least I got the constants right.

Sry sry sry.

a7

Here it is.

I used the preferred

  attachInterrupt(digitalPinToInterrupt(pinA), PinA, RISING);

otherwise your code, see it here in the wokwi simulator.

// https://wokwi.com/projects/357766586004638721
// https://forum.arduino.cc/t/uno-vs-mega-with-a-rotary-encoder/1095187

const int pinA = 2; // Our first hardware interrupt pin is digital pin 2
const 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)

  attachInterrupt(digitalPinToInterrupt(pinA), PinA, RISING);
  attachInterrupt(digitalPinToInterrupt(pinB), PinB, RISING);

  Serial.begin(9600); // start the serial monitor link
  Serial.println("-------------rotaryEncoderWorkup_v1.1-------------------");
  Serial.println(encoderPos);
}



void PinA(){ //from interrupt above
  cli(); //stop interrupts happening before we read pin values
  
  reading = PINE & 0x30; // read all eight pin values then strip away all but pinA and pinB's values
  if(reading == B00110000 && 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
    if  (encoderPos>18){encoderPos=18;}
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00010000) bFlag = 1; //signal that we're expecting pinB to signal the transition to detent from free rotation
  sei(); //restart interrupts
  
}

void PinB(){  //from interrupt above
  cli(); //stop interrupts happening before we read pin values
    reading = PINE & 0x30; //read all eight pin values then strip away all but pinA and pinB's values
  if (reading == B00110000 && 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
    if (encoderPos>18){encoderPos=0;}
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00100000) 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;
  }
}

BTW having pinA and PinA crossed my eyes quite a bit. Just sayin'.

HTH

a7

1 Like

I tested this and it works perfectly. As for the error I tried it both ways and I didn't get it to work so I thought it was something I was doing. Once again thank you for helping me understand the little quirks of this hobby we have decided to bash ourselves over the head with.

Cheers
amachinetech

I was trying to kinda lock in what I found looking at the Mega, and I noticed that pins 18 and 19 are on port D at bit numbers 2 and 3, so I figured that changing to those pins

const int pinA = 19;
const int pinB = 18;

and using (this is why!)

  attachInterrupt(digitalPinToInterrupt(pinA), PinA, RISING);
  attachInterrupt(digitalPinToInterrupt(pinB), PinB, RISING);

should mean the rest of the code would not need to be changed.

That didn't work... the first time I tried. After going in circles for awhile, it did. :expressionless:

a7

And until my beach buddy to knocks me up, she who must never be kept waiting, I took all the magic out of the code and moved it up top to show all three sets of pins can be used.

Test it here, please!

// https://wokwi.com/projects/357828229247849473
// https://forum.arduino.cc/t/uno-vs-mega-with-a-rotary-encoder/1095187

/*
// digital pins 2 and 3

# define PINX     PINE
# define maskAB   0x30
# define maskB    0x10
# define maskA    0x20

# define pinA     2
# define pinB     3
*/

/*
// digital pins 18 and 19

# define PINX     PIND
# define maskAB   0x0c
# define maskB    0x04
# define maskA    0x08

# define pinA     19
# define pinB     18
*/

/**/
// digital pins 20 and 21

# define PINX     PIND
# define maskAB   0x03
# define maskB    0x01
# define maskA    0x02

# define pinA     21
# define pinB     20
/**/

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(digitalPinToInterrupt(pinA),PinA,RISING); // set an interrupt on PinA, looking for a rising edge signal and executing the "PinA" Interrupt Service Routine (below)
  attachInterrupt(digitalPinToInterrupt(pinB),PinB,RISING); // set an interrupt on PinB, looking for a rising edge signal and executing the "PinB" Interrupt Service Routine (below)


  Serial.begin(9600); // start the serial monitor link
  Serial.println("-------------rotaryEncoderWorkup_v1.1-------------------");
  Serial.println(encoderPos);
}

void PinA(){ //from interrupt above

  cli(); //stop interrupts happening before we read pin values
  
  reading = PINX & maskAB; // read all eight pin values then strip away all but pinA and pinB's values
  if(reading == maskAB && 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
    if  (encoderPos>18){encoderPos=18;}
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == maskB) bFlag = 1; //signal that we're expecting pinB to signal the transition to detent from free rotation
  sei(); //restart interrupts
  
}

void PinB(){  //from interrupt above

  cli(); //stop interrupts happening before we read pin values
    reading = PINX & maskAB; //read all eight pin values then strip away all but pinA and pinB's values
  if (reading == maskAB && 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
    if (encoderPos>18){encoderPos=0;}
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == maskA) 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;
  }
}

The appropriate group of defines can be uncommented. I know it could be modernized and even made slick by old school standards, so knock yourselves out on that. I'm out the door.

a7

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.