PlatformIO package of the Teensy core framework compatible with GCC 10 & C++20
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

uncannyEyes7735.ino 27KB

пре 3 година
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. #define SERIAL_tt Serial // Send debug_tt output here. Must have SERIAL_tt.begin( ## )
  2. //#include "debug_tt.h"
  3. //#define BUTTON_ISR 7
  4. //--------------------------------------------------------------------------
  5. // Uncanny eyes for Adafruit 1.5" OLED (product #1431) or 1.44" TFT LCD
  6. // (#2088). Works on PJRC Teensy 3.x and on Adafruit M0 and M4 boards
  7. // (Feather, Metro, etc.). This code uses features specific to these
  8. // boards and WILL NOT work on normal Arduino or other boards!
  9. //
  10. // SEE FILE "config.h" FOR MOST CONFIGURATION (graphics, pins, display type,
  11. // etc). Probably won't need to edit THIS file unless you're doing some
  12. // extremely custom modifications.
  13. //
  14. // Adafruit invests time and resources providing this open source code,
  15. // please support Adafruit and open-source hardware by purchasing products
  16. // from Adafruit!
  17. //
  18. // Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
  19. // MIT license. SPI FIFO insight from Paul Stoffregen's ILI9341_t3 library.
  20. // Inspired by David Boccabella's (Marcwolf) hybrid servo/OLED eye concept.
  21. //--------------------------------------------------------------------------
  22. #include <SPI.h>
  23. #include <Adafruit_GFX.h>
  24. #include <ST7735_t3.h>
  25. //#define ADAFRUIT_HALLOWING
  26. typedef struct { // Struct is defined before including config.h --
  27. int8_t select; // pin numbers for each eye's screen select line
  28. int8_t wink; // and wink button (or -1 if none) specified there,
  29. uint8_t rotation; // also display rotation.
  30. uint8_t init_option; // option for Init
  31. } eyeInfo_t;
  32. #define _ADAFRUIT_ST7735H_
  33. #include "config.h" // ****** CONFIGURATION IS DONE IN HERE ******
  34. #if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_)
  35. typedef ST7735_t3 displayType; // Using TFT display(s)
  36. #else
  37. typedef Adafruit_SSD1351 displayType; // Using OLED display(s)
  38. #endif
  39. // A simple state machine is used to control eye blinks/winks:
  40. #define NOBLINK 0 // Not currently engaged in a blink
  41. #define ENBLINK 1 // Eyelid is currently closing
  42. #define DEBLINK 2 // Eyelid is currently opening
  43. typedef struct {
  44. uint8_t state; // NOBLINK/ENBLINK/DEBLINK
  45. uint32_t duration; // Duration of blink state (micros)
  46. uint32_t startTime; // Time (micros) of last state change
  47. } eyeBlink;
  48. #define NUM_EYES (sizeof eyeInfo / sizeof eyeInfo[0]) // config.h pin list
  49. struct { // One-per-eye structure
  50. displayType *display; // -> OLED/TFT object
  51. eyeBlink blink; // Current blink/wink state
  52. } eye[2];
  53. uint32_t startTime; // For FPS indicator
  54. #if defined(SYNCPIN) && (SYNCPIN >= 0)
  55. #include <Wire.h>
  56. // If two boards are synchronized over I2C, this struct is passed from one
  57. // to other. No device-independent packing & unpacking is performed...both
  58. // boards are expected to be the same architecture & endianism.
  59. struct {
  60. uint16_t iScale; // These are basically the same arguments as
  61. uint8_t scleraX; // drawEye() expects, explained in that function.
  62. uint8_t scleraY;
  63. uint8_t uT;
  64. uint8_t lT;
  65. } syncStruct = { 512,
  66. (SCLERA_WIDTH - SCREEN_WIDTH) / 2, (SCLERA_HEIGHT - SCREEN_HEIGHT) / 2, 0, 0
  67. };
  68. void wireCallback(int n) {
  69. if (n == sizeof syncStruct) {
  70. // Read 'n' bytes from I2C into syncStruct
  71. uint8_t *ptr = (uint8_t *)&syncStruct;
  72. for (uint8_t i = 0; i < sizeof syncStruct; i++) {
  73. ptr[i] = Wire.read();
  74. }
  75. }
  76. }
  77. bool receiver = false;
  78. #endif // SYNCPIN
  79. // INITIALIZATION -- runs once at startup ----------------------------------
  80. void setup(void) {
  81. uint8_t e; // Eye index, 0 to NUM_EYES-1
  82. Serial.begin(115200);
  83. //while (!Serial && millis() < 5000 );
  84. delay(1500);
  85. SERIAL_tt.println("\n" __FILE__ " " __DATE__ " " __TIME__);
  86. SERIAL_tt.println("\n********\n T4 connected Serial_tt ******* debug_tt port\n");
  87. Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
  88. //debBegin_tt( (HardwareSerial*)&SERIAL_tt, LED_BUILTIN, BUTTON_ISR);
  89. #if defined(SYNCPIN) && (SYNCPIN >= 0) // If using I2C sync...
  90. pinMode(SYNCPIN, INPUT_PULLUP); // Check for jumper to ground
  91. if (!digitalRead(SYNCPIN)) { // If there...
  92. receiver = true; // Set this one up as receiver
  93. Wire.begin(SYNCADDR);
  94. Wire.onReceive(wireCallback);
  95. } else {
  96. Wire.begin(); // Else set up as sender
  97. }
  98. #endif
  99. //Serial.begin(115200);
  100. //while (!Serial);
  101. Serial.println("Init");
  102. randomSeed(analogRead(A3)); // Seed random() from floating analog input
  103. #ifdef DISPLAY_BACKLIGHT
  104. // Enable backlight pin, initially off
  105. Serial.println("Backlight off");
  106. pinMode(DISPLAY_BACKLIGHT, OUTPUT);
  107. digitalWrite(DISPLAY_BACKLIGHT, LOW);
  108. #endif
  109. // Initialize eye objects based on eyeInfo list in config.h:
  110. for (e = 0; e < NUM_EYES; e++) {
  111. Serial.print("Create display #"); Serial.println(e);
  112. #if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
  113. //eye[e].display = new displayType(&TFT_SPI, eyeInfo[e].select,
  114. // DISPLAY_DC, -1);
  115. //for SPI
  116. //(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
  117. //eye[e].display = new displayType(eyeInfo[e].select, DISPLAY_DC, -1);
  118. eye[e].display = new displayType(eyeInfo[e].select, DISPLAY_DC, 11,13,-1 );
  119. #else // OLED
  120. eye[e].display = new displayType(128, 128, &TFT_SPI,
  121. eyeInfo[e].select, DISPLAY_DC, -1);
  122. #endif
  123. eye[e].blink.state = NOBLINK;
  124. // If project involves only ONE eye and NO other SPI devices, its
  125. // select line can be permanently tied to GND and corresponding pin
  126. // in config.h set to -1. Best to use it though.
  127. if (eyeInfo[e].select >= 0) {
  128. pinMode(eyeInfo[e].select, OUTPUT);
  129. digitalWrite(eyeInfo[e].select, HIGH); // Deselect them all
  130. }
  131. // Also set up an individual eye-wink pin if defined:
  132. if (eyeInfo[e].wink >= 0) pinMode(eyeInfo[e].wink, INPUT_PULLUP);
  133. }
  134. #if defined(BLINK_PIN) && (BLINK_PIN >= 0)
  135. pinMode(BLINK_PIN, INPUT_PULLUP); // Ditto for all-eyes blink pin
  136. #endif
  137. #if defined(DISPLAY_RESET) && (DISPLAY_RESET >= 0)
  138. // Because both displays share a common reset pin, -1 is passed to
  139. // the display constructor above to prevent the begin() function from
  140. // resetting both displays after one is initialized. Instead, handle
  141. // the reset manually here to take care of both displays just once:
  142. Serial.println("Reset displays");
  143. pinMode(DISPLAY_RESET, OUTPUT);
  144. digitalWrite(DISPLAY_RESET, LOW); delay(1);
  145. digitalWrite(DISPLAY_RESET, HIGH); delay(50);
  146. // Alternately, all display reset pin(s) could be connected to the
  147. // microcontroller reset, in which case DISPLAY_RESET should be set
  148. // to -1 or left undefined in config.h.
  149. #endif
  150. Serial.println("Call init/begin func for each display");
  151. // After all-displays reset, now call init/begin func for each display:
  152. for (e = 0; e < NUM_EYES; e++) {
  153. #if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
  154. eye[e].display->initR(eyeInfo[e].init_option);
  155. Serial.print("Init ST77xx display #"); Serial.println(e);
  156. #else // OLED
  157. eye[e].display->begin();
  158. #endif
  159. Serial.println("Rotate");
  160. eye[e].display->setRotation(eyeInfo[e].rotation);
  161. }
  162. Serial.println("done");
  163. #if defined(LOGO_TOP_WIDTH) || defined(COLOR_LOGO_WIDTH)
  164. Serial.println("Display logo");
  165. // I noticed lots of folks getting right/left eyes flipped, or
  166. // installing upside-down, etc. Logo split across screens may help:
  167. for (e = 0; e < NUM_EYES; e++) { // Another pass, after all screen inits
  168. eye[e].display->fillScreen(0);
  169. #ifdef LOGO_TOP_WIDTH
  170. // Monochrome Adafruit logo is 2 mono bitmaps:
  171. eye[e].display->drawBitmap(NUM_EYES * 64 - e * 128 - 20,
  172. 0, logo_top, LOGO_TOP_WIDTH, LOGO_TOP_HEIGHT, 0xFFFF);
  173. eye[e].display->drawBitmap(NUM_EYES * 64 - e * 128 - LOGO_BOTTOM_WIDTH / 2,
  174. LOGO_TOP_HEIGHT, logo_bottom, LOGO_BOTTOM_WIDTH, LOGO_BOTTOM_HEIGHT,
  175. 0xFFFF);
  176. #else
  177. // Color sponsor logo is one RGB bitmap:
  178. eye[e].display->fillScreen(color_logo[0]);
  179. eye[0].display->drawRGBBitmap(
  180. (eye[e].display->width() - COLOR_LOGO_WIDTH ) / 2,
  181. (eye[e].display->height() - COLOR_LOGO_HEIGHT) / 2,
  182. color_logo, COLOR_LOGO_WIDTH, COLOR_LOGO_HEIGHT);
  183. #endif
  184. // After logo is drawn
  185. }
  186. #ifdef DISPLAY_BACKLIGHT
  187. int i;
  188. Serial.println("Fade in backlight");
  189. for (i = 0; i < BACKLIGHT_MAX; i++) { // Fade logo in
  190. analogWrite(DISPLAY_BACKLIGHT, i);
  191. delay(2);
  192. }
  193. delay(1400); // Pause for screen layout/orientation
  194. Serial.println("Fade out backlight");
  195. for (; i >= 0; i--) {
  196. analogWrite(DISPLAY_BACKLIGHT, i);
  197. delay(2);
  198. }
  199. for (e = 0; e < NUM_EYES; e++) { // Clear display(s)
  200. eye[e].display->fillScreen(0);
  201. }
  202. delay(100);
  203. #else
  204. delay(2000); // Pause for screen layout/orientation
  205. #endif // DISPLAY_BACKLIGHT
  206. #endif // LOGO_TOP_WIDTH
  207. // One of the displays is configured to mirror on the X axis. Simplifies
  208. // eyelid handling in the drawEye() function -- no need for distinct
  209. // L-to-R or R-to-L inner loops. Just the X coordinate of the iris is
  210. // then reversed when drawing this eye, so they move the same. Magic!
  211. #if defined(SYNCPIN) && (SYNCPIN >= 0)
  212. if (receiver) {
  213. #endif
  214. Serial.println("Rotate/Mirror display");
  215. #if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
  216. const uint8_t mirrorTFT[] = { 0x88, 0x28, 0x48, 0xE8 }; // Mirror+rotate
  217. eye[0].display->sendCommand(
  218. #ifdef ST77XX_MADCTL
  219. ST77XX_MADCTL, // Current TFT lib
  220. #else
  221. ST7735_MADCTL, // Older TFT lib
  222. #endif
  223. &mirrorTFT[eyeInfo[0].rotation & 3], 1);
  224. #else // OLED
  225. const uint8_t rotateOLED[] = { 0x74, 0x77, 0x66, 0x65 },
  226. mirrorOLED[] = { 0x76, 0x67, 0x64, 0x75 }; // Mirror+rotate
  227. // If OLED, loop through ALL eyes and set up remap register
  228. // from either mirrorOLED[] (first eye) or rotateOLED[] (others).
  229. // The OLED library doesn't normally use the remap reg (TFT does).
  230. for (e = 0; e < NUM_EYES; e++) {
  231. eye[e].display->sendCommand(SSD1351_CMD_SETREMAP, e ?
  232. &rotateOLED[eyeInfo[e].rotation & 3] :
  233. &mirrorOLED[eyeInfo[e].rotation & 3], 1);
  234. }
  235. #endif
  236. #if defined(SYNCPIN) && (SYNCPIN >= 0)
  237. } // Don't mirror receiver screen
  238. #endif
  239. #ifdef DISPLAY_BACKLIGHT
  240. Serial.println("Backlight on!");
  241. analogWrite(DISPLAY_BACKLIGHT, BACKLIGHT_MAX);
  242. #endif
  243. Serial.println("Setup Complete!");
  244. startTime = millis(); // For frame-rate calculation
  245. }
  246. // EYE-RENDERING FUNCTION --------------------------------------------------
  247. SPISettings settings(SPI_FREQ, MSBFIRST, SPI_MODE0);
  248. void drawEye( // Renders one eye. Inputs must be pre-clipped & valid.
  249. uint8_t e, // Eye array index; 0 or 1 for left/right
  250. uint16_t iScale, // Scale factor for iris (0-1023)
  251. uint8_t scleraX, // First pixel X offset into sclera image
  252. uint8_t scleraY, // First pixel Y offset into sclera image
  253. uint8_t uT, // Upper eyelid threshold value
  254. uint8_t lT) { // Lower eyelid threshold value
  255. uint8_t screenX, screenY, scleraXsave;
  256. int16_t irisX, irisY;
  257. uint16_t p, a;
  258. uint32_t d;
  259. uint16_t colors[SCREEN_WIDTH];
  260. #if defined(SYNCPIN) && (SYNCPIN >= 0)
  261. if (receiver) {
  262. // Overwrite arguments with values in syncStruct. Disable interrupts
  263. // briefly so new data can't overwrite the struct in mid-parse.
  264. noInterrupts();
  265. iScale = syncStruct.iScale;
  266. // Screen is mirrored, this 'de-mirrors' the eye X direction
  267. scleraX = SCLERA_WIDTH - 1 - SCREEN_WIDTH - syncStruct.scleraX;
  268. scleraY = syncStruct.scleraY;
  269. uT = syncStruct.uT;
  270. lT = syncStruct.lT;
  271. interrupts();
  272. } else {
  273. // Stuff arguments into syncStruct and send to receiver
  274. syncStruct.iScale = iScale;
  275. syncStruct.scleraX = scleraX;
  276. syncStruct.scleraY = scleraY;
  277. syncStruct.uT = uT;
  278. syncStruct.lT = lT;
  279. Wire.beginTransmission(SYNCADDR);
  280. Wire.write((char *)&syncStruct, sizeof syncStruct);
  281. Wire.endTransmission();
  282. }
  283. #endif
  284. uint8_t irisThreshold = (128 * (1023 - iScale) + 512) / 1024;
  285. uint32_t irisScale = IRIS_MAP_HEIGHT * 65536 / irisThreshold;
  286. // Set up raw pixel dump to entire screen. Although such writes can wrap
  287. // around automatically from end of rect back to beginning, the region is
  288. // reset on each frame here in case of an SPI glitch.
  289. TFT_SPI.beginTransaction(settings);
  290. digitalWrite(eyeInfo[e].select, LOW); // Chip select
  291. #if defined(_ADAFRUIT_ST7735H_) || defined(_ADAFRUIT_ST77XXH_) // TFT
  292. //eye[e].display->setAddrWindow(0, 0, 128, 128);
  293. //eye[e].display->sendCommand(ST7735_RAMWR, 0, 0);
  294. #else // OLED
  295. eye[e].display->writeCommand(SSD1351_CMD_SETROW); // Y range
  296. eye[e].display->spiWrite(0); eye[e].display->spiWrite(SCREEN_HEIGHT - 1);
  297. eye[e].display->writeCommand(SSD1351_CMD_SETCOLUMN); // X range
  298. eye[e].display->spiWrite(0); eye[e].display->spiWrite(SCREEN_WIDTH - 1);
  299. eye[e].display->writeCommand(SSD1351_CMD_WRITERAM); // Begin write
  300. #endif
  301. digitalWrite(eyeInfo[e].select, LOW); // Re-chip-select
  302. digitalWrite(DISPLAY_DC, HIGH); // Data mode
  303. // Now just issue raw 16-bit values for every pixel...
  304. scleraXsave = scleraX; // Save initial X value to reset on each line
  305. irisY = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT) / 2;
  306. for (screenY = 0; screenY < SCREEN_HEIGHT; screenY++, scleraY++, irisY++) {
  307. scleraX = scleraXsave;
  308. irisX = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
  309. for (screenX = 0; screenX < SCREEN_WIDTH; screenX++, scleraX++, irisX++) {
  310. if ((lower[screenY][screenX] <= lT) ||
  311. (upper[screenY][screenX] <= uT)) { // Covered by eyelid
  312. p = 0;
  313. } else if ((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
  314. (irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
  315. p = sclera[scleraY][scleraX];
  316. } else { // Maybe iris...
  317. p = polar[irisY][irisX]; // Polar angle/dist
  318. d = p & 0x7F; // Distance from edge (0-127)
  319. if (d < irisThreshold) { // Within scaled iris area
  320. d = d * irisScale / 65536; // d scaled to iris image height
  321. a = (IRIS_MAP_WIDTH * (p >> 7)) / 512; // Angle (X)
  322. p = iris[d][a]; // Pixel = iris
  323. } else { // Not in iris
  324. p = sclera[scleraY][scleraX]; // Pixel = sclera
  325. }
  326. }
  327. //eye[e].display->drawPixel(screenX,screenY,p);
  328. colors[screenX] = p;
  329. //eye[e].display->pushColor(p);
  330. } // end column
  331. eye[e].display->writeRect(0, screenY, SCREEN_WIDTH, 1, colors);
  332. } // end scanline
  333. digitalWrite(eyeInfo[e].select, HIGH); // Deselect
  334. TFT_SPI.endTransaction();
  335. }
  336. // EYE ANIMATION -----------------------------------------------------------
  337. const uint8_t PROGMEM ease[] = { // Ease in/out curve for eye movements 3*t^2-2*t^3
  338. 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, // T
  339. 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 7, 8, 9, 9, 10, 10, // h
  340. 11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23, // x
  341. 24, 25, 26, 27, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, // 2
  342. 40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58, // A
  343. 60, 61, 62, 63, 65, 66, 67, 69, 70, 72, 73, 74, 76, 77, 78, 80, // l
  344. 81, 83, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 98, 100, 101, 103, // e
  345. 104, 106, 107, 109, 110, 112, 113, 115, 116, 118, 119, 121, 122, 124, 125, 127, // c
  346. 128, 130, 131, 133, 134, 136, 137, 139, 140, 142, 143, 145, 146, 148, 149, 151, // J
  347. 152, 154, 155, 157, 158, 159, 161, 162, 164, 165, 167, 168, 170, 171, 172, 174, // a
  348. 175, 177, 178, 179, 181, 182, 183, 185, 186, 188, 189, 190, 192, 193, 194, 195, // c
  349. 197, 198, 199, 201, 202, 203, 204, 205, 207, 208, 209, 210, 211, 213, 214, 215, // o
  350. 216, 217, 218, 219, 220, 221, 222, 224, 225, 226, 227, 228, 228, 229, 230, 231, // b
  351. 232, 233, 234, 235, 236, 237, 237, 238, 239, 240, 240, 241, 242, 243, 243, 244, // s
  352. 245, 245, 246, 246, 247, 248, 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, // o
  353. 252, 253, 253, 253, 254, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255
  354. }; // n
  355. #ifdef AUTOBLINK
  356. uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L;
  357. #endif
  358. void frame( // Process motion for a single frame of left or right eye
  359. uint16_t iScale) { // Iris scale (0-1023) passed in
  360. static uint32_t frames = 0; // Used in frame rate calculation
  361. static uint8_t eyeIndex = 0; // eye[] array counter
  362. int16_t eyeX, eyeY;
  363. uint32_t t = micros(); // Time at start of function
  364. if (!(++frames & 255)) { // Every 256 frames...
  365. uint32_t elapsed = (millis() - startTime) / 1000;
  366. if (elapsed) Serial.println(frames / elapsed); // Print FPS
  367. }
  368. if (++eyeIndex >= NUM_EYES) eyeIndex = 0; // Cycle through eyes, 1 per call
  369. // X/Y movement
  370. #if defined(JOYSTICK_X_PIN) && (JOYSTICK_X_PIN >= 0) && \
  371. defined(JOYSTICK_Y_PIN) && (JOYSTICK_Y_PIN >= 0)
  372. // Read X/Y from joystick, constrain to circle
  373. int16_t dx, dy;
  374. int32_t d;
  375. eyeX = analogRead(JOYSTICK_X_PIN); // Raw (unclipped) X/Y reading
  376. eyeY = analogRead(JOYSTICK_Y_PIN);
  377. #ifdef JOYSTICK_X_FLIP
  378. eyeX = 1023 - eyeX;
  379. #endif
  380. #ifdef JOYSTICK_Y_FLIP
  381. eyeY = 1023 - eyeY;
  382. #endif
  383. dx = (eyeX * 2) - 1023; // A/D exact center is at 511.5. Scale coords
  384. dy = (eyeY * 2) - 1023; // X2 so range is -1023 to +1023 w/center at 0.
  385. if ((d = (dx * dx + dy * dy)) > (1023 * 1023)) { // Outside circle
  386. d = (int32_t)sqrt((float)d); // Distance from center
  387. eyeX = ((dx * 1023 / d) + 1023) / 2; // Clip to circle edge,
  388. eyeY = ((dy * 1023 / d) + 1023) / 2; // scale back to 0-1023
  389. }
  390. #else // Autonomous X/Y eye motion
  391. // Periodically initiates motion to a new random point, random speed,
  392. // holds there for random period until next motion.
  393. static boolean eyeInMotion = false;
  394. static int16_t eyeOldX = 512, eyeOldY = 512, eyeNewX = 512, eyeNewY = 512;
  395. static uint32_t eyeMoveStartTime = 0L;
  396. static int32_t eyeMoveDuration = 0L;
  397. int32_t dt = t - eyeMoveStartTime; // uS elapsed since last eye event
  398. if (eyeInMotion) { // Currently moving?
  399. if (dt >= eyeMoveDuration) { // Time up? Destination reached.
  400. eyeInMotion = false; // Stop moving
  401. eyeMoveDuration = random(3000000); // 0-3 sec stop
  402. eyeMoveStartTime = t; // Save initial time of stop
  403. eyeX = eyeOldX = eyeNewX; // Save position
  404. eyeY = eyeOldY = eyeNewY;
  405. } else { // Move time's not yet fully elapsed -- interpolate position
  406. int16_t e = ease[255 * dt / eyeMoveDuration] + 1; // Ease curve
  407. eyeX = eyeOldX + (((eyeNewX - eyeOldX) * e) / 256); // Interp X
  408. eyeY = eyeOldY + (((eyeNewY - eyeOldY) * e) / 256); // and Y
  409. }
  410. } else { // Eye stopped
  411. eyeX = eyeOldX;
  412. eyeY = eyeOldY;
  413. if (dt > eyeMoveDuration) { // Time up? Begin new move.
  414. int16_t dx, dy;
  415. uint32_t d;
  416. do { // Pick new dest in circle
  417. eyeNewX = random(1024);
  418. eyeNewY = random(1024);
  419. dx = (eyeNewX * 2) - 1023;
  420. dy = (eyeNewY * 2) - 1023;
  421. } while ((d = (dx * dx + dy * dy)) > (1023 * 1023)); // Keep trying
  422. eyeMoveDuration = random(72000, 144000); // ~1/14 - ~1/7 sec
  423. eyeMoveStartTime = t; // Save initial time of move
  424. eyeInMotion = true; // Start move on next frame
  425. }
  426. }
  427. #endif // JOYSTICK_X_PIN etc.
  428. // Blinking
  429. #ifdef AUTOBLINK
  430. // Similar to the autonomous eye movement above -- blink start times
  431. // and durations are random (within ranges).
  432. if ((t - timeOfLastBlink) >= timeToNextBlink) { // Start new blink?
  433. timeOfLastBlink = t;
  434. uint32_t blinkDuration = random(36000, 72000); // ~1/28 - ~1/14 sec
  435. // Set up durations for both eyes (if not already winking)
  436. for (uint8_t e = 0; e < NUM_EYES; e++) {
  437. if (eye[e].blink.state == NOBLINK) {
  438. eye[e].blink.state = ENBLINK;
  439. eye[e].blink.startTime = t;
  440. eye[e].blink.duration = blinkDuration;
  441. }
  442. }
  443. timeToNextBlink = blinkDuration * 3 + random(4000000);
  444. }
  445. #endif
  446. if (eye[eyeIndex].blink.state) { // Eye currently blinking?
  447. // Check if current blink state time has elapsed
  448. if ((t - eye[eyeIndex].blink.startTime) >= eye[eyeIndex].blink.duration) {
  449. // Yes -- increment blink state, unless...
  450. if ((eye[eyeIndex].blink.state == ENBLINK) && ( // Enblinking and...
  451. #if defined(BLINK_PIN) && (BLINK_PIN >= 0)
  452. (digitalRead(BLINK_PIN) == LOW) || // blink or wink held...
  453. #endif
  454. ((eyeInfo[eyeIndex].wink >= 0) &&
  455. digitalRead(eyeInfo[eyeIndex].wink) == LOW) )) {
  456. // Don't advance state yet -- eye is held closed instead
  457. } else { // No buttons, or other state...
  458. if (++eye[eyeIndex].blink.state > DEBLINK) { // Deblinking finished?
  459. eye[eyeIndex].blink.state = NOBLINK; // No longer blinking
  460. } else { // Advancing from ENBLINK to DEBLINK mode
  461. eye[eyeIndex].blink.duration *= 2; // DEBLINK is 1/2 ENBLINK speed
  462. eye[eyeIndex].blink.startTime = t;
  463. }
  464. }
  465. }
  466. } else { // Not currently blinking...check buttons!
  467. #if defined(BLINK_PIN) && (BLINK_PIN >= 0)
  468. if (digitalRead(BLINK_PIN) == LOW) {
  469. // Manually-initiated blinks have random durations like auto-blink
  470. uint32_t blinkDuration = random(36000, 72000);
  471. for (uint8_t e = 0; e < NUM_EYES; e++) {
  472. if (eye[e].blink.state == NOBLINK) {
  473. eye[e].blink.state = ENBLINK;
  474. eye[e].blink.startTime = t;
  475. eye[e].blink.duration = blinkDuration;
  476. }
  477. }
  478. } else
  479. #endif
  480. if ((eyeInfo[eyeIndex].wink >= 0) &&
  481. (digitalRead(eyeInfo[eyeIndex].wink) == LOW)) { // Wink!
  482. eye[eyeIndex].blink.state = ENBLINK;
  483. eye[eyeIndex].blink.startTime = t;
  484. eye[eyeIndex].blink.duration = random(45000, 90000);
  485. }
  486. }
  487. // Process motion, blinking and iris scale into renderable values
  488. // Scale eye X/Y positions (0-1023) to pixel units used by drawEye()
  489. eyeX = map(eyeX, 0, 1023, 0, SCLERA_WIDTH - 128);
  490. eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128);
  491. if (eyeIndex == 1) eyeX = (SCLERA_WIDTH - 128) - eyeX; // Mirrored display
  492. // Horizontal position is offset so that eyes are very slightly crossed
  493. // to appear fixated (converged) at a conversational distance. Number
  494. // here was extracted from my posterior and not mathematically based.
  495. // I suppose one could get all clever with a range sensor, but for now...
  496. if (NUM_EYES > 1) eyeX += 4;
  497. if (eyeX > (SCLERA_WIDTH - 128)) eyeX = (SCLERA_WIDTH - 128);
  498. // Eyelids are rendered using a brightness threshold image. This same
  499. // map can be used to simplify another problem: making the upper eyelid
  500. // track the pupil (eyes tend to open only as much as needed -- e.g. look
  501. // down and the upper eyelid drops). Just sample a point in the upper
  502. // lid map slightly above the pupil to determine the rendering threshold.
  503. static uint8_t uThreshold = 128;
  504. uint8_t lThreshold, n;
  505. #ifdef TRACKING
  506. int16_t sampleX = SCLERA_WIDTH / 2 - (eyeX / 2), // Reduce X influence
  507. sampleY = SCLERA_HEIGHT / 2 - (eyeY + IRIS_HEIGHT / 4);
  508. // Eyelid is slightly asymmetrical, so two readings are taken, averaged
  509. if (sampleY < 0) n = 0;
  510. else n = (upper[sampleY][sampleX] +
  511. upper[sampleY][SCREEN_WIDTH - 1 - sampleX]) / 2;
  512. uThreshold = (uThreshold * 3 + n) / 4; // Filter/soften motion
  513. // Lower eyelid doesn't track the same way, but seems to be pulled upward
  514. // by tension from the upper lid.
  515. lThreshold = 254 - uThreshold;
  516. #else // No tracking -- eyelids full open unless blink modifies them
  517. uThreshold = lThreshold = 0;
  518. #endif
  519. // The upper/lower thresholds are then scaled relative to the current
  520. // blink position so that blinks work together with pupil tracking.
  521. if (eye[eyeIndex].blink.state) { // Eye currently blinking?
  522. uint32_t s = (t - eye[eyeIndex].blink.startTime);
  523. if (s >= eye[eyeIndex].blink.duration) s = 255; // At or past blink end
  524. else s = 255 * s / eye[eyeIndex].blink.duration; // Mid-blink
  525. s = (eye[eyeIndex].blink.state == DEBLINK) ? 1 + s : 256 - s;
  526. n = (uThreshold * s + 254 * (257 - s)) / 256;
  527. lThreshold = (lThreshold * s + 254 * (257 - s)) / 256;
  528. } else {
  529. n = uThreshold;
  530. }
  531. // Pass all the derived values to the eye-rendering function:
  532. drawEye(eyeIndex, iScale, eyeX, eyeY, n, lThreshold);
  533. }
  534. // AUTONOMOUS IRIS SCALING (if no photocell or dial) -----------------------
  535. #if !defined(LIGHT_PIN) || (LIGHT_PIN < 0)
  536. // Autonomous iris motion uses a fractal behavior to similate both the major
  537. // reaction of the eye plus the continuous smaller adjustments that occur.
  538. uint16_t oldIris = (IRIS_MIN + IRIS_MAX) / 2, newIris;
  539. void split( // Subdivides motion path into two sub-paths w/randimization
  540. int16_t startValue, // Iris scale value (IRIS_MIN to IRIS_MAX) at start
  541. int16_t endValue, // Iris scale value at end
  542. uint32_t startTime, // micros() at start
  543. int32_t duration, // Start-to-end time, in microseconds
  544. int16_t range) { // Allowable scale value variance when subdividing
  545. if (range >= 8) { // Limit subdvision count, because recursion
  546. range /= 2; // Split range & time in half for subdivision,
  547. duration /= 2; // then pick random center point within range:
  548. int16_t midValue = (startValue + endValue - range) / 2 + random(range);
  549. uint32_t midTime = startTime + duration;
  550. split(startValue, midValue, startTime, duration, range); // First half
  551. split(midValue , endValue, midTime , duration, range); // Second half
  552. } else { // No more subdivisons, do iris motion...
  553. int32_t dt; // Time (micros) since start of motion
  554. int16_t v; // Interim value
  555. while ((dt = (micros() - startTime)) < duration) {
  556. v = startValue + (((endValue - startValue) * dt) / duration);
  557. if (v < IRIS_MIN) v = IRIS_MIN; // Clip just in case
  558. else if (v > IRIS_MAX) v = IRIS_MAX;
  559. frame(v); // Draw frame w/interim iris scale value
  560. }
  561. }
  562. }
  563. #endif // !LIGHT_PIN
  564. // MAIN LOOP -- runs continuously after setup() ----------------------------
  565. void loop() {
  566. #if defined(LIGHT_PIN) && (LIGHT_PIN >= 0) // Interactive iris
  567. int16_t v = analogRead(LIGHT_PIN); // Raw dial/photocell reading
  568. #ifdef LIGHT_PIN_FLIP
  569. v = 1023 - v; // Reverse reading from sensor
  570. #endif
  571. if (v < LIGHT_MIN) v = LIGHT_MIN; // Clamp light sensor range
  572. else if (v > LIGHT_MAX) v = LIGHT_MAX;
  573. v -= LIGHT_MIN; // 0 to (LIGHT_MAX - LIGHT_MIN)
  574. #ifdef LIGHT_CURVE // Apply gamma curve to sensor input?
  575. v = (int16_t)(pow((double)v / (double)(LIGHT_MAX - LIGHT_MIN),
  576. LIGHT_CURVE) * (double)(LIGHT_MAX - LIGHT_MIN));
  577. #endif
  578. // And scale to iris range (IRIS_MAX is size at LIGHT_MIN)
  579. v = map(v, 0, (LIGHT_MAX - LIGHT_MIN), IRIS_MAX, IRIS_MIN);
  580. #ifdef IRIS_SMOOTH // Filter input (gradual motion)
  581. static int16_t irisValue = (IRIS_MIN + IRIS_MAX) / 2;
  582. irisValue = ((irisValue * 15) + v) / 16;
  583. frame(irisValue);
  584. #else // Unfiltered (immediate motion)
  585. frame(v);
  586. #endif // IRIS_SMOOTH
  587. #else // Autonomous iris scaling -- invoke recursive function
  588. newIris = random(IRIS_MIN, IRIS_MAX);
  589. split(oldIris, newIris, micros(), 10000000L, IRIS_MAX - IRIS_MIN);
  590. oldIris = newIris;
  591. #endif // LIGHT_PIN
  592. }