/ All_Seeing_Skull / All_Seeing_Skull.ino
All_Seeing_Skull.ino
  1  // SPDX-FileCopyrightText: 2018 Phillip Burgess for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  // All Seeing Skull -- Eye w PIR sensor
  6  //--------------------------------------------------------------------------
  7  // Uncanny eyes for Adafruit 1.5" OLED (product #1431) or 1.44" TFT LCD
  8  // (#2088).  Works on PJRC Teensy 3.x and on Adafruit M0 and M4 boards
  9  // (Feather, Metro, etc.).  This code uses features specific to these
 10  // boards and WILL NOT work on normal Arduino or other boards!
 11  //
 12  // SEE FILE "config.h" FOR MOST CONFIGURATION (graphics, pins, display type,
 13  // etc).  Probably won't need to edit THIS file unless you're doing some
 14  // extremely custom modifications.
 15  //
 16  // Adafruit invests time and resources providing this open source code,
 17  // please support Adafruit and open-source hardware by purchasing products
 18  // from Adafruit!
 19  //
 20  // Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
 21  // MIT license.  SPI FIFO insight from Paul Stoffregen's ILI9341_t3 library.
 22  // Inspired by David Boccabella's (Marcwolf) hybrid servo/OLED eye concept.
 23  //--------------------------------------------------------------------------
 24  
 25  #include <SPI.h>
 26  #include <Adafruit_GFX.h>
 27  #ifdef ARDUINO_ARCH_SAMD
 28    #include <Adafruit_ZeroDMA.h>
 29  #endif
 30  
 31  //PIR sensor
 32  int MOTION_SENSOR_PIN = A11;  // SENSE port on HalloWing
 33  unsigned long lastTriggerTime = 0L;
 34  
 35  typedef struct {        // Struct is defined before including config.h --
 36    int8_t  select;       // pin numbers for each eye's screen select line
 37    int8_t  wink;         // and wink button (or -1 if none) specified there,
 38    uint8_t rotation;     // also display rotation.
 39  } eyeInfo_t;
 40  
 41  #include "config.h"     // ****** CONFIGURATION IS DONE IN HERE ******
 42  
 43  #if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_)
 44    typedef Adafruit_ST7735  displayType; // Using TFT display(s)
 45  #else
 46    typedef Adafruit_SSD1351 displayType; // Using OLED display(s)
 47  #endif
 48  
 49  // A simple state machine is used to control eye blinks/winks:
 50  #define NOBLINK 0       // Not currently engaged in a blink
 51  #define ENBLINK 1       // Eyelid is currently closing
 52  #define DEBLINK 2       // Eyelid is currently opening
 53  typedef struct {
 54    uint8_t  state;       // NOBLINK/ENBLINK/DEBLINK
 55    uint32_t duration;    // Duration of blink state (micros)
 56    uint32_t startTime;   // Time (micros) of last state change
 57  } eyeBlink;
 58  
 59  #define NUM_EYES (sizeof eyeInfo / sizeof eyeInfo[0]) // config.h pin list
 60  
 61  struct {                // One-per-eye structure
 62    displayType *display; // -> OLED/TFT object
 63    eyeBlink     blink;   // Current blink/wink state
 64  } eye[NUM_EYES];
 65  
 66  #ifdef ARDUINO_ARCH_SAMD
 67    // SAMD boards use DMA (Teensy uses SPI FIFO instead):
 68    // Two single-line 128-pixel buffers (16bpp) are used for DMA.
 69    // Though you'd think fewer larger transfers would improve speed,
 70    // multi-line buffering made no appreciable difference.
 71    uint16_t          dmaBuf[2][128];
 72    uint8_t           dmaIdx = 0; // Active DMA buffer # (alternate fill/send)
 73    Adafruit_ZeroDMA  dma;
 74    DmacDescriptor   *descriptor;
 75  
 76    // DMA transfer-in-progress indicator and callback
 77    static volatile bool dma_busy = false;
 78    static void dma_callback(Adafruit_ZeroDMA *dma) { dma_busy = false; }
 79  #endif
 80  
 81  uint32_t startTime;  // For FPS indicator
 82  
 83  
 84  // INITIALIZATION -- runs once at startup ----------------------------------
 85  
 86  void setup(void) {
 87    uint8_t e; // Eye index, 0 to NUM_EYES-1
 88  
 89    Serial.begin(115200);
 90    randomSeed(analogRead(A3)); // Seed random() from floating analog input
 91  
 92    pinMode(MOTION_SENSOR_PIN, INPUT);
 93  
 94  #ifdef DISPLAY_BACKLIGHT
 95    // Enable backlight pin, initially off
 96    pinMode(DISPLAY_BACKLIGHT, OUTPUT);
 97    digitalWrite(DISPLAY_BACKLIGHT, LOW);
 98  #endif
 99  
100    // Initialize eye objects based on eyeInfo list in config.h:
101    for(e=0; e<NUM_EYES; e++) {
102      eye[e].display     = new displayType(eyeInfo[e].select, DISPLAY_DC, -1);
103      eye[e].blink.state = NOBLINK;
104      // If project involves only ONE eye and NO other SPI devices, its
105      // select line can be permanently tied to GND and corresponding pin
106      // in config.h set to -1.  Best to use it though.
107      if(eyeInfo[e].select >= 0) {
108        pinMode(eyeInfo[e].select, OUTPUT);
109        digitalWrite(eyeInfo[e].select, HIGH); // Deselect them all
110      }
111      // Also set up an individual eye-wink pin if defined:
112      if(eyeInfo[e].wink >= 0) pinMode(eyeInfo[e].wink, INPUT_PULLUP);
113    }
114  #if defined(BLINK_PIN) && (BLINK_PIN >= 0)
115    pinMode(BLINK_PIN, INPUT_PULLUP); // Ditto for all-eyes blink pin
116  #endif
117  
118  #if defined(DISPLAY_RESET) && (DISPLAY_RESET >= 0)
119    // Because both displays share a common reset pin, -1 is passed to
120    // the display constructor above to prevent the begin() function from
121    // resetting both displays after one is initialized.  Instead, handle
122    // the reset manually here to take care of both displays just once:
123    pinMode(DISPLAY_RESET, OUTPUT);
124    digitalWrite(DISPLAY_RESET, LOW);  delay(1);
125    digitalWrite(DISPLAY_RESET, HIGH); delay(50);
126    // Alternately, all display reset pin(s) could be connected to the
127    // microcontroller reset, in which case DISPLAY_RESET should be set
128    // to -1 or left undefined in config.h.
129  #endif
130  
131    // After all-displays reset, now call init/begin func for each display:
132    for(e=0; e<NUM_EYES; e++) {
133  #if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
134      eye[e].display->initR(INITR_144GREENTAB);
135  #else // OLED
136      eye[e].display->begin();
137  #endif
138      eye[e].display->setRotation(eyeInfo[e].rotation);
139    }
140  
141  #if defined(LOGO_TOP_WIDTH) || defined(COLOR_LOGO_WIDTH)
142    // I noticed lots of folks getting right/left eyes flipped, or
143    // installing upside-down, etc.  Logo split across screens may help:
144    for(e=0; e<NUM_EYES; e++) { // Another pass, after all screen inits
145      eye[e].display->fillScreen(0);
146      #ifdef LOGO_TOP_WIDTH
147        // Monochrome Adafruit logo is 2 mono bitmaps:
148        eye[e].display->drawBitmap(NUM_EYES*64 - e*128 - 20,
149          0, logo_top, LOGO_TOP_WIDTH, LOGO_TOP_HEIGHT, 0xFFFF);
150        eye[e].display->drawBitmap(NUM_EYES*64 - e*128 - LOGO_BOTTOM_WIDTH/2,
151          LOGO_TOP_HEIGHT, logo_bottom, LOGO_BOTTOM_WIDTH, LOGO_BOTTOM_HEIGHT,
152          0xFFFF);
153      #else
154        // Color sponsor logo is one RGB bitmap:
155        eye[e].display->fillScreen(color_logo[0]);
156        eye[0].display->drawRGBBitmap(
157          (eye[e].display->width()  - COLOR_LOGO_WIDTH ) / 2,
158          (eye[e].display->height() - COLOR_LOGO_HEIGHT) / 2,
159          color_logo, COLOR_LOGO_WIDTH, COLOR_LOGO_HEIGHT);
160      #endif
161      // After logo is drawn
162    }
163    #ifdef DISPLAY_BACKLIGHT
164      int i;
165      for(i=0; i<BACKLIGHT_MAX; i++) { // Fade logo in
166        analogWrite(DISPLAY_BACKLIGHT, i);
167        delay(2);
168      }
169      delay(1400); // Pause for screen layout/orientation
170      for(; i>=0; i--) {
171        analogWrite(DISPLAY_BACKLIGHT, i);
172        delay(2);
173      }
174      for(e=0; e<NUM_EYES; e++) { // Clear display(s)
175        eye[e].display->fillScreen(0);
176      }
177      delay(100);
178    #else
179      delay(2000); // Pause for screen layout/orientation
180    #endif // DISPLAY_BACKLIGHT
181  #endif // LOGO_TOP_WIDTH
182  
183    // One of the displays is configured to mirror on the X axis.  Simplifies
184    // eyelid handling in the drawEye() function -- no need for distinct
185    // L-to-R or R-to-L inner loops.  Just the X coordinate of the iris is
186    // then reversed when drawing this eye, so they move the same.  Magic!
187  #if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
188    const uint8_t mirrorTFT[]  = { 0x88, 0x28, 0x48, 0xE8 }; // Mirror+rotate
189    digitalWrite(eyeInfo[0].select, LOW);
190    digitalWrite(DISPLAY_DC, LOW);
191    #ifdef ST77XX_MADCTL
192      SPI.transfer(ST77XX_MADCTL); // Current TFT lib
193    #else
194      SPI.transfer(ST7735_MADCTL); // Older TFT lib
195    #endif
196    digitalWrite(DISPLAY_DC, HIGH);
197    SPI.transfer(mirrorTFT[eyeInfo[0].rotation & 3]);
198    digitalWrite(eyeInfo[0].select , HIGH);
199  #else // OLED
200    const uint8_t rotateOLED[] = { 0x74, 0x77, 0x66, 0x65 },
201                  mirrorOLED[] = { 0x76, 0x67, 0x64, 0x75 }; // Mirror+rotate
202    // If OLED, loop through ALL eyes and set up remap register
203    // from either mirrorOLED[] (first eye) or rotateOLED[] (others).
204    // The OLED library doesn't normally use the remap reg (TFT does).
205    for(e=0; e<NUM_EYES; e++) {
206      uint8_t d = e ? rotateOLED[eyeInfo[e].rotation & 3] :
207                      mirrorOLED[eyeInfo[e].rotation & 3];
208      eye[e].display->sendCommand(SSD1351_CMD_SETREMAP, &d, 1);
209    }
210  #endif
211  
212  #ifdef ARDUINO_ARCH_SAMD
213    // Set up SPI DMA on SAMD boards:
214    int                dmac_id;
215    volatile uint32_t *data_reg;
216    if(&PERIPH_SPI == &sercom0) {
217      dmac_id  = SERCOM0_DMAC_ID_TX;
218      data_reg = &SERCOM0->SPI.DATA.reg;
219  #if defined SERCOM1
220    } else if(&PERIPH_SPI == &sercom1) {
221      dmac_id  = SERCOM1_DMAC_ID_TX;
222      data_reg = &SERCOM1->SPI.DATA.reg;
223  #endif
224  #if defined SERCOM2
225    } else if(&PERIPH_SPI == &sercom2) {
226      dmac_id  = SERCOM2_DMAC_ID_TX;
227      data_reg = &SERCOM2->SPI.DATA.reg;
228  #endif
229  #if defined SERCOM3
230    } else if(&PERIPH_SPI == &sercom3) {
231      dmac_id  = SERCOM3_DMAC_ID_TX;
232      data_reg = &SERCOM3->SPI.DATA.reg;
233  #endif
234  #if defined SERCOM4
235    } else if(&PERIPH_SPI == &sercom4) {
236      dmac_id  = SERCOM4_DMAC_ID_TX;
237      data_reg = &SERCOM4->SPI.DATA.reg;
238  #endif
239  #if defined SERCOM5
240    } else if(&PERIPH_SPI == &sercom5) {
241      dmac_id  = SERCOM5_DMAC_ID_TX;
242      data_reg = &SERCOM5->SPI.DATA.reg;
243  #endif
244    }
245  
246    dma.allocate();
247    dma.setTrigger(dmac_id);
248    dma.setAction(DMA_TRIGGER_ACTON_BEAT);
249    descriptor = dma.addDescriptor(
250      NULL,               // move data
251      (void *)data_reg,   // to here
252      sizeof dmaBuf[0],   // this many...
253      DMA_BEAT_SIZE_BYTE, // bytes/hword/words
254      true,               // increment source addr?
255      false);             // increment dest addr?
256    dma.setCallback(dma_callback);
257  
258  #endif // End SAMD-specific SPI DMA init
259  
260  #ifdef DISPLAY_BACKLIGHT
261    analogWrite(DISPLAY_BACKLIGHT, BACKLIGHT_MAX);
262  #endif
263  
264    startTime = millis(); // For frame-rate calculation
265  }
266  
267  
268  // EYE-RENDERING FUNCTION --------------------------------------------------
269  
270  SPISettings settings(SPI_FREQ, MSBFIRST, SPI_MODE0);
271  
272  void drawEye( // Renders one eye.  Inputs must be pre-clipped & valid.
273    uint8_t  e,       // Eye array index; 0 or 1 for left/right
274    uint32_t iScale,  // Scale factor for iris
275    uint8_t  scleraX, // First pixel X offset into sclera image
276    uint8_t  scleraY, // First pixel Y offset into sclera image
277    uint8_t  uT,      // Upper eyelid threshold value
278    uint8_t  lT) {    // Lower eyelid threshold value
279  
280    uint8_t  screenX, screenY, scleraXsave;
281    int16_t  irisX, irisY;
282    uint16_t p, a;
283    uint32_t d;
284  
285    //PIR detect
286    if(digitalRead(MOTION_SENSOR_PIN)){  //PIR triggered
287      lastTriggerTime = millis();  //save last trigger time
288    }
289  
290  
291    // Set up raw pixel dump to entire screen.  Although such writes can wrap
292    // around automatically from end of rect back to beginning, the region is
293    // reset on each frame here in case of an SPI glitch.
294    SPI.beginTransaction(settings);
295    digitalWrite(eyeInfo[e].select, LOW);                        // Chip select
296  #if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
297    eye[e].display->setAddrWindow(0, 0, 128, 128);
298  #else // OLED
299    eye[e].display->writeCommand(SSD1351_CMD_SETROW);    // Y range
300    eye[e].display->writeData(0); eye[e].display->writeData(SCREEN_HEIGHT - 1);
301    eye[e].display->writeCommand(SSD1351_CMD_SETCOLUMN); // X range
302    eye[e].display->writeData(0); eye[e].display->writeData(SCREEN_WIDTH  - 1);
303    eye[e].display->writeCommand(SSD1351_CMD_WRITERAM);  // Begin write
304  #endif
305    digitalWrite(eyeInfo[e].select, LOW);                // Re-chip-select
306    digitalWrite(DISPLAY_DC, HIGH);                      // Data mode
307    // Now just issue raw 16-bit values for every pixel...
308  
309    //PIR sensor check trigger times
310    if((millis() - lastTriggerTime) <= 5000) {  // draw because PIR tripped
311      scleraXsave = scleraX; // Save initial X value to reset on each line
312      irisY       = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT) / 2;
313      for(screenY=0; screenY<SCREEN_HEIGHT; screenY++, scleraY++, irisY++) {
314    #ifdef ARDUINO_ARCH_SAMD
315        uint16_t *ptr = &dmaBuf[dmaIdx][0];
316    #endif
317        scleraX = scleraXsave;
318        irisX   = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
319        for(screenX=0; screenX<SCREEN_WIDTH; screenX++, scleraX++, irisX++) {
320          if((lower[screenY][screenX] <= lT) ||
321             (upper[screenY][screenX] <= uT)) {             // Covered by eyelid
322            p = 0;
323          } else if((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
324                    (irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
325            p = sclera[scleraY][scleraX];
326          } else {                                          // Maybe iris...
327            p = polar[irisY][irisX];                        // Polar angle/dist
328            d = (iScale * (p & 0x7F)) / 128;                // Distance (Y)
329            if(d < IRIS_MAP_HEIGHT) {                       // Within iris area
330              a = (IRIS_MAP_WIDTH * (p >> 7)) / 512;        // Angle (X)
331              p = iris[d][a];                               // Pixel = iris
332            } else {                                        // Not in iris
333              p = sclera[scleraY][scleraX];                 // Pixel = sclera
334            }
335          }
336    #ifdef ARDUINO_ARCH_SAMD
337          *ptr++ = __builtin_bswap16(p); // DMA: store in scanline buffer
338    #else
339          // SPI FIFO technique from Paul Stoffregen's ILI9341_t3 library:
340          while(KINETISK_SPI0.SR & 0xC000); // Wait for space in FIFO
341          KINETISK_SPI0.PUSHR = p | SPI_PUSHR_CTAS(1) | SPI_PUSHR_CONT;
342    #endif
343        } // end column
344    #ifdef ARDUINO_ARCH_SAMD
345        while(dma_busy); // Wait for prior DMA xfer to finish
346        descriptor->SRCADDR.reg = (uint32_t)&dmaBuf[dmaIdx] + sizeof dmaBuf[0];
347        dma_busy = true;
348        dmaIdx   = 1 - dmaIdx;
349        dma.startJob();
350    #endif
351      } // end scanline
352    }
353  
354    else {  //don't draw because PIR didn't trip
355      scleraXsave = scleraX; // Save initial X value to reset on each line
356      irisY       = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT) / 2;
357      for(screenY=0; screenY<SCREEN_HEIGHT; screenY++, scleraY++, irisY++) {
358    #ifdef ARDUINO_ARCH_SAMD
359        uint16_t *ptr = &dmaBuf[dmaIdx][0];
360    #endif
361        scleraX = scleraXsave;
362        irisX   = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
363        for(screenX=0; screenX<SCREEN_WIDTH; screenX++, scleraX++, irisX++) {
364          if((lower[screenY][screenX] <= lT) ||
365             (upper[screenY][screenX] <= uT)) {             // Covered by eyelid
366            p = 0;
367          } else if((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
368                    (irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
369            //p = sclera[scleraY][scleraX];
370            p = 0;
371          } else {                                          // Maybe iris...
372            //p = polar[irisY][irisX];                        // Polar angle/dist
373            p=0;
374            d = (iScale * (p & 0x7F)) / 128;                // Distance (Y)
375            if(d < IRIS_MAP_HEIGHT) {                       // Within iris area
376              a = (IRIS_MAP_WIDTH * (p >> 7)) / 512;        // Angle (X)
377              //p = iris[d][a];                               // Pixel = iris
378              p=0;
379            } else {                                        // Not in iris
380              //p = sclera[scleraY][scleraX];                 // Pixel = sclera
381              p=0;
382            }
383          }
384    #ifdef ARDUINO_ARCH_SAMD
385          *ptr++ = __builtin_bswap16(p); // DMA: store in scanline buffer
386    #else
387          // SPI FIFO technique from Paul Stoffregen's ILI9341_t3 library:
388          while(KINETISK_SPI0.SR & 0xC000); // Wait for space in FIFO
389          KINETISK_SPI0.PUSHR = p | SPI_PUSHR_CTAS(1) | SPI_PUSHR_CONT;
390    #endif
391        } // end column
392    #ifdef ARDUINO_ARCH_SAMD
393        while(dma_busy); // Wait for prior DMA xfer to finish
394        descriptor->SRCADDR.reg = (uint32_t)&dmaBuf[dmaIdx] + sizeof dmaBuf[0];
395        dma_busy = true;
396        dmaIdx   = 1 - dmaIdx;
397        dma.startJob();
398    #endif
399      } // end scanline
400    }
401  
402  
403  
404  #ifdef ARDUINO_ARCH_SAMD
405    while(dma_busy);  // Wait for last scanline to transmit
406  #else
407    KINETISK_SPI0.SR |= SPI_SR_TCF;         // Clear transfer flag
408    while((KINETISK_SPI0.SR & 0xF000) ||    // Wait for SPI FIFO to drain
409         !(KINETISK_SPI0.SR & SPI_SR_TCF)); // Wait for last bit out
410  #endif
411  
412    digitalWrite(eyeInfo[e].select, HIGH);          // Deselect
413    SPI.endTransaction();
414  }
415  
416  
417  // EYE ANIMATION -----------------------------------------------------------
418  
419  const uint8_t ease[] = { // Ease in/out curve for eye movements 3*t^2-2*t^3
420      0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  2,  2,  2,  3,   // T
421      3,  3,  4,  4,  4,  5,  5,  6,  6,  7,  7,  8,  9,  9, 10, 10,   // h
422     11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23,   // x
423     24, 25, 26, 27, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39,   // 2
424     40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58,   // A
425     60, 61, 62, 63, 65, 66, 67, 69, 70, 72, 73, 74, 76, 77, 78, 80,   // l
426     81, 83, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 98,100,101,103,   // e
427    104,106,107,109,110,112,113,115,116,118,119,121,122,124,125,127,   // c
428    128,130,131,133,134,136,137,139,140,142,143,145,146,148,149,151,   // J
429    152,154,155,157,158,159,161,162,164,165,167,168,170,171,172,174,   // a
430    175,177,178,179,181,182,183,185,186,188,189,190,192,193,194,195,   // c
431    197,198,199,201,202,203,204,205,207,208,209,210,211,213,214,215,   // o
432    216,217,218,219,220,221,222,224,225,226,227,228,228,229,230,231,   // b
433    232,233,234,235,236,237,237,238,239,240,240,241,242,243,243,244,   // s
434    245,245,246,246,247,248,248,249,249,250,250,251,251,251,252,252,   // o
435    252,253,253,253,254,254,254,254,254,255,255,255,255,255,255,255 }; // n
436  
437  #ifdef AUTOBLINK
438  uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L;
439  #endif
440  
441  void frame( // Process motion for a single frame of left or right eye
442    uint16_t        iScale) {     // Iris scale (0-1023) passed in
443    static uint32_t frames   = 0; // Used in frame rate calculation
444    static uint8_t  eyeIndex = 0; // eye[] array counter
445    int16_t         eyeX, eyeY;
446    uint32_t        t = micros(); // Time at start of function
447  
448    if(!(++frames & 255)) { // Every 256 frames...
449      uint32_t elapsed = (millis() - startTime) / 1000;
450      if(elapsed) Serial.println(frames / elapsed); // Print FPS
451    }
452  
453    if(++eyeIndex >= NUM_EYES) eyeIndex = 0; // Cycle through eyes, 1 per call
454  
455    // X/Y movement
456  
457  #if defined(JOYSTICK_X_PIN) && (JOYSTICK_X_PIN >= 0) && \
458      defined(JOYSTICK_Y_PIN) && (JOYSTICK_Y_PIN >= 0)
459  
460    // Read X/Y from joystick, constrain to circle
461    int16_t dx, dy;
462    int32_t d;
463    eyeX = analogRead(JOYSTICK_X_PIN); // Raw (unclipped) X/Y reading
464    eyeY = analogRead(JOYSTICK_Y_PIN);
465  
466  #ifdef JOYSTICK_X_FLIP
467    eyeX = 1023 - eyeX;
468  #endif
469  #ifdef JOYSTICK_Y_FLIP
470    eyeY = 1023 - eyeY;
471  #endif
472    dx = (eyeX * 2) - 1023; // A/D exact center is at 511.5.  Scale coords
473    dy = (eyeY * 2) - 1023; // X2 so range is -1023 to +1023 w/center at 0.
474    if((d = (dx * dx + dy * dy)) > (1023 * 1023)) { // Outside circle
475      d    = (int32_t)sqrt((float)d);               // Distance from center
476      eyeX = ((dx * 1023 / d) + 1023) / 2;          // Clip to circle edge,
477      eyeY = ((dy * 1023 / d) + 1023) / 2;          // scale back to 0-1023
478    }
479  
480  #else // Autonomous X/Y eye motion
481        // Periodically initiates motion to a new random point, random speed,
482        // holds there for random period until next motion.
483  
484    static boolean  eyeInMotion      = false;
485    static int16_t  eyeOldX=512, eyeOldY=512, eyeNewX=512, eyeNewY=512;
486    static uint32_t eyeMoveStartTime = 0L;
487    static int32_t  eyeMoveDuration  = 0L;
488  
489    int32_t dt = t - eyeMoveStartTime;      // uS elapsed since last eye event
490    if(eyeInMotion) {                       // Currently moving?
491      if(dt >= eyeMoveDuration) {           // Time up?  Destination reached.
492        eyeInMotion      = false;           // Stop moving
493        eyeMoveDuration  = random(3000000); // 0-3 sec stop
494        eyeMoveStartTime = t;               // Save initial time of stop
495        eyeX = eyeOldX = eyeNewX;           // Save position
496        eyeY = eyeOldY = eyeNewY;
497      } else { // Move time's not yet fully elapsed -- interpolate position
498        int16_t e = ease[255 * dt / eyeMoveDuration] + 1;   // Ease curve
499        eyeX = eyeOldX + (((eyeNewX - eyeOldX) * e) / 256); // Interp X
500        eyeY = eyeOldY + (((eyeNewY - eyeOldY) * e) / 256); // and Y
501      }
502    } else {                                // Eye stopped
503      eyeX = eyeOldX;
504      eyeY = eyeOldY;
505      if(dt > eyeMoveDuration) {            // Time up?  Begin new move.
506        int16_t  dx, dy;
507        uint32_t d;
508        do {                                // Pick new dest in circle
509          eyeNewX = random(1024);
510          eyeNewY = random(1024);
511  
512          dx      = (eyeNewX * 2) - 1023;
513          dy      = (eyeNewY * 2) - 1023;
514      } while((d = (dx * dx + dy * dy)) > (eyeXRange * eyeYRange)); // Keep trying
515        eyeNewX += eyeXOffset;
516        eyeNewY += eyeYOffset;
517        eyeMoveDuration  = random(72000, 144000); // ~1/14 - ~1/7 sec
518        eyeMoveStartTime = t;               // Save initial time of move
519        eyeInMotion      = true;            // Start move on next frame
520      }
521    }
522  
523  #endif // JOYSTICK_X_PIN etc.
524  
525    // Blinking
526  
527  #ifdef AUTOBLINK
528    // Similar to the autonomous eye movement above -- blink start times
529    // and durations are random (within ranges).
530    if((t - timeOfLastBlink) >= timeToNextBlink) { // Start new blink?
531      timeOfLastBlink = t;
532      uint32_t blinkDuration = random(36000, 72000); // ~1/28 - ~1/14 sec
533      // Set up durations for both eyes (if not already winking)
534      for(uint8_t e=0; e<NUM_EYES; e++) {
535        if(eye[e].blink.state == NOBLINK) {
536          eye[e].blink.state     = ENBLINK;
537          eye[e].blink.startTime = t;
538          eye[e].blink.duration  = blinkDuration;
539        }
540      }
541      timeToNextBlink = blinkDuration * 3 + random(4000000);
542    }
543  #endif
544  
545    if(eye[eyeIndex].blink.state) { // Eye currently blinking?
546      // Check if current blink state time has elapsed
547      if((t - eye[eyeIndex].blink.startTime) >= eye[eyeIndex].blink.duration) {
548        // Yes -- increment blink state, unless...
549        if((eye[eyeIndex].blink.state == ENBLINK) && ( // Enblinking and...
550  #if defined(BLINK_PIN) && (BLINK_PIN >= 0)
551          (digitalRead(BLINK_PIN) == LOW) ||           // blink or wink held...
552  #endif
553          ((eyeInfo[eyeIndex].wink >= 0) &&
554           digitalRead(eyeInfo[eyeIndex].wink) == LOW) )) {
555          // Don't advance state yet -- eye is held closed instead
556        } else { // No buttons, or other state...
557          if(++eye[eyeIndex].blink.state > DEBLINK) { // Deblinking finished?
558            eye[eyeIndex].blink.state = NOBLINK;      // No longer blinking
559          } else { // Advancing from ENBLINK to DEBLINK mode
560            eye[eyeIndex].blink.duration *= 2; // DEBLINK is 1/2 ENBLINK speed
561            eye[eyeIndex].blink.startTime = t;
562          }
563        }
564      }
565    } else { // Not currently blinking...check buttons!
566  #if defined(BLINK_PIN) && (BLINK_PIN >= 0)
567      if(digitalRead(BLINK_PIN) == LOW) {
568        // Manually-initiated blinks have random durations like auto-blink
569        uint32_t blinkDuration = random(36000, 72000);
570        for(uint8_t e=0; e<NUM_EYES; e++) {
571          if(eye[e].blink.state == NOBLINK) {
572            eye[e].blink.state     = ENBLINK;
573            eye[e].blink.startTime = t;
574            eye[e].blink.duration  = blinkDuration;
575          }
576        }
577      } else
578  #endif
579      if((eyeInfo[eyeIndex].wink >= 0) &&
580         (digitalRead(eyeInfo[eyeIndex].wink) == LOW)) { // Wink!
581        eye[eyeIndex].blink.state     = ENBLINK;
582        eye[eyeIndex].blink.startTime = t;
583        eye[eyeIndex].blink.duration  = random(45000, 90000);
584      }
585    }
586  
587    // Process motion, blinking and iris scale into renderable values
588  
589    // Iris scaling: remap from 0-1023 input to iris map height pixel units
590    iScale = ((IRIS_MAP_HEIGHT + 1) * 1024) /
591             (1024 - (iScale * (IRIS_MAP_HEIGHT - 1) / IRIS_MAP_HEIGHT));
592  
593    // Scale eye X/Y positions (0-1023) to pixel units used by drawEye()
594    eyeX = map(eyeX, 0, 1023, 0, SCLERA_WIDTH  - 128);
595    eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128);
596    if(eyeIndex == 1) eyeX = (SCLERA_WIDTH - 128) - eyeX; // Mirrored display
597  
598    // Horizontal position is offset so that eyes are very slightly crossed
599    // to appear fixated (converged) at a conversational distance.  Number
600    // here was extracted from my posterior and not mathematically based.
601    // I suppose one could get all clever with a range sensor, but for now...
602    if(NUM_EYES > 1) eyeX += 4;
603    if(eyeX > (SCLERA_WIDTH - 128)) eyeX = (SCLERA_WIDTH - 128);
604  
605    // Eyelids are rendered using a brightness threshold image.  This same
606    // map can be used to simplify another problem: making the upper eyelid
607    // track the pupil (eyes tend to open only as much as needed -- e.g. look
608    // down and the upper eyelid drops).  Just sample a point in the upper
609    // lid map slightly above the pupil to determine the rendering threshold.
610    static uint8_t uThreshold = 128;
611    uint8_t        lThreshold, n;
612  #ifdef TRACKING
613    int16_t sampleX = SCLERA_WIDTH  / 2 - (eyeX / 2), // Reduce X influence
614            sampleY = SCLERA_HEIGHT / 2 - (eyeY + IRIS_HEIGHT / 4);
615    // Eyelid is slightly asymmetrical, so two readings are taken, averaged
616    if(sampleY < 0) n = 0;
617    else            n = (upper[sampleY][sampleX] +
618                         upper[sampleY][SCREEN_WIDTH - 1 - sampleX]) / 2;
619    uThreshold = (uThreshold * 3 + n) / 4; // Filter/soften motion
620    // Lower eyelid doesn't track the same way, but seems to be pulled upward
621    // by tension from the upper lid.
622    lThreshold = 254 - uThreshold;
623  #else // No tracking -- eyelids full open unless blink modifies them
624    uThreshold = lThreshold = 0;
625  #endif
626  
627    // The upper/lower thresholds are then scaled relative to the current
628    // blink position so that blinks work together with pupil tracking.
629    if(eye[eyeIndex].blink.state) { // Eye currently blinking?
630      uint32_t s = (t - eye[eyeIndex].blink.startTime);
631      if(s >= eye[eyeIndex].blink.duration) s = 255;   // At or past blink end
632      else s = 255 * s / eye[eyeIndex].blink.duration; // Mid-blink
633      s          = (eye[eyeIndex].blink.state == DEBLINK) ? 1 + s : 256 - s;
634      n          = (uThreshold * s + 254 * (257 - s)) / 256;
635      lThreshold = (lThreshold * s + 254 * (257 - s)) / 256;
636    } else {
637      n          = uThreshold;
638    }
639  
640    // Pass all the derived values to the eye-rendering function:
641    drawEye(eyeIndex, iScale, eyeX, eyeY, n, lThreshold);
642  }
643  
644  
645  // AUTONOMOUS IRIS SCALING (if no photocell or dial) -----------------------
646  
647  #if !defined(LIGHT_PIN) || (LIGHT_PIN < 0)
648  
649  // Autonomous iris motion uses a fractal behavior to similate both the major
650  // reaction of the eye plus the continuous smaller adjustments that occur.
651  
652  uint16_t oldIris = (IRIS_MIN + IRIS_MAX) / 2, newIris;
653  
654  void split( // Subdivides motion path into two sub-paths w/randimization
655    int16_t  startValue, // Iris scale value (IRIS_MIN to IRIS_MAX) at start
656    int16_t  endValue,   // Iris scale value at end
657    uint32_t startTime,  // micros() at start
658    int32_t  duration,   // Start-to-end time, in microseconds
659    int16_t  range) {    // Allowable scale value variance when subdividing
660  
661    if(range >= 8) {     // Limit subdvision count, because recursion
662      range    /= 2;     // Split range & time in half for subdivision,
663      duration /= 2;     // then pick random center point within range:
664      int16_t  midValue = (startValue + endValue - range) / 2 + random(range);
665      uint32_t midTime  = startTime + duration;
666      split(startValue, midValue, startTime, duration, range); // First half
667      split(midValue  , endValue, midTime  , duration, range); // Second half
668    } else {             // No more subdivisons, do iris motion...
669      int32_t dt;        // Time (micros) since start of motion
670      int16_t v;         // Interim value
671      while((dt = (micros() - startTime)) < duration) {
672        v = startValue + (((endValue - startValue) * dt) / duration);
673        if(v < IRIS_MIN)      v = IRIS_MIN; // Clip just in case
674        else if(v > IRIS_MAX) v = IRIS_MAX;
675        frame(v);        // Draw frame w/interim iris scale value
676      }
677    }
678  }
679  
680  #endif // !LIGHT_PIN
681  
682  
683  // MAIN LOOP -- runs continuously after setup() ----------------------------
684  
685  void loop() {
686  
687  #if defined(LIGHT_PIN) && (LIGHT_PIN >= 0) // Interactive iris
688  
689    int16_t v = analogRead(LIGHT_PIN);       // Raw dial/photocell reading
690  #ifdef LIGHT_PIN_FLIP
691    v = 1023 - v;                            // Reverse reading from sensor
692  #endif
693    if(v < LIGHT_MIN)      v = LIGHT_MIN;  // Clamp light sensor range
694    else if(v > LIGHT_MAX) v = LIGHT_MAX;
695    v -= LIGHT_MIN;  // 0 to (LIGHT_MAX - LIGHT_MIN)
696  #ifdef LIGHT_CURVE  // Apply gamma curve to sensor input?
697    v = (int16_t)(pow((double)v / (double)(LIGHT_MAX - LIGHT_MIN),
698      LIGHT_CURVE) * (double)(LIGHT_MAX - LIGHT_MIN));
699  #endif
700    // And scale to iris range (IRIS_MAX is size at LIGHT_MIN)
701    v = map(v, 0, (LIGHT_MAX - LIGHT_MIN), IRIS_MAX, IRIS_MIN);
702  #ifdef IRIS_SMOOTH // Filter input (gradual motion)
703    static int16_t irisValue = (IRIS_MIN + IRIS_MAX) / 2;
704    irisValue = ((irisValue * 15) + v) / 16;
705    frame(irisValue);
706  #else // Unfiltered (immediate motion)
707    frame(v);
708  #endif // IRIS_SMOOTH
709  
710  #else  // Autonomous iris scaling -- invoke recursive function
711  
712    newIris = random(IRIS_MIN, IRIS_MAX);
713    split(oldIris, newIris, micros(), 10000000L, IRIS_MAX - IRIS_MIN);
714    oldIris = newIris;
715  
716  #endif // LIGHT_PIN
717  }