Skip to content

OLED Display

Time estimate: ~45 minutes Prerequisites: Enable I2C, MCP9808 Driver

Now that you have a kernel driver reading temperature from the MCP9808, let's display that data on an OLED screen. The MCP9808 driver exposes temperature through /dev/mcp9808 — you will build a Python application that reads this value and shows it on a small I2C OLED display using user-space tools.

An OLED (Organic Light-Emitting Diode) display uses organic compounds that emit light when an electric current passes through them — each pixel produces its own light, so there is no backlight. The small displays used in this lab use the SSD1306 controller chip, which accepts commands and pixel data over I2C (the same bus you used for the MCP9808 temperature sensor). The SSD1306 handles all display refresh internally — you just send it data.

Connect the OLED display with cables to the temperature sensor to the same I2C bus. Check if it connected properly:

i2cdetect -y 1

Should appear at address 0x3C. Note that 0x18 shows UU instead of the address — this means a kernel driver has already claimed that address (the MCP9808 temperature sensor driver):

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

Download the OLED tool

git clone https://github.com/gsebik/ssd1306_linux.git

Enter to the directory:

cd ssd1306_linux

Build it:

make

Copy to /usr/bin

sudo cp ssd1306_bin /usr/bin

Test it:

ssd1306_bin -c -I 128x32 -n 1 -m "Hello World!"

Flag reference: -c clears the display before drawing, -I 128x32 sets the display resolution (128 pixels wide, 32 pixels tall), -n 1 selects I2C bus 1, and -m specifies the message text to display.


Now create an application that display the indoor temperature and weather information on the OLED display.


Create a demo app

vim app.py
Warning

Get a free API key from weatherapi.com and replace YOUR_API_KEY_HERE. Never commit API keys to version control.

import time
import requests
import subprocess


# Set your Weather API key here
API_KEY = 'YOUR_API_KEY_HERE'  # Get a free key at weatherapi.com
LAT = 47.4979
LON = 19.0402

WEATHER_URL = f'https://api.weatherapi.com/v1/current.json?q={LAT}%2C{LON}&key={API_KEY}'


# Global variable to store latest weather info
latest_weather = "Getting weather..."
latest_indoor = "Getting temperature"

def get_weather():
    global latest_weather
    try:
        headers = {'accept': 'application/json'}
        response = requests.get(WEATHER_URL, headers=headers)
        response.raise_for_status()
        weather_data = response.json()
        temp_c = weather_data['current']['temp_c']
        condition = weather_data['current']['condition']['text']
        latest_weather = f"{temp_c}~C {condition}"
    except Exception as e:
        latest_weather = e # "Weather error"
        print(f"Failed to get weather: {e}")

def get_indoor_temp():
    global latest_indoor
    try:
        with open('/dev/mcp9808', 'r') as file:
            temp = file.read().strip()
            latest_indoor = f"{temp}~C indoor"

    except Exception as e:
        latest_indoor = "Get temp error"
        print(f"Failed to get indoor temp: {e}")


def update_display(current_time):
    # Prepare the lines to display
    time_part = current_time
    weather_part = latest_weather
    indoor_part = latest_indoor

    # Split time and weather into lines if needed
    lines = []
    lines.append(weather_part[:20])  # Topmost line (truncated if too long)
    #lines.append("")  # Another blank line
    lines.append(indoor_part[:20])  # Blank line
    lines.append(time_part[:20])  # Bottom line (time)

    # Join lines in correct order for OLED (bottom-up)
    display_text = "\\n".join(reversed(lines))
    #display_text = "hello"
    try:
        subprocess.run(["/usr/bin/ssd1306_bin", "-I", "128x32", "-c",  "-n", "1",  "-m", display_text], check=True)
    except Exception as e:
        print(f"Display update failed: {e}")

def main_loop():
    counter = 0
    while True:
        current_time = time.strftime('%Y-%m-%d %H:%M')

#        current_time = time.strftime('%Y-%m-%d %H:%M:%S')
        print(f"Current Time: {current_time}")

        if counter % 60 == 0:
            get_weather()

        get_indoor_temp()
        update_display(current_time)
        time.sleep(60)
        counter += 1

if __name__ == "__main__":
    try:
        print("Starting background app. Press Ctrl+C to stop.")
        main_loop()
    except KeyboardInterrupt:
        print("\nExiting cleanly.")

Create a systemd service file

A systemd service file tells the init system how to run your application as a managed background service. It has three sections:

  • [Unit] — metadata and ordering: Description names the service; After=network.target means "start this after networking is ready"
  • [Service] — how to run: ExecStart is the command, Restart=always means systemd will restart the process if it crashes
  • [Install] — when to start: WantedBy=multi-user.target means "start at normal boot" (not just in rescue mode)
sudo vim /etc/systemd/system/weather_app.service
[Unit]
Description=OLED Weather Service
After=network.target

[Service]
ExecStart=/usr/bin/python3 /home/linux/app.py
WorkingDirectory=/home/linux/
StandardOutput=inherit
StandardError=inherit
Restart=always
User=linux
Group=linux

[Install]
WantedBy=multi-user.target

Course Overview | Next: OLED Framebuffer Driver →

Info

Don't have an OLED display? If you have the BUSE LED matrix hardware, you can continue with BUSE Framebuffer Driver instead — it teaches the same fbdev concepts.