Sunday 7 February 2016

Sparkfun micro OLED

Sparkfun Micro OLED 64x48 Display

The 64x48 micro display from Sparkfun is a cheap and cheerful OLED display to add into a project. Adafruit also sell a  128x64 display using exactly the same SDD1306 display driver and gives you a bit more screen if you spend a few extra dollars. There's some excellent information on both sites to get you started, but both are geared towards Arduino libraries.

Micro OLED on Raspberry Pi

There are 3 ways the OLED can be hooked up using parallel, I2C and the default setup using SPI. SPI is used in the examples below.

SPI can be enabled using the raspi-config tool from the command line. If this is enabled then a /dev/spidev0.0 and /dev/spidev0.1 will be available. 

A entire post can be written on how to hookup and use SPI but this is well documented on many sites. Sparkfun has a good tutorial for SPI and I2C.

Micro OLED in action

A video to demonstrate output to the OLED display can be seen below. If a small fixed width font is used then a good number of text rows can be displayed. XBM images provide a quick format to import directly into a binary image as resources (C/C++).


SPI libraries

For Python there's an SPIDEV driver library, which is by far the easiest way to get connected up to the OLED. 
Run 
> sudo apt-get install python-spidev
to get 2.0 SPIDEV through Jessie or download the later version direct from the link above.

The SPIDEV library wraps code to talk to the kernel driver and provides a comprehensive set of functions to manage the connection. 
A simple set of attributes are exposed by the Python library to read/write settings
  • bits_per_word 
  • cshigh
  • lsbfirst
  • max_speed_hz
  • mode
Defaults should work fine but if you want you can adjust the max_speed_hz for your project. 500kHz seems to be the default speed on the Pi 2 and Pi Zero. This seems to be fast enough to run the display.

Connecting to the OLED

Example setup to connect to the OLED:

import RPi.GPIO as GPIO
import spidev
import time

spi = spidev.SpiDev()
spi.open(0,0)

GPIO.setmode(GPIO.BCM)

dc_pin    = 17 # data command pin
reset_pin = 27 # screen reset pin

GPIO.setup(dc_pin, GPIO.OUT)
GPIO.setup(reset_pin, GPIO.OUT)

This opens SPI and uses CE0 pin to address the display. Two additional GPIO pins are needed for the command type (DC) and reset pins. 17 and 27 are used in the example above, but you can use anything you like for these pins. 

Initializing the display

These steps are really important to get the display to do anything. If you read the SDD1306 spec you will see the steps are outlined as a reference, which is harder to understand how to implement for the 64x48 display. This is because the SDD1306 is actually a full 128x64 driver whereas the micro OLED is a cut down display. 

1. Pull DC high and set the reset pin low to formally reset the display. There needs to be a time period to reset the chip
GPIO.output(dc_pin, 1)
GPIO.output(reset_pin, 0)
time.sleep(0.1)

2. Enable the display, ensure command mode is set, then pull down and back up reset pin to get into a state for commands across SPI
GPIO.output(reset_pin, 1) # enable display
GPIO.output(dc_pin,0) # set to command mode
time.sleep(0.1) # wait 100ms (spec is for 5ms min)
GPIO.output(reset_pin, 0) # disable display
time.sleep(0.1) # wait 100ms (spec is for 10ms min)
GPIO.output(reset_pin, 1) # enable display

3. Display is set to initially off. Set the timing and multiplex. These are specific to the 64x48 display. 
spi.xfer([0xAE]) # display off command
spi.xfer([0xD5]) # display clock div
spi.xfer([0x80]) # set timing
spi.xfer([0xA8]) # set muliplex, 
spi.xfer([0x2F]) # note that this is specific to 64x48 displays

4. Set the display offsets. The Sparkfun is aligned to zero offsets in memory. Note this may not be the case for other implementations which use the same display driver.
spi.xfer([0xD3]) # set display offset
spi.xfer([0x0]) # com offset set to 0
spi.xfer([0x40 | 0x00]) # set startline offset to zero

5. Kick off the display
spi.xfer([0x8D]) # charge pump
spi.xfer([0x14]) # enable pump (disable with 0x00)
spi.xfer([0xA6]) # normal display
spi.xfer([0xA4]) # display all on resume

6. Next steps are for configuration. The examples here work with page addressing
spi.xfer([0x20]) # memory mode
spi.xfer([0x10]) # page address mode (10b for page, 01b for vertical)
spi.xfer([0xA0 | 0x01]) # seg remap 127 to SEG0. Rows are left to right from display ribbon
spi.xfer([0xC)]) # com scan descend. Basically flips the display from display connector ribbon
spi.xfer([0xC0]) # com scan ascend from display connector
spi.xfer([0xDA]) # set com pins
spi.xfer([0x12]) # A4 is 0 so sequential COM pin. A5 is 1 so enable com left/right remap
spi.xfer([0x81]) # set contrast
spi.xfer([0xCF]) # 00h to FFh contrast values.
spi.xfer([0xD9]) # set precharge
spi.xfer([0xF1]) # Phase 1: 1 dclk (max 15), Phase 2: 15 dclks (max 15)
spi.xfer([0xDB]) # set com deselect
spi.xfer([0x40]) # above 0.83 x Vcc for regulator output.
spi.xfer([0xAF]) # display on
time.sleep(0.1)

7. The display is now on and can be written to over SPI by setting DC high
GPIO.output(dc_pin, 1)

Drawing to the OLED

This isn't a particularly efficient method, but gives an example to show how the paged memory works and will allow you to write to the OLED memory buffer. There are 3 helper functions shown to make the code a bit more readable.

def WriteCommand(c):
        GPIO.output(dc_pin, 0)
        spi.xfer([c])
#end def

def WriteData(d):
        GPIO.output(dc_pin, 1)
        spi.xfer([d])
#end def

def SetColumnAddress(add):
        WriteCommand ((0x10 | add >> 4) + 0x02)
        WriteCommand (0x0F & add)
#end def

def DrawOLED(cx,cy):
        # 8 pixels are written on each SPI byte.
        # This is performed over 6 pages
        out = 0x00
        for y in range(0,6):
                SetColumnAddress(0)
                WriteCommand(0xB0 | y)
                for x in range(0, 64):
                        out = 0x00
                        if cx == x:
                                if cy >= y*8 and cy < (y+1)*8:
                                        out = 0x01 << (cy - (y*8))
                        WriteData(out)

Taking this into real projects

All the code shown are just examples and should be enough to expand for use in real projects. Code libraries can be built in C/C++ using the same underlying spidev calls via ioctl to the kernel driver.


My own code is a C++ library implementation making almost identical calls seen here. The main differences consist of an improved method using internal buffer in the code, which is written out to the OLED in a single full write of the display. This matches the Arduino code approach from Sparkfun. I may publish this at a later date as a useful Pi library for C++ development.

No comments:

Post a Comment