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):
4142bool eyeInMotion = false ;
@@ -56,7 +57,8 @@ uint32_t lastLightReadTime = 0;
5657float lastLightValue = 0.5 ;
5758double irisValue = 0.5 ;
5859int iPupilFactor = 42 ;
59- uint32_t boopSum = 0 ;
60+ uint32_t boopSum = 0 ,
61+ boopSumFiltered = 0 ;
6062bool booped = false ;
6163int fixate = 7 ;
6264uint8_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).
376437void 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}
0 commit comments