acidportal- 😈 Worlds smallest Evil Portal on a LilyGo T-QT |
git clone git://git.acid.vegas/acidportal.git |
Log | Files | Refs | Archive | README | LICENSE |
ESP8266_uncannyEyes.ino (19608B)
1 // Adapted by Bodmer to work with a NodeMCU and ILI9341 or ST7735 display. 2 // 3 // This code currently does not "blink" the eye! 4 // 5 // Library used is here: 6 // https://github.com/Bodmer/TFT_eSPI 7 // 8 // To do, maybe, one day: 9 // 1. Get the eye to blink 10 // 2. Add another screen for another eye 11 // 3. Add variable to set how wide open the eye is 12 // 4. Add a reflected highlight to the cornea 13 // 5. Add top eyelid shadow to eye surface 14 // 6. Add aliasing to blur mask edge 15 // 16 // With one lidded eye drawn the code runs at 28-33fps (at 27-40MHz SPI clock) 17 // which is quite reasonable. Operation at an 80MHz SPI clock is possible but 18 // the display may not be able to cope with a clock rate that high and the 19 // performance improvement is small. Operate the ESP8266 at 160MHz for best 20 // frame rate. Note the images are stored in SPI FLASH (PROGMEM) so performance 21 // will be constrained by the increased memory access time. 22 23 // Original header for this sketch is below. Note: the technical aspects of the 24 // text no longer apply to this modified version of the sketch: 25 /* 26 //-------------------------------------------------------------------------- 27 // Uncanny eyes for PJRC Teensy 3.1 with Adafruit 1.5" OLED (product #1431) 28 // or 1.44" TFT LCD (#2088). This uses Teensy-3.1-specific features and 29 // WILL NOT work on normal Arduino or other boards! Use 72 MHz (Optimized) 30 // board speed -- OLED does not work at 96 MHz. 31 // 32 // Adafruit invests time and resources providing this open source code, 33 // please support Adafruit and open-source hardware by purchasing products 34 // from Adafruit! 35 // 36 // Written by Phil Burgess / Paint Your Dragon for Adafruit Industries. 37 // MIT license. SPI FIFO insight from Paul Stoffregen's ILI9341_t3 library. 38 // Inspired by David Boccabella's (Marcwolf) hybrid servo/OLED eye concept. 39 //-------------------------------------------------------------------------- 40 */ 41 42 #include <SPI.h> 43 #include <TFT_eSPI.h> // Hardware-specific library 44 45 // Enable ONE of these #includes for the various eyes: 46 #include "defaultEye.h" // Standard human-ish hazel eye 47 //#include "noScleraEye.h" // Large iris, no sclera 48 //#include "dragonEye.h" // Slit pupil fiery dragon/demon eye 49 //#include "goatEye.h" // Horizontal pupil goat/Krampus eye 50 51 52 #define DISPLAY_DC D3 // Data/command pin for BOTH displays 53 #define DISPLAY_RESET D4 // Reset pin for BOTH displays 54 #define SELECT_L_PIN D8 // LEFT eye chip select pin 55 #define SELECT_R_PIN D8 // RIGHT eye chip select pin 56 57 // INPUT CONFIG (for eye motion -- enable or comment out as needed) -------- 58 59 // The ESP8266 is rather constrained here as it only has one analogue port. 60 // An I2C ADC could be used for more analogue channels 61 //#define JOYSTICK_X_PIN A0 // Analogue pin for eye horiz pos (else auto) 62 //#define JOYSTICK_Y_PIN A0 // Analogue pin for eye vert position (") 63 //#define JOYSTICK_X_FLIP // If set, reverse stick X axis 64 //#define JOYSTICK_Y_FLIP // If set, reverse stick Y axis 65 #define TRACKING // If enabled, eyelid tracks pupil 66 //#define IRIS_PIN A0 // Photocell or potentiometer (else auto iris) 67 //#define IRIS_PIN_FLIP // If set, reverse reading from dial/photocell 68 //#define IRIS_SMOOTH // If enabled, filter input from IRIS_PIN 69 #define IRIS_MIN 140 // Clip lower analogRead() range from IRIS_PIN 70 #define IRIS_MAX 260 // Clip upper " 71 #define WINK_L_PIN 0 // Pin for LEFT eye wink button 72 #define BLINK_PIN 1 // Pin for blink button (BOTH eyes) 73 #define WINK_R_PIN 2 // Pin for RIGHT eye wink button 74 #define AUTOBLINK // If enabled, eyes blink autonomously 75 76 // Probably don't need to edit any config below this line, ----------------- 77 // unless building a single-eye project (pendant, etc.), in which case one 78 // of the two elements in the eye[] array further down can be commented out. 79 80 // Eye blinks are a tiny 3-state machine. Per-eye allows winks + blinks. 81 #define NOBLINK 0 // Not currently engaged in a blink 82 #define ENBLINK 1 // Eyelid is currently closing 83 #define DEBLINK 2 // Eyelid is currently opening 84 typedef struct { 85 int8_t pin; // Optional button here for indiv. wink 86 uint8_t state; // NOBLINK/ENBLINK/DEBLINK 87 int32_t duration; // Duration of blink state (micros) 88 uint32_t startTime; // Time (micros) of last state change 89 } eyeBlink; 90 91 struct { 92 TFT_eSPI tft; // OLED/eye[e].tft object 93 uint8_t cs; // Chip select pin 94 eyeBlink blink; // Current blink state 95 } eye[] = { // OK to comment out one of these for single-eye display: 96 TFT_eSPI(),SELECT_L_PIN,{WINK_L_PIN,NOBLINK}, 97 //TFT_eSPI(),SELECT_R_PIN,{WINK_R_PIN,NOBLINK}, 98 }; 99 100 #define NUM_EYES (sizeof(eye) / sizeof(eye[0])) 101 102 uint32_t fstart = 0; // start time to improve frame rate calculation at startup 103 104 // INITIALIZATION -- runs once at startup ---------------------------------- 105 106 void setup(void) { 107 uint8_t e = 0; 108 109 Serial.begin(250000); 110 randomSeed(analogRead(A0)); // Seed random() from floating analogue input 111 112 eye[e].tft.init(); 113 eye[e].tft.fillScreen(TFT_BLACK); 114 eye[e].tft.setRotation(0); 115 116 fstart = millis()-1; // Subtract 1 to avoid divide by zero later 117 } 118 119 120 // EYE-RENDERING FUNCTION -------------------------------------------------- 121 #define BUFFER_SIZE 256 // 64 to 512 seems optimum = 30 fps for default eye 122 void drawEye( // Renders one eye. Inputs must be pre-clipped & valid. 123 // Use native 32 bit variables where possible as this is 10% faster! 124 uint8_t e, // Eye array index; 0 or 1 for left/right 125 uint32_t iScale, // Scale factor for iris 126 uint32_t scleraX, // First pixel X offset into sclera image 127 uint32_t scleraY, // First pixel Y offset into sclera image 128 uint32_t uT, // Upper eyelid threshold value 129 uint32_t lT) { // Lower eyelid threshold value 130 131 uint32_t screenX, screenY, scleraXsave; 132 int32_t irisX, irisY; 133 uint32_t p, a; 134 uint32_t d; 135 136 uint32_t pixels = 0; 137 uint16_t pbuffer[BUFFER_SIZE]; // This one needs to be 16 bit 138 139 // Set up raw pixel dump to entire screen. Although such writes can wrap 140 // around automatically from end of rect back to beginning, the region is 141 // reset on each frame here in case of an SPI glitch. 142 143 //eye[e].tft.setAddrWindow(319-127, 0, 319, 127); 144 eye[e].tft.setAddrWindow(0, 0, 128, 128); 145 146 //digitalWrite(eye[e].cs, LOW); // Chip select 147 148 // Now just issue raw 16-bit values for every pixel... 149 150 scleraXsave = scleraX; // Save initial X value to reset on each line 151 irisY = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT) / 2; 152 for(screenY=0; screenY<SCREEN_HEIGHT; screenY++, scleraY++, irisY++) { 153 scleraX = scleraXsave; 154 irisX = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2; 155 for(screenX=0; screenX<SCREEN_WIDTH; screenX++, scleraX++, irisX++) { 156 if((pgm_read_byte(lower + screenY * SCREEN_WIDTH + screenX) <= lT) || 157 (pgm_read_byte(upper + screenY * SCREEN_WIDTH + screenX) <= uT)) { // Covered by eyelid 158 p = 0; 159 } else if((irisY < 0) || (irisY >= IRIS_HEIGHT) || 160 (irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera 161 p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX); 162 } else { // Maybe iris... 163 p = pgm_read_word(polar + irisY * IRIS_WIDTH + irisX); // Polar angle/dist 164 d = (iScale * (p & 0x7F)) / 128; // Distance (Y) 165 if(d < IRIS_MAP_HEIGHT) { // Within iris area 166 a = (IRIS_MAP_WIDTH * (p >> 7)) / 512; // Angle (X) 167 p = pgm_read_word(iris + d * IRIS_MAP_WIDTH + a); // Pixel = iris 168 } else { // Not in iris 169 p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX); // Pixel = sclera 170 } 171 } 172 *(pbuffer + pixels++) = p>>8 | p<<8; 173 174 if (pixels >= BUFFER_SIZE) { yield(); eye[e].tft.pushColors((uint8_t*)pbuffer, pixels*2); pixels = 0;} 175 } 176 } 177 178 if (pixels) { eye[e].tft.pushColors(pbuffer, pixels); pixels = 0;} 179 } 180 181 182 // EYE ANIMATION ----------------------------------------------------------- 183 184 const uint8_t ease[] = { // Ease in/out curve for eye movements 3*t^2-2*t^3 185 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, // T 186 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 8, 9, 9, 10, 10, // h 187 11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23, // x 188 24, 25, 26, 27, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, // 2 189 40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58, // A 190 60, 61, 62, 63, 65, 66, 67, 69, 70, 72, 73, 74, 76, 77, 78, 80, // l 191 81, 83, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 98,100,101,103, // e 192 104,106,107,109,110,112,113,115,116,118,119,121,122,124,125,127, // c 193 128,130,131,133,134,136,137,139,140,142,143,145,146,148,149,151, // J 194 152,154,155,157,158,159,161,162,164,165,167,168,170,171,172,174, // a 195 175,177,178,179,181,182,183,185,186,188,189,190,192,193,194,195, // c 196 197,198,199,201,202,203,204,205,207,208,209,210,211,213,214,215, // o 197 216,217,218,219,220,221,222,224,225,226,227,228,228,229,230,231, // b 198 232,233,234,235,236,237,237,238,239,240,240,241,242,243,243,244, // s 199 245,245,246,246,247,248,248,249,249,250,250,251,251,251,252,252, // o 200 252,253,253,253,254,254,254,254,254,255,255,255,255,255,255,255 }; // n 201 202 #ifdef AUTOBLINK 203 uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L; 204 #endif 205 206 void frame( // Process motion for a single frame of left or right eye 207 uint32_t iScale) { // Iris scale (0-1023) passed in 208 static uint32_t frames = 0; // Used in frame rate calculation 209 static uint8_t eyeIndex = 0; // eye[] array counter 210 int32_t eyeX, eyeY; 211 uint32_t t = micros(); // Time at start of function 212 213 Serial.print((++frames * 1000) / (millis() - fstart)); Serial.println("fps");// Show frame rate 214 215 if(++eyeIndex >= NUM_EYES) eyeIndex = 0; // Cycle through eyes, 1 per call 216 217 // Autonomous X/Y eye motion 218 // Periodically initiates motion to a new random point, random speed, 219 // holds there for random period until next motion. 220 221 static bool eyeInMotion = false; 222 static int32_t eyeOldX=512, eyeOldY=512, eyeNewX=512, eyeNewY=512; 223 static uint32_t eyeMoveStartTime = 0L; 224 static int32_t eyeMoveDuration = 0L; 225 226 int32_t dt = t - eyeMoveStartTime; // uS elapsed since last eye event 227 if(eyeInMotion) { // Currently moving? 228 if(dt >= eyeMoveDuration) { // Time up? Destination reached. 229 eyeInMotion = false; // Stop moving 230 eyeMoveDuration = random(3000000L); // 0-3 sec stop 231 eyeMoveStartTime = t; // Save initial time of stop 232 eyeX = eyeOldX = eyeNewX; // Save position 233 eyeY = eyeOldY = eyeNewY; 234 } else { // Move time's not yet fully elapsed -- interpolate position 235 int16_t e = ease[255 * dt / eyeMoveDuration] + 1; // Ease curve 236 eyeX = eyeOldX + (((eyeNewX - eyeOldX) * e) / 256); // Interp X 237 eyeY = eyeOldY + (((eyeNewY - eyeOldY) * e) / 256); // and Y 238 } 239 } else { // Eye stopped 240 eyeX = eyeOldX; 241 eyeY = eyeOldY; 242 if(dt > eyeMoveDuration) { // Time up? Begin new move. 243 int16_t dx, dy; 244 uint32_t d; 245 do { // Pick new dest in circle 246 eyeNewX = random(1024); 247 eyeNewY = random(1024); 248 dx = (eyeNewX * 2) - 1023; 249 dy = (eyeNewY * 2) - 1023; 250 } while((d = (dx * dx + dy * dy)) > (1023 * 1023)); // Keep trying 251 eyeMoveDuration = random(50000, 150000);//random(72000, 144000); // ~1/14 - ~1/7 sec 252 eyeMoveStartTime = t; // Save initial time of move 253 eyeInMotion = true; // Start move on next frame 254 } 255 } 256 257 // Blinking 258 /* 259 #ifdef AUTOBLINK 260 // Similar to the autonomous eye movement above -- blink start times 261 // and durations are random (within ranges). 262 if((t - timeOfLastBlink) >= timeToNextBlink) { // Start new blink? 263 timeOfLastBlink = t; 264 uint32_t blinkDuration = random(36000, 72000); // ~1/28 - ~1/14 sec 265 // Set up durations for both eyes (if not already winking) 266 for(uint8_t e=0; e<NUM_EYES; e++) { 267 if(eye[e].blink.state == NOBLINK) { 268 eye[e].blink.state = ENBLINK; 269 eye[e].blink.startTime = t; 270 eye[e].blink.duration = blinkDuration; 271 } 272 } 273 timeToNextBlink = blinkDuration * 3 + random(4000000); 274 } 275 #endif 276 */ 277 /* 278 if(eye[eyeIndex].blink.state) { // Eye currently blinking? 279 // Check if current blink state time has elapsed 280 if((t - eye[eyeIndex].blink.startTime) >= eye[eyeIndex].blink.duration) { 281 // Yes -- increment blink state, unless... 282 if((eye[eyeIndex].blink.state == ENBLINK) && // Enblinking and... 283 ((digitalRead(BLINK_PIN) == LOW) || // blink or wink held... 284 digitalRead(eye[eyeIndex].blink.pin) == LOW)) { 285 // Don't advance state yet -- eye is held closed instead 286 } else { // No buttons, or other state... 287 if(++eye[eyeIndex].blink.state > DEBLINK) { // Deblinking finished? 288 eye[eyeIndex].blink.state = NOBLINK; // No longer blinking 289 } else { // Advancing from ENBLINK to DEBLINK mode 290 eye[eyeIndex].blink.duration *= 2; // DEBLINK is 1/2 ENBLINK speed 291 eye[eyeIndex].blink.startTime = t; 292 } 293 } 294 } 295 } else { // Not currently blinking...check buttons! 296 if(digitalRead(BLINK_PIN) == LOW) { 297 // Manually-initiated blinks have random durations like auto-blink 298 uint32_t blinkDuration = random(36000, 72000); 299 for(uint8_t e=0; e<NUM_EYES; e++) { 300 if(eye[e].blink.state == NOBLINK) { 301 eye[e].blink.state = ENBLINK; 302 eye[e].blink.startTime = t; 303 eye[e].blink.duration = blinkDuration; 304 } 305 } 306 } else if(digitalRead(eye[eyeIndex].blink.pin) == LOW) { // Wink! 307 eye[eyeIndex].blink.state = ENBLINK; 308 eye[eyeIndex].blink.startTime = t; 309 eye[eyeIndex].blink.duration = random(45000, 90000); 310 } 311 } 312 */ 313 // Process motion, blinking and iris scale into renderable values 314 315 // Iris scaling: remap from 0-1023 input to iris map height pixel units 316 iScale = ((IRIS_MAP_HEIGHT + 1) * 1024) / 317 (1024 - (iScale * (IRIS_MAP_HEIGHT - 1) / IRIS_MAP_HEIGHT)); 318 319 // Scale eye X/Y positions (0-1023) to pixel units used by drawEye() 320 eyeX = map(eyeX, 0, 1023, 0, SCLERA_WIDTH - 128); 321 eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128); 322 if(eyeIndex == 1) eyeX = (SCLERA_WIDTH - 128) - eyeX; // Mirrored display 323 324 // Horizontal position is offset so that eyes are very slightly crossed 325 // to appear fixated (converged) at a conversational distance. Number 326 // here was extracted from my posterior and not mathematically based. 327 // I suppose one could get all clever with a range sensor, but for now... 328 eyeX += 4; 329 if(eyeX > (SCLERA_WIDTH - 128)) eyeX = (SCLERA_WIDTH - 128); 330 331 // Eyelids are rendered using a brightness threshold image. This same 332 // map can be used to simplify another problem: making the upper eyelid 333 // track the pupil (eyes tend to open only as much as needed -- e.g. look 334 // down and the upper eyelid drops). Just sample a point in the upper 335 // lid map slightly above the pupil to determine the rendering threshold. 336 static uint8_t uThreshold = 128; 337 uint8_t lThreshold, n; 338 339 #ifdef TRACKING 340 int16_t sampleX = SCLERA_WIDTH / 2 - (eyeX / 2), // Reduce X influence 341 sampleY = SCLERA_HEIGHT / 2 - (eyeY + IRIS_HEIGHT / 4); 342 // Eyelid is slightly asymmetrical, so two readings are taken, averaged 343 if(sampleY < 0) n = 0; 344 else n = (pgm_read_byte(upper + sampleY * SCREEN_WIDTH + sampleX) + 345 pgm_read_byte(upper + sampleY * SCREEN_WIDTH + (SCREEN_WIDTH - 1 - sampleX))) / 2; 346 uThreshold = (uThreshold * 3 + n) / 4; // Filter/soften motion 347 // Lower eyelid doesn't track the same way, but seems to be pulled upward 348 // by tension from the upper lid. 349 lThreshold = 254 - uThreshold; 350 #else // No tracking -- eyelids full open unless blink modifies them 351 uThreshold = lThreshold = 0; 352 #endif 353 354 // The upper/lower thresholds are then scaled relative to the current 355 // blink position so that blinks work together with pupil tracking. 356 if(eye[eyeIndex].blink.state) { // Eye currently blinking? 357 uint32_t s = (t - eye[eyeIndex].blink.startTime); 358 if(s >= eye[eyeIndex].blink.duration) s = 255; // At or past blink end 359 else s = 255 * s / eye[eyeIndex].blink.duration; // Mid-blink 360 s = (eye[eyeIndex].blink.state == DEBLINK) ? 1 + s : 256 - s; 361 n = (uThreshold * s + 254 * (257 - s)) / 256; 362 lThreshold = (lThreshold * s + 254 * (257 - s)) / 256; 363 } else { 364 n = uThreshold; 365 } 366 367 // Pass all the derived values to the eye-rendering function: 368 drawEye(eyeIndex, iScale, eyeX, eyeY, n, lThreshold); 369 370 } 371 372 373 // AUTONOMOUS IRIS SCALING (if no photocell or dial) ----------------------- 374 375 #if !defined(IRIS_PIN) || (IRIS_PIN < 0) 376 377 // Autonomous iris motion uses a fractal behavior to similate both the major 378 // reaction of the eye plus the continuous smaller adjustments that occur. 379 380 uint16_t oldIris = (IRIS_MIN + IRIS_MAX) / 2, newIris; 381 382 void split( // Subdivides motion path into two sub-paths w/randimization 383 int16_t startValue, // Iris scale value (IRIS_MIN to IRIS_MAX) at start 384 int16_t endValue, // Iris scale value at end 385 uint32_t startTime, // micros() at start 386 int32_t duration, // Start-to-end time, in microseconds 387 int16_t range) { // Allowable scale value variance when subdividing 388 389 if(range >= 8) { // Limit subdvision count, because recursion 390 range /= 2; // Split range & time in half for subdivision, 391 duration /= 2; // then pick random center point within range: 392 int16_t midValue = (startValue + endValue - range) / 2 + random(range); 393 uint32_t midTime = startTime + duration; 394 split(startValue, midValue, startTime, duration, range); // First half 395 split(midValue , endValue, midTime , duration, range); // Second half 396 } else { // No more subdivisons, do iris motion... 397 int32_t dt; // Time (micros) since start of motion 398 int16_t v; // Interim value 399 while((dt = (micros() - startTime)) < duration) { 400 v = startValue + (((endValue - startValue) * dt) / duration); 401 if(v < IRIS_MIN) v = IRIS_MIN; // Clip just in case 402 else if(v > IRIS_MAX) v = IRIS_MAX; 403 frame(v); // Draw frame w/interim iris scale value 404 } 405 } 406 } 407 408 #endif // !IRIS_PIN 409 410 411 // MAIN LOOP -- runs continuously after setup() ---------------------------- 412 413 void loop() { 414 415 #if defined(IRIS_PIN) && (IRIS_PIN >= 0) // Interactive iris 416 417 uint16_t v = 512; //analogRead(IRIS_PIN); // Raw dial/photocell reading 418 #ifdef IRIS_PIN_FLIP 419 v = 1023 - v; 420 #endif 421 v = map(v, 0, 1023, IRIS_MIN, IRIS_MAX); // Scale to iris range 422 #ifdef IRIS_SMOOTH // Filter input (gradual motion) 423 static uint16_t irisValue = (IRIS_MIN + IRIS_MAX) / 2; 424 irisValue = ((irisValue * 15) + v) / 16; 425 frame(irisValue); 426 #else // Unfiltered (immediate motion) 427 frame(v); 428 #endif // IRIS_SMOOTH 429 430 #else // Autonomous iris scaling -- invoke recursive function 431 432 newIris = random(IRIS_MIN, IRIS_MAX); 433 split(oldIris, newIris, micros(), 10000000L, IRIS_MAX - IRIS_MIN); 434 oldIris = newIris; 435 436 #endif // IRIS_PIN 437 438 //screenshotToConsole(); 439 } 440 441 442