Arduino M4 express with GC9A01 Uncanny eyes

Hello,
I'm currently having issues combining my M4 Express with a pair of GC9A01 tft lcds. I am still learning things but have hit a brick wall. I'm trying to get them to work before I start customizing for my project. Currently I'm just getting static on both displays. I've tried swapping out displays with a third one that I have but still get static. I'm unsure if I don't have something coded correctly or if I wired things wrong. I've combined a few different tutorials to get to this point. Any help or pointing of any directions would be helpful I've been using Dronebot Workshop to figure out how to wire the GC9A01s and Wermy for wiring the M4 Express. Here is what I currently have:
(Cirkit Designer IDE)

It likely depends on the code.

It looks to me like you have an I2C interface on the LCDs (SDA, SCL), but have them connected to the SPI interface of the M4 (MO, SCK)

Hard to say whether that's a mistake or intentional, without knowing exactly which GC9A01 displays you have - there seem to be quite a variety!

Also, it looks like you have Vcc on the display, which should be 3.3V, connected to USB on the feather, which is probably 5V.

These are the ones that I'm using, the set up is the same as in the photo (which I'm surprised compared to the other known style. It is SPI style interface.

According to the item description, it is 3-5v but have tried both USB (5v) and the 3v connection on the board

I have the code connected in with the Cirkit Designer link, I changed all the ports to the supported but here is the code as well.
Main code:

//--------------------------------------------------------------------------
// Uncanny eyes for Adafruit 1.5" OLED (product #1431) or 1.44" TFT LCD
// (#2088).  Works on PJRC Teensy 3.x and on Adafruit M0 and M4 boards
// (Feather, Metro, etc.).  This code uses features specific to these
// boards and WILL NOT work on normal Arduino or other boards!
//
// SEE FILE "config.h" FOR MOST CONFIGURATION (graphics, pins, display type,
// etc).  Probably won't need to edit THIS file unless you're doing some
// extremely custom modifications.
//
// Adafruit invests time and resources providing this open source code,
// please support Adafruit and open-source hardware by purchasing products
// from Adafruit!
//
// Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
// MIT license.  SPI FIFO insight from Paul Stoffregen's ILI9341_t3 library.
// Inspired by David Boccabella's (Marcwolf) hybrid servo/OLED eye concept.
//--------------------------------------------------------------------------

#include <SPI.h>
#include <Adafruit_GFX.h>
#ifdef ARDUINO_ARCH_SAMD
  #include <Adafruit_ZeroDMA.h>
#endif

typedef struct {        // Struct is defined before including config.h --
  int8_t  select;       // pin numbers for each eye's screen select line
  int8_t  wink;         // and wink button (or -1 if none) specified there,
  uint8_t rotation;     // also display rotation.
} eyeInfo_t;

#include "config.h"     // ****** CONFIGURATION IS DONE IN HERE ******

#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_)
  typedef Adafruit_ST7735  displayType; // Using TFT display(s)
#else
  typedef Adafruit_SSD1351 displayType; // Using OLED display(s)
#endif

// A simple state machine is used to control eye blinks/winks:
#define NOBLINK 0       // Not currently engaged in a blink
#define ENBLINK 1       // Eyelid is currently closing
#define DEBLINK 2       // Eyelid is currently opening
typedef struct {
  uint8_t  state;       // NOBLINK/ENBLINK/DEBLINK
  uint32_t duration;    // Duration of blink state (micros)
  uint32_t startTime;   // Time (micros) of last state change
  uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L;
} eyeBlink;

typedef struct {
  boolean  eyeInMotion      = false;
  int16_t  eyeOldX=512, eyeOldY=512, eyeNewX=512, eyeNewY=512;
  uint32_t eyeMoveStartTime = 0L;
  int32_t  eyeMoveDuration  = 0L;
  int16_t         eyeX, eyeY;
  uint8_t uThreshold = 128;
} eyeMove;

#define NUM_EYES (sizeof eyeInfo / sizeof eyeInfo[0]) // config.h pin list

struct {                // One-per-eye structure
  displayType *display; // -> OLED/TFT object
  eyeBlink     blink;   // Current blink/wink state
  eyeMove      move;
} eye[NUM_EYES];

#ifdef ARDUINO_ARCH_SAMD
  // SAMD boards use DMA (Teensy uses SPI FIFO instead):
  // Two single-line 128-pixel buffers (16bpp) are used for DMA.
  // Though you'd think fewer larger transfers would improve speed,
  // multi-line buffering made no appreciable difference.
  uint16_t          dmaBuf[2][128];
  uint8_t           dmaIdx = 0; // Active DMA buffer # (alternate fill/send)
  Adafruit_ZeroDMA  dma;
  DmacDescriptor   *descriptor;

  // DMA transfer-in-progress indicator and callback
  static volatile bool dma_busy = false;
  static void dma_callback(Adafruit_ZeroDMA *dma) { dma_busy = false; }
#endif

uint32_t startTime;  // For FPS indicator


// INITIALIZATION -- runs once at startup ----------------------------------

void setup(void) {
  uint8_t e; // Eye index, 0 to NUM_EYES-1

  Serial.begin(115200);
  randomSeed(analogRead(A3)); // Seed random() from floating analog input

#ifdef DISPLAY_BACKLIGHT
  // Enable backlight pin, initially off
  pinMode(DISPLAY_BACKLIGHT, OUTPUT);
  digitalWrite(DISPLAY_BACKLIGHT, LOW);
#endif

  // Initialize eye objects based on eyeInfo list in config.h:
  for(e=0; e<NUM_EYES; e++) {
    eye[e].display     = new displayType(eyeInfo[e].select, DISPLAY_DC, -1);
    eye[e].blink.state = NOBLINK;
    // If project involves only ONE eye and NO other SPI devices, its
    // select line can be permanently tied to GND and corresponding pin
    // in config.h set to -1.  Best to use it though.
    if(eyeInfo[e].select >= 0) {
      pinMode(eyeInfo[e].select, OUTPUT);
      digitalWrite(eyeInfo[e].select, HIGH); // Deselect them all
    }
    // Also set up an individual eye-wink pin if defined:
    if(eyeInfo[e].wink >= 0) pinMode(eyeInfo[e].wink, INPUT_PULLUP);
  }
#if defined(BLINK_PIN) && (BLINK_PIN >= 0)
  pinMode(BLINK_PIN, INPUT_PULLUP); // Ditto for all-eyes blink pin
#endif

#if defined(DISPLAY_RESET) && (DISPLAY_RESET >= 0)
  // Because both displays share a common reset pin, -1 is passed to
  // the display constructor above to prevent the begin() function from
  // resetting both displays after one is initialized.  Instead, handle
  // the reset manually here to take care of both displays just once:
  pinMode(DISPLAY_RESET, OUTPUT);
  digitalWrite(DISPLAY_RESET, LOW);  delay(1);
  digitalWrite(DISPLAY_RESET, HIGH); delay(50);
  // Alternately, all display reset pin(s) could be connected to the
  // microcontroller reset, in which case DISPLAY_RESET should be set
  // to -1 or left undefined in config.h.
#endif

  // After all-displays reset, now call init/begin func for each display:
  for(e=0; e<NUM_EYES; e++) {
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
    eye[e].display->initR(INITR_144GREENTAB);
#else // OLED
    eye[e].display->begin();
#endif
    eye[e].display->setRotation(eyeInfo[e].rotation);
  }

#if defined(LOGO_TOP_WIDTH) || defined(COLOR_LOGO_WIDTH)
  // I noticed lots of folks getting right/left eyes flipped, or
  // installing upside-down, etc.  Logo split across screens may help:
  for(e=0; e<NUM_EYES; e++) { // Another pass, after all screen inits
    eye[e].display->fillScreen(0);
    #ifdef LOGO_TOP_WIDTH
      // Monochrome Adafruit logo is 2 mono bitmaps:
      eye[e].display->drawBitmap(NUM_EYES*64 - e*128 - 20,
        0, logo_top, LOGO_TOP_WIDTH, LOGO_TOP_HEIGHT, 0xFFFF);
      eye[e].display->drawBitmap(NUM_EYES*64 - e*128 - LOGO_BOTTOM_WIDTH/2,
        LOGO_TOP_HEIGHT, logo_bottom, LOGO_BOTTOM_WIDTH, LOGO_BOTTOM_HEIGHT,
        0xFFFF);
    #else
      // Color sponsor logo is one RGB bitmap:
      eye[e].display->fillScreen(color_logo[0]);
      eye[0].display->drawRGBBitmap(
        (eye[e].display->width()  - COLOR_LOGO_WIDTH ) / 2,
        (eye[e].display->height() - COLOR_LOGO_HEIGHT) / 2,
        color_logo, COLOR_LOGO_WIDTH, COLOR_LOGO_HEIGHT);
    #endif
    // After logo is drawn
  }
  #ifdef DISPLAY_BACKLIGHT
    int i;
    for(i=0; i<BACKLIGHT_MAX; i++) { // Fade logo in
      analogWrite(DISPLAY_BACKLIGHT, i);
      delay(2);
    }
    delay(1400); // Pause for screen layout/orientation
    for(; i>=0; i--) {
      analogWrite(DISPLAY_BACKLIGHT, i);
      delay(2);
    }
    for(e=0; e<NUM_EYES; e++) { // Clear display(s)
      eye[e].display->fillScreen(0);
    }
    delay(100);
  #else
    delay(2000); // Pause for screen layout/orientation
  #endif // DISPLAY_BACKLIGHT
#endif // LOGO_TOP_WIDTH

  // One of the displays is configured to mirror on the X axis.  Simplifies
  // eyelid handling in the drawEye() function -- no need for distinct
  // L-to-R or R-to-L inner loops.  Just the X coordinate of the iris is
  // then reversed when drawing this eye, so they move the same.  Magic!
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
  const uint8_t mirrorTFT[]  = { 0x88, 0x28, 0x48, 0xE8 }; // Mirror+rotate
  digitalWrite(eyeInfo[0].select, LOW);
  digitalWrite(DISPLAY_DC, LOW);
  #ifdef ST77XX_MADCTL
    SPI.transfer(ST77XX_MADCTL); // Current TFT lib
  #else
    SPI.transfer(ST7735_MADCTL); // Older TFT lib
  #endif
  digitalWrite(DISPLAY_DC, HIGH);
  SPI.transfer(mirrorTFT[eyeInfo[0].rotation & 3]);
  digitalWrite(eyeInfo[0].select , HIGH);
#else // OLED
  const uint8_t rotateOLED[] = { 0x74, 0x77, 0x66, 0x65 },
                mirrorOLED[] = { 0x76, 0x67, 0x64, 0x75 }; // Mirror+rotate
  // If OLED, loop through ALL eyes and set up remap register
  // from either mirrorOLED[] (first eye) or rotateOLED[] (others).
  // The OLED library doesn't normally use the remap reg (TFT does).
  for(e=0; e<NUM_EYES; e++) {
    eye[e].display->writeCommand(SSD1351_CMD_SETREMAP);
    eye[e].display->writeData(e ?
      rotateOLED[eyeInfo[e].rotation & 3] :
      mirrorOLED[eyeInfo[e].rotation & 3]);
  }
#endif

#ifdef ARDUINO_ARCH_SAMD
  // Set up SPI DMA on SAMD boards:
  int                dmac_id;
  volatile uint32_t *data_reg;
  if(&PERIPH_SPI == &sercom0) {
    dmac_id  = SERCOM0_DMAC_ID_TX;
    data_reg = &SERCOM0->SPI.DATA.reg;
#if defined SERCOM1
  } else if(&PERIPH_SPI == &sercom1) {
    dmac_id  = SERCOM1_DMAC_ID_TX;
    data_reg = &SERCOM1->SPI.DATA.reg;
#endif
#if defined SERCOM2
  } else if(&PERIPH_SPI == &sercom2) {
    dmac_id  = SERCOM2_DMAC_ID_TX;
    data_reg = &SERCOM2->SPI.DATA.reg;
#endif
#if defined SERCOM3
  } else if(&PERIPH_SPI == &sercom3) {
    dmac_id  = SERCOM3_DMAC_ID_TX;
    data_reg = &SERCOM3->SPI.DATA.reg;
#endif
#if defined SERCOM4
  } else if(&PERIPH_SPI == &sercom4) {
    dmac_id  = SERCOM4_DMAC_ID_TX;
    data_reg = &SERCOM4->SPI.DATA.reg;
#endif
#if defined SERCOM5
  } else if(&PERIPH_SPI == &sercom5) {
    dmac_id  = SERCOM5_DMAC_ID_TX;
    data_reg = &SERCOM5->SPI.DATA.reg;
#endif
  }

  dma.allocate();
  dma.setTrigger(dmac_id);
  dma.setAction(DMA_TRIGGER_ACTON_BEAT);
  descriptor = dma.addDescriptor(
    NULL,               // move data
    (void *)data_reg,   // to here
    sizeof dmaBuf[0],   // this many...
    DMA_BEAT_SIZE_BYTE, // bytes/hword/words
    true,               // increment source addr?
    false);             // increment dest addr?
  dma.setCallback(dma_callback);

#endif // End SAMD-specific SPI DMA init

#ifdef DISPLAY_BACKLIGHT
  analogWrite(DISPLAY_BACKLIGHT, BACKLIGHT_MAX);
#endif

  startTime = millis(); // For frame-rate calculation
}


// EYE-RENDERING FUNCTION --------------------------------------------------

SPISettings settings(SPI_FREQ, MSBFIRST, SPI_MODE0);

void drawEye( // Renders one eye.  Inputs must be pre-clipped & valid.
  uint8_t  e,       // Eye array index; 0 or 1 for left/right
  uint32_t iScale,  // Scale factor for iris
  uint8_t  scleraX, // First pixel X offset into sclera image
  uint8_t  scleraY, // First pixel Y offset into sclera image
  uint8_t  uT,      // Upper eyelid threshold value
  uint8_t  lT) {    // Lower eyelid threshold value

  uint8_t  screenX, screenY, scleraXsave;
  int16_t  irisX, irisY;
  uint16_t p, a;
  uint32_t d;

  // Set up raw pixel dump to entire screen.  Although such writes can wrap
  // around automatically from end of rect back to beginning, the region is
  // reset on each frame here in case of an SPI glitch.
  SPI.beginTransaction(settings);
  digitalWrite(eyeInfo[e].select, LOW);                        // Chip select
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
  eye[e].display->setAddrWindow(0, 0, 128, 128);
#else // OLED
  eye[e].display->writeCommand(SSD1351_CMD_SETROW);    // Y range
  eye[e].display->writeData(0); eye[e].display->writeData(SCREEN_HEIGHT - 1);
  eye[e].display->writeCommand(SSD1351_CMD_SETCOLUMN); // X range
  eye[e].display->writeData(0); eye[e].display->writeData(SCREEN_WIDTH  - 1);
  eye[e].display->writeCommand(SSD1351_CMD_WRITERAM);  // Begin write
#endif
  digitalWrite(eyeInfo[e].select, LOW);                // Re-chip-select
  digitalWrite(DISPLAY_DC, HIGH);                      // Data mode
  // Now just issue raw 16-bit values for every pixel...

  scleraXsave = scleraX; // Save initial X value to reset on each line
  irisY       = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT) / 2;
  for(screenY=0; screenY<SCREEN_HEIGHT; screenY++, scleraY++, irisY++) {
#ifdef ARDUINO_ARCH_SAMD
    uint16_t *ptr = &dmaBuf[dmaIdx][0];
#endif
    scleraX = scleraXsave;
    irisX   = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
    for(screenX=0; screenX<SCREEN_WIDTH; screenX++, scleraX++, irisX++) {
      if((lower[screenY][screenX] <= lT) ||
         (upper[screenY][screenX] <= uT)) {             // Covered by eyelid
        p = 0;
      } else if((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
                (irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
        p = sclera[scleraY][scleraX];
      } else {                                          // Maybe iris...
        p = polar[irisY][irisX];                        // Polar angle/dist
        d = (iScale * (p & 0x7F)) / 128;                // Distance (Y)
        if(d < IRIS_MAP_HEIGHT) {                       // Within iris area
          a = (IRIS_MAP_WIDTH * (p >> 7)) / 512;        // Angle (X)
          p = iris[d][a];                               // Pixel = iris
        } else {                                        // Not in iris
          p = sclera[scleraY][scleraX];                 // Pixel = sclera
        }
      }
#ifdef ARDUINO_ARCH_SAMD
      *ptr++ = __builtin_bswap16(p); // DMA: store in scanline buffer
#else
      // SPI FIFO technique from Paul Stoffregen's ILI9341_t3 library:
      while(KINETISK_SPI0.SR & 0xC000); // Wait for space in FIFO
      KINETISK_SPI0.PUSHR = p | SPI_PUSHR_CTAS(1) | SPI_PUSHR_CONT;
#endif
    } // end column
#ifdef ARDUINO_ARCH_SAMD
    while(dma_busy); // Wait for prior DMA xfer to finish
    descriptor->SRCADDR.reg = (uint32_t)&dmaBuf[dmaIdx] + sizeof dmaBuf[0];
    dma_busy = true;
    dmaIdx   = 1 - dmaIdx;
    dma.startJob();
#endif
  } // end scanline

#ifdef ARDUINO_ARCH_SAMD
  while(dma_busy);  // Wait for last scanline to transmit
#else
  KINETISK_SPI0.SR |= SPI_SR_TCF;         // Clear transfer flag
  while((KINETISK_SPI0.SR & 0xF000) ||    // Wait for SPI FIFO to drain
       !(KINETISK_SPI0.SR & SPI_SR_TCF)); // Wait for last bit out
#endif

  digitalWrite(eyeInfo[e].select, HIGH);          // Deselect
  SPI.endTransaction();
}


// EYE ANIMATION -----------------------------------------------------------

const uint8_t ease[] = { // Ease in/out curve for eye movements 3*t^2-2*t^3
    0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  2,  2,  2,  3,   // T
    3,  3,  4,  4,  4,  5,  5,  6,  6,  7,  7,  8,  9,  9, 10, 10,   // h
   11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23,   // x
   24, 25, 26, 27, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39,   // 2
   40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58,   // A
   60, 61, 62, 63, 65, 66, 67, 69, 70, 72, 73, 74, 76, 77, 78, 80,   // l
   81, 83, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 98,100,101,103,   // e
  104,106,107,109,110,112,113,115,116,118,119,121,122,124,125,127,   // c
  128,130,131,133,134,136,137,139,140,142,143,145,146,148,149,151,   // J
  152,154,155,157,158,159,161,162,164,165,167,168,170,171,172,174,   // a
  175,177,178,179,181,182,183,185,186,188,189,190,192,193,194,195,   // c
  197,198,199,201,202,203,204,205,207,208,209,210,211,213,214,215,   // o
  216,217,218,219,220,221,222,224,225,226,227,228,228,229,230,231,   // b
  232,233,234,235,236,237,237,238,239,240,240,241,242,243,243,244,   // s
  245,245,246,246,247,248,248,249,249,250,250,251,251,251,252,252,   // o
  252,253,253,253,254,254,254,254,254,255,255,255,255,255,255,255 }; // n

void frame( // Process motion for a single frame of left or right eye
  uint16_t        iScale) {     // Iris scale (0-1023) passed in
  static uint32_t frames   = 0; // Used in frame rate calculation
  
  uint32_t        t = micros(); // Time at start of function

  if(!(++frames & 255)) { // Every 256 frames...
    uint32_t elapsed = (millis() - startTime) / 1000;
    if(elapsed) Serial.println(frames / elapsed); // Print FPS
  }

  for(uint8_t e=0; e<NUM_EYES; e++) {
    // X/Y movement
    // Autonomous X/Y eye motion
    // Periodically initiates motion to a new random point, random speed,
    // holds there for random period until next motion.
    int32_t dt = t - eye[e].move.eyeMoveStartTime;      // uS elapsed since last eye event
    if(eye[e].move.eyeInMotion) {                       // Currently moving?
      if(dt >= eye[e].move.eyeMoveDuration) {           // Time up?  Destination reached.
        eye[e].move.eyeInMotion      = false;           // Stop moving
        eye[e].move.eyeMoveDuration  = random(3000000); // 0-3 sec stop
        eye[e].move.eyeMoveStartTime = t;               // Save initial time of stop
        eye[e].move.eyeX = eye[e].move.eyeOldX = eye[e].move.eyeNewX;           // Save position
        eye[e].move.eyeY = eye[e].move.eyeOldY = eye[e].move.eyeNewY;
      } else { // Move time's not yet fully elapsed -- interpolate position
        int16_t easeVal = ease[255 * dt / eye[e].move.eyeMoveDuration] + 1;   // Ease curve
        eye[e].move.eyeX = eye[e].move.eyeOldX + (((eye[e].move.eyeNewX - eye[e].move.eyeOldX) * easeVal) / 256); // Interp X
        eye[e].move.eyeY = eye[e].move.eyeOldY + (((eye[e].move.eyeNewY - eye[e].move.eyeOldY) * easeVal) / 256); // and Y
      }
    } else {                                // Eye stopped
      eye[e].move.eyeX = eye[e].move.eyeOldX;
      eye[e].move.eyeY = eye[e].move.eyeOldY;
      if(dt > eye[e].move.eyeMoveDuration) {            // Time up?  Begin new move.
        int16_t  dx, dy;
        uint32_t d;
        do {                                // Pick new dest in circle
          eye[e].move.eyeNewX = random(1024);
          eye[e].move.eyeNewY = random(1024);
          dx      = (eye[e].move.eyeNewX * 2) - 1023;
          dy      = (eye[e].move.eyeNewY * 2) - 1023;
        } while((d = (dx * dx + dy * dy)) > (1023 * 1023)); // Keep trying
        eye[e].move.eyeMoveDuration  = random(72000, 144000); // ~1/14 - ~1/7 sec
        eye[e].move.eyeMoveStartTime = t;               // Save initial time of move
        eye[e].move.eyeInMotion      = true;            // Start move on next frame
      }
    }


  // Blinking

#ifdef AUTOBLINK
  // Similar to the autonomous eye movement above -- blink start times
  // and durations are random (within ranges).
  if((t - eye[e].blink.timeOfLastBlink) >= eye[e].blink.timeToNextBlink) { // Start new blink?
    eye[e].blink.timeOfLastBlink = t;
    eye[e].blink.duration = random(36000, 72000); // ~1/28 - ~1/14 sec
    // Set up durations for both eyes (if not already winking)
//    for(uint8_t e=0; e<NUM_EYES; e++) {
      if(eye[e].blink.state == NOBLINK) {
        eye[e].blink.state     = ENBLINK;
        eye[e].blink.startTime = t;
      }
//    }
    eye[e].blink.timeToNextBlink = eye[e].blink.duration * 3 + random(8000000);
  }
#endif

  if(eye[e].blink.state) { // Eye currently blinking?
    // Check if current blink state time has elapsed
    if((t - eye[e].blink.startTime) >= eye[e].blink.duration) {
      // Yes -- increment blink state, unless...
      if((eye[e].blink.state == ENBLINK) && ( // Enblinking and...
        ((eyeInfo[e].wink >= 0) &&
         digitalRead(eyeInfo[e].wink) == LOW) )) {
        // Don't advance state yet -- eye is held closed instead
      } else { // No buttons, or other state...
        if(++eye[e].blink.state > DEBLINK) { // Deblinking finished?
          eye[e].blink.state = NOBLINK;      // No longer blinking
        } else { // Advancing from ENBLINK to DEBLINK mode
          eye[e].blink.duration *= 2; // DEBLINK is 1/2 ENBLINK speed
          eye[e].blink.startTime = t;
        }
      }
    }
  } else { // Not currently blinking...check buttons!
    if((eyeInfo[e].wink >= 0) &&
       (digitalRead(eyeInfo[e].wink) == LOW)) { // Wink!
      eye[e].blink.state     = ENBLINK;
      eye[e].blink.startTime = t;
      eye[e].blink.duration  = random(45000, 90000);
    }
  }

  // Process motion, blinking and iris scale into renderable values

  // Iris scaling: remap from 0-1023 input to iris map height pixel units
  iScale = ((IRIS_MAP_HEIGHT + 1) * 1024) /
           (1024 - (iScale * (IRIS_MAP_HEIGHT - 1) / IRIS_MAP_HEIGHT));

  // Scale eye X/Y positions (0-1023) to pixel units used by drawEye()
  eye[e].move.eyeX = map(eye[e].move.eyeX, 0, 1023, 0, SCLERA_WIDTH  - 128);
  eye[e].move.eyeY = map(eye[e].move.eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128);
  if(e == 1) eye[e].move.eyeX = (SCLERA_WIDTH - 128) - eye[e].move.eyeX; // Mirrored display

  // Horizontal position is offset so that eyes are very slightly crossed
  // to appear fixated (converged) at a conversational distance.  Number
  // here was extracted from my posterior and not mathematically based.
  // I suppose one could get all clever with a range sensor, but for now...
  if(NUM_EYES > 1) eye[e].move.eyeX += 4;
  if(eye[e].move.eyeX > (SCLERA_WIDTH - 128)) eye[e].move.eyeX = (SCLERA_WIDTH - 128);

  // Eyelids are rendered using a brightness threshold image.  This same
  // map can be used to simplify another problem: making the upper eyelid
  // track the pupil (eyes tend to open only as much as needed -- e.g. look
  // down and the upper eyelid drops).  Just sample a point in the upper
  // lid map slightly above the pupil to determine the rendering threshold.
  uint8_t        lThreshold, n;
#ifdef TRACKING
  int16_t sampleX = SCLERA_WIDTH  / 2 - (eye[e].move.eyeX / 2), // Reduce X influence
          sampleY = SCLERA_HEIGHT / 2 - (eye[e].move.eyeY + IRIS_HEIGHT / 4);
  // Eyelid is slightly asymmetrical, so two readings are taken, averaged
  if(sampleY < 0) n = 0;
  else            n = (upper[sampleY][sampleX] +
                       upper[sampleY][SCREEN_WIDTH - 1 - sampleX]) / 2;
  eye[e].move.uThreshold = (eye[e].move.uThreshold * 3 + n) / 4; // Filter/soften motion
  // Lower eyelid doesn't track the same way, but seems to be pulled upward
  // by tension from the upper lid.
  lThreshold = 254 - eye[e].move.uThreshold;
#else // No tracking -- eyelids full open unless blink modifies them
  uThreshold = lThreshold = 0;
#endif

  // The upper/lower thresholds are then scaled relative to the current
  // blink position so that blinks work together with pupil tracking.
  if(eye[e].blink.state) { // Eye currently blinking?
    uint32_t s = (t - eye[e].blink.startTime);
    if(s >= eye[e].blink.duration) s = 255;   // At or past blink end
    else s = 255 * s / eye[e].blink.duration; // Mid-blink
    s          = (eye[e].blink.state == DEBLINK) ? 1 + s : 256 - s;
    n          = (eye[e].move.uThreshold * s + 254 * (257 - s)) / 256;
    lThreshold = (lThreshold * s + 254 * (257 - s)) / 256;
  } else {
    n          = eye[e].move.uThreshold;
  }

  // Pass all the derived values to the eye-rendering function:
  drawEye(e, iScale, eye[e].move.eyeX, eye[e].move.eyeY, n, lThreshold);
  }
}


// AUTONOMOUS IRIS SCALING (if no photocell or dial) -----------------------

#if !defined(LIGHT_PIN) || (LIGHT_PIN < 0)

// Autonomous iris motion uses a fractal behavior to similate both the major
// reaction of the eye plus the continuous smaller adjustments that occur.

uint16_t oldIris = (IRIS_MIN + IRIS_MAX) / 2, newIris;

void split( // Subdivides motion path into two sub-paths w/randimization
  int16_t  startValue, // Iris scale value (IRIS_MIN to IRIS_MAX) at start
  int16_t  endValue,   // Iris scale value at end
  uint32_t startTime,  // micros() at start
  int32_t  duration,   // Start-to-end time, in microseconds
  int16_t  range) {    // Allowable scale value variance when subdividing

  if(range >= 8) {     // Limit subdvision count, because recursion
    range    /= 2;     // Split range & time in half for subdivision,
    duration /= 2;     // then pick random center point within range:
    int16_t  midValue = (startValue + endValue - range) / 2 + random(range);
    uint32_t midTime  = startTime + duration;
    split(startValue, midValue, startTime, duration, range); // First half
    split(midValue  , endValue, midTime  , duration, range); // Second half
  } else {             // No more subdivisons, do iris motion...
    int32_t dt;        // Time (micros) since start of motion
    int16_t v;         // Interim value
    while((dt = (micros() - startTime)) < duration) {
      v = startValue + (((endValue - startValue) * dt) / duration);
      if(v < IRIS_MIN)      v = IRIS_MIN; // Clip just in case
      else if(v > IRIS_MAX) v = IRIS_MAX;
      frame(v);        // Draw frame w/interim iris scale value
    }
  }
}

#endif // !LIGHT_PIN


// MAIN LOOP -- runs continuously after setup() ----------------------------

void loop() {

#if defined(LIGHT_PIN) && (LIGHT_PIN >= 0) // Interactive iris

  int16_t v = analogRead(LIGHT_PIN);       // Raw dial/photocell reading
#ifdef LIGHT_PIN_FLIP
  v = 1023 - v;                            // Reverse reading from sensor
#endif
  if(v < LIGHT_MIN)      v = LIGHT_MIN;  // Clamp light sensor range
  else if(v > LIGHT_MAX) v = LIGHT_MAX;
  v -= LIGHT_MIN;  // 0 to (LIGHT_MAX - LIGHT_MIN)
#ifdef LIGHT_CURVE  // Apply gamma curve to sensor input?
  v = (int16_t)(pow((double)v / (double)(LIGHT_MAX - LIGHT_MIN),
    LIGHT_CURVE) * (double)(LIGHT_MAX - LIGHT_MIN));
#endif
  // And scale to iris range (IRIS_MAX is size at LIGHT_MIN)
  v = map(v, 0, (LIGHT_MAX - LIGHT_MIN), IRIS_MAX, IRIS_MIN);
#ifdef IRIS_SMOOTH // Filter input (gradual motion)
  static int16_t irisValue = (IRIS_MIN + IRIS_MAX) / 2;
  irisValue = ((irisValue * 15) + v) / 16;
  frame(irisValue);
#else // Unfiltered (immediate motion)
  frame(v);
#endif // IRIS_SMOOTH

#else  // Autonomous iris scaling -- invoke recursive function

  newIris = random(IRIS_MIN, IRIS_MAX);
  split(oldIris, newIris, micros(), 10000000L, IRIS_MAX - IRIS_MIN);
  oldIris = newIris;

#endif // LIGHT_PIN
}

Config:

// Pin selections here are based on the original Adafruit Learning System
// guide for the Teensy 3.x project.  Some of these pin numbers don't even
// exist on the smaller SAMD M0 & M4 boards, so you may need to make other
// selections:

// GRAPHICS SETTINGS (appearance of eye) -----------------------------------

// If using a SINGLE EYE, you might want this next line enabled, which
// uses a simpler "football-shaped" eye that's left/right symmetrical.
// Default shape includes the caruncle, creating distinct left/right eyes.
#ifdef ADAFRUIT_HALLOWING // Hallowing, with one eye, does this by default
  #define SYMMETRICAL_EYELID
#else                     // Otherwise your choice, standard is asymmetrical
  #define SYMMETRICAL_EYELID
#endif

// Enable ONE of these #includes -- HUGE graphics tables for various eyes:
#include "graphics/defaultEye.h"    // Standard human-ish hazel eye -OR-
//#include "graphics/dragonEye.h"   // Slit pupil fiery dragon/demon eye -OR-
//#include "graphics/noScleraEye.h" // Large iris, no sclera -OR-
//#include "graphics/goatEye.h"     // Horizontal pupil goat/Krampus eye -OR-
//#include "graphics/newtEye.h"     // Eye of newt

// Optional: enable this line for startup logo (screen test/orient):
#if !defined ADAFRUIT_HALLOWING     // Hallowing can't always fit logo+eye
  #include "graphics/logo.h"        // Otherwise your choice, if it fits
#endif

// EYE LIST ----------------------------------------------------------------

// This table contains ONE LINE PER EYE.  The table MUST be present with
// this name and contain ONE OR MORE lines.  Each line contains THREE items:
// a pin number for the corresponding TFT/OLED display's SELECT line, a pin
// pin number for that eye's "wink" button (or -1 if not used), and a screen
// rotation value (0-3) for that eye.

eyeInfo_t eyeInfo[] = {
#ifdef ADAFRUIT_HALLOWING
  { 10, -1, 2 }, // SINGLE EYE display-select and wink pins, rotate 180
#else
 // LEFT EYE display-select and wink pins, no rotation
  { 10, 2, 2 }, // RIGHT EYE display-select and wink pins, no rotation
  { 12, 2, 2 }
#endif
};

// DISPLAY HARDWARE SETTINGS (screen type & connections) -------------------

#ifdef ADAFRUIT_HALLOWING
  #include <Adafruit_ST7735.h> // TFT display library
  #define DISPLAY_DC       7  // Display data/command pin
  #define DISPLAY_RESET    11  // Display reset pin
  #define DISPLAY_BACKLIGHT 
  #define BACKLIGHT_MAX   128
#else
  // Enable ONE of these #includes to specify the display type being used
  //#include <Adafruit_SSD1351.h>  // OLED display library -OR-
  #include <Adafruit_ST7735.h> // TFT display library (enable one only)
  #define DISPLAY_DC        7    // Data/command pin for ALL displays
  #define DISPLAY_RESET     11    // Reset pin for ALL displays
#endif

#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_)
  #define SPI_FREQ 24000000    // TFT: use max SPI (clips to 12 MHz on M0)
#else // OLED
  #if !defined(ARDUINO_ARCH_SAMD) && (F_CPU <= 72000000)
    #define SPI_FREQ 24000000  // OLED: 24 MHz on 72 MHz Teensy only
  #else
    #define SPI_FREQ 12000000  // OLED: 12 MHz in all other cases
  #endif
#endif

// INPUT SETTINGS (for controlling eye motion) -----------------------------

// JOYSTICK_X_PIN and JOYSTICK_Y_PIN specify analog input pins for manually
// controlling the eye with an analog joystick.  If set to -1 or if not
// defined, the eye will move on its own.
// IRIS_PIN speficies an analog input pin for a photocell to make pupils
// react to light (or potentiometer for manual control).  If set to -1 or
// if not defined, the pupils will change on their own.
// BLINK_PIN specifies an input pin for a button (to ground) that will
// make any/all eyes blink.  If set to -1 or if not defined, the eyes will
// only blink if AUTOBLINK is defined, or if the eyeInfo[] table above
// includes wink button settings for each eye.

//#define JOYSTICK_X_PIN A0 // Analog pin for eye horiz pos (else auto)
//#define JOYSTICK_Y_PIN A1 // Analog pin for eye vert position (")
//#define JOYSTICK_X_FLIP   // If defined, reverse stick X axis
//#define JOYSTICK_Y_FLIP   // If defined, reverse stick Y axis
#define TRACKING            // If defined, eyelid tracks pupil
#define BLINK_PIN         1 // Pin for manual blink button (BOTH eyes)
#define AUTOBLINK           // If defined, eyes also blink autonomously
#ifdef ADAFRUIT_HALLOWING
  #define LIGHT_PIN      A1 // Hallowing light sensor pin
  #define LIGHT_CURVE  0.33 // Light sensor adjustment curve
  #define LIGHT_MIN      30 // Minimum useful reading from light sensor
  #define LIGHT_MAX     980 // Maximum useful reading from sensor
#else
  #define LIGHT_PIN      A2 // Photocell or potentiometer (else auto iris)
//#define LIGHT_PIN_FLIP    // If defined, reverse reading from dial/photocell
  #define LIGHT_MIN       0 // Lower reading from sensor
  #define LIGHT_MAX    1023 // Upper reading from sensor
#endif
#define IRIS_SMOOTH         // If enabled, filter input from IRIS_PIN
#if !defined(IRIS_MIN)      // Each eye might have its own MIN/MAX
  #define IRIS_MIN      120 // Iris size (0-1023) in brightest light
#endif
#if !defined(IRIS_MAX)
  #define IRIS_MAX      720 // Iris size (0-1023) in darkest light
#endif

Thanks.

I don't see any mention of the GC9A01 controller in your code.
Have you downloaded/installed GitHub - adafruit/Adafruit_GC9A01A: Adafruit_GFX-compatible library for GC9A01A display driver ?
Maybe start with the examples in that library, and a single display...

I did the example with both eyes one eye at a time and it worked. I'm assuming that I just don't have something correct in the coding then.

I added the #include_GC9A01A.h into the code and still only produces static.

// Uncanny eyes for Adafruit 1.5" OLED (product #1431) or 1.44" TFT LCD
// (#2088).  Works on PJRC Teensy 3.x and on Adafruit M0 and M4 boards
// (Feather, Metro, etc.).  This code uses features specific to these
// boards and WILL NOT work on normal Arduino or other boards!
//
// SEE FILE "config.h" FOR MOST CONFIGURATION (graphics, pins, display type,
// etc).  Probably won't need to edit THIS file unless you're doing some
// extremely custom modifications.
//
// Adafruit invests time and resources providing this open source code,
// please support Adafruit and open-source hardware by purchasing products
// from Adafruit!
//
// Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
// MIT license.  SPI FIFO insight from Paul Stoffregen's ILI9341_t3 library.
// Inspired by David Boccabella's (Marcwolf) hybrid servo/OLED eye concept.
//--------------------------------------------------------------------------

#include <SPI.h>
#include <Adafruit_GFX.h>
#include "Adafruit_GC9A01A.h"
#ifdef ARDUINO_ARCH_SAMD
  #include <Adafruit_ZeroDMA.h>
#endif

typedef struct {        // Struct is defined before including config.h --
  int8_t  select;       // pin numbers for each eye's screen select line
  int8_t  wink;         // and wink button (or -1 if none) specified there,
  uint8_t rotation;     // also display rotation.
} eyeInfo_t;

#include "config.h"     // ****** CONFIGURATION IS DONE IN HERE ******

#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_)
  typedef Adafruit_ST7735  displayType; // Using TFT display(s)
#else
  typedef Adafruit_SSD1351 displayType; // Using OLED display(s)
#endif

// A simple state machine is used to control eye blinks/winks:
#define NOBLINK 0       // Not currently engaged in a blink
#define ENBLINK 1       // Eyelid is currently closing
#define DEBLINK 2       // Eyelid is currently opening
typedef struct {
  uint8_t  state;       // NOBLINK/ENBLINK/DEBLINK
  uint32_t duration;    // Duration of blink state (micros)
  uint32_t startTime;   // Time (micros) of last state change
  uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L;
} eyeBlink;

typedef struct {
  boolean  eyeInMotion      = false;
  int16_t  eyeOldX=512, eyeOldY=512, eyeNewX=512, eyeNewY=512;
  uint32_t eyeMoveStartTime = 0L;
  int32_t  eyeMoveDuration  = 0L;
  int16_t         eyeX, eyeY;
  uint8_t uThreshold = 128;
} eyeMove;

#define NUM_EYES (sizeof eyeInfo / sizeof eyeInfo[0]) // config.h pin list

struct {                // One-per-eye structure
  displayType *display; // -> OLED/TFT object
  eyeBlink     blink;   // Current blink/wink state
  eyeMove      move;
} eye[NUM_EYES];

#ifdef ARDUINO_ARCH_SAMD
  // SAMD boards use DMA (Teensy uses SPI FIFO instead):
  // Two single-line 128-pixel buffers (16bpp) are used for DMA.
  // Though you'd think fewer larger transfers would improve speed,
  // multi-line buffering made no appreciable difference.
  uint16_t          dmaBuf[2][128];
  uint8_t           dmaIdx = 0; // Active DMA buffer # (alternate fill/send)
  Adafruit_ZeroDMA  dma;
  DmacDescriptor   *descriptor;

  // DMA transfer-in-progress indicator and callback
  static volatile bool dma_busy = false;
  static void dma_callback(Adafruit_ZeroDMA *dma) { dma_busy = false; }
#endif

uint32_t startTime;  // For FPS indicator


// INITIALIZATION -- runs once at startup ----------------------------------

void setup(void) {
  uint8_t e; // Eye index, 0 to NUM_EYES-1

  Serial.begin(115200);
  randomSeed(analogRead(A3)); // Seed random() from floating analog input

#ifdef DISPLAY_BACKLIGHT
  // Enable backlight pin, initially off
  pinMode(DISPLAY_BACKLIGHT, OUTPUT);
  digitalWrite(DISPLAY_BACKLIGHT, LOW);
#endif

  // Initialize eye objects based on eyeInfo list in config.h:
  for(e=0; e<NUM_EYES; e++) {
    eye[e].display     = new displayType(eyeInfo[e].select, DISPLAY_DC, -1);
    eye[e].blink.state = NOBLINK;
    // If project involves only ONE eye and NO other SPI devices, its
    // select line can be permanently tied to GND and corresponding pin
    // in config.h set to -1.  Best to use it though.
    if(eyeInfo[e].select >= 0) {
      pinMode(eyeInfo[e].select, OUTPUT);
      digitalWrite(eyeInfo[e].select, HIGH); // Deselect them all
    }
    // Also set up an individual eye-wink pin if defined:
    if(eyeInfo[e].wink >= 0) pinMode(eyeInfo[e].wink, INPUT_PULLUP);
  }
#if defined(BLINK_PIN) && (BLINK_PIN >= 0)
  pinMode(BLINK_PIN, INPUT_PULLUP); // Ditto for all-eyes blink pin
#endif

#if defined(DISPLAY_RESET) && (DISPLAY_RESET >= 0)
  // Because both displays share a common reset pin, -1 is passed to
  // the display constructor above to prevent the begin() function from
  // resetting both displays after one is initialized.  Instead, handle
  // the reset manually here to take care of both displays just once:
  pinMode(DISPLAY_RESET, OUTPUT);
  digitalWrite(DISPLAY_RESET, LOW);  delay(1);
  digitalWrite(DISPLAY_RESET, HIGH); delay(50);
  // Alternately, all display reset pin(s) could be connected to the
  // microcontroller reset, in which case DISPLAY_RESET should be set
  // to -1 or left undefined in config.h.
#endif

  // After all-displays reset, now call init/begin func for each display:
  for(e=0; e<NUM_EYES; e++) {
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
    eye[e].display->initR(INITR_144GREENTAB);
#else // OLED
    eye[e].display->begin();
#endif
    eye[e].display->setRotation(eyeInfo[e].rotation);
  }

#if defined(LOGO_TOP_WIDTH) || defined(COLOR_LOGO_WIDTH)
  // I noticed lots of folks getting right/left eyes flipped, or
  // installing upside-down, etc.  Logo split across screens may help:
  for(e=0; e<NUM_EYES; e++) { // Another pass, after all screen inits
    eye[e].display->fillScreen(0);
    #ifdef LOGO_TOP_WIDTH
      // Monochrome Adafruit logo is 2 mono bitmaps:
      eye[e].display->drawBitmap(NUM_EYES*64 - e*128 - 20,
        0, logo_top, LOGO_TOP_WIDTH, LOGO_TOP_HEIGHT, 0xFFFF);
      eye[e].display->drawBitmap(NUM_EYES*64 - e*128 - LOGO_BOTTOM_WIDTH/2,
        LOGO_TOP_HEIGHT, logo_bottom, LOGO_BOTTOM_WIDTH, LOGO_BOTTOM_HEIGHT,
        0xFFFF);
    #else
      // Color sponsor logo is one RGB bitmap:
      eye[e].display->fillScreen(color_logo[0]);
      eye[0].display->drawRGBBitmap(
        (eye[e].display->width()  - COLOR_LOGO_WIDTH ) / 2,
        (eye[e].display->height() - COLOR_LOGO_HEIGHT) / 2,
        color_logo, COLOR_LOGO_WIDTH, COLOR_LOGO_HEIGHT);
    #endif
    // After logo is drawn
  }
  #ifdef DISPLAY_BACKLIGHT
    int i;
    for(i=0; i<BACKLIGHT_MAX; i++) { // Fade logo in
      analogWrite(DISPLAY_BACKLIGHT, i);
      delay(2);
    }
    delay(1400); // Pause for screen layout/orientation
    for(; i>=0; i--) {
      analogWrite(DISPLAY_BACKLIGHT, i);
      delay(2);
    }
    for(e=0; e<NUM_EYES; e++) { // Clear display(s)
      eye[e].display->fillScreen(0);
    }
    delay(100);
  #else
    delay(2000); // Pause for screen layout/orientation
  #endif // DISPLAY_BACKLIGHT
#endif // LOGO_TOP_WIDTH

  // One of the displays is configured to mirror on the X axis.  Simplifies
  // eyelid handling in the drawEye() function -- no need for distinct
  // L-to-R or R-to-L inner loops.  Just the X coordinate of the iris is
  // then reversed when drawing this eye, so they move the same.  Magic!
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
  const uint8_t mirrorTFT[]  = { 0x88, 0x28, 0x48, 0xE8 }; // Mirror+rotate
  digitalWrite(eyeInfo[0].select, LOW);
  digitalWrite(DISPLAY_DC, LOW);
  #ifdef ST77XX_MADCTL
    SPI.transfer(ST77XX_MADCTL); // Current TFT lib
  #else
    SPI.transfer(ST7735_MADCTL); // Older TFT lib
  #endif
  digitalWrite(DISPLAY_DC, HIGH);
  SPI.transfer(mirrorTFT[eyeInfo[0].rotation & 3]);
  digitalWrite(eyeInfo[0].select , HIGH);
#else // OLED
  const uint8_t rotateOLED[] = { 0x74, 0x77, 0x66, 0x65 },
                mirrorOLED[] = { 0x76, 0x67, 0x64, 0x75 }; // Mirror+rotate
  // If OLED, loop through ALL eyes and set up remap register
  // from either mirrorOLED[] (first eye) or rotateOLED[] (others).
  // The OLED library doesn't normally use the remap reg (TFT does).
  for(e=0; e<NUM_EYES; e++) {
    eye[e].display->writeCommand(SSD1351_CMD_SETREMAP);
    eye[e].display->writeData(e ?
      rotateOLED[eyeInfo[e].rotation & 3] :
      mirrorOLED[eyeInfo[e].rotation & 3]);
  }
#endif

#ifdef ARDUINO_ARCH_SAMD
  // Set up SPI DMA on SAMD boards:
  int                dmac_id;
  volatile uint32_t *data_reg;
  if(&PERIPH_SPI == &sercom0) {
    dmac_id  = SERCOM0_DMAC_ID_TX;
    data_reg = &SERCOM0->SPI.DATA.reg;
#if defined SERCOM1
  } else if(&PERIPH_SPI == &sercom1) {
    dmac_id  = SERCOM1_DMAC_ID_TX;
    data_reg = &SERCOM1->SPI.DATA.reg;
#endif
#if defined SERCOM2
  } else if(&PERIPH_SPI == &sercom2) {
    dmac_id  = SERCOM2_DMAC_ID_TX;
    data_reg = &SERCOM2->SPI.DATA.reg;
#endif
#if defined SERCOM3
  } else if(&PERIPH_SPI == &sercom3) {
    dmac_id  = SERCOM3_DMAC_ID_TX;
    data_reg = &SERCOM3->SPI.DATA.reg;
#endif
#if defined SERCOM4
  } else if(&PERIPH_SPI == &sercom4) {
    dmac_id  = SERCOM4_DMAC_ID_TX;
    data_reg = &SERCOM4->SPI.DATA.reg;
#endif
#if defined SERCOM5
  } else if(&PERIPH_SPI == &sercom5) {
    dmac_id  = SERCOM5_DMAC_ID_TX;
    data_reg = &SERCOM5->SPI.DATA.reg;
#endif
  }

  dma.allocate();
  dma.setTrigger(dmac_id);
  dma.setAction(DMA_TRIGGER_ACTON_BEAT);
  descriptor = dma.addDescriptor(
    NULL,               // move data
    (void *)data_reg,   // to here
    sizeof dmaBuf[0],   // this many...
    DMA_BEAT_SIZE_BYTE, // bytes/hword/words
    true,               // increment source addr?
    false);             // increment dest addr?
  dma.setCallback(dma_callback);

#endif // End SAMD-specific SPI DMA init

#ifdef DISPLAY_BACKLIGHT
  analogWrite(DISPLAY_BACKLIGHT, BACKLIGHT_MAX);
#endif

  startTime = millis(); // For frame-rate calculation
}


// EYE-RENDERING FUNCTION --------------------------------------------------

SPISettings settings(SPI_FREQ, MSBFIRST, SPI_MODE0);

void drawEye( // Renders one eye.  Inputs must be pre-clipped & valid.
  uint8_t  e,       // Eye array index; 0 or 1 for left/right
  uint32_t iScale,  // Scale factor for iris
  uint8_t  scleraX, // First pixel X offset into sclera image
  uint8_t  scleraY, // First pixel Y offset into sclera image
  uint8_t  uT,      // Upper eyelid threshold value
  uint8_t  lT) {    // Lower eyelid threshold value

  uint8_t  screenX, screenY, scleraXsave;
  int16_t  irisX, irisY;
  uint16_t p, a;
  uint32_t d;

  // Set up raw pixel dump to entire screen.  Although such writes can wrap
  // around automatically from end of rect back to beginning, the region is
  // reset on each frame here in case of an SPI glitch.
  SPI.beginTransaction(settings);
  digitalWrite(eyeInfo[e].select, LOW);                        // Chip select
#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
  eye[e].display->setAddrWindow(0, 0, 128, 128);
#else // OLED
  eye[e].display->writeCommand(SSD1351_CMD_SETROW);    // Y range
  eye[e].display->writeData(0); eye[e].display->writeData(SCREEN_HEIGHT - 1);
  eye[e].display->writeCommand(SSD1351_CMD_SETCOLUMN); // X range
  eye[e].display->writeData(0); eye[e].display->writeData(SCREEN_WIDTH  - 1);
  eye[e].display->writeCommand(SSD1351_CMD_WRITERAM);  // Begin write
#endif
  digitalWrite(eyeInfo[e].select, LOW);                // Re-chip-select
  digitalWrite(DISPLAY_DC, HIGH);                      // Data mode
  // Now just issue raw 16-bit values for every pixel...

  scleraXsave = scleraX; // Save initial X value to reset on each line
  irisY       = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT) / 2;
  for(screenY=0; screenY<SCREEN_HEIGHT; screenY++, scleraY++, irisY++) {
#ifdef ARDUINO_ARCH_SAMD
    uint16_t *ptr = &dmaBuf[dmaIdx][0];
#endif
    scleraX = scleraXsave;
    irisX   = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
    for(screenX=0; screenX<SCREEN_WIDTH; screenX++, scleraX++, irisX++) {
      if((lower[screenY][screenX] <= lT) ||
         (upper[screenY][screenX] <= uT)) {             // Covered by eyelid
        p = 0;
      } else if((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
                (irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
        p = sclera[scleraY][scleraX];
      } else {                                          // Maybe iris...
        p = polar[irisY][irisX];                        // Polar angle/dist
        d = (iScale * (p & 0x7F)) / 128;                // Distance (Y)
        if(d < IRIS_MAP_HEIGHT) {                       // Within iris area
          a = (IRIS_MAP_WIDTH * (p >> 7)) / 512;        // Angle (X)
          p = iris[d][a];                               // Pixel = iris
        } else {                                        // Not in iris
          p = sclera[scleraY][scleraX];                 // Pixel = sclera
        }
      }
#ifdef ARDUINO_ARCH_SAMD
      *ptr++ = __builtin_bswap16(p); // DMA: store in scanline buffer
#else
      // SPI FIFO technique from Paul Stoffregen's ILI9341_t3 library:
      while(KINETISK_SPI0.SR & 0xC000); // Wait for space in FIFO
      KINETISK_SPI0.PUSHR = p | SPI_PUSHR_CTAS(1) | SPI_PUSHR_CONT;
#endif
    } // end column
#ifdef ARDUINO_ARCH_SAMD
    while(dma_busy); // Wait for prior DMA xfer to finish
    descriptor->SRCADDR.reg = (uint32_t)&dmaBuf[dmaIdx] + sizeof dmaBuf[0];
    dma_busy = true;
    dmaIdx   = 1 - dmaIdx;
    dma.startJob();
#endif
  } // end scanline

#ifdef ARDUINO_ARCH_SAMD
  while(dma_busy);  // Wait for last scanline to transmit
#else
  KINETISK_SPI0.SR |= SPI_SR_TCF;         // Clear transfer flag
  while((KINETISK_SPI0.SR & 0xF000) ||    // Wait for SPI FIFO to drain
       !(KINETISK_SPI0.SR & SPI_SR_TCF)); // Wait for last bit out
#endif

  digitalWrite(eyeInfo[e].select, HIGH);          // Deselect
  SPI.endTransaction();
}


// EYE ANIMATION -----------------------------------------------------------

const uint8_t ease[] = { // Ease in/out curve for eye movements 3*t^2-2*t^3
    0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  2,  2,  2,  3,   // T
    3,  3,  4,  4,  4,  5,  5,  6,  6,  7,  7,  8,  9,  9, 10, 10,   // h
   11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23,   // x
   24, 25, 26, 27, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39,   // 2
   40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58,   // A
   60, 61, 62, 63, 65, 66, 67, 69, 70, 72, 73, 74, 76, 77, 78, 80,   // l
   81, 83, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 98,100,101,103,   // e
  104,106,107,109,110,112,113,115,116,118,119,121,122,124,125,127,   // c
  128,130,131,133,134,136,137,139,140,142,143,145,146,148,149,151,   // J
  152,154,155,157,158,159,161,162,164,165,167,168,170,171,172,174,   // a
  175,177,178,179,181,182,183,185,186,188,189,190,192,193,194,195,   // c
  197,198,199,201,202,203,204,205,207,208,209,210,211,213,214,215,   // o
  216,217,218,219,220,221,222,224,225,226,227,228,228,229,230,231,   // b
  232,233,234,235,236,237,237,238,239,240,240,241,242,243,243,244,   // s
  245,245,246,246,247,248,248,249,249,250,250,251,251,251,252,252,   // o
  252,253,253,253,254,254,254,254,254,255,255,255,255,255,255,255 }; // n

void frame( // Process motion for a single frame of left or right eye
  uint16_t        iScale) {     // Iris scale (0-1023) passed in
  static uint32_t frames   = 0; // Used in frame rate calculation
  
  uint32_t        t = micros(); // Time at start of function

  if(!(++frames & 255)) { // Every 256 frames...
    uint32_t elapsed = (millis() - startTime) / 1000;
    if(elapsed) Serial.println(frames / elapsed); // Print FPS
  }

  for(uint8_t e=0; e<NUM_EYES; e++) {
    // X/Y movement
    // Autonomous X/Y eye motion
    // Periodically initiates motion to a new random point, random speed,
    // holds there for random period until next motion.
    int32_t dt = t - eye[e].move.eyeMoveStartTime;      // uS elapsed since last eye event
    if(eye[e].move.eyeInMotion) {                       // Currently moving?
      if(dt >= eye[e].move.eyeMoveDuration) {           // Time up?  Destination reached.
        eye[e].move.eyeInMotion      = false;           // Stop moving
        eye[e].move.eyeMoveDuration  = random(3000000); // 0-3 sec stop
        eye[e].move.eyeMoveStartTime = t;               // Save initial time of stop
        eye[e].move.eyeX = eye[e].move.eyeOldX = eye[e].move.eyeNewX;           // Save position
        eye[e].move.eyeY = eye[e].move.eyeOldY = eye[e].move.eyeNewY;
      } else { // Move time's not yet fully elapsed -- interpolate position
        int16_t easeVal = ease[255 * dt / eye[e].move.eyeMoveDuration] + 1;   // Ease curve
        eye[e].move.eyeX = eye[e].move.eyeOldX + (((eye[e].move.eyeNewX - eye[e].move.eyeOldX) * easeVal) / 256); // Interp X
        eye[e].move.eyeY = eye[e].move.eyeOldY + (((eye[e].move.eyeNewY - eye[e].move.eyeOldY) * easeVal) / 256); // and Y
      }
    } else {                                // Eye stopped
      eye[e].move.eyeX = eye[e].move.eyeOldX;
      eye[e].move.eyeY = eye[e].move.eyeOldY;
      if(dt > eye[e].move.eyeMoveDuration) {            // Time up?  Begin new move.
        int16_t  dx, dy;
        uint32_t d;
        do {                                // Pick new dest in circle
          eye[e].move.eyeNewX = random(1024);
          eye[e].move.eyeNewY = random(1024);
          dx      = (eye[e].move.eyeNewX * 2) - 1023;
          dy      = (eye[e].move.eyeNewY * 2) - 1023;
        } while((d = (dx * dx + dy * dy)) > (1023 * 1023)); // Keep trying
        eye[e].move.eyeMoveDuration  = random(72000, 144000); // ~1/14 - ~1/7 sec
        eye[e].move.eyeMoveStartTime = t;               // Save initial time of move
        eye[e].move.eyeInMotion      = true;            // Start move on next frame
      }
    }


  // Blinking

#ifdef AUTOBLINK
  // Similar to the autonomous eye movement above -- blink start times
  // and durations are random (within ranges).
  if((t - eye[e].blink.timeOfLastBlink) >= eye[e].blink.timeToNextBlink) { // Start new blink?
    eye[e].blink.timeOfLastBlink = t;
    eye[e].blink.duration = random(36000, 72000); // ~1/28 - ~1/14 sec
    // Set up durations for both eyes (if not already winking)
//    for(uint8_t e=0; e<NUM_EYES; e++) {
      if(eye[e].blink.state == NOBLINK) {
        eye[e].blink.state     = ENBLINK;
        eye[e].blink.startTime = t;
      }
//    }
    eye[e].blink.timeToNextBlink = eye[e].blink.duration * 3 + random(8000000);
  }
#endif

  if(eye[e].blink.state) { // Eye currently blinking?
    // Check if current blink state time has elapsed
    if((t - eye[e].blink.startTime) >= eye[e].blink.duration) {
      // Yes -- increment blink state, unless...
      if((eye[e].blink.state == ENBLINK) && ( // Enblinking and...
        ((eyeInfo[e].wink >= 0) &&
         digitalRead(eyeInfo[e].wink) == LOW) )) {
        // Don't advance state yet -- eye is held closed instead
      } else { // No buttons, or other state...
        if(++eye[e].blink.state > DEBLINK) { // Deblinking finished?
          eye[e].blink.state = NOBLINK;      // No longer blinking
        } else { // Advancing from ENBLINK to DEBLINK mode
          eye[e].blink.duration *= 2; // DEBLINK is 1/2 ENBLINK speed
          eye[e].blink.startTime = t;
        }
      }
    }
  } else { // Not currently blinking...check buttons!
    if((eyeInfo[e].wink >= 0) &&
       (digitalRead(eyeInfo[e].wink) == LOW)) { // Wink!
      eye[e].blink.state     = ENBLINK;
      eye[e].blink.startTime = t;
      eye[e].blink.duration  = random(45000, 90000);
    }
  }

  // Process motion, blinking and iris scale into renderable values

  // Iris scaling: remap from 0-1023 input to iris map height pixel units
  iScale = ((IRIS_MAP_HEIGHT + 1) * 1024) /
           (1024 - (iScale * (IRIS_MAP_HEIGHT - 1) / IRIS_MAP_HEIGHT));

  // Scale eye X/Y positions (0-1023) to pixel units used by drawEye()
  eye[e].move.eyeX = map(eye[e].move.eyeX, 0, 1023, 0, SCLERA_WIDTH  - 128);
  eye[e].move.eyeY = map(eye[e].move.eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128);
  if(e == 1) eye[e].move.eyeX = (SCLERA_WIDTH - 128) - eye[e].move.eyeX; // Mirrored display

  // Horizontal position is offset so that eyes are very slightly crossed
  // to appear fixated (converged) at a conversational distance.  Number
  // here was extracted from my posterior and not mathematically based.
  // I suppose one could get all clever with a range sensor, but for now...
  if(NUM_EYES > 1) eye[e].move.eyeX += 4;
  if(eye[e].move.eyeX > (SCLERA_WIDTH - 128)) eye[e].move.eyeX = (SCLERA_WIDTH - 128);

  // Eyelids are rendered using a brightness threshold image.  This same
  // map can be used to simplify another problem: making the upper eyelid
  // track the pupil (eyes tend to open only as much as needed -- e.g. look
  // down and the upper eyelid drops).  Just sample a point in the upper
  // lid map slightly above the pupil to determine the rendering threshold.
  uint8_t        lThreshold, n;
#ifdef TRACKING
  int16_t sampleX = SCLERA_WIDTH  / 2 - (eye[e].move.eyeX / 2), // Reduce X influence
          sampleY = SCLERA_HEIGHT / 2 - (eye[e].move.eyeY + IRIS_HEIGHT / 4);
  // Eyelid is slightly asymmetrical, so two readings are taken, averaged
  if(sampleY < 0) n = 0;
  else            n = (upper[sampleY][sampleX] +
                       upper[sampleY][SCREEN_WIDTH - 1 - sampleX]) / 2;
  eye[e].move.uThreshold = (eye[e].move.uThreshold * 3 + n) / 4; // Filter/soften motion
  // Lower eyelid doesn't track the same way, but seems to be pulled upward
  // by tension from the upper lid.
  lThreshold = 254 - eye[e].move.uThreshold;
#else // No tracking -- eyelids full open unless blink modifies them
  uThreshold = lThreshold = 0;
#endif

  // The upper/lower thresholds are then scaled relative to the current
  // blink position so that blinks work together with pupil tracking.
  if(eye[e].blink.state) { // Eye currently blinking?
    uint32_t s = (t - eye[e].blink.startTime);
    if(s >= eye[e].blink.duration) s = 255;   // At or past blink end
    else s = 255 * s / eye[e].blink.duration; // Mid-blink
    s          = (eye[e].blink.state == DEBLINK) ? 1 + s : 256 - s;
    n          = (eye[e].move.uThreshold * s + 254 * (257 - s)) / 256;
    lThreshold = (lThreshold * s + 254 * (257 - s)) / 256;
  } else {
    n          = eye[e].move.uThreshold;
  }

  // Pass all the derived values to the eye-rendering function:
  drawEye(e, iScale, eye[e].move.eyeX, eye[e].move.eyeY, n, lThreshold);
  }
}


// AUTONOMOUS IRIS SCALING (if no photocell or dial) -----------------------

#if !defined(LIGHT_PIN) || (LIGHT_PIN < 0)

// Autonomous iris motion uses a fractal behavior to similate both the major
// reaction of the eye plus the continuous smaller adjustments that occur.

uint16_t oldIris = (IRIS_MIN + IRIS_MAX) / 2, newIris;

void split( // Subdivides motion path into two sub-paths w/randimization
  int16_t  startValue, // Iris scale value (IRIS_MIN to IRIS_MAX) at start
  int16_t  endValue,   // Iris scale value at end
  uint32_t startTime,  // micros() at start
  int32_t  duration,   // Start-to-end time, in microseconds
  int16_t  range) {    // Allowable scale value variance when subdividing

  if(range >= 8) {     // Limit subdvision count, because recursion
    range    /= 2;     // Split range & time in half for subdivision,
    duration /= 2;     // then pick random center point within range:
    int16_t  midValue = (startValue + endValue - range) / 2 + random(range);
    uint32_t midTime  = startTime + duration;
    split(startValue, midValue, startTime, duration, range); // First half
    split(midValue  , endValue, midTime  , duration, range); // Second half
  } else {             // No more subdivisons, do iris motion...
    int32_t dt;        // Time (micros) since start of motion
    int16_t v;         // Interim value
    while((dt = (micros() - startTime)) < duration) {
      v = startValue + (((endValue - startValue) * dt) / duration);
      if(v < IRIS_MIN)      v = IRIS_MIN; // Clip just in case
      else if(v > IRIS_MAX) v = IRIS_MAX;
      frame(v);        // Draw frame w/interim iris scale value
    }
  }
}

#endif // !LIGHT_PIN


// MAIN LOOP -- runs continuously after setup() ----------------------------

void loop() {

#if defined(LIGHT_PIN) && (LIGHT_PIN >= 0) // Interactive iris

  int16_t v = analogRead(LIGHT_PIN);       // Raw dial/photocell reading
#ifdef LIGHT_PIN_FLIP
  v = 1023 - v;                            // Reverse reading from sensor
#endif
  if(v < LIGHT_MIN)      v = LIGHT_MIN;  // Clamp light sensor range
  else if(v > LIGHT_MAX) v = LIGHT_MAX;
  v -= LIGHT_MIN;  // 0 to (LIGHT_MAX - LIGHT_MIN)
#ifdef LIGHT_CURVE  // Apply gamma curve to sensor input?
  v = (int16_t)(pow((double)v / (double)(LIGHT_MAX - LIGHT_MIN),
    LIGHT_CURVE) * (double)(LIGHT_MAX - LIGHT_MIN));
#endif
  // And scale to iris range (IRIS_MAX is size at LIGHT_MIN)
  v = map(v, 0, (LIGHT_MAX - LIGHT_MIN), IRIS_MAX, IRIS_MIN);
#ifdef IRIS_SMOOTH // Filter input (gradual motion)
  static int16_t irisValue = (IRIS_MIN + IRIS_MAX) / 2;
  irisValue = ((irisValue * 15) + v) / 16;
  frame(irisValue);
#else // Unfiltered (immediate motion)
  frame(v);
#endif // IRIS_SMOOTH

#else  // Autonomous iris scaling -- invoke recursive function

  newIris = random(IRIS_MIN, IRIS_MAX);
  split(oldIris, newIris, micros(), 10000000L, IRIS_MAX - IRIS_MIN);
  oldIris = newIris;

#endif // LIGHT_PIN
}```

I'm assuming this is the portion that would let me change the pinouts for the displays. I have them set as I did with the library example

// EYE LIST ----------------------------------------------------------------

// This table contains ONE LINE PER EYE.  The table MUST be present with
// this name and contain ONE OR MORE lines.  Each line contains THREE items:
// a pin number for the corresponding TFT/OLED display's SELECT line, a pin
// pin number for that eye's "wink" button (or -1 if not used), and a screen
// rotation value (0-3) for that eye.

eyeInfo_t eyeInfo[] = {
#ifdef ADAFRUIT_HALLOWING
  { 10, -1, 2 }, // SINGLE EYE display-select and wink pins, rotate 180
#else
 // LEFT EYE display-select and wink pins, no rotation
  { 10, 2, 2 }, // RIGHT EYE display-select and wink pins, no rotation
  { 12, 2, 2 }
#endif
};

// DISPLAY HARDWARE SETTINGS (screen type & connections) -------------------

#ifdef ADAFRUIT_HALLOWING
  #include <Adafruit_ST7735.h> // TFT display library
  #define DISPLAY_DC       7  // Display data/command pin
  #define DISPLAY_RESET    11  // Display reset pin
  #define DISPLAY_BACKLIGHT 
  #define BACKLIGHT_MAX   128
#else
  // Enable ONE of these #includes to specify the display type being used
  //#include <Adafruit_SSD1351.h>  // OLED display library -OR-
  #include <Adafruit_ST7735.h> // TFT display library (enable one only)
  #define DISPLAY_DC        7    // Data/command pin for ALL displays
  #define DISPLAY_RESET     11    // Reset pin for ALL displays
#endif

#if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_)
  #define SPI_FREQ 24000000    // TFT: use max SPI (clips to 12 MHz on M0)
#else // OLED
  #if !defined(ARDUINO_ARCH_SAMD) && (F_CPU <= 72000000)
    #define SPI_FREQ 24000000  // OLED: 24 MHz on 72 MHz Teensy only
  #else
    #define SPI_FREQ 12000000  // OLED: 12 MHz in all other cases
  #endif
#endif

I believe you'll need a

  typedef Adafruit_GC9A01A displayType;

somewhere...

Sounds simple enough, I'll figure out where it goes

So I found out that it requires a bunch of rewriting of code in order to get the uncanny eyes working with GC9A01s due to the difference in libraries and controllers I did find an example in the GC9A01 library though which apparently is for uncanny eyes.

#include <elapsedMillis.h>

//This example is configured for using 2 displays on SPI and SPI
//using a ST7789 240x240 display without a CS pin.
//If using a display with a CS pin you can change pin configuration
//in config.h
#define USE_ASYNC_UPDATES

// Define if you wish to see debug information on the ST7789 displays
//#define DEBUG_ST7789

// Define if you wish to debug memory usage.  Only works on T4.x
//#define DEBUG_MEMORY

#define BUTTON_ISR 7
//--------------------------------------------------------------------------
// Uncanny eyes for Adafruit 1.5" OLED (product #1431) or 1.44" TFT LCD
// (#2088).  Works on PJRC Teensy 3.x and on Adafruit M0 and M4 boards
// (Feather, Metro, etc.).  This code uses features specific to these
// boards and WILL NOT work on normal Arduino or other boards!
//
// SEE FILE "config.h" FOR MOST CONFIGURATION (graphics, pins, display type,
// etc).  Probably won't need to edit THIS file unless you're doing some
// extremely custom modifications.
//
// Adafruit invests time and resources providing this open source code,
// please support Adafruit and open-source hardware by purchasing products
// from Adafruit!
//
// Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
// MIT license.  SPI FIFO insight from Paul Stoffregen's ILI9341_t3 library.
// Inspired by David Boccabella's (Marcwolf) hybrid servo/OLED eye concept.
//--------------------------------------------------------------------------

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <GC9A01A_t3n.h>

typedef struct {        // Struct is defined before including config.h --
  //int8_t  select;       // pin numbers for each eye's screen select line
  int8_t  cs;            // Chip select pin.
  int8_t  dc;            // DC pin
  int8_t  mosi;         // mosi
  int8_t  sck;          // sck pin
  int8_t  rst;          // reset pin
  int8_t  wink;         // and wink button (or -1 if none) specified there,
  uint8_t rotation;     // also display rotation.
  uint8_t init_option;  // option for Init
} eyeInfo_t;

typedef GC9A01A_t3n displayType; // Using TFT display(s)
#define DISPLAY_SIZE 240

#include "config.h"     // ****** CONFIGURATION IS DONE IN HERE ******

#define RGBColor(r, g, b) GC9A01A_t3n::Color565(r, g, b)

// A simple state machine is used to control eye blinks/winks:
#define NOBLINK 0       // Not currently engaged in a blink
#define ENBLINK 1       // Eyelid is currently closing
#define DEBLINK 2       // Eyelid is currently opening
typedef struct {
  uint8_t  state;       // NOBLINK/ENBLINK/DEBLINK
  uint32_t duration;    // Duration of blink state (micros)
  uint32_t startTime;   // Time (micros) of last state change
} eyeBlink;

#define NUM_EYES (sizeof eyeInfo / sizeof eyeInfo[0]) // config.h pin list

struct {                // One-per-eye structure
  displayType *display; // -> OLED/TFT object
  eyeBlink     blink;   // Current blink/wink state
} eye[NUM_EYES];


uint32_t startTime;  // For FPS indicator


// INITIALIZATION -- runs once at startup ----------------------------------

void setup(void) {
  uint8_t e; // Eye index, 0 to NUM_EYES-1
  Serial.begin(115200);
  while (!Serial && millis() < 2000 );
  delay(500);
  DumpMemoryInfo();
  Serial.println("Init");
  Serial.flush();
  randomSeed(analogRead(A3)); // Seed random() from floating analog input

#ifdef DISPLAY_BACKLIGHT
  // Enable backlight pin, initially off
  Serial.println("Backlight off");
  pinMode(DISPLAY_BACKLIGHT, OUTPUT);
  digitalWrite(DISPLAY_BACKLIGHT, LOW);
#endif

  // Initialize eye objects based on eyeInfo list in config.h:
  for (e = 0; e < NUM_EYES; e++) {
    Serial.print("Create display #"); Serial.println(e);
    //eye[e].display     = new displayType(&TFT_SPI, eyeInfo[e].cs,
    //                       DISPLAY_DC, -1);
    //for SPI
    //(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
    eye[e].display = new displayType(eyeInfo[e].cs, eyeInfo[e].dc, eyeInfo[e].rst,
                                     eyeInfo[e].mosi, eyeInfo[e].sck);
    eye[e].blink.state = NOBLINK;
    // If project involves only ONE eye and NO other SPI devices, its
    // select line can be permanently tied to GND and corresponding pin
    // in config.h set to -1.  Best to use it though.
    if (eyeInfo[e].cs >= 0) {
      pinMode(eyeInfo[e].cs, OUTPUT);
      digitalWrite(eyeInfo[e].cs, HIGH); // Deselect them all
    }
    // Also set up an individual eye-wink pin if defined:
    if (eyeInfo[e].wink >= 0) pinMode(eyeInfo[e].wink, INPUT_PULLUP);
  }
#if defined(BLINK_PIN) && (BLINK_PIN >= 0)
  pinMode(BLINK_PIN, INPUT_PULLUP); // Ditto for all-eyes blink pin
#endif

#if defined(DISPLAY_RESET) && (DISPLAY_RESET >= 0)
  // Because both displays share a common reset pin, -1 is passed to
  // the display constructor above to prevent the begin() function from
  // resetting both displays after one is initialized.  Instead, handle
  // the reset manually here to take care of both displays just once:
  Serial.println("Reset displays");
  pinMode(DISPLAY_RESET, OUTPUT);
  digitalWrite(DISPLAY_RESET, LOW);  delay(1);
  digitalWrite(DISPLAY_RESET, HIGH); delay(50);
  // Alternately, all display reset pin(s) could be connected to the
  // microcontroller reset, in which case DISPLAY_RESET should be set
  // to -1 or left undefined in config.h.
#endif

  // After all-displays reset, now call init/begin func for each display:
  for (e = 0; e < NUM_EYES; e++) {
    eye[e].display->begin();
    Serial.print("Init ST77xx display #"); Serial.println(e);
    Serial.println("Rotate");
    eye[e].display->setRotation(eyeInfo[e].rotation);
  }
  Serial.println("done");

#if defined(LOGO_TOP_WIDTH) || defined(COLOR_LOGO_WIDTH)
  Serial.println("Display logo");
  // I noticed lots of folks getting right/left eyes flipped, or
  // installing upside-down, etc.  Logo split across screens may help:
  for (e = 0; e < NUM_EYES; e++) { // Another pass, after all screen inits
    eye[e].display->fillScreen(0);
#ifdef LOGO_TOP_WIDTH
    // Monochrome Adafruit logo is 2 mono bitmaps:
    eye[e].display->drawBitmap(NUM_EYES * DISPLAY_SIZE / 2 - e * DISPLAY_SIZE - 20,
                               0, logo_top, LOGO_TOP_WIDTH, LOGO_TOP_HEIGHT, 0xFFFF);
    eye[e].display->drawBitmap(NUM_EYES * DISPLAY_SIZE / 2 - e * DISPLAY_SIZE - LOGO_BOTTOM_WIDTH / 2,
                               LOGO_TOP_HEIGHT, logo_bottom, LOGO_BOTTOM_WIDTH, LOGO_BOTTOM_HEIGHT,
                               0xFFFF);
#else
    // Color sponsor logo is one RGB bitmap:
    eye[e].display->fillScreen(color_logo[0]);
    eye[0].display->drawRGBBitmap(
      (eye[e].display->width()  - COLOR_LOGO_WIDTH ) / 2,
      (eye[e].display->height() - COLOR_LOGO_HEIGHT) / 2,
      color_logo, COLOR_LOGO_WIDTH, COLOR_LOGO_HEIGHT);
#endif
    // After logo is drawn
  }
#ifdef DISPLAY_BACKLIGHT
  int i;
  Serial.println("Fade in backlight");
  for (i = 0; i < BACKLIGHT_MAX; i++) { // Fade logo in
    analogWrite(DISPLAY_BACKLIGHT, i);
    delay(2);
  }
  delay(1400); // Pause for screen layout/orientation
  Serial.println("Fade out backlight");
  for (; i >= 0; i--) {
    analogWrite(DISPLAY_BACKLIGHT, i);
    delay(2);
  }
  for (e = 0; e < NUM_EYES; e++) { // Clear display(s)
    eye[e].display->fillScreen(0);
  }
  delay(100);
#else
  delay(2000); // Pause for screen layout/orientation
#endif // DISPLAY_BACKLIGHT
#endif // LOGO_TOP_WIDTH

  // One of the displays is configured to mirror on the X axis.  Simplifies
  // eyelid handling in the drawEye() function -- no need for distinct
  // L-to-R or R-to-L inner loops.  Just the X coordinate of the iris is
  // then reversed when drawing this eye, so they move the same.  Magic!
  // The values for setRotation would be: 0XC8(-MX), 0xA8(+MY), 0x8(-MX), 0x68(+MY)
  // 0xC0, A0, 0, 60
  const uint8_t mirrorTFT[]  = { 0x8, 0x20, 0x40, 0xE0 }; // Mirror+rotate
  eye[0].display->sendCommand(
    GC9A01A_MADCTL, // Current TFT lib
    &mirrorTFT[eyeInfo[0].rotation & 3], 1);

#ifdef DISPLAY_BACKLIGHT
  Serial.println("Backlight on!");
  analogWrite(DISPLAY_BACKLIGHT, BACKLIGHT_MAX);
#endif
  for (e = 0; e < NUM_EYES; e++) {
    if (!eye[e].display->useFrameBuffer(1)) {
      Serial.printf("%d: Use Frame Buffer failed\n", e);
    } else {
      Serial.printf("$%d: Using Frame buffer\n", e);
    }
  }

  startTime = millis(); // For frame-rate calculation
}


// EYE-RENDERING FUNCTION --------------------------------------------------

SPISettings settings(SPI_FREQ, MSBFIRST, SPI_MODE0);

void drawEye( // Renders one eye.  Inputs must be pre-clipped & valid.
  uint8_t  e,       // Eye array index; 0 or 1 for left/right
  uint16_t iScale,  // Scale factor for iris (0-1023)
  uint16_t  scleraX, // First pixel X offset into sclera image
  uint16_t  scleraY, // First pixel Y offset into sclera image
  uint8_t  uT,      // Upper eyelid threshold value
  uint8_t  lT) {    // Lower eyelid threshold value

  uint8_t  screenX, screenY;
  uint16_t scleraXsave;
  int16_t  irisX, irisY;
  uint16_t p, a;
  uint32_t d;
  uint16_t max_d = 0;
  uint16_t max_a = 0;
  uint16_t min_d = 0xff;
  uint16_t min_a = 0xff;
  
  uint32_t  irisThreshold = (DISPLAY_SIZE * (1023 - iScale) + 512) / 1024;
  uint32_t irisScale     = IRIS_MAP_HEIGHT * 65536 / irisThreshold;


  // Set up raw pixel dump to entire screen.  Although such writes can wrap
  // around automatically from end of rect back to beginning, the region is
  // reset on each frame here in case of an SPI glitch.
  // Now just issue raw 16-bit values for every pixel...

  scleraXsave = scleraX; // Save initial X value to reset on each line
  irisY       = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT) / 2;
  // Lets wait for any previous update screen to complete.
  for (screenY = 0; screenY < SCREEN_HEIGHT; screenY++, scleraY++, irisY++) {
    scleraX = scleraXsave;
    irisX   = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
    for (screenX = 0; screenX < SCREEN_WIDTH; screenX++, scleraX++, irisX++) {
      if ((lower[screenY][screenX] <= lT) ||
          (upper[screenY][screenX] <= uT)) {             // Covered by eyelid
        p = 0;
      } else if ((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
                 (irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
        p = sclera[scleraY][scleraX];
      } else {                                          // Maybe iris...
        p = polar[irisY][irisX];                        // Polar angle/dist
        d = p & 0x7F;                                   // Distance from edge (0-127)
        if (d < irisThreshold) {                        // Within scaled iris area
          d = d * irisScale / 65536;                    // d scaled to iris image height
          a = (IRIS_MAP_WIDTH * (p >> 7)) / 512;        // Angle (X)
          p = iris[d][a];                               // Pixel = iris
          if (d > max_d) max_d = d;
          if (a > max_a) max_a = a;
          if (d < min_d) min_d = d;
          if (a < min_a) min_a = a;
        } else {                                        // Not in iris
          p = sclera[scleraY][scleraX];                 // Pixel = sclera
        }
      }

      eye[e].display->drawPixel(screenX, screenY, p);
    } // end column
  } // end scanline
#ifdef DEBUG_ST7789
  eye[e].display->setCursor(0, 0);
  eye[e].display->setTextSize(2);
  eye[e].display->setTextColor(ST77XX_RED, ST77XX_BLACK);
  eye[e].display->printf("%4u %4u %5u\n(%3u,%3u)", iScale, irisThreshold, irisScale, scleraX, scleraY);
  eye[e].display->setCursor(0, DISPLAY_SIZE - 20);
  eye[e].display->printf("%u %3u %3u %3u %3u\n", uT, lT, eye[e].blink.state, max_d, max_a);

  // Debug
  static uint32_t iScale_printed[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  bool print_iScale = (iScale_printed[iScale >> 5] & (1 << (iScale & 0x1f))) ? false : true;
  if (print_iScale) {
    iScale_printed[iScale >> 5] |= (1 << (iScale & 0x1f));
    Serial.printf("%4u : %6u %6u %4u:%4u %4u:%4u\n", iScale, irisThreshold, irisScale, min_d, max_d, min_a, max_a);
  }
#endif

#if defined(USE_ASYNC_UPDATES)
  if (!eye[e].display->updateScreenAsync()) {
    Serial.printf("%d : updateScreenAsync FAILED\n", e);
  } else {
    //Serial.printf("%d : updateScreenAsync started\n", e);
  }
#else
  eye[e].display->updateScreen();
#endif
}

// EYE ANIMATION -----------------------------------------------------------

const uint8_t ease[] PROGMEM = { // Ease in/out curve for eye movements 3*t^2-2*t^3
  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  2,  2,  2,  3,   // T
  3,  3,  4,  4,  4,  5,  5,  6,  6,  7,  7,  8,  9,  9, 10, 10,   // h
  11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23,   // x
  24, 25, 26, 27, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39,   // 2
  40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58,   // A
  60, 61, 62, 63, 65, 66, 67, 69, 70, 72, 73, 74, 76, 77, 78, 80,   // l
  81, 83, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 98, 100, 101, 103, // e
  104, 106, 107, 109, 110, 112, 113, 115, 116, 118, 119, 121, 122, 124, 125, 127, // c
  128, 130, 131, 133, 134, 136, 137, 139, 140, 142, 143, 145, 146, 148, 149, 151, // J
  152, 154, 155, 157, 158, 159, 161, 162, 164, 165, 167, 168, 170, 171, 172, 174, // a
  175, 177, 178, 179, 181, 182, 183, 185, 186, 188, 189, 190, 192, 193, 194, 195, // c
  197, 198, 199, 201, 202, 203, 204, 205, 207, 208, 209, 210, 211, 213, 214, 215, // o
  216, 217, 218, 219, 220, 221, 222, 224, 225, 226, 227, 228, 228, 229, 230, 231, // b
  232, 233, 234, 235, 236, 237, 237, 238, 239, 240, 240, 241, 242, 243, 243, 244, // s
  245, 245, 246, 246, 247, 248, 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, // o
  252, 253, 253, 253, 254, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255
}; // n

#ifdef AUTOBLINK
uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L;
#endif

void frame( // Process motion for a single frame of left or right eye
  uint16_t        iScale) {     // Iris scale (0-1023) passed in
  static uint32_t frames   = 0; // Used in frame rate calculation
  static uint8_t  eyeIndex = 0; // eye[] array counter
  int16_t         eyeX, eyeY;

  if (++eyeIndex >= NUM_EYES) eyeIndex = 0; // Cycle through eyes, 1 per call
#if defined(USE_ASYNC_UPDATES)
  elapsedMillis emWait = 0;
  while (eye[eyeIndex].display->asyncUpdateActive() && (emWait < 1000)) ;
  if (emWait >= 1000) Serial.println("Long wait");
#endif


  uint32_t        t = micros(); // Time at start of function

  if (!(++frames & 255)) { // Every 256 frames...
    uint32_t elapsed = (millis() - startTime) / 1000;
    if (elapsed) Serial.println(frames / elapsed); // Print FPS
    EstimateStackUsage();
  }

  // X/Y movement

#if defined(JOYSTICK_X_PIN) && (JOYSTICK_X_PIN >= 0) && \
    defined(JOYSTICK_Y_PIN) && (JOYSTICK_Y_PIN >= 0)

  // Read X/Y from joystick, constrain to circle
  int16_t dx, dy;
  int32_t d;
  eyeX = analogRead(JOYSTICK_X_PIN); // Raw (unclipped) X/Y reading
  eyeY = analogRead(JOYSTICK_Y_PIN);
#ifdef JOYSTICK_X_FLIP
  eyeX = 1023 - eyeX;
#endif
#ifdef JOYSTICK_Y_FLIP
  eyeY = 1023 - eyeY;
#endif
  dx = (eyeX * 2) - 1023; // A/D exact center is at 511.5.  Scale coords
  dy = (eyeY * 2) - 1023; // X2 so range is -1023 to +1023 w/center at 0.
  if ((d = (dx * dx + dy * dy)) > (1023 * 1023)) { // Outside circle
    d    = (int32_t)sqrt((float)d);               // Distance from center
    eyeX = ((dx * 1023 / d) + 1023) / 2;          // Clip to circle edge,
    eyeY = ((dy * 1023 / d) + 1023) / 2;          // scale back to 0-1023
  }

#else // Autonomous X/Y eye motion
  // Periodically initiates motion to a new random point, random speed,
  // holds there for random period until next motion.

  static boolean  eyeInMotion      = false;
  static int16_t  eyeOldX = 512, eyeOldY = 512, eyeNewX = 512, eyeNewY = 512;
  static uint32_t eyeMoveStartTime = 0L;
  static int32_t  eyeMoveDuration  = 0L;

  int32_t dt = t - eyeMoveStartTime;      // uS elapsed since last eye event
  if (eyeInMotion) {                      // Currently moving?
    if (dt >= eyeMoveDuration) {          // Time up?  Destination reached.
      eyeInMotion      = false;           // Stop moving
      eyeMoveDuration  = random(3000000); // 0-3 sec stop
      eyeMoveStartTime = t;               // Save initial time of stop
      eyeX = eyeOldX = eyeNewX;           // Save position
      eyeY = eyeOldY = eyeNewY;
    } else { // Move time's not yet fully elapsed -- interpolate position
      int16_t e = ease[255 * dt / eyeMoveDuration] + 1;   // Ease curve
      eyeX = eyeOldX + (((eyeNewX - eyeOldX) * e) / 256); // Interp X
      eyeY = eyeOldY + (((eyeNewY - eyeOldY) * e) / 256); // and Y
    }
  } else {                                // Eye stopped
    eyeX = eyeOldX;
    eyeY = eyeOldY;
    if (dt > eyeMoveDuration) {           // Time up?  Begin new move.
      int16_t  dx, dy;
      uint32_t d;
      do {                                // Pick new dest in circle
        eyeNewX = random(1024);
        eyeNewY = random(1024);
        dx      = (eyeNewX * 2) - 1023;
        dy      = (eyeNewY * 2) - 1023;
      } while ((d = (dx * dx + dy * dy)) > (1023 * 1023)); // Keep trying
      eyeMoveDuration  = random(72000, 144000); // ~1/14 - ~1/7 sec
      eyeMoveStartTime = t;               // Save initial time of move
      eyeInMotion      = true;            // Start move on next frame
      //Serial.printf("%d: Motion: %d %d (%d,%d)\n", eyeIndex, eyeMoveStartTime,
      //eyeMoveDuration, dx, dy);
    }
  }

#endif // JOYSTICK_X_PIN etc.

  // Blinking

#ifdef AUTOBLINK
  // Similar to the autonomous eye movement above -- blink start times
  // and durations are random (within ranges).
  if ((t - timeOfLastBlink) >= timeToNextBlink) { // Start new blink?
    timeOfLastBlink = t;
    uint32_t blinkDuration = random(36000, 72000); // ~1/28 - ~1/14 sec
    // Set up durations for both eyes (if not already winking)
    for (uint8_t e = 0; e < NUM_EYES; e++) {
      if (eye[e].blink.state == NOBLINK) {
        eye[e].blink.state     = ENBLINK;
        eye[e].blink.startTime = t;
        eye[e].blink.duration  = blinkDuration;
      }
    }
    timeToNextBlink = blinkDuration * 3 + random(4000000);
  }
#endif

  if (eye[eyeIndex].blink.state) { // Eye currently blinking?
    // Check if current blink state time has elapsed
    if ((t - eye[eyeIndex].blink.startTime) >= eye[eyeIndex].blink.duration) {
      // Yes -- increment blink state, unless...
      if ((eye[eyeIndex].blink.state == ENBLINK) && ( // Enblinking and...
#if defined(BLINK_PIN) && (BLINK_PIN >= 0)
            (digitalRead(BLINK_PIN) == LOW) ||           // blink or wink held...
#endif
            ((eyeInfo[eyeIndex].wink >= 0) &&
             digitalRead(eyeInfo[eyeIndex].wink) == LOW) )) {
        // Don't advance state yet -- eye is held closed instead
      } else { // No buttons, or other state...
        if (++eye[eyeIndex].blink.state > DEBLINK) { // Deblinking finished?
          eye[eyeIndex].blink.state = NOBLINK;      // No longer blinking
        } else { // Advancing from ENBLINK to DEBLINK mode
          eye[eyeIndex].blink.duration *= 2; // DEBLINK is 1/2 ENBLINK speed
          eye[eyeIndex].blink.startTime = t;
        }
      }
    }
  } else { // Not currently blinking...check buttons!
#if defined(BLINK_PIN) && (BLINK_PIN >= 0)
    if (digitalRead(BLINK_PIN) == LOW) {
      // Manually-initiated blinks have random durations like auto-blink
      uint32_t blinkDuration = random(36000, 72000);
      for (uint8_t e = 0; e < NUM_EYES; e++) {
        if (eye[e].blink.state == NOBLINK) {
          eye[e].blink.state     = ENBLINK;
          eye[e].blink.startTime = t;
          eye[e].blink.duration  = blinkDuration;
        }
      }
    } else
#endif
      if ((eyeInfo[eyeIndex].wink >= 0) &&
          (digitalRead(eyeInfo[eyeIndex].wink) == LOW)) { // Wink!
        eye[eyeIndex].blink.state     = ENBLINK;
        eye[eyeIndex].blink.startTime = t;
        eye[eyeIndex].blink.duration  = random(45000, 90000);
      }
  }

  // Process motion, blinking and iris scale into renderable values

  // Scale eye X/Y positions (0-1023) to pixel units used by drawEye()
  eyeX = map(eyeX, 0, 1023, 0, SCLERA_WIDTH  - DISPLAY_SIZE);
  eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - DISPLAY_SIZE);
  if (eyeIndex == 1) eyeX = (SCLERA_WIDTH - DISPLAY_SIZE) - eyeX; // Mirrored display

  // Horizontal position is offset so that eyes are very slightly crossed
  // to appear fixated (converged) at a conversational distance.  Number
  // here was extracted from my posterior and not mathematically based.
  // I suppose one could get all clever with a range sensor, but for now...
  if (NUM_EYES > 1) eyeX += 4;
  if (eyeX > (SCLERA_WIDTH - DISPLAY_SIZE)) eyeX = (SCLERA_WIDTH - DISPLAY_SIZE);

  // Eyelids are rendered using a brightness threshold image.  This same
  // map can be used to simplify another problem: making the upper eyelid
  // track the pupil (eyes tend to open only as much as needed -- e.g. look
  // down and the upper eyelid drops).  Just sample a point in the upper
  // lid map slightly above the pupil to determine the rendering threshold.
  static uint16_t uThreshold = DISPLAY_SIZE;
  uint16_t        lThreshold, n;
#ifdef TRACKING
  int16_t sampleX = SCLERA_WIDTH  / 2 - (eyeX / 2), // Reduce X influence
          sampleY = SCLERA_HEIGHT / 2 - (eyeY + IRIS_HEIGHT / 4);
  // Eyelid is slightly asymmetrical, so two readings are taken, averaged
  if (sampleY < 0) n = 0;
  else            n = (upper[sampleY][sampleX] +
                         upper[sampleY][SCREEN_WIDTH - 1 - sampleX]) / 2;
  uThreshold = (uThreshold * 3 + n) / 4; // Filter/soften motion
  // Lower eyelid doesn't track the same way, but seems to be pulled upward
  // by tension from the upper lid.
  lThreshold = 254 - uThreshold;
#else // No tracking -- eyelids full open unless blink modifies them
  uThreshold = lThreshold = 0;
#endif

  // The upper/lower thresholds are then scaled relative to the current
  // blink position so that blinks work together with pupil tracking.
  if (eye[eyeIndex].blink.state) { // Eye currently blinking?
    uint32_t s = (t - eye[eyeIndex].blink.startTime);
    if (s >= eye[eyeIndex].blink.duration) s = 255;  // At or past blink end
    else s = 255 * s / eye[eyeIndex].blink.duration; // Mid-blink
    s          = (eye[eyeIndex].blink.state == DEBLINK) ? 1 + s : 256 - s;
    n          = (uThreshold * s + 254 * (257 - s)) / 256;
    lThreshold = (lThreshold * s + 254 * (257 - s)) / 256;
  } else {
    n          = uThreshold;
  }

  // Pass all the derived values to the eye-rendering function:
  drawEye(eyeIndex, iScale, eyeX, eyeY, n, lThreshold);
}

// AUTONOMOUS IRIS SCALING (if no photocell or dial) -----------------------

#if !defined(LIGHT_PIN) || (LIGHT_PIN < 0)

// Autonomous iris motion uses a fractal behavior to similate both the major
// reaction of the eye plus the continuous smaller adjustments that occur.

uint16_t oldIris = (IRIS_MIN + IRIS_MAX) / 2, newIris;

void split( // Subdivides motion path into two sub-paths w/randimization
  int16_t  startValue, // Iris scale value (IRIS_MIN to IRIS_MAX) at start
  int16_t  endValue,   // Iris scale value at end
  uint32_t startTime,  // micros() at start
  int32_t  duration,   // Start-to-end time, in microseconds
  int16_t  range) {    // Allowable scale value variance when subdividing

  if (range >= 8) {    // Limit subdvision count, because recursion
    range    /= 2;     // Split range & time in half for subdivision,
    duration /= 2;     // then pick random center point within range:
    int16_t  midValue = (startValue + endValue - range) / 2 + random(range);
    uint32_t midTime  = startTime + duration;
    split(startValue, midValue, startTime, duration, range); // First half
    split(midValue  , endValue, midTime  , duration, range); // Second half
  } else {             // No more subdivisons, do iris motion...
    int32_t dt;        // Time (micros) since start of motion
    int16_t v;         // Interim value
    while ((dt = (micros() - startTime)) < duration) {
      v = startValue + (((endValue - startValue) * dt) / duration);
      if (v < IRIS_MIN)      v = IRIS_MIN; // Clip just in case
      else if (v > IRIS_MAX) v = IRIS_MAX;
      frame(v);        // Draw frame w/interim iris scale value
    }
  }
}

#endif // !LIGHT_PIN

// MAIN LOOP -- runs continuously after setup() ----------------------------

void loop() {

#if defined(LIGHT_PIN) && (LIGHT_PIN >= 0) // Interactive iris

  int16_t v = analogRead(LIGHT_PIN);       // Raw dial/photocell reading
#ifdef LIGHT_PIN_FLIP
  v = 1023 - v;                            // Reverse reading from sensor
#endif
  if (v < LIGHT_MIN)      v = LIGHT_MIN; // Clamp light sensor range
  else if (v > LIGHT_MAX) v = LIGHT_MAX;
  v -= LIGHT_MIN;  // 0 to (LIGHT_MAX - LIGHT_MIN)
#ifdef LIGHT_CURVE  // Apply gamma curve to sensor input?
  v = (int16_t)(pow((double)v / (double)(LIGHT_MAX - LIGHT_MIN),
                    LIGHT_CURVE) * (double)(LIGHT_MAX - LIGHT_MIN));
#endif
  // And scale to iris range (IRIS_MAX is size at LIGHT_MIN)
  v = map(v, 0, (LIGHT_MAX - LIGHT_MIN), IRIS_MAX, IRIS_MIN);
#ifdef IRIS_SMOOTH // Filter input (gradual motion)
  static int16_t irisValue = (IRIS_MIN + IRIS_MAX) / 2;
  irisValue = ((irisValue * 15) + v) / 16;
  frame(irisValue);
#else // Unfiltered (immediate motion)
  frame(v);
#endif // IRIS_SMOOTH

#else  // Autonomous iris scaling -- invoke recursive function

  newIris = random(IRIS_MIN, IRIS_MAX);
  split(oldIris, newIris, micros(), 10000000L, IRIS_MAX - IRIS_MIN);
  oldIris = newIris;

#endif // LIGHT_PIN
}

// from the linker
//  extern unsigned long _stextload;
extern unsigned long _stext;
extern unsigned long _etext;
//  extern unsigned long _sdataload;
extern unsigned long _sdata;
extern unsigned long _edata;
extern unsigned long _sbss;
extern unsigned long _ebss;
//  extern unsigned long _flexram_bank_config;
extern unsigned long _estack;

void DumpMemoryInfo() {
#if defined(__IMXRT1062__) && defined(DEBUG_MEMORY)
  uint32_t flexram_config = IOMUXC_GPR_GPR17;
  Serial.printf("IOMUXC_GPR_GPR17:%x IOMUXC_GPR_GPR16:%x IOMUXC_GPR_GPR14:%x\n",
                flexram_config, IOMUXC_GPR_GPR16, IOMUXC_GPR_GPR14);
  Serial.printf("Initial Stack pointer: %x\n", &_estack);
  uint32_t dtcm_size = 0;
  uint32_t itcm_size = 0;
  for (; flexram_config; flexram_config >>= 2) {
    if ((flexram_config & 0x3) == 0x2) dtcm_size += 32768;
    else if ((flexram_config & 0x3) == 0x3) itcm_size += 32768;
  }
  Serial.printf("ITCM allocated: %u  DTCM allocated: %u\n", itcm_size, dtcm_size);
  Serial.printf("ITCM init range: %x - %x Count: %u\n", &_stext, &_etext, (uint32_t)&_etext - (uint32_t)&_stext);
  Serial.printf("DTCM init range: %x - %x Count: %u\n", &_sdata, &_edata, (uint32_t)&_edata - (uint32_t)&_sdata);
  Serial.printf("DTCM cleared range: %x - %x Count: %u\n", &_sbss, &_ebss, (uint32_t)&_ebss - (uint32_t)&_sbss);
  Serial.printf("Now fill rest of DTCM with known pattern(%x - %x\n", (&_ebss + 1), (&itcm_size - 10)); Serial.flush(); //
  // Guess of where it is safe to fill memory... Maybe address of last variable we have defined - some slop...
  for (uint32_t *pfill = (&_ebss + 32); pfill < (&itcm_size - 10); pfill++) {
    *pfill = 0x01020304;  // some random value
  }
#endif
}
void EstimateStackUsage() {
#if defined(__IMXRT1062__) && defined(DEBUG_MEMORY)
  uint32_t *pmem = (&_ebss + 32);
  while (*pmem == 0x01020304) pmem++;
  Serial.printf("Estimated max stack usage: %d\n", (uint32_t)&_estack - (uint32_t)pmem);
#endif
}
/#define SERIAL_tt Serial // Send debug_tt output here. Must have SERIAL_tt.begin( ## )
//#include "debug_tt.h"
// Pin selections here are based on the original Adafruit Learning System
// guide for the Teensy 3.x project.  Some of these pin numbers don't even
// exist on the smaller SAMD M0 & M4 boards, so you may need to make other
// selections:

// GRAPHICS SETTINGS (appearance of eye) -----------------------------------

// If using a SINGLE EYE, you might want this next line enabled, which
// uses a simpler "football-shaped" eye that's left/right symmetrical.
// Default shape includes the caruncle, creating distinct left/right eyes.
// Otherwise your choice, standard is asymmetrical
  #define SYMMETRICAL_EYELID

// Enable ONE of these #includes -- HUGE graphics tables for various eyes:
#if( DISPLAY_SIZE == 240 )
#include "graphics/default_large.h"   // 240x240
#else
//#include "graphics/defaultEye.h"      // Standard human-ish hazel eye -OR-
//#include "graphics/dragonEye.h"     // Slit pupil fiery dragon/demon eye -OR-
//#include "graphics/noScleraEye.h"   // Large iris, no sclera -OR-
//#include "graphics/goatEye.h"       // Horizontal pupil goat/Krampus eye -OR-
//#include "graphics/newtEye.h"       // Eye of newt -OR-
//#include "graphics/terminatorEye.h" // Git to da choppah!
//#include "graphics/catEye.h"        // Cartoonish cat (flat "2D" colors)
//#include "graphics/owlEye.h"        // Minerva the owl (DISABLE TRACKING)
//#include "graphics/naugaEye.h"      // Nauga googly eye (DISABLE TRACKING)
//#include "graphics/doeEye.h"        // Cartoon deer eye (DISABLE TRACKING)
//#include "graphics/Nebula.h"  //Dont work
//#include "graphics/MyEyeHuman1.h"
//#include "graphics/Human-HAL9000.h"
//#include "graphics/NebulaBlueGreen.h"
//#include "graphics/SpiralGalaxy.h"
//#include "graphics/ChameleonX_Eye.h"  //no work
//#include "graphics/MyEye.h"
#endif
// Optional: enable this line for startup logo (screen test/orient):

// EYE LIST ----------------------------------------------------------------


// DISPLAY HARDWARE SETTINGS (screen type & connections) -------------------

  //#define TFT_SPI        SPI
  //#define TFT_PERIPH     PERIPH_SPI

  // Enable ONE of these #includes to specify the display type being used
#include <GC9A01A_t3n.h>
  
  #define SPI_FREQ 48000000    // TFT: use max SPI (clips to 12 MHz on M0)

// This table contains ONE LINE PER EYE.  The table MUST be present with
// this name and contain ONE OR MORE lines.  Each line contains THREE items:
// a pin number for the corresponding TFT/OLED display's SELECT line, a pin
// pin number for that eye's "wink" button (or -1 if not used), and a screen
// rotation value (0-3) for that eye.

eyeInfo_t eyeInfo[] = {
  //CS  DC MOSI SCK RST WINK ROT INIT
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
   {29,31,  21, 32,  28, -1,  0, 0 }, // RIGHT EYE display-select and wink pins, no rotation
   {10, 9,  11, 13,  8, -1,  0, 0 }, // LEFT EYE display-select and wink pins, no rotation
#else
   {0,  2,  26, 27,  3, -1,  0, 0 }, // RIGHT EYE display-select and wink pins, no rotation
   {10, 9,  11, 13,  8, -1,  0, 0 }, // LEFT EYE display-select and wink pins, no rotation
#endif
};
// INPUT SETTINGS (for controlling eye motion) -----------------------------

// JOYSTICK_X_PIN and JOYSTICK_Y_PIN specify analog input pins for manually
// controlling the eye with an analog joystick.  If set to -1 or if not
// defined, the eye will move on its own.
// IRIS_PIN speficies an analog input pin for a photocell to make pupils
// react to light (or potentiometer for manual control).  If set to -1 or
// if not defined, the pupils will change on their own.
// BLINK_PIN specifies an input pin for a button (to ground) that will
// make any/all eyes blink.  If set to -1 or if not defined, the eyes will
// only blink if AUTOBLINK is defined, or if the eyeInfo[] table above
// includes wink button settings for each eye.

//#define JOYSTICK_X_PIN A0 // Analog pin for eye horiz pos (else auto)
//#define JOYSTICK_Y_PIN A1 // Analog pin for eye vert position (")
//#define JOYSTICK_X_FLIP   // If defined, reverse stick X axis
//#define JOYSTICK_Y_FLIP   // If defined, reverse stick Y axis


#define TRACKING            // If defined, eyelid tracks pupil
#define AUTOBLINK           // If defined, eyes also blink autonomously
  //#define BLINK_PIN         1 // Pin for manual blink button (BOTH eyes)
  //#define LIGHT_PIN      A3 // Photocell or potentiometer (else auto iris)
  //#define LIGHT_PIN_FLIP    // If defined, reverse reading from dial/photocell
  #define LIGHT_MIN       0 // Lower reading from sensor
  #define LIGHT_MAX    1023 // Upper reading from sensor

#define IRIS_SMOOTH         // If enabled, filter input from IRIS_PIN
#if !defined(IRIS_MIN)      // Each eye might have its own MIN/MAX
  #define IRIS_MIN      120 // Iris size (0-1023) in brightest light
#endif
#if !defined(IRIS_MAX)
  #define IRIS_MAX      900 // Iris size (0-1023) in darkest light
#endif

Unfortunately I am working on fixing the errors that pop up from it:
In file included from it.

C:\Users\mrsno\Documents\Arduino\uncannyEyes_GC9A01A\uncannyEyes_GC9A01A.ino:37:
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:587:13: error: 'SPI_Hardware_t' in 'class SPIClass' does not name a type
  587 |   SPIClass::SPI_Hardware_t *_spi_hardware;
      |             ^~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h: In member function 'void GC9A01A_t3n::setAddr(uint16_t, uint16_t, uint16_t, uint16_t)':
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:760:5: error: 'writecommand_cont' was not declared in this scope
  760 |     writecommand_cont(GC9A01A_CASET); // Column addr set
      |     ^~~~~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:761:5: error: 'writedata16_cont' was not declared in this scope
  761 |     writedata16_cont(x0);             // XSTART
      |     ^~~~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h: In member function 'void GC9A01A_t3n::HLine(int16_t, int16_t, int16_t, uint16_t)':
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:1104:5: error: 'writecommand_cont' was not declared in this scope
 1104 |     writecommand_cont(GC9A01A_RAMWR);
      |     ^~~~~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:1106:7: error: 'writedata16_cont' was not declared in this scope
 1106 |       writedata16_cont(color);
      |       ^~~~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h: In member function 'void GC9A01A_t3n::VLine(int16_t, int16_t, int16_t, uint16_t)':
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:1133:5: error: 'writecommand_cont' was not declared in this scope
 1133 |     writecommand_cont(GC9A01A_RAMWR);
      |     ^~~~~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:1135:7: error: 'writedata16_cont' was not declared in this scope
 1135 |       writedata16_cont(color);
      |       ^~~~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h: In member function 'void GC9A01A_t3n::Pixel(int16_t, int16_t, uint16_t)':
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:1192:5: error: 'writecommand_cont' was not declared in this scope
 1192 |     writecommand_cont(GC9A01A_RAMWR);
      |     ^~~~~~~~~~~~~~~~~
c:\Users\mrsno\Documents\Arduino\libraries\GC9A01A_t3n\src/GC9A01A_t3n.h:1193:5: error: 'writedata16_cont' was not declared in this scope
 1193 |     writedata16_cont(color);
      |     ^~~~~~~~~~~~~~~~

exit status 1

Compilation error: exit status 1

I'm assuming based off the errors that all the issues are in this code:

//***************************************************
// https://github.com/kurte/ILI9341_t3n
// http://forum.pjrc.com/threads/26305-Highly-optimized-ILI9341-(320x240-TFT-color-display)-library
//
// Warning this is Kurt's updated version which allows it to work on different SPI busses.
//
// On Teensy 3.x allows you to use on only one valid hardware CS pin  which must 
// be used for DC
//
// On Teensy 4.x including Micromod you are free to use any digital pin for
// CS and DC, but you might get a modest speed increase if hardware CS is
// used for the DC pin
//
/***************************************************
  This is our library for the Adafruit  ILI9341 Breakout and Shield
  ----> http://www.adafruit.com/products/1651

  Check out the links above for our tutorials and wiring diagrams
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional)
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/

// <SoftEgg>

// Additional graphics routines by Tim Trzepacz, SoftEgg LLC added December 2015
//(And then accidentally deleted and rewritten March 2016. Oops!)
// Gradient support
//----------------
//		fillRectVGradient	- fills area with vertical gradient
//		fillRectHGradient	- fills area with horizontal gradient
//		fillScreenVGradient - fills screen with vertical gradient
// 	fillScreenHGradient - fills screen with horizontal gradient

// Additional Color Support
//------------------------
//		color565toRGB		- converts 565 format 16 bit color to
//RGB
//		color565toRGB14		- converts 16 bit 565 format color to
//14 bit RGB (2 bits clear for math and sign)
//		RGB14tocolor565		- converts 14 bit RGB back to 16 bit
//565 format color

// Low Memory Bitmap Support
//-------------------------
// writeRect8BPP - 	write 8 bit per pixel paletted bitmap
// writeRect4BPP - 	write 4 bit per pixel paletted bitmap
// writeRect2BPP - 	write 2 bit per pixel paletted bitmap
// writeRect1BPP - 	write 1 bit per pixel paletted bitmap

// String Pixel Length support
//---------------------------
//		strPixelLen			- gets pixel length of given ASCII
//string

// <\SoftEgg>
// Also some of this comes from the DMA version of the library...

/* ILI9341_t3DMA library code is placed under the MIT license
 * Copyright (c) 2016 Frank Bösing
 *
*/

#ifndef _GC9A01A_t3N_H_
#define _GC9A01A_t3N_H_

#define GC9A01A_USE_DMAMEM

// Allow us to enable or disable capabilities, particully Frame Buffer and
// Clipping for speed and size
#ifndef DISABLE_GC9A01A_FRAMEBUFFER
#if defined(__MK66FX1M0__) // T3.6
#define ENABLE_GC9A01A_FRAMEBUFFER
#define SCREEN_DMA_NUM_SETTINGS                                                \
  2 // see if making it a constant value makes difference...
#elif defined(__MK64FX512__) // T3.5
#define ENABLE_GC9A01A_FRAMEBUFFER
#define SCREEN_DMA_NUM_SETTINGS                                                \
  4 // see if making it a constant value makes difference...
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)
#define ENABLE_GC9A01A_FRAMEBUFFER
#define SCREEN_DMA_NUM_SETTINGS                                                \
  2 // see if making it a constant value makes difference...
#endif
#endif

// Allow way to override using SPI

#ifdef __cplusplus
#include "Arduino.h"
#include <DMAChannel.h>
#include <SPI.h>

#endif
#include <stdint.h>

#include "ILI9341_fonts.h"

#define GC9A01A_TFTWIDTH 240
#define GC9A01A_TFTHEIGHT 240

// NOTE: ILI9341 registers defined (but commented out) are ones that
// *might* be compatible with the GC9A01A, but aren't documented in
// the device datasheet. A couple are defined (with ILI name) and NOT
// commented out because they appeared in the manufacturer example code
// as raw register addresses, no documentation in datasheet, they SEEM
// to do the same thing as their ILI equivalents but this is not 100%
// confirmed so they've not been assigned GC9A01A register defines.

#define GC9A01A_NOP 0x00     ///< No-op register
#define GC9A01A_SWRESET 0x01 ///< Software reset register

//#define GC9A01A 0x04   ///< Read display identification information
//#define GC9A01A 0x09   ///< Read Display Status

#define GC9A01A_SLPIN 0x10  ///< Enter Sleep Mode
#define GC9A01A_SLPOUT 0x11 ///< Sleep Out
#define GC9A01A_PTLON 0x12  ///< Partial Mode ON
#define GC9A01A_NORON 0x13  ///< Normal Display Mode ON

//#define ILI9341_RDMODE 0x0A     ///< Read Display Power Mode
//#define ILI9341_RDMADCTL 0x0B   ///< Read Display MADCTL
//#define ILI9341_RDPIXFMT 0x0C   ///< Read Display Pixel Format
//#define ILI9341_RDIMGFMT 0x0D   ///< Read Display Image Format
//#define ILI9341_RDSELFDIAG 0x0F ///< Read Display Self-Diagnostic Result

#define GC9A01A_INVOFF 0x20   ///< Display Inversion OFF
#define GC9A01A_INVON 0x21    ///< Display Inversion ON
//#define ILI9341_GAMMASET 0x26 ///< Gamma Set
#define GC9A01A_DISPOFF 0x28  ///< Display OFF
#define GC9A01A_DISPON 0x29   ///< Display ON

#define GC9A01A_CASET 0x2A ///< Column Address Set
#define GC9A01A_PASET 0x2B ///< Page Address Set
#define GC9A01A_RAMWR 0x2C ///< Memory Write
#define GC9A01A_RAMRD 0x2E ///< Memory Read

#define GC9A01A_PTLAR 0x30    ///< Partial Area
#define GC9A01A_VSCRDEF 0x33  ///< Vertical Scrolling Definition
#define GC9A01A_TEOFF 0x34    ///< Tearing effect line off
#define GC9A01A_TEON 0x35     ///< Tearing effect line on
#define GC9A01A_MADCTL 0x36   ///< Memory Access Control
#define GC9A01A_VSCRSADD 0x37 ///< Vertical Scrolling Start Address
#define GC9A01A_PIXFMT 0x3A   ///< COLMOD: Pixel Format Set
// 0x3C = Write memory continue
// 0x44 = Set tear scanline
// 0x45 = Get scanline
// 0x51 = Write display brightness
// 0x53 = Write CTRL display

// 0xA7 = Vcore voltage control

// 0xB0 = RGB interface signal control
//#define ILI9341_FRMCTR1 0xB1 ///< Frame Rate Control (In Normal Mode/Full Colors)
//#define ILI9341_FRMCTR2 0xB2 ///< Frame Rate Control (In Idle Mode/8 colors)
//#define ILI9341_FRMCTR3 0xB3 ///< Frame Rate control (In Partial Mode/Full Colors)
//#define ILI9341_INVCTR 0xB4  ///< Display Inversion Control
// 0xB5 = Blanking porch control
#define GC9A01A_DFUNCTR 0xB6 ///< Display Function Control
// 0xBA = TE control

// 0xC1 = Power criterion control
#define GC9A01A_VREG1A 0xC3 ///< Vreg1a voltage control
#define GC9A01A_VREG1B 0xC4 ///< Vreg1b voltage control
//#define ILI9341_PWCTR1 0xC0 ///< Power Control 1
//#define ILI9341_PWCTR2 0xC1 ///< Power Control 2
//#define ILI9341_PWCTR3 0xC2 ///< Power Control 3
//#define ILI9341_PWCTR4 0xC3 ///< Power Control 4
//#define ILI9341_PWCTR5 0xC4 ///< Power Control 5
//#define ILI9341_VMCTR1 0xC5 ///< VCOM Control 1
//#define ILI9341_VMCTR2 0xC7 ///< VCOM Control 2
#define GC9A01A_VREG2A 0xC9 ///< Vreg2a voltage control

#define GC9A01A_RDID1 0xDA ///< Read ID 1
#define GC9A01A_RDID2 0xDB ///< Read ID 2
#define GC9A01A_RDID3 0xDC ///< Read ID 3
//#define ILI9341_RDID4 0xDD ///< Read ID 4

#define ILI9341_GMCTRP1 0xE0 ///< Positive Gamma Correction
#define ILI9341_GMCTRN1 0xE1 ///< Negative Gamma Correction
#define ILI9341_FRAMERATE 0xE8 ///< Frame rate control
// 0xE9 = SPI 2data control
// 0xEC = Charge pump frequent control
#define GC9A01A_INREGEN2 0xEF ///< Inter register enable 2
#define GC9A01A_GAMMA1 0xF0 ///< Set gamma 1
#define GC9A01A_GAMMA2 0xF1 ///< Set gamma 2
#define GC9A01A_GAMMA3 0xF2 ///< Set gamma 3
#define GC9A01A_GAMMA4 0xF3 ///< Set gamma 4
//#define ILI9341_PWCTR6     0xFC
// 0xF6 = Interface control
#define GC9A01A_INREGEN1 0xFE ///< Inter register enable 1


/*
#define ILI9341_PWCTR6  0xFC

*/

// Color definitions
#define BLACK 0x0000       /*   0,   0,   0 */
#define NAVY 0x000F        /*   0,   0, 128 */
#define DARKGREEN 0x03E0   /*   0, 128,   0 */
#define DARKCYAN 0x03EF    /*   0, 128, 128 */
#define MAROON 0x7800      /* 128,   0,   0 */
#define PURPLE 0x780F      /* 128,   0, 128 */
#define OLIVE 0x7BE0       /* 128, 128,   0 */
#define LIGHTGREY 0xC618   /* 192, 192, 192 */
#define DARKGREY 0x7BEF    /* 128, 128, 128 */
#define BLUE 0x001F        /*   0,   0, 255 */
#define GREEN 0x07E0       /*   0, 255,   0 */
#define CYAN 0x07FF        /*   0, 255, 255 */
#define RED 0xF800         /* 255,   0,   0 */
#define MAGENTA 0xF81F     /* 255,   0, 255 */
#define YELLOW 0xFFE0      /* 255, 255,   0 */
#define WHITE 0xFFFF       /* 255, 255, 255 */
#define ORANGE 0xFD20      /* 255, 165,   0 */
#define GREENYELLOW 0xAFE5 /* 173, 255,  47 */
#define PINK 0xF81F

#ifndef CL
#define CL(_r,_g,_b) ((((_r)&0xF8)<<8)|(((_g)&0xFC)<<3)|((_b)>>3))
#endif
#define sint16_t int16_t

// Lets see about supporting Adafruit fonts as well?
#if __has_include(<gfxfont.h>)
#include <gfxfont.h>
#endif

#ifndef _GFXFONT_H_
#define _GFXFONT_H_

/// Font data stored PER GLYPH
typedef struct {
  uint16_t bitmapOffset; ///< Pointer into GFXfont->bitmap
  uint8_t width;         ///< Bitmap dimensions in pixels
  uint8_t height;        ///< Bitmap dimensions in pixels
  uint8_t xAdvance;      ///< Distance to advance cursor (x axis)
  int8_t xOffset;        ///< X dist from cursor pos to UL corner
  int8_t yOffset;        ///< Y dist from cursor pos to UL corner
} GFXglyph;

/// Data stored for FONT AS A WHOLE
typedef struct {
  uint8_t *bitmap;  ///< Glyph bitmaps, concatenated
  GFXglyph *glyph;  ///< Glyph array
  uint8_t first;    ///< ASCII extents (first char)
  uint8_t last;     ///< ASCII extents (last char)
  uint8_t yAdvance; ///< Newline distance (y axis)
} GFXfont;

#endif // _GFXFONT_H_



// These enumerate the text plotting alignment (reference datum point)
#define TL_DATUM 0 // Top left (default)
#define TC_DATUM 1 // Top centre
#define TR_DATUM 2 // Top right
#define ML_DATUM 3 // Middle left
#define CL_DATUM 3 // Centre left, same as above
#define MC_DATUM 4 // Middle centre
#define CC_DATUM 4 // Centre centre, same as above
#define MR_DATUM 5 // Middle right
#define CR_DATUM 5 // Centre right, same as above
#define BL_DATUM 6 // Bottom left
#define BC_DATUM 7 // Bottom centre
#define BR_DATUM 8 // Bottom right
//#define L_BASELINE  9 // Left character baseline (Line the 'A' character would
//sit on)
//#define C_BASELINE 10 // Centre character baseline
//#define R_BASELINE 11 // Right character baseline

#ifdef __cplusplus
// At all other speeds, _pspi->beginTransaction() will use the fastest available
// clock
#ifdef KINETISK
#define GC9A01A_SPICLOCK 30000000
#define GC9A01A_SPICLOCK_READ 2000000
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
#define GC9A01A_SPICLOCK 30000000u
#define GC9A01A_SPICLOCK_READ 2000000
#else
#define GC9A01A_SPICLOCK 30000000
#define GC9A01A_SPICLOCK_READ 2000000
#endif

#if defined(__IMXRT1062__)  // Teensy 4.x
// Also define these in lower memory so as to make sure they are not cached...
typedef struct {
  DMASetting      _dmasettings[2];
  DMAChannel      _dmatx;
} GC9A01A_DMA_Data_t;
#endif


class GC9A01A_t3n : public Print {
public:
  GC9A01A_t3n(uint8_t _CS, uint8_t _DC, uint8_t _RST = 255, uint8_t _MOSI = 11,
              uint8_t _SCLK = 13);
  void begin(uint32_t spi_clock = GC9A01A_SPICLOCK,
             uint32_t spi_clock_read = GC9A01A_SPICLOCK_READ);
  void sleep(bool enable);
  void pushColor(uint16_t color);
  void fillScreen(uint16_t color);
  inline void fillWindow(uint16_t color) { fillScreen(color); }
  void drawPixel(int16_t x, int16_t y, uint16_t color);
  void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
  void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
  void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);

  void fillRectHGradient(int16_t x, int16_t y, int16_t w, int16_t h,
                         uint16_t color1, uint16_t color2);
  void fillRectVGradient(int16_t x, int16_t y, int16_t w, int16_t h,
                         uint16_t color1, uint16_t color2);
  void fillScreenVGradient(uint16_t color1, uint16_t color2);
  void fillScreenHGradient(uint16_t color1, uint16_t color2);

  void setRotation(uint8_t r);
  void setScrollMargins(uint16_t top, uint16_t bottom);
  void setScroll(uint16_t offset);
  void invertDisplay(boolean i);
  void setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
  // Pass 8-bit (each) R,G,B, get back 16-bit packed color
  static uint16_t color565(uint8_t r, uint8_t g, uint8_t b) {
    return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
  }

  // color565toRGB		- converts 565 format 16 bit color to RGB
  static void color565toRGB(uint16_t color, uint8_t &r, uint8_t &g,
                            uint8_t &b) {
    r = (color >> 8) & 0x00F8;
    g = (color >> 3) & 0x00FC;
    b = (color << 3) & 0x00F8;
  }

  // color565toRGB14		- converts 16 bit 565 format color to 14 bit RGB (2
  // bits clear for math and sign)
  // returns 00rrrrr000000000,00gggggg00000000,00bbbbb000000000
  // thus not overloading sign, and allowing up to double for additions for
  // fixed point delta
  static void color565toRGB14(uint16_t color, int16_t &r, int16_t &g,
                              int16_t &b) {
    r = (color >> 2) & 0x3E00;
    g = (color << 3) & 0x3F00;
    b = (color << 9) & 0x3E00;
  }

  // RGB14tocolor565		- converts 14 bit RGB back to 16 bit 565 format
  // color
  static uint16_t RGB14tocolor565(int16_t r, int16_t g, int16_t b) {
    return (((r & 0x3E00) << 2) | ((g & 0x3F00) >> 3) | ((b & 0x3E00) >> 9));
  }

  // Device does not have MISO line so query is not valid
  // uint8_t readdata(void);
  //uint8_t readcommand8(uint8_t r//eg, uint8_t index = 0);
  //uint16_t readScanLine();
  void setFrameRateControl(uint8_t mode);

  // Added functions to read pixel data...
  // These will only work if there is a frame buffer
  // as no MISO pin on display
  uint16_t readPixel(int16_t x, int16_t y);
  void readRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t *pcolors);


  void writeRect(int16_t x, int16_t y, int16_t w, int16_t h,
                 const uint16_t *pcolors);

  void writeSubImageRect(int16_t x, int16_t y, int16_t w, int16_t h, 
                        int16_t image_offset_x, int16_t image_offset_y, int16_t image_width, int16_t image_height, 
                        const uint16_t *pcolors);
  void writeSubImageRectBytesReversed(int16_t x, int16_t y, int16_t w, int16_t h, 
                        int16_t image_offset_x, int16_t image_offset_y, int16_t image_width, int16_t image_height, 
                        const uint16_t *pcolors);
  // writeRect8BPP - 	write 8 bit per pixel paletted bitmap
  //					bitmap data in array at pixels, one byte per
  //pixel
  //					color palette data in array at palette
  void writeRect8BPP(int16_t x, int16_t y, int16_t w, int16_t h,
                     const uint8_t *pixels, const uint16_t *palette);

  // writeRect4BPP - 	write 4 bit per pixel paletted bitmap
  //					bitmap data in array at pixels, 4 bits per
  //pixel
  //					color palette data in array at palette
  //					width must be at least 2 pixels
  void writeRect4BPP(int16_t x, int16_t y, int16_t w, int16_t h,
                     const uint8_t *pixels, const uint16_t *palette);

  // writeRect2BPP - 	write 2 bit per pixel paletted bitmap
  //					bitmap data in array at pixels, 4 bits per
  //pixel
  //					color palette data in array at palette
  //					width must be at least 4 pixels
  void writeRect2BPP(int16_t x, int16_t y, int16_t w, int16_t h,
                     const uint8_t *pixels, const uint16_t *palette);

  // writeRect1BPP - 	write 1 bit per pixel paletted bitmap
  //					bitmap data in array at pixels, 4 bits per
  //pixel
  //					color palette data in array at palette
  //					width must be at least 8 pixels
  void writeRect1BPP(int16_t x, int16_t y, int16_t w, int16_t h,
                     const uint8_t *pixels, const uint16_t *palette);

  // writeRectNBPP - 	write N(1, 2, 4, 8) bit per pixel paletted bitmap
  //					bitmap data in array at pixels
  //  Currently writeRect1BPP, writeRect2BPP, writeRect4BPP use this to do all
  //  of the work.
  //
  void writeRectNBPP(int16_t x, int16_t y, int16_t w, int16_t h,
                     uint8_t bits_per_pixel, const uint8_t *pixels,
                     const uint16_t *palette);

  // from Adafruit_GFX.h
  void drawCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color);
  void drawCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername,
                        uint16_t color);
  void fillCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color);
  void fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername,
                        int16_t delta, uint16_t color);
  void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2,
                    int16_t y2, uint16_t color);
  void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2,
                    int16_t y2, uint16_t color);
  void drawRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h,
                     int16_t radius, uint16_t color);
  void fillRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h,
                     int16_t radius, uint16_t color);
  void drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w,
                  int16_t h, uint16_t color);
  void drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color,
                uint16_t bg, uint8_t size_x, uint8_t size_y);
  void inline drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color,
                       uint16_t bg, uint8_t size) {
    drawChar(x, y, c, color, bg, size);
  }
  #ifndef CENTER
  static const int16_t CENTER = 9998;
  #endif
  void setCursor(int16_t x, int16_t y, bool autoCenter = false);
  void getCursor(int16_t *x, int16_t *y);
  void setTextColor(uint16_t c);
  void setTextColor(uint16_t c, uint16_t bg);
  void setTextSize(uint8_t sx, uint8_t sy);
  void inline setTextSize(uint8_t s) { setTextSize(s, s); }
  uint8_t getTextSizeX();
  uint8_t getTextSizeY();
  uint8_t getTextSize();
  void setTextWrap(boolean w);
  boolean getTextWrap();

  // setOrigin sets an offset in display pixels where drawing to (0,0) will
  // appear
  // for example: setOrigin(10,10); drawPixel(5,5); will cause a pixel to be
  // drawn at hardware pixel (15,15)
  void setOrigin(int16_t x = 0, int16_t y = 0) {
    _originx = x;
    _originy = y;
    // if (Serial) Serial.printf("Set Origin %d %d\n", x, y);
    updateDisplayClip();
  }
  void getOrigin(int16_t *x, int16_t *y) {
    *x = _originx;
    *y = _originy;
  }

  // setClipRect() sets a clipping rectangle (relative to any set origin) for
  // drawing to be limited to.
  // Drawing is also restricted to the bounds of the display

  void setClipRect(int16_t x1, int16_t y1, int16_t w, int16_t h) {
    _clipx1 = x1;
    _clipy1 = y1;
    _clipx2 = x1 + w;
    _clipy2 = y1 + h;
    // if (Serial) Serial.printf("Set clip Rect %d %d %d %d\n", x1, y1, w, h);
    updateDisplayClip();
  }
  void setClipRect() {
    _clipx1 = 0;
    _clipy1 = 0;
    _clipx2 = _width;
    _clipy2 = _height;
    // if (Serial) Serial.printf("clear clip Rect\n");
    updateDisplayClip();
  }

  // overwrite print functions:
  virtual size_t write(uint8_t);
  virtual size_t write(const uint8_t *buffer, size_t size);

  int16_t width(void) { return _width; }
  int16_t height(void) { return _height; }
  uint8_t getRotation(void);
  void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color);
  void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
  int16_t getCursorX(void) const { return cursor_x; }
  int16_t getCursorY(void) const { return cursor_y; }
  void setFont(const ILI9341_t3_font_t &f);
  void setFont(const GFXfont *f = NULL);
  void setFontAdafruit(void) { setFont(); }
  void drawFontChar(unsigned int c);
  void drawGFXFontChar(unsigned int c);

  void getTextBounds(const uint8_t *buffer, uint16_t len, int16_t x, int16_t y,
                     int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h);
  void getTextBounds(const char *string, int16_t x, int16_t y, int16_t *x1,
                     int16_t *y1, uint16_t *w, uint16_t *h);
  void getTextBounds(const String &str, int16_t x, int16_t y, int16_t *x1,
                     int16_t *y1, uint16_t *w, uint16_t *h);
  int16_t strPixelLen(const char *str, uint16_t cb=0xffff);  // optional number of characters...

  // added support for drawing strings/numbers/floats with centering
  // modified from tft_ili9341_ESP github library
  // Handle numbers
  int16_t drawNumber(long long_num, int poX, int poY);
  int16_t drawFloat(float floatNumber, int decimal, int poX, int poY);
  // Handle char arrays
  int16_t drawString(const String &string, int poX, int poY);
  int16_t drawString(const char string[], int16_t len, int poX, int poY);

  void setTextDatum(uint8_t datum);

  // added support for scrolling text area
  // https://github.com/vitormhenrique/ILI9341_t3
  // Discussion regarding this optimized version:
  // http://forum.pjrc.com/threads/26305-Highly-optimized-ILI9341-%28320x240-TFT-color-display%29-library
  //
  void setScrollTextArea(int16_t x, int16_t y, int16_t w, int16_t h);
  void setScrollBackgroundColor(uint16_t color);
  void enableScroll(void);
  void disableScroll(void);
  void scrollTextArea(uint8_t scrollSize);
  void resetScrollBackgroundColor(uint16_t color);

  // added support to use optional Frame buffer
  enum {
    GC9A01A_DMA_INIT = 0x01,
    GC9A01A_DMA_EVER_INIT = 0x08,
    GC9A01A_DMA_CONT = 0x02,
    GC9A01A_DMA_FINISH = 0x04,
    GC9A01A_DMA_ACTIVE = 0x80
  };
  void setFrameBuffer(uint16_t *frame_buffer);
  uint8_t
  useFrameBuffer(boolean b);  // use the frame buffer?  First call will allocate
  void freeFrameBuffer(void); // explicit call to release the buffer
  void updateScreen(void);    // call to say update the screen now.
  bool updateScreenAsync(bool update_cont = false); // call to say update the
                                                    // screen optinoally turn
                                                    // into continuous mode.
  void waitUpdateAsyncComplete(void);
  void endUpdateAsync(); // Turn of the continueous mode fla
  void dumpDMASettings();
#ifdef ENABLE_GC9A01A_FRAMEBUFFER
  uint16_t *getFrameBuffer() { return _pfbtft; }
  uint32_t frameCount() { return _dma_frame_count; }
  uint16_t subFrameCount() { return _dma_sub_frame_count; }
  boolean asyncUpdateActive(void) { return (_dma_state & GC9A01A_DMA_ACTIVE); }
  void initDMASettings(void);
  void setFrameCompleteCB(void (*pcb)(), bool fCallAlsoHalfDone = false);
#else
  uint32_t frameCount() { return 0; }
  uint16_t subFrameCount() { return 0; }
  uint16_t *getFrameBuffer() { return NULL; }
  boolean asyncUpdateActive(void) { return false; }
#endif
  void updateChangedAreasOnly(bool updateChangedOnly) {
#ifdef ENABLE_GC9A01A_FRAMEBUFFER
    _updateChangedAreasOnly = updateChangedOnly;
#endif
  }

  // Setup to send a command with parameters like ST77xx code
  void sendCommand(uint8_t commandByte, const uint8_t *dataBytes, uint8_t numDataBytes);

protected:
  SPIClass *_pspi = nullptr;
  SPIClass::SPI_Hardware_t *_spi_hardware;

  uint8_t _spi_num;         // Which buss is this spi on?
  uint32_t _SPI_CLOCK;      // #define ILI9341_SPICLOCK 30000000
  uint32_t _SPI_CLOCK_READ; //#define ILI9341_SPICLOCK_READ 2000000

#if defined(KINETISK)
  KINETISK_SPI_t *_pkinetisk_spi;
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
  IMXRT_LPSPI_t *_pimxrt_spi;

#elif defined(KINETISL)
  KINETISL_SPI_t *_pkinetisl_spi;
#endif

  int16_t _width, _height; // Display w/h as modified by current rotation
  int16_t cursor_x, cursor_y;
  bool _center_x_text = false;
  bool _center_y_text = false;
  int16_t _clipx1, _clipy1, _clipx2, _clipy2;
  int16_t _originx, _originy;
  int16_t _displayclipx1, _displayclipy1, _displayclipx2, _displayclipy2;
  bool _invisible = false;
  bool _standard = true; // no bounding rectangle or origin set.

  uint16_t _x0_last = 0xffff;
  uint16_t _x1_last = 0xffff;
  uint16_t _y0_last = 0xffff;
  uint16_t _y1_last = 0xffff;

  inline void updateDisplayClip() {
    _displayclipx1 = max(0, min(_clipx1 + _originx, width()));
    _displayclipx2 = max(0, min(_clipx2 + _originx, width()));

    _displayclipy1 = max(0, min(_clipy1 + _originy, height()));
    _displayclipy2 = max(0, min(_clipy2 + _originy, height()));
    _invisible =
        (_displayclipx1 == _displayclipx2 || _displayclipy1 == _displayclipy2);
    _standard = (_displayclipx1 == 0) && (_displayclipx2 == _width) &&
                (_displayclipy1 == 0) && (_displayclipy2 == _height);
    if (Serial) {
      // Serial.printf("UDC (%d %d)-(%d %d) %d %d\n", _displayclipx1,
      // _displayclipy1, _displayclipx2,
      //	_displayclipy2, _invisible, _standard);
    }
  }

  int16_t scroll_x, scroll_y, scroll_width, scroll_height;
  boolean scrollEnable,
      isWritingScrollArea; // If set, 'wrap' text at right edge of display

  uint16_t textcolor, textbgcolor, scrollbgcolor;
  uint32_t textcolorPrexpanded, textbgcolorPrexpanded;
  uint8_t textsize_x, textsize_y, rotation, textdatum;
  boolean wrap; // If set, 'wrap' text at right edge of display
  const ILI9341_t3_font_t *font;
  // Anti-aliased font support
  uint8_t fontbpp = 1;
  uint8_t fontbppindex = 0;
  uint8_t fontbppmask = 1;
  uint8_t fontppb = 8;
  uint8_t *fontalphalut;
  float fontalphamx = 1;

  uint32_t padX;

  // GFX Font support
  const GFXfont *gfxFont = nullptr;
  int8_t _gfxFont_min_yOffset = 0;

  // Opaque font chracter overlap?
  unsigned int _gfx_c_last;
  int16_t _gfx_last_cursor_x, _gfx_last_cursor_y;
  int16_t _gfx_last_char_x_write = 0;
  uint16_t _gfx_last_char_textcolor;
  uint16_t _gfx_last_char_textbgcolor;
  bool gfxFontLastCharPosFG(int16_t x, int16_t y);

  uint8_t _rst;
  uint8_t _cs, _dc;
  uint8_t pcs_data, pcs_command;
  uint8_t _mosi, _sclk;

///////////////////////////////
// BUGBUG:: reorganize this area better!
#if defined(KINETISK)
  // inline uint8_t sizeFIFO() {return _fifo_size; }
  uint32_t _fifo_full_test;
  void waitFifoNotFull(void);
  void waitFifoEmpty(void);
  void waitTransmitComplete(void);
  void waitTransmitComplete(uint32_t mcr);

#elif defined(__IMXRT1052__) || defined(__IMXRT1062__)
  uint8_t pending_rx_count = 0; // hack ...
  void waitFifoNotFull(void);
  void waitFifoEmpty(void);
  void waitTransmitComplete(void);
  uint16_t waitTransmitCompleteReturnLast();
  void waitTransmitComplete(uint32_t mcr);
#elif defined(KINETISL)
#endif
//////////////////////////////

// add support to allow only one hardware CS (used for dc)
#if defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
  uint32_t _cspinmask;
  volatile uint32_t *_csport;
  uint32_t _spi_tcr_current;
  uint32_t _dcpinmask;
  uint32_t _tcr_dc_assert;
  uint32_t _tcr_dc_not_assert;
  volatile uint32_t *_dcport;
#else
  uint8_t _cspinmask;
  volatile uint8_t *_csport;
#endif
#ifdef KINETISL
  volatile uint8_t *_dcport;
  uint8_t _dcpinmask;
#endif
#ifdef ENABLE_GC9A01A_FRAMEBUFFER
  // Add support for optional frame buffer
  uint16_t *_pfbtft;              // Optional Frame buffer
  uint8_t _use_fbtft;             // Are we in frame buffer mode?
  uint16_t *_we_allocated_buffer; // We allocated the buffer;
  int16_t _changed_min_x, _changed_max_x, _changed_min_y, _changed_max_y;
  bool _updateChangedAreasOnly = false; // current default off,
  void (*_frame_complete_callback)() = nullptr;
  bool _frame_callback_on_HalfDone = false;

// Add DMA support.
  static GC9A01A_t3n *_dmaActiveDisplay[3]; // Use pointer to this as a way to

  volatile uint8_t _dma_state = 0;            // DMA status
  volatile uint32_t _dma_frame_count = 0;     // Can return a frame count...
  volatile uint16_t _dma_sub_frame_count = 0; // Can return a frame count...
#if defined(__MK66FX1M0__)
  // T3.6 use Scatter/gather with chain to do transfer
  static DMASetting _dmasettings[3][3];
  DMAChannel _dmatx;
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
  // try work around DMA memory cached.  So have a couple of buffers we copy
  // frame buffer into
  // as to move it out of the memory that is cached...

  static const uint32_t _count_pixels = GC9A01A_TFTWIDTH * GC9A01A_TFTHEIGHT;
  static GC9A01A_DMA_Data_t _dma_data[3]; // one structure for each possible SPI buss
  //DMASetting _dmasettings[3];
  //DMAChannel _dmatx;
  volatile uint32_t _dma_pixel_index = 0;
  uint16_t _dma_buffer_size; // the actual size we are using <= DMA_BUFFER_SIZE;
  uint16_t _dma_cnt_sub_frames_per_frame;
  uint32_t _spi_fcr_save; // save away previous FCR register value
#elif defined(__MK64FX512__)
  // T3.5 - had issues scatter/gather so do just use channels/interrupts
  // and update and continue
  static DMAChannel _dmatx;
  static DMAChannel _dmarx;
  static uint16_t _dma_count_remaining;
  static uint16_t _dma_write_size_words;
#endif
  static void dmaInterrupt(void);
  static void dmaInterrupt1(void);
  static void dmaInterrupt2(void);
  void process_dma_interrupt(void);
#endif
  void charBounds(char c, int16_t *x, int16_t *y, int16_t *minx, int16_t *miny,
                  int16_t *maxx, int16_t *maxy);

  void setAddr(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
      __attribute__((always_inline)) {
    #if 1
    writecommand_cont(GC9A01A_CASET); // Column addr set
    writedata16_cont(x0);             // XSTART
    writedata16_cont(x1);             // XEND
    writecommand_cont(GC9A01A_PASET); // Row addr set
    writedata16_cont(y0);             // YSTART
    writedata16_cont(y1);             // YEND
    #else
    if ((x0 != _x0_last) || (x1 != _x1_last)) {
      writecommand_cont(GC9A01A_CASET); // Column addr set
      writedata16_cont(x0);             // XSTART
      writedata16_cont(x1);             // XEND
      _x0_last = x0;
      _x1_last = x1;
    }
    if ((y0 != _y0_last) || (y1 != _y1_last)) {
      writecommand_cont(GC9A01A_PASET); // Row addr set
      writedata16_cont(y0);             // YSTART
      writedata16_cont(y1);             // YEND
      _y0_last = y0;
      _y1_last = y1;
    }
    #endif    
  }
//. From Onewire utility files
#if defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x

  void DIRECT_WRITE_LOW(volatile uint32_t *base, uint32_t mask)
      __attribute__((always_inline)) {
    *(base + 34) = mask;
  }
  void DIRECT_WRITE_HIGH(volatile uint32_t *base, uint32_t mask)
      __attribute__((always_inline)) {
    *(base + 33) = mask;
  }
#endif

  void beginSPITransaction(uint32_t clock) __attribute__((always_inline)) {
    _pspi->beginTransaction(SPISettings(clock, MSBFIRST, SPI_MODE0));
#if defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
    if (!_dcport)
      _spi_tcr_current = _pimxrt_spi->TCR; // Only if DC is on hardware CS
#endif
    if (_csport) {
#if defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
      DIRECT_WRITE_LOW(_csport, _cspinmask);
#else
      *_csport &= ~_cspinmask;
#endif
    }
  }
  void endSPITransaction() __attribute__((always_inline)) {
    if (_csport) {
#if defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
      DIRECT_WRITE_HIGH(_csport, _cspinmask);
#else
      *_csport |= _cspinmask;
#endif
    }
    _pspi->endTransaction();
  }
#if defined(KINETISK)
  void writecommand_cont(uint8_t c) __attribute__((always_inline)) {
    _pkinetisk_spi->PUSHR =
        c | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT;
    waitFifoNotFull();
  }
  void writedata8_cont(uint8_t c) __attribute__((always_inline)) {
    _pkinetisk_spi->PUSHR =
        c | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT;
    waitFifoNotFull();
  }
  void writedata16_cont(uint16_t d) __attribute__((always_inline)) {
    _pkinetisk_spi->PUSHR =
        d | (pcs_data << 16) | SPI_PUSHR_CTAS(1) | SPI_PUSHR_CONT;
    waitFifoNotFull();
  }
  void writecommand_last(uint8_t c) __attribute__((always_inline)) {
    uint32_t mcr = _pkinetisk_spi->MCR;
    _pkinetisk_spi->PUSHR =
        c | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_EOQ;
    waitTransmitComplete(mcr);
  }
  void writedata8_last(uint8_t c) __attribute__((always_inline)) {
    uint32_t mcr = _pkinetisk_spi->MCR;
    _pkinetisk_spi->PUSHR =
        c | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_EOQ;
    waitTransmitComplete(mcr);
  }
  void writedata16_last(uint16_t d) __attribute__((always_inline)) {
    uint32_t mcr = _pkinetisk_spi->MCR;
    _pkinetisk_spi->PUSHR =
        d | (pcs_data << 16) | SPI_PUSHR_CTAS(1) | SPI_PUSHR_EOQ;
    waitTransmitComplete(mcr);
  }
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
#ifndef TCR_MASK
#define TCR_MASK  (LPSPI_TCR_PCS(3) | LPSPI_TCR_FRAMESZ(31) | LPSPI_TCR_CONT | LPSPI_TCR_RXMSK )
#endif
  void maybeUpdateTCR(
      uint32_t requested_tcr_state) /*__attribute__((always_inline)) */ {
    if ((_spi_tcr_current & TCR_MASK) != requested_tcr_state) {
      bool dc_state_change = (_spi_tcr_current & LPSPI_TCR_PCS(3)) !=
                             (requested_tcr_state & LPSPI_TCR_PCS(3));
      _spi_tcr_current = (_spi_tcr_current & ~TCR_MASK) | requested_tcr_state;
      // only output when Transfer queue is empty.
      if (!dc_state_change || !_dcpinmask) {
        while ((_pimxrt_spi->FSR & 0x1f))
          ;
        _pimxrt_spi->TCR = _spi_tcr_current; // update the TCR

      } else {
        waitTransmitComplete();
        if (requested_tcr_state & LPSPI_TCR_PCS(3))
          DIRECT_WRITE_HIGH(_dcport, _dcpinmask);
        else
          DIRECT_WRITE_LOW(_dcport, _dcpinmask);
        _pimxrt_spi->TCR = _spi_tcr_current &
                           ~(LPSPI_TCR_PCS(3) |
                             LPSPI_TCR_CONT); // go ahead and update TCR anyway?
      }
    }
  }

  // BUGBUG:: currently assumming we only have CS_0 as valid CS
  void writecommand_cont(uint8_t c) __attribute__((always_inline)) {
    maybeUpdateTCR(_tcr_dc_assert | LPSPI_TCR_FRAMESZ(7) /*| LPSPI_TCR_CONT*/);
    _pimxrt_spi->TDR = c;
    pending_rx_count++; //
    waitFifoNotFull();
  }
  void writedata8_cont(uint8_t c) __attribute__((always_inline)) {
    maybeUpdateTCR(_tcr_dc_not_assert | LPSPI_TCR_FRAMESZ(7) | LPSPI_TCR_CONT);
    _pimxrt_spi->TDR = c;
    pending_rx_count++; //
    waitFifoNotFull();
  }
  void writedata16_cont(uint16_t d) __attribute__((always_inline)) {
    maybeUpdateTCR(_tcr_dc_not_assert | LPSPI_TCR_FRAMESZ(15) | LPSPI_TCR_CONT);
    _pimxrt_spi->TDR = d;
    pending_rx_count++; //
    waitFifoNotFull();
  }
  void writecommand_last(uint8_t c) __attribute__((always_inline)) {
    maybeUpdateTCR(_tcr_dc_assert | LPSPI_TCR_FRAMESZ(7));
    _pimxrt_spi->TDR = c;
    //		_pimxrt_spi->SR = LPSPI_SR_WCF | LPSPI_SR_FCF | LPSPI_SR_TCF;
    pending_rx_count++; //
    waitTransmitComplete();
  }
  void writedata8_last(uint8_t c) __attribute__((always_inline)) {
    maybeUpdateTCR(_tcr_dc_not_assert | LPSPI_TCR_FRAMESZ(7));
    _pimxrt_spi->TDR = c;
    //		_pimxrt_spi->SR = LPSPI_SR_WCF | LPSPI_SR_FCF | LPSPI_SR_TCF;
    pending_rx_count++; //
    waitTransmitComplete();
  }
  void writedata16_last(uint16_t d) __attribute__((always_inline)) {
    maybeUpdateTCR(_tcr_dc_not_assert | LPSPI_TCR_FRAMESZ(15));
    _pimxrt_spi->TDR = d;
    //		_pimxrt_spi->SR = LPSPI_SR_WCF | LPSPI_SR_FCF | LPSPI_SR_TCF;
    pending_rx_count++; //
    waitTransmitComplete();
  }

#elif defined(KINETISL)
  // Lets see how hard to make it work OK with T-LC
  uint8_t _dcpinAsserted;
  uint8_t _data_sent_not_completed;
  void waitTransmitComplete() {
    while (_data_sent_not_completed) {
      uint16_t timeout_count = 0xff; // hopefully enough
      while (!(_pkinetisl_spi->S & SPI_S_SPRF) && timeout_count--)
        ; // wait
      uint8_t d __attribute__((unused));
      d = _pkinetisl_spi->DL;
      d = _pkinetisl_spi->DH;
      _data_sent_not_completed--; // We hopefully received our data...
    }
  }
  uint16_t waitTransmitCompleteReturnLast() {
    uint16_t d = 0;
    while (_data_sent_not_completed) {
      uint16_t timeout_count = 0xff; // hopefully enough
      while (!(_pkinetisl_spi->S & SPI_S_SPRF) && timeout_count--)
        ; // wait
      d = (_pkinetisl_spi->DH << 8) | _pkinetisl_spi->DL;
      _data_sent_not_completed--; // We hopefully received our data...
    }
    return d;
  }

  void setCommandMode() __attribute__((always_inline)) {
    if (!_dcpinAsserted) {
      waitTransmitComplete();
      *_dcport &= ~_dcpinmask;
      _dcpinAsserted = 1;
    }
  }

  void setDataMode() __attribute__((always_inline)) {
    if (_dcpinAsserted) {
      waitTransmitComplete();
      *_dcport |= _dcpinmask;
      _dcpinAsserted = 0;
    }
  }

  void outputToSPI(uint8_t c) {
    if (_pkinetisl_spi->C2 & SPI_C2_SPIMODE) {
      // Wait to change modes until any pending output has been done.
      waitTransmitComplete();
      _pkinetisl_spi->C2 = 0; // make sure 8 bit mode.
    }
    while (!(_pkinetisl_spi->S & SPI_S_SPTEF))
      ; // wait if output buffer busy.
    // Clear out buffer if there is something there...
    if ((_pkinetisl_spi->S & SPI_S_SPRF)) {
      uint8_t d __attribute__((unused));
      d = _pkinetisl_spi->DL;
      _data_sent_not_completed--;
    }
    _pkinetisl_spi->DL = c;     // output byte
    _data_sent_not_completed++; // let system know we sent something
  }

  void outputToSPI16(uint16_t data) {
    if (!(_pkinetisl_spi->C2 & SPI_C2_SPIMODE)) {
      // Wait to change modes until any pending output has been done.
      waitTransmitComplete();
      _pkinetisl_spi->C2 = SPI_C2_SPIMODE; // make sure 8 bit mode.
    }
    uint8_t s;
    do {
      s = _pkinetisl_spi->S;
      // wait if output buffer busy.
      // Clear out buffer if there is something there...
      if ((s & SPI_S_SPRF)) {
        uint8_t d __attribute__((unused));
        d = _pkinetisl_spi->DL;
        d = _pkinetisl_spi->DH;
        _data_sent_not_completed--; // let system know we sent something
      }

    } while (!(s & SPI_S_SPTEF) || (s & SPI_S_SPRF));

    _pkinetisl_spi->DL = data;      // output low byte
    _pkinetisl_spi->DH = data >> 8; // output high byte
    _data_sent_not_completed++;     // let system know we sent something
  }

  void writecommand_cont(uint8_t c) {
    setCommandMode();
    outputToSPI(c);
  }
  void writedata8_cont(uint8_t c) {
    setDataMode();
    outputToSPI(c);
  }

  void writedata16_cont(uint16_t c) {
    setDataMode();
    outputToSPI16(c);
  }

  void writecommand_last(uint8_t c) {
    setCommandMode();
    outputToSPI(c);
    waitTransmitComplete();
  }
  void writedata8_last(uint8_t c) {
    setDataMode();
    outputToSPI(c);
    waitTransmitComplete();
  }
  void writedata16_last(uint16_t c) {
    setDataMode();
    outputToSPI16(c);
    waitTransmitComplete();
    _pkinetisl_spi->C2 = 0; // Set back to 8 bit mode...
    _pkinetisl_spi->S;      // Read in the status;
  }

#endif

#ifdef ENABLE_GC9A01A_FRAMEBUFFER
  void clearChangedRange() {
    _changed_min_x = 0x7fff;
    _changed_max_x = -1;
    _changed_min_y = 0x7fff;
    _changed_max_y = -1;
  }

  void updateChangedRange(int16_t x, int16_t y, int16_t w, int16_t h)
      __attribute__((always_inline)) {
    if (x < _changed_min_x)
      _changed_min_x = x;
    if (y < _changed_min_y)
      _changed_min_y = y;
    x += w - 1;
    y += h - 1;
    if (x > _changed_max_x)
      _changed_max_x = x;
    if (y > _changed_max_y)
      _changed_max_y = y;
  }

  // could combine with above, but avoids the +-...
  void updateChangedRange(int16_t x, int16_t y) __attribute__((always_inline)) {
    if (x < _changed_min_x)
      _changed_min_x = x;
    if (y < _changed_min_y)
      _changed_min_y = y;
    if (x > _changed_max_x)
      _changed_max_x = x;
    if (y > _changed_max_y)
      _changed_max_y = y;
  }
#endif


  void HLine(int16_t x, int16_t y, int16_t w, uint16_t color)
      __attribute__((always_inline)) {
#ifdef ENABLE_GC9A01A_FRAMEBUFFER
    if (_use_fbtft) {
      drawFastHLine(x, y, w, color);
      return;
    }
#endif
    x += _originx;
    y += _originy;

    // Rectangular clipping
    if ((y < _displayclipy1) || (x >= _displayclipx2) || (y >= _displayclipy2))
      return;
    if (x < _displayclipx1) {
      w = w - (_displayclipx1 - x);
      x = _displayclipx1;
    }
    if ((x + w - 1) >= _displayclipx2)
      w = _displayclipx2 - x;
    if (w < 1)
      return;

    setAddr(x, y, x + w - 1, y);
    writecommand_cont(GC9A01A_RAMWR);
    do {
      writedata16_cont(color);
    } while (--w > 0);
  }
  void VLine(int16_t x, int16_t y, int16_t h, uint16_t color)
      __attribute__((always_inline)) {
#ifdef ENABLE_GC9A01A_FRAMEBUFFER
    if (_use_fbtft) {
      drawFastVLine(x, y, h, color);
      return;
    }
#endif
    x += _originx;
    y += _originy;

    // Rectangular clipping
    if ((x < _displayclipx1) || (x >= _displayclipx2) || (y >= _displayclipy2))
      return;
    if (y < _displayclipy1) {
      h = h - (_displayclipy1 - y);
      y = _displayclipy1;
    }
    if ((y + h - 1) >= _displayclipy2)
      h = _displayclipy2 - y;
    if (h < 1)
      return;

    setAddr(x, y, x, y + h - 1);
    writecommand_cont(GC9A01A_RAMWR);
    do {
      writedata16_cont(color);
    } while (--h > 0);
  }
  /**
   * Found in a pull request for the Adafruit framebuffer library. Clever!
   * https://github.com/tricorderproject/arducordermini/pull/1/files#diff-d22a481ade4dbb4e41acc4d7c77f683d
   * Converts  0000000000000000rrrrrggggggbbbbb
   *     into  00000gggggg00000rrrrr000000bbbbb
   * with mask 00000111111000001111100000011111
   * This is useful because it makes space for a parallel fixed-point multiply
   * This implements the linear interpolation formula: result = bg * (1.0 -
   *alpha) + fg * alpha
   * This can be factorized into: result = bg + (fg - bg) * alpha
   * alpha is in Q1.5 format, so 0.0 is represented by 0, and 1.0 is represented
   *by 32
   * @param	fg		Color to draw in RGB565 (16bit)
   * @param	bg		Color to draw over in RGB565 (16bit)
   * @param	alpha	Alpha in range 0-255
   **/
  uint16_t alphaBlendRGB565(uint32_t fg, uint32_t bg, uint8_t alpha)
      __attribute__((always_inline)) {
    alpha = (alpha + 4) >> 3; // from 0-255 to 0-31
    bg = (bg | (bg << 16)) & 0b00000111111000001111100000011111;
    fg = (fg | (fg << 16)) & 0b00000111111000001111100000011111;
    uint32_t result =
        ((((fg - bg) * alpha) >> 5) + bg) & 0b00000111111000001111100000011111;
    return (uint16_t)((result >> 16) | result); // contract result
  }
  /**
   * Same as above, but fg and bg are premultiplied, and alpah is already in
   * range 0-31
   */
  uint16_t alphaBlendRGB565Premultiplied(uint32_t fg, uint32_t bg,
                                         uint8_t alpha)
      __attribute__((always_inline)) {
    uint32_t result =
        ((((fg - bg) * alpha) >> 5) + bg) & 0b00000111111000001111100000011111;
    return (uint16_t)((result >> 16) | result); // contract result
  }
  void Pixel(int16_t x, int16_t y, uint16_t color)
      __attribute__((always_inline)) {
    x += _originx;
    y += _originy;

    if ((x < _displayclipx1) || (x >= _displayclipx2) || (y < _displayclipy1) ||
        (y >= _displayclipy2))
      return;

#ifdef ENABLE_GC9A01A_FRAMEBUFFER
    if (_use_fbtft) {
      updateChangedRange(
          x, y); // update the range of the screen that has been changed;
      _pfbtft[y * _width + x] = color;
      return;
    }
#endif
    setAddr(x, y, x, y);
    writecommand_cont(GC9A01A_RAMWR);
    writedata16_cont(color);
  }
  void drawFontBits(bool opaque, uint32_t bits, uint32_t numbits, int32_t x,
                    int32_t y, uint32_t repeat);
  void drawFontPixel(uint8_t alpha, uint32_t x, uint32_t y);
  uint32_t fetchpixel(const uint8_t *p, uint32_t index, uint32_t x);
};

#ifndef GC9A01A_swap
#define GC9A01A_swap(a, b)                                                     \
  {                                                                            \
    typeof(a) t = a;                                                           \
    a = b;                                                                     \
    b = t;                                                                     \
  }
#endif

// To avoid conflict when also using Adafruit_GFX or any Adafruit library
// which depends on Adafruit_GFX, #include the Adafruit library *BEFORE*
// you #include ILI9341_t3.h.
// Warning the implemention of class needs to be here, else the code
// compiled in the c++ file will cause duplicate defines in the link phase.
//#ifndef _ADAFRUIT_GFX_H
#ifdef Adafruit_GFX_Button
#undef Adafruit_GFX_Button
#endif
#define Adafruit_GFX_Button GC9A01A_Button
class GC9A01A_Button {
public:
  GC9A01A_Button(void) { _gfx = NULL; }
  void initButton(GC9A01A_t3n *gfx, int16_t x, int16_t y, uint8_t w, uint8_t h,
                  uint16_t outline, uint16_t fill, uint16_t textcolor,
                  const char *label, uint8_t textsize_x, uint8_t textsize_y) {
    _x = x;
    _y = y;
    _w = w;
    _h = h;
    _outlinecolor = outline;
    _fillcolor = fill;
    _textcolor = textcolor;
    _textsize_x = textsize_x;
    _textsize_y = textsize_y;
    _gfx = gfx;
    strncpy(_label, label, 9);
    _label[9] = 0;
  }
  void drawButton(bool inverted = false) {
    uint16_t fill, outline, text;

    if (!inverted) {
      fill = _fillcolor;
      outline = _outlinecolor;
      text = _textcolor;
    } else {
      fill = _textcolor;
      outline = _outlinecolor;
      text = _fillcolor;
    }
    _gfx->fillRoundRect(_x - (_w / 2), _y - (_h / 2), _w, _h, min(_w, _h) / 4,
                        fill);
    _gfx->drawRoundRect(_x - (_w / 2), _y - (_h / 2), _w, _h, min(_w, _h) / 4,
                        outline);
    _gfx->setCursor(_x - strlen(_label) * 3 * _textsize_x,
                    _y - 4 * _textsize_y);
    _gfx->setTextColor(text);
    _gfx->setTextSize(_textsize_x, _textsize_y);
    _gfx->print(_label);
  }

  bool contains(int16_t x, int16_t y) {
    if ((x < (_x - _w / 2)) || (x > (_x + _w / 2)))
      return false;
    if ((y < (_y - _h / 2)) || (y > (_y + _h / 2)))
      return false;
    return true;
  }

  void press(boolean p) {
    laststate = currstate;
    currstate = p;
  }
  bool isPressed() { return currstate; }
  bool justPressed() { return (currstate && !laststate); }
  bool justReleased() { return (!currstate && laststate); }

private:
  GC9A01A_t3n *_gfx;
  int16_t _x, _y;
  uint16_t _w, _h;
  uint8_t _textsize_x, _textsize_y;
  uint16_t _outlinecolor, _fillcolor, _textcolor;
  char _label[10];
  boolean currstate, laststate;
};
//#endif

#endif // __cplusplus

#endif

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.