Need help with OLED Menu

I have nearly a working Base for my project. But if I uncomment the Item 5 to have the same structure as the other menus, the display remains black. I dont get my mistake.
Who can give me a hint?
I want to let it run on a Nano.

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

// OLED width and height
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// OLED reset pin (usualy do not use)
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Rotary Encoder pins
const int encoderPinA = 2;
const int encoderPinB = 4;
const int encoderButtonPin = 3;

class Menu {
private:
  char** items;
  int numItems;
  int currentIndex;
  char* title;
  Menu* parentMenu;

public:
  Menu(char** menuItems, int itemCount, char* menuTitle, Menu* parent = nullptr) {
    items = menuItems;
    numItems = itemCount;
    currentIndex = 0;
    title = menuTitle;
    parentMenu = parent;
  }

  void nextItem() {
    currentIndex = (currentIndex + 1) % numItems;
  }

  void previousItem() {
    currentIndex = (currentIndex - 1 + numItems) % numItems;
  }

  void setCurrentIndex(int index) {
    currentIndex = index;
  }

  char* selectItem() {
    return items[currentIndex];
  }

  void displayMenu() {
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.setCursor(0, 0);
    display.print(title);
    display.setTextSize(1);

    int start = max(0, currentIndex - 2);
    int end = min(numItems, start + 5);

    for (int i = start; i < end; i++) {
      if (i == currentIndex) {
        display.fillRect(0, 14 + (i - start) * 12, SCREEN_WIDTH, 14, WHITE);
        display.setTextColor(BLACK);
      } else {
        display.setTextColor(WHITE);
      }
      display.setCursor(0, 17 + (i - start) * 12);
      display.print(items[i]);
    }

    display.display();
  }

  Menu* getParentMenu() {
    return parentMenu;
  }

  int getCurrentIndex() {
    return currentIndex;
  }
};

// Menu Items
const char* mainMenuItems[] = { "Item A", "Item B", "Item C", "Item D", "Item E" };
const char* subMenu1Items[] = { "SubItem A1", "SubItem A2", "SubItem A3", "Back" };
const char* subMenu2Items[] = { "SubItem B1", "SubItem B2", "SubItem B3", "Back" };
const char* subMenu3Items[] = { "SubItem C1", "SubItem C2", "SubItem C3", "Back" };
const char* subMenu4Items[] = { "SubItem D1", "SubItem D2", "SubItem D3", "Back" };
const char* subMenu5Items[] = { "SubItem E1", "SubItem E2", "SubItem E3", "Back" };

const int numMainMenuItems = sizeof(mainMenuItems) / sizeof(mainMenuItems[0]);
const int numSubMenu1Items = sizeof(subMenu1Items) / sizeof(subMenu1Items[0]);
const int numSubMenu2Items = sizeof(subMenu2Items) / sizeof(subMenu2Items[0]);
const int numSubMenu3Items = sizeof(subMenu3Items) / sizeof(subMenu3Items[0]);
const int numSubMenu4Items = sizeof(subMenu4Items) / sizeof(subMenu4Items[0]);
const int numSubMenu5Items = sizeof(subMenu5Items) / sizeof(subMenu5Items[0]);

// Menu objects
Menu mainMenu(mainMenuItems, numMainMenuItems, "Main Menu");
Menu subMenu1(subMenu1Items, numSubMenu1Items, "SubMenuA", &mainMenu);
Menu subMenu2(subMenu2Items, numSubMenu2Items, "SubMenuB", &mainMenu);
Menu subMenu3(subMenu3Items, numSubMenu3Items, "SubMenuC", &mainMenu);
Menu subMenu4(subMenu4Items, numSubMenu4Items, "SubMenuD", &mainMenu);
Menu subMenu5(subMenu5Items, numSubMenu5Items, "SubMenuE", &mainMenu);

// Variable that holds the current menu
Menu* currentMenu = &mainMenu;

// Rotary Encoder Variables
volatile long encoderValue = 0;
long lastencoderValue = 0;

bool buttonPressed = false;

void buttonPress() {
  buttonPressed = true;
}

// Encoder read function
void updateEncoder() {
  int ENCB = digitalRead(encoderPinB);
  if (ENCB > 0) {
    encoderValue++;
  } else {
    encoderValue--;
  }
}

/*void selectedItemFunc(const char* item) {
  char buffer[40];
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.print("Selected:");
  display.setCursor(0, 30);
  display.print(item);
  display.display();
  delay(1000);
}*/

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

  // OLED starting
  display.begin(SSD1306_EXTERNALVCC, 0x3C);

  display.display();
  delay(2000);
  display.clearDisplay();

  // Set Rotary Encoder pins as input
  pinMode(encoderPinA, INPUT);
  pinMode(encoderPinB, INPUT);
  pinMode(encoderButtonPin, INPUT_PULLUP);

  // Set interrupt operation for encoder inputs
  attachInterrupt(digitalPinToInterrupt(encoderPinA), updateEncoder, RISING);
  attachInterrupt(digitalPinToInterrupt(encoderButtonPin), buttonPress, FALLING);

  // Show menu item at startup
  currentMenu->displayMenu();
}

void loop() {
  // Check encoder value and update menu
  if (encoderValue != lastencoderValue) {
    if (encoderValue > lastencoderValue) {
      currentMenu->nextItem();
    } else {
      currentMenu->previousItem();
    }
    lastencoderValue = encoderValue;
    currentMenu->displayMenu();
  }

  // Select item when Encoder button is pressed
  if (buttonPressed) {
    buttonPressed = false;
    if (currentMenu == &mainMenu) {
      switch (currentMenu->getCurrentIndex()) {
        case 0:
          currentMenu = &subMenu1;
          currentMenu->setCurrentIndex(0);
          break;
        case 1:
          currentMenu = &subMenu2;
          currentMenu->setCurrentIndex(0);
          break;
        case 2:
          currentMenu = &subMenu3;
          currentMenu->setCurrentIndex(0);
          break;
        case 3:
          currentMenu = &subMenu4;
          currentMenu->setCurrentIndex(0);
          break;
          /*case 4: currentMenu = &subMenu5; currentMenu->setCurrentIndex(0);
          break;*/
          /*default: selectedItemFunc(currentMenu->selectItem());
          break;*/
      }
    } else if (currentMenu == &subMenu1) {
      switch (currentMenu->getCurrentIndex()) {
        case 3:
          currentMenu = currentMenu->getParentMenu();
          break;
      }
    } else if (currentMenu == &subMenu2) {
      switch (currentMenu->getCurrentIndex()) {
        case 3:
          currentMenu = currentMenu->getParentMenu();
          break;
      }
    } else if (currentMenu == &subMenu3) {
      switch (currentMenu->getCurrentIndex()) {
        case 3:
          currentMenu = currentMenu->getParentMenu();
          break;
      }
    } else if (currentMenu == &subMenu4) {
      switch (currentMenu->getCurrentIndex()) {
        case 3:
          currentMenu = currentMenu->getParentMenu();
          break;
      }
    }
    /*else if (currentMenu == &subMenu5) {
      switch (currentMenu->getCurrentIndex()) {
        case 3: currentMenu = currentMenu->getParentMenu();
          break;
      }
    }*/
    currentMenu->displayMenu();
  }
}

Your code ignores the return value of this function at its peril.

You have no idea whether you had enough free RAM for it to succeed.

Sloppy programming.

thank you... and how does that help me?

I just handed you the most likely answer as to why you code isn't working.

Good-bye.

Sympathetic... you must be very popular :sweat_smile:

With over 1000 likes and over 200 solutions I think that he is quite popular :rofl:

Anyway, you can make yourself more popular by telling us which board you're compiling for. If it's an Uno or Nano your skating on thin ice with the code that you presented and if you enable your 5th menu you're skating on too thin ice.

Your sketch as presented uses 880 bytes dynamic memory, with the 5th menu enabled it uses 904 bytes.
You will need to add 1024 bytes for your display which makes it 1928 bytes for the code with the 5th menu entry; this is because the Adafruit_SSD1306 library uses dynamic memory allocation. So you only have 2048 - 1928 = 120 bytes left for everything else (which is more gthan likely not enough).

You need to learn, not just rely on others.

Not sloppy programming, but lazy implementation - just lazy, assuming a function will work.

Do you understand what was pointed out in the earlier post ?

Unfortunately not.

Up to a point I have been able to implement everything as far as I think it could work. And now I'm at the point where I can't get any further.

I'm just getting started.

If I could see why it works without and not with the menu item 5, I would have a direction to orient myself.

At the moment I can only guess and try random things.

As I wrote at the beginning, I have a Nano lying here on which I would like to install this. I am not getting any error messages.

After compiling with menu 5 my sketch uses uses 15914 bytes (51%) of program storage space and the Global variables use 904 bytes (44%) of dynamic memory.

But the display don´t show something. Only when i disable item 5 i get a screen with the menus.

  if(!display.begin(SSD1306_EXTERNALVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }

Please read post #6 again; your code uses 1928 bytes of dynamic memory.

That may be all.

Nevertheless, I don't understand why nothing is displayed with menu item 5.

I am aware that I will need more memory at some point. But I'm already failing with this simple structure.....

Problem solved. Thanks

More than likely your code exhibits undefined behaviour due to lack of free memory. E.g. your code calls display.clearDisplay(). This will push e.g. the program counter (two bytes) on the stack; so at that moment you are at 1928 + 2 bytes used. That function might also use some local variables which again cost memory; and it might call another function which again costst you some memory. Once that function is finished the memory is freed and you're back at 1928 bytes.
If you push too much or use too much in functions the heap and the stack (you can look that up) collide and variables might be overwritten which will result in undefined behaviour.

Note that this is just an example; I'm not going to dig through the Adafruit libraries.

PS: The ISRs push far more on the stack.

How?

1 Like

I have shortened the submenus in each menu. The result was a little more memory and everything works.
This means that the Nano is completely unsuitable for my project.

I still have a PI Pico lying around here. I'll have to see what it can do or order a suitable controller and then continue practicing.

@sterretje
your last answer was very helpful for me. thank you very much

You can place all your menu text in PROGMEM.

PS
I added a PS about the ISRs to my previous reply.

Never done it before. I'll have to read up on it and try it out. Maybe i'll find an example somewhere to adapt my code accordingly.

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