A tiny screen for a Raspberry Pi

I have a few old Raspberry Pis lying around, including a Model B Rev 1 running Raspberry Pi OS Buster lite. I use it to serve up my simple MUD game. It has no wi-fi so it’s connected by ethernet to my router which also gives it power over USB. I really must write that up some time.

Anyway, I have a whole bunch of cheap, tiny 1306 i2c OLED 128×64 pixel displays so I thought it might be fun to see if I could use one as a little shell display so I could do some basic admin on the headless Pi locally, just with a USB keyboard.

It took way longer than I expected, especially given I found someone had already done this, but it works. Here’s how I did it. Please bear in mind… my Pi is very old, I’m using Buster Lite as my OS, my OLED display’s address is 0x3C, it’s set to auto login to a command prompt, no GUI – your mileage may well vary!

It’s based on this project: https://github.com/satoshinm/oledterm – however, that was written in Python 2, uses a newer Pi, uses an spi not i2c interface, and because the OLED display libraries now only work in Python 3, I couldn’t get it to work. Issue #4 on that repo was the key to solving this, but I thought I’d summarise how I got this to work.

First, I connected the display. GND on the display to GND on the Pi, VCC to +3.3v on the Pi, SDA to Raspberry Pi pin 3, SCL to Pi pin 5 – remember this is an old Raspberry Pi original model B!

I installed git and downloaded oledterm:
git clone https://github.com/satoshinm/oledterm

This wouldn’t run for various reasons – not least because I needed to install luma.core to drive the OLED display, and I needed to install pip to install that:
sudo apt install python3-pip
sudo -H pip3 install --upgrade luma.oled

Then I copied the Python 3 version of oledterm from here and saved it as a file called oledterm3.py

I then edited /etc/rc.local to add this:
sudo python3 /home/pi/oledterm/oledterm3.py --display ssd1306 --interface i2c --i2c-port 0 &
exit 0

I also edited go.sh the same way. Let me explain the options in more detail. My display type is set to ssd1306, this is a very common kind of small OLED display. If my display’s i2c address were not 0x3c, I’d have needed to add an option to change that here. I specify the interface as i2c, rather than SPI as used in oledterm, and because I have a very old Pi I need to specify the i2c port as 0. With a newer Pi you could probably omit –i2c-port, or set it to 1.

I then unplugged the HDMI display, and rebooted – and lo! I could just about see a tiny shell and use my USB keyboard to type instructions! I could even edit text in nano – just about! Who needs more than 31 columns and 9 rows of text, anyway!?

If you like this, you may also like my adventures with using OLED displays in Arduino-based TinyBASIC computers, a micro:bit pulse oximeter, air quality sensor, or playing ArduBoy games on a BBC micro:bit.

Python 3 version of oledterm by Krizzel87

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# based on:
# Copyright (c) 2014-17 Richard Hull and contributors
# See LICENSE.rst for details.

import os
import time
import sys
import subprocess
from luma.core import cmdline
from luma.core.virtual import terminal
from PIL import ImageFont

ROWS = 9
COLS = 31

# based on demo_opts.py
from luma.core import cmdline, error
def get_device(actual_args=None):
    Create device from command-line arguments and return it.
    if actual_args is None:
        actual_args = sys.argv[1:]
    parser = cmdline.create_parser(description='luma.examples arguments')
    args = parser.parse_args(actual_args)

    if args.config:
        # load config from file
        config = cmdline.load_config(args.config)
        args = parser.parse_args(config + actual_args)

    # create device
        device = cmdline.create_device(args)
    except error.Error as e:


    return device

# based on luma.examples terminal
def make_font(name, size):
    font_path = os.path.abspath(os.path.join(
        os.path.dirname(__file__), 'fonts', name))
    return ImageFont.truetype(font_path, size)

def main():
    if not os.access(VIRTUAL_TERMINAL_DEVICE, os.R_OK):
       print(("Unable to access %s, try running as root?" % (VIRTUAL_TERMINAL_DEVICE,)))
       raise SystemExit

    fontname = "tiny.ttf"
    size = 6

    font = make_font(fontname, size) if fontname else None
    term = terminal(device, font, animate=False)

    for i in range(0, ROWS):
        term.puts(str(i) * COLS)

    while True:
        # Get terminal text; despite man page, `screendump` differs from reading vcs dev
        #data = file(VIRTUAL_TERMINAL_DEVICE).read()
        data = subprocess.check_output(["screendump"])
	#print [data]
        # Clear, but don't flush to avoid flashing
        term._cx, term._cy = (0, 0)
        #term._canvas.rectangle(term._device.bounding_box, fill=term.bgcolor)
        term._canvas.rectangle(term._device.bounding_box, fill="black")

        # puts() flushes on newline(), so reimplement it ourselves

        for char in data:
            if '\r' in chr(char):
            elif chr(10) in chr(char):
                # no scroll, no flush
                x = 0
                term._cy += term._ch
            elif '\b' in chr(char):
                x =- 1
            elif '\t' in chr(char):

        #print "refresh"
        #print data

if __name__ == "__main__":
    os.system("stty --file=/dev/console rows %d" % (ROWS,))
    os.system("stty --file=/dev/console cols %d" % (COLS,))
        device = get_device()
    except KeyboardInterrupt:
This entry was posted in computers, Raspberry Pi, Raspbian and tagged , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *


You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>