A display in MicroPython is a peripheral that shows text or graphics without relying on USB.
So far, the only way our microcontroller could tell us something was through print() via the USB cable. But if we want to build a wall thermostat, a clock, or a Tamagotchi, we need a display.
In the maker ecosystem, two standards dominate due to their price and ease of use:
- Character LCD (1602 / 2004): Ideal for displaying simple text. Robust and very readable.
- Graphic OLED (SSD1306): Small, sharp, and capable of drawing graphics, logos, and text of various sizes.
Both are easily managed using the I2C protocol we covered in previous chapters.
LCD Displays with I2C Adapter (PCF8574)
Classic LCD displays (based on the Hitachi HD44780 controller) are a wiring nightmare: they need 6 or more GPIO pins to function.
Fortunately, almost all displays we buy today come with a black “backpack” soldered on the back: a PCF8574 chip. This chip converts the I2C bus (2 wires) into the multiple signals the display requires.
Connection
- VCC: 5V (Most LCDs appear very dim when powered at 3.3V).
- GND: Ground.
- SDA / SCL: To the I2C pins on your board.
Voltage Precautions Even if you power the display at 5V (from the VIN/V5 pin), the SDA and SCL lines will also operate at 5V due to the display’s pull-up resistors. The ESP32 is usually tolerant, but using a Level Shifter is recommended if you want to be 100% safe. In practice, many people connect them directly and it works, but it’s good to be aware of the risk.
Library and Code
MicroPython does not include a driver for these LCDs by default. We need to install a library. The most common is the combination of lcd_api.py and esp8266_i2c_lcd.py (the name is misleading; it works for ESP32 and Pico too).
We can install it easily if we have mip:
import mip
mip.install("github:dhylands/python_lcd_driver/libs/esp8266_i2c_lcd.py")
mip.install("github:dhylands/python_lcd_driver/libs/lcd_api.py")
Once installed, using them is very simple:
from machine import SoftI2C, Pin
from esp8266_i2c_lcd import I2cLcd
import time
# I2C Configuration (Adjust pins to your board)
i2c = SoftI2C(scl=Pin(22), sda=Pin(21), freq=100000)
# Scan to find the address (usually 0x27 or 0x3F)
devices = i2c.scan()
if len(devices) == 0:
print("No LCD display found")
else:
lcd_address = devices[0]
# Initialize the LCD (address, rows, columns)
lcd = I2cLcd(i2c, lcd_address, 2, 16) # 2 rows, 16 columns
# Write text
lcd.putstr("Hello World!")
# Move cursor (column, row) -> Row 1 (the bottom one), Column 0
lcd.move_to(0, 1)
lcd.putstr("MicroPython :D")
time.sleep(3)
# Clear screen
lcd.clear()
# Backlight control
lcd.backlight_off()
time.sleep(1)
lcd.backlight_on()
OLED Displays (SSD1306)
If you’re looking for something more modern, OLED displays are the answer. They don’t need a backlight (each pixel emits its own light), achieving pure black and spectacular contrast.
Most cheap ones (0.96 inches) use the SSD1306 controller and have a resolution of 128x64 pixels.
The Framebuffer Concept
Unlike LCDs where we send characters (“A”, “B”…), OLED is a dot matrix.
MicroPython handles this using a Framebuffer.
- We create an image in the microcontroller’s RAM.
- We draw on it (points, lines, text).
- When we finish, we send the whole image to the display at once with the
.show()command.
Code
The ssd1306 driver is usually included in standard MicroPython firmware, so there’s nothing to install.
from machine import Pin, SoftI2C
import ssd1306
import time
# I2C Configuration
i2c = SoftI2C(scl=Pin(22), sda=Pin(21))
# Display dimensions
WIDTH = 128
HEIGHT = 64
# Initialize OLED object
oled = ssd1306.SSD1306_I2C(WIDTH, HEIGHT, i2c)
# 1. Clear screen (fill with black)
oled.fill(0)
# 2. Draw things in memory
oled.text("Hello OLED!", 0, 0) # Text at (x=0, y=0)
oled.text("Temp: 25.4 C", 0, 10) # Text further down
# Draw geometric shapes
oled.rect(10, 30, 50, 20, 1) # Rectangle (x, y, w, h, color)
oled.fill_rect(70, 30, 20, 20, 1) # Filled rectangle
oled.pixel(100, 40, 1) # A single pixel
# 3. IMPORTANT! Send data to the display
oled.show()
Looks pixelated?
The default text font is 8x8 pixels and cannot be easily resized with the basic driver. If you need large or fancy fonts, you’ll have to use advanced graphics libraries (like writer.py by Peter Hinch), though they consume more memory.
Inverting Colors and Tricks
A cool feature of OLEDs is that we can play with the bits.
# Invert colors (White background, black text)
oled.fill(1) # Fill everything with white (on)
oled.text("Black Text", 0, 0, 0) # The last 0 indicates "off" color
oled.show()
Which Display to Choose
| Feature | LCD 1602 / 2004 | OLED SSD1306 |
|---|---|---|
| Type | Character (Text) | Graphic (Pixels) |
| Size | Large and bulky | Very small (0.96”) |
| Visibility | Good in ambient light | Excellent in darkness, poor in sunlight |
| Power Consumption | High (due to backlight) | Very low (depends on lit pixels) |
| Use | Industrial menus, debug | Wearables, modern interfaces |
| Voltage | Preferably 5V | 3.3V (Perfect for ESP32/Pico) |
Now we have the ability to display data. We can combine what we’ve learned: read a temperature sensor (DHT22) and show the value on an OLED display in real-time. That’s already a complete project!