Get in contact

posts/page

Pi Pico OLED Tutorial – Damien Kee

By the end of this tutorial your Pi Pico will be showing text, shapes and a moving progress bar on a small OLED screen. Work through each step in order and tick it off as you go. Don’t skip ahead: each part builds on the one before.

What you need

ItemNotes
Raspberry Pi Pico 2With header pins soldered on
Micro-USB cableMust be a data cable, not a charge-only cable
ComputerWindows, Mac or Linux
0.96" OLED display128 x 64 pixels, I2C, 4 pins labelled GND, VCC, SCL, SDA
BreadboardOptional, but makes wiring easier
4 jumper wiresIdeally 4 different colours

How to read this tutorial

  • Each part is broken into small numbered steps. Do one step at a time.
  • Text in this style is something you type, or the exact name of a menu or button.
  • Screenshots and photos show what your screen or circuit should look like, so you can check yours matches before moving on.
  • A Check at the end of each part tells you what you should see before moving on.

Part 1: Set up Thonny to talk to the Pico

Thonny is the program you write code in. The Pico needs MicroPython (a small version of Python) installed on it before Thonny can talk to it. You only do this part once per Pico.

1A. Install Thonny

  1. Go to thonny.org.
  2. Click the download link for your computer (Windows, Mac or Linux).
  3. Run the installer and accept the default settings.
  4. Open Thonny.
  5. If Thonny asks for your language and initial settings, pick your language and choose Standard, then click Let's go!.

1B. Put MicroPython on the Pico

Skip to 1C if your teacher has told you MicroPython is already installed.

  1. Find the small white button on the Pico labelled BOOTSEL.
  2. Hold BOOTSEL down.
  3. While still holding it, plug the USB cable into the Pico, then into the computer.
  4. Let go of BOOTSEL. A new drive called RP2350 should appear on the computer, like a USB stick. (On an older Pico 1 it’s called RPI-RP2.)
  1. In Thonny, click the interpreter label in the bottom-right corner of the window.
  2. Click Configure interpreter....
  3. In the first drop-down, choose MicroPython (Raspberry Pi Pico).
  1. Click the link Install or update MicroPython near the bottom of the window.
  2. Set the boxes like this (check them against the picture below):
    • Target volume: RP2350 (the drive letter after it, such as (E:), may be different on your computer)
    • MicroPython family: RP2
    • Variant: Raspberry Pi • Pico 2 (it must say Pico 2 and must not say W)
    • Version: leave as the newest

Important: pick the right variant. Each kind of Pico needs its own MicroPython. This tutorial uses the Pico 2, which has Pico 2 printed on the board. A W board (Pico W or Pico 2 W) has a small silver metal box (the Wi-Fi chip) at the end away from the USB port. Choosing the wrong variant causes errors later.

Install MicroPython window set up for a Pico 2

  1. Click Install and wait for it to say Done!.
  2. Click Close, then click OK to close the Options window.

1C. Connect Thonny to the Pico

  1. Make sure the Pico is plugged in (no need to hold BOOTSEL this time).
  2. Click the interpreter label in the bottom-right corner again.
  3. Choose MicroPython (Raspberry Pi Pico) from the list. On some computers it also shows a port name, such as COM3 or /dev/ttyACM0.
  1. Look at the Shell panel at the bottom. It should show a message starting with MicroPython v1. and ending with Raspberry Pi Pico2 with RP2350, followed by a >>> prompt.
  2. Click next to the >>>, type print("Hello World") and press Enter. The Pico should reply Hello World.

Thonny Shell showing the MicroPython version and Hello World printed back

Check: The Shell prints Hello World back to you.

The Pico has a tiny green LED on the board. Making it blink proves your code is reaching the Pico.

2A. Write the program

  1. Click File > New (or the white page icon) to open a blank tab.
  2. Type in this code exactly, including the spaces at the start of the indented lines:
from machine import Pin
import time

led = Pin("LED", Pin.OUT)   # the on-board LED

while True:
    led.on()
    time.sleep(0.5)          # wait half a second
    led.off()
    time.sleep(0.5)

2B. Run it

  1. Click the green Run current script button (the play triangle), or press F5.
  2. Thonny asks where to save. Click Raspberry Pi Pico.
  1. Name the file blink.py and click OK.
  2. Watch the Pico. The LED next to the USB port should flash on and off every half second.

Error [CYW43] Failed to start CYW43? Your Pico has a W (Wi-Fi) version of MicroPython, but your board has no Wi-Fi chip. Go back to Part 1B and reinstall MicroPython, choosing the Raspberry Pi Pico 2 variant (no W). Then run blink.py again.

  1. If you can’t see a Files panel on the left of Thonny, click View > Files to open it. The bottom half shows the files saved on the Pico, including blink.py.

2C. Stop it

  1. Click the red Stop button (or press Ctrl + F2).
  2. The LED stops changing and the >>> prompt comes back in the Shell.

2D. Try a change

  1. Change both 0.5 values to 0.1.
  2. Click Run again. The LED should flash much faster.

Check: The LED blinks, and you can start and stop it from Thonny.

Why the program never ends: while True: means “repeat forever”. Programs on microcontrollers often run forever, so you stop them with the Stop button.

Part 3: Install the ssd1306 library

A library is code someone else wrote that you can use. The ssd1306 library knows how to talk to the OLED, so you don’t have to. It gets saved onto the Pico, so you only install it once per Pico.

3A. Install with the package manager

  1. Make sure the Pico is plugged in and connected (you see >>> in the Shell).
  2. Click the red Stop button so nothing is running.
  3. Click Tools > Manage packages....
  1. In the search box, type ssd1306.
  2. Click Search micropython-lib and PyPI.
  1. Click the result called ssd1306. If you see more than one, choose the one from micropython-lib.
  2. Click Install.
  3. Wait until the install finishes. The page should now show Installed version and Installed to: /lib, like the picture below.
  4. Click Close.

Manage packages window after ssd1306 has installed to /lib

3B. Check it installed

  1. In the Files panel, look in the Raspberry Pi Pico section.
  2. You should see a folder called lib. Click the + next to it to open it.
  3. Inside should be ssd1306.py and a folder called ssd1306-0.1.0.dist-info (the numbers may be different). Leave both of them there.

Files panel showing the lib folder on the Pico with ssd1306.py inside

  1. In the Shell, type import ssd1306 and press Enter.
  2. If nothing happens and you get a new >>>, it worked. An error such as ImportError: no module named 'ssd1306' means it didn’t install.

3C. Backup method (only if 3A didn’t work)

Some school networks block the package manager. Your teacher may give you the file ssd1306.py instead.

  1. In the top half of the Files panel (This computer), find where you saved ssd1306.py.
  2. Right-click ssd1306.py.
  3. Click Upload to /.
  4. Check it now appears in the Raspberry Pi Pico section.

Check: Typing import ssd1306 in the Shell gives no error.

Part 4: Wire up the OLED

The OLED needs only 4 wires: two for power and two for data. The data connection is called I2C: SDA carries the data and SCL carries a timing signal (the clock).

4A. Unplug first

  1. Click Stop in Thonny.
  2. Unplug the USB cable from the Pico. Always wire with the power off.

4B. Find the pins on the Pico

  1. Turn the Pico so the USB port points up and the chip faces you.
  2. Pin 1 is the top-left pin. Pins count down the left side (1 to 20), then up the right side (21 to 40).
  3. So pin 40 is the top-right pin, and pin 2 is just below pin 1.

Pico 2 pinout diagram with USB at the top, pin 1 (GP0) top-left and pin 40 (VBUS) top-right

4C. Connect the 4 wires

Connect one wire at a time and tick it off. Careful: on the OLED the order is GND, VCC, SCL, SDA, which is easy to mix up.

Pin names can vary. On some OLED boards the SCL pin is labelled SCK instead. They mean the same thing (the clock), so connect SCK wherever this tutorial says SCL. Some boards also put the pins in a different order, so always go by the labels printed on your board.

DoneOLED pinPico pinPico pin nameSuggested wire colour
[ ]GNDPin 38GNDBlack
[ ]VCCPin 363V3(OUT)Red
[ ]SCLPin 2GP1Yellow
[ ]SDAPin 1GP0Blue

A Pico 2 and 0.96 inch OLED wired together on a breadboard

4D. Double-check before powering up

  • VCC goes to pin 36 (3V3), not pin 40 or pin 39.
  • GND goes to a GND pin.
  • SDA goes to GP0 and SCL goes to GP1 (not swapped).
  • No bare wire ends are touching each other.

4E. Power up

  1. Plug the USB cable back in.
  2. In Thonny, click Stop once to reconnect if the Shell doesn’t show >>>.

Check: The Shell shows >>>. The OLED will stay blank for now. That’s normal, because it needs a program to tell it what to show.

Part 5: Hello World on the OLED

Now for your first OLED program. It sets up the screen, writes some text, and shows it.

5A. Check the Pico can see the OLED

  1. In the Shell, type these two lines, pressing Enter after each:
from machine import Pin, I2C
I2C(0, sda=Pin(0), scl=Pin(1)).scan()
  1. You should see [60]. That’s the OLED’s address (60 is the same as 0x3C).
  2. If you see [61], write that down. Your OLED uses address 0x3D (see Troubleshooting).
  3. If you see [] (empty), the Pico can’t find the OLED. Unplug and check your wiring against Part 4.

5B. Write the program

  1. Click File > New to open a blank tab.
  2. Type in this code:
from machine import Pin, I2C
import ssd1306

# Set up the screen
i2c = I2C(0, sda=Pin(0), scl=Pin(1))
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

oled.fill(0)                    # clear the screen (0 = black)
oled.text("Hello World!", 0, 0) # text at x=0, y=0 (top-left)
oled.text("My name is...", 0, 20)
oled.show()                     # send it to the screen

5C. Run it

  1. Click Run (or press F5).
  2. Save it to the Raspberry Pi Pico as hello_oled.py.
  3. Look at the OLED. You should see Hello World! at the top.

The OLED showing Hello World! on the top line and My name is… below it

Taking a photo of your OLED? The screen redraws itself many times a second, one row at a time. Your eyes see the whole picture, but a phone camera is fast enough to catch it part-way through, so some text can look cut off in the photo (like “My name is…” above). It’s not a fault. Keep this in mind when you take photos for your documentation: take a few shots, or try dimmer light, and keep the best one.

5D. Make it yours

  1. Change My name is... to your own name.
  2. Run it again.
  3. Try changing the 20 to 40. Your name moves further down the screen.
  4. Now delete the line oled.show() and run it again. What happens? Put the line back.

5E. How it works

  • The screen is 128 pixels wide (x: 0 to 127) and 64 pixels tall (y: 0 to 63).
  • (0, 0) is the top-left corner. Bigger x goes right; bigger y goes down.
  • Each letter is 8 x 8 pixels, so 16 letters fit across and 8 lines fit down.
  • Drawing commands like text() draw in the Pico’s memory. Nothing appears until you call oled.show(). That’s why the screen stayed the same in step 5D.4.

Check: Your name appears on the OLED.

Part 6: Run the OLED demo program

The demo shows off the main things the OLED can do: text, lines, boxes, circles and a moving progress bar.

6A. Open the demo

  1. Click File > New.
  2. Copy in the code below (or open the main.py file your teacher gave you).
# OLED demo using the standard ssd1306 library
# Wiring: SDA -> GP0 (pin 1), SCL -> GP1 (pin 2), VCC -> 3V3 (pin 36), GND -> GND
# Screen: x = 0 to 127 (left to right), y = 0 to 63 (top to bottom)
# Text is 8 x 8 pixels, so 16 characters across and 8 lines down

from machine import Pin, I2C
import ssd1306
import time

# --- Set up the screen ---
i2c = I2C(0, sda=Pin(0), scl=Pin(1))
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

# --- Text ---
oled.fill(0)                          # clear the screen
oled.text("Hello, Pico!", 16, 0)      # x = 16 centres 12 characters
oled.text("Line 2 text", 0, 16)       # y = 16 is text line 2
oled.text("Temp: " + str(23.5) + " C", 0, 32)
oled.show()                           # put it on the screen
time.sleep(3)

# --- Shapes ---
oled.fill(0)
oled.rect(0, 0, 128, 64, 1)           # border round the screen
oled.line(0, 0, 127, 63, 1)           # diagonal line
oled.ellipse(64, 32, 20, 20, 1)       # circle in the middle
oled.fill_rect(90, 10, 20, 15, 1)     # solid box
oled.show()
time.sleep(3)

# --- Counter with a progress bar ---
for i in range(101):
    oled.fill(0)
    oled.text("Counting...", 20, 8)
    oled.text(str(i) + "%", 52, 32)
    oled.rect(0, 52, 128, 10, 1)              # bar outline
    oled.fill_rect(2, 54, int(i * 1.24), 6, 1)  # bar fill (124 px when full)
    oled.show()
    time.sleep(0.05)

oled.fill_rect(0, 8, 128, 8, 0)       # wipe just the "Counting..." line
oled.text("Done!", 44, 8)
oled.show()

6B. Run it

  1. Click Run.
  2. Save it to the Raspberry Pi Pico as oled_demo.py.
  3. Watch the OLED. You should see three screens in a row:
    1. Text for 3 seconds
    2. Shapes (a border, a diagonal line, a circle and a solid box) for 3 seconds
    3. A progress bar counting from 0% to 100%, then Done!

The OLED on the demo’s text screen, showing Hello Pico, Line 2 text and Temp 23.5 C

Some text in these photos looks cut off. That’s just the camera catching the screen while it redraws (see the note in Part 5C). On the real screen everything shows in full.

The OLED on the demo’s shapes screen, showing a border, a diagonal line, a circle and a solid box

The OLED part-way through the demo, showing Counting…, 32% and a progress bar about a third full

6C. Understand the pattern

Look at the counter loop. Every time round it does three things:

  1. Clear the screen: oled.fill(0)
  2. Draw everything: text(), rect(), fill_rect()
  3. Show it: oled.show()

Clear, draw, show. Use this pattern for anything that changes, like sensor readings or a game.

6D. Challenges

  • Change the demo text to say something about you.
  • Move the circle to the top-left corner.
  • Make the progress bar count down from 100 to 0.
  • Draw a smiley face using ellipse() and pixel().

6E. Optional: run automatically without a computer

A Pico runs a file called main.py by itself as soon as it gets power.

  1. Click File > Save as....
  2. Choose Raspberry Pi Pico.
  3. Name it main.py and click OK.
  4. Unplug the Pico, then plug it into a USB power bank or phone charger. The demo starts by itself.

To stop it running automatically, delete or rename main.py on the Pico using the Files panel.

Check: You’ve seen all three demo screens. Well done!

Troubleshooting

ProblemWhat to try
No RP2350 drive appears (Part 1B)Try a different USB cable; many are charge-only. Hold BOOTSEL the whole time you plug in.
Shell says Device is busy or could not connectClick Stop. If that fails, unplug the Pico, wait 3 seconds, plug it back in, then click Stop.
Pico isn’t in the interpreter listMicroPython isn’t installed yet (redo Part 1B), or the cable is charge-only.
ImportError: no module named 'ssd1306'The library isn’t on the Pico. Redo Part 3.
OSError: [Errno 5] EIO or ENODEVThe Pico can’t talk to the OLED. Check SDA and SCL aren’t swapped, and all 4 wires are firmly in.
Scan shows [61] instead of [60]Change the setup line to oled = ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3D)
Program runs with no error but screen stays blankCheck you have oled.show() after drawing.
Image is upside downAdd oled.rotate(0) after the setup line.
AttributeError on ellipseYour MicroPython is old. Redo Part 1B to update it.
Top of the screen is yellow, the rest blueNormal: some OLEDs have a yellow strip across the top 16 pixels.

Cheat sheet

Every program starts with this setup:

from machine import Pin, I2C
import ssd1306

i2c = I2C(0, sda=Pin(0), scl=Pin(1))
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

In the commands below, the last number 1 means white (pixel on) and 0 means black (pixel off).

CommandWhat it does
oled.show()Puts everything you’ve drawn on the screen
oled.fill(0)Clears the screen to black
oled.fill(1)Turns the whole screen white
oled.text("Hi", x, y)Writes text with its top-left corner at x, y
oled.pixel(x, y, 1)Turns on one pixel
oled.line(x1, y1, x2, y2, 1)Draws a line between two points
oled.hline(x, y, width, 1)Draws a horizontal line
oled.vline(x, y, height, 1)Draws a vertical line
oled.rect(x, y, width, height, 1)Draws a box outline
oled.fill_rect(x, y, width, height, 1)Draws a solid box
oled.ellipse(x, y, r, r, 1)Draws a circle with centre x, y and radius r
oled.ellipse(x, y, r, r, 1, True)Draws a solid circle
oled.contrast(0 to 255)Sets the brightness
oled.invert(1)Swaps black and white (0 swaps back)
oled.poweroff() / oled.poweron()Turns the screen off / on

Handy numbers

  • Screen: 128 x 64 pixels. (0, 0) is top-left.
  • Text lines start at y = 0, 8, 16, 24, 32, 40, 48, 56.
  • To centre text: x = (128 - len(text) * 8) // 2

Get in Contact

  • Brenda Gahan - Head of Learning image

    Brenda Gahan - Head of Learning

    Sunshine Coast Grammar School
    star star star star star

    His wealth of knowledge and expertise allows him to tailor each workshop to the specific skill level and interests of the students (and teachers) and ensures that everyone is engaged and challenged.

  • Digital Technologies Teacher image

    Digital Technologies Teacher

    Victoria Point State High School
    star star star star star

    Students engaged in hands-on learning experiences that developed their problem-solving skills. It was a valuable and highly relevant real world learning opportunity.

  • Tammy Grady image

    Tammy Grady

    Senior Project Officer, QMEA
    star star star star star

    Through the Queensland Minerals and Energy Academy (QMEA), Damien has worked with hundreds of students and Teachers from across the State, all with varying levels of robotic and programming experience. Feedback from Damien’s workshops are always full of praise and appreciation, not only for his professional knowledge, but his adaptable teaching style and ease of delivery.

  • STEM Teacher - Years 7-10 image

    STEM Teacher - Years 7-10

    Tannum Sands State High School
    star star star star star

    Damien's professionalism and communications skills are superb. The very best educational expert visitor we have had come to the college.

  • Primary School Teacher - Year 5 image

    Primary School Teacher - Year 5

    Central Queensland Primary School
    star star star star star

    Damien's delivery style captivated students across every year level, from junior to senior. His depth of knowledge and genuine ability to tailor content to our students' abilities made a real difference, keeping everyone engaged and getting the most out of the experience regardless of their starting point.

  • Head of Curriculum image

    Head of Curriculum

    South East QLD Primary School
    star star star star star

    What stood out most was how thoroughly Damien customised the workshop to our school's specific needs. Rather than a one-size-fits-all session, he took the time to understand what we were after and delivered exactly that.