mirror of
https://github.com/Bodmer/TFT_eSPI.git
synced 2024-09-21 18:37:11 +00:00
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:
parent
975347d5de
commit
24b0eca084
140
examples/Generic/Animated_Eyes_1/Animated_Eyes_1.ino
Normal file
140
examples/Generic/Animated_Eyes_1/Animated_Eyes_1.ino
Normal 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();
|
||||
}
|
93
examples/Generic/Animated_Eyes_1/config.h
Normal file
93
examples/Generic/Animated_Eyes_1/config.h
Normal 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
|
11600
examples/Generic/Animated_Eyes_1/data/catEye.h
Normal file
11600
examples/Generic/Animated_Eyes_1/data/catEye.h
Normal file
File diff suppressed because it is too large
Load Diff
13349
examples/Generic/Animated_Eyes_1/data/defaultEye.h
Normal file
13349
examples/Generic/Animated_Eyes_1/data/defaultEye.h
Normal file
File diff suppressed because it is too large
Load Diff
15646
examples/Generic/Animated_Eyes_1/data/doeEye.h
Normal file
15646
examples/Generic/Animated_Eyes_1/data/doeEye.h
Normal file
File diff suppressed because it is too large
Load Diff
17018
examples/Generic/Animated_Eyes_1/data/dragonEye.h
Normal file
17018
examples/Generic/Animated_Eyes_1/data/dragonEye.h
Normal file
File diff suppressed because it is too large
Load Diff
12807
examples/Generic/Animated_Eyes_1/data/goatEye.h
Normal file
12807
examples/Generic/Animated_Eyes_1/data/goatEye.h
Normal file
File diff suppressed because it is too large
Load Diff
101
examples/Generic/Animated_Eyes_1/data/logo.h
Normal file
101
examples/Generic/Animated_Eyes_1/data/logo.h
Normal 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 };
|
7609
examples/Generic/Animated_Eyes_1/data/naugaEye.h
Normal file
7609
examples/Generic/Animated_Eyes_1/data/naugaEye.h
Normal file
File diff suppressed because it is too large
Load Diff
13346
examples/Generic/Animated_Eyes_1/data/newtEye.h
Normal file
13346
examples/Generic/Animated_Eyes_1/data/newtEye.h
Normal file
File diff suppressed because it is too large
Load Diff
17018
examples/Generic/Animated_Eyes_1/data/noScleraEye.h
Normal file
17018
examples/Generic/Animated_Eyes_1/data/noScleraEye.h
Normal file
File diff suppressed because it is too large
Load Diff
7609
examples/Generic/Animated_Eyes_1/data/owlEye.h
Normal file
7609
examples/Generic/Animated_Eyes_1/data/owlEye.h
Normal file
File diff suppressed because it is too large
Load Diff
13346
examples/Generic/Animated_Eyes_1/data/terminatorEye.h
Normal file
13346
examples/Generic/Animated_Eyes_1/data/terminatorEye.h
Normal file
File diff suppressed because it is too large
Load Diff
429
examples/Generic/Animated_Eyes_1/eye_functions.ino
Normal file
429
examples/Generic/Animated_Eyes_1/eye_functions.ino
Normal 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
|
65
examples/Generic/Animated_Eyes_1/user.cpp
Normal file
65
examples/Generic/Animated_Eyes_1/user.cpp
Normal 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
|
83
examples/Generic/Animated_Eyes_1/user_bat.cpp
Normal file
83
examples/Generic/Animated_Eyes_1/user_bat.cpp
Normal 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
|
64
examples/Generic/Animated_Eyes_1/user_xmas.cpp
Normal file
64
examples/Generic/Animated_Eyes_1/user_xmas.cpp
Normal 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
|
139
examples/Generic/Animated_Eyes_2/Animated_Eyes_2.ino
Normal file
139
examples/Generic/Animated_Eyes_2/Animated_Eyes_2.ino
Normal 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();
|
||||
}
|
93
examples/Generic/Animated_Eyes_2/config.h
Normal file
93
examples/Generic/Animated_Eyes_2/config.h
Normal 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
|
11600
examples/Generic/Animated_Eyes_2/data/catEye.h
Normal file
11600
examples/Generic/Animated_Eyes_2/data/catEye.h
Normal file
File diff suppressed because it is too large
Load Diff
13349
examples/Generic/Animated_Eyes_2/data/defaultEye.h
Normal file
13349
examples/Generic/Animated_Eyes_2/data/defaultEye.h
Normal file
File diff suppressed because it is too large
Load Diff
15646
examples/Generic/Animated_Eyes_2/data/doeEye.h
Normal file
15646
examples/Generic/Animated_Eyes_2/data/doeEye.h
Normal file
File diff suppressed because it is too large
Load Diff
17018
examples/Generic/Animated_Eyes_2/data/dragonEye.h
Normal file
17018
examples/Generic/Animated_Eyes_2/data/dragonEye.h
Normal file
File diff suppressed because it is too large
Load Diff
12807
examples/Generic/Animated_Eyes_2/data/goatEye.h
Normal file
12807
examples/Generic/Animated_Eyes_2/data/goatEye.h
Normal file
File diff suppressed because it is too large
Load Diff
101
examples/Generic/Animated_Eyes_2/data/logo.h
Normal file
101
examples/Generic/Animated_Eyes_2/data/logo.h
Normal 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 };
|
7609
examples/Generic/Animated_Eyes_2/data/naugaEye.h
Normal file
7609
examples/Generic/Animated_Eyes_2/data/naugaEye.h
Normal file
File diff suppressed because it is too large
Load Diff
13346
examples/Generic/Animated_Eyes_2/data/newtEye.h
Normal file
13346
examples/Generic/Animated_Eyes_2/data/newtEye.h
Normal file
File diff suppressed because it is too large
Load Diff
17018
examples/Generic/Animated_Eyes_2/data/noScleraEye.h
Normal file
17018
examples/Generic/Animated_Eyes_2/data/noScleraEye.h
Normal file
File diff suppressed because it is too large
Load Diff
7609
examples/Generic/Animated_Eyes_2/data/owlEye.h
Normal file
7609
examples/Generic/Animated_Eyes_2/data/owlEye.h
Normal file
File diff suppressed because it is too large
Load Diff
13346
examples/Generic/Animated_Eyes_2/data/terminatorEye.h
Normal file
13346
examples/Generic/Animated_Eyes_2/data/terminatorEye.h
Normal file
File diff suppressed because it is too large
Load Diff
429
examples/Generic/Animated_Eyes_2/eye_functions.ino
Normal file
429
examples/Generic/Animated_Eyes_2/eye_functions.ino
Normal 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
|
65
examples/Generic/Animated_Eyes_2/user.cpp
Normal file
65
examples/Generic/Animated_Eyes_2/user.cpp
Normal 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
|
83
examples/Generic/Animated_Eyes_2/user_bat.cpp
Normal file
83
examples/Generic/Animated_Eyes_2/user_bat.cpp
Normal 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
|
64
examples/Generic/Animated_Eyes_2/user_xmas.cpp
Normal file
64
examples/Generic/Animated_Eyes_2/user_xmas.cpp
Normal 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
|
31
examples/Generic/Animated_Eyes_2/wiring.ino
Normal file
31
examples/Generic/Animated_Eyes_2/wiring.ino
Normal 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.
|
||||
|
||||
|
||||
*/
|
Loading…
Reference in New Issue
Block a user