OLED - What is happening

In hopes of better understanding the OLED object, and ultimately being able to code some nice graphics, I will start a rant about the OLED code here.

(Note that in 1.0.12, there is no easy way of embedding the display object so as to edit it freely, because the way “includes” are handled has not been fixed yet (hopefully fixed in v1.1.0). Long story. Use the object skeleton patch, display gills skeleton.axp provided below.

Let’s look at the gills/display object (Local Data) but ignore all the low-level controller stuff for now.

It looks like the function fill(uint8_t) inside the gills (big genes) OLED object is a good starting point.

void fill(uint8_t v) {
    i2cAcquireBus(&I2CD1);

    cmd(COLUMNADDR, 0, 127); /* Column start end */
    cmd(PAGEADDR, 0, 7);     /* Page start end */

    txbuf[0] = 0x40;

    uint8_t i; for (i = 1; i < 129; i++) {
        txbuf[i] = v;
    }

    uint8_t p; for (p = 0; p < 8; p++) {
        i2cMasterTransmitTimeout(&I2CD1, __I2C_ADDR, txbuf, 129, rxbuf, 0, 30);
    }

    i2cReleaseBus(&I2CD1);
}

Let’s look at each line and figure out what it does:

i2cAcquireBus()

→ blah blah some hardware stuff to take control of the I2C system. &I2CD1 here means “to the address of I2C1 driver”, that’s just fixed by hardware and ChibiOS. Pins PB8 and PB9, it is what it is.

cmd(xxx)

This is a simple function (also found inside the OLED object) to push a quick set of bytes to the OLED controller, so it carries out the respective command. the relevant list of commands can be found in the SSH1106 datasheet, or we can just look at the KSO_OLED enum in our OLED object. Anyway,

cmd(COLUMNADDR, 0, 127); /* Column start end */
cmd(PAGEADDR, 0, 7);     /* Page start end */

is telling the OLED that we are going to write 128 pixels, from 0 to 127 (the full range of columns, as the OLED is 128 pixels wide) and pages 0 to 7.

Let’s understand pages first. The controller is set up in a way that you can only write one area of 8 pixels at a time, arranged in a vertical column (or I might be wrong). This is one page, basically. 128 pixels wide, 8 pixels high. and 8 times 8 is 64 pixels, the height of our OLED display.

so the two cmd() above tell the OLED that we are going to write all 128*64 pixels in the following I2C transfer.

txbuf[0] = 0x40;

this is something the controller seems to require, like the first byte of the transfer must be 0x40 or something. I am guessing 0x40 means “update the internal display buffer with what is coming next” as opposed to 0x00 “listen for a command” (found in cmd() ) but I can’t be bothered to look it up. But looking at this it makes sense now that our txbuf array in the OLED object is 129 bytes long, not 128, accounting for this extra byte we need to send.

uint8_t p; for (p = 0; p < 8; p++) {

So here is our loop, let’s do something 8 times. = 8 pages

i2cMasterTransmitTimeout(&I2CD1, __I2C_ADDR, txbuf, 129, rxbuf, 0, 30);

this is the ChibiOS function we need to use to transfer things to the OLED (with DMA and all that fancy stuff). If you look at the code inside cmd() it actually uses the same function, only cmd() is specialized on pushing just two bytes.

By the way, cmd() is being “overloaded”, which is a C++ thing and basically means there are several versions of the same function, depending on what types of (or how many) arguments you provide. This is why we can send a cmd(uint8_t) , but also a double cmd(uint8_t, uint8_t) , or even a 3-byte cmd(uint8_t, uint8_t, uint8_t) and the compiler will know what to do because we have provided the overloaded functions.

What’s kinda cool but also scary is that the overloaded cmd’s actually reference back to the single-byte cmd… go and have a look if you understand what I am saying here!

Where was I … right, the i2cMasterTransmitTimeout(&I2CD1, __I2C_ADDR, txbuf, 129, rxbuf, 0, 30); . a lot of cryptic info there, but in general we only need to understand that we tell it to send whatever is inside txbuf , and 129 bytes of it, and place whatever we receive back in rxbuf , with a length of 0 bytes, i.e. ignore it. It is just faster this way). The 30 means a timeout (not even sure if this is milliseconds but I believe so).

Finally i2cReleaseBus so (in theory) other objects can access the I2C bus after we’re done.

In simple language, what the function does is:

  • take control of the bus (avoid simultaneous send and receive from multiple objects)
  • send some config commands
  • set the first byte of txbuf to 0x40 (means “update internal display buffer”?)
  • set byte 1 to 129 of txbuf to whatever we like. could be 0 to entirely clear the display, or 0xFF to entirely fill the display with white pixels, or a grid pattern like in the screenshot above. I forgot what value this would be, or if I had to do some more conditioning per column, maybe you can find out?
  • send the same txbuf 8 times, to update each of the 8 pages (128*8 pixels each)
  • release I2C bus so other objects can access I2C (your I2C gyroscope of course, and that Wii Nunchuk you’ve been experimenting with)

From here, there is a lot of other stuff that the OLED (and some accompanying objects by tiar) does, like convert a font to pixels, or convert some scope data to a waveform display, or convert a blue control value to a horizontal bar …

EDIT: turns out the fill() function may not be entirely correct… might work for other controllers but the SSH1106 needs some extra commands I believe. Check out the following patch:

I’ve also removed the problematic include parts mentioned in the first post so this object can be run as is.

This also means, no fonts, no scope … just rows of glitch art on your display.

display gills skeleton.axp (8.8 KB)

1 Like