Add 2 animated eyes examples

Animated_Eyes_1 is an example for a single display
Animated_Eyes_2 is an example for two displays
This commit is contained in:
Bodmer 2020-11-08 22:53:23 +00:00
parent 975347d5de
commit 24b0eca084
35 changed files with 260676 additions and 0 deletions

View File

@ -0,0 +1,140 @@
// An adaption of the "UncannyEyes" sketch (see eye_functions tab)
// for the TFT_eSPI library. As written the sketch is for driving
// one (240x320 minimum) TFT display, showing 2 eyes. See example
// Animated_Eyes_2 for a dual 128x128 TFT display configued sketch.
// The number of displays and chip selects used are defined in the
// config.h tab. The display count can be set to 1. If using one
// TFT and the chip select for that display is already defined in
// the TFT_eSPI library then change the chip select pins to -1 in the
// "config.h" tab.
// The wiring for 2 TFT displays to an ESP32 is described in the
// "wiring" tab of this sketch.
// Configuration settings for the eye, eye style, display count,
// chip selects and x offsets can be defined in the sketch "config.h" tab.
// Performance (frames per second = fps) can be improved by using
// DMA (for SPI displays only) on ESP32 and STM32 processors. Use
// as high a SPI clock rate as is supported by the display. 27MHz
// minimum, some diplays can be operated at higher clock rates in
// the range 40-80MHz.
// Single defaultEye performance for different processors
// No DMA With DMA
// ESP8266 (160MHz CPU) 40MHz SPI 36 fps
// ESP32 27MHz SPI 53 fps 85 fps
// ESP32 40MHz SPI 67 fps 102 fps
// ESP32 80MHz SPI 82 fps 116 fps // Note: Few displays work reliably at 80MHz
// STM32F401 55MHz SPI 44 fps 90 fps
// STM32F446 55MHz SPI 83 fps 155 fps
// STM32F767 55MHz SPI 136 fps 197 fps
// DMA can be used with STM32 and ESP32 processors when the interface
// is SPI, uncomment the next line:
#define USE_DMA
// Load TFT driver library
#include <SPI.h>
#include <TFT_eSPI.h>
TFT_eSPI tft; // A single instance is used for 1 or 2 displays
// A pixel buffer is used during eye rendering
#define BUFFER_SIZE 1024 // 128 to 1024 seems optimum
#ifdef USE_DMA
#define BUFFERS 2 // 2 toggle buffers with DMA
#else
#define BUFFERS 1 // 1 buffer for no DMA
#endif
uint16_t pbuffer[BUFFERS][BUFFER_SIZE]; // Pixel rendering buffer
bool dmaBuf = 0; // DMA buffer selection
// This struct is populated in config.h
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 and the x offset
int16_t xposition; // position of eye on the screen
} eyeInfo_t;
#include "config.h" // ****** CONFIGURATION IS DONE IN HERE ******
extern void user_setup(void); // Functions in the user*.cpp files
extern void user_loop(void);
#define SCREEN_X_START 0
#define SCREEN_X_END SCREEN_WIDTH // Badly named, actually the "eye" width!
#define SCREEN_Y_START 0
#define SCREEN_Y_END SCREEN_HEIGHT // Actually "eye" height
// 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;
struct { // One-per-eye structure
int16_t tft_cs; // Chip select pin for each display
eyeBlink blink; // Current blink/wink state
int16_t xposition; // x position of eye image
} eye[NUM_EYES];
uint32_t startTime; // For FPS indicator
// INITIALIZATION -- runs once at startup ----------------------------------
void setup(void) {
Serial.begin(115200);
//while (!Serial);
Serial.println("Starting");
#if defined(DISPLAY_BACKLIGHT) && (DISPLAY_BACKLIGHT >= 0)
// Enable backlight pin, initially off
Serial.println("Backlight turned off");
pinMode(DISPLAY_BACKLIGHT, OUTPUT);
digitalWrite(DISPLAY_BACKLIGHT, LOW);
#endif
// User call for additional features
user_setup();
// Initialiase the eye(s), this will set all chip selects low for the tft.init()
initEyes();
// Initialise TFT
Serial.println("Initialising displays");
tft.init();
#ifdef USE_DMA
tft.initDMA();
#endif
// Raise chip select(s) so that displays can be individually configured
digitalWrite(eye[0].tft_cs, HIGH);
if (NUM_EYES > 1) digitalWrite(eye[1].tft_cs, HIGH);
for (uint8_t e = 0; e < NUM_EYES; e++) {
digitalWrite(eye[e].tft_cs, LOW);
tft.setRotation(eyeInfo[e].rotation);
tft.fillScreen(TFT_BLACK);
digitalWrite(eye[e].tft_cs, HIGH);
}
#if defined(DISPLAY_BACKLIGHT) && (DISPLAY_BACKLIGHT >= 0)
Serial.println("Backlight now on!");
analogWrite(DISPLAY_BACKLIGHT, BACKLIGHT_MAX);
#endif
startTime = millis(); // For frame-rate calculation
}
// MAIN LOOP -- runs continuously after setup() ----------------------------
void loop() {
updateEye();
}

View File

@ -0,0 +1,93 @@
// 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.
//#define SYMMETRICAL_EYELID
// Enable ONE of these #includes -- HUGE graphics tables for various eyes:
#include "data/defaultEye.h" // Standard human-ish hazel eye -OR-
//#include "data/dragonEye.h" // Slit pupil fiery dragon/demon eye -OR-
//#include "data/noScleraEye.h" // Large iris, no sclera -OR-
//#include "data/goatEye.h" // Horizontal pupil goat/Krampus eye -OR-
//#include "data/newtEye.h" // Eye of newt -OR-
//#include "data/terminatorEye.h" // Git to da choppah!
//#include "data/catEye.h" // Cartoonish cat (flat "2D" colors)
//#include "data/owlEye.h" // Minerva the owl (DISABLE TRACKING)
//#include "data/naugaEye.h" // Nauga googly eye (DISABLE TRACKING)
//#include "data/doeEye.h" // Cartoon deer eye (DISABLE TRACKING)
// DISPLAY HARDWARE SETTINGS (screen type & connections) -------------------
#define TFT_COUNT 1 // Number of screens (1 or 2)
#define TFT1_CS -1 // TFT 1 chip select pin (set to -1 to use TFT_eSPI setup)
#define TFT2_CS -1 // TFT 2 chip select pin (set to -1 to use TFT_eSPI setup)
#define TFT_1_ROT 1 // TFT 1 rotation
#define TFT_2_ROT 1 // TFT 2 rotation
#define EYE_1_XPOSITION 0 // x shift for eye 1 image on display
#define EYE_2_XPOSITION 320 - 128 // x shift for eye 2 image on display
#define DISPLAY_BACKLIGHT -1 // Pin for backlight control (-1 for none)
#define BACKLIGHT_MAX 255
// EYE LIST ----------------------------------------------------------------
#define NUM_EYES 2 // Number of eyes to display (1 or 2)
#define BLINK_PIN -1 // Pin for manual blink button (BOTH eyes)
#define LH_WINK_PIN -1 // Left wink pin (set to -1 for no pin)
#define RH_WINK_PIN -1 // Right wink pin (set to -1 for no pin)
// 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), a screen
// rotation value (0-3) and x position offset for that eye.
#if (NUM_EYES == 2)
eyeInfo_t eyeInfo[] = {
{ TFT1_CS, LH_WINK_PIN, TFT_1_ROT, EYE_1_XPOSITION }, // LEFT EYE chip select and wink pins, rotation and offset
{ TFT2_CS, RH_WINK_PIN, TFT_2_ROT, EYE_2_XPOSITION }, // RIGHT EYE chip select and wink pins, rotation and offset
};
#else
eyeInfo_t eyeInfo[] = {
{ TFT1_CS, LH_WINK_PIN, TFT_1_ROT, EYE_1_XPOSITION }, // EYE chip select and wink pins, rotation and offset
};
#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 LIGHT_PIN -1 // Light sensor pin
#define LIGHT_CURVE 0.33 // Light sensor adjustment curve
#define LIGHT_MIN 0 // Minimum useful reading from light sensor
#define LIGHT_MAX 1023 // Maximum useful 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 90 // Iris size (0-1023) in brightest light
#endif
#if !defined(IRIS_MAX)
#define IRIS_MAX 130 // Iris size (0-1023) in darkest light
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
// Logo helps with screen orientation & positioning
#define LOGO_TOP_WIDTH 59
#define LOGO_TOP_HEIGHT 59
const uint8_t logo_top[472] PROGMEM= {
0X00, 0X00, 0X00, 0X01, 0XC0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03,
0XC0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X07, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X0F,
0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X0F, 0XF0, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X1F, 0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3F,
0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X7F, 0XF8, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X7F, 0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0XFF,
0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X01, 0XFF, 0XFC, 0X00, 0X00, 0X00,
0X00, 0X00, 0X01, 0XFF, 0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X01, 0XFF,
0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XFF, 0XFC, 0X00, 0X00, 0X00,
0X00, 0X00, 0X03, 0XFF, 0XFE, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0X83, 0XFF,
0XFE, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XF3, 0XFF, 0XFE, 0X00, 0X00, 0X00,
0XFF, 0XFF, 0XFB, 0XFF, 0XFC, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFF, 0XFF,
0XFC, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFF, 0XFB, 0XFC, 0X30, 0X00, 0X00,
0X3F, 0XFF, 0XFF, 0XF1, 0XFB, 0XFF, 0X00, 0X00, 0X1F, 0XFF, 0XFF, 0XF1,
0XFF, 0XFF, 0XE0, 0X00, 0X1F, 0XFF, 0XFF, 0XE1, 0XFF, 0XFF, 0XFE, 0X00,
0X0F, 0XFF, 0XFF, 0XE1, 0XFF, 0XFF, 0XFF, 0X80, 0X07, 0XFF, 0XEF, 0XE1,
0XFF, 0XFF, 0XFF, 0XE0, 0X03, 0XFF, 0XC1, 0XE3, 0XFF, 0XFF, 0XFF, 0XE0,
0X03, 0XFF, 0XC0, 0XF3, 0XFF, 0XFF, 0XFF, 0XE0, 0X01, 0XFF, 0XF0, 0X7F,
0XC3, 0XFF, 0XFF, 0XC0, 0X00, 0XFF, 0XF8, 0X7F, 0X01, 0XFF, 0XFF, 0X00,
0X00, 0X7F, 0XFF, 0XFE, 0X03, 0XFF, 0XFE, 0X00, 0X00, 0X1F, 0XFF, 0XFF,
0X0F, 0XFF, 0XFC, 0X00, 0X00, 0X07, 0XFF, 0XFF, 0XFF, 0XFF, 0XF0, 0X00,
0X00, 0X01, 0XFF, 0X3F, 0XFF, 0XFF, 0XE0, 0X00, 0X00, 0X07, 0XFC, 0X39,
0XFF, 0XFF, 0X80, 0X00, 0X00, 0X0F, 0XF8, 0X38, 0XFF, 0XFF, 0X00, 0X00,
0X00, 0X1F, 0XF0, 0X78, 0X7F, 0XFC, 0X00, 0X00, 0X00, 0X3F, 0XF0, 0XF8,
0X7F, 0X00, 0X00, 0X00, 0X00, 0X3F, 0XF1, 0XFC, 0X7F, 0X80, 0X00, 0X00,
0X00, 0X7F, 0XFF, 0XFE, 0X3F, 0XC0, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFE,
0X3F, 0XC0, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XE0, 0X00, 0X00,
0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XE0, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF,
0XFF, 0XE0, 0X00, 0X00, 0X01, 0XFF, 0XFF, 0XBF, 0XFF, 0XE0, 0X00, 0X00,
0X01, 0XFF, 0XFF, 0XBF, 0XFF, 0XE0, 0X00, 0X00, 0X01, 0XFF, 0XFF, 0X1F,
0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XFE, 0X1F, 0XFF, 0XE0, 0X00, 0X00,
0X03, 0XFF, 0XFC, 0X0F, 0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XF0, 0X0F,
0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XC0, 0X07, 0XFF, 0XE0, 0X00, 0X00,
0X07, 0XFE, 0X00, 0X03, 0XFF, 0XE0, 0X00, 0X00, 0X07, 0XF0, 0X00, 0X01,
0XFF, 0XE0, 0X00, 0X00, 0X03, 0X80, 0X00, 0X00, 0X7F, 0XE0, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0X3F, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X0F, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X07, 0XE0, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X00, 0XE0, 0X00, 0X00 };
#define LOGO_BOTTOM_WIDTH 128
#define LOGO_BOTTOM_HEIGHT 37
const uint8_t logo_bottom[592] PROGMEM= {
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X00, 0X7F, 0X00, 0X00, 0X00,
0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X00,
0XFF, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00,
0X3E, 0X00, 0X00, 0X01, 0XFF, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00,
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01, 0XFF, 0X00, 0X00, 0X00,
0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01,
0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00,
0X3E, 0X00, 0X00, 0X01, 0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0,
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01, 0XF0, 0X00, 0X00, 0X00,
0X00, 0X00, 0X03, 0XE0, 0X1F, 0XFF, 0X00, 0XFE, 0X3E, 0X07, 0XFF, 0XC1,
0XFF, 0X1F, 0X0E, 0X7C, 0X03, 0XE3, 0XE3, 0XFF, 0X3F, 0XFF, 0X81, 0XFF,
0XBE, 0X0F, 0XFF, 0XE1, 0XFF, 0X1F, 0X3E, 0X7C, 0X03, 0XE3, 0XE3, 0XFF,
0X7F, 0XFF, 0XC3, 0XFF, 0XFE, 0X1F, 0XFF, 0XF1, 0XFF, 0X1F, 0X7E, 0X7C,
0X03, 0XE3, 0XE3, 0XFF, 0X7F, 0XFF, 0XC7, 0XFF, 0XFE, 0X1F, 0XFF, 0XF1,
0XFF, 0X1F, 0XFE, 0X7C, 0X03, 0XE3, 0XE3, 0XFF, 0X7E, 0X0F, 0XC7, 0XFF,
0XFE, 0X1F, 0X83, 0XF1, 0XFF, 0X1F, 0XFE, 0X7C, 0X03, 0XE3, 0XE3, 0XFF,
0X7C, 0X07, 0XC7, 0XE0, 0X3E, 0X1F, 0X01, 0XF1, 0XF0, 0X1F, 0XFE, 0X7C,
0X03, 0XE3, 0XE3, 0XE0, 0X7C, 0X07, 0XC7, 0XE0, 0X3E, 0X1F, 0X01, 0XF1,
0XF0, 0X1F, 0X80, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0X00, 0X07, 0XC7, 0XC0,
0X3E, 0X00, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
0X00, 0X07, 0XC7, 0XC0, 0X3E, 0X00, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
0X03, 0XE3, 0XE3, 0XE0, 0X3F, 0XFF, 0XC7, 0XC0, 0X3E, 0X0F, 0XFF, 0XF1,
0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0X7F, 0XFF, 0XC7, 0XC0,
0X3E, 0X1F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
0XFF, 0XFF, 0XC7, 0XC0, 0X3E, 0X3F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
0X03, 0XE3, 0XE3, 0XE0, 0XFC, 0X07, 0XC7, 0XC0, 0X3E, 0X3F, 0X01, 0XF1,
0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0XF8, 0X07, 0XC7, 0XC0,
0X3E, 0X3E, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
0XF8, 0X07, 0XC7, 0XE0, 0X3E, 0X3E, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
0X03, 0XE3, 0XE3, 0XE0, 0XF8, 0X07, 0XC7, 0XE0, 0X7E, 0X3E, 0X01, 0XF1,
0XF0, 0X1F, 0X00, 0X7E, 0X03, 0XE3, 0XE3, 0XE0, 0XFC, 0X1F, 0XC7, 0XFF,
0XFE, 0X3F, 0X07, 0XF1, 0XF0, 0X1F, 0X00, 0X7F, 0XFF, 0XE3, 0XE3, 0XFF,
0XFF, 0XFF, 0XC7, 0XFF, 0XFE, 0X3F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7F,
0XFF, 0XE3, 0XE3, 0XFF, 0XFF, 0XFF, 0XC3, 0XFF, 0XBE, 0X3F, 0XFF, 0XF1,
0XF0, 0X1F, 0X00, 0X7F, 0XFF, 0XE3, 0XE3, 0XFF, 0X7F, 0XE7, 0XC3, 0XFF,
0X3E, 0X1F, 0XF9, 0XF1, 0XF0, 0X1F, 0X00, 0X3F, 0XE3, 0XE3, 0XE1, 0XFF,
0X1F, 0X87, 0XC0, 0XFC, 0X3E, 0X07, 0XE1, 0XF1, 0XF0, 0X1F, 0X00, 0X0F,
0XC1, 0XE3, 0XE0, 0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
0XB7, 0X63, 0XDD, 0XC6, 0X08, 0X76, 0X1C, 0X7F, 0XFF, 0XFF, 0XFF, 0XFF,
0XFF, 0XFF, 0XFF, 0XFF, 0XB3, 0X6D, 0XDD, 0XBB, 0XBB, 0XB6, 0XFB, 0XBF,
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XB3, 0X6E, 0XDD, 0XBF,
0XBB, 0XB6, 0XFB, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
0XB5, 0X6E, 0XDD, 0XC7, 0XB8, 0X76, 0X3C, 0X7F, 0XFF, 0XFF, 0XFF, 0XFF,
0XFF, 0XFF, 0XFF, 0XFF, 0XB5, 0X6E, 0XDD, 0XFB, 0XBB, 0XB6, 0XFF, 0XBF,
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XB6, 0X6D, 0XEB, 0XBB,
0XBB, 0XB6, 0XFB, 0XBF };

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,429 @@
//
// Code adapted by Bodmer as an example for TFT_eSPI, this runs on any
// TFT_eSPI compatible processor so ignore the technical limitations
// detailed in the original header below. Assorted changes have been
// made including removal of the display mirror kludge.
//--------------------------------------------------------------------------
// 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.
//--------------------------------------------------------------------------
#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;
#endif
// Initialise eyes ---------------------------------------------------------
void initEyes(void)
{
Serial.println("Initialise eye objects");
// Initialise eye objects based on eyeInfo list in config.h:
for (uint8_t e = 0; e < NUM_EYES; e++) {
Serial.print("Create display #"); Serial.println(e);
eye[e].tft_cs = eyeInfo[e].select;
eye[e].blink.state = NOBLINK;
eye[e].xposition = eyeInfo[e].xposition;
pinMode(eye[e].tft_cs, OUTPUT);
digitalWrite(eye[e].tft_cs, LOW);
// 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
}
// UPDATE EYE --------------------------------------------------------------
void updateEye (void)
{
#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
}
// EYE-RENDERING FUNCTION --------------------------------------------------
void drawEye( // Renders one eye. Inputs must be pre-clipped & valid.
// Use native 32 bit variables where possible as this is 10% faster!
uint8_t e, // Eye array index; 0 or 1 for left/right
uint32_t iScale, // Scale factor for iris
uint32_t scleraX, // First pixel X offset into sclera image
uint32_t scleraY, // First pixel Y offset into sclera image
uint32_t uT, // Upper eyelid threshold value
uint32_t lT) { // Lower eyelid threshold value
uint32_t screenX, screenY, scleraXsave;
int32_t irisX, irisY;
uint32_t p, a;
uint32_t d;
uint32_t pixels = 0;
// 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.
digitalWrite(eye[e].tft_cs, LOW);
tft.startWrite();
tft.setAddrWindow(eye[e].xposition, 0, 128, 128);
// 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;
// Eyelid image is left<>right swapped for two displays
uint16_t lidX = 0;
uint16_t dlidX = -1;
if (e) dlidX = 1;
for (screenY = 0; screenY < SCREEN_HEIGHT; screenY++, scleraY++, irisY++) {
scleraX = scleraXsave;
irisX = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
if (e) lidX = 0; else lidX = SCREEN_WIDTH - 1;
for (screenX = 0; screenX < SCREEN_WIDTH; screenX++, scleraX++, irisX++, lidX += dlidX) {
if ((pgm_read_byte(lower + screenY * SCREEN_WIDTH + lidX) <= lT) ||
(pgm_read_byte(upper + screenY * SCREEN_WIDTH + lidX) <= uT)) { // Covered by eyelid
p = 0;
} else if ((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
(irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX);
} else { // Maybe iris...
p = pgm_read_word(polar + irisY * IRIS_WIDTH + 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 = pgm_read_word(iris + d * IRIS_MAP_WIDTH + a); // Pixel = iris
} else { // Not in iris
p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX); // Pixel = sclera
}
}
*(&pbuffer[dmaBuf][0] + pixels++) = p >> 8 | p << 8;
if (pixels >= BUFFER_SIZE) {
yield();
#ifdef USE_DMA
tft.pushPixelsDMA(&pbuffer[dmaBuf][0], pixels);
dmaBuf = !dmaBuf;
#else
tft.pushPixels(pbuffer, pixels);
#endif
pixels = 0;
}
}
}
if (pixels) {
#ifdef USE_DMA
tft.pushPixelsDMA(&pbuffer[dmaBuf][0], pixels);
#else
tft.pushPixels(pbuffer, pixels);
#endif
}
tft.endWrite();
digitalWrite(eye[e].tft_cs, HIGH);
}
// 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
// Process motion for a single frame of left or right eye
void frame(uint16_t iScale) // Iris scale (0-1023)
{
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
if (!(++frames & 255)) { // Every 256 frames...
float elapsed = (millis() - startTime) / 1000.0;
if (elapsed) Serial.println((uint16_t)(frames / elapsed)); // Print FPS
}
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...
#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 - 128);
eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128);
// 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) {
if (eyeIndex == 1) eyeX += 4;
else 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 = (pgm_read_byte(upper + sampleY * SCREEN_WIDTH + sampleX) +
pgm_read_byte(upper + sampleY * SCREEN_WIDTH + (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);
if (eyeIndex == (NUM_EYES - 1)) {
user_loop(); // Call user code after rendering last eye
}
}
// 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.
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

View File

@ -0,0 +1,65 @@
#if 1 // Change to 0 to disable this code (must enable ONE user*.cpp only!)
// This file provides a crude way to "drop in" user code to the eyes,
// allowing concurrent operations without having to maintain a bunch of
// special derivatives of the eye code (which is still undergoing a lot
// of development). Just replace the source code contents of THIS TAB ONLY,
// compile and upload to board. Shouldn't need to modify other eye code.
// User globals can go here, recommend declaring as static, e.g.:
// static int foo = 42;
// Called once near the end of the setup() function.
void user_setup(void) {
}
// Called periodically during eye animation. This is invoked in the
// interval before starting drawing on the last eye so it won't exacerbate
// visible tearing in eye rendering.
// This function BLOCKS, it does NOT multitask with the eye animation code,
// and performance here will have a direct impact on overall refresh rates,
// so keep it simple. Avoid loops (e.g. if animating something like a servo
// or NeoPixels in response to some trigger) and instead rely on state
// machines or similar. Additionally, calls to this function are NOT time-
// constant -- eye rendering time can vary frame to frame, so animation or
// other over-time operations won't look very good using simple +/-
// increments, it's better to use millis() or micros() and work
// algebraically with elapsed times instead.
void user_loop(void) {
/*
Suppose we have a global bool "animating" (meaning something is in
motion) and global uint32_t's "startTime" (the initial time at which
something triggered movement) and "transitionTime" (the total time
over which movement should occur, expressed in microseconds).
Maybe it's servos, maybe NeoPixels, or something different altogether.
This function might resemble something like (pseudocode):
if(!animating) {
Not in motion, check sensor for trigger...
if(read some sensor) {
Motion is triggered! Record startTime, set transition
to 1.5 seconds and set animating flag:
startTime = micros();
transitionTime = 1500000;
animating = true;
No motion actually takes place yet, that will begin on
the next pass through this function.
}
} else {
Currently in motion, ignore trigger and move things instead...
uint32_t elapsed = millis() - startTime;
if(elapsed < transitionTime) {
Part way through motion...how far along?
float ratio = (float)elapsed / (float)transitionTime;
Do something here based on ratio, 0.0 = start, 1.0 = end
} else {
End of motion reached.
Take whatever steps here to move into final position (1.0),
and then clear the "animating" flag:
animating = false;
}
}
*/
}
#endif // 0

View File

@ -0,0 +1,83 @@
// SERVO BAT: flapping paper-cutout bat (attached to servo on SERVO_PIN)
// triggered by contact-sensitive conductive thread on CAPTOUCH_PIN.
// See user.cpp for basics of connecting user code to animated eyes.
#if 0 // Change to 1 to enable this code (must enable ONE user*.cpp only!)
#include "Adafruit_FreeTouch.h"
#include <Servo.h>
#define CAPTOUCH_PIN A5 // Capacitive touch pin - attach conductive thread here
#define SERVO_PIN 4 // Servo plugged in here
// Set up capacitive touch button using the FreeTouch library
static Adafruit_FreeTouch touch(CAPTOUCH_PIN, OVERSAMPLE_4, RESISTOR_50K, FREQ_MODE_NONE);
static long oldState; // Last-read touch value
static bool isTouched = false; // When true, bat is flapping
static uint32_t touchTime = 0; // millis() time when flapping started
static uint32_t touchThreshold;
Servo servo;
void user_setup(void) {
if (!touch.begin())
Serial.println("Cap touch init failed");
servo.attach(SERVO_PIN);
servo.write(0); // Move servo to idle position
servo.detach();
// Attempt to auto-calibrate the touch threshold
// (assumes thread is NOT touched on startup!)
touchThreshold = 0;
for(int i=0; i<10; i++) {
touchThreshold += touch.measure(); // Accumulate 10 readings
delay(50);
}
touchThreshold /= 10; // Average "not touched" value
touchThreshold = ((touchThreshold * 127) + 1023) / 128; // Threshold = ~1% toward max
oldState = touch.measure();
}
#define FLAP_TIME_RISING 900 // 0-to-180 degree servo sweep time, in milliseconds
#define FLAP_TIME_FALLING 1200 // 180-to-0 servo sweep time
#define FLAP_REPS 3 // Number of times to flap
#define FLAP_TIME_PER (FLAP_TIME_RISING + FLAP_TIME_FALLING)
#define FLAP_TIME_TOTAL (FLAP_TIME_PER * FLAP_REPS)
void user_loop(void) {
long newState = touch.measure();
Serial.println(newState);
if (isTouched) {
uint32_t elapsed = millis() - touchTime;
if (elapsed >= FLAP_TIME_TOTAL) { // After all flaps are completed
isTouched = false; // Bat goes idle again
servo.write(0);
servo.detach();
} else {
elapsed %= FLAP_TIME_PER; // Time within current flap cycle
if (elapsed < FLAP_TIME_RISING) { // Over the course of 0 to FLAP_TIME_RISING...
servo.write(elapsed * 180 / FLAP_TIME_RISING); // Move 0 to 180 degrees
} else { // Over course of FLAP_TIME_FALLING, return to 0
servo.write(180 - ((elapsed - FLAP_TIME_RISING) * 180 / FLAP_TIME_FALLING));
}
}
} else {
// Bat is idle...check for capacitive touch...
if (newState > touchThreshold && oldState < touchThreshold) {
delay(100); // Short delay to debounce
newState = touch.measure(); // Verify whether still touched
if (newState > touchThreshold) { // It is!
isTouched = true; // Start a new flap session
touchTime = millis();
servo.attach(SERVO_PIN);
servo.write(0);
}
}
}
oldState = newState; // Save cap touch state
}
#endif // 0

View File

@ -0,0 +1,64 @@
#if 0 // Change to 1 to enable this code (must enable ONE user*.cpp only!)
// Christmas demo for eye + NeoPixels. Randomly sets pixels in holiday-themed colors.
#include <Adafruit_NeoPixel.h>
// Pin 8 is the built-in NeoPixels on Circuit Playground Express & Bluetooth.
// With a TFT Gizmo attached, you can use A1 or A2 to easily connect a strand.
#define LED_PIN 8
#define LED_COUNT 10
#define LED_BRIGHTNESS 50 // about 1/5 brightness (max = 255)
#define TWINKLE_INTERVAL 333 // Every 333 ms (1/3 second), change a pixel
#define LIT_PIXELS (LED_COUNT / 3) // Must be LESS than LED_COUNT/2
Adafruit_NeoPixel pixels(LED_COUNT, LED_PIN);
uint32_t timeOfLastTwinkle = 0; // Used for timing pixel changes
uint8_t litPixel[LIT_PIXELS]; // Indices of which pixels are lit
uint8_t pixelIndex = LIT_PIXELS; // Index of currently-changing litPixel
uint32_t colors[] = { 0xFF0000, 0x00FF00, 0xFFFFFF }; // Red, green, white
#define NUM_COLORS (sizeof colors / sizeof colors[0])
void user_setup(void) {
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
pixels.show(); // Turn OFF all pixels ASAP
pixels.setBrightness(LED_BRIGHTNESS);
memset(litPixel, 255, sizeof litPixel); // Fill with out-of-range nonsense
}
void user_loop(void) {
uint32_t t = millis();
if((t - timeOfLastTwinkle) >= TWINKLE_INTERVAL) { // Time to update pixels?
timeOfLastTwinkle = t;
if(++pixelIndex >= LIT_PIXELS) pixelIndex = 0;
// Pick a NEW pixel that's not currently lit and not adjacent to a lit one.
// This just brute-force randomly tries pixels until a valid one is found,
// no mathematical cleverness. Should only take a few iterations and won't
// significantly slow down the eyes.
int newPixel, pixelAfter, pixelBefore;
do {
newPixel = random(LED_COUNT);
pixelAfter = (newPixel + 1) % LED_COUNT;
pixelBefore = (newPixel - 1);
if(pixelBefore < 0) pixelBefore = LED_COUNT - 1;
} while(pixels.getPixelColor(newPixel) ||
pixels.getPixelColor(pixelAfter) ||
pixels.getPixelColor(pixelBefore));
// Turn OFF litPixel[pixelIndex]
pixels.setPixelColor(litPixel[pixelIndex], 0);
// 'newPixel' is the winner. Save in the litPixel[] array for later...
litPixel[pixelIndex] = newPixel;
// Turn ON newPixel with a random color from the colors[] list.
pixels.setPixelColor(newPixel, colors[random(NUM_COLORS)]);
pixels.show();
}
}
#endif // 0

View File

@ -0,0 +1,139 @@
// An adaption of the "UncannyEyes" sketch (see eye_functions tab)
// for the TFT_eSPI library. As written the sketch is for driving
// two TFT displays.
// The number of displays and chip selects used are defined in the
// config.h tab. The display count can be set to 1. If using one
// TFT and the chip select for that display is already defined in
// the TFT_eSPI library then change the chip select pins to -1 in the
// "config.h" tab.
// The wiring for 2 TFT displays to an ESP32 is described in the
// "wiring" tab of this sketch.
// Configuration settings for the eye, eye style, display count,
// chip selects and x offsets can be defined in the sketch "config.h" tab.
// Performance (frames per second = fps) can be improved by using
// DMA (for SPI displays only) on ESP32 and STM32 processors. Use
// as high a SPI clock rate as is supported by the display. 27MHz
// minimum, some diplays can be operated at higher clock rates in
// the range 40-80MHz.
// Single defaultEye performance for different processors
// No DMA With DMA
// ESP8266 (160MHz CPU) 40MHz SPI 36 fps
// ESP32 27MHz SPI 53 fps 85 fps
// ESP32 40MHz SPI 67 fps 102 fps
// ESP32 80MHz SPI 82 fps 116 fps // Note: Few displays work reliably at 80MHz
// STM32F401 55MHz SPI 44 fps 90 fps
// STM32F446 55MHz SPI 83 fps 155 fps
// STM32F767 55MHz SPI 136 fps 197 fps
// DMA can be used with STM32 and ESP32 processors when the interface
// is SPI, uncomment the next line:
#define USE_DMA
// Load TFT driver library
#include <SPI.h>
#include <TFT_eSPI.h>
TFT_eSPI tft; // A single instance is used for 1 or 2 displays
// A pixel buffer is used during eye rendering
#define BUFFER_SIZE 1024 // 128 to 1024 seems optimum
#ifdef USE_DMA
#define BUFFERS 2 // 2 toggle buffers with DMA
#else
#define BUFFERS 1 // 1 buffer for no DMA
#endif
uint16_t pbuffer[BUFFERS][BUFFER_SIZE]; // Pixel rendering buffer
bool dmaBuf = 0; // DMA buffer selection
// This struct is populated in config.h
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 and the x offset
int16_t xposition; // position of eye on the screen
} eyeInfo_t;
#include "config.h" // ****** CONFIGURATION IS DONE IN HERE ******
extern void user_setup(void); // Functions in the user*.cpp files
extern void user_loop(void);
#define SCREEN_X_START 0
#define SCREEN_X_END SCREEN_WIDTH // Badly named, actually the "eye" width!
#define SCREEN_Y_START 0
#define SCREEN_Y_END SCREEN_HEIGHT // Actually "eye" height
// 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;
struct { // One-per-eye structure
int16_t tft_cs; // Chip select pin for each display
eyeBlink blink; // Current blink/wink state
int16_t xposition; // x position of eye image
} eye[NUM_EYES];
uint32_t startTime; // For FPS indicator
// INITIALIZATION -- runs once at startup ----------------------------------
void setup(void) {
Serial.begin(115200);
//while (!Serial);
Serial.println("Starting");
#if defined(DISPLAY_BACKLIGHT) && (DISPLAY_BACKLIGHT >= 0)
// Enable backlight pin, initially off
Serial.println("Backlight turned off");
pinMode(DISPLAY_BACKLIGHT, OUTPUT);
digitalWrite(DISPLAY_BACKLIGHT, LOW);
#endif
// User call for additional features
user_setup();
// Initialiase the eye(s), this will set all chip selects low for the tft.init()
initEyes();
// Initialise TFT
Serial.println("Initialising displays");
tft.init();
#ifdef USE_DMA
tft.initDMA();
#endif
// Raise chip select(s) so that displays can be individually configured
digitalWrite(eye[0].tft_cs, HIGH);
if (NUM_EYES > 1) digitalWrite(eye[1].tft_cs, HIGH);
for (uint8_t e = 0; e < NUM_EYES; e++) {
digitalWrite(eye[e].tft_cs, LOW);
tft.setRotation(eyeInfo[e].rotation);
tft.fillScreen(TFT_BLACK);
digitalWrite(eye[e].tft_cs, HIGH);
}
#if defined(DISPLAY_BACKLIGHT) && (DISPLAY_BACKLIGHT >= 0)
Serial.println("Backlight now on!");
analogWrite(DISPLAY_BACKLIGHT, BACKLIGHT_MAX);
#endif
startTime = millis(); // For frame-rate calculation
}
// MAIN LOOP -- runs continuously after setup() ----------------------------
void loop() {
updateEye();
}

View File

@ -0,0 +1,93 @@
// 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.
//#define SYMMETRICAL_EYELID
// Enable ONE of these #includes -- HUGE graphics tables for various eyes:
#include "data/defaultEye.h" // Standard human-ish hazel eye -OR-
//#include "data/dragonEye.h" // Slit pupil fiery dragon/demon eye -OR-
//#include "data/noScleraEye.h" // Large iris, no sclera -OR-
//#include "data/goatEye.h" // Horizontal pupil goat/Krampus eye -OR-
//#include "data/newtEye.h" // Eye of newt -OR-
//#include "data/terminatorEye.h" // Git to da choppah!
//#include "data/catEye.h" // Cartoonish cat (flat "2D" colors)
//#include "data/owlEye.h" // Minerva the owl (DISABLE TRACKING)
//#include "data/naugaEye.h" // Nauga googly eye (DISABLE TRACKING)
//#include "data/doeEye.h" // Cartoon deer eye (DISABLE TRACKING)
// DISPLAY HARDWARE SETTINGS (screen type & connections) -------------------
#define TFT_COUNT 2 // Number of screens (1 or 2)
#define TFT1_CS 22 // TFT 1 chip select pin (set to -1 to use TFT_eSPI setup)
#define TFT2_CS 21 // TFT 2 chip select pin (set to -1 to use TFT_eSPI setup)
#define TFT_1_ROT 1 // TFT 1 rotation
#define TFT_2_ROT 3 // TFT 2 rotation
#define EYE_1_XPOSITION 0 // x shift for eye 1 image on display
#define EYE_2_XPOSITION 0 // x shift for eye 2 image on display
#define DISPLAY_BACKLIGHT -1 // Pin for backlight control (-1 for none)
#define BACKLIGHT_MAX 255
// EYE LIST ----------------------------------------------------------------
#define NUM_EYES 2 // Number of eyes to display (1 or 2)
#define BLINK_PIN -1 // Pin for manual blink button (BOTH eyes)
#define LH_WINK_PIN -1 // Left wink pin (set to -1 for no pin)
#define RH_WINK_PIN -1 // Right wink pin (set to -1 for no pin)
// 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), a screen
// rotation value (0-3) and x position offset for that eye.
#if (NUM_EYES == 2)
eyeInfo_t eyeInfo[] = {
{ TFT1_CS, LH_WINK_PIN, TFT_1_ROT, EYE_1_XPOSITION }, // LEFT EYE chip select and wink pins, rotation and offset
{ TFT2_CS, RH_WINK_PIN, TFT_2_ROT, EYE_2_XPOSITION }, // RIGHT EYE chip select and wink pins, rotation and offset
};
#else
eyeInfo_t eyeInfo[] = {
{ TFT1_CS, LH_WINK_PIN, TFT_1_ROT, EYE_1_XPOSITION }, // EYE chip select and wink pins, rotation and offset
};
#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 LIGHT_PIN -1 // Light sensor pin
#define LIGHT_CURVE 0.33 // Light sensor adjustment curve
#define LIGHT_MIN 0 // Minimum useful reading from light sensor
#define LIGHT_MAX 1023 // Maximum useful 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 90 // Iris size (0-1023) in brightest light
#endif
#if !defined(IRIS_MAX)
#define IRIS_MAX 130 // Iris size (0-1023) in darkest light
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
// Logo helps with screen orientation & positioning
#define LOGO_TOP_WIDTH 59
#define LOGO_TOP_HEIGHT 59
const uint8_t logo_top[472] PROGMEM= {
0X00, 0X00, 0X00, 0X01, 0XC0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03,
0XC0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X07, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X0F,
0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X0F, 0XF0, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X1F, 0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3F,
0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X7F, 0XF8, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X7F, 0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0XFF,
0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X01, 0XFF, 0XFC, 0X00, 0X00, 0X00,
0X00, 0X00, 0X01, 0XFF, 0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X01, 0XFF,
0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XFF, 0XFC, 0X00, 0X00, 0X00,
0X00, 0X00, 0X03, 0XFF, 0XFE, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0X83, 0XFF,
0XFE, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XF3, 0XFF, 0XFE, 0X00, 0X00, 0X00,
0XFF, 0XFF, 0XFB, 0XFF, 0XFC, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFF, 0XFF,
0XFC, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFF, 0XFB, 0XFC, 0X30, 0X00, 0X00,
0X3F, 0XFF, 0XFF, 0XF1, 0XFB, 0XFF, 0X00, 0X00, 0X1F, 0XFF, 0XFF, 0XF1,
0XFF, 0XFF, 0XE0, 0X00, 0X1F, 0XFF, 0XFF, 0XE1, 0XFF, 0XFF, 0XFE, 0X00,
0X0F, 0XFF, 0XFF, 0XE1, 0XFF, 0XFF, 0XFF, 0X80, 0X07, 0XFF, 0XEF, 0XE1,
0XFF, 0XFF, 0XFF, 0XE0, 0X03, 0XFF, 0XC1, 0XE3, 0XFF, 0XFF, 0XFF, 0XE0,
0X03, 0XFF, 0XC0, 0XF3, 0XFF, 0XFF, 0XFF, 0XE0, 0X01, 0XFF, 0XF0, 0X7F,
0XC3, 0XFF, 0XFF, 0XC0, 0X00, 0XFF, 0XF8, 0X7F, 0X01, 0XFF, 0XFF, 0X00,
0X00, 0X7F, 0XFF, 0XFE, 0X03, 0XFF, 0XFE, 0X00, 0X00, 0X1F, 0XFF, 0XFF,
0X0F, 0XFF, 0XFC, 0X00, 0X00, 0X07, 0XFF, 0XFF, 0XFF, 0XFF, 0XF0, 0X00,
0X00, 0X01, 0XFF, 0X3F, 0XFF, 0XFF, 0XE0, 0X00, 0X00, 0X07, 0XFC, 0X39,
0XFF, 0XFF, 0X80, 0X00, 0X00, 0X0F, 0XF8, 0X38, 0XFF, 0XFF, 0X00, 0X00,
0X00, 0X1F, 0XF0, 0X78, 0X7F, 0XFC, 0X00, 0X00, 0X00, 0X3F, 0XF0, 0XF8,
0X7F, 0X00, 0X00, 0X00, 0X00, 0X3F, 0XF1, 0XFC, 0X7F, 0X80, 0X00, 0X00,
0X00, 0X7F, 0XFF, 0XFE, 0X3F, 0XC0, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFE,
0X3F, 0XC0, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XE0, 0X00, 0X00,
0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XE0, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF,
0XFF, 0XE0, 0X00, 0X00, 0X01, 0XFF, 0XFF, 0XBF, 0XFF, 0XE0, 0X00, 0X00,
0X01, 0XFF, 0XFF, 0XBF, 0XFF, 0XE0, 0X00, 0X00, 0X01, 0XFF, 0XFF, 0X1F,
0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XFE, 0X1F, 0XFF, 0XE0, 0X00, 0X00,
0X03, 0XFF, 0XFC, 0X0F, 0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XF0, 0X0F,
0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XC0, 0X07, 0XFF, 0XE0, 0X00, 0X00,
0X07, 0XFE, 0X00, 0X03, 0XFF, 0XE0, 0X00, 0X00, 0X07, 0XF0, 0X00, 0X01,
0XFF, 0XE0, 0X00, 0X00, 0X03, 0X80, 0X00, 0X00, 0X7F, 0XE0, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0X3F, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X0F, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X07, 0XE0, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X00, 0XE0, 0X00, 0X00 };
#define LOGO_BOTTOM_WIDTH 128
#define LOGO_BOTTOM_HEIGHT 37
const uint8_t logo_bottom[592] PROGMEM= {
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X00, 0X7F, 0X00, 0X00, 0X00,
0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X00,
0XFF, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00,
0X3E, 0X00, 0X00, 0X01, 0XFF, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00,
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01, 0XFF, 0X00, 0X00, 0X00,
0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01,
0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00,
0X3E, 0X00, 0X00, 0X01, 0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0,
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01, 0XF0, 0X00, 0X00, 0X00,
0X00, 0X00, 0X03, 0XE0, 0X1F, 0XFF, 0X00, 0XFE, 0X3E, 0X07, 0XFF, 0XC1,
0XFF, 0X1F, 0X0E, 0X7C, 0X03, 0XE3, 0XE3, 0XFF, 0X3F, 0XFF, 0X81, 0XFF,
0XBE, 0X0F, 0XFF, 0XE1, 0XFF, 0X1F, 0X3E, 0X7C, 0X03, 0XE3, 0XE3, 0XFF,
0X7F, 0XFF, 0XC3, 0XFF, 0XFE, 0X1F, 0XFF, 0XF1, 0XFF, 0X1F, 0X7E, 0X7C,
0X03, 0XE3, 0XE3, 0XFF, 0X7F, 0XFF, 0XC7, 0XFF, 0XFE, 0X1F, 0XFF, 0XF1,
0XFF, 0X1F, 0XFE, 0X7C, 0X03, 0XE3, 0XE3, 0XFF, 0X7E, 0X0F, 0XC7, 0XFF,
0XFE, 0X1F, 0X83, 0XF1, 0XFF, 0X1F, 0XFE, 0X7C, 0X03, 0XE3, 0XE3, 0XFF,
0X7C, 0X07, 0XC7, 0XE0, 0X3E, 0X1F, 0X01, 0XF1, 0XF0, 0X1F, 0XFE, 0X7C,
0X03, 0XE3, 0XE3, 0XE0, 0X7C, 0X07, 0XC7, 0XE0, 0X3E, 0X1F, 0X01, 0XF1,
0XF0, 0X1F, 0X80, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0X00, 0X07, 0XC7, 0XC0,
0X3E, 0X00, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
0X00, 0X07, 0XC7, 0XC0, 0X3E, 0X00, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
0X03, 0XE3, 0XE3, 0XE0, 0X3F, 0XFF, 0XC7, 0XC0, 0X3E, 0X0F, 0XFF, 0XF1,
0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0X7F, 0XFF, 0XC7, 0XC0,
0X3E, 0X1F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
0XFF, 0XFF, 0XC7, 0XC0, 0X3E, 0X3F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
0X03, 0XE3, 0XE3, 0XE0, 0XFC, 0X07, 0XC7, 0XC0, 0X3E, 0X3F, 0X01, 0XF1,
0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0XF8, 0X07, 0XC7, 0XC0,
0X3E, 0X3E, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
0XF8, 0X07, 0XC7, 0XE0, 0X3E, 0X3E, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
0X03, 0XE3, 0XE3, 0XE0, 0XF8, 0X07, 0XC7, 0XE0, 0X7E, 0X3E, 0X01, 0XF1,
0XF0, 0X1F, 0X00, 0X7E, 0X03, 0XE3, 0XE3, 0XE0, 0XFC, 0X1F, 0XC7, 0XFF,
0XFE, 0X3F, 0X07, 0XF1, 0XF0, 0X1F, 0X00, 0X7F, 0XFF, 0XE3, 0XE3, 0XFF,
0XFF, 0XFF, 0XC7, 0XFF, 0XFE, 0X3F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7F,
0XFF, 0XE3, 0XE3, 0XFF, 0XFF, 0XFF, 0XC3, 0XFF, 0XBE, 0X3F, 0XFF, 0XF1,
0XF0, 0X1F, 0X00, 0X7F, 0XFF, 0XE3, 0XE3, 0XFF, 0X7F, 0XE7, 0XC3, 0XFF,
0X3E, 0X1F, 0XF9, 0XF1, 0XF0, 0X1F, 0X00, 0X3F, 0XE3, 0XE3, 0XE1, 0XFF,
0X1F, 0X87, 0XC0, 0XFC, 0X3E, 0X07, 0XE1, 0XF1, 0XF0, 0X1F, 0X00, 0X0F,
0XC1, 0XE3, 0XE0, 0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
0XB7, 0X63, 0XDD, 0XC6, 0X08, 0X76, 0X1C, 0X7F, 0XFF, 0XFF, 0XFF, 0XFF,
0XFF, 0XFF, 0XFF, 0XFF, 0XB3, 0X6D, 0XDD, 0XBB, 0XBB, 0XB6, 0XFB, 0XBF,
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XB3, 0X6E, 0XDD, 0XBF,
0XBB, 0XB6, 0XFB, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
0XB5, 0X6E, 0XDD, 0XC7, 0XB8, 0X76, 0X3C, 0X7F, 0XFF, 0XFF, 0XFF, 0XFF,
0XFF, 0XFF, 0XFF, 0XFF, 0XB5, 0X6E, 0XDD, 0XFB, 0XBB, 0XB6, 0XFF, 0XBF,
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XB6, 0X6D, 0XEB, 0XBB,
0XBB, 0XB6, 0XFB, 0XBF };

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,429 @@
//
// Code adapted by Bodmer as an example for TFT_eSPI, this runs on any
// TFT_eSPI compatible processor so ignore the technical limitations
// detailed in the original header below. Assorted changes have been
// made including removal of the display mirror kludge.
//--------------------------------------------------------------------------
// 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.
//--------------------------------------------------------------------------
#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;
#endif
// Initialise eyes ---------------------------------------------------------
void initEyes(void)
{
Serial.println("Initialise eye objects");
// Initialise eye objects based on eyeInfo list in config.h:
for (uint8_t e = 0; e < NUM_EYES; e++) {
Serial.print("Create display #"); Serial.println(e);
eye[e].tft_cs = eyeInfo[e].select;
eye[e].blink.state = NOBLINK;
eye[e].xposition = eyeInfo[e].xposition;
pinMode(eye[e].tft_cs, OUTPUT);
digitalWrite(eye[e].tft_cs, LOW);
// 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
}
// UPDATE EYE --------------------------------------------------------------
void updateEye (void)
{
#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
}
// EYE-RENDERING FUNCTION --------------------------------------------------
void drawEye( // Renders one eye. Inputs must be pre-clipped & valid.
// Use native 32 bit variables where possible as this is 10% faster!
uint8_t e, // Eye array index; 0 or 1 for left/right
uint32_t iScale, // Scale factor for iris
uint32_t scleraX, // First pixel X offset into sclera image
uint32_t scleraY, // First pixel Y offset into sclera image
uint32_t uT, // Upper eyelid threshold value
uint32_t lT) { // Lower eyelid threshold value
uint32_t screenX, screenY, scleraXsave;
int32_t irisX, irisY;
uint32_t p, a;
uint32_t d;
uint32_t pixels = 0;
// 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.
digitalWrite(eye[e].tft_cs, LOW);
tft.startWrite();
tft.setAddrWindow(eye[e].xposition, 0, 128, 128);
// 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;
// Eyelid image is left<>right swapped for two displays
uint16_t lidX = 0;
uint16_t dlidX = -1;
if (e) dlidX = 1;
for (screenY = 0; screenY < SCREEN_HEIGHT; screenY++, scleraY++, irisY++) {
scleraX = scleraXsave;
irisX = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
if (e) lidX = 0; else lidX = SCREEN_WIDTH - 1;
for (screenX = 0; screenX < SCREEN_WIDTH; screenX++, scleraX++, irisX++, lidX += dlidX) {
if ((pgm_read_byte(lower + screenY * SCREEN_WIDTH + lidX) <= lT) ||
(pgm_read_byte(upper + screenY * SCREEN_WIDTH + lidX) <= uT)) { // Covered by eyelid
p = 0;
} else if ((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
(irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX);
} else { // Maybe iris...
p = pgm_read_word(polar + irisY * IRIS_WIDTH + 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 = pgm_read_word(iris + d * IRIS_MAP_WIDTH + a); // Pixel = iris
} else { // Not in iris
p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX); // Pixel = sclera
}
}
*(&pbuffer[dmaBuf][0] + pixels++) = p >> 8 | p << 8;
if (pixels >= BUFFER_SIZE) {
yield();
#ifdef USE_DMA
tft.pushPixelsDMA(&pbuffer[dmaBuf][0], pixels);
dmaBuf = !dmaBuf;
#else
tft.pushPixels(pbuffer, pixels);
#endif
pixels = 0;
}
}
}
if (pixels) {
#ifdef USE_DMA
tft.pushPixelsDMA(&pbuffer[dmaBuf][0], pixels);
#else
tft.pushPixels(pbuffer, pixels);
#endif
}
tft.endWrite();
digitalWrite(eye[e].tft_cs, HIGH);
}
// 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
// Process motion for a single frame of left or right eye
void frame(uint16_t iScale) // Iris scale (0-1023)
{
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
if (!(++frames & 255)) { // Every 256 frames...
float elapsed = (millis() - startTime) / 1000.0;
if (elapsed) Serial.println((uint16_t)(frames / elapsed)); // Print FPS
}
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...
#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 - 128);
eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128);
// 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) {
if (eyeIndex == 1) eyeX += 4;
else 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 = (pgm_read_byte(upper + sampleY * SCREEN_WIDTH + sampleX) +
pgm_read_byte(upper + sampleY * SCREEN_WIDTH + (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);
if (eyeIndex == (NUM_EYES - 1)) {
user_loop(); // Call user code after rendering last eye
}
}
// 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.
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

View File

@ -0,0 +1,65 @@
#if 1 // Change to 0 to disable this code (must enable ONE user*.cpp only!)
// This file provides a crude way to "drop in" user code to the eyes,
// allowing concurrent operations without having to maintain a bunch of
// special derivatives of the eye code (which is still undergoing a lot
// of development). Just replace the source code contents of THIS TAB ONLY,
// compile and upload to board. Shouldn't need to modify other eye code.
// User globals can go here, recommend declaring as static, e.g.:
// static int foo = 42;
// Called once near the end of the setup() function.
void user_setup(void) {
}
// Called periodically during eye animation. This is invoked in the
// interval before starting drawing on the last eye so it won't exacerbate
// visible tearing in eye rendering.
// This function BLOCKS, it does NOT multitask with the eye animation code,
// and performance here will have a direct impact on overall refresh rates,
// so keep it simple. Avoid loops (e.g. if animating something like a servo
// or NeoPixels in response to some trigger) and instead rely on state
// machines or similar. Additionally, calls to this function are NOT time-
// constant -- eye rendering time can vary frame to frame, so animation or
// other over-time operations won't look very good using simple +/-
// increments, it's better to use millis() or micros() and work
// algebraically with elapsed times instead.
void user_loop(void) {
/*
Suppose we have a global bool "animating" (meaning something is in
motion) and global uint32_t's "startTime" (the initial time at which
something triggered movement) and "transitionTime" (the total time
over which movement should occur, expressed in microseconds).
Maybe it's servos, maybe NeoPixels, or something different altogether.
This function might resemble something like (pseudocode):
if(!animating) {
Not in motion, check sensor for trigger...
if(read some sensor) {
Motion is triggered! Record startTime, set transition
to 1.5 seconds and set animating flag:
startTime = micros();
transitionTime = 1500000;
animating = true;
No motion actually takes place yet, that will begin on
the next pass through this function.
}
} else {
Currently in motion, ignore trigger and move things instead...
uint32_t elapsed = millis() - startTime;
if(elapsed < transitionTime) {
Part way through motion...how far along?
float ratio = (float)elapsed / (float)transitionTime;
Do something here based on ratio, 0.0 = start, 1.0 = end
} else {
End of motion reached.
Take whatever steps here to move into final position (1.0),
and then clear the "animating" flag:
animating = false;
}
}
*/
}
#endif // 0

View File

@ -0,0 +1,83 @@
// SERVO BAT: flapping paper-cutout bat (attached to servo on SERVO_PIN)
// triggered by contact-sensitive conductive thread on CAPTOUCH_PIN.
// See user.cpp for basics of connecting user code to animated eyes.
#if 0 // Change to 1 to enable this code (must enable ONE user*.cpp only!)
#include "Adafruit_FreeTouch.h"
#include <Servo.h>
#define CAPTOUCH_PIN A5 // Capacitive touch pin - attach conductive thread here
#define SERVO_PIN 4 // Servo plugged in here
// Set up capacitive touch button using the FreeTouch library
static Adafruit_FreeTouch touch(CAPTOUCH_PIN, OVERSAMPLE_4, RESISTOR_50K, FREQ_MODE_NONE);
static long oldState; // Last-read touch value
static bool isTouched = false; // When true, bat is flapping
static uint32_t touchTime = 0; // millis() time when flapping started
static uint32_t touchThreshold;
Servo servo;
void user_setup(void) {
if (!touch.begin())
Serial.println("Cap touch init failed");
servo.attach(SERVO_PIN);
servo.write(0); // Move servo to idle position
servo.detach();
// Attempt to auto-calibrate the touch threshold
// (assumes thread is NOT touched on startup!)
touchThreshold = 0;
for(int i=0; i<10; i++) {
touchThreshold += touch.measure(); // Accumulate 10 readings
delay(50);
}
touchThreshold /= 10; // Average "not touched" value
touchThreshold = ((touchThreshold * 127) + 1023) / 128; // Threshold = ~1% toward max
oldState = touch.measure();
}
#define FLAP_TIME_RISING 900 // 0-to-180 degree servo sweep time, in milliseconds
#define FLAP_TIME_FALLING 1200 // 180-to-0 servo sweep time
#define FLAP_REPS 3 // Number of times to flap
#define FLAP_TIME_PER (FLAP_TIME_RISING + FLAP_TIME_FALLING)
#define FLAP_TIME_TOTAL (FLAP_TIME_PER * FLAP_REPS)
void user_loop(void) {
long newState = touch.measure();
Serial.println(newState);
if (isTouched) {
uint32_t elapsed = millis() - touchTime;
if (elapsed >= FLAP_TIME_TOTAL) { // After all flaps are completed
isTouched = false; // Bat goes idle again
servo.write(0);
servo.detach();
} else {
elapsed %= FLAP_TIME_PER; // Time within current flap cycle
if (elapsed < FLAP_TIME_RISING) { // Over the course of 0 to FLAP_TIME_RISING...
servo.write(elapsed * 180 / FLAP_TIME_RISING); // Move 0 to 180 degrees
} else { // Over course of FLAP_TIME_FALLING, return to 0
servo.write(180 - ((elapsed - FLAP_TIME_RISING) * 180 / FLAP_TIME_FALLING));
}
}
} else {
// Bat is idle...check for capacitive touch...
if (newState > touchThreshold && oldState < touchThreshold) {
delay(100); // Short delay to debounce
newState = touch.measure(); // Verify whether still touched
if (newState > touchThreshold) { // It is!
isTouched = true; // Start a new flap session
touchTime = millis();
servo.attach(SERVO_PIN);
servo.write(0);
}
}
}
oldState = newState; // Save cap touch state
}
#endif // 0

View File

@ -0,0 +1,64 @@
#if 0 // Change to 1 to enable this code (must enable ONE user*.cpp only!)
// Christmas demo for eye + NeoPixels. Randomly sets pixels in holiday-themed colors.
#include <Adafruit_NeoPixel.h>
// Pin 8 is the built-in NeoPixels on Circuit Playground Express & Bluetooth.
// With a TFT Gizmo attached, you can use A1 or A2 to easily connect a strand.
#define LED_PIN 8
#define LED_COUNT 10
#define LED_BRIGHTNESS 50 // about 1/5 brightness (max = 255)
#define TWINKLE_INTERVAL 333 // Every 333 ms (1/3 second), change a pixel
#define LIT_PIXELS (LED_COUNT / 3) // Must be LESS than LED_COUNT/2
Adafruit_NeoPixel pixels(LED_COUNT, LED_PIN);
uint32_t timeOfLastTwinkle = 0; // Used for timing pixel changes
uint8_t litPixel[LIT_PIXELS]; // Indices of which pixels are lit
uint8_t pixelIndex = LIT_PIXELS; // Index of currently-changing litPixel
uint32_t colors[] = { 0xFF0000, 0x00FF00, 0xFFFFFF }; // Red, green, white
#define NUM_COLORS (sizeof colors / sizeof colors[0])
void user_setup(void) {
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
pixels.show(); // Turn OFF all pixels ASAP
pixels.setBrightness(LED_BRIGHTNESS);
memset(litPixel, 255, sizeof litPixel); // Fill with out-of-range nonsense
}
void user_loop(void) {
uint32_t t = millis();
if((t - timeOfLastTwinkle) >= TWINKLE_INTERVAL) { // Time to update pixels?
timeOfLastTwinkle = t;
if(++pixelIndex >= LIT_PIXELS) pixelIndex = 0;
// Pick a NEW pixel that's not currently lit and not adjacent to a lit one.
// This just brute-force randomly tries pixels until a valid one is found,
// no mathematical cleverness. Should only take a few iterations and won't
// significantly slow down the eyes.
int newPixel, pixelAfter, pixelBefore;
do {
newPixel = random(LED_COUNT);
pixelAfter = (newPixel + 1) % LED_COUNT;
pixelBefore = (newPixel - 1);
if(pixelBefore < 0) pixelBefore = LED_COUNT - 1;
} while(pixels.getPixelColor(newPixel) ||
pixels.getPixelColor(pixelAfter) ||
pixels.getPixelColor(pixelBefore));
// Turn OFF litPixel[pixelIndex]
pixels.setPixelColor(litPixel[pixelIndex], 0);
// 'newPixel' is the winner. Save in the litPixel[] array for later...
litPixel[pixelIndex] = newPixel;
// Turn ON newPixel with a random color from the colors[] list.
pixels.setPixelColor(newPixel, colors[random(NUM_COLORS)]);
pixels.show();
}
}
#endif // 0

View File

@ -0,0 +1,31 @@
/*
This is the example wiring used for the sketch testing.
You must not define the TFT_CS pin in the TFT_eSPI library if you are
using two independant displays. Instead the chip selects (CS) must be
defined in the "config.h" tab of this sketch. The sketch can then select
the dispay to send graphics to.
If you are only using one display, then TFT_CS can be defined in the
TFT_eSPI library.
The "Setup47_ST7735.h" file was used for the two TFT test using the wiring
as shown below:
Function ESP32 pin TFT 1 TFT 2
MOSI 23 -> SDA -> SDA // The TFT pin may be named DIN
MISO 19 // Not connected
SCLK 18 -> CLK -> CLK // The TFT pin may be named SCK
TFT_DC 2 -> DC -> DC // The TFT pin may be named AO
TFT_RST 4 -> RST -> RST
CS 1 22 -> CS // Connected to TFT 1 only
CS 2 21 -> CS // Connected to TFT 2 only
+5V/VIN -> VCC -> VCC
0V -> GND -> GND
+5V/VIN -> LED -> LED // Some displays do not have a backlight BL/LED pin
The displays used for testing were 128x128 ST7735 displays, the TFT_eSPI library setup file may need
to be changed as these displays come in many configuration variants.
*/