OLED Drawing Methods, Framebuffer, and Animation
Summary
With the OLED initialized from the previous chapter, you are ready to draw. This chapter covers the full MicroPython drawing API: placing text with oled.text(), painting the screen with oled.fill(), drawing pixels, lines, rectangles, and filled rectangles. You will also learn the MicroPython framebuf module — a fast off-screen buffer that lets you build up a complex image before pushing it to the display all at once, which eliminates flicker. The chapter finishes with three animated projects: a bouncing ball, a two-player Pong game, and a real-time display that shows live sensor readings updating on screen.
Concepts Covered
This chapter covers the following 17 concepts from the learning graph:
- OLED Framebuffer
- oled.text() Method
- oled.fill() Method
- oled.show() Method
- oled.pixel() Method
- oled.line() Method
- oled.rect() Method
- oled.fill_rect() Method
- OLED Bounce Animation
- OLED Pong Game
- OLED Real-Time Sensor Display
- Framebuf Module
- framebuf.FrameBuffer Class
- framebuf.MONO_HLSB Format
- framebuf.RGB565 Format
- Bitmap Drawing
- Custom Drawing Functions
Prerequisites
This chapter builds on concepts from:
Welcome to Chapter 16
A blank OLED and a full drawing toolkit — this is where your projects come alive! Pixels, lines, rectangles, text, and smooth animations all follow from a handful of simple methods. By the end of this chapter you will have a bouncing ball on screen and the concepts to build a complete Pong game. Ready to draw?
The OLED Drawing API
The SSD1306 and SH1106 objects inherit their drawing methods from MicroPython's framebuf.FrameBuffer class. You work on a framebuffer — a region of RAM that mirrors the display pixels. Every drawing method modifies this in-memory buffer; calling oled.show() sends the buffer to the physical display.
All coordinates use the standard screen convention: (x, y) where x increases to the right and y increases downward. The top-left corner is (0, 0). For a 128×64 display, the bottom-right corner is (127, 63).
oled.fill(color)
oled.fill(color) paints every pixel on the screen with the given color:
- 0 = black (pixel off)
- 1 = white (pixel on)
Call oled.fill(0) at the start of each animation frame to erase the previous frame:
1 | |
oled.text(string, x, y, color=1)
oled.text() draws a string starting at position (x, y). Each character is 8×8 pixels. For 128-pixel-wide display, that gives 16 characters per row (128 / 8 = 16).
1 2 3 4 | |
oled.pixel(x, y, color=1)
oled.pixel(x, y) turns a single pixel on or off:
1 2 | |
oled.line(x1, y1, x2, y2, color)
oled.line() draws a straight line from (x1, y1) to (x2, y2):
1 2 | |
oled.rect(x, y, width, height, color)
oled.rect() draws a rectangle outline (no fill):
1 | |
oled.fill_rect(x, y, width, height, color)
oled.fill_rect() draws a filled rectangle:
1 2 | |
Bounce Animation — Putting It Together
Before looking at the code, here is the algorithm for a bouncing ball animation:
- Store ball position
(bx, by)and velocity(vx, vy). - Each frame: clear the screen, draw the ball at
(bx, by), calloled.show(). - Update position:
bx += vx,by += vy. - Reverse velocity when the ball hits a wall:
- If
bxreaches 0 or max_x, flipvx(negate it). - If
byreaches 0 or max_y, flipvy.
Here is the complete program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
The Framebuf Module — Off-Screen Drawing
The framebuf module lets you create independent off-screen buffers. This is useful when you need to pre-render a sprite, icon, or bitmap and then blit (copy) it onto the main display buffer.
A framebuf.FrameBuffer object is a rectangle of pixels stored in a bytearray. You create one by specifying a buffer, width, height, and pixel format.
Pixel formats:
framebuf.MONO_HLSB— monochrome, 1 bit per pixel, horizontal layout, MSB first. This is the format used by SSD1306 OLED displays internally.framebuf.RGB565— color, 16 bits per pixel (5 bits red, 6 bits green, 5 bits blue). Used by color TFT displays.
1 2 3 4 5 6 7 8 9 10 11 12 | |
Bitmap drawing means copying a pre-defined pixel pattern (stored as bytes) into the FrameBuffer using blit(). Icons, sprites, and custom fonts all use this technique.
Custom Drawing Functions
Because the standard API only provides primitive shapes, you will often write custom drawing functions for circles, progress bars, graphs, and charts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
OLED Real-Time Sensor Display
One of the most practical OLED applications is a real-time sensor display that updates the screen every second with live readings. Here is a pattern for updating without flicker:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
Eliminate Display Flicker
Display flicker happens when you call oled.show() multiple times per frame (once after each drawing command). The fix: draw everything into the framebuffer first — fill(0), then all your drawing calls — then call show() exactly once at the end. The entire frame changes at once with zero flicker.
Diagram: OLED Drawing Coordinate System
OLED Drawing Coordinate System MicroSim
Type: diagram
sim-id: oled-coordinate-system
Library: p5.js
Status: Specified
Bloom Level: Remember (L1) Bloom Verb: identify Learning Objective: Students can identify any (x, y) coordinate on a 128×64 OLED display and predict which pixel will be affected by a drawing command.
Canvas layout: - Center: a scaled-up 128×64 grid representing the OLED display - Hover crosshairs show the current (x, y) coordinate - Right panel: the MicroPython command that would draw at the hovered position
Visual elements: - Gray grid lines every 8 pixels (matching the text character grid) - Axis labels: 0 at top-left; 127 at top-right; 63 at bottom-left - Live crosshair follows the mouse with (x,y) shown in a tooltip
Interactive controls: - Click to "draw" a pixel — it stays lit - createButton() "Clear" resets all drawn pixels - Dropdown to select drawing mode: pixel, text, line, rect
Instructional Rationale: A large interactive grid makes the abstract coordinate system concrete. Students can click to place pixels and see the code before writing any hardware programs.
Implementation: p5.js. 128×64 array stores pixel state; mouse position mapped to grid coords; code snippet updates in right panel.
Key Takeaways
- All drawing commands modify the in-memory framebuffer — nothing appears on screen until
oled.show(). - Call
oled.show()exactly once at the end of each frame to eliminate flicker. oled.fill(0)clears the screen;oled.fill(1)lights every pixel.oled.text("str", x, y)draws text with 8×8 pixel characters; 16 chars max per row at 128 width.oled.rect()draws an outline;oled.fill_rect()draws a filled rectangle.- The
framebuf.FrameBufferclass allows off-screen rendering and blit-based sprite drawing. framebuf.MONO_HLSBis the pixel format for monochrome OLED displays.
Quick Check: How many 8×8 text characters fit in one row of a 128-pixel-wide OLED? (Click to reveal)
16 characters — 128 ÷ 8 = 16 columns. At 64 pixels tall, you get 8 text rows (64 ÷ 8 = 8), giving a maximum of 16 × 8 = 128 characters on a full 128×64 display.
Graphics Master!
Text, lines, rectangles, animations, and custom functions — your OLED can now show anything you can imagine. Chapter 17 takes you into the world of color: full-color TFT displays and e-paper screens that hold their image without any power at all. Onward!