Eye Scanner
A pupil that sweeps slowly back and forth is one of the cheapest, most convincing signals a robot
can send: it looks like the machine is thinking. This lab builds it by looping an x offset
and redrawing the pupils on every step.
It is also the lab where the color display changes the rules, and the change is big enough that the rest of the kit is built around it.
Watch me look around
Moving eyes are the first thing that makes a screen feel like a face instead of a picture of one. Let's make some pixels move.
Why This Lab Can't Wipe the Screen
On the OLED, every frame started with oled.fill(BLACK) and nobody noticed, because the wipe
happened in RAM and the screen only ever saw the finished picture.
Here there is no RAM copy. A full wipe is 115,200 bytes going down the wire, and you watch it happen. Do that on every frame and the animation flickers hard and crawls.
So this lab erases only the two eye boxes instead:
| Approach | Pixels repainted per frame | Result |
|---|---|---|
| Wipe the whole screen | 57,600 | Visible flicker, low frame rate |
| Erase two eye boxes | 10,304 | Smooth, no flicker, identical picture |
Under a fifth of the work for the same result. On this hardware that is not an optimization you save for later — it is the price of admission, which is why it arrives at lab 11 instead of lab 29.
Sample Program Code
Notice the split: draw_static_parts() runs once, and draw_eyes() runs on every frame and
touches nothing but the eyes.
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | |
Here is one frame of the animation, with both pupils sitting near the center of their sweep:

The mouth in that picture was drawn exactly once, before the loop started. Every frame after that touched only the two rectangles around the eyes.
Make the Invisible Visible
Here is the best trick in this kit. Erasing paints black onto black, so the single most important thing your program does is completely invisible.
Change one line:
1 | |
Now every box your program repaints lights up as a red rectangle with the eyes drawn on top of it, and everything the program leaves alone stays black. You can see your optimization working.
Color Is Free Here
A red pixel and a black pixel are both two bytes of RGB565. Making my program explain itself costs exactly nothing — and a bug you can see beats a bug you can only reason about.
Things to Try
- Break it the OLED way. Replace
draw_eyes()with a version that callsdisplay.fill(OFF)and redraws everything. Run it. The flicker and the frame rate together are the answer to "why does this kit care about partial redraw so early?" - Shrink the erase box to just the pupil's travel and see whether it still looks right. It will not, quite — and finding out why is the point.
- Change
EYE_WIDTHto 50 without touchingEYE_BOX_Xand watch leftovers pile up at the edges. An erase box has to be sized for the largest thing that can appear in it. - Do exercise 3 again with
ERASE_COLOR = config.RED. Now the leftover pixels are visibly outside a rectangle you can see the edges of. That is debugging made easy, for free.
References
- Drawing Rectangles — where
fill_rect()as an eraser was introduced - Only Redraw What Changed — the same idea, measured in microseconds
- Five Broken Faces — where a missing erase becomes a bug to diagnose