SSD1306 - U8g2lib Framerate?

Hi there!

I want to create a pretty drawing intensive application and need high display refresh rates of my little SSD1306 oled.
At the moment Im trying different constructors of the u8g2lib and some of them do not seem to work properly.

  1. U8G2_SSD1306_128X64_NONAME_F_SW_I2C
    ----> Extremly slow, draws everything fine

  2. U8G2_SSD1306_128X64_NONAME_F_HW_I2C
    ----> Slow, draws everything fine

  3. U8G2_SSD1306_128X64_NONAME_1_HW_I2C
    ----> YAY! Super fast, BUT draws only the lower 8 horizontal pixels?!!?

I really want (3) to work as it seems to very fast but it draws only the little portion of the lower screen.

My code (simplified) for displaying animation:

unsinged long lastFrame = 0;
int frameRate = 40; //25fps

void draw()
{
  if(millis()-lastFrame >= frameRate)
  {
     lastFrame = millis();     
     u8g2->clearBuffer();
     drawLine();
     u8g2->sendBuffer();
  } 
}

void drawLine()
{
  yPos += 1;
  u8g2->drawLine(0,yPos,15,32);
  if(yPos > 64)
  {
    yPos = 0;
  }
}

Thanks in advance!

Maybe to illustrate the problem a little better see this video:

The first two constructors are 1) 1.9fps and 2) 7.7fps 3) 60.4fps

You can see, the last constructor is very fast but only draws the lower horizontal lines on refresh. Pretty awkward that in the example fps code the text is still written. But its not overwritten on refresh.

Any ideas to avoid this problem?

Thanks in advance!

Hi

Thanks a lot for doing performance measures with U8g2.

Regarding the _1_ vs. _F_ You must not use sendBuffer and clearBuffer together with the _1_ constructor.
sendBuffer and clearBuffer are only allowed for _F_ constructors.

The differences are described here: setup_tutorial · olikraus/u8g2 Wiki · GitHub

Basically you trade RAM vs. Speed, so the _1_ constructor will definitly be slower than the _F_ variant if used correctly.

Regarding your measurement: I tried hard to understand your code, but I am unable to follow, how you derive the frame rate.
I do not have an up to date measue, but I will check the current speed with u8g2.

Here is another link on this topic: http://www.best-microcontroller-projects.com/ssd1306.html

Oliver

Thanks so much your reply!

With the F constructor everthing is drawn fine but still not really the speed I want. Is it possible to achieve 25fps on an Arduino with u8g2lib?

This is my current full code:

.h-File:

#ifndef SCRoboDraw_h
#define SCRoboDraw_h

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

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

extern U8G2_SSD1306_128X64_NONAME_F_HW_I2C *u8g2;

class SCRoboDraw
{
 public:
    SCRoboDraw();
    void initScreen();
    void draw();
    void drawLine();
 private:
    unsigned long lastFrame = 0;
    int frameRate = 40; // 1000ms/25fps;
    int yPos = 0;
};

#endif

.cpp-File

#include "Arduino.h"
#include "SCRoboDraw.h"

U8G2_SSD1306_128X64_NONAME_F_HW_I2C *u8g2;

SCRoboDraw::SCRoboDraw()
{
}

void SCRoboDraw::initScreen()
{
  u8g2 = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(U8G2_R0,U8X8_PIN_NONE); 
  u8g2->begin();
}

void SCRoboDraw::draw()
{
  if(millis()-lastFrame >= frameRate)
  {
     lastFrame = millis();
     u8g2->clearBuffer();
     drawLine();
     u8g2->sendBuffer();
  } 
}

void SCRoboDraw::drawLine()
{
  yPos += 1;
  u8g2->drawLine(0,yPos,127,56);
  if(yPos > 64)
  {
    yPos = 0;
  }
}

I think you are right, 7.7 fps ( like seen in the Video) is probably the maximum with I2C. However SPI should be 2 to 3 times faster on the Uno.

Oliver

7.7fps still seems a bit slow. When running I2C at 400Khz, it should be closer to 30fps. Would you mind testing my SSD1306 library? It has a simpler API:

Larry's OLED Library

** Update **
I was curious, so I wrote a little test program to run on my ATMega32u4 (Pro Micro) running at 8Mhz and 3.3v Vcc. The test first fills the display with 0xff followed by 0x00 (20 fills). Then it draws 8x8 characters of the numbers 0 to 9 (20 times). The results are consistently 23.5 FPS. Here's the video of it in action:

Google Photos Video

Here's the code:

#include <oled_96.h>

void setup() {
  // put your setup code here, to run once:
  oledInit(0x3c, 0, 0);
}

void loop() {
  // put your main code here, to run repeatedly:
int i, j, iRate;
unsigned long ulTime;
char szTemp[32];

   oledFill(0);
   oledWriteString(0,0,"About to run",0,0);
   oledWriteString(0,1,"OLED Perf test",0,0);
   oledWriteString(0,2,"1-Erase display",0,0);
   oledWriteString(0,3,"2-Draw 8x8 chars",0,0);
   delay(4000);
   
   ulTime = millis();
   for (i=0; i<10; i++)
   {
       oledFill(0xff);
       oledFill(0x00);
   }
   ulTime = millis() - ulTime;
   iRate = 200000UL / ulTime; // 10x # of frames to show 1/10 of a FPS precision
   sprintf(szTemp, "Rate = %d.%d FPS", iRate/10, iRate % 10);
   oledWriteString(0,0,szTemp, 0,0);
   delay(4000);

   szTemp[16] = 0; // string terminator
   ulTime = millis();
   for (i=0; i<20; i++)
   {
      memset(szTemp, '0' + (i % 10), 16);
      for (j=0; j<8; j++)
         oledWriteString(0,j,szTemp,0,0);
   }
   ulTime = millis() - ulTime;
   iRate = 200000UL / ulTime;
   oledFill(0x00);
   sprintf(szTemp, "Rate = %d.%d FPS", iRate/10, iRate % 10);
   oledWriteString(0,0,szTemp, 0,0);
   delay(4000);
}

New info...
I rewrote the code to bit-bang the I2C protocol using direct port access. I'm able to get 80 FPS on a 32u4 and 86.5FPS on an ATtiny85 @ 16Mhz. I'll be sharing this code shortly...

No doubt, transfer can be much faster when we bypass digitalWrite(). My goal for U8g2 was to use only Arduino Standard functions.

Oliver

Surely Frame Rate depends on how fast you send the 1024 bytes from SRAM buffer to the OLED.
The SSD1306 hardware I2C @ 400kHz means 23ms. i.e. 40 FPS is a practical limit.

Yes, the Arduino Wire library could be improved. e.g. non-blocking
digitalWrite() is not relevant to hardware I2C at all.

Of course Oliver wants to support users who choose to mis-wire their I2C devices.
Providing a bit-banged alternative that uses digitalWrite() means it can run on ANY Arduino target.

Whereas the SSD1306 I2C interface is restricted to 400kHz, the SPI interface is 10MHz.
In theory, you should be able to achieve dramatic Frame Rates.

David.

david_prentice:
Surely Frame Rate depends on how fast you send the 1024 bytes from SRAM buffer to the OLED.
The SSD1306 hardware I2C @ 400kHz means 23ms. i.e. 40 FPS is a practical limit.

Yes, the Arduino Wire library could be improved. e.g. non-blocking
digitalWrite() is not relevant to hardware I2C at all.

Of course Oliver wants to support users who choose to mis-wire their I2C devices.
Providing a bit-banged alternative that uses digitalWrite() means it can run on ANY Arduino target.

Whereas the SSD1306 I2C interface is restricted to 400kHz, the SPI interface is 10MHz.
In theory, you should be able to achieve dramatic Frame Rates.

David.

I'm doing this experiment to see how far I can push the I2C interface of the SSD1306 and the AVR. I'm at the point now where I'm "uglifying" the code to get speed improvements. I'm keeping it in C++, but monitoring the compiler output to make sure it does what I want. For example, it's ignoring my "inline" requests for certain functions, so I'm going to brute force inline them instead. Of course the SPI interface can achieve higher speeds, but the I2C interface is talking to the same chip (and is the more popular version of this display configuration). I'm up to 129FPS and there's still more room for improvement.

Your oledLoadBMP() ignores the colour Palette in the BMP header.

A "Horse" animation runs at a similar speed with oledLoadBMP() as drawBitmap() from the Adafruit_SSD1306 library.

Yes, I am sure that you can optimise the screen drawing. But you have to respect the datasheet I2C limits.

David.

david_prentice:
Your oledLoadBMP() ignores the colour Palette in the BMP header.

A "Horse" animation runs at a similar speed with oledLoadBMP() as drawBitmap() from the Adafruit_SSD1306 library.

Yes, I am sure that you can optimise the screen drawing. But you have to respect the datasheet I2C limits.

David.

I haven't updated my public library with the new code I'm working on. It makes sense that the Adafruit code draws the bitmap at similar speed because both of our libraries use the Wire library and tell it to set the I2C hardware clock speed to 400Khz. What I'm saying is that for the SSD1306, we don't have to respect the I2C limits because the chip's serial logic (probably the same shift registers) handle 10Mhz from SPI. I'm feeding it irregular timed signals, some with less than 500ns between state changes, yet it still works fine.

I agree with you. The SSD1306 may well accept faster I2C than the datasheet specifies.

The SPI interface on the ILI9341 works at far greater speeds than officially specified.
However the parallel timings are accurate.

I would not attempt to run the AVR TWI interface faster than 400kHz but you can certainly use the TWI peripheral more efficiently.

David.

Don't know if this thread is still alive, but I have been playing around with higher refresh rates and would like to know if my findings are valid or not.

I am using an esp32 as the mcu and a 128x64 OLED over I2C. I drew 8192 frames, with a 1ms delay between, (I could tweak this to go faster but haven't yet) and then measured the time to draw these 8192 frames.

The time to draw these frames ranged between 16.147 and 16.151 seconds. Doing basic math, this equals a framerate of over 500 fps...

However I do know that FPS is not equal to refresh rate, so I took my 720fps slow motion camera, pointed it at the OLED, and observed that every single frame was displayed.

Does this mean that the OLED was refreshing at over 500hz?

Think about it. The SSD1306 displays the "frame" in its internal GDRAM at a certain scan rate. The Cathode Ray Tube in an old TV has an electron beam that scans horizontally drawing each line down the screen.

OLEDs and TFTs do much the same thing. You can do a single scan or an interlaced scan. A TV used an interlaced scan.

You can write to the GDRAM faster than the GDRAM is displayed by the regular Frame scan.

For example. Your OLED or TFT might display at 70Hz or so. A human eye sees a steady picture.
An MCU can send a whole set of 128x64 pixels in about 1024us with 8MHz SPI or 23040us with 400kHz I2C.

The SSD1306 specifies max SPI=10MHz and I2C=400kHz
Your ESP32 can achieve faster SPI and faster I2C speeds. In practice, my SSD1306 will work faster than the spec.

Motion pictures will look quite smooth at 30 FPS.
Clearly you can send 1000 frames a second to the GDRAM (via SPI) but you would never see all 1000 frames.

Even with I2C you can get 41 FPS. My SSD1306 will (just) work with 800kHz I2C i.e. 83 FPS.

You would need to use a faster SSD1306 scan rate to display separate frames.

David.

I am not disagreeing with the logic behind it, however, look at the following picture.

I am testing the refresh rate by setting a single pixel at a time, filling the display, top left to bottom right, with 4ms delay nested in the loop. This results in 40.005 seconds to draw 8192 frames, giving an average frame rate of 204.77 FPS.

Setting my slow-mo camera to 240 fps. I was able to capture individual pixel changes on most occasions. As you can see from the second and fourth frames. In the third frame 2 pixel were changed which is probably down to slight timings. I cannot set the slow motion frame rate any higher as the resolution drops and I can no longer discern the pixels.

Seeing single pixel changes means that the display is showing 204.77 pixels per second, keeping up with the ESP32's FPS output.

Is there an explanation on this?

Your camera is capturing a frame every 4.17 ms
Your sketch is averaging a new frame every 4.88ms

I suspect that what this actually means is that most frames appear at 4.16ms and occasional frames appear at more than 4.88ms.

Easy enough to see with a Logic Analyser.
Bear in mind that the ESP32 will probably be blitting the SSD1306 using DMA.
The ESP32 has lots of other tasks to get on with. e.g. operating the Wireless whether you asked it or not.

Does it matter? A human can't see 240FPS. I don't know how quickly the OLED pixels can actually change.

Monochrome animations work quite nicely on the OLED.
Now run the same animation on a GLCD. You just get a blurry mess.

Regardless of Wireless, it seems unlikely that your 4.17ms period capture would ever catch a 2-pixel change.

If you do not have a Logic Analyser, post your code. Someone might try it for themselves.

David.

There are two additions to u8g2:

I have added "setBusClock" to allow manual tuning of the I2C (and SPI) clock speed. This function is already available in the u8g2 version of the Arduino IDE.

Another speed improvement is the upcoming option to update only parts of the screen. This is discussed here. This issue also contains some working code for this.

Oliver

I was intrigued by the theoretical FPS (Frames Per Second) that can be achieved with a 128x64 SSD1306.

Normally you would display an XBM bitmap or an Adafruit bitmap.
An animation simply consists of displaying each frame in sequence.

I have attached a sketch that animates 15 frames on a SSD1306.
The time for 15 frames is printed on the Serial Terminal. 15 frames at 30 FPS would take 500 ms.

Since your "animation" is in Flash, you might just as well pre-process it into the actual format used in the library screen buffer.

The result is dramatic. I can achieve 240 FPS on a Uno using SPI !! (280 FPS with Adafruit_SSD1306)

Note that the sketch uses current versions of U8g2lib and Adafruit_SSD1306.
You can change bus speeds with u8g2.setBusClock()
Or with the new full-fat constructors from Adafruit_SSD1306

David.

p.s. does anyone have any nice monochrome animations suitable for 128x64?
A Uno can store up to 20 full frames in Flash memory.
Monochrome Line animations can work with bigger TFT screens e.g. 240x240
I can resize a GIF, JPG, ... on a PC. And convert to the RAW buffer format.

OLED_fps.zip (20.7 KB)