acid-drop- Hacking the planet from a LilyGo T-Deck using custom firmware |
git clone git://git.acid.vegas/acid-drop.git |
Log | Files | Refs | Archive | README | LICENSE |
eye_functions.ino (18557B)
1 // 2 // Code adapted by Bodmer as an example for TFT_eSPI, this runs on any 3 // TFT_eSPI compatible processor so ignore the technical limitations 4 // detailed in the original header below. Assorted changes have been 5 // made including removal of the display mirror kludge. 6 7 //-------------------------------------------------------------------------- 8 // Uncanny eyes for Adafruit 1.5" OLED (product #1431) or 1.44" TFT LCD 9 // (#2088). Works on PJRC Teensy 3.x and on Adafruit M0 and M4 boards 10 // (Feather, Metro, etc.). This code uses features specific to these 11 // boards and WILL NOT work on normal Arduino or other boards! 12 // 13 // SEE FILE "config.h" FOR MOST CONFIGURATION (graphics, pins, display type, 14 // etc). Probably won't need to edit THIS file unless you're doing some 15 // extremely custom modifications. 16 // 17 // Adafruit invests time and resources providing this open source code, 18 // please support Adafruit and open-source hardware by purchasing products 19 // from Adafruit! 20 // 21 // Written by Phil Burgess / Paint Your Dragon for Adafruit Industries. 22 // MIT license. SPI FIFO insight from Paul Stoffregen's ILI9341_t3 library. 23 // Inspired by David Boccabella's (Marcwolf) hybrid servo/OLED eye concept. 24 //-------------------------------------------------------------------------- 25 26 #if !defined(LIGHT_PIN) || (LIGHT_PIN < 0) 27 // Autonomous iris motion uses a fractal behavior to similate both the major 28 // reaction of the eye plus the continuous smaller adjustments that occur. 29 uint16_t oldIris = (IRIS_MIN + IRIS_MAX) / 2, newIris; 30 #endif 31 32 // Initialise eyes --------------------------------------------------------- 33 void initEyes(void) 34 { 35 Serial.println("Initialise eye objects"); 36 37 // Initialise eye objects based on eyeInfo list in config.h: 38 for (uint8_t e = 0; e < NUM_EYES; e++) { 39 Serial.print("Create display #"); Serial.println(e); 40 41 eye[e].tft_cs = eyeInfo[e].select; 42 eye[e].blink.state = NOBLINK; 43 eye[e].xposition = eyeInfo[e].xposition; 44 45 pinMode(eye[e].tft_cs, OUTPUT); 46 digitalWrite(eye[e].tft_cs, LOW); 47 48 // Also set up an individual eye-wink pin if defined: 49 if (eyeInfo[e].wink >= 0) pinMode(eyeInfo[e].wink, INPUT_PULLUP); 50 } 51 52 #if defined(BLINK_PIN) && (BLINK_PIN >= 0) 53 pinMode(BLINK_PIN, INPUT_PULLUP); // Ditto for all-eyes blink pin 54 #endif 55 } 56 57 // UPDATE EYE -------------------------------------------------------------- 58 void updateEye (void) 59 { 60 #if defined(LIGHT_PIN) && (LIGHT_PIN >= 0) // Interactive iris 61 62 int16_t v = analogRead(LIGHT_PIN); // Raw dial/photocell reading 63 #ifdef LIGHT_PIN_FLIP 64 v = 1023 - v; // Reverse reading from sensor 65 #endif 66 if (v < LIGHT_MIN) v = LIGHT_MIN; // Clamp light sensor range 67 else if (v > LIGHT_MAX) v = LIGHT_MAX; 68 v -= LIGHT_MIN; // 0 to (LIGHT_MAX - LIGHT_MIN) 69 #ifdef LIGHT_CURVE // Apply gamma curve to sensor input? 70 v = (int16_t)(pow((double)v / (double)(LIGHT_MAX - LIGHT_MIN), 71 LIGHT_CURVE) * (double)(LIGHT_MAX - LIGHT_MIN)); 72 #endif 73 // And scale to iris range (IRIS_MAX is size at LIGHT_MIN) 74 v = map(v, 0, (LIGHT_MAX - LIGHT_MIN), IRIS_MAX, IRIS_MIN); 75 #ifdef IRIS_SMOOTH // Filter input (gradual motion) 76 static int16_t irisValue = (IRIS_MIN + IRIS_MAX) / 2; 77 irisValue = ((irisValue * 15) + v) / 16; 78 frame(irisValue); 79 #else // Unfiltered (immediate motion) 80 frame(v); 81 #endif // IRIS_SMOOTH 82 83 #else // Autonomous iris scaling -- invoke recursive function 84 85 newIris = random(IRIS_MIN, IRIS_MAX); 86 split(oldIris, newIris, micros(), 10000000L, IRIS_MAX - IRIS_MIN); 87 oldIris = newIris; 88 89 #endif // LIGHT_PIN 90 } 91 92 // EYE-RENDERING FUNCTION -------------------------------------------------- 93 void drawEye( // Renders one eye. Inputs must be pre-clipped & valid. 94 // Use native 32 bit variables where possible as this is 10% faster! 95 uint8_t e, // Eye array index; 0 or 1 for left/right 96 uint32_t iScale, // Scale factor for iris 97 uint32_t scleraX, // First pixel X offset into sclera image 98 uint32_t scleraY, // First pixel Y offset into sclera image 99 uint32_t uT, // Upper eyelid threshold value 100 uint32_t lT) { // Lower eyelid threshold value 101 102 uint32_t screenX, screenY, scleraXsave; 103 int32_t irisX, irisY; 104 uint32_t p, a; 105 uint32_t d; 106 107 uint32_t pixels = 0; 108 109 // Set up raw pixel dump to entire screen. Although such writes can wrap 110 // around automatically from end of rect back to beginning, the region is 111 // reset on each frame here in case of an SPI glitch. 112 digitalWrite(eye[e].tft_cs, LOW); 113 tft.startWrite(); 114 tft.setAddrWindow(eye[e].xposition, 0, 128, 128); 115 116 // Now just issue raw 16-bit values for every pixel... 117 118 scleraXsave = scleraX; // Save initial X value to reset on each line 119 irisY = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT) / 2; 120 121 // Eyelid image is left<>right swapped for two displays 122 uint16_t lidX = 0; 123 uint16_t dlidX = -1; 124 if (e) dlidX = 1; 125 for (screenY = 0; screenY < SCREEN_HEIGHT; screenY++, scleraY++, irisY++) { 126 scleraX = scleraXsave; 127 irisX = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2; 128 if (e) lidX = 0; else lidX = SCREEN_WIDTH - 1; 129 for (screenX = 0; screenX < SCREEN_WIDTH; screenX++, scleraX++, irisX++, lidX += dlidX) { 130 if ((pgm_read_byte(lower + screenY * SCREEN_WIDTH + lidX) <= lT) || 131 (pgm_read_byte(upper + screenY * SCREEN_WIDTH + lidX) <= uT)) { // Covered by eyelid 132 p = 0; 133 } else if ((irisY < 0) || (irisY >= IRIS_HEIGHT) || 134 (irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera 135 p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX); 136 } else { // Maybe iris... 137 p = pgm_read_word(polar + irisY * IRIS_WIDTH + irisX); // Polar angle/dist 138 d = (iScale * (p & 0x7F)) / 128; // Distance (Y) 139 if (d < IRIS_MAP_HEIGHT) { // Within iris area 140 a = (IRIS_MAP_WIDTH * (p >> 7)) / 512; // Angle (X) 141 p = pgm_read_word(iris + d * IRIS_MAP_WIDTH + a); // Pixel = iris 142 } else { // Not in iris 143 p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX); // Pixel = sclera 144 } 145 } 146 *(&pbuffer[dmaBuf][0] + pixels++) = p >> 8 | p << 8; 147 148 if (pixels >= BUFFER_SIZE) { 149 yield(); 150 #ifdef USE_DMA 151 tft.pushPixelsDMA(&pbuffer[dmaBuf][0], pixels); 152 dmaBuf = !dmaBuf; 153 #else 154 tft.pushPixels(pbuffer, pixels); 155 #endif 156 pixels = 0; 157 } 158 } 159 } 160 161 if (pixels) { 162 #ifdef USE_DMA 163 tft.pushPixelsDMA(&pbuffer[dmaBuf][0], pixels); 164 #else 165 tft.pushPixels(pbuffer, pixels); 166 #endif 167 } 168 tft.endWrite(); 169 digitalWrite(eye[e].tft_cs, HIGH); 170 } 171 172 // EYE ANIMATION ----------------------------------------------------------- 173 174 const uint8_t ease[] = { // Ease in/out curve for eye movements 3*t^2-2*t^3 175 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, // T 176 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 8, 9, 9, 10, 10, // h 177 11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23, // x 178 24, 25, 26, 27, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, // 2 179 40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58, // A 180 60, 61, 62, 63, 65, 66, 67, 69, 70, 72, 73, 74, 76, 77, 78, 80, // l 181 81, 83, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 98, 100, 101, 103, // e 182 104, 106, 107, 109, 110, 112, 113, 115, 116, 118, 119, 121, 122, 124, 125, 127, // c 183 128, 130, 131, 133, 134, 136, 137, 139, 140, 142, 143, 145, 146, 148, 149, 151, // J 184 152, 154, 155, 157, 158, 159, 161, 162, 164, 165, 167, 168, 170, 171, 172, 174, // a 185 175, 177, 178, 179, 181, 182, 183, 185, 186, 188, 189, 190, 192, 193, 194, 195, // c 186 197, 198, 199, 201, 202, 203, 204, 205, 207, 208, 209, 210, 211, 213, 214, 215, // o 187 216, 217, 218, 219, 220, 221, 222, 224, 225, 226, 227, 228, 228, 229, 230, 231, // b 188 232, 233, 234, 235, 236, 237, 237, 238, 239, 240, 240, 241, 242, 243, 243, 244, // s 189 245, 245, 246, 246, 247, 248, 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, // o 190 252, 253, 253, 253, 254, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255 191 }; // n 192 193 #ifdef AUTOBLINK 194 uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L; 195 #endif 196 197 // Process motion for a single frame of left or right eye 198 void frame(uint16_t iScale) // Iris scale (0-1023) 199 { 200 static uint32_t frames = 0; // Used in frame rate calculation 201 static uint8_t eyeIndex = 0; // eye[] array counter 202 int16_t eyeX, eyeY; 203 uint32_t t = micros(); // Time at start of function 204 205 if (!(++frames & 255)) { // Every 256 frames... 206 float elapsed = (millis() - startTime) / 1000.0; 207 if (elapsed) Serial.println((uint16_t)(frames / elapsed)); // Print FPS 208 } 209 210 if (++eyeIndex >= NUM_EYES) eyeIndex = 0; // Cycle through eyes, 1 per call 211 212 // X/Y movement 213 214 #if defined(JOYSTICK_X_PIN) && (JOYSTICK_X_PIN >= 0) && \ 215 defined(JOYSTICK_Y_PIN) && (JOYSTICK_Y_PIN >= 0) 216 217 // Read X/Y from joystick, constrain to circle 218 int16_t dx, dy; 219 int32_t d; 220 eyeX = analogRead(JOYSTICK_X_PIN); // Raw (unclipped) X/Y reading 221 eyeY = analogRead(JOYSTICK_Y_PIN); 222 #ifdef JOYSTICK_X_FLIP 223 eyeX = 1023 - eyeX; 224 #endif 225 #ifdef JOYSTICK_Y_FLIP 226 eyeY = 1023 - eyeY; 227 #endif 228 dx = (eyeX * 2) - 1023; // A/D exact center is at 511.5. Scale coords 229 dy = (eyeY * 2) - 1023; // X2 so range is -1023 to +1023 w/center at 0. 230 if ((d = (dx * dx + dy * dy)) > (1023 * 1023)) { // Outside circle 231 d = (int32_t)sqrt((float)d); // Distance from center 232 eyeX = ((dx * 1023 / d) + 1023) / 2; // Clip to circle edge, 233 eyeY = ((dy * 1023 / d) + 1023) / 2; // scale back to 0-1023 234 } 235 236 #else // Autonomous X/Y eye motion 237 // Periodically initiates motion to a new random point, random speed, 238 // holds there for random period until next motion. 239 240 static bool eyeInMotion = false; 241 static int16_t eyeOldX = 512, eyeOldY = 512, eyeNewX = 512, eyeNewY = 512; 242 static uint32_t eyeMoveStartTime = 0L; 243 static int32_t eyeMoveDuration = 0L; 244 245 int32_t dt = t - eyeMoveStartTime; // uS elapsed since last eye event 246 if (eyeInMotion) { // Currently moving? 247 if (dt >= eyeMoveDuration) { // Time up? Destination reached. 248 eyeInMotion = false; // Stop moving 249 eyeMoveDuration = random(3000000); // 0-3 sec stop 250 eyeMoveStartTime = t; // Save initial time of stop 251 eyeX = eyeOldX = eyeNewX; // Save position 252 eyeY = eyeOldY = eyeNewY; 253 } else { // Move time's not yet fully elapsed -- interpolate position 254 int16_t e = ease[255 * dt / eyeMoveDuration] + 1; // Ease curve 255 eyeX = eyeOldX + (((eyeNewX - eyeOldX) * e) / 256); // Interp X 256 eyeY = eyeOldY + (((eyeNewY - eyeOldY) * e) / 256); // and Y 257 } 258 } else { // Eye stopped 259 eyeX = eyeOldX; 260 eyeY = eyeOldY; 261 if (dt > eyeMoveDuration) { // Time up? Begin new move. 262 int16_t dx, dy; 263 uint32_t d; 264 do { // Pick new dest in circle 265 eyeNewX = random(1024); 266 eyeNewY = random(1024); 267 dx = (eyeNewX * 2) - 1023; 268 dy = (eyeNewY * 2) - 1023; 269 } while ((d = (dx * dx + dy * dy)) > (1023 * 1023)); // Keep trying 270 eyeMoveDuration = random(72000, 144000); // ~1/14 - ~1/7 sec 271 eyeMoveStartTime = t; // Save initial time of move 272 eyeInMotion = true; // Start move on next frame 273 } 274 } 275 #endif // JOYSTICK_X_PIN etc. 276 277 // Blinking 278 #ifdef AUTOBLINK 279 // Similar to the autonomous eye movement above -- blink start times 280 // and durations are random (within ranges). 281 if ((t - timeOfLastBlink) >= timeToNextBlink) { // Start new blink? 282 timeOfLastBlink = t; 283 uint32_t blinkDuration = random(36000, 72000); // ~1/28 - ~1/14 sec 284 // Set up durations for both eyes (if not already winking) 285 for (uint8_t e = 0; e < NUM_EYES; e++) { 286 if (eye[e].blink.state == NOBLINK) { 287 eye[e].blink.state = ENBLINK; 288 eye[e].blink.startTime = t; 289 eye[e].blink.duration = blinkDuration; 290 } 291 } 292 timeToNextBlink = blinkDuration * 3 + random(4000000); 293 } 294 #endif 295 296 if (eye[eyeIndex].blink.state) { // Eye currently blinking? 297 // Check if current blink state time has elapsed 298 if ((t - eye[eyeIndex].blink.startTime) >= eye[eyeIndex].blink.duration) { 299 // Yes -- increment blink state, unless... 300 if ((eye[eyeIndex].blink.state == ENBLINK) && ( // Enblinking and... 301 #if defined(BLINK_PIN) && (BLINK_PIN >= 0) 302 (digitalRead(BLINK_PIN) == LOW) || // blink or wink held... 303 #endif 304 ((eyeInfo[eyeIndex].wink >= 0) && 305 digitalRead(eyeInfo[eyeIndex].wink) == LOW) )) { 306 // Don't advance state yet -- eye is held closed instead 307 } else { // No buttons, or other state... 308 if (++eye[eyeIndex].blink.state > DEBLINK) { // Deblinking finished? 309 eye[eyeIndex].blink.state = NOBLINK; // No longer blinking 310 } else { // Advancing from ENBLINK to DEBLINK mode 311 eye[eyeIndex].blink.duration *= 2; // DEBLINK is 1/2 ENBLINK speed 312 eye[eyeIndex].blink.startTime = t; 313 } 314 } 315 } 316 } else { // Not currently blinking...check buttons! 317 #if defined(BLINK_PIN) && (BLINK_PIN >= 0) 318 if (digitalRead(BLINK_PIN) == LOW) { 319 // Manually-initiated blinks have random durations like auto-blink 320 uint32_t blinkDuration = random(36000, 72000); 321 for (uint8_t e = 0; e < NUM_EYES; e++) { 322 if (eye[e].blink.state == NOBLINK) { 323 eye[e].blink.state = ENBLINK; 324 eye[e].blink.startTime = t; 325 eye[e].blink.duration = blinkDuration; 326 } 327 } 328 } else 329 #endif 330 if ((eyeInfo[eyeIndex].wink >= 0) && 331 (digitalRead(eyeInfo[eyeIndex].wink) == LOW)) { // Wink! 332 eye[eyeIndex].blink.state = ENBLINK; 333 eye[eyeIndex].blink.startTime = t; 334 eye[eyeIndex].blink.duration = random(45000, 90000); 335 } 336 } 337 338 // Process motion, blinking and iris scale into renderable values 339 340 // Scale eye X/Y positions (0-1023) to pixel units used by drawEye() 341 eyeX = map(eyeX, 0, 1023, 0, SCLERA_WIDTH - 128); 342 eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128); 343 344 // Horizontal position is offset so that eyes are very slightly crossed 345 // to appear fixated (converged) at a conversational distance. Number 346 // here was extracted from my posterior and not mathematically based. 347 // I suppose one could get all clever with a range sensor, but for now... 348 if (NUM_EYES > 1) { 349 if (eyeIndex == 1) eyeX += 4; 350 else eyeX -= 4; 351 } 352 if (eyeX > (SCLERA_WIDTH - 128)) eyeX = (SCLERA_WIDTH - 128); 353 354 // Eyelids are rendered using a brightness threshold image. This same 355 // map can be used to simplify another problem: making the upper eyelid 356 // track the pupil (eyes tend to open only as much as needed -- e.g. look 357 // down and the upper eyelid drops). Just sample a point in the upper 358 // lid map slightly above the pupil to determine the rendering threshold. 359 static uint8_t uThreshold = 128; 360 uint8_t lThreshold, n; 361 #ifdef TRACKING 362 int16_t sampleX = SCLERA_WIDTH / 2 - (eyeX / 2), // Reduce X influence 363 sampleY = SCLERA_HEIGHT / 2 - (eyeY + IRIS_HEIGHT / 4); 364 // Eyelid is slightly asymmetrical, so two readings are taken, averaged 365 if (sampleY < 0) n = 0; 366 else n = (pgm_read_byte(upper + sampleY * SCREEN_WIDTH + sampleX) + 367 pgm_read_byte(upper + sampleY * SCREEN_WIDTH + (SCREEN_WIDTH - 1 - sampleX))) / 2; 368 uThreshold = (uThreshold * 3 + n) / 4; // Filter/soften motion 369 // Lower eyelid doesn't track the same way, but seems to be pulled upward 370 // by tension from the upper lid. 371 lThreshold = 254 - uThreshold; 372 #else // No tracking -- eyelids full open unless blink modifies them 373 uThreshold = lThreshold = 0; 374 #endif 375 376 // The upper/lower thresholds are then scaled relative to the current 377 // blink position so that blinks work together with pupil tracking. 378 if (eye[eyeIndex].blink.state) { // Eye currently blinking? 379 uint32_t s = (t - eye[eyeIndex].blink.startTime); 380 if (s >= eye[eyeIndex].blink.duration) s = 255; // At or past blink end 381 else s = 255 * s / eye[eyeIndex].blink.duration; // Mid-blink 382 s = (eye[eyeIndex].blink.state == DEBLINK) ? 1 + s : 256 - s; 383 n = (uThreshold * s + 254 * (257 - s)) / 256; 384 lThreshold = (lThreshold * s + 254 * (257 - s)) / 256; 385 } else { 386 n = uThreshold; 387 } 388 389 // Pass all the derived values to the eye-rendering function: 390 drawEye(eyeIndex, iScale, eyeX, eyeY, n, lThreshold); 391 392 if (eyeIndex == (NUM_EYES - 1)) { 393 user_loop(); // Call user code after rendering last eye 394 } 395 } 396 397 // AUTONOMOUS IRIS SCALING (if no photocell or dial) ----------------------- 398 399 #if !defined(LIGHT_PIN) || (LIGHT_PIN < 0) 400 401 // Autonomous iris motion uses a fractal behavior to similate both the major 402 // reaction of the eye plus the continuous smaller adjustments that occur. 403 404 void split( // Subdivides motion path into two sub-paths w/randimization 405 int16_t startValue, // Iris scale value (IRIS_MIN to IRIS_MAX) at start 406 int16_t endValue, // Iris scale value at end 407 uint32_t startTime, // micros() at start 408 int32_t duration, // Start-to-end time, in microseconds 409 int16_t range) { // Allowable scale value variance when subdividing 410 411 if (range >= 8) { // Limit subdvision count, because recursion 412 range /= 2; // Split range & time in half for subdivision, 413 duration /= 2; // then pick random center point within range: 414 int16_t midValue = (startValue + endValue - range) / 2 + random(range); 415 uint32_t midTime = startTime + duration; 416 split(startValue, midValue, startTime, duration, range); // First half 417 split(midValue , endValue, midTime , duration, range); // Second half 418 } else { // No more subdivisons, do iris motion... 419 int32_t dt; // Time (micros) since start of motion 420 int16_t v; // Interim value 421 while ((dt = (micros() - startTime)) < duration) { 422 v = startValue + (((endValue - startValue) * dt) / duration); 423 if (v < IRIS_MIN) v = IRIS_MIN; // Clip just in case 424 else if (v > IRIS_MAX) v = IRIS_MAX; 425 frame(v); // Draw frame w/interim iris scale value 426 } 427 } 428 } 429 #endif // !LIGHT_PIN