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.