SH1106 128x64 OLED Display

These routines allow you to plot points and draw lines on I2C OLED display modules based on the SH1106 driver chip, available from a number of Chinese suppliers; for example, the Geekcreit 1.3" I2C OLED display, available from Banggood [1].


Note that this library will not work with SPI displays, or with displays based on the SSD1306 or SSD1309 driver chips, as none of these support reading back the display memory.

The graphics commands

The library assumes the origin (0, 0) is in the bottom left corner, so the top right corner is (127, 63). It provides the following commands:

(init-display) - initialises the display.

(moveto x y) - moves the drawing position to (x, y).

(drawto x y) - draws a line from the drawing position to (x, y).

(point x y) - plots a point at (x, y).

(clear) - clears the display.

I've also included a simple Turtle Graphics application that draws a Dragon Curve, to put the display through its paces!


These constants are used by the routines:

(defvar *address* 60)
(defvar *commands* #x00)
(defvar *onecommand* #x80)
(defvar *data* #x40)
(defvar *onedata* #xC0)
(defvar *init* '(#xA1 #xAF))

Initialise the display

This routine sets up the display and turns it on:

(defun init-display ()
  (with-i2c (s *address*)
    (write-byte *commands* s)
    (dolist (x *init*) (write-byte x s))))

You need to give the (init-display) command after powering-up the display.

Write a command to the display

The routine single writes a single display command to the display:

(defun single (byte s)
  (write-byte *onecommand* s)
  (write-byte byte s))

Clear the screen

The clear routine clears the screen by writing zero bytes to the display.

(defun clear ()
  (dotimes (p 8)
    (with-i2c (s *address*)
      (single (+ #xB0 p) s)
      (dotimes (q 8)
        (restart-i2c s)
        (write-byte *data* s)
        (dotimes (i 20) (write-byte 0 s))))))

It's slightly complicated by the need to cater for the 32-byte buffer limit of the Arduino core I2C routines.

Plot a point

The point routine reads the byte from display memory, sets a bit in the appropriate position, and then writes it back again:

(defun point (x y)
  (let (j)
    (with-i2c (s *address*)
      (single (+ #x00 (logand (+ x 2) #x0F)) s)
      (single (+ #x10 (ash (+ x 2) -4)) s)
      (single (+ #xB0 (ash y -3)) s)
      (single #xE0 s) ; Read modify write
      (write-byte *onedata* s)
      (restart-i2c s 2)
      (read-byte s)   ; Dummy read
      (setq j (read-byte s))
      (restart-i2c s)
      (write-byte *onedata* s)
      (write-byte (logior (ash 1 (logand y #x07)) j) s)
      (single #xEE s))))

Move the plotting position

The moveto routine moves the plotting position x0,y0:

(defvar x0 0)
(defvar y0 0)

(defun moveto (x y)
  (setq x0 x)
  (setq y0 y))

Draw a line

Finally, drawto draws from the current plotting position to the point x,y:

(defun drawto (x y)
  (let* ((dx (abs (- x x0)))
         (dy (abs (- y y0)))
         (sx (if (< x0 x) 1 -1))
         (sy (if (< y0 y) 1 -1))
         (err (- dx dy))
     (point x0 y0)
     (when (and (= x0 x) (= y0 y)) (return))
     (setq e2 (ash err 1)) 
     (when (> e2 (- dy)) (decf err dy) (incf x0 sx))
     (when (< e2 dx) (incf err dx) (incf y0 sy)))))

This uses Bresenham's line algorithm to draw the best line between two points without needing any divisions or multiplications [2].

Testing it out

At this point you should be able to enter the following commands:

(moveto 0 0)
(drawto 127 63)

and see a diagonal line across the display.

For a demonstration program see Graphics display interface in Lisp.

  1. ^ Geekcreit 1.3 Inch 4 Pin White OLED LCD Display on Banggood.
  2. ^ Bresenham's line algorithm on Wikipedia.