OLED Display Graphics
Drawing Shapes on SSD1306
OLED displays, such as the 128x32 pixel SSD1306, use monochrome pixels (black & white) for rendering graphics and text. MicroPython's ssd1306 library provides functions to draw pixels and text.
Understanding the Display Grid (128x32)
- The OLED screen has 128 columns (X-axis) and 32 rows (Y-axis).
- The top-left corner (0,0) is the origin.
- X-axis moves right (0 → 127).
- Y-axis moves down (0 → 31).
Visual Illustration:
Drawing Pixels
The most basic way to draw is by setting individual pixels.
oled.pixel(10, 10, 1) # Set pixel at (10,10) to white (1)
oled.pixel(20, 15, 1) # Set pixel at (20,15) to white (1)
oled.show() # Refresh display
📝 Tip: oled.pixel(x, y, color)
x, y→ Position of the pixelcolor = 1(white) or0(black)
Drawing a Line with Only pixel()
Since we don't have hline() or vline(), we manually turn on pixels.
Horizontal Line
Vertical Line
Diagonal Line
Thicker line
To draw thicker lines, simply repeat drawing at adjacent Y-coordinates:
line_width = 3 # example thickness
for y_offset in range(line_width): # draw 3 lines stacked vertically
for x in range(0, 92):
oled.pixel(x, 10 + y_offset, 1)
oled.show()
- This code draws a horizontal line 3 pixels thick starting at
y=10.
Drawing a Rectangle with pixel()
To draw a rectangle, we manually create four edges using loops.
x0, y0 = 20, 5 # Top-left corner
w, h = 40, 15 # Width & height
# Top & bottom edges
for x in range(x0, x0 + w):
oled.pixel(x, y0, 1) # Top edge
oled.pixel(x, y0 + h - 1, 1) # Bottom edge
# Left & right edges
for y in range(y0, y0 + h):
oled.pixel(x0, y, 1) # Left edge
oled.pixel(x0 + w - 1, y, 1) # Right edge
oled.show()
Filled Rectangle with pixel()
x0, y0 = 20, 5 # Top-left corner
w, h = 40, 15 # Width & height
for x in range(x0, x0 + w):
for y in range(y0, y0 + h):
oled.pixel(x, y, 1) # Fill pixels
oled.show()
Draw custom pixel patterns
Below is an example that draws a wider car—taking advantage of the 128×32 display—using only the pixel() function. In this example, the car is defined as a list of strings where each asterisk (*) represents a pixel that should be turned on. The pattern is 40 characters wide and 7 rows tall, and it is centered on the display.
# Define a wider car pattern (each string is exactly 40 characters long)
car_pattern = [
" **************** ", # 40 characters
" ****** ****** ", # 40 characters
" ****** ****** ", # 40 characters
" ****** ****** ", # 40 characters
" ****************************** ", # 40 characters
" * ******* ", # 40 characters
" * ******* ", # 40 characters
" * ******* ", # 40 characters
" * ******* ", # 40 characters
" ********************************** ", # 40 characters
" ***** ***** ", # 40 characters
" *** *** " # 40 characters
]
# Calculate starting positions to center the pattern on the display
pattern_width = len(car_pattern[0]) # Should be 40
pattern_height = len(car_pattern) # Now 12 rows
start_x = (128 - pattern_width) // 2 # Center horizontally
start_y = (32 - pattern_height) // 2 # Center vertically
# Draw the car pattern using only the pixel() function
for row_index, row in enumerate(car_pattern):
for col_index, char in enumerate(row):
if char == "*": # Turn on pixel wherever there is an asterisk
oled.pixel(start_x + col_index, start_y + row_index, 1)
oled.show() # Refresh the display to show the car
How It Works
-
OLED Initialization:
We clear the display usingoled.fill(0). -
Car Pattern Definition:
The car is represented as a list of 11 strings (rows), each exactly 40 characters wide. In this pattern, every asterisk (*) indicates that the corresponding pixel should be lit. -
Centering the Pattern:
The code calculatesstart_xandstart_yso that the 40×11 pattern is centered on the 128×32 display. -
Drawing the Pattern:
The code loops through each row and each column of the pattern. For each asterisk, the corresponding pixel on the OLED is turned on usingoled.pixel(). -
Display Update:
Finally,oled.show()refreshes the screen so that the drawn car appears.
This approach uses only the pixel() function to draw a recognizable, wider car that takes advantage of the full width of the 128×32 OLED display. Enjoy experimenting with pixel art!
Professional Context: Industrial & Automotive Displays
Your 128×32 OLED is great for learning, but professional embedded systems use displays designed for reliability, readability, and harsh environments. Here's how they compare:
Display Comparison
| Feature | SSD1306 OLED | Industrial HMI | Automotive Cluster | Aerospace/Medical |
|---|---|---|---|---|
| Resolution | 128×32 / 128×64 | 320×240 to 1920×1080 | 1920×720+ | 640×480 to 4K |
| Technology | OLED monochrome | TFT LCD color | TFT/OLED + GPU | LCD with redundancy |
| Brightness | 100-150 cd/m² | 500-1000 cd/m² | 500-1000 cd/m² (sunlight readable) | 200-500 cd/m² |
| Temperature | -20 to +70°C | -30 to +80°C | -40 to +85°C | -55 to +85°C |
| Interface | I2C (slow) | SPI/Parallel/LVDS | LVDS/HDMI | Redundant ARINC |
| Update rate | ~10 fps (I2C limited) | 30-60 fps | 60 fps | 30 fps minimum |
| Lifetime | 10k-50k hours | 50k-100k hours | 50k+ hours | 100k+ hours |
| Sunlight readable | No | Optional | Yes (required) | Yes (often) |
| Touch | No | Resistive/Capacitive | Capacitive | Sometimes |
| Price | ~$3 | $50-500 | $100-1000+ | $500-5000+ |
Why Your OLED Is Slow
I2C at 400 kHz:
128×32 = 4096 pixels = 512 bytes
512 bytes × 8 bits × (1/400kHz) = ~10ms minimum
+ Address, commands, overhead = ~15-20ms per frame
→ Maximum ~50-60 fps theoretical, ~10-30 fps practical
SPI at 10 MHz:
Same data in ~0.4ms
→ Much faster updates possible
Parallel RGB interface:
24 bits/pixel streamed continuously
→ Full video rate, but many pins required
Automotive Instrument Clusters
Modern car dashboards are sophisticated graphics systems:
Your OLED:
CPU: RP2040 (no graphics acceleration)
Memory: 512 bytes framebuffer
Update: pixel() calls in Python loop
Result: Simple shapes, text, ~10 fps
Automotive cluster:
CPU: Dedicated graphics processor (GPU)
Memory: 64-512 MB video RAM
Update: Hardware-accelerated 2D/3D
Result: Smooth gauges, animations, video, 60 fps
Plus:
- Automatic brightness (ambient light sensor)
- Anti-glare coating
- Wide viewing angle
- Redundant backlight
- Functional safety (shows warnings reliably)
Industrial HMI Panels
Factory automation uses rugged touchscreen panels:
| Feature | Your OLED | Industrial HMI |
|---|---|---|
| Enclosure | None (bare PCB) | IP65/IP67 sealed |
| Shock/vibration | Not rated | IEC 60068 tested |
| EMC | None | CE/FCC certified |
| Input | None | Multi-touch, glove-compatible |
| Software | Custom MicroPython | SCADA/PLC integration |
| Diagnostics | None | Built-in self-test |
| Lifespan | ? | 7+ years guaranteed |
Graphics Acceleration
Your Pico draws pixels one at a time. Professional systems use hardware:
# Your approach (software rendering):
for x in range(128):
for y in range(32):
oled.pixel(x, y, calculate_color(x, y))
# Time: ~100ms for full screen
# Hardware-accelerated (conceptual):
gpu.draw_rectangle(0, 0, 128, 32, color)
gpu.draw_text("Speed: 50", font, x, y)
gpu.render() # GPU does all work
# Time: <1ms
What the Industry Uses
| Manufacturer | Product | Application |
|---|---|---|
| Solomon Systech | SSD1306/SSD1351 | Small OLED modules |
| Bosch | Instrument clusters | Automotive dashboards |
| Continental | HMI displays | Vehicle infotainment |
| Siemens | SIMATIC HMI | Industrial automation |
| Garmin | G1000 | Aviation glass cockpit |
| Advantech | Industrial panels | Factory automation |
Hardware Limits Principle
What Software Can and Cannot Fix
Software CAN improve: - Flicker → double buffering (draw to memory, swap) - Slow updates → only redraw changed regions - Memory usage → streaming partial updates - Readability → better fonts, contrast optimization
Software CANNOT fix: - I2C bandwidth limit → need SPI or parallel interface - 128×32 resolution → need different display - Monochrome → need color display hardware - No sunlight readability → need high-brightness + anti-reflective display - Touch input → need touchscreen hardware - Burn-in (OLED aging) → inherent to OLED technology
The lesson: You can optimize your drawing code to update at 30 fps instead of 10 fps. But you cannot software-upgrade a 128×32 monochrome display to show video or work in sunlight—that requires different hardware.
Real Example: Update Speed
| Task | SSD1306 via I2C | Industrial TFT |
|---|---|---|
| Clear screen | ~15ms | <1ms |
| Draw text (10 chars) | ~5ms | <0.1ms |
| Full redraw | ~20ms (50 fps max) | <1ms (1000+ fps possible) |
| Animation | Choppy | Smooth |
| Video playback | Impossible | Easy |
Your robot's OLED showing sensor values at 10 Hz is perfectly fine. A car dashboard showing smooth speedometer needle movement needs GPU hardware.
Further Reading
- I2C Wiring on PicoCar - Bus pins and scan tips
- SSD1306 Datasheet - Display controller details
- LVGL - Open-source embedded graphics library
- Qt for MCUs - Professional embedded GUI framework