[SOLVED] Slow I2C Oled Display

I'm making a Remote Control for my RC's and now i am programming the menu.
It works fine, except for it's speed. I have to hold the push button for a long time untill the ESP32 detect it. I don't know how to optimize the code. Should I use multithreading?

Code:

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

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

#define LCDWidth                        u8g2.getDisplayWidth()
#define ALIGN_CENTER(t)                 ((LCDWidth - (u8g2.getUTF8Width(t))) / 2)
#define ALIGN_RIGHT(t)                  (LCDWidth -  u8g2.getUTF8Width(t))
#define ALIGN_LEFT                      0

U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);   // All Boards without Reset of the Display

// 'Max', 20x10px
const unsigned char maxbat [] PROGMEM = {
	0xfc, 0xff, 0x0f, 0x04, 0x00, 0x08, 0xf7, 0xde, 0x0b, 0xf1, 0xde, 0x0b, 0xf1, 0xde, 0x0b, 0xf1, 
	0xde, 0x0b, 0xf1, 0xde, 0x0b, 0xf7, 0xde, 0x0b, 0x04, 0x00, 0x08, 0xfc, 0xff, 0x0f
};
// 'Medium', 20x10px
const unsigned char medbat [] PROGMEM = {
	0xfc, 0xff, 0x0f, 0x04, 0x00, 0x08, 0x07, 0xde, 0x0b, 0x01, 0xde, 0x0b, 0x01, 0xde, 0x0b, 0x01, 
	0xde, 0x0b, 0x01, 0xde, 0x0b, 0x07, 0xde, 0x0b, 0x04, 0x00, 0x08, 0xfc, 0xff, 0x0f
};
// 'Low', 20x10px
const unsigned char lowbat [] PROGMEM = {
	0xfc, 0xff, 0x0f, 0x04, 0x00, 0x08, 0x07, 0xc0, 0x0b, 0x01, 0xc0, 0x0b, 0x01, 0xc0, 0x0b, 0x01, 
	0xc0, 0x0b, 0x01, 0xc0, 0x0b, 0x07, 0xc0, 0x0b, 0x04, 0x00, 0x08, 0xfc, 0xff, 0x0f
};

// 'ConfigIcon', 13x13px
const unsigned char configIcon [] PROGMEM = {
	0x00, 0x00, 0x4e, 0x04, 0x4a, 0x04, 0x4e, 0x04, 0x44, 0x0e, 0x44, 0x0a, 0x44, 0x0e, 0x44, 0x04, 
	0xe4, 0x04, 0xa4, 0x04, 0xe4, 0x04, 0x44, 0x04, 0x00, 0x00
};

// 'JogosIcon', 10x10px
const unsigned char JogosIcon [] PROGMEM = {
	0x02, 0x01, 0xfd, 0x02, 0x01, 0x02, 0xcd, 0x02, 0x45, 0x02, 0x01, 0x02, 0x79, 0x02, 0x85, 0x02, 
	0x85, 0x02, 0x02, 0x01
};

// 'VoltarIcon', 10x10px
const unsigned char VoltarIcon [] PROGMEM = {
	0x30, 0x00, 0x18, 0x00, 0x0c, 0x00, 0xfe, 0x00, 0x0c, 0x01, 0x18, 0x02, 0x30, 0x02, 0x00, 0x01, 
	0xfc, 0x00, 0x00, 0x00
};

// 'JoysticksIcon', 10x10px
const unsigned char JoysticksIcon [] PROGMEM = {
	0x30, 0x00, 0x78, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0x78, 0x00, 0x30, 0x00, 0x30, 0x00, 0x78, 0x00, 
	0xfe, 0x01, 0xff, 0x03
};

// 'PowerSavingIcon', 10x10px
const unsigned char PowerSavingIcon [] PROGMEM = {
	0x30, 0x00, 0xfc, 0x00, 0x84, 0x00, 0x94, 0x00, 0xa4, 0x00, 0x94, 0x00, 0xa4, 0x00, 0x94, 0x00, 
	0x84, 0x00, 0xfc, 0x00
};

// 'ControlesIcon', 10x10px
const unsigned char ControlesIcon [] PROGMEM = {
	0x78, 0x00, 0x86, 0x01, 0x32, 0x01, 0x49, 0x02, 0x49, 0x02, 0x79, 0x02, 0x49, 0x02, 0x4a, 0x01, 
	0x86, 0x01, 0x78, 0x00
};

const uint8_t* bitmaps[] = { VoltarIcon, ControlesIcon, PowerSavingIcon, JogosIcon, JoysticksIcon, VoltarIcon, VoltarIcon };
// ============================================================ //

const unsigned char *batLevel;
const unsigned char *RCbatLevel;
char * rcName = "IVECO HIGHWAY";
int canal = 123;
int start = 0;

int selected = 1; // Para os menus

unsigned long startMillis;  //some global variables available anywhere in the program
unsigned long currentMillis;
const unsigned long period = 1000;  //the value is a number of milliseconds

bool menuAtivo = false;

void setup(void) {
  u8g2.begin();
  startMillis = millis();  //initial start time

  pinMode(14, INPUT_PULLUP);
  pinMode(13, INPUT_PULLUP);
  pinMode(12, INPUT_PULLUP);

  Serial.begin(115200);
}

int x = 0;

void loop() {

  if(menuAtivo) {
    menu();
    return;
  }
  
  currentMillis = millis();  //get the current "time" (actually the number of milliseconds since the program started)
  if (currentMillis - startMillis >= period)  //test whether the period has elapsed
  {
    u8g2.clearBuffer();					// clear the internal memory
    bateria();
    u8g2.setFont(u8g2_font_amstrad_cpc_extended_8u);
    u8g2.setCursor(1, 13);
    u8g2.print(F("CANAL: "));
    u8g2.setCursor(55, 13);
    u8g2.print(canal);

    u8g2.setFont(u8g2_font_t0_11_mr);
    u8g2.setCursor(ALIGN_CENTER(rcName), 30);
    u8g2.print(rcName);
    
    // Ícones
    u8g2.drawXBMP(107, 3, 20, 10, batLevel);
    u8g2.drawXBMP(107, 52, 20, 10, RCbatLevel);
    u8g2.drawXBMP(1, 50, 13, 13, configIcon);

    u8g2.drawHLine(0, 15, 128);
    u8g2.drawHLine(0, 47, 128);

    u8g2.sendBuffer();					// transfer internal memory to the display
    x += 1;
  }

  if (x == 5) {
    menuAtivo = true;
  }
  
}

void Controles() {}

void PowerSaving() {}

void Jogos() {}

void Debug() {}

// =========MENU========= //
typedef void (*FuncPtr)();


void menu() {
  const char *options[5] = {
    " Voltar",
    " Controles",
    " Power Saving",
    " Jogos",
    " Debug"
  };

  FuncPtr funcoesMenu[5] = {
    loop,
    Controles,
    PowerSaving,
    Jogos,
    Debug
  };

  const int menuLength = sizeof(options) / sizeof(options[0]);
  const unsigned long period = 20;  // Intervalo em milissegundos
  int start = 0;
  selected = 0;  // Começa no item "Voltar"

  while (menuAtivo) {
    if (digitalRead(14) == LOW) {
      selected += 1;

      if (selected >= menuLength) {
        selected = 0;  // Volta para o primeiro item
        start = 0;
      }

      if (selected >= start + 3) {
        start += 1;  // Ajuste o inĂ­cio para rolar para baixo
      }
    }

    if (digitalRead(12) == LOW) {
      if (selected == 0) {
        selected = menuLength - 1;  // Volta para o Ășltimo item
        start = menuLength - 3;     // Ajuste o inĂ­cio para mostrar os Ășltimos itens
      } else {
        selected -= 1;
      }

      if (selected < start) {
        start -= 1;  // Ajuste o inĂ­cio para rolar para cima
      }
    }

    if (digitalRead(13) == LOW) {
      //rodando = false;
      if (selected == 0) {
        menuAtivo = false;
        return;
      }

      funcoesMenu[selected]();  // Chame a função correspondente ao item selecionado
      
    }

    currentMillis = millis();
    if (currentMillis - startMillis >= period) {
      startMillis = currentMillis;  // Reset do timer

      u8g2.clearBuffer();
      u8g2.setCursor(ALIGN_CENTER("MENU"), 13);
      u8g2.print(F("MENU"));
      u8g2.drawHLine(0, 15, 128);

      for (int i = start; i < start + 5 && i < menuLength; i++) {
        int yPos = 15 + (i - start) * 12;
        if (i == selected) {
          u8g2.setDrawColor(1);
          u8g2.drawBox(0, yPos + 6, 128, 10);
          u8g2.setDrawColor(0);
        } else {
          u8g2.setDrawColor(1);
        }
        u8g2.drawXBMP(0, yPos + 6, 10, 10, bitmaps[i]);
        u8g2.setCursor(14, yPos + 15);
        u8g2.print(options[i]);
      }

      u8g2.sendBuffer();
    }
  }
}



void bateria() {
  // LĂȘ a V. bateria
  int x = 60; // V. Ficticia (Controle)
  int y = 99; // V. Ficticia (Carrinho)
  
  if (x > 66) {
    batLevel = maxbat;
  } else if (x > 33) {
    batLevel = medbat;
  } else {
    batLevel = lowbat;
  }

  if (y > 66) {
    RCbatLevel = maxbat;
  } else if (y > 33) {
    RCbatLevel = medbat;
  } else {
    RCbatLevel = lowbat;
  }

}

The "Issue" is happening on the 'if (digitalRead(13))', 'if (digitalRead(12))', 'if (digitalRead(14))'.

As I said, I have to hold the button for a long time for the esp32 to read the click

What is "it"?

  pinMode(14, INPUT_PULLUP);
  pinMode(13, INPUT_PULLUP);
  pinMode(12, INPUT_PULLUP);

You should name these pins.

My bad.

14 scroll 1 item down
12 scroll 1 item up
13 perform the respective function

byte scrollDownPin = 14;
byte scrollUpPin = 12;
byte executePin = 13;
.
.
pinMode(scrollDownPin, INPUT_PULLUP);
pinMode(scrollUpPin, INPUT_PULLUP);
pinMode(executePin INPUT_PULLUP);
.
.
if (digitalRead(scrollUpPin) == LOW) {
.
if (digitalRead(scrollDownPin) == LOW) {
.
if (digitalRead(executePin) == LOW) {

What is "it?"

ah, yes. It's the Program speed

I'm using jumpers to simulate the button click, and if I "click" fast, the ESP32 doesn't read the click

Here's whats is happening:
ezgif-5-95ddddc2a5

But when i hold the jumper down, it works

More like it reads thousands of "clicks"... Here is an official Arduino explanation with example code on how to read your "clicks" one at a time.

https://docs.arduino.cc/built-in-examples/digital/Debounce/

Here is a simulation that you can view, try, change with a button debounce function...

Do I need to use a debounceDelay and lastDebounceTime for each button? Or just one for all?

You could make one debounce function and send the pin to debounce to the function

void setup() {
.
.
  pinMode(buttonPin, INPUT_PULLUP);
.
.
}

void loop() {
.
.
  if (buttonPin == LOW) // this button is "tied high" so a LOW is a button press
    debounceThis(buttonPin);
.
.
}

void debounceThis (byte pin) {
  // debounce code using "pin" as a local variable
}

One problem is that you are using software i2c. Hardware i2c is obviously going to be more efficient. You could also use Wire.setClock(400000) to increase the speed further.

Is there a reason you are using software i2c?

Am I correct in saying that this code is re-drawing the menu every 20ms? Drawing the menu involves printing quite a lot of text, and then updating the OLED, and this happens whether anything on the screen has changed or not?

It could be that this process takes 15ms+ and is repeated every 20ms, which might make it quite easy to miss short button presses.

I didn't even know that I was using software i2c.
Which ports on the esp32 are Hardware i2c?

I think I'm going to create a global variable that if I press any button on the controller it will become true and only when that happens the display will be updated

In the line of code I mentioned above, the _SW_I2C part indicates that software i2c is used. There will be a _HW_I2C equivalent. Check the U8G2 library documentation in case the parameter list is different.

I'm not very familiar with ESP32 and there are many different models of Arduino compatible esp32 boards. Search for a pinout of the exact model of ESP32 board you are using to find out where the default i2c pins are located.

Sounds like a sensible idea.

I changed it to U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0) and now it's incredible fast. Now it scroll through the entire menu in less than half a second!

1 Like

Great. I guess that means you are using the default hardware i2c pins. And button clicks are no longer lost?

Did you try Wire.setClock() like I suggested?

The switch to hardware I2C that I made solved my problem. I don't think I need to add Wire.setClock

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