Kjetil's Information Center: A Blog About My Projects

SSD1306 PBM Viewer

I recently bought a SSD1306 miniature OLED display. This communicates through I2C and can be connected to various boards, like the Raspberry Pi 3 which I have used.

As an initial experiment, I have made a Python script that will load a 2-color PBM image file and display it on the OLED. The image has to be in binary (P4) format and exactly 128x64 which is the same as the resolution. I have re-used the same initialization commands as mentioned here to get up and running quickly.

Enjoy:

#!/usr/bin/python
import smbus

class SSD1306(object):
    def __init__(self, bus=1, address=0x3c):
        self._address = address
        self._bus = smbus.SMBus(bus)

        self._command(0xae) # Display off.

        self._command(0xa8) # Multiplex ratio...
        self._command(0x3f) # ...63
        self._command(0xd3) # Display offset...
        self._command(0x00) # ...0
        self._command(0x40) # Display start line at 0.
        self._command(0xa1) # Segment Re-map with column 127 mapped to SEG0.
        self._command(0xc8) # Remapped mode, scan from COM[N-1] to COM0.
        self._command(0xda) # COM pins hardware configuration...
        self._command(0x32) # ...Alternative and Left/Right
        self._command(0xa4) # Entire display ON.
        self._command(0xa6) # Inverse display mode.
        self._command(0xd5) # Display clock...
        self._command(0x80) # ...No clock divide ratio and max frequency.
        self._command(0x8d) # Charge pump...
        self._command(0x14) # ...Enabled.
        self._command(0x20) # Memory addressing mode...
        self._command(0x20) # ...Horizontal.

        self._command(0xaf) # Display on.
    
    def _command(self, command_byte):
        self._bus.write_byte_data(self._address, 0x00, command_byte)

    def _data(self, data_byte):
        self._bus.write_byte_data(self._address, 0x40, data_byte)

    def reset_cursor(self):
        self._command(0x21) # Column address...
        self._command(0x00) # ...start at 0...
        self._command(0x7f) # ...end at 127.
        self._command(0x22) # Page address...
        self._command(0x00) # ...start at 0...
        self._command(0x07) # ...end at 7.

    def pbm(self, filename):
        fh = open(filename, "r")
        if not fh.readline().startswith("P4"):
            raise Exception("Not a binary PBM image!")
        header = fh.readline()
        while header.startswith("#"):
            header = fh.readline() # Ignore comments.
        if not header.startswith("128 64"):
            raise Exception("Dimensions must be 128x64!")
        data = fh.read()
        fh.close()

        if len(data) != 1024:
            raise Exception("Size of data is not 1024 bytes!")

        self.reset_cursor()
        for row_offset in [0, 128, 256, 384, 512, 640, 768, 896]:
            for column in range(0, 128):
                byte  = ((ord(data[row_offset + (column / 8)      ]) >> (7 - (column % 8))) & 1) << 1
                byte += ((ord(data[row_offset + (column / 8) + 16 ]) >> (7 - (column % 8))) & 1) << 0
                byte += ((ord(data[row_offset + (column / 8) + 32 ]) >> (7 - (column % 8))) & 1) << 3
                byte += ((ord(data[row_offset + (column / 8) + 48 ]) >> (7 - (column % 8))) & 1) << 2
                byte += ((ord(data[row_offset + (column / 8) + 64 ]) >> (7 - (column % 8))) & 1) << 5
                byte += ((ord(data[row_offset + (column / 8) + 80 ]) >> (7 - (column % 8))) & 1) << 4
                byte += ((ord(data[row_offset + (column / 8) + 96 ]) >> (7 - (column % 8))) & 1) << 7
                byte += ((ord(data[row_offset + (column / 8) + 112]) >> (7 - (column % 8))) & 1) << 6
                self._data(byte ^ 0xff)

if __name__ == "__main__":
    import sys

    if len(sys.argv) < 2:
        print "Usage: %s <128x64 binary PBM image>" % (sys.argv[0])
        sys.exit(1)

    display = SSD1306()
    display.pbm(sys.argv[1])
          


And here is how it looks with an image loaded:

SSD1306 in action.


Topic: Scripts and Code, by Kjetil @ 21/07-2019, Article Link