Help troubleshooting rotary encoder with menu

Hello, so I am trying to create a simple menu to change two values using a rotary encoder (no breakout board). So far I've managed to get the home screen to display my two values and have two "menu screens" that I can cycle through where I should be able to change the values. For some reason the first menu screen works as intended, I rotate the encoder and the value goes up or down accordingly and is update when back on the home screen, the second menu screen does not display any change nor does the value in the home screen change. I'm honestly stumped and chatgpt says it should work so here I am. Any ideas or suggestions or critiques would be greatly appreciated.
Thanks!

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Encoder.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET 4 
#define SCREEN_ADDRESS 0x3D 

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Encoder encoder(5, 6); 
const int BUTTON_PIN = 7; 

enum Screen {
  MAIN_SCREEN,
  WARM_UP_SCREEN,
  CUT_TIME_SCREEN
};

Screen currentScreen = MAIN_SCREEN;
Screen previousScreen = MAIN_SCREEN;

String warmUpTime = "4"; // Initialize warm-up time variable
String cutTime = "22";     // Initialize cut time variable
int encoderChange = 0; // Declare the encoderChange variable

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP); 

  // Initialize the OLED display
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); 
  }
  
  // Clear the display
  display.clearDisplay();
  
  // Set text size and color
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
}

void loop() {

  int encoderValue = encoder.read();


  if (digitalRead(BUTTON_PIN) == LOW) {
    delay(250); // Debounce 
    switch (currentScreen) {
      case MAIN_SCREEN:
        currentScreen = WARM_UP_SCREEN;
        break;
      case WARM_UP_SCREEN:
        currentScreen = CUT_TIME_SCREEN;
        break;
      case CUT_TIME_SCREEN:
        currentScreen = MAIN_SCREEN;
        break;
    }

   
    encoderChange = 0;
  }

  
  encoderChange = encoder.read();
  if (encoderChange != 0) {
    switch (currentScreen) {
      case WARM_UP_SCREEN:
        int newWarmUpTime = warmUpTime.toInt() + encoderChange;
        // Ensure the new warm-up time is within a reasonable range
        if (newWarmUpTime >= 0 && newWarmUpTime <= 30) {
          warmUpTime = String(newWarmUpTime);
        }
        break;

      case CUT_TIME_SCREEN:
        int newCutTime = cutTime.toInt() + encoderChange;
        // Ensure the new cut time is within a reasonable range
        if (newCutTime >= 0 && newCutTime <= 60) {
          cutTime = String(newCutTime);
        }
        break;
    }
    encoder.write(0); // Reset the encoder value
  }

  // Update the display within the loop
  display.clearDisplay();
  display.setCursor(0, 0);

  switch (currentScreen) {
    case MAIN_SCREEN:
      display.setTextSize(1); 
      display.print("Warm Up Time: ");
      display.print(warmUpTime);
      display.println("s");

      display.setCursor(0, 16);
      display.print("Cut Time: ");
      display.print(cutTime);
      display.println("s");
      break;

    case WARM_UP_SCREEN:
      display.setTextSize(2); 
      display.print("Warm Up");
      display.setCursor(0, 24);
      display.print("Time:");

      display.setTextSize(2); 
      display.setCursor(60, 24); 
      display.print(warmUpTime);
      display.println("s");
      break;

    case CUT_TIME_SCREEN:
      display.setTextSize(2); 
      display.print("Cut Time:");

      display.setTextSize(2); 
      display.setCursor(45, 24); 
      display.print(cutTime);
      display.println("s");
      break;
  }


  display.display();

  // Update the previous screen
  previousScreen = currentScreen;
}

Which Arduino are you using?

I can't imagine a good reason for using Strings to keep time, and plenty of reasons for not doing so.

Especially considering that use of Strings leads to program crashes and memory problems on AVR-based Arduinos, like the Uno, Mega, etc.

Ahh ok, thanks for the tip, will look into using something else.
Currently using a Micro.

The Arduino Micro with the ATmega32U4 has the same problem with Strings. Avoid them. Strings are never necessary.

Use unsigned long integers to keep track of time in milliseconds, and the millis() function for timestamps, timing decisions, etc.

This tutorial explains how to use millisecond timers to blink an LED, rather than using delay(), which blocks program execution.

Hi @manlywho ,
Welcome to the forum..

Yes, as pointed out don't need the String objects, quite an easy change..
Encoder should be on pins that support an interrupt, moved them both to 2 and 3 which should be good for your board..
Updated the screen at loop speed, not so desirable..
Curious kind of looks like you knew this as you did already keep track of last screen..
So only clear screen when menu changes, added screen variable updating directly to the encoder switch..
oh, the encoder switch has locally declared vars in the cases, need to wrap entire case in {}..

seems to function better..

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Encoder.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET 4
#define SCREEN_ADDRESS 0x3D

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Encoder encoder(3, 2);
const int BUTTON_PIN = 7;

enum Screen {
  MAIN_SCREEN,
  WARM_UP_SCREEN,
  CUT_TIME_SCREEN
};

Screen currentScreen = MAIN_SCREEN;
Screen previousScreen = WARM_UP_SCREEN;

int warmUpTime = 4; // Initialize warm-up time variable
int cutTime = 22;     // Initialize cut time variable
int encoderChange = 0; // Declare the encoderChange variable

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);

  // Initialize the OLED display
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }

  // Clear the display
  display.clearDisplay();

  // Set text size and color
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
}

void loop() {

  int encoderValue = encoder.read();


  if (digitalRead(BUTTON_PIN) == LOW) {
    delay(100); // Debounce
    switch (currentScreen) {
      case MAIN_SCREEN:
        currentScreen = WARM_UP_SCREEN;
        break;
      case WARM_UP_SCREEN:
        currentScreen = CUT_TIME_SCREEN;
        break;
      case CUT_TIME_SCREEN:
        currentScreen = MAIN_SCREEN;
        break;
    }
    encoderChange = 0;
  }


  encoderChange = encoder.read();
  if (encoderChange != 0) {
    switch (currentScreen) {
      case WARM_UP_SCREEN: {
          int newWarmUpTime = warmUpTime + encoderChange;
          // Ensure the new warm-up time is within a reasonable range
          if (newWarmUpTime >= 0 && newWarmUpTime <= 30) {
            display.setCursor(60, 24);
            display.setTextColor(SSD1306_BLACK);
            display.print(warmUpTime);
            display.println("s");
            display.display();
            display.setTextColor(SSD1306_WHITE);
            warmUpTime = newWarmUpTime;
            display.setCursor(60, 24);
            display.print(warmUpTime);
            display.println("s");
            display.display();
          }
          break;
        }
      case CUT_TIME_SCREEN: {
          int newCutTime = cutTime + encoderChange;
          // Ensure the new cut time is within a reasonable range
          if (newCutTime >= 0 && newCutTime <= 60) {
            display.setCursor(45, 24);
            display.setTextColor(SSD1306_BLACK);
            display.print(cutTime);
            display.println("s");
            display.display();
            display.setCursor(45, 24);
            display.setTextColor(SSD1306_WHITE);
            cutTime = newCutTime;
            display.print(cutTime);
            display.println("s");
            display.display();
          }
          break;
        }
    }
    encoder.write(0); // Reset the encoder value
  }

  if (currentScreen != previousScreen) {
    // Update the display within the loop
    display.clearDisplay();
    display.setCursor(0, 0);

    switch (currentScreen) {
      case MAIN_SCREEN:
        display.setTextSize(1);
        display.print("Warm Up Time: ");
        display.print(warmUpTime);
        display.println("s");

        display.setCursor(0, 16);
        display.print("Cut Time: ");
        display.print(cutTime);
        display.println("s");
        break;

      case WARM_UP_SCREEN:
        display.setTextSize(2);
        display.print("Warm Up");
        display.setCursor(0, 24);
        display.print("Time:");

        display.setTextSize(2);
        display.setCursor(60, 24);
        display.print(warmUpTime);
        display.println("s");
        break;

      case CUT_TIME_SCREEN:
        display.setTextSize(2);
        display.print("Cut Time:");

        display.setTextSize(2);
        display.setCursor(45, 24);
        display.print(cutTime);
        display.println("s");
        break;
    }


    display.display();

    // Update the previous screen
    previousScreen = currentScreen;
  }
}

Project simmed here..

good luck.. ~q

1 Like

Avoid declaring new (temp) variables inside switch/case. Declare all variables you need outside/before the switch where they are to be used.

1 Like

The fix was putting the encoder on interrupt pins, but sadly with the Micro I was already using those pins for data and clock on my screen. Switched to my mega2560 with additional interrupt pins and had no problems.

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