Adafruit_Learning_System_Gu.../M4_Eyes/globals.h
2022-02-23 13:22:52 -05:00

240 lines
11 KiB
C

// SPDX-FileCopyrightText: 2019 Phillip Burgess for Adafruit Industries
//
// SPDX-License-Identifier: MIT
//34567890123456789012345678901234567890123456789012345678901234567890123456
#include "Adafruit_Arcada.h"
#include "DMAbuddy.h" // DMA-bug-workaround class
#if defined(GLOBAL_VAR) // #defined in .ino file ONLY!
#define GLOBAL_INIT(X) = (X)
#define INIT_EYESTRUCTS
#else
#define GLOBAL_VAR extern
#define GLOBAL_INIT(X)
#endif
#if defined(ARCADA_LEFTTFT_SPI) // MONSTER M4SK or custom Arcada setup
#define NUM_EYES 2
// MONSTER M4SK light sensor is not active by default.
// Use "lightSensor : 102" in config
#else
#define NUM_EYES 1
#endif
// GLOBAL VARIABLES --------------------------------------------------------
GLOBAL_VAR Adafruit_Arcada arcada;
GLOBAL_VAR bool showSplashScreen GLOBAL_INIT(true); // Clear to suppress the splash screen
#define MAX_DISPLAY_SIZE 240
GLOBAL_VAR int DISPLAY_SIZE GLOBAL_INIT(240); // Start with assuming a 240x240 display
GLOBAL_VAR uint32_t stackReserve GLOBAL_INIT(5192); // See image-loading code
GLOBAL_VAR int eyeRadius GLOBAL_INIT(0); // 0 = Use default in loadConfig()
GLOBAL_VAR int eyeDiameter; // Calculated from eyeRadius later
GLOBAL_VAR int irisRadius GLOBAL_INIT(60); // Approx size in screen pixels
GLOBAL_VAR int slitPupilRadius GLOBAL_INIT(0); // 0 = round pupil
GLOBAL_VAR uint8_t eyelidIndex GLOBAL_INIT(0x00); // From table: learn.adafruit.com/assets/61921
GLOBAL_VAR uint16_t eyelidColor GLOBAL_INIT(0x0000); // Expand eyelidIndex to 16-bit
// mapRadius is the size of one quadrant of the polar-to-rectangular map,
// in pixels. To cover the front hemisphere of the eye, this should be a
// minimum of (eyeRadius * Pi / 2) -- but, to provide some coverage beyond
// just the front hemisphere, the value of 'coverage' determines how far
// this map wraps around the eye. 0.0 = no coverage, 0.5 = front hemisphere,
// 1.0 = full sphere. Do not bother making this 1.0 -- the far back side of
// the eye is never actually seen, since we're using a displacement map hack
// and not actually rotating a sphere, plus the resulting map would take a
// TON of RAM, probably more than we have. The default here, 0.6, provides
// a good balance between coverage and RAM, only occasionally will you see
// a crescent of back-of-eye color (and the sclera texture map can be
// designed to blend into it). eyeRadius is calculated in loadConfig() as
// eyeRadius * Pi * coverage -- if eyeRadius is 125 and coverage is 0.6,
// mapRadius will be 236 pixels, and the resulting polar angle/dist maps
// will total about 111K RAM.
GLOBAL_VAR float coverage GLOBAL_INIT(0.6);
GLOBAL_VAR int mapRadius; // calculated in loadConfig()
GLOBAL_VAR int mapDiameter; // calculated in loadConfig()
GLOBAL_VAR uint8_t *displace GLOBAL_INIT(NULL);
GLOBAL_VAR uint8_t *polarAngle GLOBAL_INIT(NULL);
GLOBAL_VAR int8_t *polarDist GLOBAL_INIT(NULL);
GLOBAL_VAR uint8_t upperOpen[MAX_DISPLAY_SIZE];
GLOBAL_VAR uint8_t upperClosed[MAX_DISPLAY_SIZE];
GLOBAL_VAR uint8_t lowerOpen[MAX_DISPLAY_SIZE];
GLOBAL_VAR uint8_t lowerClosed[MAX_DISPLAY_SIZE];
GLOBAL_VAR char *upperEyelidFilename GLOBAL_INIT(NULL);
GLOBAL_VAR char *lowerEyelidFilename GLOBAL_INIT(NULL);
GLOBAL_VAR uint16_t lightSensorMin GLOBAL_INIT(0);
GLOBAL_VAR uint16_t lightSensorMax GLOBAL_INIT(1023);
GLOBAL_VAR float lightSensorCurve GLOBAL_INIT(1.0);
GLOBAL_VAR float irisMin GLOBAL_INIT(0.45);
GLOBAL_VAR float irisRange GLOBAL_INIT(0.35);
GLOBAL_VAR bool tracking GLOBAL_INIT(true);
GLOBAL_VAR float trackFactor GLOBAL_INIT(0.5);
GLOBAL_VAR uint32_t gazeMax GLOBAL_INIT(3000000); // Max wait time (uS) for major eye movements
// 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 eyeTargetY GLOBAL_INIT(0.0); // Range is from -1.0 to +1.0.
// Pin definition stuff will go here
GLOBAL_VAR int8_t lightSensorPin GLOBAL_INIT(-1);
GLOBAL_VAR int8_t blinkPin GLOBAL_INIT(-1); // Manual both-eyes blink pin (-1 = none)
#if defined(ADAFRUIT_MONSTER_M4SK_EXPRESS)
GLOBAL_VAR int8_t boopPin GLOBAL_INIT(A2);
#else
GLOBAL_VAR int8_t boopPin GLOBAL_INIT(-1);
#endif
GLOBAL_VAR uint32_t boopThreshold GLOBAL_INIT(17500);
#if defined(ADAFRUIT_MONSTER_M4SK_EXPRESS)
GLOBAL_VAR bool voiceOn GLOBAL_INIT(false);
GLOBAL_VAR float currentPitch GLOBAL_INIT(1.0);
GLOBAL_VAR float defaultPitch GLOBAL_INIT(1.0);
GLOBAL_VAR float gain GLOBAL_INIT(1.0);
GLOBAL_VAR uint8_t waveform GLOBAL_INIT(0);
GLOBAL_VAR uint32_t modulate GLOBAL_INIT(30); // Dalek pitch
#endif
// EYE-RELATED STRUCTURES --------------------------------------------------
// Eyes are rendered column-at-a-time, using DMA to issue one column of
// data while the next is being calculated, alternating between two column
// structures (there would be barely enough RAM to buffer a whole 240x240
// screen anyway). Each column being rendered/issued makes use of 1 to 3
// linked DMA descriptors, ostensibly containing: 1) background pixels in
// the eyelid area "below" the eye, 2) rendered pixels within the eye
// itself (drawn in the renderBuf[] scanline buffer, allocated for 240
// pixels to match the screen size, though usually only a portion will be
// used, and 3) more background pixels in the eyelid area "above" the eye.
#if NUM_EYES > 1
#define NUM_DESCRIPTORS 1 // See note below
#else
#define NUM_DESCRIPTORS 3
#endif
// IMPORTANT NOTE: original plan (described above, with dynamic descriptor
// list) was FOILED by a silicon bug (documented in the SAMD51 errata)
// when using linked descriptors on multiple channels. The fix, for now,
// is to skip the eyelid optimization and fully buffer/render each line,
// with a single descriptor. This is NOT a problem with a single eye
// (since only one channel) and we can still use the hack for HalloWing M4.
typedef struct {
uint16_t renderBuf[MAX_DISPLAY_SIZE]; // Pixel buffer
DmacDescriptor descriptor[NUM_DESCRIPTORS]; // DMA descriptor list
} columnStruct;
// A simple state machine is used to control eye blinks/winks:
#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 {
uint8_t state; // NOBLINK/ENBLINK/DEBLINK
uint32_t duration; // Duration of blink state (micros)
uint32_t startTime; // Time (micros) of last state change
} eyeBlink;
// Data for iris and sclera texture maps
typedef struct {
char *filename;
float spin; // RPM * 1024.0
uint16_t color;
uint16_t *data;
uint16_t width;
uint16_t height;
uint16_t startAngle; // INITIAL rotation 0-1023 CCW
uint16_t angle; // CURRENT rotation 0-1023 CCW
uint16_t mirror; // 0 = normal, 1023 = flip X axis
uint16_t iSpin; // Per-frame fixed integer spin, overrides 'spin' value
} texture;
// Each eye then uses the following structure. Each eye must be on its own
// SPI bus with distinct control lines (unlike the Uncanny Eyes code where
// they take turns on one bus). Two of the column structures as described
// above, then a lot of DMA nitty-gritty and animation state data.
typedef struct {
// These first values are initialized in the tables below:
const char *name; // For loading per-eye configurables
SPIClass *spi; // Pointer to corresponding SPI object
int8_t cs; // CS pin #
int8_t dc; // DC pin #
int8_t rst; // RST pin # (-1 if using Seesaw)
int8_t winkPin; // Manual eye wink control (-1 = none)
// Remaining values are initialized in code:
columnStruct column[2]; // Alternating column structures A/B
Adafruit_SPITFT *display; // Pointer to display object
DMAbuddy dma; // DMA channel object with fix() function
DmacDescriptor *dptr; // DMA channel descriptor pointer
uint32_t dmaStartTime; // For DMA timeout handler
uint8_t colNum; // Column counter (0-239)
uint8_t colIdx; // Alternating 0/1 index into column[] array
bool dma_busy; // true = DMA transfer in progress
bool column_ready; // true = next column is already rendered
uint16_t pupilColor; // 16-bit 565 RGB, big-endian
uint16_t backColor; // 16-bit 565 RGB, big-endian
texture iris; // iris texture map
texture sclera; // sclera texture map
uint8_t rotation; // Screen rotation (GFX lib)
// Stuff carried over from Uncanny Eyes code. It now needs to be
// independent per-eye because we interleave between drawing the
// two eyes scanline-by-line rather than drawing each eye in full.
// This'll likely get cleaned up a little, but for now...
eyeBlink blink;
float eyeX, eyeY; // Save per-eye to avoid tearing
float pupilFactor; // ditto
float blinkFactor;
float upperLidFactor, lowerLidFactor;
} eyeStruct;
#ifdef INIT_EYESTRUCTS
eyeStruct eye[NUM_EYES] = {
#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 } };
#else
{ NULL , &ARCADA_TFT_SPI, ARCADA_TFT_CS, ARCADA_TFT_DC, ARCADA_TFT_RST, -1 } };
#endif
#else
extern eyeStruct eye[];
#endif
// FUNCTION PROTOTYPES -----------------------------------------------------
// Functions in file.cpp
extern int file_setup(bool msc=true);
extern void handle_filesystem_change();
// This is set true when filesystem contents have changed.
// Set true initially so the program starts with the "changed" task.
extern bool filesystem_change_flag GLOBAL_INIT(true);
extern void loadConfig(char *filename);
extern ImageReturnCode loadEyelid(char *filename, uint8_t *minArray, uint8_t *maxArray, uint8_t init, uint32_t maxRam);
extern ImageReturnCode loadTexture(char *filename, uint16_t **data, uint16_t *width, uint16_t *height, uint32_t maxRam);
// Functions in memory.cpp
extern uint32_t availableRAM(void);
extern uint32_t availableNVM(void);
extern uint8_t *writeDataToFlash(uint8_t *src, uint32_t len);
// Functions in pdmvoice.cpp
#if defined(ADAFRUIT_MONSTER_M4SK_EXPRESS)
extern bool voiceSetup(bool modEnable);
extern float voicePitch(float p);
extern void voiceGain(float g);
extern void voiceMod(uint32_t freq, uint8_t waveform);
extern volatile uint16_t voiceLastReading;
#endif // ADAFRUIT_MONSTER_M4SK_EXPRESS
// Functions in tablegen.cpp
extern void calcDisplacement(void);
extern void calcMap(void);
extern float screen2map(int in);
extern float map2screen(int in);
// Functions in user.cpp
extern void user_setup(void);
extern void user_loop(void);