Skip to content

Commit fefa16b

Browse files
committed
add Qualia board qteye_blink_qualia test
1 parent 99d0979 commit fefa16b

File tree

2 files changed

+210
-13
lines changed

2 files changed

+210
-13
lines changed

larger-tricks/eyeballs/qteye_blink_esp32s3.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
import gc9a01
1111

1212
# config: behaviors
13-
eye_twitch_time = 2 # bigger is less twitchy
13+
eye_twitch_time = 2 # bigger is less twitchy
1414
eye_twitch_amount = 20 # allowable deviation from center for iris
15-
eye_blink_time = 1.2 # bigger is slower
15+
eye_blink_time = 0.8 # bigger is slower
1616
eye_rotation = 0 # 0 or 180, don't do 90 or 270 because too slow
1717

1818
# config: wiring for QT Py, should work on any QT Py or XIAO board, but ESP32-S3 is fastest
@@ -28,10 +28,10 @@
2828
dw, dh = 240, 240 # display dimensions
2929

3030
# load our eye and iris bitmaps
31-
## static so load from disk
31+
## static so load from disk (also can't have it it RAM and eyelids in RAM too)
3232
eyeball_bitmap = displayio.OnDiskBitmap(open("/imgs/eye0_ball2.bmp", "rb"))
3333
eyeball_pal = eyeball_bitmap.pixel_shader
34-
## moves aorund, so load into RAM
34+
## moves around, so load into RAM
3535
iris_bitmap, iris_pal = adafruit_imageload.load("imgs/eye0_iris0.bmp")
3636
iris_pal.make_transparent(0)
3737
## also moves, so load into RAM (hopefully)
@@ -53,8 +53,16 @@
5353

5454
# class to help us track eye info (not needed for this use exactly, but I find it interesting)
5555
class Eye:
56+
"""
57+
global variables used:
58+
- iris_cx, iris_cy
59+
- eye_twitch_amount
60+
- eye_twitch_time
61+
- eye_blink_time
62+
"""
5663
def __init__(self, spi, dc, cs, rst, rot=0, eye_speed=0.25):
57-
display_bus = displayio.FourWire(spi, command=dc, chip_select=cs, reset=rst)
64+
# ends up being 80 MHz on ESP32-S3, nice
65+
display_bus = displayio.FourWire(spi, command=dc, chip_select=cs, reset=rst, baudrate=64_000_000)
5866
display = gc9a01.GC9A01(display_bus, width=dw, height=dh, rotation=rot)
5967
display.auto_refresh = False
6068
main = displayio.Group()
@@ -75,18 +83,11 @@ def __init__(self, spi, dc, cs, rst, rot=0, eye_speed=0.25):
7583
self.lid_next_time = 0
7684

7785
def update(self):
78-
"""
79-
global variables used:
80-
- eye_twitch_amount
81-
- eye_twitch_time
82-
- eye_blink_time
83-
-
84-
"""
8586
# make the eye twitch around
8687
self.x = self.x * (1-self.eye_speed) + self.tx * self.eye_speed # "easing"
8788
self.y = self.y * (1-self.eye_speed) + self.ty * self.eye_speed
8889
self.iris.x = int( self.x )
89-
self.iris.y = int( self.y ) + 10 # have it look down a bit FIXME
90+
self.iris.y = int( self.y ) # + 10 # have it look down a bit FIXME
9091
if time.monotonic() > self.next_time:
9192
# pick a new "target" for the eye to look at
9293
t = random.uniform(0.25, eye_twitch_time)
@@ -97,6 +98,7 @@ def update(self):
9798
elif time.monotonic() > self.lid_next_time:
9899
# make the eye blink its eyelids
99100
self.lid_next_time = time.monotonic() + random.uniform( eye_blink_time*0.5, eye_blink_time*1.5)
101+
#self.lid_next_time = time.monotonic() + 100
100102
self.lidpos = self.lidpos + self.lidpos_inc
101103
self.lids[0] = self.lidpos
102104
if self.lidpos == 0 or self.lidpos == eyelid_sprite_cnt-1:
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# qteye_blink_qualia.py - a stand-alone round LCD "eye" on a ESP32-S3 Qualia board
2+
# 16 Oct 2023 - @todbot / Tod Kurt
3+
# Part of circuitpython-tricks/larger-tricks/eyeballs
4+
# also see: https://todbot.com/blog/2022/05/19/multiple-displays-in-circuitpython-compiling-custom-circuitpython/
5+
6+
import time, math, random
7+
import board, busio
8+
import displayio
9+
import adafruit_imageload
10+
11+
# config: behaviors
12+
eye_twitch_time = 2 # bigger is less twitchy
13+
eye_twitch_amount = 20 # allowable deviation from center for iris
14+
eye_blink_time = 1.8 # bigger is slower
15+
eye_rotation = 00 # 0 or 180, don't do 90 or 270 because too slow (on gc9a01)
16+
17+
18+
# config: wiring for QT Py, should work on any QT Py or XIAO board, but ESP32-S3 is fastest
19+
# import gc9a01
20+
# spi0 = busio.SPI(clock=tft0_clk, MOSI=tft0_mosi)
21+
# tft0_clk = board.SCK
22+
# tft0_mosi = board.MOSI
23+
# tft_L0_rst = board.MISO
24+
# tft_L0_dc = board.RX
25+
# tft_L0_cs = board.TX
26+
# display_bus = displayio.FourWire(spi, command=dc, chip_select=cs, reset=rst, baudrate=64_000_000)
27+
# display = gc9a01.GC9A01(display_bus, width=dw, height=dh, rotation=rot)
28+
# display.auto_refresh = False
29+
# dw, dh = 240, 240 # display dimensions
30+
31+
# config: for Qualia ESP32S3 board and 480x480 round display
32+
# followed the instructions here:
33+
# https://learn.adafruit.com/adafruit-qualia-esp32-s3-for-rgb666-displays/circuitpython-display-setup
34+
# and cribbed from @dexter/@rsbohn's work:
35+
# https://gist.github.com/rsbohn/26a8e69c8fe80112a24e7de09177e8d9
36+
import dotclockframebuffer
37+
from framebufferio import FramebufferDisplay
38+
39+
init_sequence_TL021WVC02 = bytes((
40+
b'\xff\x05w\x01\x00\x00\x10'
41+
b'\xc0\x02;\x00'
42+
b'\xc1\x02\x0b\x02'
43+
b'\xc2\x02\x00\x02'
44+
b'\xcc\x01\x10'
45+
b'\xcd\x01\x08'
46+
b'\xb0\x10\x02\x13\x1b\r\x10\x05\x08\x07\x07$\x04\x11\x0e,3\x1d'
47+
b'\xb1\x10\x05\x13\x1b\r\x11\x05\x08\x07\x07$\x04\x11\x0e,3\x1d'
48+
b'\xff\x05w\x01\x00\x00\x11'
49+
b'\xb0\x01]'
50+
b'\xb1\x01C'
51+
b'\xb2\x01\x81'
52+
b'\xb3\x01\x80'
53+
b'\xb5\x01C'
54+
b'\xb7\x01\x85'
55+
b'\xb8\x01 '
56+
b'\xc1\x01x'
57+
b'\xc2\x01x'
58+
b'\xd0\x01\x88'
59+
b'\xe0\x03\x00\x00\x02'
60+
b'\xe1\x0b\x03\xa0\x00\x00\x04\xa0\x00\x00\x00 '
61+
b'\xe2\r\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
62+
b'\xe3\x04\x00\x00\x11\x00'
63+
b'\xe4\x02"\x00'
64+
b'\xe5\x10\x05\xec\xa0\xa0\x07\xee\xa0\xa0\x00\x00\x00\x00\x00\x00\x00\x00'
65+
b'\xe6\x04\x00\x00\x11\x00'
66+
b'\xe7\x02"\x00'
67+
b'\xe8\x10\x06\xed\xa0\xa0\x08\xef\xa0\xa0\x00\x00\x00\x00\x00\x00\x00\x00'
68+
b'\xeb\x07\x00\x00@@\x00\x00\x00'
69+
b'\xed\x10\xff\xff\xff\xba\n\xbfE\xff\xffT\xfb\xa0\xab\xff\xff\xff'
70+
b'\xef\x06\x10\r\x04\x08?\x1f'
71+
b'\xff\x05w\x01\x00\x00\x13'
72+
b'\xef\x01\x08'
73+
b'\xff\x05w\x01\x00\x00\x00'
74+
b'6\x01\x00'
75+
b':\x01`'
76+
b'\x11\x80d'
77+
b')\x802'
78+
))
79+
80+
# I'm just guessing at all of this
81+
tft_timings = {
82+
"frequency": 6_500_000,
83+
"width": 480,
84+
"height": 480,
85+
"hsync_pulse_width": 20,
86+
"hsync_front_porch": 40,
87+
"hsync_back_porch": 40,
88+
"vsync_pulse_width": 10,
89+
"vsync_front_porch": 40,
90+
"vsync_back_porch": 40,
91+
"hsync_idle_low": False,
92+
"vsync_idle_low": False,
93+
"de_idle_high": False,
94+
"pclk_active_high": False,
95+
"pclk_idle_high": False,
96+
}
97+
98+
displayio.release_displays()
99+
board.I2C().deinit()
100+
i2c = busio.I2C(board.SCL, board.SDA, frequency=400_000)
101+
dotclockframebuffer.ioexpander_send_init_sequence(
102+
i2c, init_sequence_TL021WVC02, **(dict(board.TFT_IO_EXPANDER)))
103+
i2c.deinit()
104+
105+
fb = dotclockframebuffer.DotClockFramebuffer(
106+
**(dict(board.TFT_PINS)), **tft_timings)
107+
display = FramebufferDisplay(fb, rotation=eye_rotation)
108+
display.auto_refresh=False
109+
110+
dw_real, dh_real = 480, 480
111+
dw, dh = 240,240 # for emulation with 240x240 displays
112+
113+
# load our eye and iris bitmaps
114+
## static so load from disk (also can't have it it RAM and eyelids in RAM too)
115+
eyeball_bitmap = displayio.OnDiskBitmap(open("/imgs/eye0_ball2.bmp", "rb"))
116+
eyeball_pal = eyeball_bitmap.pixel_shader
117+
## moves around, so load into RAM
118+
iris_bitmap, iris_pal = adafruit_imageload.load("imgs/eye0_iris0.bmp")
119+
iris_pal.make_transparent(0)
120+
## also moves, so load into RAM (hopefully)
121+
try:
122+
eyelid_bitmap, eyelid_pal = adafruit_imageload.load("/imgs/eyelid_spritesheet2.bmp")
123+
except Exception as e:
124+
print("couldn't load",e)
125+
eyelid_bitmap = displayio.OnDiskBitmap(open("/imgs/eyelid_spritesheet2.bmp", "rb"))
126+
eyelid_pal = eyelid_bitmap.pixel_shader
127+
eyelid_sprite_cnt = eyelid_bitmap.width // dw # should be 16
128+
eyelid_pal.make_transparent(1)
129+
130+
# compute or declare some useful info about the eyes
131+
iris_w, iris_h = iris_bitmap.width, iris_bitmap.height # iris is normally 110x110
132+
iris_cx, iris_cy = dw//2 - iris_w//2, dh//2 - iris_h//2
133+
134+
135+
# class to help us track eye info (not needed for this use exactly, but I find it interesting)
136+
class Eye:
137+
"""
138+
global variables used:
139+
- iris_cx, iris_cy
140+
- eye_twitch_amount
141+
- eye_twitch_time
142+
- eye_blink_time
143+
"""
144+
def __init__(self, display, eye_speed=0.25):
145+
# ends up being 80 MHz on ESP32-S3, nice
146+
main = displayio.Group(scale=2)
147+
display.root_group = main
148+
self.display = display
149+
self.eyeball = displayio.TileGrid(eyeball_bitmap, pixel_shader=eyeball_pal)
150+
self.iris = displayio.TileGrid(iris_bitmap, pixel_shader=iris_pal, x=iris_cx,y=iris_cy)
151+
self.lids = displayio.TileGrid(eyelid_bitmap, pixel_shader=eyelid_pal, x=0, y=0, tile_width=dw, tile_height=dw)
152+
main.append(self.eyeball)
153+
main.append(self.iris)
154+
main.append(self.lids)
155+
self.x, self.y = iris_cx, iris_cy
156+
self.tx, self.ty = self.x, self.y
157+
self.next_time = 0
158+
self.eye_speed = eye_speed
159+
self.lidpos = 0
160+
self.lidpos_inc = 1
161+
self.lid_next_time = 0
162+
163+
def update(self):
164+
# make the eye twitch around
165+
self.x = self.x * (1-self.eye_speed) + self.tx * self.eye_speed # "easing"
166+
self.y = self.y * (1-self.eye_speed) + self.ty * self.eye_speed
167+
self.iris.x = int( self.x )
168+
self.iris.y = int( self.y ) # + 10 # have it look down a bit FIXME
169+
if time.monotonic() > self.next_time:
170+
# pick a new "target" for the eye to look at
171+
t = random.uniform(0.25, eye_twitch_time)
172+
self.next_time = time.monotonic() + t
173+
self.tx = iris_cx + random.uniform(-eye_twitch_amount,eye_twitch_amount)
174+
self.ty = iris_cy + random.uniform(-eye_twitch_amount,eye_twitch_amount)
175+
# elif to minimize display changes per update
176+
elif time.monotonic() > self.lid_next_time:
177+
# make the eye blink its eyelids
178+
self.lid_next_time = time.monotonic() + random.uniform( eye_blink_time*0.5, eye_blink_time*1.5)
179+
self.lidpos = self.lidpos + self.lidpos_inc
180+
self.lids[0] = self.lidpos
181+
if self.lidpos == 0 or self.lidpos == eyelid_sprite_cnt-1:
182+
self.lidpos_inc *= -1 # change direction
183+
184+
self.display.refresh()
185+
186+
187+
188+
# a list of all the eyes, in this case, only one
189+
the_eyes = [
190+
Eye( display ),
191+
]
192+
193+
while True:
194+
for eye in the_eyes:
195+
eye.update()

0 commit comments

Comments
 (0)