Arduino Nano: Code not working when merged togheter

Hi. I'm trying to use two SSD1306 through a TCA9548A together with a rotary encoder. When turning the encoder CCW, display #1 should count downwards and by turning the other way, display #2 should count upwards.

I've tested the display with a separate code and it works.
I've tested the rotary encoder with a separate code and it works.

My problem is when I merge the two codes. Then both displays show "0". The rotary does not respond (no feedback on display or SerialMonitor). Only the push-function seems to work.

Any ideas?

Here's my code:

For the display:

#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_BMP280.h>
#include <Adafruit_ADXL345_U.h>

// SSD1306 def:
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

int UPvalue = 0;
int DOWNvalue = 0;


void setup() {
  Serial.begin(9600);
 
  // Start I2C communication
  Wire.begin();

  // Initialize multiplexer on bus number 2
  TCA9548A(0);
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  } 
  display.clearDisplay();

  // Initialize multiplexer on bus number 3
  TCA9548A(1);
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  } 
  display.clearDisplay();
 }

void loop()
{

  DisplayValue(0,UPvalue);
  DisplayValue(1,DOWNvalue);

  UPvalue++;
  DOWNvalue--;

  delay(500);
}

void DisplayValue(int displayNumber, int outputValue)
{
  TCA9548A(displayNumber);
  
  // Display altitude on SSD1306 OLED display
  display.clearDisplay(); // Clear display buffer
  display.setTextSize(2); 
  display.setTextColor(WHITE); // Set text color to white
  display.setCursor(0, 0);
  display.print(outputValue);
  display.display();

}

// Select I2C BUS
void TCA9548A(uint8_t bus)
{
  Wire.beginTransmission(0x70);  // TCA9548A address
  Wire.write(1 << bus);          // send byte to select bus
  Wire.endTransmission();
  //Serial.print("bus active:" );
  //Serial.println(bus);
}

For the rotary encoder:

#include <Arduino.h>

// Rotary encoder pins
#define PIN_A 2
#define PIN_B 3
#define PUSH_BTN 4

// A turn counter for the rotary encoder (negative = anti-clockwise)
int rotationCounter = 0;
int UPvalue = 0;
int DOWNvalue = 0;

// Flag from interrupt routine (moved=true)
volatile bool rotaryEncoder = false;



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

    // The module already has pullup resistors on board
    pinMode(PIN_A, INPUT);
    pinMode(PIN_B, INPUT);

    // But not for the push switch
    pinMode(PUSH_BTN, INPUT_PULLUP);

    // We need to monitor both pins, rising and falling for all states
    attachInterrupt(digitalPinToInterrupt(PIN_A), rotary, CHANGE);
    attachInterrupt(digitalPinToInterrupt(PIN_B), rotary, CHANGE);
    Serial.println("Setup completed");
}

void loop()
{
    // Has rotary encoder moved?
    if (rotaryEncoder)
    {
        // Get the movement (if valid)
        int8_t rotationValue = checkRotaryEncoder();

        // If valid movement, do something
        if (rotationValue > 0)
        {
          UPvalue += rotationValue;
          Serial.print("UPvalue: ");
          Serial.println(UPvalue);
        }
        if (rotationValue < 0)
        {
          DOWNvalue += rotationValue;
          Serial.print("DOWNvalue: ");
          Serial.println(DOWNvalue);
        }
    }

    if (digitalRead(PUSH_BTN) == LOW)
    {
      UPvalue = 0;
      DOWNvalue = 0;
      Serial.print("UPvalue: ");
      Serial.println(UPvalue);
      Serial.print("DOWNvalue: ");
      Serial.println(DOWNvalue);
 
      // Wait until button released (demo only! Blocking call!)
      while (digitalRead(PUSH_BTN) == LOW)
      {
        delay(100);
      }
    }
}


//Interrupt/encoder-code copied from RalphBacon /226-Better-Rotary-Encoder---no-switch-bounce:

// Interrupt routine just sets a flag when rotation is detected
void rotary()
{
  rotaryEncoder = true;
}

// Rotary encoder has moved (interrupt tells us) but what happened?
// See https://www.pinteric.com/rotary.html
int8_t checkRotaryEncoder()
{
  // Reset the flag that brought us here (from ISR)
  rotaryEncoder = false;

  static uint8_t lrmem = 3;
  static int lrsum = 0;
  static int8_t TRANS[] = {0, -1, 1, 14, 1, 0, 14, -1, -1, 14, 0, 1, 14, 1, -1, 0};

  // Read BOTH pin states to deterimine validity of rotation (ie not just switch bounce)
  int8_t l = digitalRead(PIN_A);
  int8_t r = digitalRead(PIN_B);

  // Move previous value 2 bits to the left and add in our new values
  lrmem = ((lrmem & 0x03) << 2) + 2 * l + r;

  // Convert the bit pattern to a movement indicator (14 = impossible, ie switch bounce)
  lrsum += TRANS[lrmem];

  /* encoder not in the neutral (detent) state */
  if (lrsum % 4 != 0)
  {
    return 0;
  }

  /* encoder in the neutral state - clockwise rotation*/
  if (lrsum == 4)
  {
    lrsum = 0;
    return 1;
  }

  /* encoder in the neutral state - anti-clockwise rotation*/
  if (lrsum == -4)
  {
    lrsum = 0;
    return -1;
  }

  // An impossible rotation has been detected - ignore the movement
  lrsum = 0;
  return 0;
}

Merged code:

#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_BMP280.h>
#include <Adafruit_ADXL345_U.h>

// SSD1306 def:
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// Rotary encoder pins
#define PIN_A 2
#define PIN_B 3
#define PUSH_BTN 4

// A turn counter for the rotary encoder (negative = anti-clockwise)
int rotationCounter = 0;
int UPvalue = 0;
int DOWNvalue = 0;

// Flag from interrupt routine (moved=true)
volatile bool rotaryEncoder = false;

void setup() {
  Serial.begin(9600);
 
  // Start I2C communication
  Wire.begin();

  // Init screen 0
  TCA9548A(0);
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  } 
  display.clearDisplay();

  // Init screen 1
  TCA9548A(1);
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  } 
  display.clearDisplay();

  pinMode(PIN_A, INPUT);
  pinMode(PIN_B, INPUT);

  // But not for the push switch
  pinMode(PUSH_BTN, INPUT_PULLUP);

  // We need to monitor both pins, rising and falling for all states
  attachInterrupt(digitalPinToInterrupt(PIN_A), rotary, CHANGE);
  attachInterrupt(digitalPinToInterrupt(PIN_B), rotary, CHANGE);
  Serial.println("Setup completed");
 }

void loop()
{
  // Has rotary encoder moved?
  if (rotaryEncoder)
  {
    // Get the movement (if valid)
    int8_t rotationValue = checkRotaryEncoder();

    // If valid movement, do something
    if (rotationValue > 0)
    {
      UPvalue += rotationValue;
      Serial.print("UPvalue: ");
      Serial.println(UPvalue);
    }
    if (rotationValue < 0)
    {
      DOWNvalue += rotationValue;
      Serial.print("DOWNvalue: ");
      Serial.println(DOWNvalue);
    }
  }

  if (digitalRead(PUSH_BTN) == LOW)
  {
    UPvalue = 0;
    DOWNvalue = 0;
    Serial.print("UPvalue: ");
    Serial.println(UPvalue);
    Serial.print("DOWNvalue: ");
    Serial.println(DOWNvalue);

    // Wait until button released (demo only! Blocking call!)
    while (digitalRead(PUSH_BTN) == LOW)
    {
      delay(100);
    }
  }

  DisplayValue(0,UPvalue);
  DisplayValue(1,DOWNvalue);

  //UPvalue++;
  //DOWNvalue--;

  //delay(500);
}

void DisplayValue(int displayNumber, int outputValue)
{
  TCA9548A(displayNumber);
  
  // Display altitude on SSD1306 OLED display
  display.clearDisplay(); // Clear display buffer
  display.setTextSize(2); 
  display.setTextColor(WHITE); // Set text color to white
  display.setCursor(0, 0);
  display.print(outputValue);
  display.display();

}

// Select I2C BUS
void TCA9548A(uint8_t bus)
{
  Wire.beginTransmission(0x70);  // TCA9548A address
  Wire.write(1 << bus);          // send byte to select bus
  Wire.endTransmission();
  //Serial.print("bus active:" );
  //Serial.println(bus);
}


//Interrupt/encoder-code copied from RalphBacon /226-Better-Rotary-Encoder---no-switch-bounce:

// Interrupt routine just sets a flag when rotation is detected
void rotary()
{
  rotaryEncoder = true;
}

// Rotary encoder has moved (interrupt tells us) but what happened?
// See https://www.pinteric.com/rotary.html
int8_t checkRotaryEncoder()
{
  // Reset the flag that brought us here (from ISR)
  rotaryEncoder = false;

  static uint8_t lrmem = 3;
  static int lrsum = 0;
  static int8_t TRANS[] = {0, -1, 1, 14, 1, 0, 14, -1, -1, 14, 0, 1, 14, 1, -1, 0};

  // Read BOTH pin states to deterimine validity of rotation (ie not just switch bounce)
  int8_t l = digitalRead(PIN_A);
  int8_t r = digitalRead(PIN_B);

  // Move previous value 2 bits to the left and add in our new values
  lrmem = ((lrmem & 0x03) << 2) + 2 * l + r;

  // Convert the bit pattern to a movement indicator (14 = impossible, ie switch bounce)
  lrsum += TRANS[lrmem];

  /* encoder not in the neutral (detent) state */
  if (lrsum % 4 != 0)
  {
    return 0;
  }

  /* encoder in the neutral state - clockwise rotation*/
  if (lrsum == 4)
  {
    lrsum = 0;
    return 1;
  }

  /* encoder in the neutral state - anti-clockwise rotation*/
  if (lrsum == -4)
  {
    lrsum = 0;
    return -1;
  }

  // An impossible rotation has been detected - ignore the movement
  lrsum = 0;
  return 0;
}

What do you try to achieve? What has your two display to show - the same picture or two different?

Display #1 should count down for each CCW step of the encoder and display #2 should count up for each CW step of the encoder.

I don't think it is possible.

This library uses a memory buffer for writing to the display. You have a single buffer for both displays, so I don't think you can get two screens to work independently.

True, the fact that nothing changes at all on the screens now is a bug in the code, it should not be so. Looks like the encoder is not working.
I think that once you fix this bug, the screens will start to change. But not the way you planned. I suggest two options - either they will always change synchronously, or you will simply see a distorted picture and all sorts of interference on both screens.

Thanx for answering. Not sure I understand.
The two displays are working independently, as shown in the first code.
Could you please point out the bug?

Well, then I'm wrong, you're in luck.
Forget everything i wrote above

Looks like my problem occurs when I add the rotary encoder code and enabling the interrupts..

I've read through the code and I haven't spotted anything yet that is clearly a problem.

My theory at the moment is that the 2 displays are being constantly updated, even though the values may not have changed. When the encoder is moved, the interrupt fires and sets the Boolean variable to indicate that movement was detected. This could (probably is) happening while the displays are being updated. But perhaps by the time the displays have finished updating and loop() gets around to call checkRotaryEncoder() to read the 2 encoder pins, they are back to the same values again, so the code thinks the encoder has not moved at all.

What are you seeing on serial monitor?

The serial monitor show the same as the screen; No movement.

I change the code and setup a little bit. Removed the MUX and have one display hooked up to the I2C.

By placing the display code inside the if statement as shown below, everything works fine:

if (rotaryEncoder)
  {
    // Get the movement (if valid)
    int8_t rotationValue = checkRotaryEncoder();

    // If valid movement, do something
    if (rotationValue != 0)
    {
      rotationCounter += rotationValue;
      Serial.print(rotationValue < 1 ? "L" :  "R");
      Serial.println(rotationCounter);
      display.clearDisplay(); // Clear display buffer
      display.setTextSize(4); 
      display.setTextColor(WHITE); // Set text color to white
      display.setCursor(0, 0);
      display.print(rotationCounter);
      display.display();
    }
  } 

By placing outside, I have the same problem:

  if (rotaryEncoder)
  {
    // Get the movement (if valid)
    int8_t rotationValue = checkRotaryEncoder();

    // If valid movement, do something
    if (rotationValue != 0)
    {
      rotationCounter += rotationValue;
      Serial.print(rotationValue < 1 ? "L" :  "R");
      Serial.println(rotationCounter);
    }
  } else {

    display.clearDisplay(); // Clear display buffer
    display.setTextSize(4); 
    display.setTextColor(WHITE); // Set text color to white
    display.setCursor(0, 0);
    display.print(rotationCounter);
    display.display();
  }

When I turn the rotary the numbers will not update (not on serial or display). If I turn very slowly, the number sometimes jumps one or two.

Unfortunately, I need to update the screen outside the if :frowning:

The forum can't help with code you haven't posted!

This seems to fit with my idea about why it is not working.

I don't think the code you are using to read the encoder is well designed. The usual reason for using interrupts with encoders is that the signals can change very fast, so they must be captured quickly to avoid missing some of the changes. For that reason, the signals are captured during the interrupt routine.

But in the code you are using, the interrupt routine only sets a Boolean flag. The encoder signals are read later in loop() and by that time it can be too late to capture the signals.

Is there a special reason why you are using this code and not one of the more popular encoder libraries?

Another way which may help avoid missing encoder signals would be to increase the i2c speed. Add this line in setup()

Wire.setClock(400000);

Thanx for the feedback.
I found this code and thought it looked robust.

Any libraries you can recommend?

Look in the library manager. I think there is one written by Paul Stoffregen which should be good.

1 Like

https://www.hackster.io/news/control-multiple-i2c-oled-displays-on-a-single-bus-cf5770bc61de

https://marksbench.com/electronics/getting-two-i2c-displays-working-at-once-on-an-arduino/

https://www.youtube.com/watch?v=YCkFFtVEEG4

https://emariete.com/en/connect-an-oled-display-ssd1306-to-espeasy/

I've tried that. For some strange reasons (without digging into it), the i2c address is 0x3C regardless of the selection done on the back.

did you check that with I2C scanner ?

No, i did not. After looking at the attachements you added, I'll give it another try :wink:

If you are using one address for both OLED you will have the same screens on both.

yup, that what happened last time (even though I had different settings on the back of the displays) :-o

= I2C scanner = second address

1 Like

Hi,could not get Paul's to work, but found another one that works.
I had to put my display update routine into a timer interrupt, then everything works fine. Only tested with one display (not two). But this is at least a step in the right direction. Thanx for your help!