Description
I've tried using this library with a Wemos D1 mini and a Wemos OLED Shield since this is one of the major libraries out there that handles the 64x48 resolution displays, but I have found it to be way too slow.
On a 160Mhz ESP8266 MCU, the display update time (just the .display() call) took 118ms.
As noted in issue #11 , the library uses the default 100KHz I2C bus speed. Changing it to 700+KHz decreased the update time to 17ms. Not bad, considering the ESP8266 has only a software I2C (more on that later), but still too slow, especially considering the tiny screen size.
It is only when I looked deeper into how the data was sent to the display that I noticed that it was sending the screen data one byte at a time, each with the overhead of the I2c address + control & command byte, so for each byte of the screen data, it was sending 3 bytes on the wire. In addition to that, it was sending 12 (6*2) extra messages to set the page and column address for the page of data being sent, so that extra time on the wire.
void MicroOLED::display(void) {
uint8_t i, j;
for (i=0; i<6; i++) {
setPageAddress(i);
setColumnAddress(0);
for (j=0;j<0x40;j++) {
data(screenmemory[i*0x40+j]);
}
}
}
All of this seems completely unnecessary, especially considering that SSD1306 supports plain horizontal addressing mode that matches the memory buffer structure in screenmemory
and auto-increments the current address pointer with rollover to the beginning.
We can easily set this mode in the begin()
method:
// Set horizontal addressing mode
command(0x20);
command(0x00);
// Set column address (64 columns)
command(0x21);
command(32 + 0);
command(32 + 63);
// Set page address (6 pages, 8 each for 48 rows)
command(0x22);
command(0);
Then we could just write the entire block in pretty much one go. Well, technically two, since the protocol requires the first byte to be ack'ed:
void MicroOLED::display(void) {
uint8_t buffer[2];
// Store the first byte so we can reuse the screenmemory and not have to
// copy it into a separate buffer.
uint8_t firstByte = screenmemory[0];
// Write the first byte.
buffer[0] = 0x40;
buffer[1] = firstByte;
twi_writeTo(i2c_address, buffer, 2, false);
// Write the rest.
screenmemory[0] = 0x40;
twi_writeTo(i2c_address, screenmemory, 384, true);
screenmemory[0] = firstByte;
}
Doing this speeds things up quite a bit, bringing it down to 5ms.
As mentioned earlier, ESP8266 doesn't have a hardware I2C. Using a faster software I2C implementation (https://github.com/pasko-zh/brzo_i2c), the time went down to 3-4ms, but I didn't want to bring in yet another library, so I decided to stick with the above implementation.
Looking at the Arduino's Wire API (https://www.arduino.cc/en/Reference/Wire), they mention that the default implementation only has a 32-byte buffer, and if we were to send more in one transmission, it would be discarded. So I suppose that may have served the reason for this implementation. Still, Adafruit's SSD1306 library (https://github.com/adafruit/Adafruit_SSD1306) does at least some batching and sends data 16 bytes at a time, so the overhead is much smaller. Plus, it uses the horizontal mode addressing, so the extra transmissions to set the page and column sizes are not needed.