SSD1306 OLED 128 x 64 display

Hi folks,

I’ve been using the ssd1306 code from @smittytone to drive an adafruit 128x64 display. Thanks a lot @smittytone for putting the library together and for the the clear code and comments, which have been very useful to try and figure out what’s going on.

I found that it worked for me after a few modifications, namely:

  • changing SSD1306_OLEDHEIGHT to 64
  • changing the _rst assignment in the constructor function from ‘imp_rst_pin’ to ‘imp_reset_pin’
  • and updating any reference to buffer size from 512 to 1024

I’ve managed to get the display showing some text, but it doesn’t look quite right to me - as though it’s skipping every other line / row of pixels. I’ve attached some pictures in both normal and inverted mode where it’s more obvious. The code I’m using (beyond the ssd1306 class with the changes above) is below too.

Any thoughts on why it might look that way?

Thanks,

Yousaf

`const OLED_ADDRESS = 0x3D;
i2c_addr <- OLED_ADDRESS << 1;

i2cbus <- hardware.i2c89;
resetpin <- hardware.pin7;

oledDisplay <- SSD1306_OLED(i2cbus, i2c_addr, resetpin);
oledDisplay.init();
oledDisplay.clear_display();
oledDisplay.set_text_cursor(0,0);
oledDisplay.print_text(“Testing”);`

Hi, @vondartleroy. Thanks for spotting the typo: I changed ‘imp_rst_pin’ to ‘imp_reset_pin’ and forgot to change it in the other!

I’m not sure right now what the problem is, but it looks like how the SSD1306 is shifting its own cursor after every line (byte) of character data you send it.

Try updating the SSD1306 class’ set_text_cursor() function to this:

`function set_text_cursor(row = 0, col = 0)
{
if (row < 0 || row > 3 || col < 0 || col > 15) return

_text_cursor_x = col
_text_cursor_y = row
	
local a = _text_cursor_x * 8
local b = _text_cursor_y
    
local c = "\\x03"
if (SSD1306_OLEDHEIGHT == 64) c = "\\x07"
    
_i2c.write(_i2c_address, "\\x00" + SSD1306_COLUMNADDR + a.tochar() + (SSD1306_OLEDWIDTH - 1).tochar())
_i2c.write(_i2c_address, "\\x00" + SSD1306_PAGEADDR + b.tochar() + c)

}`

And let me know what happens. It looks OK on a 128 x 32 display, but I don’t have a 128 x 64 screen to try.

Hi

You can refactor the buffer size calculation by declaring:

_buffer_size = 0

Then initialising:

_buffer_size = SSD1306_OLEDWIDTH * SSD1306_OLEDHEIGHT / 8

Then using by replacing every occurence of 512/1024 with _buffer_size.

And I’ve been able to get 8 lines of text with the following changes:

function set_text_cursor(row = 0, col = 0)
{
    if (row < 0 || row > 7 || col < 0 || col > 15) return
    
    _text_cursor_x = col
    _text_cursor_y = row
    
    local a = _text_cursor_x * 8
    local b = _text_cursor_y
    
    _i2c.write(_i2c_address, "\\x00" + SSD1306_COLUMNADDR + a.tochar() + (SSD1306_OLEDWIDTH - 1).tochar())
    _i2c.write(_i2c_address, "\\x00" + SSD1306_PAGEADDR + b.tochar() + ((SSD1306_OLEDHEIGHT / 8) - 1).tochar())
}

n.b. change 3 to 7, change c to (SSD1306_OLEDHEIGHT / 8) - 1

    _i2c.write(_i2c_address, "\\x00" + SSD1306_SETMULTIPLEX + "\\x3F")

n.b. change 1F to 3F

    _i2c.write(_i2c_address, "\\x00" + SSD1306_SETCOMPINS + "\\x12")

n.b. change 02 to 12

So neither proposal alone seems to fix the issue for me, but combining them does the trick :slight_smile:

The set_text_cursor function I’m using is now:

`function set_text_cursor(row = 0, col = 0)
{
if (row < 0 || row > 7 || col < 0 || col > 15) return

_text_cursor_x = col
_text_cursor_y = row
	
local a = _text_cursor_x * 8
local b = _text_cursor_y
    
local c = (SSD1306_OLEDHEIGHT / 8) - 1
    
_i2c.write(_i2c_address, "\\x00" + SSD1306_COLUMNADDR + a.tochar() + (SSD1306_OLEDWIDTH - 1).tochar())
_i2c.write(_i2c_address, "\\x00" + SSD1306_PAGEADDR + b.tochar() + c)

}`

and I made the changes that @quimarche suggested to the init function, namely:
_i2c.write(_i2c_address, "\\x00" + SSD1306_SETMULTIPLEX + "\\x3F") _i2c.write(_i2c_address, "\\x00" + SSD1306_SETCOMPINS + "\\x12")

If I could trouble you to explain why that works it’d be really appreciated…

A couple of other questions I had with some shot in the dark ideas:
-Do you have any pointers on using a different font / character set? I rather like the one Adafruit use which I believe is an ascii 5x7 font. Is there conceptually an easy way to either convert it to 8x8 or use 5x7 with this class? Modifying the print_char function would be where I’d naively start, but are there any particular things I should keep in mind?
-On a similar note, is there a simple way to change the size of the font? Something like an arithmetic operation on the hex characters in the print_char function?

Thanks again for your help.

Yousaf

Yousaf, the SETMULTIPLEX value is essentially the number of lines on the OLED - the value you write is that of the last line, counting from zero. So you have 0x3F (63) and I have 0x1F (31).

SETCOMPINS is a setting related to hardware configuration: how the SSD1306’s COM pins connect to and address the OLED panel. This, I think, is implementation specific .

I’ve added notes to this in the class, and related the value put into the SSD1306_SETMULTIPLEX register to SSD1306_OLEDHEIGHT.

Switching fonts isn’t too hard: you can drop a 5 x 7 character pattern into an 8 x 8 array. 5 x 7 chars are, IIRC, usually larger when output because they don’t include whitespace to separate one char from another. 8 pixels width is usually convenient because it falls on byte lines, ie, one byte is one horizontal row of pixels. Here, though, the chip expects one byte to equal one column of pixels, so if you add a spacer line at the bottom to make your chars 5 x 8, you just write five bytes out in print_char() instead of eight. Actually, you’d probably write six, the extra column being a blank spacer.

My class doesn’t do this because I used an existing character pattern matrix, for which I needed to add the rotate_matrix() function. Correctly established, your 5 x 7 set wouldn’t need rotating and could be easily tweaked to give proportional spacing, ie. ‘i’ might be 3 x 7 whereas the wider ‘W’ might be 6 x 7.

Font re-sizing is do-able: you just include the maths to convert a 8 x 8 matrix (or whatever) into 16 x 16 - it’s just pixel doubling. The results won’t be too impressive, so IMHO you’d be better off drawing a set of double-size characters.

Guys,

I have been trying out this OLED class with a 128x64, many thanks to @smittytone, and I can offer the doubled-up characters. I first rotated the character maps so they don’t need rotating to use them, then I replaced them with a more traditional 5x7 font so you can get 20 characters on a line in normal size, still clearly readable. Finally I added print_bigtext to double up the font size and make it more readable. As @smittytone said, a proper big font would be awesome, I might do that next. Oh and I added some line-drawing characters and a box drawing routine. Result attached.

I am not quite sure how to share this code if anyone wants to use it. Do I make a fork from @smittytone, or do I start my own?

Following the above, I’ve modified the original SSD1306 class to support 128 x 64 OLEDs as well as 128 x 32: just add the pixel dimension when you instantiate the class, eg:

oled <- SSD1306_OLED(hardware.i2c12, 0x78, hardware.pin5, 128, 32)

You now pass the I2C address as well (second parameter).

Inspired by my later comment, I’ve also adapted the class for an alternative character set, this one proportionally spaced. Rather than build it into the original, it’s here:

https://gist.github.com/smittytone/20ed962d9f06072bd1c0

Hi,

i got a serial version of the 128x64 display and i’am curious about to draw a bitmap.

The Arduino Sample Code says this:

void DigoleSerialDisp::drawBitmap(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *bitmap) { uint8_t i = 0; if ((w & 7) != 0) i = 1; Print::print("DIM"); write(x); //x; write(y); write(w); write(h); for (int j = 0; j < h * ((w >> 3) + i); j++) { write(pgm_read_byte_near(bitmap + j)); // delay(5); } }

this is the sample image:

prog_uchar welcomeimage[] PROGMEM = { 0, 0, 0, 0, 0, 127 , 0, 8, 1, 2, 0, 127 , 0, 8, 0, 148, 0, 127 , 0, 16, 0, 89, 112, 127 , 3, 144, 0, 16, 144, 127 , 28, 145, 192, 16, 144, 127 , 1, 30, 128, 80, 144, 127 , 9, 49, 3, 208, 144, 127 , 5, 72, 0, 80, 144, 127 , 2, 72, 0, 150, 144, 127 , 3, 8, 0, 152, 208, 127 , 5, 24, 0, 64, 160, 127 , 4, 148, 0, 64, 128, 127 , 8, 36, 0, 128, 128, 127 , 16, 34, 3, 240, 128, 127 , 32, 65, 0, 14, 0, 127 , 0, 129, 128, 1, 252, 127 , 3, 0, 64, 0, 0, 127 , 0, 0, 0, 0, 0, 127 , 0, 0, 0, 0, 0, 127 , 0, 0, 0, 0, 0, 127 };

and this the running sample code:

mydisp.drawBitmap(12, 12, 41, 21, welcomeimage);

Any hint to do it with the imp?

I got it myself…

myBitmap <- [ 0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x00,0x06,0x00,0x66,0x60,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x07,0xf0,0x07,0x00,0x6e,0x60,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x3f,0xfe,0x0f,0x00,0xee,0xe0,0x00,0x00,0x00 ,0x00,0x00,0x00,0x01,0x80,0x00,0x7c,0xff,0xff,0x8f,0x01,0xcc,0xc0,0x00,0x00,0x00 ,0x00,0x00,0x00,0x01,0x80,0x00,0x7f,0xff,0xff,0xff,0x03,0xfd,0xc0,0x00,0x00,0x00 ,0x00,0x00,0x00,0x03,0xc0,0x00,0x7f,0xff,0xff,0xff,0x03,0xfd,0xc0,0x00,0x00,0x00 ,0x00,0x00,0x00,0x03,0xc0,0x00,0x7f,0xf8,0x07,0xff,0x03,0xff,0x80,0x00,0x00,0x00 ,0x00,0x00,0x00,0x07,0xe0,0x00,0x3f,0xe0,0x03,0xfe,0x07,0xff,0xc0,0x00,0x00,0x00 ,0x00,0x00,0x00,0x0f,0xf0,0x00,0x3f,0x80,0x00,0xfe,0x07,0xff,0xc0,0x00,0x00,0x00 ,0x00,0x00,0x00,0x0f,0xf0,0x00,0x3f,0x00,0x00,0x7e,0x07,0xff,0x80,0x00,0x00,0x00 ,0x00,0x00,0x00,0x1f,0xf8,0x00,0x3e,0x00,0x00,0x3e,0x07,0xff,0x80,0x00,0x00,0x00 ,0x00,0x00,0x00,0x1f,0xf8,0x00,0x7e,0x00,0x00,0x3f,0x03,0xff,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x31,0x8c,0x00,0x7c,0x00,0x03,0x9f,0x01,0xff,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x01,0xc0,0x00,0x7c,0xe0,0x0f,0xdf,0x00,0xfe,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x01,0xc0,0x00,0x78,0xfc,0x1f,0xcf,0x00,0xf0,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x01,0xc0,0x00,0xf8,0xfc,0x1f,0x8f,0x81,0xc0,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x01,0xc0,0x00,0xf8,0x7c,0x1f,0x0f,0x81,0xc0,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0xe0,0x00,0xf8,0x1c,0x0c,0x0f,0x81,0x80,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0xe0,0x01,0xf8,0x00,0x00,0x0f,0xc3,0x80,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0xf0,0x0f,0xf8,0x00,0x00,0x0f,0xfb,0x80,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x70,0x3f,0xfc,0x00,0x00,0x0f,0xff,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x78,0x7f,0xfc,0x00,0x00,0x1f,0xff,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x3c,0x7f,0xfc,0x00,0x02,0x1f,0xff,0x80,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x1c,0x7f,0x3e,0x00,0x1c,0x3f,0x7f,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x1f,0x38,0x3f,0x01,0xf0,0x7e,0x0f,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x0f,0x80,0x1f,0x80,0x00,0xfc,0x0c,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x07,0xc0,0x1f,0xc0,0x01,0xfc,0x1c,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x01,0xf0,0x0f,0xe0,0x03,0xf8,0x1c,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0xff,0x07,0xfc,0x1f,0xf0,0x18,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x3f,0xff,0xff,0xff,0xe0,0x38,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xc0,0x38,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0x00,0x30,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1f,0xfc,0x00,0x30,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x60,0x00,0x00,0x00,0x00,0x01,0x80,0x00,0x0e,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x60,0x00,0x00,0x00,0x00,0x03,0xc0,0x00,0x0e,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x60,0x00,0x00,0x00,0x00,0x03,0x80,0x00,0x0e,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x60,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x00,0x00,0x60,0x00,0x00,0x00,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ,0x01,0xf0,0x60,0x7c,0x01,0xf3,0xf9,0x99,0x81,0xf8,0x1e,0x3b,0xe3,0xe1,0xef,0xc0 ,0x07,0xfc,0x61,0xff,0x07,0xf3,0xf9,0xf9,0x87,0xf8,0x1e,0x3f,0xff,0xf1,0xff,0xe0 ,0x0e,0x0e,0x61,0xc3,0x8e,0x00,0xc1,0xe1,0x8e,0x00,0x1e,0x3f,0xff,0xf1,0xff,0xf0 ,0x0c,0x06,0x63,0x81,0x8c,0x00,0xc1,0xc1,0x8c,0x00,0x1e,0x3c,0x7c,0xf9,0xf0,0xf0 ,0x0c,0x06,0x63,0x01,0x9c,0x00,0xc1,0x81,0x9c,0x00,0x1e,0x3c,0x78,0x79,0xe0,0xf0 ,0x1f,0xfe,0x67,0xff,0xd8,0x00,0xc1,0x81,0x9c,0x00,0x1e,0x3c,0x78,0x79,0xe0,0x78 ,0x1f,0xff,0x67,0xff,0xd8,0x00,0xc1,0x81,0x98,0x00,0x1e,0x3c,0x78,0x79,0xe0,0x78 ,0x1c,0x00,0x63,0x00,0x18,0x00,0xc1,0x81,0x9c,0x00,0x1e,0x3c,0x78,0x79,0xe0,0x78 ,0x0c,0x00,0x63,0x00,0x1c,0x00,0xc1,0x81,0x9c,0x00,0x1e,0x3c,0x78,0x79,0xf0,0xf0 ,0x0c,0x00,0x63,0x00,0x1c,0x00,0xc1,0x81,0x8c,0x00,0x1e,0x3c,0x78,0x79,0xf9,0xf0 ,0x06,0x04,0x61,0x81,0x0e,0x00,0xc1,0x81,0x8f,0x00,0x1e,0x3c,0x78,0x79,0xff,0xe0 ,0x07,0xfc,0x60,0xff,0x07,0xf8,0xf9,0x81,0x87,0xf8,0x1e,0x3c,0x78,0x79,0xff,0xe0 ,0x01,0xf8,0x60,0x7e,0x01,0xf0,0x79,0x81,0x81,0xf0,0x0e,0x38,0x38,0x79,0xe7,0x80 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xe0,0x00 ,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xe0,0x00 ];

function drawBitmap(x, y, w, h, data) { imp.sleep(0.2); local i = 0; if ((w & 7) != 0) i = 1; display.write("DIM"); display.write(x); display.write(y); display.write(w); display.write(h); for (local j = 0; j < h * ((w >> 3) + i); j++) { display.write(data[j]); } }

drawBitmap(0, 0, 128, 64, myBitmap);