Tempo calculation errors when sending fast MIDI CC messages to oled display

Hi. First of all, I'm a beginner at programming and English is not my main language, but I'll try my best to be understood. I'm trying to put an OLED SSD1306 128x64 i2c display in my Arduino MIDI project. Arduino receives MIDI clock and CC messages, then send pulses (off of this minimal code) to optocouplers to act as an on/off switch and tap tempo to a delay guitar pedal. Got the code working, but the main problem is when I try to put an i2c OLED display to show tempo and CC messages. With slow CC messages there is no problem, but with quick MIDI CC messages, it messes with tempo calculation during the stream of CC messages.
I'm using a Pro Micro, MIDI libray (GitHub - FortySevenEffects/arduino_midi_library: MIDI for Arduino) and Adafruit_SSD1306 library (GitHub - adafruit/Adafruit_SSD1306: Arduino library for SSD1306 monochrome 128x64 and 128x32 OLEDs)
MIDI Clock and CC messages are received from a Boss MS-3.
This is the code that has problems. Serial.prints are commented. There are obvious comments because I'm learning.

#include <MIDI.h>                                             //MIDI library.

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);          //Arduino Leonardo/Sparkfun Pro Micro use Serial1.

#include <Adafruit_SSD1306.h>                                 //SSD1306 library

#define OLED_Address 0x3C                                     //i2c address (behind display)
#define SCREEN_WIDTH 128                                      //Use const byte. Oled Width=128 pixels
#define SCREEN_HEIGHT 64                                      //Use const byte. Oled Height=64 pixels

Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT);           //Creates "display" 128x64px. &Wire and -1 Reset as default.

byte channel = 16;                                            //1-16 for testing display caracters max length
byte number = 127;                                            //0-127 for testing display caracters max length
byte value = 127;                                             //0-127 for testing display caracters max length

unsigned long currentMicros;
unsigned long previousMicros;
unsigned long beatLength;

unsigned int tempo = 120;                                     //round(60000000./beatLength (in micros)) 60000000us = 1s.

int clockCounter = 0;                                         //0 to 255, no more than 24 Midi Clock pulses by beat to calculate tempo.

void setup()
{
  Serial.begin(38400);                                        //Inicialize Arduino IDE serial monitor for debugging.

  MIDI.begin(MIDI_CHANNEL_OMNI);                              //Inicialize MIDI library. Deault channel = 1 (it can be set between ()). MIDI_CHANNEL_OMNI listens all channels. MIDI_CHANNEL_OFF = no input.
  MIDI.turnThruOff();                                         //Turn Off MIDI THRU. More performance (more counters in void loop)
  MIDI.setHandleControlChange(myControlChange);               //setHandleControlChange calls "myControlChange" function when Controls Changes messages arrive.
  MIDI.setHandleClock(myClock);                               //setHandleClock calls "myClock" function when Midi Clock msj arrive. If clock msj (0xF8, 248) then count up to 24 repeats to calculate tempo.

  oled.begin(SSD1306_SWITCHCAPVCC, OLED_Address);             //Inicialize oled with 0x3C i2c address (behind display)
  oled.clearDisplay();                                        //Clear the display buffer

  char bufSetupCC [22];
  oled.setTextSize(1);
  oled.setCursor(0, 0);
  oled.setTextColor(WHITE, BLACK);
  sprintf(bufSetupCC, "CH:%2d CC#:%3d Val:%3d", channel, number, value);
  oled.print(bufSetupCC);

  char bufSetupTempo [8];
  oled.setTextSize(3);
  oled.setCursor(0, 20);
  sprintf(bufSetupTempo, "%3d BPM", tempo);
  oled.print(bufSetupTempo);

  oled.display();
}

void loop()
{
  MIDI.read();                                                //Continuos reads if midi messages arrives. Channels can be setted between () (redundant with MIDI.begin? Test)
}

void myControlChange(byte ch, byte cc, byte val)              //Defines every parameter of a Control Change message Channel, Number and Value as byte (no more than 128 values: 0 to 127)
{
  channel = ch;
  number = cc;
  value = val;

  ccDisplay();                                                //comment this and uncomment in updateClock to display every 8 clockCounters.
}

void myClock()
{
  if (updateClock())
  {
    currentMicros = micros();
    beatLength = currentMicros - previousMicros;
    previousMicros = currentMicros;
    tempo = round(60000000. / beatLength);                    //tempo calculation. Using micros is more precise in higher tempos. round is not really needed.

    tempoDisplay();                                           //Sends to tempoDisplay every 24 clockCounter.
  }
}

bool updateClock()
{
  /*if (clockCounter == 6 || clockCounter == 12 || clockCounter == 18)        //Sends to ccDisplay every 8 clockCounter. Ok for high tempos, slow por low tempos.
    {
    ccDisplay();
    }*/

  if (clockCounter == 24)                                     //If 24 loops, 24 Midi Clock pulses by beat, then resets counters. If not, +1 counter until 24.
  {
    clockCounter = 1;                                         //Resets counter.
  }
  else
  {
    clockCounter++;                                           //+1 up to 24.
  }
  return clockCounter == 1;
}
void ccDisplay()
{
  /*char bufCCSerial[22];                                     //unccoment to Serial.print
    sprintf(bufCCSerial, "CH:%2d CC#:%3d Val:%3d", channel, number, value);
    Serial.println(bufCCSerial);*/                                //Print CC message in serial monitor if arrives

  char buf1 [3];                                              //char needs 1 more control caracter at final
  oled.setTextSize(1);
  oled.setCursor(17, 0);
  sprintf(buf1, "%2d", channel);
  oled.print(buf1);

  char buf2 [4];
  oled.setCursor(59, 0);
  sprintf(buf2, "%3d", number);
  oled.print(buf2);

  char buf3 [4];
  oled.setCursor(107, 0);
  sprintf(buf3, "%3d", value);
  oled.print(buf3);

  oled.display();
}

void tempoDisplay()
{
  /*char bufTempoSerial[8];                                    //uncomment to Serial.print
    sprintf(bufTempoSerial, "%3d BPM", tempo);
    Serial.println(bufTempoSerial);*/                            //Prints tempo in BPM.

  char buf4 [4];
  oled.setTextSize(3);
  oled.setCursor(0, 20);
  sprintf(buf4, "%3d", tempo);
  oled.print(buf4);

  oled.display();
}

I'm testing 100 MIDI CC messages in a stream (CH1, CC#1, Value: 0 to 100) that comes in 2500ms approx.
If I Serial.print CC messages and tempo calculation, it works ok. The problem is when I try the same with printing to display, it messes with tempo calculation.
The best I could achieve is printing every 6 clocks, so it depends on how fast is tempo. It means, for example, at 250bpm, screen refresh is every 60ms, but at 60bpm scree refresh is every 250ms.
Other failure test was put

if (clockCounter == 6 || clockCounter == 12 || clockCounter == 18)
  {
    oled.display();
  }

in ccDisplay. It messes tempo calculation as well and screen refreshes at random times.

I'm lost in thinking a way to refresh the OLED at a limit of 60ms when a CC message arrives and refresh rate not to be tempo dependant.
I don't need screen refresh all the time, only when tempo changes and CC arrives.
I don't know if there is a buffer overload or where the code stops, but I think it has to be something with oled.display that delays time measure in myClock function.
I don't think that another SSD1306 but in SPI solve this problem, but maybe could help?
If you need further information, please tell me, I'll try my best.

Thanks in advance.

Well I do think it would solve your problem. But before you go there try to increase the speed of the I2C bus.

I2C is slow, the normal clock speed is 100KHz, and it takes 10 clock cycles to transfer one byte and there is overheads to send as well as the data. Also make sure you have a 4K7 pull up resistor on each of the I2C lines. I don’t know off hand what calls there are to change this speed but there will be in the Adafruit library as an option. The normal next speed up is 400KHz, and then you can try 1MHz.

With SPI you can run much faster, 1MHz minimum.

You could also try another library, one that doesn’t write a whole screen at once but only the bits you want to refresh.

You could limit the display only when the tempo changes by making a note of the last tempo in a variable and comparing this with an if statement to the new value and only update if they are different.

Or to limit the speed of refresh only display when so many milliseconds have passed using the millis () timer, just like you do in the blink without delay example in the IDE.

Thanks for the responses.
I'm only printing to screen the values that change. I put a photo of the display, it seems to have all pull up resistors.
Increasing clock speed to 800KHz made an improvement (I think 800KHz is the limit, because 1MHz breaks what is displayed). Now I can print to screen every 4 MIDI Clock messages. Still refresh rate depends on tempo, and if I print every CC message it messes with tempo calculation. I think I will try getting an SPI OLED.
This is the code with Adafruit Library. I add printing tempo only if it changes. Note that I try printing every 250ms in loop() and ccDisplay() without success.

#include <MIDI.h>                                             //MIDI library.

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);          //Arduino Leonardo/Sparkfun Pro Micro use Serial1.

#include <Adafruit_SSD1306.h>                                 //SSD1306 library

#define OLED_Address 0x3C                                     //i2c address (behind display)
#define SCREEN_WIDTH 128                                      //Use const byte. Oled Width=128 pixels
#define SCREEN_HEIGHT 64                                      //Use const byte. Oled Height=64 pixels

//Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT);           //Creates "oled" 128x64px. &Wire and -1 Reset as default.
//Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1, 400000, 400000);  //doesn´t work. tempo varies
//Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1, 1000000, 1000000);  //doesn´t work, clkDuring=1MHz to much for this display, brokes what is displayed.

Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1, 800000, 100000);  //clkDuring=800KHz. clkDuring value makes an improvement, clkAfter (100KHz default, 400KHz, 800KHz, 1MHz) doesn't. Works printing every 4 clockCounters. Doesn´t work with ccDisplay() in MyControlChange(), tempo varies

byte channel = 16;                                            //1-16 for testing display caracters max length
byte number = 127;                                            //0-127 for testing display caracters max length
byte value = 127;                                             //0-127 for testing display caracters max length

unsigned long currentMicros;
unsigned long previousMicros;
unsigned long beatLength;

unsigned int tempo = 120;                                     //round(60000000./beatLength (in micros)) 60000000us = 1s.
unsigned int prevTempo;                                       //Previous Tempo to store and detect tempo changes.

int clockCounter = 0;                                         //0 to 255, no more than 24 Midi Clock pulses by beat to calculate tempo.

unsigned long previousCCTime;
unsigned long ccTime;

void setup()
{
  Serial.begin(38400);                                        //Inicialize Arduino IDE serial monitor for debugging.

  MIDI.begin(MIDI_CHANNEL_OMNI);                              //Inicialize MIDI library. Deault channel = 1 (it can be set between ()). MIDI_CHANNEL_OMNI listens all channels. MIDI_CHANNEL_OFF = no input.
  MIDI.turnThruOff();                                         //Turn Off MIDI THRU. More performance (more counters in void loop)
  MIDI.setHandleControlChange(myControlChange);               //setHandleControlChange calls "myControlChange" function when Controls Changes messages arrive.
  MIDI.setHandleClock(myClock);                               //setHandleClock calls "myClock" function when Midi Clock msj arrive. If clock msj (0xF8, 248) then count up to 24 repeats to calculate tempo.

  oled.begin(SSD1306_SWITCHCAPVCC, OLED_Address);             //Inicialize oled with 0x3C i2c address (behind display)
  oled.clearDisplay();                                        //Clear the display buffer

  char bufSetupCC [22];
  oled.setTextSize(1);
  oled.setCursor(0, 0);
  oled.setTextColor(WHITE, BLACK);
  sprintf(bufSetupCC, "CH:%2d CC#:%3d Val:%3d", channel, number, value);
  oled.print(bufSetupCC);

  char bufSetupTempo [8];
  oled.setTextSize(3);
  oled.setCursor(0, 20);
  sprintf(bufSetupTempo, "%3d BPM", tempo);
  oled.print(bufSetupTempo);

  oled.display();
}

void loop()
{
  MIDI.read();                                                //Continuos reads if midi messages arrives. Channels can be setted between () (redundant with MIDI.begin? Test)

  /*ccTime = millis();
    if((ccTime - previousCCTime) > 250)                         //Doesn't work, tempo varies with only MIDI clock messages
    {
    previousCCTime = ccTime;
    ccDisplay();
    }*/
}

void myControlChange(byte ch, byte cc, byte val)              //Defines every parameter of a Control Change message Channel, Number and Value as byte (no more than 128 values: 0 to 127)
{
  channel = ch;
  number = cc;
  value = val;

  ccDisplay();                                                //comment this and uncomment in updateClock to display every 8 clockCounters.
}

void myClock()
{
  if (updateClock())
  {
    currentMicros = micros();
    beatLength = currentMicros - previousMicros;
    previousMicros = currentMicros;
    tempo = round(60000000. / beatLength);                    //tempo calculation. Using micros is more precise in higher tempos. round is not really needed.

    //tempoDisplay();                                           //Sends to tempoDisplay every 24 clockCounter.
  }

  if (tempo != prevTempo && tempo < 1000)                     //Detects if tempo changes and tempo < 1000.
  {
    tempoDisplay();                                           //Sends to tempoDisplay if tempo changes.
  }
  prevTempo = tempo;                                          //Remembers last tempo to compare with new tempo.
}

bool updateClock()
{
  /*if (clockCounter == 6 || clockCounter == 12 || clockCounter == 18)        //OK with dedault clkDuring(400Khz) Sends to ccDisplay every 6 clockCounter. Ok for high tempos, slow por low tempos.
    {
    ccDisplay();
    }*/

  /*if (clockCounter == 4 || clockCounter == 8 || clockCounter == 12 || clockCounter == 16 || clockCounter == 20)        //OK with clkDuring=800KHz. Sends to ccDisplay every 4 clockCounter. Ok for high tempos, slow por low tempos.
    {
    ccDisplay();
    }*/

  if (clockCounter == 24)                                     //If 24 loops, 24 Midi Clock pulses by beat, then resets counters. If not, +1 counter until 24.
  {
    clockCounter = 1;                                         //Resets counter.
  }
  else
  {
    clockCounter++;                                           //+1 up to 24.
  }
  return clockCounter == 1;
}
void ccDisplay()
{
  /*char bufCCSerial[22];                                     //unccoment to Serial.print
    sprintf(bufCCSerial, "CH:%2d CC#:%3d Val:%3d", channel, number, value);
    Serial.println(bufCCSerial);*/                                //Print CC message in serial monitor if arrives

  char buf1 [3];                                              //char needs 1 more control caracter at final
  oled.setTextSize(1);
  oled.setCursor(17, 0);
  sprintf(buf1, "%2d", channel);
  oled.print(buf1);

  char buf2 [4];
  oled.setCursor(59, 0);
  sprintf(buf2, "%3d", number);
  oled.print(buf2);

  char buf3 [4];
  oled.setCursor(107, 0);
  sprintf(buf3, "%3d", value);
  oled.print(buf3);

  oled.display();

  /*ccTime = millis();
    if((ccTime - previousCCTime) > 250)                         //Doesn't work, tempo varies with only MIDI clock messages
    {
    previousCCTime = ccTime;
    oled.display();
    }*/
}

void tempoDisplay()
{
  /*char bufTempoSerial[8];                                    //uncomment to Serial.print
    sprintf(bufTempoSerial, "%3d BPM", tempo);
    Serial.println(bufTempoSerial);*/                            //Prints tempo in BPM.

  char buf4 [4];
  oled.setTextSize(3);
  oled.setCursor(0, 20);
  sprintf(buf4, "%3d", tempo);
  oled.print(buf4);

  oled.display();
}

This is the code with u8x8 part of u8g2 library (GitHub - olikraus/u8g2: U8glib library for monochrome displays, version 2). It works better than Adafruit Library in printing every CC message, still messes with tempo calculation but way less than Adafruit Library. For some reason I cannot make it print every "n" MIDI clock messages (clockCounter). Printing every 250ms doesn't work either.

#include <MIDI.h>                                             //MIDI library.

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);          //Arduino Leonardo/Sparkfun Pro Micro use Serial1.

#include <U8x8lib.h>                                          //u8x8 library

U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE, /* clock=*/ 3, /* data=*/ 2);   //HW I2C

byte channel = 16;                                            //1-16 for testing display caracters max length
byte number = 127;                                            //0-127 for testing display caracters max length
byte value = 127;                                             //0-127 for testing display caracters max length

unsigned long currentMicros;
unsigned long previousMicros;
unsigned long beatLength;

unsigned int tempo = 120;                                     //round(60000000./beatLength (in micros)) 60000000us = 1s.
unsigned int prevTempo;                                       //Previous Tempo to store and detect tempo changes.

int clockCounter = 0;                                         //0 to 255, no more than 24 Midi Clock pulses by beat to calculate tempo.

unsigned long previousCCTime;
unsigned long ccTime;

void setup()
{
  Serial.begin(38400);                                        //Inicialize Arduino IDE serial monitor for debugging.

  MIDI.begin(MIDI_CHANNEL_OMNI);                              //Inicialize MIDI library. Deault channel = 1 (it can be set between ()). MIDI_CHANNEL_OMNI listens all channels. MIDI_CHANNEL_OFF = no input.
  MIDI.turnThruOff();                                         //Turn Off MIDI THRU. More performance (more counters in void loop)
  MIDI.setHandleControlChange(myControlChange);               //setHandleControlChange calls "myControlChange" function when Controls Changes messages arrive.
  MIDI.setHandleClock(myClock);                               //setHandleClock calls "myClock" function when Midi Clock msj arrive. If clock msj (0xF8, 248) then count up to 24 repeats to calculate tempo.

  u8x8.begin();
  
  u8x8.setFont(u8x8_font_amstrad_cpc_extended_r);
  u8x8.setCursor(0, 0);
  u8x8.print("CH   CC#   Val");
  u8x8.setCursor(0, 1);
  u8x8.print("16   127   127");

  u8x8.setFont(u8x8_font_profont29_2x3_r);
  u8x8.setCursor(0, 3);
  u8x8.print("120 BPM");
}

void loop()
{
  MIDI.read();                                                //Continuos reads if midi messages arrives. Channels can be setted between () (redundant with MIDI.begin? Test)

  /*ccTime = millis();                                          
  if((ccTime - previousCCTime) > 250)                         //Doesn't work, tempo varies with only MIDI clock messages
  {
    previousCCTime = ccTime;
    ccDisplay();
  }*/
}

void myControlChange(byte ch, byte cc, byte val)              //Defines every parameter of a Control Change message Channel, Number and Value as byte (no more than 128 values: 0 to 127)
{
  channel = ch;
  number = cc;
  value = val;

  ccDisplay();                                                //comment here and uncomment in updateClock to display every 6 clockCounters.
}

void myClock()
{
  if (updateClock())
  {
    currentMicros = micros();
    beatLength = currentMicros - previousMicros;
    previousMicros = currentMicros;
    tempo = round(60000000. / beatLength);                    //tempo calculation. Using micros is more precise in higher tempos. round is not really needed.

    //tempoDisplay();                                           //Sends to tempoDisplay every 24 clockCounter.
  }

  if (tempo != prevTempo && tempo < 1000)                     //Detects if tempo changes and tempo < 1000.
  {
    tempoDisplay();                                           //Sends to tempoDisplay if tempo changes.
  }
  prevTempo = tempo;                                          //Remembers last tempo to compare with new tempo.
}

bool updateClock()
{
  /*if (clockCounter == 6 || clockCounter == 12 || clockCounter == 18)        //Doesn't work with u8x8. Sends to ccDisplay every 6 clockCounter. Ok for high tempos, slow por low tempos.
    {
    ccDisplay();
    }*/

  if (clockCounter == 24)                                     //If 24 loops, 24 Midi Clock pulses by beat, then resets counters. If not, +1 counter until 24.
  {
    clockCounter = 1;                                         //Resets counter.
  }
  else
  {
    clockCounter++;                                           //+1 up to 24.
  }
  return clockCounter == 1;
}
void ccDisplay()
{
  /*char bufCCSerial[22];                                     //unccoment to Serial.print
    sprintf(bufCCSerial, "CH:%2d CC#:%3d Val:%3d", channel, number, value);
    Serial.println(bufCCSerial);*/                                //Print CC message in serial monitor if arrives

  char buf1[3];
  u8x8.setFont(u8x8_font_amstrad_cpc_extended_r);
  sprintf(buf1, "%2d", channel);
  u8x8.drawString(0, 1, buf1);

  char buf2[4];
  u8x8.setFont(u8x8_font_amstrad_cpc_extended_r);
  sprintf(buf2, "%3d", number);
  u8x8.drawString(5, 1, buf2);

  char buf3[4];
  u8x8.setFont(u8x8_font_amstrad_cpc_extended_r);
  sprintf(buf3, "%3d", value);
  u8x8.drawString(11, 1, buf3);

  /*ccTime = millis();                                          
  if((ccTime - previousCCTime) > 250)                         //Doesn't work, tempo varies, and doesn't display last CC message
  {
    previousCCTime = ccTime;
    char buf1[3];
    u8x8.setFont(u8x8_font_amstrad_cpc_extended_r);
    sprintf(buf1, "%2d", channel);
    u8x8.drawString(0, 1, buf1);
  
    char buf2[4];
    u8x8.setFont(u8x8_font_amstrad_cpc_extended_r);
    sprintf(buf2, "%3d", number);
    u8x8.drawString(5, 1, buf2);
  
    char buf3[4];
    u8x8.setFont(u8x8_font_amstrad_cpc_extended_r);
    sprintf(buf3, "%3d", value);
    u8x8.drawString(11, 1, buf3);
  }*/
}

void tempoDisplay()
{
  /*char bufTempoSerial[8];                                    //uncomment to Serial.print
    sprintf(bufTempoSerial, "%3d BPM", tempo);
    Serial.println(bufTempoSerial);*/                            //Prints tempo in BPM.

  char buf4[4];
  u8x8.setFont(u8x8_font_profont29_2x3_r);
  sprintf(buf4, "%3d", tempo);
  u8x8.drawString(0, 3, buf4);
}

I'll try with the SPI version of this display.
Thanks!

Doesn’t look like an SPI display to me, you would expect 5 connectors on an SPI not 4.
+, GND, clock, data and chip select. Without the latter you can’t have more than one device on the bus.

Yes, the display I have now is i2c. I´ll try to get the SPI variant with 5 pins beside + and GND.