Raspberry Pi Pico Projects

Interfacing SSD1306 OLED Display with Raspberry Pi Pico

How to interface an SSD1306 OLED display with a Raspberry Pi Pico board

Overview: RPI Pico with OLED Display

In this tutorial, we will learn Interfacing SSD1306 OLED Display with Raspberry Pi Pico microcontroller. OLED displays are known for their attractive appearance and pixel density, making them ideal for displaying small-level graphics.

We will be using the 0.96-inch I2C OLED display. As it only requires two wires for interfacing. The Raspberry Pi Pico, which comes with an RP2040 microcontroller, has two pairs of I2C pins. Any of these I2C pins can be used to interface with the SSD1306 OLED display.

The code for this tutorial will be written in MicroPython and requires the SSD1306 driver code. Once the driver code is written, we can display any text or graphics on the OLED display. In this example, we will display the analog voltage value from a potentiometer on the OLED display. However, before starting this guide, we recommend going through the Raspberry Pi Pico Getting Started Guide and familiarizing yourself with the use of I2C pins.


Components Required

You can easily purchase the required components from the links provided below.

S.NCOMPONENTS NAMEQUANTITYPURCHASE LINKS
1Raspberry Pi Pico Basic Starter Kit1 Amazon | AliExpress
2Raspberry Pi Pico1 Amazon | AliExpress
30.96" I2C OLED Display1 Amazon | AliExpress
4Potentiometer1 Amazon | AliExpress
5Breadboard1 Amazon | AliExpress
6Jumper Wires10 Amazon | AliExpress

SSD1306 OLED Display

The SSD1306 OLED display is popular for microcontroller projects due to its attractive viewing angle and high pixel density. This specific model is a 0.96/1.3-inch blue OLED display module that can be interfaced with any microcontroller using SPI or I2C protocols. It has a resolution of 128×64 and the package includes a display board, a display, and a 4-pin male header pre-soldered to the board.

SSD1306 I2C OLED Display

OLED technology, short for Organic Light-Emitting Diode, is a self-light-emitting technology that uses a thin, multi-layered organic film placed between an anode and cathode. Unlike LCD technology, OLED does not require a backlight. That makes it a popular choice for displays.


Interfacing SSD1306 OLED Display with Raspberry Pi Pico

To interface the SSD1306 OLED display with the Raspberry Pi Pico microcontroller. We will use the I2C protocol. The Raspberry Pi Pico board has two pairs of I2C pins that can be used for interfacing. To learn more about using I2C pins and applications, refer to our Raspberry Pi Pico I2C guide.

Interfacing SSD1306 0.96 inch OLED Display with Raspberry Pi Pico

In this example, we will be feeding an analog voltage input from a potentiometer to the GP28 analog pin of the Raspberry Pi Pico. Then displaying the analog voltage on the OLED screen. The circuit is simple and easy to use, as it only requires connecting the SDA and SCL pins of the OLED display to the GP21 and GP20 pins of the Pico, respectively.


OLED Display Raspberry Pi Pico Code

We will be using MicroPython code to interface the OLED display with the Pico board. You can either use Thonny IDE or the uPyCraft IDE for programming. Here I am using Thonny IDE. So, the Thonny IDE requires an SSD1306 driver code. which must be written before displaying any text or graphics on the OLED display.

The programming process is divided into two main parts. writing the SSD1306.py driver code and the main.py code. The SSD1306.py code should be written and uploaded first, and then the main.py code can be uploaded and run.

SSD1306.py

The code for the SSD1306.py file is responsible for setting up the OLED display and configuring it to work with the Raspberry Pi Pico. It includes library imports, initialization commands, and functions for displaying text and graphics on the OLED screen.

So in Thonny IDE, create a new file. Copy the following code & save the file by the name ssd1306.py.

# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
 
from micropython import const
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)
 
# Subclassing FrameBuffer provides support for graphics primitives
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
class SSD1306(framebuf.FrameBuffer):
    def __init__(self, width, height, external_vcc):
        self.width = width
        self.height = height
        self.external_vcc = external_vcc
        self.pages = self.height // 8
        self.buffer = bytearray(self.pages * self.width)
        super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
        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.width > 2 * self.height 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 poweron(self):
        self.write_cmd(SET_DISP | 0x01)
 
    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_data(self.buffer)
 
 
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)
        self.write_list = [b"\x40", None]  # Co=0, D/C#=1
        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_data(self, buf):
        self.write_list[1] = buf
        self.i2c.writevto(self.addr, self.write_list)
 
 
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
        import time
 
        self.res(1)
        time.sleep_ms(1)
        self.res(0)
        time.sleep_ms(10)
        self.res(1)
        super().__init__(width, height, external_vcc)
 
    def write_cmd(self, cmd):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs(1)
        self.dc(0)
        self.cs(0)
        self.spi.write(bytearray([cmd]))
        self.cs(1)
 
    def write_data(self, buf):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs(1)
        self.dc(1)
        self.cs(0)
        self.spi.write(buf)
        self.cs(1)

main.py

The main.py code, on the other hand, is where the main logic of the program resides. This code reads the analog voltage value from the potentiometer and displays it on the OLED screen. It also includes commands for controlling the OLED display and updating the displayed information.

Open a new tab again in the Thonny IDE. Copy the following code and paste it on the Thonny IDE new Window. Save the file by the name main.py.

# Display Image & text on I2C driven ssd1306 OLED display 
from machine import Pin, I2C
from ssd1306 import SSD1306_I2C
import framebuf
import machine
import utime
 
sensor_temp = machine.ADC(28)
conversion_factor = 3.3 / (65535)
 
WIDTH  = 128                                            # oled display width
HEIGHT = 64                                             # oled display height
 
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=200000)       # Init I2C using pins GP8 & GP9 (default I2C0 pins)
print("I2C Address      : "+hex(i2c.scan()[0]).upper()) # Display device address
print("I2C Configuration: "+str(i2c))                   # Display I2C config
 
 
oled = SSD1306_I2C(WIDTH, HEIGHT, i2c)                  # Init oled display
 
# Raspberry Pi logo as 32x32 bytearray
buffer = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|?\x00\x01\x86@\x80\x01\x01\x80\x80\x01\x11\x88\x80\x01\x05\xa0\x80\x00\x83\xc1\x00\x00C\xe3\x00\x00~\xfc\x00\x00L'\x00\x00\x9c\x11\x00\x00\xbf\xfd\x00\x00\xe1\x87\x00\x01\xc1\x83\x80\x02A\x82@\x02A\x82@\x02\xc1\xc2@\x02\xf6>\xc0\x01\xfc=\x80\x01\x18\x18\x80\x01\x88\x10\x80\x00\x8c!\x00\x00\x87\xf1\x00\x00\x7f\xf6\x00\x008\x1c\x00\x00\x0c \x00\x00\x03\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
 
while True:
    reading = sensor_temp.read_u16() * conversion_factor
    # Load the raspberry pi logo into the framebuffer (the image is 32x32)
    fb = framebuf.FrameBuffer(buffer, 32, 32, framebuf.MONO_HLSB)
 
    # Clear the oled display in case it has junk on it.
    oled.fill(0)
 
    # Blit the image from the framebuffer to the oled display
    oled.blit(fb, 96, 0)
       
    
    # Add some text
    oled.text("ADC: ",5,8)
    oled.text(str(round(reading,2)),40,8)
 
 
    # Finally update the oled display so the image & text is displayed
    oled.show()

Testing

Once the code is uploaded and running. The OLED display will immediately start showing the Raspberry Pi logo along with the analog voltage value from the potentiometer. By rotating the potentiometer knob, you can see the OLED display showing different values. Overall, interfacing an SSD1306 OLED display with a Raspberry Pi Pico is a straightforward process with the help of this MicroPython code.

OLED Display Potentiometer and RPI Pico
Interfacing SSD1306 OLED Display with Raspberry Pi Pico

Together, these two code files work in harmony to create a functioning program that can read an analog voltage value and display it on an OLED screen. This is a simple but powerful example of how the Raspberry Pi Pico can be used to interface with various peripheral devices and sensors.

Raspberry Pi Pico with OLED Dispaly
Interfacing SSD1306 OLED Display with Raspberry Pi Pico

Conclusion

In conclusion, interfacing the SSD1306 OLED display with the Raspberry Pi Pico is a simple process using MicroPython code. With the OLED display, you can display text, graphics, and sensor data in a clear and attractive way.

Related Articles

One Comment

  1. Pingback: Read Internal Temperature Sensor Value from Raspberry Pi Pico

Leave a Reply

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

Back to top button