Getting two rotary encoders to work on a Mega board

Good day all

I have a project where I require two rotary encoders to change the display on two i2c LCD's - one encoder per LCD. I'm using an Arduino Mega board.

While I get the first encoder to work flawlessly, I cannot get the second encoder to respond at all.

The code as it stands:

/*******Interrupt-based Rotary Encoder write to LCD*******


/********************************/
// include the library code
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
/**********************************************************/

LiquidCrystal_I2C lcd1(0x20,16,2);        // set the LCD1 address to 0x20 for a 16 chars and 2 line display
LiquidCrystal_I2C lcd2(0x21,16,2);        // set the LCD2 address to 0x20 for a 16 chars and 2 line display

static int RE1_pinA = 2;                  // Our first hardware interrupt pin is digital pin 2
static int RE1_pinB = 3;                  // Our second hardware interrupt pin is digital pin 3
volatile byte RE1_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 RE1_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 RE1_encoderPos = 250;   //this variable stores our current value of encoder position. Change to int or uint16_t instead of byte if you want to record a larger range than 0-255
volatile uint16_t RE1_oldEncPos = 250;    //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 LCD)
volatile uint16_t RE1_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

static int RE2_pinA = 18;                  // Our first hardware interrupt pin is digital pin 18
static int RE2_pinB = 19;                  // Our second hardware interrupt pin is digital pin 19
volatile byte RE2_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 RE2_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 RE2_encoderPos = 150;   //this variable stores our current value of encoder position. Change to int or uint16_t instead of byte if you want to record a larger range than 0-255
volatile uint16_t RE2_oldEncPos = 150;    //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 LCD)
volatile uint16_t RE2_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() 
{
  lcd1.init();                    //initialize the lcd
  lcd1.backlight();               //open the backlight
  lcd1.setCursor(0,0);
  lcd1.print("Encoder pos: ");
  lcd1.setCursor(13,0);
  lcd1.print(RE1_encoderPos);

  lcd2.init();                    //initialize the lcd
  lcd2.backlight();               //open the backlight
  lcd2.setCursor(0,0);
  lcd2.print("Encoder pos: ");
  lcd2.setCursor(13,0);
  lcd2.print(RE2_encoderPos);

  pinMode(RE1_pinA, INPUT_PULLUP);    // set pinA as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  pinMode(RE1_pinB, INPUT_PULLUP);    // set pinB as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)

  pinMode(RE2_pinA, INPUT_PULLUP);    // set pinA as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  pinMode(RE2_pinB, INPUT_PULLUP);    // set pinB as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  
  attachInterrupt(0,RE1_PinA,RISING); // set an interrupt on PinA (connected to pin 2 on MEGA), looking for a rising edge signal and executing the "PinA" Interrupt Service Routine (below)
  attachInterrupt(1,RE1_PinB,RISING); // set an interrupt on PinB (connected to pin 3 on MEGA), looking for a rising edge signal and executing the "PinB" Interrupt Service Routine (below)

  attachInterrupt(5,RE2_PinA,RISING); // set an interrupt on PinA (connected to pin 18 on MEGA), looking for a rising edge signal and executing the "PinA" Interrupt Service Routine (below)
  attachInterrupt(4,RE2_PinB,RISING); // set an interrupt on PinB (connected to pin 19 on MEGA), looking for a rising edge signal and executing the "PinB" Interrupt Service Routine (below)

  // Check Arduino board type to determine correct mask
  #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
}


void RE1_PinA()
{
  RE1_reading = PIN & ABMASK;                       // read all eight pin values then strip away all but pinA and pinB's values
  if(RE1_reading == ABMASK && RE1_aFlag)            //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
  {
    RE1_encoderPos --;                              //decrement the encoder's position count
    RE1_bFlag = 0;                                  //reset flags for the next turn
    RE1_aFlag = 0;                                  //reset flags for the next turn
  }
  else if (RE1_reading == AMASK) RE1_bFlag = 1;     //signal that we're expecting pinB to signal the transition to detent from free rotation
}


void RE1_PinB()
{
  RE1_reading = PIN & ABMASK;                       // read all eight pin values then strip away all but pinA and pinB's values
  if (RE1_reading == ABMASK && RE1_bFlag)           //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
  {
    RE1_encoderPos ++;                              //increment the encoder's position count
    RE1_bFlag = 0;                                  //reset flags for the next turn
    RE1_aFlag = 0;                                  //reset flags for the next turn
  }
  else if (RE1_reading == BMASK) RE1_aFlag = 1;     //signal that we're expecting pinA to signal the transition to detent from free rotation
}

void RE2_PinA()
{
  RE2_reading = PIN & ABMASK;                       // read all eight pin values then strip away all but pinA and pinB's values
  if(RE2_reading == ABMASK && RE2_aFlag)            //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
  {
    RE2_encoderPos --;                              //decrement the encoder's position count
    RE2_bFlag = 0;                                  //reset flags for the next turn
    RE2_aFlag = 0;                                  //reset flags for the next turn
  }
  else if (RE2_reading == AMASK) RE2_bFlag = 1;     //signal that we're expecting pinB to signal the transition to detent from free rotation
}


void RE2_PinB()
{
  RE2_reading = PIN & ABMASK;                       // read all eight pin values then strip away all but pinA and pinB's values
  if (RE2_reading == ABMASK && RE2_bFlag)           //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
  {
    RE2_encoderPos ++;                              //increment the encoder's position count
    RE2_bFlag = 0;                                  //reset flags for the next turn
    RE2_aFlag = 0;                                  //reset flags for the next turn
  }
  else if (RE2_reading == BMASK) RE2_aFlag = 1;     //signal that we're expecting pinA to signal the transition to detent from free rotation
}


void loop()
{
  if(RE1_oldEncPos != RE1_encoderPos) 
  {
      lcd1.setCursor(13,0);   //Set cursor
      lcd1.print("   ");      //Delete old reading
      lcd1.setCursor(13,0);   //Set cursor
      lcd1.print(RE1_encoderPos); //Print new reading
      RE1_oldEncPos = RE1_encoderPos;
  }

  if(RE2_oldEncPos != RE2_encoderPos) 
  {
      lcd2.setCursor(13,0);   //Set cursor
      lcd2.print("   ");      //Delete old reading
      lcd2.setCursor(13,0);   //Set cursor
      lcd2.print(RE2_encoderPos); //Print new reading
      RE2_oldEncPos = RE2_encoderPos;
  }
}[code]

I would appreciate any suggestions.

Regards

[/code]

I'm not very familiar with reading a whole port (8 bits read into a byte)

I guess you have to change the bitmask to the IO-pins 18,19 to make it work with this code.
As 18,19 is pretty far away I guess this is a complete other port. SO additional changes might be requiered.

This code is a good example for: if you want to modify code you have to understand 80% of the code to make it work.

Reading the single bits will be easier. This means instead of reading a whole port with 8 bits,
just read the single io-ports.

byte Enc2_A_Input = digitalRead(RE2_pinA);
byte Enc2_B_Input = digitalRead(RE2_pinB);

depending on the single-io-port beeing high or low set the corresponding bit in RE2_reading as "1" by an bitwise OR-operation and as "0" by a bitwise and-operation with all bits beeing 1 and the bit that should become 0 with 0

Then the rest of the code should work

best regards Stefan

Thanks Stefan.

The code is not my own, but a concatenation of various snippets found on a number of sites. I'll have to chew on your suggestion a bit - this is all pretty new to me.

Could you have gotten the interrupt numbers incorrect? It is recommended to use digitalPinToInterrupt(pin_number) rather than hard-coded numbers.

I suspected it wasn't your code. (Otherwise you wouldn't have to ask such questions). Asking these questions is completely OK. But I guess now you are in front of a hill for climbing up that hill some additional learning is nesessary.
To learn how bitwise or and bitwise and-operations work
I recommend doing this with small testprograms with serial-output.
The pieces to "chew" will be much smaller doing it with small test-programs.

Some additional hints for this: the Serial.print-command has options to print the vlaues of variables in binary format
a decimal "2" will be printed as "00000010"
a decimal "3" will be printed as "00000011"
a decimal "4" will be printed as "00000100"

etc.
This will make it much easier to analyse what the different bitwise operators are doing

best regards Stefan

StefanL38:
Some additional hints for this: the Serial.print-command has options to print the vlaues of variables in binary format
a decimal "2" will be printed as "00000010"
a decimal "3" will be printed as "00000011"
a decimal "4" will be printed as "00000100"

Serial.println() doesn't print leading zeroes so 2, 3, and 4 show up as "10", "11", and "100".

void setup()
{
  Serial.begin(115200);
  Serial.println(2, BIN);
  Serial.println(3, BIN);
  Serial.println(4, BIN);
}

void loop() {}

Hi John,

thank you for pointing out this.
So I did a search for how to do it with leading zeros. Lot's of ways to do it.
This version has a pretty compact code

byte MyByteVar1;

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

  MyByteVar1 = 1;
  Serial.print("Decimal ");
  Serial.print(MyByteVar1);
  Serial.print(" binary 8 digits ");
  print8DigitsBinary(MyByteVar1);
  Serial.println();
  
  MyByteVar1 = 2;
  Serial.print("Decimal ");
  Serial.print(MyByteVar1);
  Serial.print(" binary 8 digits ");
  print8DigitsBinary(MyByteVar1);
  Serial.println();
  
  MyByteVar1 = 3;
  Serial.print("Decimal ");
  Serial.print(MyByteVar1);
  Serial.print(" binary 8 digits ");
  print8DigitsBinary(MyByteVar1);
  Serial.println();
  
  MyByteVar1 = 4;
  Serial.print("Decimal ");
  Serial.print(MyByteVar1);
  Serial.print(" binary 8 digits ");
  print8DigitsBinary(MyByteVar1);
  Serial.println();
  
  MyByteVar1 = 5;
  Serial.print("Decimal ");
  Serial.print(MyByteVar1);
  Serial.print(" binary 8 digits ");
  print8DigitsBinary(MyByteVar1);
  Serial.println();  
}


void print8DigitsBinary(byte inByte)
{
  for (int b = 7; b >= 0; b--)
  {
    Serial.print(bitRead(inByte, b));
  }
}

void loop() {
  // put your main code here, to run repeatedly:

}

Serial output

Decimal 1 binary 8 digits 00000001
Decimal 2 binary 8 digits 00000010
Decimal 3 binary 8 digits 00000011
Decimal 4 binary 8 digits 00000100
Decimal 5 binary 8 digits 00000101

best regards Stefan

Hi John

I also suspected that the interrupt numbers on pins 18 & 19 may be wrong and I did amend the code to use "digitalPinToInterrupt(pin_number)" to see if that would clear things up, but still no luck.

I am exploring Stefan's suggestion to be more selective about what is being read on the port.

The saga continues...

1 Like