Optimized SSD1351 library with buffering

Status
Not open for further replies.

kirberich

Member
After playing around with an SSD1351 for a little while a few weeks ago, I decided to port Paul's optimisations for the ILI9351 display to SSD1351 OLED displays to try and understand some low-level things about the teensy as well as the display itself. It's not finished, I've removed some bits like rotation and font support for now because I wanted to make this work first.

The fastest frame rate achievable is about 80fps, using no buffering and low colour mode, writing a solid colour to the screen.

In most cases, the buffered version is faster than the non-buffered version, going up to significantly faster when doing lots of random access pixel manipulation. Filling the whole display with drawPixel goes up to ~60fps with buffering, ~11 without. Additionally, single buffering means no flickering.

The API is pretty much the same as the normal Adafruit GFX library, with the addition of having to call updateDisplay() to send data to the screen once per frame for the buffered modes.

My next step is bit-packing the HighColor mode to reduce the RAM usage from 50kb (128x128 x 3 bytes) to 36kb (128x128 x 2.2bytes), then adding a 256 color indexed mode and finally hopefully double buffering.

You can find the library here: https://github.com/kirberich/teensy_ssd1351

Some gotchas:
  • Currently doesn't run when overclocking the teensy
  • I've only tried this on the Adafruit 128x96 version of the SSD1351

Here's a quick example video of it in action: https://www.youtube.com/watch?v=unFFJBnWf4U
 
Wow great work!

Is there a chance that this would also work on the adafruit feather 32u4 bluefruit?
I need the bluetooth functionality of this board as well as these super fast update rates.

Any chance we can talk once on skype?

Best,
Rik
 
Thank you!

The very short answer to your question:

If you want to use the library as it is, then your easiest choice is to ditch the feather and get a BLE module and use it on a teensy, or use the feather just for communications and use the teensy as a graphics chip (though you'll still have speed problems then).

Longer answer:

The approach I've taken here doesn't really translate to a chip like the 32u4. The raw speed improvements in this mostly come from using the SPI FIFOs, hardware chip select pins and high clock speed of the teensy - the 32u4 doesn't have any of those.

The other big improvement is the buffer, which allows the communication with the display to be much quicker for pixel-level operations. That buffer needs a lot of ram. Even in 256 colour mode, the 128x96 version of the display still needs about 12kb of ram, 6 times what the feather board has.

This isn't to say you couldn't do some optimisations for the board you're using - I don't know what you're currently using, but if you haven't looked at u8glib yet, that might be fast enough for you. Otherwise I suggest doing what did at the start of this - just write a test sketch that sends the SPI commands directly, so you can see the maximum rate at which you can talk to the display. You could take the startup sequence from my code, you just need to rewrite the SPI data sending functions.

Once you've got the display set up, to get the maximum frame rate, just do what updateScreen in my library does, which is to set the display's video ram to the whole display area and then just push out colours for every pixel.

In my first tests, the biggest speed improvements came from setting the D/C pin as quickly as possible, in the case of the teensy replacing digitalWrite with the hardware-chip-select functionality. You can do something similar though by just writing a bit mask straight to the appropriate pin register of the 32u4. The register manipulation stuff can seem a bit daunting if you haven't used it before, but here's a pretty good writeup about it: http://r6500.blogspot.co.uk/2014/12/low-level-gpio-on-arduino-leonardo.html.

Let me know if this helps! Depending on how you're trying to use the display there might also be other optimisations that might be possible, like setting the correct colour depths, drawing pixel data that is saved in the flash instead of the RAM, etc. If you can let me know what exactly you're trying to display on the display, I might be able to give some more specific advice.
 
Amazing work! I knew someone could come up with something fast for the SSD1351, finally! The Adafruit library performance is ridiculous and u8glib is better but still unacceptable IMO. The display is really what is holding my whole sketch back so I'm happy to see this.

I have a few questions:

1. I'm curious why you're using an include file for your whole class (no cpp file). Does I'm missing something or it was just more of a shortcut (I'm a dev but not C++ dev) :p
2. The benchmarks about all color types (high, low, indexed) are pretty cool, maybe just say how many colors exactly each mode are for the less experts of us :)
3. When you say not overclocked Teensy, I'm assuming you mean 72mhz ? I believe 96mhz is the default CPU frequency tho, any plan to support more cpu clock speeds ?
4. Really looking forward to the font stuff to be added back in, hopefully you can get to that soon-ish :) I've stared and added your repo to my watch list and will be looking back every once in a while!

Thanks for sharing this with us :cool:
 
Thank you! I'm happy there are people this is useful to, especially as I don't even really have a project using this myself.

To your questions:

1. This is because in general, you can't separate the implementation of templates from the header files in the same way you do with classes. There are ways to fix this, and I'll have to decide what to do as I'm implementing more things because the header file is getting ridiculous, but for now this was the least brain-hurty way of making everything compile.
2. Very good point - I've updated the readme with some more information about what the different colour spaces actually mean. To put it here as well: high color means 262k colours, which is 6 bits per RGB channel. Low color means 64k colours, which is 5 bits for red, 6 for green and 5 for blue. The library now also supports indexed (256 color) mode, but this only makes a difference in terms of buffer size, the display communication doesn't get faster because of it.
3. Yeah right now it only works in 72mhz - however I'm pretty sure I know what the problem is. I think I just forgot to limit the SPI bus speed, so this should be a two-character fix. I just need to get around to trying that out.
4. This is the big thing that's missing for me as well, and it's the one thing that always drove me to u8glib rather than the adafruit library, so I'm definitely looking forward to having good font support. If you've got specific functionality in mind that you need for your project, let me know and I'll try to do those things first!

Cheers
Rob
 
Cool!

I'm thinking just a basic font support (with a single font or maybe two) with a couple font size and just that you can specify position and text will be a really good start!

I'm not really using any crazy features with u8glib. Later down the road, some features to automatically center text on a coordinate and things like that can be handy but definitely not required for a first version.

Thanks
 
I started looking at the font support yesterday - I got something rendering, but it's producing incredibly weird glitches. I don't have a lot of time to lok at this in the next few days, but I'll let you know when it works!
 
It turns out I had a bit of time over lunch and I was able to make basic font support work again.

This is, for now, based on the code from the original Adafruit library, rather than Paul's optimised version, so it'll be slowish if not using the buffer. I added a getTextWidth function, but it breaks if there is any text wrapping and it seems to be slightly off for most fonts, while it doesn't work at all with the default font, so use with caution.

I added a "fonts" example that shows some very basic font usage. Let me know if you have any problems with it!

The whole fonts api might change in the future, I especially might switch most things to move to std::string instead of char*. I'll update the thread if I break anything though!
 
Sweet! Looks like its getting more and more complete quickly for me being able to use it in my project. At this point, I'm thinking I might as well start switching over to it and see what happen and what is missing. I'm currently using u8glib, I wonder if it would be easier to switch to Adafruit library first, which I was using before and then switch over to your library. Anyway, I will figure this out and start giving feedback once I really start using it :)

Btw, I'm using a 128x128 version of the SSD1351 so I will be able to test this display size for you!
 
IMG_1356.jpg

I got it working! I have totally different pins layout so I had to specify them in the constructor (and change screen height as well) but the library stayed untouched so that's good. I'm sure I will find some issues / missing things once I start playing more with it (this is just the font example sketch) but at least it's working! Looks like I will start on converting my code soon.

I really want to see the results of the before / after benchmarks as well. I plan to use low color with single buffering so we'll see...
 
@paul Sure! I can't promise the api is stable though, nor that everything works as advertised. The fact that it just hangs at 96mhz is particularly problematic.
 
Last edited:
@3400tz: brilliant! Also that's a nice-looking board you have there.

Let me know if the different pinout gives you trouble, though it looks like it's working well. I think if you had tried it using pins that don't support hardware chip-select it would have printed some serial output and quit.

I also spent some more time last night fixing the font alignment, getting text width should always work now, except for wrapping text.
 
Last edited:
I've now added a few more little things to make aligning of text easier. Instead of print(str), you can also do drawText(str, x, y, alignment) with alignment being one of the constants of ssd1351::ALIGN_LEFT/ALIGN_CENTER/ALIGN_RIGHT.

I also made a few small breaking changes: setFont now takes a reference instead of a pointer, so it now needs to be called with setFont(font) instead of setFont(&font), and I'm now including all fonts inside the library, so there's no need to include the header file in the sketch (only the fonts that are actually used are compiled anyway).
 
Sounds good! I'm hoping I will be able to start switching everything over to your library sometime next week. It does sounds complete enough at this point and we should be able to figure out any glitches. I would like to be able to run it at 96mhz at some point but that's definitely not a show stopper for now.

Thanks for your work so far.
 
Thank you!

Just wanted to say thank you for this. Just an hour ago I was looking at dismall refresh rates using the adafruit library and then came across your library and wow -- 60FPS on a 128x128.

Kind of a bummer about the lack of overclocking. But my application may not strictly need the extra speed.

Do let us know if you crack the issue with the overclock!

Much thanks again!
 
Thank you for the nice words, it makes me very happy to see this is useful to someone! The lack of overclocking support definitely is a bummer, I still haven't found out what's causing it - though I know it's not the simple issue I suspected.

I'm travelling right now but happen to have a teensy and display with me, maybe on Sunday I'll get a chance to look at the oc problem.

On thing to keep in mind though is that for most applications, overclocking won't give a very large speed bump here. The bit that generally takes longest is the communication with the display, and that is already running at the controller's maximum SPI speed (24mhz). Still though, it's be nice to sort this out, and be it just to not confuse people with random crashes.

Also if you're missing any other features in particular, let me know!

Cheers
Rob
 
So I've done a bit of digging, and it turns out the error might actually lie in my display, not in my code. I've tried out Sumotoy's very nice library for the same display, and when using it with overclocking it breaks exactly like this one.

I recommend you try using the library at 96MHz and just see if it works - if it doesn't, there is one thing you can do: do #define SLOW_SPI before including the library. This will slow down the SPI communication from 18 to 15mhz, which, at least for me, makes the display work. The downside is that this does slow down the display quite a lot, so you might find you get better speed with a non-overclocked teensy.

Let me know how it goes!
 
So I've done a bit of digging, and it turns out the error might actually lie in my display, not in my code. I've tried out Sumotoy's very nice library for the same display, and when using it with overclocking it breaks exactly like this one.

I recommend you try using the library at 96MHz and just see if it works - if it doesn't, there is one thing you can do: do #define SLOW_SPI before including the library. This will slow down the SPI communication from 18 to 15mhz, which, at least for me, makes the display work. The downside is that this does slow down the display quite a lot, so you might find you get better speed with a non-overclocked teensy.

Let me know how it goes!

Thanks for the reply. Still love the uber-performance of this library.

Sadly, I'm having a lot of trouble integrating it with my project that drives APA102 LEDs. I suspect that something in the SSD library is not cooperating with other things that use the SPI bus.

Setting FASTLEDTEST to 1 int he following sketch drives the APA102s, but not the display (more than a frame or two).. Disabling FASTLEDTEST, the display works fine.

Code:
#define FASTLEDTEST 1


#define SLOW_SPI

#include <Arduino.h>
#include <SPI.h>
#include <ssd1351.h>

// use this to do Color c = RGB(...) instead of `RGB c = RGB(...)` or ssd1351::LowColor c = RGB(...)
// because it's slightly faster and guarantees you won't be sending wrong colours to the display.

// Choose color depth - IndexedColor, LowColor and HighColor currently supported
// typedef ssd1351::IndexedColor Color;
// typedef ssd1351::LowColor Color;
typedef ssd1351::HighColor Color;

// Choose display buffering - NoBuffer or SingleBuffer currently supported
// auto display = ssd1351::SSD1351<Color, ssd1351::NoBuffer, 128, 96>();
auto display = ssd1351::SSD1351<Color, ssd1351::SingleBuffer, 128, 128>();

#include <FastLED.h>
#define NUM_LEDS 42
CRGB leds[NUM_LEDS];



bool up = false;
int pos = 127;
const int particles = 256;
int offsets[particles];
int x_pos[particles];
int y_pos[particles];
Color particle_colors[particles];

void setup() {
  Serial.begin(9600);
  Serial.println("Booting...");
  display.begin();
  Serial.println("Display set up.");

if( FASTLEDTEST )
{
  pinMode( 7, OUTPUT );
  FastLED.addLeds<APA102, BGR>(leds, NUM_LEDS);
  FastLED.setBrightness(10);
}
 
  for (int i = 0; i < particles; i++) {
    x_pos[i] = random(0, 128);
    y_pos[i] = random(0, 96);
    particle_colors[i] = ssd1351::RGB(0, i + 10, i/2 + 10);
  }
}

void loop() {

  for( int i = 0 ; i < NUM_LEDS ; i++ )
    leds[i] = CRGB::Green;

    leds[millis()/100 % NUM_LEDS] = CRGB::White;

if( FASTLEDTEST )
{
  // beginTransaction prevents SPI bus conflicts
  SPI.beginTransaction(SPISettings(24000000, MSBFIRST, SPI_MODE0));
  digitalWrite(7, HIGH);  // enable access to LEDs
  FastLED.show();
  digitalWrite(7, LOW);
  SPI.endTransaction();   // allow other libs to use SPI again
}
    
  unsigned long before = millis();
  display.fillScreen(ssd1351::RGB());
  Color circleColor = ssd1351::RGB(0, 128, 255);

  for (int i = 0; i < particles; i++) {
    offsets[i] += random(-2, 3);
    display.drawLine(
      x_pos[i] + offsets[i],
      y_pos[i] + offsets[i],
      pos,
      80 + sin(pos / 4.0) * 20,
      particle_colors[i]
    );
    display.drawCircle(
      x_pos[i] + offsets[i],
      y_pos[i] + offsets[i],
      1,
      circleColor
    );
  }
  display.updateScreen();
  Serial.println(millis() - before);

  if (up) {
    pos++;
    if (pos >= 127) {
      up = false;
    }
  } else {
    pos--;
    if (pos < 0) {
      up = true;
    }
  }
}
 
Oh yeah that's very possible, I haven't tested the library at all with multiple spi devices. However, most of the code should be ok for shared spi, I just need to debug the bits that break it.

I've got a strand of apa102s lying around, hopefully I can have a go at this sometime this week.

Thanks for the heads-up!
 
Oh yeah that's very possible, I haven't tested the library at all with multiple spi devices. However, most of the code should be ok for shared spi, I just need to debug the bits that break it.

I've got a strand of apa102s lying around, hopefully I can have a go at this sometime this week.

Thanks for the heads-up!


That would be great! Look forward to an update.

In terms of the APA102s, you actually don't need to have them hooked up to trigger the issue. They don't write anything back.

Similiar issues occurred with other SPI libraries for the APA102s.

I've been studying the source code on and off and don't see any obvious cases where a beginTransaction occurs without an endTransaction, but I haven't taken a deep dive (nor am I really qualified to understand the cortex SPI API.)
 
I tried teensy_ssd1351, it is working fine except
a) Cannot work with interrupt. I noticed that it try to handle port interrupt. However i used other interrupts. I disable the SPI via a new public function from ssd1351::end () (there is a begin())

b) The library was already using 20MHz, regardless of the cpu clock speed. The library does not work at higher cpu clock speee because there is no proper setting in the code to generate 20Mhz from cpu clock rate. See cores/teensy3/SPIFIFO.h
 
I would like to use a core interrupt, i.e., system tick timer (SysTick) which is at Vector 15, with no IRQ number. SPI.usingInterrupt() is for non-core interrupt.
 
@kirberich
First of all, thank you so much for this library!!! I notice the thread is mostly silent these days, however this is still the very best SSD1351 Lib for the great little Teensy. I would very much like to use your library, however my projects needs the ability to rotate the display. Do you still plan on updating it?

If not, any idea how I could bring back display rotation?

Cheers,
Mik
 
Status
Not open for further replies.
Back
Top