LCD Sub-Menu Functions with 4x20 OLED?

Interesting! So can you make the menu structure using this method to simulate those gifs? Obviously, we will change the existing text!

changing a character changes a character = making the >-symbol jump up and down

This jumping up / down will be initiated by keyboard-presses.
What is printed on the keys of your keyboard?
Can you post a picture of the keyboard?

Just a generic style keypad:

We will use the "Keypad.h" library from the Arduino IDE installer

So which keys to you want to use for

  • arrow up/down
  • enter / cancel

You pick, doesn't really matter haha. There will be one key for UP, one key for DOWN, one key for SELECT, and one key to return to the main menu

hm there are just the digits 0..9 # * not really symbolising the functions up/down select / return
Of course you could out a sticker on the keys

So let's say
star shall be return
double-cross shall be select

* return
# select

5 up
0 down

programming works this way:
wite a demo-code for one component
In this case the keyboard

take a keyboard-democode adapt it to print out the symbols of your keyboard
It is very likely that the internal wiring and how you connect the wires to IO-pins does not immidiately match the codes defintion.
there you will have to test and modify either the wiring or the code's definition which coordinate of line/columm is what character that shall be printed

So take the keyboard-example code and start testing

Keypad code:

#include <Keypad.h>

const byte ROWS = 4; //four rows
const byte COLS = 3; //three columns
char keys[ROWS][COLS] = {
  {'1','2','3'},
  {'4','5','6'},
  {'7','8','9'},
  {'#','0','*'}
};
byte rowPins[ROWS] = {5, 4, 3, 2}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {8, 7, 6}; //connect to the column pinouts of the keypad

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

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

void loop(){
  char key = keypad.getKey();

}

this code needs a small modification for testing by printing to the serial monitor
make sure to adjust the baudrate of the serial monitor to 115200 baud

#include <Keypad.h>

const byte ROWS = 4; //four rows
const byte COLS = 3; //three columns
char keys[ROWS][COLS] = {
  {'1','2','3'},
  {'4','5','6'},
  {'7','8','9'},
  {'#','0','*'}
};
byte rowPins[ROWS] = {5, 4, 3, 2}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {8, 7, 6}; //connect to the column pinouts of the keypad

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

void setup(){
  Serial.begin(115200); // make sure to adjust the serial monitor to 115200 baud
  Serial.println( F("Setup-Start") );
  Serial.println( F("press keys") );
}

void loop(){
  char key = keypad.getKey();

  if (key != NO_KEY) {
    Serial.println(key);
  }
}

And this is a modified version of the Display democode that uses SafeStrings.

I have added a function

PrintTextToLCD(LineNr, Text)

which makes it more convenient to print to the display.
It does compile but the real test is with the real hardware

/****************************************************
       PINOUT: Arduino Uno -> Character OLED
****************************************************/
// The 8 bit data bus is connected to PORTD 0-7 of the Arduino Uno
// The 4 bit data bus is connected to PORTD 4-7 of the Arduino Uno

#define E_Pin 10
#define R_W   9
#define R_S   8

unsigned char InterfaceMode = 4; // 4 = 4-Bit parallel
// 8 = 8-Bit Parallel

#include <SafeString.h>

createSafeString(Line1_SS, 21); // reserve 20 bytes for the SafeString-variable
createSafeString(Line2_SS, 21); // reserve 20 bytes for the SafeString-variable
createSafeString(Line3_SS, 21); // reserve 20 bytes for the SafeString-variable
createSafeString(Line4_SS, 21); // reserve 20 bytes for the SafeString-variable


/****************************************************
                  Text Strings
****************************************************/

char const text4[] = ("   4-Bit Parallel   ");
char const text5[] = ("   8-Bit Parallel   ");

char const text6[] = ("ABCDEFGHIJKLMOPQRSTU");
char const text7[] = ("VWXYZabcdefghijklmno");
char const text8[] = ("pqrstuvwxyz123456789");
char const text9[] = (" <(' ')> || <(' ')> ");

/****************************************************
                Function Commands
****************************************************/

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//  4-bit / 8-bit 6800 Parallel Interface
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

void latch() {                 // command to latch E
  digitalWrite(E_Pin, HIGH);
  delay(1);
  digitalWrite(E_Pin, LOW);
  delay(2);
}

void command(char i) {
  switch (InterfaceMode) {

    case 4: PORTD = i;
      digitalWrite(R_S, LOW);    // Command
      digitalWrite(R_W, LOW);    // Write
      latch();                   // Latch upper 4 bits
      i = i << 4;                // shift 4 bits
      PORTD = i;                 // Take lower 4 bits
      latch();                   // Latch lower 4 bits
      break;

    case 8: PORTD = i;
      digitalWrite(R_S, LOW);    // Data
      digitalWrite(R_W, LOW);    // Write
      latch();                   // Latch 8 bits
      break;
  }
}

void data(char i) {
  switch (InterfaceMode) {

    case 4: PORTD = i;
      digitalWrite(R_S, HIGH);              // Data
      digitalWrite(R_W, LOW);               // Write
      latch();                              // Latch upper 4 bits
      i = i << 4;                           // shift 4 bits
      PORTD = i;                            // Take lower 4 bits
      latch();                              // Latch lower 4 bits
      break;

    case 8: PORTD = i;
      digitalWrite(R_S, HIGH);              // Data
      digitalWrite(R_W, LOW);               // Write
      latch();                              // Latch 8 bits
      break;
  }
}

/****************************************************
                   Display Functions
****************************************************/

void clear_screen() {                   // clear display
  command(0x01);
}

void ret_home() {                       // Return to home position
  command(0x02);
}

void AssignTextTo_SS() {
  Line1_SS = "       Display      ";
  Line2_SS = "   International    ";
  Line3_SS = "   CHARACTER TEST   ";
  Line4_SS = "     Hello World    ";
}


void PrintTextToLCD(byte LineNr, SafeString& Text_SS) {

  switch (LineNr) { // set cursor to line

    case 1:
      ret_home();                 // First Line
      break; // immidiately jump down to END-OF-SWITCH

    case 2:
      command(0xc0);              // Second Line
      break; // immidiately jump down to END-OF-SWITCH

    case 3:
      command(0x94);              // Third Line
      break; // immidiately jump down to END-OF-SWITCH

    case 4:
      command(0xD4);              // Fourth Line
      break; // immidiately jump down to END-OF-SWITCH
  } // END-OF-SWITCH

  char Space = ' ';
  byte len = Text_SS.length();

  for ( int i = 0; i < len; i++) {
    data(Text_SS[i]);  // send characters to display
  }
  // clear until end of line with spaces
  for ( int i = len; i < 20; i++) {
    data(Space);          // clear with spaces until end of line
  }
}


void disp1() {                // DISPLAY TEXT
  clear_screen();
  PrintTextToLCD(1, Line1_SS);
  PrintTextToLCD(2, Line2_SS);
  PrintTextToLCD(3, Line3_SS);
  PrintTextToLCD(4, Line3_SS);
}


void disp2() {                // DISPLAY TEXT
  clear_screen();
  ret_home();                 // First Line
  for ( int i = 0; i < 20; i++) {
    data(text6[i]);
  }
  command(0xc0);              // Second Line
  for (int i = 0; i < 20; i++) {
    data(text7[i]);
  }
  command(0x94);              // Third Line
  for ( int i = 0; i < 20; i++) {
    data(text8[i]);
  }
  command(0xD4);              // Fourth Line
  for (int i = 0; i < 20; i++) {
    data(text9[i]);
  }
}

/****************************************************
               Initialization Routine
****************************************************/

void init1() {
  digitalWrite(E_Pin, LOW);
  delay(300);

  switch (InterfaceMode) {
    case 4: command(0x28);     //Enable 4-Bit Mode
      delay(5);
      break;
    case 8: command(0x38);     //Enable 8-Bit Mode
      delay(5);
      break;
  }
  command(0x08);             //Display OFF
  delay(2);
  command(0x01);             //Clear Display
  delay(2);
  command(0x06);             //Entry Mode set
  delay(2);
  command(0x02);             //Return Home
  delay(2);
  command(0x0C);             //Display ON
  delay(2);
}

/*****************************************************
            Setup Function, to run once
*****************************************************/

void setup() {
  AssignTextTo_SS();

  DDRD = 0xFF; // Enable pins on PORT D as outputs
  DDRB = 0xFF; // Enable pins on PORT B as outputs
  init1();
  delay(100);
}

/*****************************************************
            Loop Function, to run repeatedly
*****************************************************/

void loop() {
  disp1();
  delay(2500);
  disp2();
  delay(2500);
}

Ok! Now for the menu structure when you can!

I could. I have quite a lot experience with programming in general but I haven't coded such a kind of menu for some years. This means I have to analyse it myself. How it works in detail.

I will do that but I will publish it only after you have made your "homework" of learning how coding if-conditions to print "up-key" "down-key" was pressed to the serial monitor is done.

Deal! My son loves this interactive learning haha! I'll see if his brother wants to join in on this too!

at my local time it is 1:30 pm. I will go to bed now.

I think you have some code to play with

Here is a more modified display-demo-code

This version uses more self-explaining names for functions and constants
Self-explainig names make commenting obsolete as the code is explaining itself.

This code compiles. But there is a chance that there is still a bug inside
This means you have to test it on the real hardware if it really works.

/****************************************************
       PINOUT: Arduino Uno -> Character OLED
****************************************************/
// The 8 bit data bus is connected to PORTD 0-7 of the Arduino Uno
// The 4 bit data bus is connected to PORTD 4-7 of the Arduino Uno

#define E_Pin 10
#define R_W   9
#define R_S   8

#define SecondLine 0xc0
#define ThirdLine  0x94
#define FourthLine 0xD4

unsigned char InterfaceMode = 4; // 4 = 4-Bit parallel
// 8 = 8-Bit Parallel

#include <SafeString.h>

createSafeString(Line1_SS, 21); // reserve 20 bytes for the SafeString-variable
createSafeString(Line2_SS, 21); // reserve 20 bytes for the SafeString-variable
createSafeString(Line3_SS, 21); // reserve 20 bytes for the SafeString-variable
createSafeString(Line4_SS, 21); // reserve 20 bytes for the SafeString-variable


/****************************************************
                  Text Strings
****************************************************/

char const text4[] = ("   4-Bit Parallel   ");
char const text5[] = ("   8-Bit Parallel   ");

char const text6[] = ("ABCDEFGHIJKLMOPQRSTU");
char const text7[] = ("VWXYZabcdefghijklmno");
char const text8[] = ("pqrstuvwxyz123456789");
char const text9[] = (" <(' ')> || <(' ')> ");

/****************************************************
                Function Commands
****************************************************/

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//  4-bit / 8-bit 6800 Parallel Interface
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

void latch() {                 // command to latch E
  digitalWrite(E_Pin, HIGH);
  delay(1);
  digitalWrite(E_Pin, LOW);
  delay(2);
}

void command(char i) {
  switch (InterfaceMode) {

    case 4: PORTD = i;
      digitalWrite(R_S, LOW);    // Command
      digitalWrite(R_W, LOW);    // Write
      latch();                   // Latch upper 4 bits
      i = i << 4;                // shift 4 bits
      PORTD = i;                 // Take lower 4 bits
      latch();                   // Latch lower 4 bits
      break;

    case 8: PORTD = i;
      digitalWrite(R_S, LOW);    // Data
      digitalWrite(R_W, LOW);    // Write
      latch();                   // Latch 8 bits
      break;
  }
}

void SendCharToLCD(char i) {
  switch (InterfaceMode) {

    case 4: PORTD = i;
      digitalWrite(R_S, HIGH);              // Data
      digitalWrite(R_W, LOW);               // Write
      latch();                              // Latch upper 4 bits
      i = i << 4;                           // shift 4 bits
      PORTD = i;                            // Take lower 4 bits
      latch();                              // Latch lower 4 bits
      break;

    case 8: PORTD = i;
      digitalWrite(R_S, HIGH);              // Data
      digitalWrite(R_W, LOW);               // Write
      latch();                              // Latch 8 bits
      break;
  }
}

/****************************************************
                   Display Functions
****************************************************/

void clear_screen() {                   // clear display
  command(0x01);
}

void ret_home() {                       // Return to home position
  command(0x02);
}

void AssignTextTo_SS() {
  Line1_SS = "       Display      ";
  Line2_SS = "   International    ";
  Line3_SS = "   CHARACTER TEST   ";
  Line4_SS = "     Hello World    ";
}


void PrintTextToLCD(byte LineNr, SafeString& Text_SS) {

  switch (LineNr) { // set cursor to line

    case 1:
      ret_home();                 // First Line
      break; // immidiately jump down to END-OF-SWITCH

    case 2:
      command(SecondLine);             
      break; // immidiately jump down to END-OF-SWITCH

    case 3:
      command(ThirdLine);            
      break; // immidiately jump down to END-OF-SWITCH

    case 4:
      command(FourthLine);              
      break; // immidiately jump down to END-OF-SWITCH
  } // END-OF-SWITCH

  char Space = ' ';
  byte len = Text_SS.length();

  for ( int i = 0; i < len; i++) {
    SendCharToLCD(Text_SS[i]);  // send characters to display
  }
  // clear until end of line with spaces
  for ( int i = len; i < 20; i++) {
    SendCharToLCD(Space);          // clear with spaces until end of line
  }
}


void disp1() {                // DISPLAY TEXT
  clear_screen();
  PrintTextToLCD(1, Line1_SS);
  PrintTextToLCD(2, Line2_SS);
  PrintTextToLCD(3, Line3_SS);
  PrintTextToLCD(4, Line3_SS);
}


void disp2() {                // DISPLAY TEXT
  clear_screen();
  ret_home();                 
  for ( int i = 0; i < 20; i++) {
    SendCharToLCD(text6[i]);
  }
  command(SecondLine);              
  for (int i = 0; i < 20; i++) {
    SendCharToLCD(text7[i]);
  }
  command(ThirdLine);              
  for ( int i = 0; i < 20; i++) {
    SendCharToLCD(text8[i]);
  }
  command(FourthLine);              
  for (int i = 0; i < 20; i++) {
    SendCharToLCD(text9[i]);
  }
}

/****************************************************
               Initialization Routine
****************************************************/

void init1() {
  digitalWrite(E_Pin, LOW);
  delay(300);

  switch (InterfaceMode) {
    case 4: command(0x28);     //Enable 4-Bit Mode
      delay(5);
      break;
    case 8: command(0x38);     //Enable 8-Bit Mode
      delay(5);
      break;
  }
  command(0x08);             //Display OFF
  delay(2);
  command(0x01);             //Clear Display
  delay(2);
  command(0x06);             //Entry Mode set
  delay(2);
  command(0x02);             //Return Home
  delay(2);
  command(0x0C);             //Display ON
  delay(2);
}

/*****************************************************
            Setup Function, to run once
*****************************************************/

void setup() {
  AssignTextTo_SS();

  DDRD = 0xFF; // Enable pins on PORT D as outputs
  DDRB = 0xFF; // Enable pins on PORT B as outputs
  init1();
  delay(100);
}

/*****************************************************
            Loop Function, to run repeatedly
*****************************************************/

void loop() {
  disp1();
  delay(2500);
  disp2();
  delay(2500);
}

best regards Stefan

This should do:

#include <Keypad.h>

const byte ROWS = 4; //four rows
const byte COLS = 3; //three columns
char keys[ROWS][COLS] = {
  {'1','2','3'},
  {'4','5','6'},
  {'7','8','9'},
  {'#','0','*'}
};
byte rowPins[ROWS] = {5, 4, 3, 2}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {8, 7, 6}; //connect to the column pinouts of the keypad

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

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

void loop(){
  char key = keypad.getKey();

  if (key == '5'){
    Serial.println("UP");
  }
  if (key == '0'){
    Serial.println("DOWN");
  }
  if (key == '#'){
    Serial.println("SELECT");
  }
  if (key == '*'){
    Serial.println("MAIN MENU");
  }
}

did you really try that? What was code / the precise pin mapping you tried?
Imho if you get the default LiquidCrystal working it makes your life far easier.

This is the code for the US2066

case 4: PORTD = i;
      digitalWrite(R_S, HIGH);              // Data
      digitalWrite(R_W, LOW);               // Write
      latch();                              // Latch upper 4 bits
      i = i << 4;                           // shift 4 bits
      PORTD = i;                            // Take lower 4 bits
      latch();                              // Latch lower 4 bits
      break;

    case 8: PORTD = i;
      digitalWrite(R_S, HIGH);              // Data
      digitalWrite(R_W, LOW);               // Write
      latch();                              // Latch 8 bits
      break;
  }

except for cursor-columm positioning the main thing is there

Is the standard LCD based on HD44870 doing the same thing?
two things that make me doubt:
waveshare wrote a library for the arduino Mega 2560 (uses 2560-specific ports)
I took a look into liquidcrystal.h/.cpp
cursor-positioning
HD44780

#define LCD_SETDDRAMADDR 0x80
void LiquidCrystal::setCursor(uint8_t col, uint8_t row)
 command(LCD_SETDDRAMADDR | (col + _row_offsets[row]));

US2066

#define SecondLine 0xc0
command(SecondLine);             
//then start sending data

hopefully NOT! it will do it in "another" - yes "better" way -
for example the "liquid crystal" will not interfere with the lower 4 bits on port D - where we have on an UNO the serial pins, and LC will not disturb pin 2 and 3 which this code does as it writes the full 8 bits (even in 4 bit mode).

to me the control-commands seem to be very different
which would mean a lot of work to adapt it

0x80 + 0 + 0x40 is exactly the same what the example code (0xC0) does for the second row ... just saying.

You can either invest time to make the example code fit or get LiquidCrystal (or literally "any" other LCD lib) running which implements the official LCD API (known by most of the Arduino guys) and inherits from print.h. I think I don't have to explain to you what advantages this will bring for this and further projects.

I did not analyse the details of the offset-calculations. 0x40 in decimal is 64.
In the SU2066-code the rows start counting at 1
row 1: most upper (= first row) (in standard LCD row 0
row 1: one row below most upper (= second row) (in standard LCD row 1
etc.
does it still equal?
of course the fastest way to test is connecting the real US2066-driver LCD and flash a testcode
that uses the standard Liquidcrystal-library.

@ironmarvel : where did you get the code that makes an Arduino Uno work with the US2066-display?

The driver provided by the manufacturer newhaven makes use of Arduino Mega 2560-specific port-definitions.

best regards Stefan