-
Notifications
You must be signed in to change notification settings - Fork 2
/
globals.h
executable file
·244 lines (218 loc) · 10.7 KB
/
globals.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
// SPDX-FileCopyrightText: 2019 Phillip Burgess for Adafruit Industries
//
// SPDX-License-Identifier: MIT
//34567890123456789012345678901234567890123456789012345678901234567890123456
#include <hardware/dma.h>
#include "Adafruit_Arcada.h"
#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(false); // 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.9);
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
} 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
spi_inst_t *rp2040_spi; // Pointer to RP2040 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
uint dma_channel; // DMA channel
dma_channel_config dma_config; // DMA config
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;
uint32_t frames;
uint32_t lastFrameRateReportTime;
} eyeStruct;
#ifdef INIT_EYESTRUCTS
eyeStruct eye[NUM_EYES] = {
#if (NUM_EYES > 1)
// name spi cs dc rst wink
{ "right", &ARCADA_TFT_SPI, ARCADA_TFT_RP2040_SPI, ARCADA_TFT_CS, ARCADA_TFT_DC, ARCADA_TFT_RST, -1 },
{ "left", &ARCADA_LEFTTFT_SPI, ARCADA_LEFTTFT_RP2040_SPI, ARCADA_LEFTTFT_CS, ARCADA_LEFTTFT_DC, ARCADA_LEFTTFT_RST, -1 }
};
#else
{ NULL, &ARCADA_TFT_SPI, ARCADA_TFT_RP2040_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);