Skip to content

Commit c0034b0

Browse files
EyeLights Arduino blink eyes done, I think
1 parent 5b0cf73 commit c0034b0

2 files changed

Lines changed: 86 additions & 103 deletions

File tree

EyeLights_Blinky_Eyes/EyeLights_Blinky_Eyes/EyeLights_Blinky_Eyes.ino

Lines changed: 85 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ but unfortunately the resolution is such that the pupils just look like
1010
circles regardless. I'm keeping it in despite the added complexity,
1111
because this WILL look great later on a bigger matrix or a TFT/OLED,
1212
and this way the hard parts won't require a re-write at such time.
13+
It's a really adorable effect with enough pixels.
1314
*/
1415

1516
#include <Adafruit_IS31FL3741.h> // For LED driver
@@ -18,11 +19,17 @@ and this way the hard parts won't require a re-write at such time.
1819

1920
#define RADIUS 3.4 // Size of pupil (3X because of downsampling later)
2021

22+
uint8_t eye_color[3] = { 255, 128, 0 }; // Amber pupils
23+
uint8_t ring_open_color[3] = { 75, 75, 75 }; // Color of LED rings when eyes open
24+
uint8_t ring_blink_color[3] = { 50, 25, 0 }; // Color of LED ring "eyelid" when blinking
25+
2126
// Some boards have just one I2C interface, but some have more...
2227
TwoWire *i2c = &Wire; // e.g. change this to &Wire1 for QT Py RP2040
2328

24-
Adafruit_EyeLights_buffered glasses(true); // Buffered + 3X canvas
25-
GFXcanvas16 *canvas; // Pointer to canvas object
29+
// GLOBAL VARIABLES ---------------------
30+
31+
Adafruit_EyeLights_buffered glasses(true); // Buffered spex + 3X canvas
32+
GFXcanvas16 *canvas; // Pointer to canvas object
2633

2734
// Reading through the code, you'll see a lot of references to this "3X"
2835
// space. This is referring to the glasses' optional "offscreen" drawing
@@ -31,32 +38,33 @@ GFXcanvas16 *canvas; // Pointer to canvas object
3138
// antialiasing. It's why the pupils have soft edges and can make
3239
// fractional-pixel motions.
3340

34-
uint32_t frames = 0;
35-
uint32_t start_time;
36-
37-
#define GAMMA 2.6
38-
39-
float y_pos[13];
40-
// Initialize eye position and move/blink animation timekeeping
4141
float cur_pos[2] = { 9.0, 7.5 }; // Current position of eye in canvas space
4242
float next_pos[2] = { 9.0, 7.5 }; // Next position "
43-
bool in_motion = false; // true = eyes moving, False = eyes paused
43+
bool in_motion = false; // true = eyes moving, false = eyes paused
4444
uint8_t blink_state = 0; // 0, 1, 2 = unblinking, closing, opening
45-
uint32_t move_start_time = 0;
45+
uint32_t move_start_time = 0; // For animation timekeeping
4646
uint32_t move_duration = 0;
4747
uint32_t blink_start_time = 0;
4848
uint32_t blink_duration = 0;
49+
float y_pos[13]; // Coords of LED ring pixels in canvas space
50+
uint32_t ring_open_color_packed; // ring_open_color[] as packed RGB integer
51+
uint16_t eye_color565; // eye_color[] as a GFX packed '565' value
52+
uint32_t frames = 0; // For frames-per-second calculation
53+
uint32_t start_time;
4954

55+
// These offsets position each pupil on the canvas grid and make them
56+
// fixate slightly (converge on a point) so they're not always aligned
57+
// the same on the pixel grid, which would be conspicuously pixel-y.
5058
float x_offset[2] = { 5.0, 31.0 };
59+
// These help perform x-axis clipping on the rasterized ellipses,
60+
// so they don't "bleed" outside the rings and require erasing.
61+
int box_x_min[2] = { 3, 33 };
62+
int box_x_max[2] = { 21, 51 };
5163

52-
uint16_t eye_color = glasses.color565(255, 128, 0);
53-
//uint8_t eye_color[3] = { 255, 128, 0 }; // Amber pupils
54-
uint8_t ring_open_color[3] = { 75, 75, 75 }; // Color of LED rings when eyes open
55-
uint8_t ring_blink_color[3] = { 50, 25, 0 }; // Color of LED ring "eyelid" when blinking
64+
#define GAMMA 2.6 // For color correction, shouldn't need changing
5665

57-
// Pre-compute color of LED ring in fully open (unblinking) state
58-
uint32_t ring_open_color_packed;
5966

67+
// HELPER FUNCTIONS ---------------------
6068

6169
// Crude error handler, prints message to Serial console, flashes LED
6270
void err(char *str, uint8_t hz) {
@@ -86,37 +94,6 @@ uint32_t interp(uint8_t color1[3], uint8_t color2[3], float blend) {
8694
return gammify(rgb);
8795
}
8896

89-
90-
void setup() { // Runs once at program start...
91-
92-
// Initialize hardware
93-
Serial.begin(115200);
94-
if (! glasses.begin(IS3741_ADDR_DEFAULT, i2c)) err("IS3741 not found", 2);
95-
96-
canvas = glasses.getCanvas();
97-
if (!canvas) err("Can't allocate canvas", 5);
98-
99-
i2c->setClock(1000000);
100-
101-
// Configure glasses for reduced brightness, enable output
102-
glasses.setLEDscaling(0xFF);
103-
glasses.setGlobalCurrent(20);
104-
glasses.enable(true);
105-
106-
// INITIALIZE TABLES & OTHER GLOBALS ----
107-
108-
// Pre-compute the Y position of 1/2 of the LEDs in a ring, relative
109-
// to the 3X canvas resolution, so ring & matrix animation can be aligned.
110-
for (uint8_t i=0; i<13; i++) {
111-
float angle = (float)i / 24.0 * M_PI * 2.0;
112-
y_pos[i] = 10.0 - cos(angle) * 12.0;
113-
}
114-
115-
ring_open_color_packed = gammify(ring_open_color);
116-
117-
start_time = millis();
118-
}
119-
12097
// Rasterize an arbitrary ellipse into the offscreen 3X canvas, given
12198
// foci point1 and point2 and with area determined by global RADIUS
12299
// (when foci are same point; a circle). Foci and radius are all
@@ -154,26 +131,62 @@ void rasterize(float point1[2], float point2[2], int rect[4]) {
154131

155132
// Like I'm sure there's a way to rasterize this by spans rather than
156133
// all these square roots on every pixel, but for now...
157-
for (int y=rect[1]; y<rect[3]; y++) { // For each row...
158-
float y5 = (float)y + 0.5; // Pixel center
159-
float dy1 = y5 - point1[1]; // Y distance from pixel to first point
160-
float dy2 = y5 - point2[1]; // " to second
161-
dy1 *= dy1; // Y1^2
162-
dy2 *= dy2; // Y2^2
163-
for (int x=rect[0]; x<rect[2]; x++) { // For each column...
164-
float x5 = (float)x + 0.5; // Pixel center
165-
float dx1 = x5 - point1[0]; // X distance from pixel to first point
166-
float dx2 = x5 - point2[0]; // " to second
167-
float d1 = sqrt(dx1 * dx1 + dy1); // 2D distance to first point
168-
float d2 = sqrt(dx2 * dx2 + dy2); // " to second
169-
if ((d1 + d2 + d) <= perimeter) {
170-
canvas->drawPixel(x, y, eye_color); // Point is inside ellipse
134+
for (int y=rect[1]; y<rect[3]; y++) { // For each row...
135+
float y5 = (float)y + 0.5; // Pixel center
136+
float dy1 = y5 - point1[1]; // Y distance from pixel to first point
137+
float dy2 = y5 - point2[1]; // " to second
138+
dy1 *= dy1; // Y1^2
139+
dy2 *= dy2; // Y2^2
140+
for (int x=rect[0]; x<rect[2]; x++) { // For each column...
141+
float x5 = (float)x + 0.5; // Pixel center
142+
float dx1 = x5 - point1[0]; // X distance from pixel to first point
143+
float dx2 = x5 - point2[0]; // " to second
144+
float d1 = sqrt(dx1 * dx1 + dy1); // 2D distance to first point
145+
float d2 = sqrt(dx2 * dx2 + dy2); // " to second
146+
if ((d1 + d2 + d) <= perimeter) { // Point inside ellipse?
147+
canvas->drawPixel(x, y, eye_color565);
171148
}
172149
}
173150
}
174151
}
175152

176-
void loop() { // Repeat forever...
153+
154+
// ONE-TIME INITIALIZATION --------------
155+
156+
void setup() {
157+
// Initialize hardware
158+
Serial.begin(115200);
159+
if (! glasses.begin(IS3741_ADDR_DEFAULT, i2c)) err("IS3741 not found", 2);
160+
161+
canvas = glasses.getCanvas();
162+
if (!canvas) err("Can't allocate canvas", 5);
163+
164+
i2c->setClock(1000000); // 1 MHz I2C for extra butteriness
165+
166+
// Configure glasses for reduced brightness, enable output
167+
glasses.setLEDscaling(0xFF);
168+
glasses.setGlobalCurrent(20);
169+
glasses.enable(true);
170+
171+
// INITIALIZE TABLES & OTHER GLOBALS ----
172+
173+
// Pre-compute the Y position of 1/2 of the LEDs in a ring, relative
174+
// to the 3X canvas resolution, so ring & matrix animation can be aligned.
175+
for (uint8_t i=0; i<13; i++) {
176+
float angle = (float)i / 24.0 * M_PI * 2.0;
177+
y_pos[i] = 10.0 - cos(angle) * 12.0;
178+
}
179+
180+
// Convert some colors from [R,G,B] (easier to specify) to packed integers
181+
ring_open_color_packed = gammify(ring_open_color);
182+
eye_color565 = glasses.color565(eye_color[0], eye_color[1], eye_color[2]);
183+
184+
start_time = millis(); // For frames-per-second math
185+
}
186+
187+
// MAIN LOOP ----------------------------
188+
189+
void loop() {
177190
canvas->fillScreen(0);
178191

179192
// The eye animation logic is a carry-over from like a billion
@@ -259,51 +272,30 @@ void loop() { // Repeat forever...
259272
// Draw the raster part of each eye...
260273
for (uint8_t e=0; e<2; e++) {
261274
// Each eye's foci are offset slightly, to fixate toward center
262-
#if 0
263-
float p1a[2] = { p1[0] + x_offset[e], p1[1] };
264-
float p2a[2] = { p2[0] + x_offset[e], p2[1] };
265-
#else
266275
float p1a[2], p2a[2];
267276
p1a[0] = p1[0] + x_offset[e];
268277
p2a[0] = p2[0] + x_offset[e];
269278
p1a[1] = p2a[1] = p1[1];
270-
#endif
271279
// Compute bounding rectangle (in 3X space) of ellipse
272280
// (min X, min Y, max X, max Y). Like the ellipse rasterizer,
273281
// this isn't optimal, but will suffice.
274282
int bounds[4];
275-
bounds[0] = max(int(min(p1a[0], p2a[0]) - RADIUS), 0);
276-
bounds[1] = max(int(min(p1a[1], p2a[1]) - RADIUS), 0);
277-
// bounds[1] = max(bounds[1], (int)upper);
278-
bounds[2] = min(int(max(p1a[0], p2a[0]) + RADIUS + 1), 18);
283+
bounds[0] = max(int(min(p1a[0], p2a[0]) - RADIUS), box_x_min[e]);
284+
bounds[1] = max(max(int(min(p1a[1], p2a[1]) - RADIUS), 0), (int)upper);
285+
bounds[2] = min(int(max(p1a[0], p2a[0]) + RADIUS + 1), box_x_max[e]);
279286
bounds[3] = min(int(max(p1a[1], p2a[1]) + RADIUS + 1), 15);
280-
// bounds[2] = min(bounds[3], (int)lower);
281-
bounds[0] = 0;
282-
bounds[1] = 0;
283-
bounds[2] = 18 * 3;
284-
bounds[3] = 5 * 3;
285-
286-
#if 0
287-
bounds = (
288-
max(int(min(p1a[0], p2a[0]) - radius), 0),
289-
max(int(min(p1a[1], p2a[1]) - radius), 0, int(upper)),
290-
min(int(max(p1a[0], p2a[0]) + radius + 1), 18),
291-
min(int(max(p1a[1], p2a[1]) + radius + 1), 15, int(lower) + 1),
292-
)
293-
#endif
294287
rasterize(p1a, p2a, bounds); // Render ellipse into buffer
295288
}
296289

297-
// If the eye is currently blinking, and if the top edge of the
298-
// eyelid overlaps the bitmap, draw a scanline across the bitmap
299-
// and update the bounds rect so the whole width of the bitmap
300-
// is scaled.
290+
// If the eye is currently blinking, and if the top edge of the eyelid
291+
// overlaps the bitmap, draw lines across the bitmap as if eyelids.
301292
if (blink_state and upper >= 0.0) {
302-
canvas->fillRect(0, 0, canvas->width(), (int)upper + 1, 0x0004);
293+
int iu = (int)upper;
294+
canvas->drawLine(box_x_min[0], iu, box_x_max[0] - 1, iu, eye_color565);
295+
canvas->drawLine(box_x_min[1], iu, box_x_max[1] - 1, iu, eye_color565);
303296
}
304297

305-
306-
glasses.scale();
298+
glasses.scale(); // Smooth filter 3X canvas to LED grid
307299

308300
// Matrix and rings share a few pixels. To make the rings take
309301
// precedence, they're drawn later. So blink state is revisited now...
@@ -332,12 +324,3 @@ bounds[3] = 5 * 3;
332324
elapsed = millis() - start_time;
333325
Serial.println(frames * 1000 / elapsed);
334326
}
335-
336-
337-
#if 0
338-
# Two eye objects. The first starts at column 1 of the matrix with its
339-
# pupil offset by +2 (in 3X space), second at column 11 with -2 offset.
340-
# The offsets make the pupils fixate slightly (converge on a point), so
341-
# the two pupils aren't always aligned the same on the pixel grid, which
342-
# would be conspicuously pixel-y.
343-
#endif // 0

EyeLights_Blinky_Eyes/EyeLights_Blinky_Eyes_CircuitPython/code.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
just look like circles regardless. I'm keeping it in despite the added
1111
complexity, because CircuitPython devices WILL get faster, LED matrix
1212
densities WILL improve, and this way the code won't require a re-write
13-
at such a later time.
13+
at such a later time. It's a really adorable effect with enough pixels.
1414
"""
1515

1616
import math

0 commit comments

Comments
 (0)