Rewrite of the SSD1306Ascii text only library

I have just posted a new version of the SSD1306Ascii library on GitHub.

The original version was posted on Google Code in 2012.

There are many existing full featured graphics libraries for these displays. The goal for this library is to only display text with minimum use of RAM and flash.

Here is memory use for the SPI "Hello world!" example:

Sketch uses 2,582 bytes (8%) of program storage space. Maximum
is 32,256 bytes.

Global variables use 52 bytes (2%) of dynamic memory, leaving
1,996 bytes for local variables. Maximum is 2,048 bytes.

// Test for minimum program size.

// pin definitions
#define OLED_DC 9
#define OLED_CS 10

#include <SPI.h>
#include <SSD1306Ascii.h>
#include <SSD1306SpiAscii.h>

SSD1306SpiAscii oled;
//------------------------------------------------------------------------------
void setup() {                
  oled.begin(&Adafruit128x64, OLED_CS, OLED_DC);
  oled.setFont(System5x7);
  oled.clear();
  oled.print("Hello world!");
}
//------------------------------------------------------------------------------
void loop() {}

Here are key design goals:

Small size is the highest priority. Speed and features are lower priority.

Support multiple fonts. More than 40 fonts are included with this release. Fonts are only loaded if you reference them.

Support fixed width and proportional fonts.

Optionally magnify fonts by a factor of two.

Support 128x32 and 128x64 displays with I2C and SPI interfaces.

Use the standard Wire library for I2C. An optimization option is available to increase I2C performance.

Use the standard SPI library for hardware SPI. An optimization option is available for AVR to increase performance and reduce code size.

Provide software SPI so the display can be connected to any digital pins.

Here are are some development pictures:

Test1

Test2

Very nice fonts!

The FontSample examples wouldn't compile for me. There is a line where the characters "/*" are printed and they were interpreted as the beginning of a comment.

Now I have to decide whether I should throw out my own version of this library.

The FontSample examples wouldn't compile for me. There is a line where the characters "/*" are printed and they were interpreted as the beginning of a comment.

Thank you for finding this. Wonder why it compiled for me?

I put the characters in their collating sequence and updated GitHub.

  oled.println("*+,-./0123456789:");

Very nice fonts!

Most of the fonts are from openGLCD. I give credit in the html.

I may construct more X11 fonts from .bdf files.

fat16lib:
Wonder why it compiled for me?

I'll bet you've bid farewell to IDE 1.0.x. I got the error in 1.0.6. In 1.6.4 it compiles.

I couldn't resist comparing your library to my own effort, using a program that just prints a line of 5x7 characters (21 characters). The RAM use was only different by a handful of bytes, the code size differed by about 10% and the execution time differed by 500%. Speed isn't usually that important on these little displays but that still caught my eye.

Could you also tell which version had the better numbers, yours or the older one ?

Could you also tell which version had the better numbers, yours or the older one ?

The old version is larger slower and has fewer features.

I couldn't resist comparing your library to my own effort, using a program that just prints a line of 5x7 characters (21 characters). The RAM use was only different by a handful of bytes, the code size differed by about 10% and the execution time differed by 500%. Speed isn't usually that important on these little displays but that still caught my eye.

Are you using an I2C display? The oled.set400kHz() call would account for most of the speed difference.

The Wire library adds a lot to the minimal size of a program. I may add a class for AVR that uses a very small I2C library that I wrote.

Here are sizes for a simple "Hello world!" program on an Uno.

SPI:

// Test for minimum program size.

// pin definitions
#define OLED_DC 9
#define OLED_CS 10

#include <SPI.h>
#include <SSD1306Ascii.h>
#include <SSD1306SpiAscii.h>

SSD1306SpiAscii oled;
//------------------------------------------------------------------------------
void setup() {                
  oled.begin(&Adafruit128x64, OLED_CS, OLED_DC);
  oled.setFont(System5x7);
  oled.clear();
  oled.print("Hello world!");
}
//------------------------------------------------------------------------------
void loop() {}

Sketch uses 2,582 bytes (8%) of program storage space. Maximum is 32,256 bytes.
Global variables use 52 bytes (2%) of dynamic memory, leaving 1,996 bytes for local variables. Maximum is 2,048 bytes.

I2C with Wire:

// Test for minimum program size.

// 0X3C+SA0 - 0x3C or 0x3D
#define I2C_ADDRESS 0x3C
#include <Wire.h>
#include "SSD1306Ascii.h"
#include "SSD1306I2cAscii.h"

SSD1306I2cAscii oled;
//------------------------------------------------------------------------------
void setup() {                
  oled.begin(&Adafruit128x64, I2C_ADDRESS);
  oled.setFont(System5x7);
  oled.clear();
  oled.print("Hello world!");
}
//------------------------------------------------------------------------------
void loop() {}

Sketch uses 3,964 bytes (12%) of program storage space. Maximum is 32,256 bytes.
Global variables use 254 bytes (12%) of dynamic memory, leaving 1,794 bytes for local variables. Maximum is 2,048 bytes.

I2C with my AvrI2c library:

// Test for minimum program size.

// 0X3C+SA0 - 0x3C or 0x3D
#define I2C_ADDRESS 0x3C
#include "AvrI2c.h"
#include "SSD1306Ascii.h"
#include "SSD1306IAsciiAvrI2c.h"

SSD1306AsciiAvrI2c oled;
//------------------------------------------------------------------------------
void setup() {                
  oled.begin(&Adafruit128x64, I2C_ADDRESS);
  oled.setFont(System5x7);
  oled.clear();
  oled.print("Hello world!");
}
//------------------------------------------------------------------------------
void loop() {}

Sketch uses 2,458 bytes (7%) of program storage space. Maximum is 32,256 bytes.
Global variables use 47 bytes (2%) of dynamic memory, leaving 2,001 bytes for local variables. Maximum is 2,048 bytes.

My AvrI2c library saves 1506 bytes of flash and 207 bytes of RAM when compared to Wire.

This will only be an advantage if no other library in your program uses Wire.

fat16lib:
Are you using an I2C display? The oled.set400kHz() call would account for most of the speed difference.

Sorry, I should have said. I am using hardware SPI.

I looked at the code and can see where the difference is. I think it's a design decision. I originally set out to optimize the I2C version of my code for speed since it was kind of slow. And then I couldn't resist doing the same for SPI, even though it was already fast enough. (edit: It's not compatible with the 1.6.x notion of SPI transactions.)

fat16lib:
The Wire library adds a lot to the minimal size of a program. I may add a class for AVR that uses a very small I2C library that I wrote....
...My AvrI2c library saves 1506 bytes of flash and 207 bytes of RAM when compared to Wire.

This will only be an advantage if no other library in your program uses Wire.

Very nice! I have had that on my list of things to do for a while. I was thinking the bloated Wire library could be made smaller and have a larger buffer at the same time.

I was thinking the bloated Wire library could be made smaller and have a larger buffer at the same time.

I updated GitHub with a new version. I changed the class names and added more examples.

I added a new SSD1306AsciiAvrI2c class based on my AvrI2c class.

AvrI2c is an extremely simple class with no buffering. The HelloWorldAvrI2c example uses 47 bytes of RAM as posted and only 33 bytes if you use the F() macro like this:

 oled.print(F("Hello world!"));

AvrI2c is slightly faster than Wire. The WireI2c128x64 example runs in 79.1 ms and the AvrI2c128x64 example runs in 64.1 ms.

No buffering? I thought that was necessary. Ha. Also on my list was to learn more about the I2C protocol, something I still haven't done.

There was a guy who was trying to speed up his I2C that got me looking at mine a little more closely. His display code was smaller and faster, but still slower than what it would be with your AvrI2C library.

Hi fat16lib,

I have done some testing with the examples and found that they do compile for the Arduino DUE, so that is nice.

I also tried to compile some examples for a ESP8266 with the Arduino-ES8266 AddOn, sadly I could not compile any of them.

You probably say : what's a ESP8266 ?
The ESP8266 is a SOC 'system on chip'. It has 802.11 b/g/n WiFi, tcp/ip stack, uart, SPI, I2C, ADC, 80/160 Mhz clock. A highly integrated 32bit CPU in a small package, modules are completed with a minimum of 256kB flashmemory and a PCB/ceramic WiFi antenna and/or U.FL connector. Modules are sold in many variations with only a few up to max 16 GPIO pins. The module_size is about the same size as the 0.96' SSD1306 oled's. The price is less than 5 $/€. Programmable from within the Arduino IDE with the Arduino-ESP-addon. GitHub - esp8266/Arduino: ESP8266 core for Arduino . The IDE sees it as just another Arduino compatible board.

This little WiFi module is often used together with a small OLED display for small wireless projects where there is often need for simple textual messages so it would be great to have a library like the SSD1306Ascii working together with the ESP8266 AddOn.

Some of the compile-error-messages point to the handling of avr/pgmspace.h, what often happens when compiling libraries for the ESP8266. Maybe the AddOn generates these messages because it is still in its infancy. (the first ESP8266-AddOn for Arduino is just a few weeks old).
The other errormessages are pointing to the I2C Wire library. The ESP8266 uses it's own version of the Wire library because it initializes in this way: Wire.begin(SDA_pin, SCL_pin). The SDA & SCL pins must be difined with the initialisation.
That may conflict with the way your library handles the I2C and it may not be easy to fix this. Hopefully the SSD1306Ascii library can be adapted to overcome these kind of errors.

Some of the compile-error-messages point to the handling of avr/pgmspace.h

Now there should only be one include for avr/pgmspace.h. The ZevvPeep8x16.h file had an extra include.

The include of avr/pgmspace.h is in allFonts.h and is protected with an #ifdef.

#ifdef __AVR__
#include <avr/pgmspace.h>
/** declare a font for AVR. */
#define GLCDFONTDECL(_n) static const uint8_t __attribute__ ((progmem))_n[]
#define readFontByte(addr) pgm_read_byte(addr)
#else  // __AVR__
/** declare a font. */
#define GLCDFONTDECL(_n) static const uint8_t _n[]
/** Fake read from flash. */
#define readFontByte(addr) (*(const unsigned char *)(addr))
#endif  // __AVR__

I moved the Wire.begin() call to the example programs so you can modify it to have this form:

Wire.begin(SDA_pin, SCL_pin).

Try again and see what happens.

I would like to compare this library to the one I've been using within the context of a finished project. But the available fonts do not match.

How do you add a font?

fat16lib:
Now there should only be one include for avr/pgmspace.h. The ZevvPeep8x16.h file had an extra include.
// - - - - //
I moved the Wire.begin() call to the example programs so you can modify it to have this form:

Wire.begin(SDA_pin, SCL_pin).

Try again and see what happens.

That makes sense. The compiling error(s) are gone so that looks good.
Tomorrow I will test some of your demo_sketches with the actual ESP8266 hardware to see if it all works out. I will let you know the results.

thanx for your fast response.

How do you add a font?

Your font must be in a fontName.h file in openGLCD format. openGLCD uses the openGLCDFontCreator tool located here.

If you have a fixed width font you can probably make the fontName.h file by hand.

Look at fixed width fonts in the fonts folder. Here is the beginning of System5x7.h:

GLCDFONTDECL(System5x7) = {
    0x0, 0x0, // size of zero indicates fixed width font,
    0x05, // width
    0x07, // height
    0x20, // first char
    0x61, // char count
    
    // Fixed width; char width table not used !!!!
    
    // font data
    0x00, 0x00, 0x00, 0x00, 0x00,// (space)
	0x00, 0x00, 0x5F, 0x00, 0x00,// !
        ...

You then add #include "fontName.h" to the allFonts.h file.

That's it.

Here is the start of allFonts.h with some fonts I added.

// Font Indices
#define FONT_LENGTH			  0
#define FONT_FIXED_WIDTH	2
#define FONT_HEIGHT			  3
#define FONT_FIRST_CHAR		4
#define FONT_CHAR_COUNT		5
#define FONT_WIDTH_TABLE	6
//
// FONT_LENGTH is a 16 bit Big Endian length field.
// Unfortunately, FontCreator2 screwed up the value it put in the field
// so it is pretty much meaningless. However it still is used to indicate
// some special things.
// 00 00 (fixed width font with 1 padding pixel on right and below)
// 00 01 (fixed width font with no padding pixels)

// any other value means variable width font in FontCreator2 (thiele)
// format with pixel padding

#include "Adafruit5x7.h"        // Font from Adafruit GFX library
#include "font5x7.h"
#include "lcd5x7.h"
#include "Stang5x7.h"
#include "X11fixed7x14.h"
#include "X11fixed7x14B.h"
#include "ZevvPeep8x16.h"

Okay, thanks.

I just received this SH1106 display. It is a very nice 1.3" display.

The SH1106 is a 132x64 controller so I made a few modifications to support SH1106 and added two examples on GitHub.

I've been using my SSD1306 character-only code with an ST7565 recently. It would probably easy to port it to your library as well.

Actually, it would be nice if your library supported many different controllers and displays, similar to u8glib.

In your spare time of course. :slight_smile:

Hi all!

How can I turn the entire text on the display 180 degrees?

Hi.

I haven't got the whole thread memorised so i'm not sure you can do this with this library.
During initialisation, send these commands:

0xa0 for writing from top to bottom.
0xa1 for writing from bottom to top.
0xC8 for writing from left to right.
0xC0 for writing from right to left.

You should set both values for top/bottom and left/right to be sure to have it work properly.