📱 Level 5 – App Communication

Project 5.2: "0.96" OLED Graphic Display

What you’ll learn

  • ✅ OLED basics: Show text and simple graphics on the 0.96″ (128×64) OLED.
  • ✅ Visual data: Draw bar charts to display sensor values.
  • ✅ Menus and animations: Create a navigable screen and simple motion.
  • ✅ Live updates: Refresh the screen with real-time readings.

Key ideas

  • Short definition: An OLED is a tiny screen where we draw text and pixels with code.
  • Real-world link: Smartwatches and dashboards use small screens to show live info.

Blocks glossary (used in this project)

  • I2C setup: Tells the board how to talk to the OLED screen.
  • OLED shows string: Prints text on the OLED at a position and size.
  • OLED draw line / rectangle: Draws shapes to visualize data.
  • OLED built-in picture: Displays a pre-made icon like a heart.
  • OLED scroll / frame: Animates text using scrolling or frame-by-frame display.
  • OLED clear display: Wipes the screen before drawing new content.

What you need

Part How many? Pin connection
D1 R32 1 USB cable (30 cm)
0.96″ OLED (128×64) SSD1306 1 I2C: SCL Pin 22, SDA Pin 21, VCC, GND

🔌 Wiring tip: Match SCL→Pin 22 and SDA→Pin 21. If text doesn’t show, check the I2C address (default 0x3C).
📍 Pin map snapshot: Pins 22 and 21 are used for I2C; other pins remain free for sensors.


Before you start

  • USB cable is plugged in
  • Serial monitor is open
  • Test print shows:
print("Ready!")  # Confirm serial is working

Microprojects (5 mini missions)

Microproject 5.2.1 – Text and graphics on large OLED

Goal: Display “Clu-Bots” and a heart icon.
Blocks used:

  • I2C setup: Create i2c_extend.
  • OLED init (SSD1306): Create oled object.
  • OLED shows string: Print “Clu-Bots”.
  • Built-in picture Heart: Show icon.

Block sequence:

  1. Setup I2C with SCL=22, SDA=21, freq=100000.
  2. Create OLED with address 0x3C and driver SSD1306.
  3. Show “Clu-Bots” at x=0, y=0, size=1.
  4. Show Heart at x=0, y=16, size=1.

MicroPython code:

import machine  # Access pins and I2C hardware
import oled128x64  # OLED driver for SSD1306 128x64
from expression_picture import Heart  # Built-in heart icon
i2c_extend = machine.SoftI2C(scl=machine.Pin(22), sda=machine.Pin(21), freq=100000)  # Create I2C on pins 22/21
oled = oled128x64.OLED(i2c_extend, address=0x3c, font_address=0x3A0000, types=0)  # Initialize OLED at I2C address 0x3C
print("Microproject 5.2.1: OLED initialized")  # Serial feedback
oled.clear()  # Clear the screen before drawing
print("Cleared screen")  # Confirm clear
oled.shows('Clu-Bots', x=0, y=0, size=1, space=0, center=False)  # Draw text at top-left
print("Displayed text: 'Clu-Bots' at (0,0)")  # Confirm text position
oled.image(Heart, x=0, y=16, size=1)  # Draw a heart icon below the text
print("Displayed Heart icon at (0,16)")  # Confirm icon position
oled.show()  # Refresh the display to apply drawings
print("OLED show() called")  # Confirm display refresh

Reflection: Drawing starts with a clean screen—then add text and images step by step.
Challenge:

  • Easy: Move the text to x=20, y=0.
  • Harder: Increase size to 2 and center=True.

Microproject 5.2.2 – Bar charts for sensor data (simulated)

Goal: Draw a bar that represents a value (0–100).
Blocks used:

  • OLED rect: Draw a rectangle outline.
  • OLED shows string: Label the chart.
  • Math map: Map 0–100 to pixel width (0–100 px).

Block sequence:

  1. Set value = 65 (simulated sensor).
  2. Map value from [0–100] to [0–100] pixels.
  3. Draw a hollow rectangle as the chart outline.
  4. Draw a filled bar inside using lines.

MicroPython code:

import machine  # Needed for consistency in this file
import oled128x64  # OLED driver
i2c_extend = machine.SoftI2C(scl=machine.Pin(22), sda=machine.Pin(21), freq=100000)  # I2C setup
oled = oled128x64.OLED(i2c_extend, address=0x3c, font_address=0x3A0000, types=0)  # OLED init
value = 65  # Simulated sensor value (0 to 100)
print("Microproject 5.2.2: Sensor value =", value)  # Show chosen value
bar_width = int((value * 100) / 100)  # Map 0–100 to 0–100 pixels
print("Mapped bar width (px) =", bar_width)  # Show computed width
oled.clear()  # Clear screen for a fresh chart
print("Cleared screen for chart")  # Confirm clear
oled.shows('Level:', x=0, y=0, size=1, space=0, center=False)  # Label the chart
print("Displayed label 'Level:'")  # Confirm label
oled.rect(0, 16, 100, 12, 1)  # Draw chart outline at y=16 (width=100, height=12)
print("Drew outline rect at (0,16) size (100x12)")  # Confirm outline
for x in range(1, bar_width):  # Loop to draw a filled bar as vertical lines
    oled.line(x, 17, x, 27, 1)  # Draw a line inside the rectangle
print("Drew filled bar to width:", bar_width)  # Confirm bar fill
oled.show()  # Update the screen
print("OLED show() called for chart")  # Confirm refresh

Reflection: Mapping helps turn numbers into pictures your eyes understand fast.
Challenge:

  • Easy: Try value = 25 and see the shorter bar.
  • Harder: Draw a second bar labeled “Max:” below it.

Microproject 5.2.3 – Navigable menu screens

Goal: Switch between “Home,” “Status,” and “Settings” screens using a variable.
Blocks used:

  • Variable: Set current_screen = “Home”.
  • if / elif: Choose what to draw.
  • OLED clear: Reset before drawing each screen.

Block sequence:

  1. Set current_screen = “Home”.
  2. If Home: big title.
  3. Elif Status: show numbers.
  4. Elif Settings: show options.
  5. Print which screen is active.

MicroPython code:

import machine  # Hardware access
import oled128x64  # OLED driver
i2c_extend = machine.SoftI2C(scl=machine.Pin(22), sda=machine.Pin(21), freq=100000)  # I2C setup
oled = oled128x64.OLED(i2c_extend, address=0x3c, font_address=0x3A0000, types=0)  # OLED init
current_screen = "Home"  # Start on the Home screen
print("Microproject 5.2.3: current_screen =", current_screen)  # Show current screen
oled.clear()  # Clear display before drawing
print("Cleared screen")  # Confirm clear
if current_screen == "Home":  # Decide which screen to draw
    oled.shows('HOME', x=0, y=0, size=2, space=0, center=False)  # Draw Home title
    print("Drew HOME screen")  # Confirm drawing
elif current_screen == "Status":  # Second screen
    oled.shows('STATUS', x=0, y=0, size=2, space=0, center=False)  # Title
    oled.shows('Temp:25C', x=0, y=20, size=1, space=0, center=False)  # Example value
    print("Drew STATUS screen")  # Confirm drawing
elif current_screen == "Settings":  # Third screen
    oled.shows('SETTINGS', x=0, y=0, size=2, space=0, center=False)  # Title
    oled.shows('Mode: Auto', x=0, y=20, size=1, space=0, center=False)  # Option
    print("Drew SETTINGS screen")  # Confirm drawing
oled.show()  # Update display to show chosen screen
print("OLED show() called")  # Confirm update

Reflection: Menus are just choices—draw different screens based on a variable.
Challenge:

  • Easy: Change current_screen to “Status”.
  • Harder: Add a fourth screen: “About”.

Microproject 5.2.4 – Simple animations

Goal: Scroll text and move an icon for a playful effect.
Blocks used:

  • OLED scroll string: Animate text.
  • OLED shift up: Move the image.
  • Delay ms: Slow animation.

Block sequence:

  1. Scroll “Clu-Bots” with size 2.
  2. Draw Heart at bottom.
  3. Shift up in a loop for 10 steps.
  4. Delay between shifts.

MicroPython code:

import machine  # Hardware access
import oled128x64  # OLED driver
from expression_picture import Heart  # Heart icon
import time  # Delay for smooth animation
i2c_extend = machine.SoftI2C(scl=machine.Pin(22), sda=machine.Pin(21), freq=100000)  # I2C setup
oled = oled128x64.OLED(i2c_extend, address=0x3c, font_address=0x3A0000, types=0)  # OLED init
oled.clear()  # Clear screen
print("Microproject 5.2.4: Animation start")  # Status
oled.scroll('Clu-Bots', y=0, size=2, speed=5, space=0)  # Scroll text across the top
print("Scrolling 'Clu-Bots' at size=2")  # Confirm scroll
oled.image(Heart, x=0, y=48, size=1)  # Draw heart near bottom
print("Placed Heart at (0,48)")  # Confirm placement
for step in range(10):  # Repeat movement 10 times
    oled.shift_up(1)  # Move all pixels up by 1
    print("Shift up step:", step)  # Show step count
    time.sleep(0.1)  # Slow down animation
oled.show()  # Final refresh
print("Animation finished")  # Confirm end

Reflection: Small delays make animations smooth—your eyes need time to see motion.
Challenge:

  • Easy: Increase steps to 20.
  • Harder: Move the heart diagonally using line drawing between positions.

Microproject 5.2.5 – Integration with real-time data (simulated)

Goal: Update the screen with changing values every second.
Blocks used:

  • OLED shows string: Display current value.
  • Timer/loop: Refresh regularly.
  • Math: Change the value over time.

Block sequence:

  1. Set value = 0.
  2. In a loop, add 5 each second.
  3. Limit to 100, then wrap to 0.
  4. Show text and a bar.

MicroPython code:

import machine  # Hardware access
import oled128x64  # OLED driver
import time  # Delay to control refresh rate
i2c_extend = machine.SoftI2C(scl=machine.Pin(22), sda=machine.Pin(21), freq=100000)  # I2C setup
oled = oled128x64.OLED(i2c_extend, address=0x3c, font_address=0x3A0000, types=0)  # OLED init
value = 0  # Start at 0
print("Microproject 5.2.5: Live update started at value =", value)  # Status
while True:  # Create a continuous update loop
    oled.clear()  # Clear before drawing new frame
    print("Cleared screen for new frame")  # Confirm clear
    oled.shows('Live:', x=0, y=0, size=1, space=0, center=False)  # Title
    print("Title 'Live:' drawn")  # Confirm title
    oled.shows(str(value), x=40, y=0, size=2, space=0, center=False)  # Show big number
    print("Value drawn:", value)  # Confirm value
    bar_width = int((value * 100) / 100)  # Map 0–100 to 0–100 pixels
    print("Mapped bar width:", bar_width)  # Confirm width
    oled.rect(0, 32, 100, 12, 1)  # Draw outline bar
    print("Outline rect drawn at (0,32)")  # Confirm outline
    for x in range(1, bar_width):  # Fill bar with lines
        oled.line(x, 33, x, 43, 1)  # Vertical line inside bar
    print("Filled bar to width:", bar_width)  # Confirm fill
    oled.show()  # Refresh OLED
    print("Frame shown")  # Confirm refresh
    value = value + 5  # Increase value by 5
    print("Value increased to:", value)  # Confirm update
    if value > 100:  # Wrap around when past 100
        value = 0  # Reset to 0
        print("Value wrapped to 0")  # Confirm wrap
    time.sleep(1)  # Wait 1 second before next update

Reflection: Real-time loops redraw the screen like a flipbook—each frame shows new data.
Challenge:

  • Easy: Change step from +5 to +10.
  • Harder: Replace the simulated value with an actual sensor (e.g., soil moisture).

Main project – 0.96″ OLED Graphic Display

Blocks steps (with glossary)

  • I2C setup: Create i2c_extend for pins 22/21.
  • OLED init: Create oled with address 0x3C, SSD1306 driver.
  • Text + shapes: Use shows, line, rect to build UI.
  • Animation: Use scroll and shift to add motion.
  • Live update: Loop to refresh text and charts.

Block sequence:

  1. Setup I2C (SCL=22, SDA=21, freq=100000).
  2. Init OLED (address 0x3C).
  3. Draw title and icon.
  4. Draw bar chart of a value.
  5. Animate text or icon.
  6. Loop and update in real-time.

MicroPython code (mirroring blocks)

# Project 5.2 – 0.96" OLED Graphic Display
import machine  # Access pins and create I2C
import oled128x64  # SSD1306 OLED driver for 128x64 display
from expression_picture import Heart  # Built-in heart icon for quick graphics
import time  # Control loop timing
i2c_extend = machine.SoftI2C(scl=machine.Pin(22), sda=machine.Pin(21), freq=100000)  # Setup I2C on pins 22/21
print("I2C ready on SCL=22, SDA=21")  # Confirm wiring
oled = oled128x64.OLED(i2c_extend, address=0x3c, font_address=0x3A0000, types=0)  # Initialize OLED (0x3C)
print("OLED initialized (SSD1306 128x64)")  # Confirm screen init
value = 50  # Start value at mid-scale
print("Starting value:", value)  # Show initial value
while True:  # Begin continuous UI loop
    oled.clear()  # Clear the screen for a fresh frame
    print("Screen cleared")  # Confirm clear
    oled.shows('Clu-Bots', x=0, y=0, size=1, space=0, center=False)  # Draw title text
    print("Title drawn at (0,0)")  # Confirm title
    oled.image(Heart, x=100, y=0, size=1)  # Draw heart to decorate the header
    print("Heart icon drawn at (100,0)")  # Confirm icon
    bar_width = int((value * 100) / 100)  # Map 0–100 to 0–100 pixels
    print("Bar width mapped:", bar_width)  # Confirm mapping
    oled.rect(0, 20, 100, 12, 1)  # Draw bar outline
    print("Bar outline at (0,20)")  # Confirm outline
    for x in range(1, bar_width):  # Fill bar with lines
        oled.line(x, 21, x, 31, 1)  # Draw filled area
    print("Bar filled to width:", bar_width)  # Confirm fill
    oled.shows('Value:', x=0, y=36, size=1, space=0, center=False)  # Label value
    print("Label 'Value:' drawn")  # Confirm label
    oled.shows(str(value), x=50, y=36, size=2, space=0, center=False)  # Show numeric value
    print("Numeric value drawn:", value)  # Confirm number
    oled.show()  # Refresh OLED to show frame
    print("Frame refreshed")  # Confirm refresh
    value = value + 5  # Increase value step
    print("Value increased to:", value)  # Confirm update
    if value > 100:  # Wrap at 100
        value = 0  # Reset value
        print("Value wrapped to 0")  # Confirm wrap
    time.sleep(1)  # Delay for visible update

External explanation

  • What it teaches: You built a live dashboard: titles, icons, bars, and numbers that refresh every second.
  • Why it works: I2C connects the OLED; each frame clears the screen and redraws text and shapes; a loop updates the value and the bar based on simple math.
  • Key concept: “Clear, draw, show—repeat.”

Story time

Your robot now has a face. The OLED is a tiny window that shows its mood, stats, and messages—like a smartwatch on wheels.


Debugging (2)

Debugging 5.2.A – Screen does not update

Problem: Nothing changes after you draw.
Clues: You see no text; prints say “drawn,” but the OLED stays blank.
Broken code:

oled.shows('Text', x=0, y=0, size=1, space=0, center=False)  # Draws text
# oled.show()  # Forgot to refresh the screen

Fixed code:

oled.shows('Text', x=0, y=0, size=1, space=0, center=False)  # Draw text
oled.show()  # Refresh to make drawings visible
print("Called show() to update display")  # Explain the fix

Why it works: The OLED needs show() to push drawings from memory onto the screen.
Avoid next time: After drawing, always call show().

Debugging 5.2.B – Corrupted graphics

Problem: Text overlaps or shapes smear.
Clues: Old pixels remain or bars stack up.
Broken code:

# oled.clear()  # Missing clear before drawing new frame
oled.rect(0, 20, 100, 12, 1)  # Draws over previous frame

Fixed code:

oled.clear()  # Wipe previous frame
print("Cleared before drawing new frame")  # Explain the step
oled.rect(0, 20, 100, 12, 1)  # Draw fresh outline

Why it works: Clearing removes old pixels so new frames don’t overlap.
Avoid next time: Clear once per frame when doing animations or live updates.


Final checklist

  • I saw “Clu-Bots” and a heart icon
  • The bar chart changed with the value
  • The screen updated once per second

Extras

  • 🧠 Student tip: Build a “battery” bar (label it BAT) and wrap at 100%.
  • 🧑‍🏫 Instructor tip: Have pairs describe their UI out loud—title, value, bar—before coding.
  • 📖 Glossary:
    • I2C: A two-wire way to talk to sensors and displays.
    • Frame: One full screen drawing before show().
    • Map: Turning a value range into a pixel range.
  • 💡 Mini tips:
    • If nothing shows, check the address 0x3C.
    • Keep y positions spaced (0, 16, 32) to avoid overlap.
    • Use small size for text (1–2) so it fits on 128×64.

Hard stop rule respected: All sections included, and every code line has a comment or a clear, explanatory print message.

On this page