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
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
// Pin selections here are based on the original Adafruit Learning System
|
||||||
|
// guide for the Teensy 3.x project. Some of these pin numbers don't even
|
||||||
|
// exist on the smaller SAMD M0 & M4 boards, so you may need to make other
|
||||||
|
// selections:
|
||||||
|
|
||||||
|
// GRAPHICS SETTINGS (appearance of eye) -----------------------------------
|
||||||
|
|
||||||
|
// If using a SINGLE EYE, you might want this next line enabled, which
|
||||||
|
// uses a simpler "football-shaped" eye that's left/right symmetrical.
|
||||||
|
// Default shape includes the caruncle, creating distinct left/right eyes.
|
||||||
|
|
||||||
|
//#define SYMMETRICAL_EYELID
|
||||||
|
|
||||||
|
// Enable ONE of these #includes -- HUGE graphics tables for various eyes:
|
||||||
|
#include "data/defaultEye.h" // Standard human-ish hazel eye -OR-
|
||||||
|
//#include "data/dragonEye.h" // Slit pupil fiery dragon/demon eye -OR-
|
||||||
|
//#include "data/noScleraEye.h" // Large iris, no sclera -OR-
|
||||||
|
//#include "data/goatEye.h" // Horizontal pupil goat/Krampus eye -OR-
|
||||||
|
//#include "data/newtEye.h" // Eye of newt -OR-
|
||||||
|
//#include "data/terminatorEye.h" // Git to da choppah!
|
||||||
|
//#include "data/catEye.h" // Cartoonish cat (flat "2D" colors)
|
||||||
|
//#include "data/owlEye.h" // Minerva the owl (DISABLE TRACKING)
|
||||||
|
//#include "data/naugaEye.h" // Nauga googly eye (DISABLE TRACKING)
|
||||||
|
//#include "data/doeEye.h" // Cartoon deer eye (DISABLE TRACKING)
|
||||||
|
|
||||||
|
// DISPLAY HARDWARE SETTINGS (screen type & connections) -------------------
|
||||||
|
#define TFT_COUNT 1 // Number of screens (1 or 2)
|
||||||
|
#define TFT1_CS -1 // TFT 1 chip select pin (set to -1 to use TFT_eSPI setup)
|
||||||
|
#define TFT2_CS -1 // TFT 2 chip select pin (set to -1 to use TFT_eSPI setup)
|
||||||
|
#define TFT_1_ROT 1 // TFT 1 rotation
|
||||||
|
#define TFT_2_ROT 1 // TFT 2 rotation
|
||||||
|
#define EYE_1_XPOSITION 0 // x shift for eye 1 image on display
|
||||||
|
#define EYE_2_XPOSITION 320 - 128 // x shift for eye 2 image on display
|
||||||
|
|
||||||
|
#define DISPLAY_BACKLIGHT -1 // Pin for backlight control (-1 for none)
|
||||||
|
#define BACKLIGHT_MAX 255
|
||||||
|
|
||||||
|
// EYE LIST ----------------------------------------------------------------
|
||||||
|
#define NUM_EYES 2 // Number of eyes to display (1 or 2)
|
||||||
|
|
||||||
|
#define BLINK_PIN -1 // Pin for manual blink button (BOTH eyes)
|
||||||
|
#define LH_WINK_PIN -1 // Left wink pin (set to -1 for no pin)
|
||||||
|
#define RH_WINK_PIN -1 // Right wink pin (set to -1 for no pin)
|
||||||
|
|
||||||
|
// This table contains ONE LINE PER EYE. The table MUST be present with
|
||||||
|
// this name and contain ONE OR MORE lines. Each line contains THREE items:
|
||||||
|
// a pin number for the corresponding TFT/OLED display's SELECT line, a pin
|
||||||
|
// pin number for that eye's "wink" button (or -1 if not used), a screen
|
||||||
|
// rotation value (0-3) and x position offset for that eye.
|
||||||
|
|
||||||
|
#if (NUM_EYES == 2)
|
||||||
|
eyeInfo_t eyeInfo[] = {
|
||||||
|
{ TFT1_CS, LH_WINK_PIN, TFT_1_ROT, EYE_1_XPOSITION }, // LEFT EYE chip select and wink pins, rotation and offset
|
||||||
|
{ TFT2_CS, RH_WINK_PIN, TFT_2_ROT, EYE_2_XPOSITION }, // RIGHT EYE chip select and wink pins, rotation and offset
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
eyeInfo_t eyeInfo[] = {
|
||||||
|
{ TFT1_CS, LH_WINK_PIN, TFT_1_ROT, EYE_1_XPOSITION }, // EYE chip select and wink pins, rotation and offset
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// INPUT SETTINGS (for controlling eye motion) -----------------------------
|
||||||
|
|
||||||
|
// JOYSTICK_X_PIN and JOYSTICK_Y_PIN specify analog input pins for manually
|
||||||
|
// controlling the eye with an analog joystick. If set to -1 or if not
|
||||||
|
// defined, the eye will move on its own.
|
||||||
|
// IRIS_PIN speficies an analog input pin for a photocell to make pupils
|
||||||
|
// react to light (or potentiometer for manual control). If set to -1 or
|
||||||
|
// if not defined, the pupils will change on their own.
|
||||||
|
// BLINK_PIN specifies an input pin for a button (to ground) that will
|
||||||
|
// make any/all eyes blink. If set to -1 or if not defined, the eyes will
|
||||||
|
// only blink if AUTOBLINK is defined, or if the eyeInfo[] table above
|
||||||
|
// includes wink button settings for each eye.
|
||||||
|
|
||||||
|
//#define JOYSTICK_X_PIN A0 // Analog pin for eye horiz pos (else auto)
|
||||||
|
//#define JOYSTICK_Y_PIN A1 // Analog pin for eye vert position (")
|
||||||
|
//#define JOYSTICK_X_FLIP // If defined, reverse stick X axis
|
||||||
|
//#define JOYSTICK_Y_FLIP // If defined, reverse stick Y axis
|
||||||
|
#define TRACKING // If defined, eyelid tracks pupil
|
||||||
|
#define AUTOBLINK // If defined, eyes also blink autonomously
|
||||||
|
|
||||||
|
// #define LIGHT_PIN -1 // Light sensor pin
|
||||||
|
#define LIGHT_CURVE 0.33 // Light sensor adjustment curve
|
||||||
|
#define LIGHT_MIN 0 // Minimum useful reading from light sensor
|
||||||
|
#define LIGHT_MAX 1023 // Maximum useful reading from sensor
|
||||||
|
|
||||||
|
#define IRIS_SMOOTH // If enabled, filter input from IRIS_PIN
|
||||||
|
#if !defined(IRIS_MIN) // Each eye might have its own MIN/MAX
|
||||||
|
#define IRIS_MIN 90 // Iris size (0-1023) in brightest light
|
||||||
|
#endif
|
||||||
|
#if !defined(IRIS_MAX)
|
||||||
|
#define IRIS_MAX 130 // Iris size (0-1023) in darkest light
|
||||||
|
#endif
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,101 @@
|
||||||
|
// Logo helps with screen orientation & positioning
|
||||||
|
|
||||||
|
#define LOGO_TOP_WIDTH 59
|
||||||
|
#define LOGO_TOP_HEIGHT 59
|
||||||
|
|
||||||
|
const uint8_t logo_top[472] PROGMEM= {
|
||||||
|
0X00, 0X00, 0X00, 0X01, 0XC0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03,
|
||||||
|
0XC0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X07, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X0F,
|
||||||
|
0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X0F, 0XF0, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X1F, 0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3F,
|
||||||
|
0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X7F, 0XF8, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X7F, 0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0XFF,
|
||||||
|
0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X01, 0XFF, 0XFC, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X01, 0XFF, 0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X01, 0XFF,
|
||||||
|
0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XFF, 0XFC, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X03, 0XFF, 0XFE, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0X83, 0XFF,
|
||||||
|
0XFE, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XF3, 0XFF, 0XFE, 0X00, 0X00, 0X00,
|
||||||
|
0XFF, 0XFF, 0XFB, 0XFF, 0XFC, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFF, 0XFF,
|
||||||
|
0XFC, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFF, 0XFB, 0XFC, 0X30, 0X00, 0X00,
|
||||||
|
0X3F, 0XFF, 0XFF, 0XF1, 0XFB, 0XFF, 0X00, 0X00, 0X1F, 0XFF, 0XFF, 0XF1,
|
||||||
|
0XFF, 0XFF, 0XE0, 0X00, 0X1F, 0XFF, 0XFF, 0XE1, 0XFF, 0XFF, 0XFE, 0X00,
|
||||||
|
0X0F, 0XFF, 0XFF, 0XE1, 0XFF, 0XFF, 0XFF, 0X80, 0X07, 0XFF, 0XEF, 0XE1,
|
||||||
|
0XFF, 0XFF, 0XFF, 0XE0, 0X03, 0XFF, 0XC1, 0XE3, 0XFF, 0XFF, 0XFF, 0XE0,
|
||||||
|
0X03, 0XFF, 0XC0, 0XF3, 0XFF, 0XFF, 0XFF, 0XE0, 0X01, 0XFF, 0XF0, 0X7F,
|
||||||
|
0XC3, 0XFF, 0XFF, 0XC0, 0X00, 0XFF, 0XF8, 0X7F, 0X01, 0XFF, 0XFF, 0X00,
|
||||||
|
0X00, 0X7F, 0XFF, 0XFE, 0X03, 0XFF, 0XFE, 0X00, 0X00, 0X1F, 0XFF, 0XFF,
|
||||||
|
0X0F, 0XFF, 0XFC, 0X00, 0X00, 0X07, 0XFF, 0XFF, 0XFF, 0XFF, 0XF0, 0X00,
|
||||||
|
0X00, 0X01, 0XFF, 0X3F, 0XFF, 0XFF, 0XE0, 0X00, 0X00, 0X07, 0XFC, 0X39,
|
||||||
|
0XFF, 0XFF, 0X80, 0X00, 0X00, 0X0F, 0XF8, 0X38, 0XFF, 0XFF, 0X00, 0X00,
|
||||||
|
0X00, 0X1F, 0XF0, 0X78, 0X7F, 0XFC, 0X00, 0X00, 0X00, 0X3F, 0XF0, 0XF8,
|
||||||
|
0X7F, 0X00, 0X00, 0X00, 0X00, 0X3F, 0XF1, 0XFC, 0X7F, 0X80, 0X00, 0X00,
|
||||||
|
0X00, 0X7F, 0XFF, 0XFE, 0X3F, 0XC0, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFE,
|
||||||
|
0X3F, 0XC0, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XE0, 0X00, 0X00,
|
||||||
|
0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XE0, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF,
|
||||||
|
0XFF, 0XE0, 0X00, 0X00, 0X01, 0XFF, 0XFF, 0XBF, 0XFF, 0XE0, 0X00, 0X00,
|
||||||
|
0X01, 0XFF, 0XFF, 0XBF, 0XFF, 0XE0, 0X00, 0X00, 0X01, 0XFF, 0XFF, 0X1F,
|
||||||
|
0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XFE, 0X1F, 0XFF, 0XE0, 0X00, 0X00,
|
||||||
|
0X03, 0XFF, 0XFC, 0X0F, 0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XF0, 0X0F,
|
||||||
|
0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XC0, 0X07, 0XFF, 0XE0, 0X00, 0X00,
|
||||||
|
0X07, 0XFE, 0X00, 0X03, 0XFF, 0XE0, 0X00, 0X00, 0X07, 0XF0, 0X00, 0X01,
|
||||||
|
0XFF, 0XE0, 0X00, 0X00, 0X03, 0X80, 0X00, 0X00, 0X7F, 0XE0, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X3F, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X0F, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X07, 0XE0, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0XE0, 0X00, 0X00 };
|
||||||
|
|
||||||
|
#define LOGO_BOTTOM_WIDTH 128
|
||||||
|
#define LOGO_BOTTOM_HEIGHT 37
|
||||||
|
|
||||||
|
const uint8_t logo_bottom[592] PROGMEM= {
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X00, 0X7F, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X00,
|
||||||
|
0XFF, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X3E, 0X00, 0X00, 0X01, 0XFF, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01, 0XFF, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01,
|
||||||
|
0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X3E, 0X00, 0X00, 0X01, 0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01, 0XF0, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X03, 0XE0, 0X1F, 0XFF, 0X00, 0XFE, 0X3E, 0X07, 0XFF, 0XC1,
|
||||||
|
0XFF, 0X1F, 0X0E, 0X7C, 0X03, 0XE3, 0XE3, 0XFF, 0X3F, 0XFF, 0X81, 0XFF,
|
||||||
|
0XBE, 0X0F, 0XFF, 0XE1, 0XFF, 0X1F, 0X3E, 0X7C, 0X03, 0XE3, 0XE3, 0XFF,
|
||||||
|
0X7F, 0XFF, 0XC3, 0XFF, 0XFE, 0X1F, 0XFF, 0XF1, 0XFF, 0X1F, 0X7E, 0X7C,
|
||||||
|
0X03, 0XE3, 0XE3, 0XFF, 0X7F, 0XFF, 0XC7, 0XFF, 0XFE, 0X1F, 0XFF, 0XF1,
|
||||||
|
0XFF, 0X1F, 0XFE, 0X7C, 0X03, 0XE3, 0XE3, 0XFF, 0X7E, 0X0F, 0XC7, 0XFF,
|
||||||
|
0XFE, 0X1F, 0X83, 0XF1, 0XFF, 0X1F, 0XFE, 0X7C, 0X03, 0XE3, 0XE3, 0XFF,
|
||||||
|
0X7C, 0X07, 0XC7, 0XE0, 0X3E, 0X1F, 0X01, 0XF1, 0XF0, 0X1F, 0XFE, 0X7C,
|
||||||
|
0X03, 0XE3, 0XE3, 0XE0, 0X7C, 0X07, 0XC7, 0XE0, 0X3E, 0X1F, 0X01, 0XF1,
|
||||||
|
0XF0, 0X1F, 0X80, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0X00, 0X07, 0XC7, 0XC0,
|
||||||
|
0X3E, 0X00, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
|
||||||
|
0X00, 0X07, 0XC7, 0XC0, 0X3E, 0X00, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
|
||||||
|
0X03, 0XE3, 0XE3, 0XE0, 0X3F, 0XFF, 0XC7, 0XC0, 0X3E, 0X0F, 0XFF, 0XF1,
|
||||||
|
0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0X7F, 0XFF, 0XC7, 0XC0,
|
||||||
|
0X3E, 0X1F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
|
||||||
|
0XFF, 0XFF, 0XC7, 0XC0, 0X3E, 0X3F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
|
||||||
|
0X03, 0XE3, 0XE3, 0XE0, 0XFC, 0X07, 0XC7, 0XC0, 0X3E, 0X3F, 0X01, 0XF1,
|
||||||
|
0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0XF8, 0X07, 0XC7, 0XC0,
|
||||||
|
0X3E, 0X3E, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
|
||||||
|
0XF8, 0X07, 0XC7, 0XE0, 0X3E, 0X3E, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
|
||||||
|
0X03, 0XE3, 0XE3, 0XE0, 0XF8, 0X07, 0XC7, 0XE0, 0X7E, 0X3E, 0X01, 0XF1,
|
||||||
|
0XF0, 0X1F, 0X00, 0X7E, 0X03, 0XE3, 0XE3, 0XE0, 0XFC, 0X1F, 0XC7, 0XFF,
|
||||||
|
0XFE, 0X3F, 0X07, 0XF1, 0XF0, 0X1F, 0X00, 0X7F, 0XFF, 0XE3, 0XE3, 0XFF,
|
||||||
|
0XFF, 0XFF, 0XC7, 0XFF, 0XFE, 0X3F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7F,
|
||||||
|
0XFF, 0XE3, 0XE3, 0XFF, 0XFF, 0XFF, 0XC3, 0XFF, 0XBE, 0X3F, 0XFF, 0XF1,
|
||||||
|
0XF0, 0X1F, 0X00, 0X7F, 0XFF, 0XE3, 0XE3, 0XFF, 0X7F, 0XE7, 0XC3, 0XFF,
|
||||||
|
0X3E, 0X1F, 0XF9, 0XF1, 0XF0, 0X1F, 0X00, 0X3F, 0XE3, 0XE3, 0XE1, 0XFF,
|
||||||
|
0X1F, 0X87, 0XC0, 0XFC, 0X3E, 0X07, 0XE1, 0XF1, 0XF0, 0X1F, 0X00, 0X0F,
|
||||||
|
0XC1, 0XE3, 0XE0, 0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||||
|
0XB7, 0X63, 0XDD, 0XC6, 0X08, 0X76, 0X1C, 0X7F, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||||
|
0XFF, 0XFF, 0XFF, 0XFF, 0XB3, 0X6D, 0XDD, 0XBB, 0XBB, 0XB6, 0XFB, 0XBF,
|
||||||
|
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XB3, 0X6E, 0XDD, 0XBF,
|
||||||
|
0XBB, 0XB6, 0XFB, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||||
|
0XB5, 0X6E, 0XDD, 0XC7, 0XB8, 0X76, 0X3C, 0X7F, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||||
|
0XFF, 0XFF, 0XFF, 0XFF, 0XB5, 0X6E, 0XDD, 0XFB, 0XBB, 0XB6, 0XFF, 0XBF,
|
||||||
|
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XB6, 0X6D, 0XEB, 0XBB,
|
||||||
|
0XBB, 0XB6, 0XFB, 0XBF };
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
// Pin selections here are based on the original Adafruit Learning System
|
||||||
|
// guide for the Teensy 3.x project. Some of these pin numbers don't even
|
||||||
|
// exist on the smaller SAMD M0 & M4 boards, so you may need to make other
|
||||||
|
// selections:
|
||||||
|
|
||||||
|
// GRAPHICS SETTINGS (appearance of eye) -----------------------------------
|
||||||
|
|
||||||
|
// If using a SINGLE EYE, you might want this next line enabled, which
|
||||||
|
// uses a simpler "football-shaped" eye that's left/right symmetrical.
|
||||||
|
// Default shape includes the caruncle, creating distinct left/right eyes.
|
||||||
|
|
||||||
|
//#define SYMMETRICAL_EYELID
|
||||||
|
|
||||||
|
// Enable ONE of these #includes -- HUGE graphics tables for various eyes:
|
||||||
|
#include "data/defaultEye.h" // Standard human-ish hazel eye -OR-
|
||||||
|
//#include "data/dragonEye.h" // Slit pupil fiery dragon/demon eye -OR-
|
||||||
|
//#include "data/noScleraEye.h" // Large iris, no sclera -OR-
|
||||||
|
//#include "data/goatEye.h" // Horizontal pupil goat/Krampus eye -OR-
|
||||||
|
//#include "data/newtEye.h" // Eye of newt -OR-
|
||||||
|
//#include "data/terminatorEye.h" // Git to da choppah!
|
||||||
|
//#include "data/catEye.h" // Cartoonish cat (flat "2D" colors)
|
||||||
|
//#include "data/owlEye.h" // Minerva the owl (DISABLE TRACKING)
|
||||||
|
//#include "data/naugaEye.h" // Nauga googly eye (DISABLE TRACKING)
|
||||||
|
//#include "data/doeEye.h" // Cartoon deer eye (DISABLE TRACKING)
|
||||||
|
|
||||||
|
// DISPLAY HARDWARE SETTINGS (screen type & connections) -------------------
|
||||||
|
#define TFT_COUNT 2 // Number of screens (1 or 2)
|
||||||
|
#define TFT1_CS 22 // TFT 1 chip select pin (set to -1 to use TFT_eSPI setup)
|
||||||
|
#define TFT2_CS 21 // TFT 2 chip select pin (set to -1 to use TFT_eSPI setup)
|
||||||
|
#define TFT_1_ROT 1 // TFT 1 rotation
|
||||||
|
#define TFT_2_ROT 3 // TFT 2 rotation
|
||||||
|
#define EYE_1_XPOSITION 0 // x shift for eye 1 image on display
|
||||||
|
#define EYE_2_XPOSITION 0 // x shift for eye 2 image on display
|
||||||
|
|
||||||
|
#define DISPLAY_BACKLIGHT -1 // Pin for backlight control (-1 for none)
|
||||||
|
#define BACKLIGHT_MAX 255
|
||||||
|
|
||||||
|
// EYE LIST ----------------------------------------------------------------
|
||||||
|
#define NUM_EYES 2 // Number of eyes to display (1 or 2)
|
||||||
|
|
||||||
|
#define BLINK_PIN -1 // Pin for manual blink button (BOTH eyes)
|
||||||
|
#define LH_WINK_PIN -1 // Left wink pin (set to -1 for no pin)
|
||||||
|
#define RH_WINK_PIN -1 // Right wink pin (set to -1 for no pin)
|
||||||
|
|
||||||
|
// This table contains ONE LINE PER EYE. The table MUST be present with
|
||||||
|
// this name and contain ONE OR MORE lines. Each line contains THREE items:
|
||||||
|
// a pin number for the corresponding TFT/OLED display's SELECT line, a pin
|
||||||
|
// pin number for that eye's "wink" button (or -1 if not used), a screen
|
||||||
|
// rotation value (0-3) and x position offset for that eye.
|
||||||
|
|
||||||
|
#if (NUM_EYES == 2)
|
||||||
|
eyeInfo_t eyeInfo[] = {
|
||||||
|
{ TFT1_CS, LH_WINK_PIN, TFT_1_ROT, EYE_1_XPOSITION }, // LEFT EYE chip select and wink pins, rotation and offset
|
||||||
|
{ TFT2_CS, RH_WINK_PIN, TFT_2_ROT, EYE_2_XPOSITION }, // RIGHT EYE chip select and wink pins, rotation and offset
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
eyeInfo_t eyeInfo[] = {
|
||||||
|
{ TFT1_CS, LH_WINK_PIN, TFT_1_ROT, EYE_1_XPOSITION }, // EYE chip select and wink pins, rotation and offset
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// INPUT SETTINGS (for controlling eye motion) -----------------------------
|
||||||
|
|
||||||
|
// JOYSTICK_X_PIN and JOYSTICK_Y_PIN specify analog input pins for manually
|
||||||
|
// controlling the eye with an analog joystick. If set to -1 or if not
|
||||||
|
// defined, the eye will move on its own.
|
||||||
|
// IRIS_PIN speficies an analog input pin for a photocell to make pupils
|
||||||
|
// react to light (or potentiometer for manual control). If set to -1 or
|
||||||
|
// if not defined, the pupils will change on their own.
|
||||||
|
// BLINK_PIN specifies an input pin for a button (to ground) that will
|
||||||
|
// make any/all eyes blink. If set to -1 or if not defined, the eyes will
|
||||||
|
// only blink if AUTOBLINK is defined, or if the eyeInfo[] table above
|
||||||
|
// includes wink button settings for each eye.
|
||||||
|
|
||||||
|
//#define JOYSTICK_X_PIN A0 // Analog pin for eye horiz pos (else auto)
|
||||||
|
//#define JOYSTICK_Y_PIN A1 // Analog pin for eye vert position (")
|
||||||
|
//#define JOYSTICK_X_FLIP // If defined, reverse stick X axis
|
||||||
|
//#define JOYSTICK_Y_FLIP // If defined, reverse stick Y axis
|
||||||
|
#define TRACKING // If defined, eyelid tracks pupil
|
||||||
|
#define AUTOBLINK // If defined, eyes also blink autonomously
|
||||||
|
|
||||||
|
// #define LIGHT_PIN -1 // Light sensor pin
|
||||||
|
#define LIGHT_CURVE 0.33 // Light sensor adjustment curve
|
||||||
|
#define LIGHT_MIN 0 // Minimum useful reading from light sensor
|
||||||
|
#define LIGHT_MAX 1023 // Maximum useful reading from sensor
|
||||||
|
|
||||||
|
#define IRIS_SMOOTH // If enabled, filter input from IRIS_PIN
|
||||||
|
#if !defined(IRIS_MIN) // Each eye might have its own MIN/MAX
|
||||||
|
#define IRIS_MIN 90 // Iris size (0-1023) in brightest light
|
||||||
|
#endif
|
||||||
|
#if !defined(IRIS_MAX)
|
||||||
|
#define IRIS_MAX 130 // Iris size (0-1023) in darkest light
|
||||||
|
#endif
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,101 @@
|
||||||
|
// Logo helps with screen orientation & positioning
|
||||||
|
|
||||||
|
#define LOGO_TOP_WIDTH 59
|
||||||
|
#define LOGO_TOP_HEIGHT 59
|
||||||
|
|
||||||
|
const uint8_t logo_top[472] PROGMEM= {
|
||||||
|
0X00, 0X00, 0X00, 0X01, 0XC0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03,
|
||||||
|
0XC0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X07, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X0F,
|
||||||
|
0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X0F, 0XF0, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X1F, 0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3F,
|
||||||
|
0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X7F, 0XF8, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X7F, 0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0XFF,
|
||||||
|
0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X01, 0XFF, 0XFC, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X01, 0XFF, 0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X01, 0XFF,
|
||||||
|
0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XFF, 0XFC, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X03, 0XFF, 0XFE, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0X83, 0XFF,
|
||||||
|
0XFE, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XF3, 0XFF, 0XFE, 0X00, 0X00, 0X00,
|
||||||
|
0XFF, 0XFF, 0XFB, 0XFF, 0XFC, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFF, 0XFF,
|
||||||
|
0XFC, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFF, 0XFB, 0XFC, 0X30, 0X00, 0X00,
|
||||||
|
0X3F, 0XFF, 0XFF, 0XF1, 0XFB, 0XFF, 0X00, 0X00, 0X1F, 0XFF, 0XFF, 0XF1,
|
||||||
|
0XFF, 0XFF, 0XE0, 0X00, 0X1F, 0XFF, 0XFF, 0XE1, 0XFF, 0XFF, 0XFE, 0X00,
|
||||||
|
0X0F, 0XFF, 0XFF, 0XE1, 0XFF, 0XFF, 0XFF, 0X80, 0X07, 0XFF, 0XEF, 0XE1,
|
||||||
|
0XFF, 0XFF, 0XFF, 0XE0, 0X03, 0XFF, 0XC1, 0XE3, 0XFF, 0XFF, 0XFF, 0XE0,
|
||||||
|
0X03, 0XFF, 0XC0, 0XF3, 0XFF, 0XFF, 0XFF, 0XE0, 0X01, 0XFF, 0XF0, 0X7F,
|
||||||
|
0XC3, 0XFF, 0XFF, 0XC0, 0X00, 0XFF, 0XF8, 0X7F, 0X01, 0XFF, 0XFF, 0X00,
|
||||||
|
0X00, 0X7F, 0XFF, 0XFE, 0X03, 0XFF, 0XFE, 0X00, 0X00, 0X1F, 0XFF, 0XFF,
|
||||||
|
0X0F, 0XFF, 0XFC, 0X00, 0X00, 0X07, 0XFF, 0XFF, 0XFF, 0XFF, 0XF0, 0X00,
|
||||||
|
0X00, 0X01, 0XFF, 0X3F, 0XFF, 0XFF, 0XE0, 0X00, 0X00, 0X07, 0XFC, 0X39,
|
||||||
|
0XFF, 0XFF, 0X80, 0X00, 0X00, 0X0F, 0XF8, 0X38, 0XFF, 0XFF, 0X00, 0X00,
|
||||||
|
0X00, 0X1F, 0XF0, 0X78, 0X7F, 0XFC, 0X00, 0X00, 0X00, 0X3F, 0XF0, 0XF8,
|
||||||
|
0X7F, 0X00, 0X00, 0X00, 0X00, 0X3F, 0XF1, 0XFC, 0X7F, 0X80, 0X00, 0X00,
|
||||||
|
0X00, 0X7F, 0XFF, 0XFE, 0X3F, 0XC0, 0X00, 0X00, 0X00, 0X7F, 0XFF, 0XFE,
|
||||||
|
0X3F, 0XC0, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XE0, 0X00, 0X00,
|
||||||
|
0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XE0, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF,
|
||||||
|
0XFF, 0XE0, 0X00, 0X00, 0X01, 0XFF, 0XFF, 0XBF, 0XFF, 0XE0, 0X00, 0X00,
|
||||||
|
0X01, 0XFF, 0XFF, 0XBF, 0XFF, 0XE0, 0X00, 0X00, 0X01, 0XFF, 0XFF, 0X1F,
|
||||||
|
0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XFE, 0X1F, 0XFF, 0XE0, 0X00, 0X00,
|
||||||
|
0X03, 0XFF, 0XFC, 0X0F, 0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XF0, 0X0F,
|
||||||
|
0XFF, 0XE0, 0X00, 0X00, 0X03, 0XFF, 0XC0, 0X07, 0XFF, 0XE0, 0X00, 0X00,
|
||||||
|
0X07, 0XFE, 0X00, 0X03, 0XFF, 0XE0, 0X00, 0X00, 0X07, 0XF0, 0X00, 0X01,
|
||||||
|
0XFF, 0XE0, 0X00, 0X00, 0X03, 0X80, 0X00, 0X00, 0X7F, 0XE0, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X3F, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X0F, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X07, 0XE0, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0XE0, 0X00, 0X00 };
|
||||||
|
|
||||||
|
#define LOGO_BOTTOM_WIDTH 128
|
||||||
|
#define LOGO_BOTTOM_HEIGHT 37
|
||||||
|
|
||||||
|
const uint8_t logo_bottom[592] PROGMEM= {
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X00, 0X7F, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X00,
|
||||||
|
0XFF, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X3E, 0X00, 0X00, 0X01, 0XFF, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01, 0XFF, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01,
|
||||||
|
0XF8, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X3E, 0X00, 0X00, 0X01, 0XF0, 0X00, 0X00, 0X00, 0X00, 0X00, 0X03, 0XE0,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X3E, 0X00, 0X00, 0X01, 0XF0, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X03, 0XE0, 0X1F, 0XFF, 0X00, 0XFE, 0X3E, 0X07, 0XFF, 0XC1,
|
||||||
|
0XFF, 0X1F, 0X0E, 0X7C, 0X03, 0XE3, 0XE3, 0XFF, 0X3F, 0XFF, 0X81, 0XFF,
|
||||||
|
0XBE, 0X0F, 0XFF, 0XE1, 0XFF, 0X1F, 0X3E, 0X7C, 0X03, 0XE3, 0XE3, 0XFF,
|
||||||
|
0X7F, 0XFF, 0XC3, 0XFF, 0XFE, 0X1F, 0XFF, 0XF1, 0XFF, 0X1F, 0X7E, 0X7C,
|
||||||
|
0X03, 0XE3, 0XE3, 0XFF, 0X7F, 0XFF, 0XC7, 0XFF, 0XFE, 0X1F, 0XFF, 0XF1,
|
||||||
|
0XFF, 0X1F, 0XFE, 0X7C, 0X03, 0XE3, 0XE3, 0XFF, 0X7E, 0X0F, 0XC7, 0XFF,
|
||||||
|
0XFE, 0X1F, 0X83, 0XF1, 0XFF, 0X1F, 0XFE, 0X7C, 0X03, 0XE3, 0XE3, 0XFF,
|
||||||
|
0X7C, 0X07, 0XC7, 0XE0, 0X3E, 0X1F, 0X01, 0XF1, 0XF0, 0X1F, 0XFE, 0X7C,
|
||||||
|
0X03, 0XE3, 0XE3, 0XE0, 0X7C, 0X07, 0XC7, 0XE0, 0X3E, 0X1F, 0X01, 0XF1,
|
||||||
|
0XF0, 0X1F, 0X80, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0X00, 0X07, 0XC7, 0XC0,
|
||||||
|
0X3E, 0X00, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
|
||||||
|
0X00, 0X07, 0XC7, 0XC0, 0X3E, 0X00, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
|
||||||
|
0X03, 0XE3, 0XE3, 0XE0, 0X3F, 0XFF, 0XC7, 0XC0, 0X3E, 0X0F, 0XFF, 0XF1,
|
||||||
|
0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0X7F, 0XFF, 0XC7, 0XC0,
|
||||||
|
0X3E, 0X1F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
|
||||||
|
0XFF, 0XFF, 0XC7, 0XC0, 0X3E, 0X3F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
|
||||||
|
0X03, 0XE3, 0XE3, 0XE0, 0XFC, 0X07, 0XC7, 0XC0, 0X3E, 0X3F, 0X01, 0XF1,
|
||||||
|
0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0, 0XF8, 0X07, 0XC7, 0XC0,
|
||||||
|
0X3E, 0X3E, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C, 0X03, 0XE3, 0XE3, 0XE0,
|
||||||
|
0XF8, 0X07, 0XC7, 0XE0, 0X3E, 0X3E, 0X01, 0XF1, 0XF0, 0X1F, 0X00, 0X7C,
|
||||||
|
0X03, 0XE3, 0XE3, 0XE0, 0XF8, 0X07, 0XC7, 0XE0, 0X7E, 0X3E, 0X01, 0XF1,
|
||||||
|
0XF0, 0X1F, 0X00, 0X7E, 0X03, 0XE3, 0XE3, 0XE0, 0XFC, 0X1F, 0XC7, 0XFF,
|
||||||
|
0XFE, 0X3F, 0X07, 0XF1, 0XF0, 0X1F, 0X00, 0X7F, 0XFF, 0XE3, 0XE3, 0XFF,
|
||||||
|
0XFF, 0XFF, 0XC7, 0XFF, 0XFE, 0X3F, 0XFF, 0XF1, 0XF0, 0X1F, 0X00, 0X7F,
|
||||||
|
0XFF, 0XE3, 0XE3, 0XFF, 0XFF, 0XFF, 0XC3, 0XFF, 0XBE, 0X3F, 0XFF, 0XF1,
|
||||||
|
0XF0, 0X1F, 0X00, 0X7F, 0XFF, 0XE3, 0XE3, 0XFF, 0X7F, 0XE7, 0XC3, 0XFF,
|
||||||
|
0X3E, 0X1F, 0XF9, 0XF1, 0XF0, 0X1F, 0X00, 0X3F, 0XE3, 0XE3, 0XE1, 0XFF,
|
||||||
|
0X1F, 0X87, 0XC0, 0XFC, 0X3E, 0X07, 0XE1, 0XF1, 0XF0, 0X1F, 0X00, 0X0F,
|
||||||
|
0XC1, 0XE3, 0XE0, 0XFC, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
|
||||||
|
0X00, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||||
|
0XB7, 0X63, 0XDD, 0XC6, 0X08, 0X76, 0X1C, 0X7F, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||||
|
0XFF, 0XFF, 0XFF, 0XFF, 0XB3, 0X6D, 0XDD, 0XBB, 0XBB, 0XB6, 0XFB, 0XBF,
|
||||||
|
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XB3, 0X6E, 0XDD, 0XBF,
|
||||||
|
0XBB, 0XB6, 0XFB, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||||
|
0XB5, 0X6E, 0XDD, 0XC7, 0XB8, 0X76, 0X3C, 0X7F, 0XFF, 0XFF, 0XFF, 0XFF,
|
||||||
|
0XFF, 0XFF, 0XFF, 0XFF, 0XB5, 0X6E, 0XDD, 0XFB, 0XBB, 0XB6, 0XFF, 0XBF,
|
||||||
|
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XB6, 0X6D, 0XEB, 0XBB,
|
||||||
|
0XBB, 0XB6, 0XFB, 0XBF };
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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