0

Electronic Animated Eyes using Teensy 3.1
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Re: Electronic Animated Eyes using Teensy 3.1

by chuckvideo on Mon Dec 28, 2015 11:41 am

I wonder if there might be even a more simple method to accomplish this, like perhaps a row of photoresistors arranged in a semicircle and using a comparator to "see" which photoresistor has just changed value the most - thus indicating which direction motion is coming from.

chuckvideo
 
Posts: 20
Joined: Wed Mar 03, 2010 8:25 pm

Re: Electronic Animated Eyes using Teensy 3.1

by pburgess on Mon Dec 28, 2015 3:04 pm

The joystick just position is read using analogRead() for the X&Y positions, the position of which is 'clipped' to stay inside a circle (so it doesn't look all the way out to the corners).

That's the easiest place to start. PIR sensors probably aren't the right way to go since they tend to have a very broad field of view.

pburgess
 
Posts: 3991
Joined: Sun Oct 26, 2008 2:29 am

Re: Electronic Animated Eyes using Teensy 3.1

by TopHatRaver on Sun Jan 24, 2016 3:55 pm

So I cannot seem to get this sketch to work at all on my Teensy. I downloaded the Blinky code to the Teensy and it worked fine - the led routinely blinks. But, when I try the given sketch, I get some errors but it still compiles and uploads to my Teensy, I presume. But the OLED display does nothing. I've included pictures of my set up as well. I've double checked the wiring but I'm sure I'm just missing something simple.

Odd that you spam filters didn't like the word "g o a t" anywhere in the post...hence the XXXX's :)

In file included from C:\Users\Tim\Documents\Arduino\Teensy3.1_Eyes-master\uncannyEyes\uncannyEyes.ino:27:0:
C:\Users\Tim\Documents\Arduino\libraries\Adafruit-SSD1351/Adafruit_SSD1351.h:22:0: warning: "swap" redefined [enabled by default]
#define swap(a, b) { uint16_t t = a; a = b; b = t; }
^
In file included from C:\Users\Tim\Documents\Arduino\Teensy3.1_Eyes-master\uncannyEyes\uncannyEyes.ino:17:0:
C:\Users\Tim\Documents\Arduino\libraries\Adafruit_GFX/Adafruit_GFX.h:11:0: note: this is the location of the previous definition
#define swap(a, b) { int16_t t = a; a = b; b = t; }
^
C:\Users\Tim\Documents\Arduino\Teensy3.1_Eyes-master\uncannyEyes\uncannyEyes.ino:460:17: warning: extra tokens at end of #ifdef directive [enabled by default]
#ifdef IRIS_PIN && (IRIS_PIN >= 0) // Interactive iris
^
C:\Users\Tim\Documents\Arduino\Teensy3.1_Eyes-master\uncannyEyes\uncannyEyes.ino: In function 'void frame(uint16_t)':
C:\Users\Tim\Documents\Arduino\Teensy3.1_Eyes-master\uncannyEyes\uncannyEyes.ino:327:67: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
if((t - eye[eyeIndex].blink.startTime) >= eye[eyeIndex].blink.duration) {
^
C:\Users\Tim\Documents\Arduino\Teensy3.1_Eyes-master\uncannyEyes\uncannyEyes.ino:404:33: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
if(s >= eye[eyeIndex].blink.duration) s = 255; // At or past blink end
^

Sketch uses 183,580 bytes (70%) of program storage space. Maximum is 262,144 bytes.
Global variables use 4,876 bytes (7%) of dynamic memory, leaving 60,660 bytes for local variables. Maximum is 65,536 bytes.

Code: Select all | TOGGLE FULL SIZE
//--------------------------------------------------------------------------
// Uncanny eyes for PJRC Teensy 3.1 with Adafruit 1.5" OLED (product #1431)
// or 1.44" TFT LCD (#2088).  This uses Teensy-3.1-specific features and
// WILL NOT work on normal Arduino or other boards!  Use 72 MHz (Optimized)
// board speed -- OLED does not work at 96 MHz.
//
// 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>      // Core graphics lib for Adafruit displays
// Enable ONE of these #includes -- HUGE graphics tables for various eyes:
#include "defaultEye.h"        // Standard human-ish hazel eye
//#include "noScleraEye.h"       // Large iris, no sclera
//#include "dragonEye.h"         // Slit pupil fiery dragon/demon eye
//#include "XXXXEye.h"           // Horizontal pupil XXXX/Krampus eye
// Then tweak settings below, e.g. change IRIS_MIN/MAX or disable TRACKING.

// DISPLAY HARDWARE CONFIG -------------------------------------------------

#include <Adafruit_SSD1351.h>  // OLED display library -OR-
//#include <Adafruit_ST7735.h> // TFT display library (enable one only)

#ifdef _ADAFRUIT_ST7735H_
typedef Adafruit_ST7735  displayType; // Using TFT display(s)
#else
typedef Adafruit_SSD1351 displayType; // Using OLED display(s)
#endif

#define DISPLAY_DC      7 // Data/command pin for BOTH displays
#define DISPLAY_RESET   8 // Reset pin for BOTH displays
#define SELECT_L_PIN    9 // LEFT eye chip select pin
#define SELECT_R_PIN   10 // RIGHT eye chip select pin

// INPUT CONFIG (for eye motion -- enable or comment out as needed) --------

//#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 set, reverse stick X axis
//#define JOYSTICK_Y_FLIP   // If set, reverse stick Y axis
#define TRACKING          // If enabled, eyelid tracks pupil
#define IRIS_PIN       A2 // Photocell or potentiometer (else auto iris)
//#define IRIS_PIN_FLIP     // If set, reverse reading from dial/photocell
#define IRIS_SMOOTH       // If enabled, filter input from IRIS_PIN
#define IRIS_MIN      120 // Clip lower analogRead() range from IRIS_PIN
#define IRIS_MAX      720 // Clip upper "
#define WINK_L_PIN      0 // Pin for LEFT eye wink button
#define BLINK_PIN       1 // Pin for blink button (BOTH eyes)
#define WINK_R_PIN      2 // Pin for RIGHT eye wink button
#define AUTOBLINK         // If enabled, eyes blink autonomously

// Probably don't need to edit any config below this line, -----------------
// unless building a single-eye project (pendant, etc.), in which case one
// of the two elements in the eye[] array further down can be commented out.

// Eye blinks are a tiny 3-state machine.  Per-eye allows winks + blinks.
#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 {
  int8_t   pin;       // Optional button here for indiv. wink
  uint8_t  state;     // NOBLINK/ENBLINK/DEBLINK
  int32_t  duration;  // Duration of blink state (micros)
  uint32_t startTime; // Time (micros) of last state change
} eyeBlink;

struct {
  displayType display; // OLED/TFT object
  uint8_t     cs;      // Chip select pin
  eyeBlink    blink;   // Current blink state
} eye[] = { // OK to comment out one of these for single-eye display:
  displayType(SELECT_L_PIN,DISPLAY_DC,0),SELECT_L_PIN,{WINK_L_PIN,NOBLINK},
  displayType(SELECT_R_PIN,DISPLAY_DC,0),SELECT_R_PIN,{WINK_R_PIN,NOBLINK},
};
#define NUM_EYES (sizeof(eye) / sizeof(eye[0]))


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

void setup(void) {
  uint8_t e;

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

  // Both displays share a common reset line; 0 is passed to display
  // constructor (so no reset in begin()) -- must reset manually here:
  pinMode(DISPLAY_RESET, OUTPUT);
  digitalWrite(DISPLAY_RESET, LOW);  delay(1);
  digitalWrite(DISPLAY_RESET, HIGH); delay(50);

  for(e=0; e<NUM_EYES; e++) { // Deselect all
    pinMode(eye[e].cs, OUTPUT);
    digitalWrite(eye[e].cs, HIGH);
  }
  for(e=0; e<NUM_EYES; e++) {
    digitalWrite(eye[e].cs, LOW); // Select one eye for init
#ifdef _ADAFRUIT_ST7735H_ // TFT
    eye[e].display.initR(INITR_144GREENTAB);
#else // OLED
    eye[e].display.begin();
#endif
    if(eye[e].blink.pin >= 0) pinMode(eye[e].blink.pin, INPUT_PULLUP);
    digitalWrite(eye[e].cs, HIGH); // Deselect
  }
#ifdef BLINK_PIN
  pinMode(BLINK_PIN, INPUT_PULLUP);
#endif

  // 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!
#ifdef _ADAFRUIT_ST7735H_ // TFT
  digitalWrite(eye[0].cs , LOW);
  digitalWrite(DISPLAY_DC, LOW);
  SPI.transfer(ST7735_MADCTL);
  digitalWrite(DISPLAY_DC, HIGH);
  SPI.transfer(0x88); // MADCTL_MY | MADCTL_BGR
  digitalWrite(eye[0].cs , HIGH);
#else // OLED
  eye[0].display.writeCommand(SSD1351_CMD_SETREMAP);
  eye[0].display.writeData(0x76);
#endif
}


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

SPISettings settings(24000000, MSBFIRST, SPI_MODE3); // Teensy 3.1 max SPI

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);
#ifdef _ADAFRUIT_ST7735H_ // TFT
  eye[e].display.setAddrWindow(0, 0, 127, 127);
#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(eye[e].cs, LOW);                       // 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++) {
    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
        }
      }
      // 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;
    }
  }

  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
  digitalWrite(eye[e].cs, 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

#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;
  uint32_t        t = micros(); // Time at start of function

  Serial.println((++frames * 1000) / millis()); // Show frame rate

  if(++eyeIndex >= NUM_EYES) eyeIndex = 0; // Cycle through eyes, 1 per call

  // 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
    }
  }

#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...
        ((digitalRead(BLINK_PIN) == LOW) ||         // blink or wink held...
          digitalRead(eye[eyeIndex].blink.pin) == 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(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 if(digitalRead(eye[eyeIndex].blink.pin) == 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

  // 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()
  eyeX = map(eyeX, 0, 1023, 0, SCLERA_WIDTH  - 128);
  eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128);
  if(eyeIndex == 1) eyeX = (SCLERA_WIDTH - 128) - 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...
  eyeX += 4;
  if(eyeX > (SCLERA_WIDTH - 128)) 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.
  static uint8_t uThreshold = 128;
  uint8_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(IRIS_PIN) || (IRIS_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 // !IRIS_PIN


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

void loop() {

#ifdef IRIS_PIN && (IRIS_PIN >= 0) // Interactive iris

  uint16_t v = analogRead(IRIS_PIN);       // Raw dial/photocell reading
#ifdef IRIS_PIN_FLIP
  v = 1023 - v;
#endif
  v = map(v, 0, 1023, IRIS_MIN, IRIS_MAX); // Scale to iris range
#ifdef IRIS_SMOOTH // Filter input (gradual motion)
  static uint16_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 // IRIS_PIN
}
Attachments
OLED.JPG
OLED.JPG (33.69 KiB) Viewed 836 times
Wires.JPG
Wires.JPG (31.03 KiB) Viewed 836 times
board.JPG
My breadboard wiring
board.JPG (87.75 KiB) Viewed 836 times

TopHatRaver
 
Posts: 50
Joined: Wed Jul 29, 2015 3:56 am

Re: Electronic Animated Eyes using Teensy 3.1

by pburgess on Sun Jan 24, 2016 5:05 pm

Looks like the wires to pins 7 & 9 are switched. The compiler messages are just pedantic warnings and nothing fatal, so it'll finish & upload anyway.

Also, looks like the headers aren't soldered to the Teensy board? Can't really count on those connections. I know the breadboard is a temporary state & you'll probably solder it all in a more compact state...but even just a tiny smidgen of solder on the header pins you're actually using will help...you don't have to solder the whole header. You can break off a few 'sacrificial pins,' solder them to just those points on the board, test in the breadboard, then desolder one at a time by reheating the joint & pulling out with tweezers or small pliers.

pburgess
 
Posts: 3991
Joined: Sun Oct 26, 2008 2:29 am

Re: Electronic Animated Eyes using Teensy 3.1

by TopHatRaver on Mon Jan 25, 2016 1:25 am

Thanks for the quick reply. I ended up soldering pins to the Teensy as I had problems in the past with unreliable connections on the breadboard without soldering. I also just plugged the OLED directly into the Teensy without the breadboard - figured why not, I got the female-female wires. It came down to a wrong wire on the wrong pin. My SI was going to MISO instead of MOSI! I knew it was something simple I was missing. Thanks again, and thanks for making this sketch in the first place, its awesome and will make my costume amazing at EDC!

TopHatRaver
 
Posts: 50
Joined: Wed Jul 29, 2015 3:56 am

Re: Electronic Animated Eyes using Teensy 3.1

by pqmaker on Mon Feb 01, 2016 12:46 pm

Hello,

We want to make a large pair of animated eyes for a puppet of a cow.
Will this project work with the larger TFT LCD Panels (2.4",2.8", 3.5) or does it only work with the 1.44" LCD?

Thank you

pqmaker
 
Posts: 1
Joined: Mon Feb 01, 2016 12:40 pm

Re: Electronic Animated Eyes using Teensy 3.1

by pburgess on Mon Feb 01, 2016 1:01 pm

pqmaker: only works with the 1.44/1.5" displays. The larger screens have about 5x the pixels, which would exceed the processing and internal storage capabilities of the microcontroller.

pburgess
 
Posts: 3991
Joined: Sun Oct 26, 2008 2:29 am

Re: Electronic Animated Eyes using Teensy 3.1

by Dragten on Tue Feb 23, 2016 7:54 am

Hello! First of all, a million thanks to you, pburgess, for the amazing project and tutorial, I cannot thank you enough.

However, I have a question:
Is it possible to somehow include both random auto look-around and manual one? Either changing with a flip switch or manual one overriding the auto when joystick is in use?

Bonus question:
I have grabbed the eyes python script off GitHub, I have Python 2.7 and Imaging Library installed, but I have no idea how to run the bastard. Having it in the same folder does not seem to produce any .h files, even though I see the command line working when I start it.
Alternatively, how to use it to create the dragon eyes? I am just interested in changing the color of the iris.

Dragten
 
Posts: 10
Joined: Tue Feb 23, 2016 7:51 am

Re: Electronic Animated Eyes using Teensy 3.1

by pburgess on Tue Feb 23, 2016 1:19 pm

Both autonomous and manual eye motion: it is possible, but requires getting into the code a bit; there's no simple "turn both on" configuration option.

First, you'd need a switch or button between ground and one of the digital pins (let's say pin #3). Then enable a pullup on the same pin in setup(), a la:
Code: Select all | TOGGLE FULL SIZE
pinMode(3, INPUT_PULLUP);

Elsewhere in the code...in the frame() function specifically, there's a big chunk of code starting with this comment around line 239:
Code: Select all | TOGGLE FULL SIZE
  // X/Y movement

Just below that you'll see an "#if defined" block and an "#else" block...the first of these is for manual control, second is for autonomous control. You'd need to replace the #if/#else/#endif (preprocessor directives) with regular C if/else conditions, based on the state of the switch, e.g.:
Code: Select all | TOGGLE FULL SIZE
if(digitalRead(3)) {
  // Manual control code goes here
} else {
  // Autonomous code goes here
}

That should handle a basic switching between the two, though it'll "jump" when switching modes. Hopefully that'd good enough, because eliminating the jump would require a whole bunch of additional code...not just reading the state of the switch, but watching for it for changes and having some additional code to handle the manual-to-autonomous and autonomous-to-manual transitions.

Running the Python code: here's the invocation for the default (human) eye:
Code: Select all | TOGGLE FULL SIZE
python tablegen.py defaultEye/sclera.png defaultEye/iris.png defaultEye/upper.png defaultEye/lower.png 80 > defaultEye.h

You won't see anything exciting when this runs, it's all redirected to the .h file. Try changing "defaultEye.h" to a different file (e.g. "fooEye.h") and see what you get.

I think the iris size (the '80') above was 160 in the case of the dragon eye.

pburgess
 
Posts: 3991
Joined: Sun Oct 26, 2008 2:29 am

Re: Electronic Animated Eyes using Teensy 3.1

by Dragten on Tue Feb 23, 2016 1:28 pm

Thank you very much for very detailed help, I appreciate it immensely.
I will try to get it to work!
Best regards.

Dragten
 
Posts: 10
Joined: Tue Feb 23, 2016 7:51 am

Re: Electronic Animated Eyes using Teensy 3.1

by Dragten on Fri Feb 26, 2016 12:27 pm

pburgess, thank you once again, I have made it to work without any problems. In fact, it works like a charm, nothing bad happens when I switch between modes, no "jumps".

As for the python, running the command, I just get the "syntax error", :/

In different issues: when in manual mode, the eye twitches a lot when in neutral position. It stops if the joystick moves all the way to X or Y. Can this be caused by the joystick itself?

Dragten
 
Posts: 10
Joined: Tue Feb 23, 2016 7:51 am

Re: Electronic Animated Eyes using Teensy 3.1

by pburgess on Fri Feb 26, 2016 1:59 pm

Regarding the eye twitch: yeah, a little 'noise' is normal in the potentiometer wipers that make up the joystick.

There's a couple ways we could try to mitigate this in software. One is to have some minimum threshold of movement before it triggers. In the global section (before any code), could try adding:
Code: Select all | TOGGLE FULL SIZE
#define XY_THRESHOLD 5
int16_t prevEyeX = 0, prevEyeY = 0;

Then, in the frame() function, just BEFORE this line:
Code: Select all | TOGGLE FULL SIZE
#else // Autonomous X/Y eye motion

add:
Code: Select all | TOGGLE FULL SIZE
dx = eyeX - prevEyeX; // Distance from prior pos to current pos
dy = eyeY - prevEyeY;
if((dx * dx + dy * dy) < (XY_THRESHOLD * XY_THRESHOLD)) {
  eyeX = prevEyeX; // Not enough movement
  eyeY = prevEyeY; // ignore it -- keep using old position
} else {
  prevEyeX = eyeX; // Keep it & note new position
  prevEyeY = eyeY;
}


That might look a little hokey and twitchy when moving though, I'm not sure. Try it! (And try different values for XY_THRESHOLD -- larger values = more movement needed.)
A different approach is to use a low-pass filter to smooth out any small motions...however, this might have a deleterious effect on larger motions (or might look really cool, can'y say for sure).

In globals:
Code: Select all | TOGGLE FULL SIZE
#define FILTER_FACTOR 10
int16_t prevEyeX = 0, prevEyeY = 0;

And in the same spot in the frame() function, use this instead:
Code: Select all | TOGGLE FULL SIZE
eyeX = (((long)prevEyeX * (FILTER_FACTOR-1)) + eyeX) / FILTER_FACTOR;
eyeY = (((long)prevEyeY * (FILTER_FACTOR-1)) + eyeY) / FILTER_FACTOR;
prevEyeX = eyeX;
prevEyeY = eyeY;

Then try different values for 'FILTER_FACTOR' -- larger values will be smoother, but at some point it's just gonna be too "sloshy" and not lifelike.

Could also use both in combination, but that'll require some adjustments in the above code...try one or the other first and see if it's sufficient to solve the issue.

pburgess
 
Posts: 3991
Joined: Sun Oct 26, 2008 2:29 am

Re: Electronic Animated Eyes using Teensy 3.1

by jwheat058 on Thu Mar 03, 2016 2:13 pm

Hello - Not sure if this is the right place to post this question, but I have my set of animated eyes working with the joystick option and everything looks and works great! The question that I have is... Is there a way to control the eye movement wirelessly? Say have the joystick in my hands about 15 feet away and control the eye movement via RF? Thanks! Jeff

jwheat058
 
Posts: 11
Joined: Sat Sep 26, 2015 11:43 am

Re: Electronic Animated Eyes using Teensy 3.1

by pburgess on Thu Mar 03, 2016 3:15 pm

It's technically possible, but there's no simple "enable this" bit in the code like some of the other features. Actually it would require quite a bit of engineering. Wireless anything is kinda not-fun, wireless analog anything doubly so.

Two likely approaches come to mind:

1. Add a Bluefruit LE UART and use the Circuit Playground app on iOS/Android to use the phone's accelerometer as a makeshift joystick, reading data over one of the Teensy's serial ports. Difficulty Level 3/5 (where 5 is "ow ow slamming my hand in the car door"). You'd likely need to make changes to the wiring because the wink/blink inputs and the display control pins overlap ALL the available hardware serial ports on the Teensy (I'd suggest using Serial1 and move wink-left and blink to other pins, don't mess with the screen stuff) (or you COULD use SoftwareSerial on other pins but I'd rather keep as much processor oomph available for rendering the animation). See the 'Bluetooth-Controlled NeoPixel Goggles' project for bare-minimum example code for reading from BLE UART.

2. Use two XBee modules, paired together and one configured for serial output and connected to Teensy's Serial1 as above, the other configured for ADC input (from the analog joystick). Understanding and configuring some of the stranger (non-UART) modes of XBee would make this like Difficultly Level 5/5 (or worse, if there is such a thing, like first setting fire to your hand THEN slamming it in the car door).

pburgess
 
Posts: 3991
Joined: Sun Oct 26, 2008 2:29 am

Re: Electronic Animated Eyes using Teensy 3.1

by jwheat058 on Thu Mar 03, 2016 3:25 pm

Ha! That's kinda what I thought (that it would not be easy)... And I've slammed my hand my share of car doors, so I may just leave it at that... Thanks! Jeff

jwheat058
 
Posts: 11
Joined: Sat Sep 26, 2015 11:43 am

Please be positive and constructive with your questions and comments.