Introduction
In this tutorial, we will learn how to interface an OLED display with an ESP32 microcontroller using MicroPython. OLED displays are great for displaying text and simple graphics, making them ideal for various projects. We will use the SSD1306 OLED driver for this tutorial.
Prerequisites
Before we dive into the code, ensure you have the following:
- ESP32 microcontroller
- SSD1306 OLED display
- Breadboard and jumper wires
- MicroPython installed on the ESP32
- Thonny IDE or any other suitable IDE for writing and uploading MicroPython code
SSD1306 OLED Driver
First, let's look at the SSD1306 OLED driver module. This module handles the communication with the OLED display and provides functions to draw text and graphics.
ssd1306.py Module Code
This module handles the low-level operations of the SSD1306 OLED display.
#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()
main.py Code
The main script imports the SSD1306_I2C
class from the ssd1306.py
module and uses it to display text on the OLED.
# code written by Shemanto Sharkar (let's connect on LinkedIn: https://www.linkedin.com/in/shemanto/)
# step-1: importing necessary modules
from machine import Pin, I2C
import ssd1306
# step-2: telling ESP32 where our sensor's data pin is connected
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
# step-3: reading data continuously inside loop
while True:
try:
oled.text('Hello World!', 10, 10)
oled.show()
except OSError as e: # Error Handling
print("Error Data")
Detailed Code Breakdown
- Importing Necessary Modules: ```python
from machine import Pin, I2C
import ssd1306
- `from machine import Pin, I2C`: Imports the `Pin` and `I2C` classes from the `machine` module.
- `import ssd1306`: Imports the `ssd1306` module for OLED display control.
2. **Setting Up the I2
C Interface:**
```python
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
-
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
: Initializes the I2C interface on the ESP32 with GPIO 22 as the clock line and GPIO 21 as the data line.
- Setting Up the OLED Display: ```python
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
- Initializes the OLED display with a width of 128 pixels and a height of 64 pixels using the I2C interface.
4. **Displaying Text on the OLED:**
```python
while True:
try:
oled.text('Hello World!', 10, 10)
oled.show()
except OSError as e:
print("Error Data")
-
while True
: Starts an infinite loop to continuously display data. -
oled.text('Hello World!', 10, 10)
: Displays the text "Hello World!" at coordinates (10, 10) on the OLED. -
oled.show()
: Updates the OLED display with the new data. -
except OSError as e
: Catches any errors that occur during the display process and prints an error message.
Diagram
Here’s a diagram illustrating the connections:
ESP32 Microcontroller:
----------------------
___________
| |
| |
| 21 |--------> OLED (SDA)
| |
| 22 |--------> OLED (SCL)
|___________|
|
|
GND
VCC (3.3V)
Connections:
- Connect the VCC pin of the OLED to the 3.3V pin of the ESP32.
- Connect the GND pin of the OLED to the GND pin of the ESP32.
- Connect the SDA pin of the OLED to GPIO 21 of the ESP32.
- Connect the SCL pin of the OLED to GPIO 22 of the ESP32.
Conclusion
By following this tutorial, you will be able to display text on an OLED using an ESP32 microcontroller running MicroPython. This basic setup can be extended for various applications like displaying sensor data, system status, and more. Happy coding!
If you have any questions or need further assistance, feel free to reach out on LinkedIn: Shemanto Sharkar.