From 8085d8403c878f2411aba3977d7cfa058a5b55cb Mon Sep 17 00:00:00 2001 From: Phillip Burgess Date: Mon, 31 May 2021 12:20:02 -0700 Subject: [PATCH 1/5] Change eye struct table for non-MonsterM4sk 2-eye setups --- M4_Eyes/globals.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/M4_Eyes/globals.h b/M4_Eyes/globals.h index 296300994..6a546eb90 100644 --- a/M4_Eyes/globals.h +++ b/M4_Eyes/globals.h @@ -188,7 +188,7 @@ typedef struct { #ifdef INIT_EYESTRUCTS eyeStruct eye[NUM_EYES] = { - #if defined(ADAFRUIT_MONSTER_M4SK_EXPRESS) + #if (NUM_EYES > 1) // name spi cs dc rst wink { "right", &ARCADA_TFT_SPI , ARCADA_TFT_CS, ARCADA_TFT_DC, ARCADA_TFT_RST, -1 }, { "left" , &ARCADA_LEFTTFT_SPI, ARCADA_LEFTTFT_CS, ARCADA_LEFTTFT_DC, ARCADA_LEFTTFT_RST, -1 } }; From 213f3d7842b34315ed811fe233e1111e4dd9d834 Mon Sep 17 00:00:00 2001 From: Phillip Burgess Date: Mon, 31 May 2021 13:16:10 -0700 Subject: [PATCH 2/5] Check ARCADA_LEFTTFT_SPI rather than ADAFRUIT_MONSTER_M4SK_EXPRESS to enable second eye ARCADA_LEFTTFT_SPI is normally only defined on Monster M4sk but a custom Arcada config can override this and allow 2 eyes on other boards. --- M4_Eyes/globals.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/M4_Eyes/globals.h b/M4_Eyes/globals.h index 6a546eb90..3e5c50193 100644 --- a/M4_Eyes/globals.h +++ b/M4_Eyes/globals.h @@ -11,9 +11,10 @@ #define GLOBAL_INIT(X) #endif -#if defined(ADAFRUIT_MONSTER_M4SK_EXPRESS) +#if defined(ARCADA_LEFTTFT_SPI) // MONSTER M4SK or custom Arcada setup #define NUM_EYES 2 - // Light sensor is not active by default. Use "lightSensor : 102" in config + // MONSTER M4SK light sensor is not active by default. + // Use "lightSensor : 102" in config #else #define NUM_EYES 1 #endif From b01710217102e376298ffa1f171b2c4f9c8ff814 Mon Sep 17 00:00:00 2001 From: Phillip Burgess Date: Tue, 1 Jun 2021 00:35:16 -0700 Subject: [PATCH 3/5] Add microsaccade movement, fix some minor typos & bugs --- M4_Eyes/M4_Eyes.ino | 97 ++++++++++++++++++++++++++------------------- M4_Eyes/globals.h | 2 +- M4_Eyes/user.cpp | 2 + 3 files changed, 60 insertions(+), 41 deletions(-) diff --git a/M4_Eyes/M4_Eyes.ino b/M4_Eyes/M4_Eyes.ino index c30eda7a6..95ee676c6 100644 --- a/M4_Eyes/M4_Eyes.ino +++ b/M4_Eyes/M4_Eyes.ino @@ -34,7 +34,6 @@ #error "Please select Tools->USB Stack->TinyUSB before compiling" #endif -#include #define GLOBAL_VAR #include "globals.h" @@ -43,6 +42,8 @@ bool eyeInMotion = false; float eyeOldX, eyeOldY, eyeNewX, eyeNewY; uint32_t eyeMoveStartTime = 0L; int32_t eyeMoveDuration = 0L; +uint32_t lastSaccadeStop = 0L; +int32_t saccadeInterval = 0L; // Some sloppy eye state stuff, some carried over from old eye code... // kinda messy and badly named and will get cleaned up/moved/etc. @@ -472,49 +473,65 @@ void loop() { // ONCE-PER-FRAME EYE ANIMATION LOGIC HAPPENS HERE ------------------- - float eyeX, eyeY; // Eye movement - 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 - if (moveEyesRandomly) { - eyeMoveDuration = random(10000, 3000000); // 0.01-3 sec stop - eyeMoveStartTime = t; // Save initial time of stop + float eyeX, eyeY; + if(moveEyesRandomly) { + int32_t dt = t - eyeMoveStartTime; // uS elapsed since last eye event + if(eyeInMotion) { // Eye currently moving? + if(dt >= eyeMoveDuration) { // Time up? Destination reached. + eyeInMotion = false; // Stop moving + // The "move" duration temporarily becomes a hold duration... + eyeMoveDuration = random(30000, 1000000); // Time between microsaccades + if(!saccadeInterval) { // Cleared when "big" saccade finishes + lastSaccadeStop = t; // Time when saccade stopped + saccadeInterval = random(eyeMoveDuration, 3000000); // Next in 30ms to 3sec + } + // Similarly, the "move" start time becomes the "stop" starting time... + eyeMoveStartTime = t; // Save time of event + eyeX = eyeOldX = eyeNewX; // Save position + eyeY = eyeOldY = eyeNewY; + } else { // Move time's not yet fully elapsed -- interpolate position + float e = (float)dt / float(eyeMoveDuration); // 0.0 to 1.0 during move + e = 3 * e * e - 2 * e * e * e; // Easing function: 3*e^2-2*e^3 0.0 to 1.0 + eyeX = eyeOldX + (eyeNewX - eyeOldX) * e; // Interp X + eyeY = eyeOldY + (eyeNewY - eyeOldY) * e; // and Y } - eyeX = eyeOldX = eyeNewX; // Save position - eyeY = eyeOldY = eyeNewY; - } else { // Move time's not yet fully elapsed -- interpolate position - float e = (float)dt / float(eyeMoveDuration); // 0.0 to 1.0 during move - e = 3 * e * e - 2 * e * e * e; // Easing function: 3*e^2-2*e^3 0.0 to 1.0 - eyeX = eyeOldX + (eyeNewX - eyeOldX) * e; // Interp X - eyeY = eyeOldY + (eyeNewY - eyeOldY) * e; // and Y - } - } else { // Eye stopped - eyeX = eyeOldX; - eyeY = eyeOldY; - if(dt > eyeMoveDuration) { // Time up? Begin new move. - // r is the radius in X and Y that the eye can go, from (0,0) in the center. - float r = (float)mapDiameter - (float)DISPLAY_SIZE * M_PI_2; // radius of motion - r *= 0.6; // calibration constant - - if (moveEyesRandomly) { - eyeNewX = random(-r, r); - float h = sqrt(r * r - x * x); - eyeNewY = random(-h, h); - } else { - eyeNewX = eyeTargetX * r; - eyeNewY = eyeTargetY * r; + } else { // Eye is currently stopped + eyeX = eyeOldX; + eyeY = eyeOldY; + if(dt > eyeMoveDuration) { // Time up? Begin new move. + if((t - lastSaccadeStop) > saccadeInterval) { // Time for a "big" saccade + // r is the radius in X and Y that the eye can go, from (0,0) in the center. + float r = ((float)mapDiameter - (float)DISPLAY_SIZE * M_PI_2) * 0.75; + eyeNewX = random(-r, r); + float h = sqrt(r * r - eyeNewX * eyeNewX); + eyeNewY = random(-h, h); + // Set the duration for this move, and start it going. + eyeMoveDuration = random(83000, 166000); // ~1/12 - ~1/6 sec + saccadeInterval = 0; // Calc next interval when this one stops + } else { // Microsaccade + // r is possible radius of motion, 1/10 size of full saccade. + // We don't bother with clipping because if it strays just a little, + // that's okay, it'll get put in-bounds on next full saccade. + float r = (float)mapDiameter - (float)DISPLAY_SIZE * M_PI_2; + r *= 0.06; + float dx = random(-r, r); + eyeNewX = eyeX - mapRadius + dx; + float h = sqrt(r * r - dx * dx); + eyeNewY = eyeY - mapRadius + random(-h, h); + eyeMoveDuration = random(6000, 25000); // 6-25 ms microsaccade + } + eyeNewX += mapRadius; // Translate new point into map space + eyeNewY += mapRadius; + eyeMoveStartTime = t; // Save initial time of move + eyeInMotion = true; // Start move on next frame } - - eyeNewX += mapRadius; - eyeNewY += mapRadius; - - // Set the duration for this move, and start it going. - eyeMoveDuration = random(83000, 166000); // ~1/12 - ~1/6 sec - eyeMoveStartTime = t; // Save initial time of move - eyeInMotion = true; // Start move on next frame } + } else { + // Allow user code to control eye position (e.g. IR sensor, joystick, etc.) + float r = ((float)mapDiameter - (float)DISPLAY_SIZE * M_PI_2) * 0.9; + eyeX = mapRadius + eyeTargetX * r; + eyeY = mapRadius + eyeTargetY * r; } // Eyes fixate (are slightly crossed) -- amount is filtered for boops diff --git a/M4_Eyes/globals.h b/M4_Eyes/globals.h index 3e5c50193..058872bff 100644 --- a/M4_Eyes/globals.h +++ b/M4_Eyes/globals.h @@ -73,7 +73,7 @@ GLOBAL_VAR float trackFactor GLOBAL_INIT(0.5); // Random eye motion: provided by the base project, but overridable by user code. GLOBAL_VAR bool moveEyesRandomly GLOBAL_INIT(true); // Clear to suppress random eye motion and let user code control it -GLOBAL_VAR float eyeTargetX GLOBAL_INIT(0.0); // THen set these continuously in user_loop. +GLOBAL_VAR float eyeTargetX GLOBAL_INIT(0.0); // Then set these continuously in user_loop. GLOBAL_VAR float eyeTargetY GLOBAL_INIT(0.0); // Range is from -1.0 to +1.0. // Pin definition stuff will go here diff --git a/M4_Eyes/user.cpp b/M4_Eyes/user.cpp index 19fe91642..3916d0003 100644 --- a/M4_Eyes/user.cpp +++ b/M4_Eyes/user.cpp @@ -1,5 +1,7 @@ #if 1 // Change to 0 to disable this code (must enable ONE user*.cpp only!) +#include "globals.h" + // This file provides a crude way to "drop in" user code to the eyes, // allowing concurrent operations without having to maintain a bunch of // special derivatives of the eye code (which is still undergoing a lot From 372b84b2c7213fe8cb3a99c32b2ed94793c0eab4 Mon Sep 17 00:00:00 2001 From: Phillip Burgess Date: Tue, 1 Jun 2021 07:59:02 -0700 Subject: [PATCH 4/5] Tweak microsaccade numbers & comment --- M4_Eyes/M4_Eyes.ino | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/M4_Eyes/M4_Eyes.ino b/M4_Eyes/M4_Eyes.ino index 95ee676c6..c460929ab 100644 --- a/M4_Eyes/M4_Eyes.ino +++ b/M4_Eyes/M4_Eyes.ino @@ -481,7 +481,7 @@ void loop() { if(dt >= eyeMoveDuration) { // Time up? Destination reached. eyeInMotion = false; // Stop moving // The "move" duration temporarily becomes a hold duration... - eyeMoveDuration = random(30000, 1000000); // Time between microsaccades + eyeMoveDuration = random(35000, 1000000); // Time between microsaccades if(!saccadeInterval) { // Cleared when "big" saccade finishes lastSaccadeStop = t; // Time when saccade stopped saccadeInterval = random(eyeMoveDuration, 3000000); // Next in 30ms to 3sec @@ -510,16 +510,16 @@ void loop() { eyeMoveDuration = random(83000, 166000); // ~1/12 - ~1/6 sec saccadeInterval = 0; // Calc next interval when this one stops } else { // Microsaccade - // r is possible radius of motion, 1/10 size of full saccade. + // r is possible radius of motion, ~1/10 size of full saccade. // We don't bother with clipping because if it strays just a little, // that's okay, it'll get put in-bounds on next full saccade. float r = (float)mapDiameter - (float)DISPLAY_SIZE * M_PI_2; - r *= 0.06; + r *= 0.07; float dx = random(-r, r); eyeNewX = eyeX - mapRadius + dx; float h = sqrt(r * r - dx * dx); eyeNewY = eyeY - mapRadius + random(-h, h); - eyeMoveDuration = random(6000, 25000); // 6-25 ms microsaccade + eyeMoveDuration = random(7000, 25000); // 7-25 ms microsaccade } eyeNewX += mapRadius; // Translate new point into map space eyeNewY += mapRadius; From 9319336a44cfef158bc8b12ca4bafeea5f5e52d9 Mon Sep 17 00:00:00 2001 From: Phillip Burgess Date: Sun, 6 Jun 2021 09:29:34 -0700 Subject: [PATCH 5/5] Add notes about optimizer setting --- M4_Eyes/M4_Eyes.ino | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/M4_Eyes/M4_Eyes.ino b/M4_Eyes/M4_Eyes.ino index c460929ab..d00dad44d 100644 --- a/M4_Eyes/M4_Eyes.ino +++ b/M4_Eyes/M4_Eyes.ino @@ -5,6 +5,11 @@ // "Uncanny Eyes" project (better for SAMD21 chips or Teensy 3.X and // 128x128 TFT or OLED screens, single SPI bus). +// IMPORTANT: DO NOT compile with optimizer settings exceeding -O3, else +// the flash storage code may brick your board! If this happens, install +// CircuitPython to reinitialize the filesystem, copy over your eye files +// (keep backups!), then upload this code (compiled at -O3 or less). + // LET'S HAVE A WORD ABOUT COORDINATE SYSTEMS before continuing. From an // outside observer's point of view, looking at the display(s) on these // boards, the eyes are rendered COLUMN AT A TIME, working LEFT TO RIGHT,