Skip to content

Commit 7f74afa

Browse files
committed
Merge remote-tracking branch 'adafruit/master'
2 parents c93c2ae + f1da865 commit 7f74afa

10 files changed

Lines changed: 590 additions & 136 deletions

File tree

M4_Eyes/M4_Eyes.ino

Lines changed: 147 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
#define GLOBAL_VAR
3838
#include "globals.h"
39+
extern Adafruit_ImageReader reader;
3940

4041
// Global eye state that applies to all eyes (not per-eye):
4142
bool eyeInMotion = false;
@@ -56,7 +57,8 @@ uint32_t lastLightReadTime = 0;
5657
float lastLightValue = 0.5;
5758
double irisValue = 0.5;
5859
int iPupilFactor = 42;
59-
uint32_t boopSum = 0;
60+
uint32_t boopSum = 0,
61+
boopSumFiltered = 0;
6062
bool booped = false;
6163
int fixate = 7;
6264
uint8_t lightSensorFailCount = 0;
@@ -106,8 +108,17 @@ SPISettings settings(DISPLAY_FREQ, MSBFIRST, SPI_MODE0);
106108
// the affected DMA channel (DMAbuddy::fix()).
107109
#define DMA_TIMEOUT ((240 * 16 * 4000) / (DISPLAY_FREQ / 1000))
108110

111+
static inline uint16_t readBoop(void) {
112+
uint16_t counter = 0;
113+
pinMode(boopPin, OUTPUT);
114+
digitalWrite(boopPin, HIGH);
115+
pinMode(boopPin, INPUT);
116+
while(digitalRead(boopPin) && (++counter < 1000));
117+
return counter;
118+
}
119+
109120
// Crude error handler. Prints message to Serial Monitor, blinks LED.
110-
void fatal(char *message, uint16_t blinkDelay) {
121+
void fatal(const char *message, uint16_t blinkDelay) {
111122
Serial.println(message);
112123
for(bool ledState = HIGH;; ledState = !ledState) {
113124
digitalWrite(LED_BUILTIN, ledState);
@@ -125,7 +136,7 @@ void setup() {
125136
int i = file_setup();
126137

127138
Serial.begin(9600);
128-
// while(!Serial);
139+
//while(!Serial) delay(10);
129140

130141
Serial.printf("Available RAM at start: %d\n", availableRAM());
131142
Serial.printf("Available flash at start: %d\n", availableNVM());
@@ -152,11 +163,43 @@ void setup() {
152163
#endif
153164

154165
uint8_t e;
166+
// Initialize displays
155167
for(e=0; e<NUM_EYES; e++) {
156168
eye[e].display = new Adafruit_ST7789(eye[e].spi, eye[e].cs, eye[e].dc, eye[e].rst);
157169
eye[e].display->init(240, 240);
158-
eye[e].display->setRotation(3);
159170
eye[e].spi->setClockSource(DISPLAY_CLKSRC);
171+
eye[e].display->fillScreen(0x1234);
172+
eye[e].display->setRotation(0);
173+
}
174+
175+
if (reader.drawBMP("/splash.bmp", *(eye[0].display), 0, 0) == IMAGE_SUCCESS) {
176+
Serial.println("Splashing");
177+
#if NUM_EYES > 1
178+
// other eye
179+
reader.drawBMP("/splash.bmp", *(eye[1].display), 0, 0);
180+
#endif
181+
// backlight on for a bit
182+
for (int bl=0; bl<=250; bl+=10) {
183+
#if NUM_EYES > 1
184+
seesaw.analogWrite(SEESAW_BACKLIGHT_PIN, bl);
185+
#endif
186+
analogWrite(BACKLIGHT_PIN, bl);
187+
delay(10);
188+
}
189+
delay(2000);
190+
// backlight back off
191+
for (int bl=250; bl>=0; bl-=10) {
192+
#if NUM_EYES > 1
193+
seesaw.analogWrite(SEESAW_BACKLIGHT_PIN, bl);
194+
#endif
195+
analogWrite(BACKLIGHT_PIN, bl);
196+
delay(10);
197+
}
198+
}
199+
200+
// Initialize DMAs
201+
for(e=0; e<NUM_EYES; e++) {
202+
eye[e].display->setRotation(3);
160203
eye[e].display->fillScreen(0);
161204
eye[e].dma.allocate();
162205
eye[e].dma.setTrigger(eye[e].spi->getDMAC_ID_TX());
@@ -330,6 +373,14 @@ void setup() {
330373
calcDisplacement();
331374
Serial.printf("Free RAM: %d\n", availableRAM());
332375

376+
if(boopPin >= 0) {
377+
boopThreshold = 0;
378+
for(i=0; i<240; i++) {
379+
boopThreshold += readBoop();
380+
}
381+
boopThreshold = boopThreshold * 110 / 100; // 10% overhead
382+
}
383+
333384
randomSeed(SysTick->VAL + analogRead(A2));
334385
eyeOldX = eyeNewX = eyeOldY = eyeNewY = mapRadius; // Start in center
335386
for(e=0; e<NUM_EYES; e++) { // For each eye...
@@ -339,6 +390,16 @@ void setup() {
339390
lastLightReadTime = micros() + 2000000; // Delay initial light reading
340391
}
341392

393+
static inline uint16_t readLightSensor(void) {
394+
#if NUM_EYES > 1
395+
if(lightSensorPin >= 100) {
396+
return seesaw.analogRead(lightSensorPin - 100);
397+
}
398+
#else
399+
return analogRead(lightSensorPin);
400+
#endif
401+
}
402+
342403
// LOOP FUNCTION - CALLED REPEATEDLY UNTIL POWER-OFF -----------------------
343404

344405
/*
@@ -376,15 +437,15 @@ an independent frame rate depending on particular complexity at the moment).
376437
void loop() {
377438
if(++eyeNum >= NUM_EYES) eyeNum = 0; // Cycle through eyes...
378439

379-
uint8_t x = eye[eyeNum].colNum;
440+
uint8_t x = eye[eyeNum].colNum;
441+
uint32_t t = micros();
380442

381443
// If next column for this eye is not yet rendered...
382444
if(!eye[eyeNum].column_ready) {
383445
if(!x) { // If it's the first column...
384446

385447
// ONCE-PER-FRAME EYE ANIMATION LOGIC HAPPENS HERE -------------------
386448

387-
uint32_t t = micros();
388449
float eyeX, eyeY;
389450
// Eye movement
390451
int32_t dt = t - eyeMoveStartTime; // uS elapsed since last eye event
@@ -419,66 +480,19 @@ void loop() {
419480
}
420481

421482
// Eyes fixate (are slightly crossed) -- amount is filtered for boops
422-
int nufix = booped ? 150 : 7;
483+
int nufix = booped ? 90 : 7;
423484
fixate = ((fixate * 15) + nufix) / 16;
424485
// save eye position to this eye's struct so it's same throughout render
425486
if(eyeNum & 1) eyeX += fixate; // Eyes converge slightly toward center
426487
else eyeX -= fixate;
427488
eye[eyeNum].eyeX = eyeX;
428489
eye[eyeNum].eyeY = eyeY;
429490

430-
// Handle pupil scaling
431-
if(lightSensorPin >= 0) {
432-
// Read light sensor, but not too often (Seesaw hates that)
433-
#define LIGHT_INTERVAL (1000000 / 8) // 8 Hz, don't poll Seesaw too often
434-
if((t - lastLightReadTime) >= LIGHT_INTERVAL) {
435-
// Fun fact: eyes have a "consensual response" to light -- both
436-
// pupils will react even if the opposite eye is stimulated.
437-
// Meaning we can get away with using a single light sensor for
438-
// both eyes. This comment has nothing to do with the code.
439-
uint16_t rawReading = (lightSensorPin >= 100) ?
440-
seesaw.analogRead(lightSensorPin - 100) : analogRead(lightSensorPin);
441-
if(rawReading <= 1023) {
442-
if(rawReading < lightSensorMin) rawReading = lightSensorMin; // Clamp light sensor range
443-
else if(rawReading > lightSensorMax) rawReading = lightSensorMax; // to within usable range
444-
float v = (float)(rawReading - lightSensorMin) / (float)(lightSensorMax - lightSensorMin); // 0.0 to 1.0
445-
v = pow(v, lightSensorCurve);
446-
lastLightValue = irisMin + v * irisRange;
447-
lastLightReadTime = t;
448-
lightSensorFailCount = 0;
449-
} else { // I2C error
450-
if(++lightSensorFailCount >= 50) { // If repeated errors in succession...
451-
lightSensorPin = -1; // Stop trying to use the light sensor
452-
} else {
453-
lastLightReadTime = t - LIGHT_INTERVAL + 40000; // Try again in 40 ms
454-
}
455-
}
456-
}
457-
irisValue = (irisValue * 0.97) + (lastLightValue * 0.03); // Filter response for smooth reaction
458-
} else {
459-
// Not light responsive. Use autonomous iris w/fractal subdivision
460-
float n, sum = 0.5;
461-
for(uint16_t i=0; i<IRIS_LEVELS; i++) { // 0,1,2,3,...
462-
uint16_t iexp = 1 << (i+1); // 2,4,8,16,...
463-
uint16_t imask = (iexp - 1); // 2^i-1 (1,3,7,15,...)
464-
uint16_t ibits = iris_frame & imask; // 0 to mask
465-
if(ibits) {
466-
float weight = (float)ibits / (float)iexp; // 0.0 to <1.0
467-
n = iris_prev[i] * (1.0 - weight) + iris_next[i] * weight;
468-
} else {
469-
n = iris_next[i];
470-
iris_prev[i] = iris_next[i];
471-
iris_next[i] = -0.5 + ((float)random(1000) / 999.0); // -0.5 to +0.5
472-
}
473-
iexp = 1 << (IRIS_LEVELS - i); // ...8,4,2,1
474-
sum += n / (float)iexp;
475-
}
476-
irisValue = irisMin + (sum * irisRange); // 0.0-1.0 -> iris min/max
477-
if((++iris_frame) >= (1 << IRIS_LEVELS)) iris_frame = 0;
478-
}
479-
480491
// pupilFactor? irisValue? TO DO: pick a name and stick with it
481492
eye[eyeNum].pupilFactor = irisValue;
493+
// Also note - irisValue is calculated at the END of this function
494+
// for the next frame (because the sensor must be read when there's
495+
// no SPI traffic to the left eye)
482496

483497
// Similar to the autonomous eye movement above -- blink start times
484498
// and durations are random (within ranges).
@@ -500,37 +514,39 @@ void loop() {
500514
int ix = (int)map2screen(mapRadius - eye[eyeNum].eyeX) + 120, // Pupil position
501515
iy = (int)map2screen(mapRadius - eye[eyeNum].eyeY) + 120; // on screen
502516
iy += irisRadius / 2; // top edge of iris (ish) in screen pixels
503-
float qqq; // So many sloppy temp vars in here for now, sorry
517+
float uq, lq; // So many sloppy temp vars in here for now, sorry
504518
if(eyeNum & 1) ix = 239 - ix; // Flip for right eye
505519
if(iy > upperOpen[ix]) {
506-
qqq = 1.0;
520+
uq = 1.0;
507521
} else if(iy < upperClosed[ix]) {
508-
qqq = 0.0;
522+
uq = 0.0;
509523
} else {
510-
qqq = (float)(iy - upperClosed[ix]) / (float)(upperOpen[ix] - upperClosed[ix]);
524+
uq = (float)(iy - upperClosed[ix]) / (float)(upperOpen[ix] - upperClosed[ix]);
525+
}
526+
if(booped) {
527+
uq = 0.9;
528+
lq = 0.7;
529+
} else {
530+
lq = 1.0 - uq;
511531
}
512532
// Dampen eyelid movements slightly
513533
// SAVE upper & lower lid factors per eye,
514534
// they need to stay consistent across frame
515-
eye[eyeNum].upperLidFactor = (eye[eyeNum].upperLidFactor * 0.75) + (qqq * 0.25);
516-
eye[eyeNum].lowerLidFactor = 1.0 - eye[eyeNum].upperLidFactor;
535+
eye[eyeNum].upperLidFactor = (eye[eyeNum].upperLidFactor * 0.6) + (uq * 0.4);
536+
eye[eyeNum].lowerLidFactor = (eye[eyeNum].lowerLidFactor * 0.6) + (lq * 0.4);
537+
517538

518539
// Process blinks
519540
if(eye[eyeNum].blink.state) { // Eye currently blinking?
520541
// Check if current blink state time has elapsed
521542
if((t - eye[eyeNum].blink.startTime) >= eye[eyeNum].blink.duration) {
522-
if((eye[eyeNum].blink.state == ENBLINK) && booped) {
523-
// do nothing, don't advance blink state while booped
543+
if(++eye[eyeNum].blink.state > DEBLINK) { // Deblinking finished?
544+
eye[eyeNum].blink.state = NOBLINK; // No longer blinking
545+
eye[eyeNum].blinkFactor = 0.0;
546+
} else { // Advancing from ENBLINK to DEBLINK mode
547+
eye[eyeNum].blink.duration *= 2; // DEBLINK is 1/2 ENBLINK speed
548+
eye[eyeNum].blink.startTime = t;
524549
eye[eyeNum].blinkFactor = 1.0;
525-
} else {
526-
if(++eye[eyeNum].blink.state > DEBLINK) { // Deblinking finished?
527-
eye[eyeNum].blink.state = NOBLINK; // No longer blinking
528-
eye[eyeNum].blinkFactor = 0.0;
529-
} else { // Advancing from ENBLINK to DEBLINK mode
530-
eye[eyeNum].blink.duration *= 2; // DEBLINK is 1/2 ENBLINK speed
531-
eye[eyeNum].blink.startTime = t;
532-
eye[eyeNum].blinkFactor = 1.0;
533-
}
534550
}
535551
} else {
536552
eye[eyeNum].blinkFactor = (float)(t - eye[eyeNum].blink.startTime) / (float)eye[eyeNum].blink.duration;
@@ -549,21 +565,9 @@ void loop() {
549565

550566
// Once per frame (of eye #0), reset boopSum...
551567
if((eyeNum == 0) && (boopPin >= 0)) {
552-
if(boopSum > boopThreshold) {
568+
boopSumFiltered = ((boopSumFiltered * 3) + boopSum) / 4;
569+
if(boopSumFiltered > boopThreshold) {
553570
if(!booped) {
554-
timeOfLastBlink = t;
555-
// slow, intentional blink
556-
uint32_t blinkDuration = random(60000, 100000);
557-
// Set up durations for both eyes (if not already winking)
558-
for(uint8_t e=0; e<NUM_EYES; e++) {
559-
// if(eye[e].blink.state == NOBLINK) {
560-
if(1) {
561-
eye[e].blink.state = ENBLINK;
562-
eye[e].blink.startTime = t;
563-
eye[e].blink.duration = blinkDuration;
564-
}
565-
}
566-
timeToNextBlink = blinkDuration * 3 + random(4000000);
567571
Serial.println("BOOP!");
568572
}
569573
booped = true;
@@ -813,8 +817,63 @@ void loop() {
813817
eye[eyeNum].display->setAddrWindow(0, 0, 240, 240);
814818
delayMicroseconds(1);
815819
digitalWrite(eye[eyeNum].dc, HIGH); // Data mode
820+
if(eyeNum == (NUM_EYES-1)) {
821+
// Handle pupil scaling
822+
if(lightSensorPin >= 0) {
823+
// Read light sensor, but not too often (Seesaw hates that)
824+
#define LIGHT_INTERVAL (1000000 / 10) // 10 Hz, don't poll Seesaw too often
825+
if((t - lastLightReadTime) >= LIGHT_INTERVAL) {
826+
// Fun fact: eyes have a "consensual response" to light -- both
827+
// pupils will react even if the opposite eye is stimulated.
828+
// Meaning we can get away with using a single light sensor for
829+
// both eyes. This comment has nothing to do with the code.
830+
uint16_t rawReading = readLightSensor();
831+
if(rawReading <= 1023) {
832+
if(rawReading < lightSensorMin) rawReading = lightSensorMin; // Clamp light sensor range
833+
else if(rawReading > lightSensorMax) rawReading = lightSensorMax; // to within usable range
834+
float v = (float)(rawReading - lightSensorMin) / (float)(lightSensorMax - lightSensorMin); // 0.0 to 1.0
835+
v = pow(v, lightSensorCurve);
836+
lastLightValue = irisMin + v * irisRange;
837+
lastLightReadTime = t;
838+
lightSensorFailCount = 0;
839+
} else { // I2C error
840+
if(++lightSensorFailCount >= 25) { // If repeated errors in succession...
841+
lightSensorPin = -1; // Stop trying to use the light sensor
842+
} else {
843+
lastLightReadTime = t - LIGHT_INTERVAL + 30000; // Try again in 30 ms
844+
}
845+
}
846+
}
847+
irisValue = (irisValue * 0.97) + (lastLightValue * 0.03); // Filter response for smooth reaction
848+
} else {
849+
// Not light responsive. Use autonomous iris w/fractal subdivision
850+
float n, sum = 0.5;
851+
for(uint16_t i=0; i<IRIS_LEVELS; i++) { // 0,1,2,3,...
852+
uint16_t iexp = 1 << (i+1); // 2,4,8,16,...
853+
uint16_t imask = (iexp - 1); // 2^i-1 (1,3,7,15,...)
854+
uint16_t ibits = iris_frame & imask; // 0 to mask
855+
if(ibits) {
856+
float weight = (float)ibits / (float)iexp; // 0.0 to <1.0
857+
n = iris_prev[i] * (1.0 - weight) + iris_next[i] * weight;
858+
} else {
859+
n = iris_next[i];
860+
iris_prev[i] = iris_next[i];
861+
iris_next[i] = -0.5 + ((float)random(1000) / 999.0); // -0.5 to +0.5
862+
}
863+
iexp = 1 << (IRIS_LEVELS - i); // ...8,4,2,1
864+
sum += n / (float)iexp;
865+
}
866+
irisValue = irisMin + (sum * irisRange); // 0.0-1.0 -> iris min/max
867+
if((++iris_frame) >= (1 << IRIS_LEVELS)) iris_frame = 0;
868+
}
869+
}
816870
} // end first-column check
817871

872+
// MUST read the booper when there’s no SPI traffic across the nose!
873+
if((eyeNum == (NUM_EYES-1)) && (boopPin >= 0)) {
874+
boopSum += readBoop();
875+
}
876+
818877
memcpy(eye[eyeNum].dptr, &eye[eyeNum].column[eye[eyeNum].colIdx].descriptor[0], sizeof(DmacDescriptor));
819878
eye[eyeNum].dma_busy = true;
820879
eye[eyeNum].dma.startJob();
@@ -824,13 +883,4 @@ void loop() {
824883
}
825884
eye[eyeNum].colIdx ^= 1; // Alternate 0/1 line structs
826885
eye[eyeNum].column_ready = false; // OK to render next line
827-
828-
if((eyeNum == 0) && (boopPin >= 0)) {
829-
uint16_t counter = 0;
830-
pinMode(boopPin, OUTPUT);
831-
digitalWrite(boopPin, HIGH);
832-
pinMode(boopPin, INPUT);
833-
while(digitalRead(boopPin) && (++counter < 1000));
834-
boopSum += counter;
835-
}
836886
}
7.56 KB
Binary file not shown.
7.56 KB
Binary file not shown.

M4_Eyes/file.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,8 @@ void loadConfig(char *filename) {
247247

248248
lightSensorPin = doc["lightSensor"] | lightSensorPin;
249249
boopPin = doc["boopSensor"] | boopPin;
250-
boopThreshold = doc["boopThreshold"] | boopThreshold;
250+
// Computed at startup, NOT from file now
251+
// boopThreshold = doc["boopThreshold"] | boopThreshold;
251252

252253
// Values that can be distinct per-eye but have a common default...
253254
uint16_t pupilColor = dwim(doc["pupilColor"] , eye[0].pupilColor),

0 commit comments

Comments
 (0)