Text scrolling in menu not working properly on OLED screen

I've got a simple state machine displaying on an OLED display and on this menu I wanted the text to scroll off the left side of the screen if it is too long to fit on the screen. This code compiles but doesn't scroll when the selected item highlighted and I can't figure out what is going wrong. Using <U8g2lib.h> library

#include <Arduino.h>
#include <U8g2lib.h>

// Display configuration
U8G2_SSD1322_NHD_256X64_1_4W_SW_SPI u8g2(U8G2_R0, 12, 11, 10, 9, 8);

// Rotary Encoder Pins
#define CLK 3
#define DT 4
#define SW 2

// Button Pins
#define BTN_MENU1 5
#define BTN_MENU2 6
#define BTN_MENU3 7

// LED Pins
#define LED_PIN 13

// Icons
static const unsigned char connection_icon[] = {0x08, 0x19, 0x2a, 0x4c, 0x28, 0x18, 0x28, 0x4c, 0x2a, 0x19, 0x08};
static const unsigned char music_icon[] = {0xfc, 0x1f, 0x04, 0x10, 0x04, 0x10, 0x04, 0x10, 0x04, 0x10, 0x04, 0x10, 0x04, 0x10, 0x04, 0x10, 0x07, 0x1c, 0x07, 0x1c, 0x07, 0x1c};
static const unsigned char playback_icon[] = {0x43, 0x01, 0x45, 0x01, 0x49, 0x01, 0x51, 0x01, 0x61, 0x01, 0x51, 0x01, 0x49, 0x01, 0x45, 0x01, 0x43, 0x01};
static const unsigned char forward_button[] = {0x00, 0x02, 0x00, 0x06, 0x00, 0x0e, 0xf8, 0x1f, 0xff, 0x3f, 0xff, 0x1f, 0x0f, 0x0e, 0x00, 0x06, 0x00, 0x02};
static const unsigned char back_button[] = {0x10, 0x00, 0x18, 0x00, 0x1c, 0x00, 0xfe, 0x03, 0xff, 0x0f, 0xfe, 0x1f, 0x1c, 0x1c, 0x18, 0x38, 0x10, 0x38, 0x00, 0x3c, 0x00, 0x1e, 0x00, 0x07};
static const unsigned char you_like_jazz[] = {0x00, 0x3e, 0x1b, 0x3f, 0x8a, 0x30, 0xee, 0x10, 0xd1, 0x3f, 0xd1, 0x3e, 0xd1, 0x76, 0xde, 0x36, 0xd0, 0x36, 0xe0, 0x1f};

// Connection icons
static const unsigned char full_connection[] = {0x00, 0x06, 0x00, 0x06, 0xc0, 0x06, 0xc0, 0x06, 0xd8, 0x06, 0xd8, 0x06, 0xdb, 0x06, 0xdb, 0x06};
static const unsigned char nfull_connection[] = {0xc0, 0xc0, 0xd8, 0xd8, 0xdb, 0xdb};
static const unsigned char nothing[] = {};

// Note icons
static const unsigned char crotchet_up[] = {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xf8, 0xfc, 0xfe, 0xff, 0x7f, 0x3e};
static const unsigned char crotchet_down[] = {0x7c, 0xfe, 0xff, 0x7f, 0x3f, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01};
static const unsigned char four_Hi_Hat[] = {0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0x80, 0x00, 0x20, 0x00, 0x08, 0x00, 0x02, 0x80, 0x00, 0x20, 0x00, 0x08, 0x00, 0x02, 0x80, 0x00, 0x20, 0x00, 0x08, 0x00, 0x02, 0x80, 0x00, 0x20, 0x00, 0x08, 0x00, 0x02, 0x80, 0x00, 0x20, 0x00, 0x08, 0x00, 0x02, 0x80, 0x00, 0x20, 0x00, 0x08, 0x00, 0x02, 0x80, 0x00, 0x20, 0x00, 0x08, 0x00, 0x02, 0x80, 0x00, 0x20, 0x00, 0x08, 0x00, 0x02, 0x80, 0x00, 0x20, 0x00, 0x08, 0x00, 0x02, 0x80, 0x00, 0x20, 0x00, 0x08, 0x00, 0x02, 0x41, 0x40, 0x10, 0x10, 0x04, 0x04, 0x01, 0x22, 0x80, 0x08, 0x20, 0x02, 0x88, 0x00, 0x14, 0x00, 0x05, 0x40, 0x01, 0x50, 0x00, 0x08, 0x00, 0x02, 0x80, 0x00, 0x20, 0x00, 0x14, 0x00, 0x05, 0x40, 0x01, 0x50, 0x00, 0x22, 0x80, 0x08, 0x20, 0x02, 0x88, 0x00, 0x41, 0x40, 0x10, 0x10, 0x04, 0x04, 0x01};

struct Icon {
  const unsigned char* data;
  uint8_t width;
  uint8_t height;
};

const Icon drum_icons[9] = {
  {full_connection, 11, 8},
  {full_connection, 11, 8},
  {nfull_connection, 8, 6},
  {full_connection, 11, 8},
  {nfull_connection, 8, 6},
  {full_connection, 11, 8},
  {full_connection, 11, 8},
  {nfull_connection, 8, 6},
  {nothing},
};

// Menu structure
struct MenuItem {
  const char* PROGMEM name;
  void (*action)();
};

// Forward declarations
void drawMenu();
void drawOtherScreen();
void drawHomeScreen();
void drawSongSelected();
void updateMenu(int direction);
void updateMenu_2(int direction);
void drawMenuIndicator();
void drawScrollingText();

// Global variables
byte menuIndex = 0;
byte menuIndex_2 = 0;
byte menuOffset = 0;
const int visibleCount PROGMEM = 3;  // Max number of items visible on screen
bool pressed = false;
unsigned long lastButtonPress = 0;
bool redraw = true;
int txt_xPos = 256;
int scrollSpeed = 2;
char* scrollingText;
bool LED_Practice = LOW;

int menuScrollOffset = 0;
unsigned long lastScrollTime = 0;
const unsigned long scrollInterval = 100; // milliseconds per scroll step

// Encoder state
int currentStateCLK;
int lastStateCLK;

// Screen state
enum Screen {MENU_CONNECTION, MENU_MUSIC, MENU_PLAYBACK, SONG_SELECTED };
Screen currentScreen = MENU_MUSIC;

// Example actions
void action1() {
  Serial.println(F("Action 1 triggered"));
}
void action2() {
  Serial.println(F("Action 2 triggered"));
}
void action3() {
  Serial.println(F("Action 3 triggered"));
}
void playMusic() {
  currentScreen = SONG_SELECTED;
  menuIndex_2 = 0;
  redraw = true;
}

void addMusic() {
  Serial.println("Action 3 triggered");
}

MenuItem menu_connection[] = {
  {"Snare", nullptr},
  {"Bass Drum", nullptr},
  {"Hi-Hat", nullptr},
  {"High Tom", nullptr},
  {"Low Tom", nullptr},
  {"Floor Tom", nullptr},
  {"Crash Cymbal", nullptr},
  {"Ride Cymbal", nullptr},
  {"+ Add Drum", action1},
};
int menu1Length = sizeof(menu_connection) / sizeof(MenuItem);

MenuItem menu_music[] = {
  {"Simple Rock Beat", playMusic},
  {"Living On A Prayer - Bon Jovi", playMusic},
  {"Boogie Wonderland - Earth, Wind & Fire", playMusic},
  {"HighWay to Hell - AC/DC", playMusic},
  {"In Bloom - Nirvana", playMusic},
  {"+ Add Music", addMusic},
};
int menu2Length = sizeof(menu_music) / sizeof(MenuItem);

MenuItem menu_playback[] = {
  {"Menu3: Item I", action1},
  {"Menu3: Item II", action2},
  {"Menu3: Item III", action3},
  {"Menu3: Item IV", nullptr},
};
int menu3Length = sizeof(menu_playback) / sizeof(MenuItem);

MenuItem* currentMenu = menu_music;
int currentMenuLength = 0;

void setup() {
  Serial.begin(9600);
  u8g2.begin();

  pinMode(CLK, INPUT);
  pinMode(DT, INPUT);
  pinMode(SW, INPUT_PULLUP);
  pinMode(BTN_MENU1, INPUT_PULLUP);
  pinMode(BTN_MENU2, INPUT_PULLUP);
  pinMode(BTN_MENU3, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);

  lastStateCLK = digitalRead(CLK);
  currentMenu = menu_music;
  currentMenuLength = menu1Length;

  showTitleScreen();
}

void loop() {

  currentStateCLK = digitalRead(CLK);

  if (digitalRead(BTN_MENU1) == LOW && millis() - lastButtonPress > 300 && currentScreen != MENU_CONNECTION) { //doesn't reset the highlight to the top if it's already on that menu
    currentScreen = MENU_CONNECTION;
    currentMenu = menu_connection;
    currentMenuLength = menu1Length;
    menuIndex = 0;
    menuOffset = 0;
    lastButtonPress = millis();
    redraw = true;
  }

  if (digitalRead(BTN_MENU2) == LOW && millis() - lastButtonPress > 300 && currentScreen != MENU_MUSIC) {
    currentScreen = MENU_MUSIC;
    currentMenu = menu_music;
    currentMenuLength = menu2Length;
    menuIndex = 0;
    menuOffset = 0;
    lastButtonPress = millis();
    redraw = true;
  }

  if (digitalRead(BTN_MENU3) == LOW && millis() - lastButtonPress > 300 && currentScreen != MENU_PLAYBACK) {
    currentScreen = MENU_PLAYBACK;
    currentMenu = menu_playback;
    currentMenuLength = menu3Length;
    menuIndex = 0;
    menuOffset = 0;
    lastButtonPress = millis();
    redraw = true;
  }

  if (currentScreen == MENU_CONNECTION || currentScreen == MENU_MUSIC || currentScreen == MENU_PLAYBACK) {
    currentStateCLK = digitalRead(CLK);
    if (currentStateCLK != lastStateCLK && currentStateCLK == HIGH) {
      if (digitalRead(DT) != currentStateCLK) updateMenu(-1);
      else updateMenu(1);
    }
  }
  else if (currentScreen == SONG_SELECTED) {
    if (currentStateCLK != lastStateCLK && currentStateCLK == HIGH) {
      if (digitalRead(DT) != currentStateCLK) updateMenu_2(-1);
      else updateMenu_2(1);
    }
  }

  lastStateCLK = currentStateCLK;

  if (digitalRead(SW) == LOW && !pressed && millis() - lastButtonPress > 50) {
    pressed = true;
    lastButtonPress = millis();
    if (currentScreen == SONG_SELECTED) {
      if (menuIndex_2 == 0) {
        Serial.println("Practice selected");
        LED_Practice = HIGH;
      } else if (menuIndex_2 == 1) {
        currentScreen = MENU_MUSIC;
        redraw = true;
      } else if (LED_Practice == HIGH) {
        delay(50);
        LED_Practice = LOW;
      }
    } else {
      if (currentMenu[menuIndex].action) {
        currentMenu[menuIndex].action();
      }
    }
  } else if (digitalRead(SW) == HIGH) {
    pressed = false;
  }


  // Drawing
  if (redraw) {
    if (currentScreen == MENU_CONNECTION || currentScreen == MENU_MUSIC || currentScreen == MENU_PLAYBACK) {
      drawMenu();
    } else if (currentScreen == SONG_SELECTED) {
      drawSongSelected();
    }
    redraw = false;
  }

  if (LED_Practice == HIGH) {
    digitalWrite(LED_PIN, HIGH);
    if (menuIndex == 0) { //rock beat was selected to practice
      
    }
  } else {
    digitalWrite(LED_PIN, LOW);
  }
}

void updateMenu(int direction) {

  unsigned long now = millis();
  if (now - lastScrollTime >= scrollInterval) {
    lastScrollTime = now;
    menuScrollOffset++;
  }

  int oldIndex = menuIndex;
  menuIndex += direction;
  if (menuIndex < 0) menuIndex = 0;
  if (menuIndex >= currentMenuLength) menuIndex = currentMenuLength - 1;

  if (menuIndex < menuOffset)
    menuOffset = menuIndex;
  else if (menuIndex >= menuOffset + visibleCount)
    menuOffset = menuIndex - visibleCount + 1;

  if (menuIndex != oldIndex) {
    redraw = true;
  }
}

void updateMenu_2(int direction) {
  if (menuIndex == 0) { // make sure the user doesn't do a hidden scroll and find the exit button
    int currentMenuLength = 2;
    int oldIndex = menuIndex_2;
    menuIndex_2 += direction;
    if (menuIndex_2 < 0) menuIndex_2 = 0;
    if (menuIndex_2 >= currentMenuLength) menuIndex_2 = currentMenuLength - 1;

    if (menuIndex_2 < menuOffset)
      menuOffset = menuIndex_2;
    else if (menuIndex_2 >= menuOffset + visibleCount)
      menuOffset = menuIndex_2 - visibleCount + 1;

    if (menuIndex_2 != oldIndex) {
      redraw = true;
    }
  }
}

void drawMenuIndicator() {
  u8g2.setDrawColor(1);
  u8g2.drawFrame(0, 0, 85, 15);
  u8g2.drawFrame(84, 0, 86, 15);
  u8g2.drawFrame(169, 0, 86, 15);

  u8g2.drawXBM(39, 2, 7, 11, connection_icon);
  u8g2.drawXBM(120, 2, 13, 11, music_icon);
  u8g2.drawXBM(207, 3, 9, 9, playback_icon);

  // Highlight the active menu
  if (currentScreen == MENU_CONNECTION) {
    u8g2.setDrawColor(1);
    u8g2.drawBox(0, 0, 85, 15);
    u8g2.setDrawColor(0);
    u8g2.drawXBM(39, 2, 7, 11, connection_icon);
    u8g2.setDrawColor(1);
  } else if (currentScreen == MENU_MUSIC || currentScreen == SONG_SELECTED) {
    u8g2.setDrawColor(1);
    u8g2.drawBox(84, 0, 86, 15);
    u8g2.setDrawColor(0);
    u8g2.drawXBM(120, 2, 13, 11, music_icon);
    u8g2.setDrawColor(1);
  } else if (currentScreen == MENU_PLAYBACK) {
    u8g2.setDrawColor(1);
    u8g2.drawBox(169, 0, 86, 15);
    u8g2.setDrawColor(0);
    u8g2.drawXBM(207, 3, 9, 9, playback_icon);
    u8g2.setDrawColor(1);
  }
}

void drawMenu() {
  u8g2.firstPage();
  do {
    u8g2.setFont(u8g2_font_ncenB12_tr);
    drawMenuIndicator();

    uint8_t lineHeight = u8g2.getMaxCharHeight();
    uint8_t baseline = u8g2.getFontAscent();
    uint8_t topOffset = 18;

    for (int i = 0; i < visibleCount; i++) {
      int itemIndex = menuOffset + i;
      if (itemIndex >= currentMenuLength) break;

      int y = topOffset + (i * lineHeight) + baseline;

      if (itemIndex == menuIndex) {
        u8g2.drawBox(0, y - baseline - 1, 256, lineHeight - 1);
        u8g2.setDrawColor(0);
      }

      if (currentScreen == MENU_CONNECTION) {
        u8g2.drawStr(50, y, currentMenu[itemIndex].name);
        u8g2.drawXBM(190, y - baseline + 4, drum_icons[itemIndex].width, drum_icons[itemIndex].height, drum_icons[itemIndex].data);
      }
      
      else if (currentScreen == MENU_MUSIC) {
        static int scrollOffset = 0;
        static unsigned long lastScrollTime = 0;
        int xPos = 2;

        const char* itemName = currentMenu[itemIndex].name;
        int scrollSpeed = 1;
        uint8_t strWidth = u8g2.getStrWidth(itemName);

        if (itemIndex == menuIndex && strWidth > 256) {
          unsigned long now = millis();
          if (now - lastScrollTime >= scrollInterval) {
            scrollOffset += scrollSpeed;
            if (scrollOffset > strWidth + 20) {
              scrollOffset = 0;
            }
            lastScrollTime = now;
          }
          xPos -= scrollOffset;
        } else if (itemIndex == menuIndex) {
          scrollOffset = 0;
        }

        u8g2.drawStr(xPos, y, itemName);
      }
      else {
        u8g2.drawStr(2, y, currentMenu[itemIndex].name);
      }

      if (itemIndex == menuIndex) {
        u8g2.setDrawColor(1);
      }
    }
  } while (u8g2.nextPage());
}






void drawScrollingText() {
  u8g2.setFont(u8g2_font_6x10_tr);
  int strWidth = u8g2.getStrWidth(scrollingText);
  u8g2.drawStr(txt_xPos, 20, scrollingText);
  txt_xPos -= scrollSpeed;

  if (txt_xPos < -u8g2.getStrWidth(scrollingText)) {
    txt_xPos = 256;  // Reset position to the right side
  }
}

void drawSongSelected() {
  if (menuIndex == 0) {
    u8g2.firstPage();
    do {

      scrollingText = "Simple Rock Beat";
      drawScrollingText();

      u8g2.setDrawColor(1);
      u8g2.drawXBM(162, 17, 8, 21, crotchet_up);
      u8g2.drawXBM(218, 17, 8, 21, crotchet_up);
      u8g2.drawXBM(134, 6, 50, 18, four_Hi_Hat);
      u8g2.drawXBM(190, 6, 50, 18, four_Hi_Hat);
      u8g2.drawXBM(134, 39, 8, 19, crotchet_down);
      u8g2.drawXBM(190, 39, 8, 19, crotchet_down);

      u8g2.drawLine(242, 24, 129, 24); // draw bar lines
      u8g2.drawLine(242, 45, 129, 45);
      u8g2.drawLine(242, 38, 129, 38);
      u8g2.drawLine(242, 31, 129, 31);

      u8g2.setFont(u8g2_font_ncenB12_tr);
      u8g2.drawStr(20, 36, "Practice");
      u8g2.drawStr(20, 52, "Exit");
      u8g2.drawXBM(100, 26, 14, 9, forward_button);
      u8g2.drawXBM(100, 40, 14, 12, back_button);

      // Now highlight selected item
      if (menuIndex_2 == 0) {
        u8g2.setDrawColor(2);
        u8g2.drawBox(20, 24, 100, 12);  // Highlight "Practice"
        u8g2.setDrawColor(1);
      } else if (menuIndex_2 == 1) {
        u8g2.setDrawColor(2);
        u8g2.drawBox(20, 40, 100, 12);  // Highlight "Exit"
        u8g2.setDrawColor(1);
      }
      delay(10);
    } while (u8g2.nextPage());
  } 
  
  if (menuIndex == 1) {
    u8g2.firstPage();
    do {
      u8g2.drawStr(20, 36, "FUNK BEAT");
    } while (u8g2.nextPage());
  } else if (menuIndex == 2) {
    u8g2.firstPage();
    do {
      u8g2.drawStr(20, 36, "you like jazz?");
      u8g2.drawXBM(199, 17, 15, 10, you_like_jazz);
    } while (u8g2.nextPage());
  }
}

void showTitleScreen() {

  u8g2.firstPage();
  do {

    u8g2.setFont(u8g2_font_profont29_tr);
    u8g2.drawStr(48, 36, "GlowGroove");
    u8g2.setFont(u8g2_font_5x7_tr);
    u8g2.drawStr(56, 47, "See the beat, feel the groove");

  } while (u8g2.nextPage());
  delay(2000);
}

Nice screenshot, now you could attach the sketch properly between <CODE> tags?
You might also find it beneficial to read How to get the best out of this forum

Thank you, I uploaded the full code so everything is a bit clearer. It's still a work in progress

What does it do?

S'lotta code there. I'm interested in how you manage scrolling but my reading ability is only average.

Are you even getting to the part that should do scrolling? If I could build and run your sketch, I would add serial printing (bump the baud rate to something 21st century-ish 115200) to confirm the values of key variables and confirm that they properly inform the flow through your code.

Either to find why it isn't even trying to scroll or why such attempts fail to do.

Did you start with any isolated effort to develop a scrolling capability? Do you have code from that work, perhaps a small sketch that does no more than scroll "Hello World watch this scroll across the screen what is too small to show this entire message!"?

I hope you ever saw scrolling outside the context of the larger sketch that may itself be the problem. Now.

Post your test scrolling sketch if you have one.

a7

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