MicroPython: OLED Display with ESP32 and ESP8266

In this guide, you’ll learn how to use the 0.96 inch SSD1306 OLED display with an ESP32 or ESP8266 using MicroPython firmware. As an example, we’ll show you how to display a simple ‘Hello, World!’ message. Later, we’ll also show you how to use other useful functions to interact with the OLED display.

oled display esp8266 esp32 micropython

You might also like reading our dedicated guides for ESP with OLED display using Arduino IDE:

Prerequisites

To follow this tutorial you need MicroPython firmware installed in your ESP32 or ESP8266 boards. You also need an IDE to write and upload the code to your board. We suggest using Thonny IDE or uPyCraft IDE:

Learn more about MicroPython: MicroPython Programming with ESP32 and ESP8266 eBook

Introducing the OLED display

In this guide we’ll use the 0.96 inch SSD1306 OLED display that is 128×64 pixels and uses I2C communication protocol.

ssd1306 0.96inch i2c oled display

I2C communication

For the I2C OLED display, these are the connections you need to make:

OLEDESP32ESP8266
Vin3.3V3.3V
GNDGNDGND
SCLGPIO 22GPIO 5 (D1)
SDAGPIO 21GPIO 4 (D2)

Parts Required

Here’s a list of parts you need for this project:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematic – ESP32

Follow the next schematic diagram if you’re using an ESP32 board:

oled display with esp32 micropython

Recommended reading: ESP32 Pinout Reference Guide

Schematic – ESP8266

Follow the next schematic diagram if you’re using an ESP8266 board:

esp8266 oled display micropython

Recommended reading: ESP8266 Pinout Reference Guide

SSD1306 OLED Library

The library to write to the OLED display isn’t part of the standard MicroPython library by default. So, you need to upload the library to your ESP32/ESP8266 board.

#MicroPython SSD1306 OLED driver, I2C and SPI interfaces created by Adafruit

import time
import framebuf

# register definitions
SET_CONTRAST        = const(0x81)
SET_ENTIRE_ON       = const(0xa4)
SET_NORM_INV        = const(0xa6)
SET_DISP            = const(0xae)
SET_MEM_ADDR        = const(0x20)
SET_COL_ADDR        = const(0x21)
SET_PAGE_ADDR       = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP       = const(0xa0)
SET_MUX_RATIO       = const(0xa8)
SET_COM_OUT_DIR     = const(0xc0)
SET_DISP_OFFSET     = const(0xd3)
SET_COM_PIN_CFG     = const(0xda)
SET_DISP_CLK_DIV    = const(0xd5)
SET_PRECHARGE       = const(0xd9)
SET_VCOM_DESEL      = const(0xdb)
SET_CHARGE_PUMP     = const(0x8d)


class SSD1306:
    def __init__(self, width, height, external_vcc):
        self.width = width
        self.height = height
        self.external_vcc = external_vcc
        self.pages = self.height // 8
        # Note the subclass must initialize self.framebuf to a framebuffer.
        # This is necessary because the underlying data buffer is different
        # between I2C and SPI implementations (I2C needs an extra byte).
        self.poweron()
        self.init_display()

    def init_display(self):
        for cmd in (
            SET_DISP | 0x00, # off
            # address setting
            SET_MEM_ADDR, 0x00, # horizontal
            # resolution and layout
            SET_DISP_START_LINE | 0x00,
            SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
            SET_MUX_RATIO, self.height - 1,
            SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
            SET_DISP_OFFSET, 0x00,
            SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12,
            # timing and driving scheme
            SET_DISP_CLK_DIV, 0x80,
            SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1,
            SET_VCOM_DESEL, 0x30, # 0.83*Vcc
            # display
            SET_CONTRAST, 0xff, # maximum
            SET_ENTIRE_ON, # output follows RAM contents
            SET_NORM_INV, # not inverted
            # charge pump
            SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14,
            SET_DISP | 0x01): # on
            self.write_cmd(cmd)
        self.fill(0)
        self.show()

    def poweroff(self):
        self.write_cmd(SET_DISP | 0x00)

    def contrast(self, contrast):
        self.write_cmd(SET_CONTRAST)
        self.write_cmd(contrast)

    def invert(self, invert):
        self.write_cmd(SET_NORM_INV | (invert & 1))

    def show(self):
        x0 = 0
        x1 = self.width - 1
        if self.width == 64:
            # displays with width of 64 pixels are shifted by 32
            x0 += 32
            x1 += 32
        self.write_cmd(SET_COL_ADDR)
        self.write_cmd(x0)
        self.write_cmd(x1)
        self.write_cmd(SET_PAGE_ADDR)
        self.write_cmd(0)
        self.write_cmd(self.pages - 1)
        self.write_framebuf()

    def fill(self, col):
        self.framebuf.fill(col)

    def pixel(self, x, y, col):
        self.framebuf.pixel(x, y, col)

    def scroll(self, dx, dy):
        self.framebuf.scroll(dx, dy)

    def text(self, string, x, y, col=1):
        self.framebuf.text(string, x, y, col)


class SSD1306_I2C(SSD1306):
    def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False):
        self.i2c = i2c
        self.addr = addr
        self.temp = bytearray(2)
        # Add an extra byte to the data buffer to hold an I2C data/command byte
        # to use hardware-compatible I2C transactions.  A memoryview of the
        # buffer is used to mask this byte from the framebuffer operations
        # (without a major memory hit as memoryview doesn't copy to a separate
        # buffer).
        self.buffer = bytearray(((height // 8) * width) + 1)
        self.buffer[0] = 0x40  # Set first byte of data buffer to Co=0, D/C=1
        self.framebuf = framebuf.FrameBuffer1(memoryview(self.buffer)[1:], width, height)
        super().__init__(width, height, external_vcc)

    def write_cmd(self, cmd):
        self.temp[0] = 0x80 # Co=1, D/C#=0
        self.temp[1] = cmd
        self.i2c.writeto(self.addr, self.temp)

    def write_framebuf(self):
        # Blast out the frame buffer using a single I2C transaction to support
        # hardware I2C interfaces.
        self.i2c.writeto(self.addr, self.buffer)

    def poweron(self):
        pass


class SSD1306_SPI(SSD1306):
    def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
        self.rate = 10 * 1024 * 1024
        dc.init(dc.OUT, value=0)
        res.init(res.OUT, value=0)
        cs.init(cs.OUT, value=1)
        self.spi = spi
        self.dc = dc
        self.res = res
        self.cs = cs
        self.buffer = bytearray((height // 8) * width)
        self.framebuf = framebuf.FrameBuffer1(self.buffer, width, height)
        super().__init__(width, height, external_vcc)

    def write_cmd(self, cmd):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs.high()
        self.dc.low()
        self.cs.low()
        self.spi.write(bytearray([cmd]))
        self.cs.high()

    def write_framebuf(self):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs.high()
        self.dc.high()
        self.cs.low()
        self.spi.write(self.buffer)
        self.cs.high()

    def poweron(self):
        self.res.high()
        time.sleep_ms(1)
        self.res.low()
        time.sleep_ms(10)
        self.res.high()

View raw code

Follow the next set of instructions for the IDE you’re using:

  • A. Upload OLED library with uPyCraft IDE
  • B. Upload OLED library with Thonny IDE

A. Upload OLED library with uPyCraft IDE

This section shows how to upload a library using uPyCraft IDE. If you’re using Thonny IDE, read the next section.

1. Create a new file by pressing the New File button.

2. Copy the OLED library code into that file. The OLED library code can be found here.

Note: the SSD1306 OLED display library was built by Adafruit and will no longer
be updated. At the moment, it works fine. However, we’ll update this guide if we
find a similar library that works as well as this one.

3. After copying the code, save the file by pressing the Save button.

4. Call this new file “ssd1306.py” and press ok.

import library upycraft ide save file

5. Click the Download and Run button.

The file should be saved on the device folder with the name “ssd1306.py” as
highlighted in the following figure.

import save library uPyCraft IDE script file

Now, you can use the library functionalities in your code by importing the library.

B. Upload OLED library with Thonny IDE

If you’re using Thonny IDE, follow the next steps:

1. Copy the library code to a new file. The OLED library code can be found here.

2. Save that file as ssd1306.py.

3. Go to Device > Upload current script with the current name

thonny ide upload library file script micropython

And that’s it. The library was uploaded to your board. To make sure that it was uploaded successfully, in the Shell you can type:

%lsdevice

It should return the files currently saved on your board. One of them should be the ssd1306.py file.

show device folder thonny ide

After uploading the library to your board, you can use the library functionalities in your code by importing the library.

Code

After uploading the library to the ESP32 or ESP8266, copy the following code to the
main.py file. It simply prints the ‘Hello, World!‘ message three times in the display.

# Complete project details at https://RandomNerdTutorials.com

from machine import Pin, I2C
import ssd1306
from time import sleep

# ESP32 Pin assignment 
i2c = I2C(-1, scl=Pin(22), sda=Pin(21))

# ESP8266 Pin assignment
#i2c = I2C(-1, scl=Pin(5), sda=Pin(4))

oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)

oled.text('Hello, World 1!', 0, 0)
oled.text('Hello, World 2!', 0, 10)
oled.text('Hello, World 3!', 0, 20)
        
oled.show()

View raw code

How the Code Works

Start by importing the necessary modules to interact with the GPIOs and send data to the OLED via I2C communication. You need to import the Pin and I2C classes from the machine module.

from machine import Pin, I2C

You also need to import the OLED library that you previously uploaded to theboard as ssd1306.py file.

import ssd1306

The ESP32 default I2C pins are GPIO 22 (SCL) and GPIO 21 (SDA). The ESP8266 default I2C pins are GPIO 5 (SLC) and GPIO 4 (SDA).

Use the following line if you’re using an ESP32 board:

#ESP32 Pin assignment
i2c = I2C(-1, scl=Pin(22), sda=Pin(21))

Comment the previous line and uncomment the following if you’re using an ESP8266 board:

#ESP8266 Pin assignment
i2c = I2C(-1, scl=Pin(5), sda=Pin(4))

Define the OLED width and height on the following variables:

oled_width = 128
oled_height = 64

After that, create an SSD1306_I2C object called oled. This object accepts the OLED width, height, and the I2C pins you’ve defined earlier.

oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)

After initializing the OLED display, you just need to use the text() function on the oled object to write text. After the text() function, you need to call the show() method to update the OLED.

oled.text('Hello, World 1!', 0, 0)
oled.text('Hello, World 2!', 0, 10)
oled.text('Hello, World 3!', 0, 20)
oled.show()

The text() method accepts the following arguments (in this order):

  • Message: must be of type String
  • X position: where the text starts
  • Y position: where the text is displayed vertically
  • Text color: it can be either black or white. The default color is white and this parameter is optional.
    • 0 = black
    • 1 = white

For example, the following line writes the ‘Hello, World 1!’ message in white color. The text starts on x = 0 and y = 0.

oled.text('Hello, World 1!', 0, 0)

The next line of code writes the text on the next line (y =10).

oled.text('Hello, World 2!', 0, 10)

Finally, for the changes to take effect, use the show() method on the oled object.

oled.show()

Demonstration

Upload the code to your board. Your OLED display should look as follows:

oled display text esp8266 esp32 micropython

Other OLED functions

The library provides other methods to interact with the OLED display.

Fill the screen

To fill the entire screen with white, use the fill() function as follows:

oled.fill(1)
oled.show()
oled fill white screen esp8266 esp32 micropython

To clear the screen use the fill() method as pass 0 as argument. (Sets all pixels to black):

oled.fill(0)
oled.show()

Draw a pixel

To draw a pixel, use the pixel() method, followed by the show() method. The pixel() method accepts the following arguments:

  • X coordinate: pixel location horizontally
  • Y coordinate: pixel location vertically
  • Pixel color: can be black or white
    • 0 = black
    • 1 = white

For example, to draw a white pixel on the top left corner:

oled.pixel(0, 0, 1)
oled.show()
oled display a pixel esp8266 esp32 micropython

Invert colors

You can also invert the OLED colors: white with black and vice versa, using the invert() method:

oled.invert(True)
oled invert colors esp8266 esp32 micropython

To get back to the original colors, use:

oled.invert(False)

Displaying data from sensors

The text() function only accepts variables of type String as a message. Sensor readings are usually stored in int or float variables.

If you want to display sensor readings and they are stored in int or float variables, they should be converted to a String. To convert the data to a string you can use the str() function:

temperature = 12.34
temperature_string = str(temperature)

Then, you can display the temperature_string variable on the OLED using the text() and show() methods:

oled.text(temperature_string)
oled.show()

Wrapping Up

This quick guide showed you how to use the OLED basic functionalities: write text and draw pixels with the ESP32 and ESP8266 using MicroPython. Now, you can use the OLED in your own projects to display messages, or sensor readings.

We have other MicroPython tutorials that you might also like:

If you want to learn more about programming the ESP32 and ESP8266 boards with MicroPython, take a look our eBook: MicroPython Programming with ESP32 and ESP8266.


Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »

Learn how to program and build projects with the ESP32 and ESP8266 using MicroPython firmware DOWNLOAD »


Enjoyed this project? Stay updated by subscribing our weekly newsletter!

3 thoughts on “MicroPython: OLED Display with ESP32 and ESP8266”

  1. Thanks a lot for the great tutorial. Everything worked for me right away!

    I still have a question:

    Is it possible to use a different font with the given driver “ssd1306.py”?

Leave a Comment

Download our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.