First Commit
This commit is contained in:
@@ -0,0 +1,442 @@
|
||||
// Adapted by Bodmer to work with a NodeMCU and ILI9341 or ST7735 display.
|
||||
//
|
||||
// This code currently does not "blink" the eye!
|
||||
//
|
||||
// Library used is here:
|
||||
// https://github.com/Bodmer/TFT_eSPI
|
||||
//
|
||||
// To do, maybe, one day:
|
||||
// 1. Get the eye to blink
|
||||
// 2. Add another screen for another eye
|
||||
// 3. Add variable to set how wide open the eye is
|
||||
// 4. Add a reflected highlight to the cornea
|
||||
// 5. Add top eyelid shadow to eye surface
|
||||
// 6. Add aliasing to blur mask edge
|
||||
//
|
||||
// With one lidded eye drawn the code runs at 28-33fps (at 27-40MHz SPI clock)
|
||||
// which is quite reasonable. Operation at an 80MHz SPI clock is possible but
|
||||
// the display may not be able to cope with a clock rate that high and the
|
||||
// performance improvement is small. Operate the ESP8266 at 160MHz for best
|
||||
// frame rate. Note the images are stored in SPI FLASH (PROGMEM) so performance
|
||||
// will be constrained by the increased memory access time.
|
||||
|
||||
// Original header for this sketch is below. Note: the technical aspects of the
|
||||
// text no longer apply to this modified version of the sketch:
|
||||
/*
|
||||
//--------------------------------------------------------------------------
|
||||
// Uncanny eyes for PJRC Teensy 3.1 with Adafruit 1.5" OLED (product #1431)
|
||||
// or 1.44" TFT LCD (#2088). This uses Teensy-3.1-specific features and
|
||||
// WILL NOT work on normal Arduino or other boards! Use 72 MHz (Optimized)
|
||||
// board speed -- OLED does not work at 96 MHz.
|
||||
//
|
||||
// Adafruit invests time and resources providing this open source code,
|
||||
// please support Adafruit and open-source hardware by purchasing products
|
||||
// from Adafruit!
|
||||
//
|
||||
// Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
|
||||
// MIT license. SPI FIFO insight from Paul Stoffregen's ILI9341_t3 library.
|
||||
// Inspired by David Boccabella's (Marcwolf) hybrid servo/OLED eye concept.
|
||||
//--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include <SPI.h>
|
||||
#include <TFT_eSPI.h> // Hardware-specific library
|
||||
|
||||
// Enable ONE of these #includes for the various eyes:
|
||||
#include "defaultEye.h" // Standard human-ish hazel eye
|
||||
//#include "noScleraEye.h" // Large iris, no sclera
|
||||
//#include "dragonEye.h" // Slit pupil fiery dragon/demon eye
|
||||
//#include "goatEye.h" // Horizontal pupil goat/Krampus eye
|
||||
|
||||
|
||||
#define DISPLAY_DC D3 // Data/command pin for BOTH displays
|
||||
#define DISPLAY_RESET D4 // Reset pin for BOTH displays
|
||||
#define SELECT_L_PIN D8 // LEFT eye chip select pin
|
||||
#define SELECT_R_PIN D8 // RIGHT eye chip select pin
|
||||
|
||||
// INPUT CONFIG (for eye motion -- enable or comment out as needed) --------
|
||||
|
||||
// The ESP8266 is rather constrained here as it only has one analogue port.
|
||||
// An I2C ADC could be used for more analogue channels
|
||||
//#define JOYSTICK_X_PIN A0 // Analogue pin for eye horiz pos (else auto)
|
||||
//#define JOYSTICK_Y_PIN A0 // Analogue pin for eye vert position (")
|
||||
//#define JOYSTICK_X_FLIP // If set, reverse stick X axis
|
||||
//#define JOYSTICK_Y_FLIP // If set, reverse stick Y axis
|
||||
#define TRACKING // If enabled, eyelid tracks pupil
|
||||
//#define IRIS_PIN A0 // Photocell or potentiometer (else auto iris)
|
||||
//#define IRIS_PIN_FLIP // If set, reverse reading from dial/photocell
|
||||
//#define IRIS_SMOOTH // If enabled, filter input from IRIS_PIN
|
||||
#define IRIS_MIN 140 // Clip lower analogRead() range from IRIS_PIN
|
||||
#define IRIS_MAX 260 // Clip upper "
|
||||
#define WINK_L_PIN 0 // Pin for LEFT eye wink button
|
||||
#define BLINK_PIN 1 // Pin for blink button (BOTH eyes)
|
||||
#define WINK_R_PIN 2 // Pin for RIGHT eye wink button
|
||||
#define AUTOBLINK // If enabled, eyes blink autonomously
|
||||
|
||||
// Probably don't need to edit any config below this line, -----------------
|
||||
// unless building a single-eye project (pendant, etc.), in which case one
|
||||
// of the two elements in the eye[] array further down can be commented out.
|
||||
|
||||
// Eye blinks are a tiny 3-state machine. Per-eye allows winks + blinks.
|
||||
#define NOBLINK 0 // Not currently engaged in a blink
|
||||
#define ENBLINK 1 // Eyelid is currently closing
|
||||
#define DEBLINK 2 // Eyelid is currently opening
|
||||
typedef struct {
|
||||
int8_t pin; // Optional button here for indiv. wink
|
||||
uint8_t state; // NOBLINK/ENBLINK/DEBLINK
|
||||
int32_t duration; // Duration of blink state (micros)
|
||||
uint32_t startTime; // Time (micros) of last state change
|
||||
} eyeBlink;
|
||||
|
||||
struct {
|
||||
TFT_eSPI tft; // OLED/eye[e].tft object
|
||||
uint8_t cs; // Chip select pin
|
||||
eyeBlink blink; // Current blink state
|
||||
} eye[] = { // OK to comment out one of these for single-eye display:
|
||||
TFT_eSPI(),SELECT_L_PIN,{WINK_L_PIN,NOBLINK},
|
||||
//TFT_eSPI(),SELECT_R_PIN,{WINK_R_PIN,NOBLINK},
|
||||
};
|
||||
|
||||
#define NUM_EYES (sizeof(eye) / sizeof(eye[0]))
|
||||
|
||||
uint32_t fstart = 0; // start time to improve frame rate calculation at startup
|
||||
|
||||
// INITIALIZATION -- runs once at startup ----------------------------------
|
||||
|
||||
void setup(void) {
|
||||
uint8_t e = 0;
|
||||
|
||||
Serial.begin(250000);
|
||||
randomSeed(analogRead(A0)); // Seed random() from floating analogue input
|
||||
|
||||
eye[e].tft.init();
|
||||
eye[e].tft.fillScreen(TFT_BLACK);
|
||||
eye[e].tft.setRotation(0);
|
||||
|
||||
fstart = millis()-1; // Subtract 1 to avoid divide by zero later
|
||||
}
|
||||
|
||||
|
||||
// EYE-RENDERING FUNCTION --------------------------------------------------
|
||||
#define BUFFER_SIZE 256 // 64 to 512 seems optimum = 30 fps for default eye
|
||||
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;
|
||||
uint16_t pbuffer[BUFFER_SIZE]; // This one needs to be 16-bit
|
||||
|
||||
// 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.
|
||||
|
||||
//eye[e].tft.setAddrWindow(319-127, 0, 319, 127);
|
||||
eye[e].tft.setAddrWindow(0, 0, 128, 128);
|
||||
|
||||
//digitalWrite(eye[e].cs, LOW); // Chip select
|
||||
|
||||
// Now just issue raw 16-bit values for every pixel...
|
||||
|
||||
scleraXsave = scleraX; // Save initial X value to reset on each line
|
||||
irisY = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT) / 2;
|
||||
for(screenY=0; screenY<SCREEN_HEIGHT; screenY++, scleraY++, irisY++) {
|
||||
scleraX = scleraXsave;
|
||||
irisX = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
|
||||
for(screenX=0; screenX<SCREEN_WIDTH; screenX++, scleraX++, irisX++) {
|
||||
if((pgm_read_byte(lower + screenY * SCREEN_WIDTH + screenX) <= lT) ||
|
||||
(pgm_read_byte(upper + screenY * SCREEN_WIDTH + screenX) <= 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 + pixels++) = p>>8 | p<<8;
|
||||
|
||||
if (pixels >= BUFFER_SIZE) { yield(); eye[e].tft.pushColors((uint8_t*)pbuffer, pixels*2); pixels = 0;}
|
||||
}
|
||||
}
|
||||
|
||||
if (pixels) { eye[e].tft.pushColors(pbuffer, pixels); pixels = 0;}
|
||||
}
|
||||
|
||||
|
||||
// EYE ANIMATION -----------------------------------------------------------
|
||||
|
||||
const uint8_t ease[] = { // Ease in/out curve for eye movements 3*t^2-2*t^3
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, // T
|
||||
3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 8, 9, 9, 10, 10, // h
|
||||
11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23, // x
|
||||
24, 25, 26, 27, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, // 2
|
||||
40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58, // A
|
||||
60, 61, 62, 63, 65, 66, 67, 69, 70, 72, 73, 74, 76, 77, 78, 80, // l
|
||||
81, 83, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 98,100,101,103, // e
|
||||
104,106,107,109,110,112,113,115,116,118,119,121,122,124,125,127, // c
|
||||
128,130,131,133,134,136,137,139,140,142,143,145,146,148,149,151, // J
|
||||
152,154,155,157,158,159,161,162,164,165,167,168,170,171,172,174, // a
|
||||
175,177,178,179,181,182,183,185,186,188,189,190,192,193,194,195, // c
|
||||
197,198,199,201,202,203,204,205,207,208,209,210,211,213,214,215, // o
|
||||
216,217,218,219,220,221,222,224,225,226,227,228,228,229,230,231, // b
|
||||
232,233,234,235,236,237,237,238,239,240,240,241,242,243,243,244, // s
|
||||
245,245,246,246,247,248,248,249,249,250,250,251,251,251,252,252, // o
|
||||
252,253,253,253,254,254,254,254,254,255,255,255,255,255,255,255 }; // n
|
||||
|
||||
#ifdef AUTOBLINK
|
||||
uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L;
|
||||
#endif
|
||||
|
||||
void frame( // Process motion for a single frame of left or right eye
|
||||
uint32_t iScale) { // Iris scale (0-1023) passed in
|
||||
static uint32_t frames = 0; // Used in frame rate calculation
|
||||
static uint8_t eyeIndex = 0; // eye[] array counter
|
||||
int32_t eyeX, eyeY;
|
||||
uint32_t t = micros(); // Time at start of function
|
||||
|
||||
Serial.print((++frames * 1000) / (millis() - fstart)); Serial.println("fps");// Show frame rate
|
||||
|
||||
if(++eyeIndex >= NUM_EYES) eyeIndex = 0; // Cycle through eyes, 1 per call
|
||||
|
||||
// Autonomous X/Y eye motion
|
||||
// Periodically initiates motion to a new random point, random speed,
|
||||
// holds there for random period until next motion.
|
||||
|
||||
static bool eyeInMotion = false;
|
||||
static int32_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(3000000L); // 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(50000, 150000);//random(72000, 144000); // ~1/14 - ~1/7 sec
|
||||
eyeMoveStartTime = t; // Save initial time of move
|
||||
eyeInMotion = true; // Start move on next frame
|
||||
}
|
||||
}
|
||||
|
||||
// Blinking
|
||||
/*
|
||||
#ifdef AUTOBLINK
|
||||
// Similar to the autonomous eye movement above -- blink start times
|
||||
// and durations are random (within ranges).
|
||||
if((t - timeOfLastBlink) >= timeToNextBlink) { // Start new blink?
|
||||
timeOfLastBlink = t;
|
||||
uint32_t blinkDuration = random(36000, 72000); // ~1/28 - ~1/14 sec
|
||||
// Set up durations for both eyes (if not already winking)
|
||||
for(uint8_t e=0; e<NUM_EYES; e++) {
|
||||
if(eye[e].blink.state == NOBLINK) {
|
||||
eye[e].blink.state = ENBLINK;
|
||||
eye[e].blink.startTime = t;
|
||||
eye[e].blink.duration = blinkDuration;
|
||||
}
|
||||
}
|
||||
timeToNextBlink = blinkDuration * 3 + random(4000000);
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
/*
|
||||
if(eye[eyeIndex].blink.state) { // Eye currently blinking?
|
||||
// Check if current blink state time has elapsed
|
||||
if((t - eye[eyeIndex].blink.startTime) >= eye[eyeIndex].blink.duration) {
|
||||
// Yes -- increment blink state, unless...
|
||||
if((eye[eyeIndex].blink.state == ENBLINK) && // Enblinking and...
|
||||
((digitalRead(BLINK_PIN) == LOW) || // blink or wink held...
|
||||
digitalRead(eye[eyeIndex].blink.pin) == LOW)) {
|
||||
// Don't advance state yet -- eye is held closed instead
|
||||
} else { // No buttons, or other state...
|
||||
if(++eye[eyeIndex].blink.state > DEBLINK) { // Deblinking finished?
|
||||
eye[eyeIndex].blink.state = NOBLINK; // No longer blinking
|
||||
} else { // Advancing from ENBLINK to DEBLINK mode
|
||||
eye[eyeIndex].blink.duration *= 2; // DEBLINK is 1/2 ENBLINK speed
|
||||
eye[eyeIndex].blink.startTime = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // Not currently blinking...check buttons!
|
||||
if(digitalRead(BLINK_PIN) == LOW) {
|
||||
// Manually-initiated blinks have random durations like auto-blink
|
||||
uint32_t blinkDuration = random(36000, 72000);
|
||||
for(uint8_t e=0; e<NUM_EYES; e++) {
|
||||
if(eye[e].blink.state == NOBLINK) {
|
||||
eye[e].blink.state = ENBLINK;
|
||||
eye[e].blink.startTime = t;
|
||||
eye[e].blink.duration = blinkDuration;
|
||||
}
|
||||
}
|
||||
} else if(digitalRead(eye[eyeIndex].blink.pin) == LOW) { // Wink!
|
||||
eye[eyeIndex].blink.state = ENBLINK;
|
||||
eye[eyeIndex].blink.startTime = t;
|
||||
eye[eyeIndex].blink.duration = random(45000, 90000);
|
||||
}
|
||||
}
|
||||
*/
|
||||
// Process motion, blinking and iris scale into renderable values
|
||||
|
||||
// Iris scaling: remap from 0-1023 input to iris map height pixel units
|
||||
iScale = ((IRIS_MAP_HEIGHT + 1) * 1024) /
|
||||
(1024 - (iScale * (IRIS_MAP_HEIGHT - 1) / IRIS_MAP_HEIGHT));
|
||||
|
||||
// Scale eye X/Y positions (0-1023) to pixel units used by drawEye()
|
||||
eyeX = map(eyeX, 0, 1023, 0, SCLERA_WIDTH - 128);
|
||||
eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128);
|
||||
if(eyeIndex == 1) eyeX = (SCLERA_WIDTH - 128) - eyeX; // Mirrored display
|
||||
|
||||
// Horizontal position is offset so that eyes are very slightly crossed
|
||||
// to appear fixated (converged) at a conversational distance. Number
|
||||
// here was extracted from my posterior and not mathematically based.
|
||||
// I suppose one could get all clever with a range sensor, but for now...
|
||||
eyeX += 4;
|
||||
if(eyeX > (SCLERA_WIDTH - 128)) eyeX = (SCLERA_WIDTH - 128);
|
||||
|
||||
// Eyelids are rendered using a brightness threshold image. This same
|
||||
// map can be used to simplify another problem: making the upper eyelid
|
||||
// track the pupil (eyes tend to open only as much as needed -- e.g. look
|
||||
// down and the upper eyelid drops). Just sample a point in the upper
|
||||
// lid map slightly above the pupil to determine the rendering threshold.
|
||||
static uint8_t uThreshold = 128;
|
||||
uint8_t lThreshold, n;
|
||||
|
||||
#ifdef TRACKING
|
||||
int16_t sampleX = SCLERA_WIDTH / 2 - (eyeX / 2), // Reduce X influence
|
||||
sampleY = SCLERA_HEIGHT / 2 - (eyeY + IRIS_HEIGHT / 4);
|
||||
// Eyelid is slightly asymmetrical, so two readings are taken, averaged
|
||||
if(sampleY < 0) n = 0;
|
||||
else n = (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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// AUTONOMOUS IRIS SCALING (if no photocell or dial) -----------------------
|
||||
|
||||
#if !defined(IRIS_PIN) || (IRIS_PIN < 0)
|
||||
|
||||
// Autonomous iris motion uses a fractal behavior to similate both the major
|
||||
// reaction of the eye plus the continuous smaller adjustments that occur.
|
||||
|
||||
uint16_t oldIris = (IRIS_MIN + IRIS_MAX) / 2, newIris;
|
||||
|
||||
void split( // Subdivides motion path into two sub-paths w/randimization
|
||||
int16_t startValue, // Iris scale value (IRIS_MIN to IRIS_MAX) at start
|
||||
int16_t endValue, // Iris scale value at end
|
||||
uint32_t startTime, // micros() at start
|
||||
int32_t duration, // Start-to-end time, in microseconds
|
||||
int16_t range) { // Allowable scale value variance when subdividing
|
||||
|
||||
if(range >= 8) { // Limit subdvision count, because recursion
|
||||
range /= 2; // Split range & time in half for subdivision,
|
||||
duration /= 2; // then pick random center point within range:
|
||||
int16_t midValue = (startValue + endValue - range) / 2 + random(range);
|
||||
uint32_t midTime = startTime + duration;
|
||||
split(startValue, midValue, startTime, duration, range); // First half
|
||||
split(midValue , endValue, midTime , duration, range); // Second half
|
||||
} else { // No more subdivisons, do iris motion...
|
||||
int32_t dt; // Time (micros) since start of motion
|
||||
int16_t v; // Interim value
|
||||
while((dt = (micros() - startTime)) < duration) {
|
||||
v = startValue + (((endValue - startValue) * dt) / duration);
|
||||
if(v < IRIS_MIN) v = IRIS_MIN; // Clip just in case
|
||||
else if(v > IRIS_MAX) v = IRIS_MAX;
|
||||
frame(v); // Draw frame w/interim iris scale value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !IRIS_PIN
|
||||
|
||||
|
||||
// MAIN LOOP -- runs continuously after setup() ----------------------------
|
||||
|
||||
void loop() {
|
||||
|
||||
#if defined(IRIS_PIN) && (IRIS_PIN >= 0) // Interactive iris
|
||||
|
||||
uint16_t v = 512; //analogRead(IRIS_PIN); // Raw dial/photocell reading
|
||||
#ifdef IRIS_PIN_FLIP
|
||||
v = 1023 - v;
|
||||
#endif
|
||||
v = map(v, 0, 1023, IRIS_MIN, IRIS_MAX); // Scale to iris range
|
||||
#ifdef IRIS_SMOOTH // Filter input (gradual motion)
|
||||
static uint16_t irisValue = (IRIS_MIN + IRIS_MAX) / 2;
|
||||
irisValue = ((irisValue * 15) + v) / 16;
|
||||
frame(irisValue);
|
||||
#else // Unfiltered (immediate motion)
|
||||
frame(v);
|
||||
#endif // IRIS_SMOOTH
|
||||
|
||||
#else // Autonomous iris scaling -- invoke recursive function
|
||||
|
||||
newIris = random(IRIS_MIN, IRIS_MAX);
|
||||
split(oldIris, newIris, micros(), 10000000L, IRIS_MAX - IRIS_MIN);
|
||||
oldIris = newIris;
|
||||
|
||||
#endif // IRIS_PIN
|
||||
|
||||
//screenshotToConsole();
|
||||
}
|
||||
|
||||
|
||||
|
||||
11966
libraries/TFT_eSPI/examples/Generic/ESP8266_uncannyEyes/defaultEye.h
Normal file
11966
libraries/TFT_eSPI/examples/Generic/ESP8266_uncannyEyes/defaultEye.h
Normal file
File diff suppressed because it is too large
Load Diff
15638
libraries/TFT_eSPI/examples/Generic/ESP8266_uncannyEyes/dragonEye.h
Normal file
15638
libraries/TFT_eSPI/examples/Generic/ESP8266_uncannyEyes/dragonEye.h
Normal file
File diff suppressed because it is too large
Load Diff
11430
libraries/TFT_eSPI/examples/Generic/ESP8266_uncannyEyes/goatEye.h
Normal file
11430
libraries/TFT_eSPI/examples/Generic/ESP8266_uncannyEyes/goatEye.h
Normal file
File diff suppressed because it is too large
Load Diff
15638
libraries/TFT_eSPI/examples/Generic/ESP8266_uncannyEyes/noScleraEye.h
Normal file
15638
libraries/TFT_eSPI/examples/Generic/ESP8266_uncannyEyes/noScleraEye.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,220 @@
|
||||
// Include code in this tab and call screenshotToConsole() this dumps an
|
||||
// image off the screen and sends it to a PC via the serial port in a Run
|
||||
// Length Encoded format for viewing with a "ILIScreenshotViewer" utility.
|
||||
|
||||
// The PC "ILIScreenshotViewer" is part of the ILI9241_due library in the
|
||||
// Tools folder, that library can be found here:
|
||||
// https://github.com/marekburiak/ILI9341_Due
|
||||
|
||||
// Converted by Bodmer to operate with the TFT_ILI9341_ESP library:
|
||||
// https://github.com/Bodmer/TFT_ILI9341_ESP
|
||||
|
||||
/*
|
||||
The functions below have been adapted from the ILI9341_due library, the file
|
||||
header from the .cpp source file is included below:
|
||||
|
||||
ILI9341_due_.cpp - Arduino Due library for interfacing with ILI9341-based TFTs
|
||||
|
||||
Copyright (c) 2014 Marek Buriak
|
||||
|
||||
This library is based on ILI9341_t3 library from Paul Stoffregen
|
||||
(https://github.com/PaulStoffregen/ILI9341_t3), Adafruit_ILI9341
|
||||
and Adafruit_GFX libraries from Limor Fried/Ladyada
|
||||
(https://github.com/adafruit/Adafruit_ILI9341).
|
||||
|
||||
This file is part of the Arduino ILI9341_due library.
|
||||
Sources for this library can be found at https://github.com/marekburiak/ILI9341_Due.
|
||||
|
||||
ILI9341_due is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
ILI9341_due is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details:
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
//====================================================================================
|
||||
|
||||
void screenshotToConsole()
|
||||
{
|
||||
uint8_t e = 0;
|
||||
uint8_t lastColor[3];
|
||||
uint8_t color[3];
|
||||
uint32_t sameColorPixelCount = 0;
|
||||
uint16_t sameColorPixelCount16 = 0;
|
||||
uint32_t sameColorStartIndex = 0;
|
||||
uint32_t totalImageDataLength = 0;
|
||||
|
||||
// delay(1000);
|
||||
|
||||
// Header text
|
||||
Serial.println((eye[e].tft.width() - 1));
|
||||
Serial.println((eye[e].tft.height() - 1));
|
||||
Serial.println(F("==== PIXEL DATA START ===="));
|
||||
|
||||
// Get first pixel to prime the Run Length Encoded
|
||||
// Function format is: tft.readRectRGB( x, y, width, height, buffer);
|
||||
// color is a pointer to a buffer that the RGB 8-bit values are piped into
|
||||
// the buffer size must be >= (width * height * 3) bytes
|
||||
eye[e].tft.readRectRGB(0, 0, 1, 1, color); // 1 x 1 so reading 1 pixel at 0,0
|
||||
|
||||
lastColor[0] = color[0]; // Red
|
||||
lastColor[1] = color[1]; // Green
|
||||
lastColor[2] = color[2]; // Blue
|
||||
|
||||
printHex8(color, 3); //Send color of the first pixel to serial port
|
||||
totalImageDataLength += 6;
|
||||
sameColorStartIndex = 0;
|
||||
|
||||
for (uint32_t py = 0; py < (eye[e].tft.height() - 1); py++)
|
||||
{
|
||||
for (uint32_t px = 0; px < (eye[e].tft.width() - 1); px++)
|
||||
{
|
||||
uint32_t i = px + eye[e].tft.width() * py;
|
||||
yield();
|
||||
if (i)
|
||||
{
|
||||
eye[e].tft.readRectRGB(px, py, 1, 1, color);
|
||||
|
||||
if (color[0] != lastColor[0] ||
|
||||
color[1] != lastColor[1] ||
|
||||
color[2] != lastColor[2])
|
||||
{
|
||||
sameColorPixelCount = i - sameColorStartIndex;
|
||||
if (sameColorPixelCount > 65535)
|
||||
{
|
||||
sameColorPixelCount16 = 65535;
|
||||
printHex16(&sameColorPixelCount16, 1);
|
||||
printHex8(lastColor, 3);
|
||||
totalImageDataLength += 10;
|
||||
sameColorPixelCount16 = sameColorPixelCount - 65535;
|
||||
}
|
||||
else
|
||||
sameColorPixelCount16 = sameColorPixelCount;
|
||||
printHex16(&sameColorPixelCount16, 1);
|
||||
printHex8(color, 3);
|
||||
totalImageDataLength += 10;
|
||||
|
||||
sameColorStartIndex = i;
|
||||
lastColor[0] = color[0];
|
||||
lastColor[1] = color[1];
|
||||
lastColor[2] = color[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sameColorPixelCount = (uint32_t)eye[e].tft.width() * (uint32_t)eye[e].tft.height() - sameColorStartIndex;
|
||||
if (sameColorPixelCount > 65535)
|
||||
{
|
||||
sameColorPixelCount16 = 65535;
|
||||
printHex16(&sameColorPixelCount16, 1);
|
||||
printHex8(lastColor, 3);
|
||||
totalImageDataLength += 10;
|
||||
sameColorPixelCount16 = sameColorPixelCount - 65535;
|
||||
}
|
||||
else
|
||||
sameColorPixelCount16 = sameColorPixelCount;
|
||||
printHex16(&sameColorPixelCount16, 1);
|
||||
totalImageDataLength += 4;
|
||||
printHex32(&totalImageDataLength, 1);
|
||||
|
||||
// Footer text
|
||||
Serial.println();
|
||||
Serial.println(F("==== PIXEL DATA END ===="));
|
||||
Serial.print(F("Total Image Data Length: "));
|
||||
Serial.println(totalImageDataLength);
|
||||
}
|
||||
|
||||
void printHex8(uint8_t *data, uint8_t length) // prints 8-bit data in hex
|
||||
{
|
||||
char tmp[length * 2 + 1];
|
||||
byte first;
|
||||
byte second;
|
||||
for (int i = 0; i < length; i++) {
|
||||
first = (data[i] >> 4) & 0x0f;
|
||||
second = data[i] & 0x0f;
|
||||
// base for converting single digit numbers to ASCII is 48
|
||||
// base for 10-16 to become upper-case characters A-F is 55
|
||||
// note: difference is 7
|
||||
tmp[i * 2] = first + 48;
|
||||
tmp[i * 2 + 1] = second + 48;
|
||||
if (first > 9) tmp[i * 2] += 7;
|
||||
if (second > 9) tmp[i * 2 + 1] += 7;
|
||||
}
|
||||
tmp[length * 2] = 0;
|
||||
Serial.print(tmp);
|
||||
}
|
||||
|
||||
void printHex16(uint16_t *data, uint8_t length) // prints 8-bit data in hex
|
||||
{
|
||||
char tmp[length * 4 + 1];
|
||||
byte first;
|
||||
byte second;
|
||||
byte third;
|
||||
byte fourth;
|
||||
for (int i = 0; i < length; i++) {
|
||||
first = (data[i] >> 12) & 0x0f;
|
||||
second = (data[i] >> 8) & 0x0f;
|
||||
third = (data[i] >> 4) & 0x0f;
|
||||
fourth = data[i] & 0x0f;
|
||||
//Serial << first << " " << second << " " << third << " " << fourth << endl;
|
||||
// base for converting single digit numbers to ASCII is 48
|
||||
// base for 10-16 to become upper-case characters A-F is 55
|
||||
// note: difference is 7
|
||||
tmp[i * 4] = first + 48;
|
||||
tmp[i * 4 + 1] = second + 48;
|
||||
tmp[i * 4 + 2] = third + 48;
|
||||
tmp[i * 4 + 3] = fourth + 48;
|
||||
//tmp[i*5+4] = 32; // add trailing space
|
||||
if (first > 9) tmp[i * 4] += 7;
|
||||
if (second > 9) tmp[i * 4 + 1] += 7;
|
||||
if (third > 9) tmp[i * 4 + 2] += 7;
|
||||
if (fourth > 9) tmp[i * 4 + 3] += 7;
|
||||
}
|
||||
tmp[length * 4] = 0;
|
||||
Serial.print(tmp);
|
||||
}
|
||||
|
||||
void printHex32(uint32_t *data, uint8_t length) // prints 8-bit data in hex
|
||||
{
|
||||
char tmp[length * 8 + 1];
|
||||
byte dataByte[8];
|
||||
for (int i = 0; i < length; i++) {
|
||||
dataByte[0] = (data[i] >> 28) & 0x0f;
|
||||
dataByte[1] = (data[i] >> 24) & 0x0f;
|
||||
dataByte[2] = (data[i] >> 20) & 0x0f;
|
||||
dataByte[3] = (data[i] >> 16) & 0x0f;
|
||||
dataByte[4] = (data[i] >> 12) & 0x0f;
|
||||
dataByte[5] = (data[i] >> 8) & 0x0f;
|
||||
dataByte[6] = (data[i] >> 4) & 0x0f;
|
||||
dataByte[7] = data[i] & 0x0f;
|
||||
//Serial << first << " " << second << " " << third << " " << fourth << endl;
|
||||
// base for converting single digit numbers to ASCII is 48
|
||||
// base for 10-16 to become upper-case characters A-F is 55
|
||||
// note: difference is 7
|
||||
tmp[i * 4] = dataByte[0] + 48;
|
||||
tmp[i * 4 + 1] = dataByte[1] + 48;
|
||||
tmp[i * 4 + 2] = dataByte[2] + 48;
|
||||
tmp[i * 4 + 3] = dataByte[3] + 48;
|
||||
tmp[i * 4 + 4] = dataByte[4] + 48;
|
||||
tmp[i * 4 + 5] = dataByte[5] + 48;
|
||||
tmp[i * 4 + 6] = dataByte[6] + 48;
|
||||
tmp[i * 4 + 7] = dataByte[7] + 48;
|
||||
//tmp[i*5+4] = 32; // add trailing space
|
||||
if (dataByte[0] > 9) tmp[i * 4] += 7;
|
||||
if (dataByte[1] > 9) tmp[i * 4 + 1] += 7;
|
||||
if (dataByte[2] > 9) tmp[i * 4 + 2] += 7;
|
||||
if (dataByte[3] > 9) tmp[i * 4 + 3] += 7;
|
||||
if (dataByte[4] > 9) tmp[i * 4 + 4] += 7;
|
||||
if (dataByte[5] > 9) tmp[i * 4 + 5] += 7;
|
||||
if (dataByte[6] > 9) tmp[i * 4 + 6] += 7;
|
||||
if (dataByte[7] > 9) tmp[i * 4 + 7] += 7;
|
||||
}
|
||||
tmp[length * 8] = 0;
|
||||
Serial.print(tmp);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user