Lab 15: Don't Block the Loop
Every earlier animated lab paced itself with sleep(), which freezes the entire program while it waits. This lab swaps sleep() for ticks_ms() so a face can blink on its own timer while the main loop stays free to do anything else — like check a button, which the very next lab adds.
Sample Program Code
The face blinks automatically every four seconds, without ever calling sleep():
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 | # Lab 15: Don't Block the Loop
# Every earlier lab paced itself with sleep(), which freezes the whole
# program while it waits. This lab swaps sleep() for ticks_ms() so the
# face can blink on its own timer while the main loop stays free to do
# other things -- like check a button, which the next lab adds.
#
# There is a second kind of blocking on this display that the OLED kit
# never had to think about: a slow DRAW blocks just as hard as a sleep().
# display.fill(BLACK) takes real milliseconds because it is pushing
# 115,200 bytes, and nothing else in your program runs while it does. So
# the non-blocking pattern below is paired with the small-redraw pattern
# from lab 11. Both are needed; neither is enough alone.
import config
import shapes
from utime import ticks_ms, ticks_diff
display = config.init_display()
WHITE = config.WHITE
BLACK = config.BLACK
NO_FILL = config.NO_FILL
FILL = config.FILL
TOP_HALF = 3
BOTTOM_HALF = 12
HALF_WIDTH = config.WIDTH // 2
EYE_SPACING = 48
LEFT_EYE_X = HALF_WIDTH - EYE_SPACING
RIGHT_EYE_X = HALF_WIDTH + EYE_SPACING
EYE_Y = 100
EYE_RADIUS = 28
PUPIL_RADIUS = 10
BLINK_RADIUS_Y = 14
BLINK_Y = EYE_Y + 7
STROKE = 4
MOUTH_Y = 168
MOUTH_RADIUS_X = 48
MOUTH_RADIUS_Y = 24
EYE_BOX = EYE_RADIUS + 4
BLINK_EVERY_MS = 4000 # how often the face blinks on its own
BLINK_HOLD_MS = 150 # how long the eyes stay shut
def draw_open_eye(x):
shapes.ellipse(display, x, EYE_Y, EYE_RADIUS, EYE_RADIUS, WHITE, FILL)
shapes.ellipse(display, x, EYE_Y, PUPIL_RADIUS, PUPIL_RADIUS, BLACK, FILL)
def draw_closed_eye(x):
for offset in range(STROKE):
shapes.ellipse(display, x, BLINK_Y + offset, EYE_RADIUS,
BLINK_RADIUS_Y, WHITE, NO_FILL, TOP_HALF)
def draw_smile():
for offset in range(STROKE):
shapes.ellipse(display, HALF_WIDTH, MOUTH_Y - offset,
MOUTH_RADIUS_X, MOUTH_RADIUS_Y,
WHITE, NO_FILL, BOTTOM_HALF)
def set_eyes(blinking):
for x in (LEFT_EYE_X, RIGHT_EYE_X):
display.fill_rect(x - EYE_BOX, EYE_Y - EYE_BOX,
EYE_BOX * 2, EYE_BOX * 2, BLACK)
if blinking:
draw_closed_eye(x)
else:
draw_open_eye(x)
display.fill(BLACK)
draw_smile()
set_eyes(False)
blinking = False
last_blink = ticks_ms()
blink_started = 0
while True:
now = ticks_ms()
if not blinking and ticks_diff(now, last_blink) >= BLINK_EVERY_MS:
blinking = True
blink_started = now
set_eyes(True)
if blinking and ticks_diff(now, blink_started) >= BLINK_HOLD_MS:
blinking = False
last_blink = now
set_eyes(False)
# this loop never calls sleep(), so this spot is free for a button
# check, a second animation, or anything else that needs to run often
# Things to try:
#
# 1. Replace set_eyes() with a version that does display.fill(BLACK) and
# redraws the smile too. The loop still never sleeps -- but it is now
# blocked for tens of milliseconds on every blink, which is the same
# problem wearing a different hat. Time it and see.
|
Here's the resting face between blinks:

A Second Kind of Blocking This Kit Has That the OLED Kit Didn't
The non-blocking pattern itself is identical to the OLED kit's — compare ticks_diff() against a remembered timestamp instead of sleeping through the wait. What's new is a second way to block that has nothing to do with sleep() at all: a slow draw blocks a loop exactly as hard as a sleep() call does, because display.fill(BLACK) takes real milliseconds to push 115,200 bytes, and nothing else in the program runs while it does.
So the non-blocking timer pattern here is always paired with the small-redraw pattern from Lab 11. Neither one alone is enough; a loop that never sleeps but still calls face.clear() on every blink is blocked in a different disguise.