Rotary encoder code for Mega 2560

I am trying to adapt the following code for Mega, as it was originally written for, and works well with, Uno: it interfaces with a KY-040 rotary encoder to simply increments/decrement a counter based on a clockwise/counterclockwise rotation of the rotary encoder.

/*******Interrupt-based Rotary Encoder Sketch*******
by Simon Merrett, based on insight from Oleg Mazurov, Nick Gammon, rt, Steve Spence
modified at EE to include the select switch
Tutorial at:
http://exploreembedded.com/wiki/Interactive_Menus_for_your_project_with_a_Display_and_an_Encoder
*/

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
static int selectSwitch = 9; //The select switch for our encoder.

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 uint16_t 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 uint16_t 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)
  pinMode(selectSwitch, INPUT_PULLUP);
  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;
  }

  // The select switch is pulled high, hence the pin goes low if the switch is pressed. 
  if(digitalRead(selectSwitch)==0)
  {
    Serial.println("Key Pressed");
    delay(5); // wait for debounce to get over
  }
}

After countless research, I read that the pinout for hardware interrupts, which this code absolutely requires, for Mega is different from Uno. So, if we are to use pins 2 and 3 as this code suggests on the Mega, we have to change this line

  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)

to this

  attachInterrupt(4,PinA,RISING); // set an interrupt on PinA, looking for a rising edge signal and executing the "PinA" Interrupt Service Routine (below)
  attachInterrupt(5,PinB,RISING); // set an interrupt on PinB, looking for a rising edge signal and executing the "PinB" Interrupt Service Routine (below)

Which I did, and still, the only thing that works is the rotary encoder's button -- the Mega absolutely does not register any rotating of the encoder (no LED on TX is observed).

Since I am new to Arduino, something tells me I am missing some concepts here, particularly where those if/else statements relate to those Hexs and binaries (see void functions PinA and PinB)...

Can someone please help point me in the right direction?

Just use the encoder library

Thanks -- I did look into this but just for my understanding, why is what I did not working?

Instead of interrupt numbers use digitalPinToInterrupt().
The Mega also has interrupts on pin 2 and 3 as on Uno. This is where you connect the external signals. The internal pin numbers (port and mask) may differ!!!

I found this other forum.

Excellent ideas from @DKWatson.

We generalize in this way:

#ifdef __AVR_ATmega2560__
    #define	PIN		PINE
	#define	ABMASK	0b00110000
	#define AMASK	0b00010000
	#define BMASK	0b00100000
#endif

#ifdef __AVR_ATmega328P__
    #define	PIN		PIND
	#define	ABMASK	0b00001100
	#define AMASK	0b00000100
	#define BMASK	0b00001000
#endif

so that the PinA(), PinB() functions become

void PinA()
{
	cli();
	reading = PIN & ABMASK;
	if(reading == ABMASK && aFlag) 
	{
		encoderPos ++;
		bFlag = 0;
		aFlag = 0;
	}
	else if (reading == AMASK) bFlag = 1;
	sei();
}

void PinB()
{
	cli();
	reading = PIN & ABMASK;
	if (reading == ABMASK && bFlag) 
	{
		encoderPos --;
		bFlag = 0;
		aFlag = 0;
	}
	else if (reading == BMASK) aFlag = 1;
	sei();
}

This works!

i thought that in general you only need one interrupt. it may be CHANGE or simply RISING. if RISING the direction is determined from the state of the other encode pin. if CHANGE, both pins need to be looked at and it provides an extra step of resolution

I have never used a Mega 2560 but I do use the same interrupt code for most of the MCUs I use, adapting as required.

Had a quick look at the pinout and assignments for the Mega 2560.

Int0 and Int1 uses pins 20 and 21 respectively, which is PortD pins 0 and 1. Those pins are also used for SDA and SCL for I2C. If you want to use them you will need to change the pins the interrupts look at.

Int2 and Int3 uses pins 19 and 18 while also using PortD pins 2 and 3, the same as Int0 and Int1 for the UNO.

So, if you use Int2 and Int3 on pins 18 and 19 the code should work as is.

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