Skip to content

Commit 47b700e

Browse files
Merge pull request #801 from adafruit/philb-branch
Add M4_Eyes
2 parents bf2efef + a81414b commit 47b700e

11 files changed

Lines changed: 1860 additions & 0 deletions

File tree

M4_Eyes/DMAbuddy.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Very infrequently the jobStatus member in Adafruit_ZeroDMA gets out of
2+
// sync and the screen DMA updates lock up. This is a hacky workaround.
3+
// jobStatus is a protected member of Adafruit_ZeroDMA, we can't reset it
4+
// directly in the sketch, but a subclass can. So we have this minimal
5+
// subclass with a DMA-channel-toggle-off-on-and-jobStatus-reset function.
6+
7+
class DMAbuddy : public Adafruit_ZeroDMA {
8+
public:
9+
// Call this function when a DMA stall is detected:
10+
void fix(void) {
11+
// We literally just switch the channel off and on again to fix it...
12+
DMAC->Channel[channel].CHCTRLA.bit.ENABLE = 0; // Disable channel
13+
DMAC->Channel[channel].CHCTRLA.bit.ENABLE = 1; // Enable channel
14+
jobStatus = DMA_STATUS_OK; // Back in business!
15+
}
16+
};

M4_Eyes/M4_Eyes.ino

Lines changed: 808 additions & 0 deletions
Large diffs are not rendered by default.

M4_Eyes/eyes/hazel/config.eye

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"boopThreshold" : 17500, // lower = more sensitive
3+
"eyeRadius" : 125,
4+
"eyelidIndex" : "0x00", // From table: learn.adafruit.com/assets/61921
5+
"pupilColor" : [ 0, 0, 0 ],
6+
"backColor" : [ 140, 40, 20 ],
7+
"irisTexture" : "hazel/iris.bmp",
8+
"scleraTexture" : "hazel/sclera.bmp",
9+
"upperEyelid" : "hazel/upper.bmp",
10+
"lowerEyelid" : "hazel/lower.bmp",
11+
"left" : {
12+
},
13+
"right" : {
14+
}
15+
}

M4_Eyes/eyes/hazel/iris.bmp

192 KB
Binary file not shown.

M4_Eyes/eyes/hazel/lower.bmp

7.56 KB
Binary file not shown.

M4_Eyes/eyes/hazel/sclera.bmp

234 KB
Binary file not shown.

M4_Eyes/eyes/hazel/upper.bmp

7.56 KB
Binary file not shown.

M4_Eyes/file.cpp

Lines changed: 493 additions & 0 deletions
Large diffs are not rendered by default.

M4_Eyes/globals.h

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
//34567890123456789012345678901234567890123456789012345678901234567890123456
2+
3+
#include <Adafruit_GFX.h> // Core graphics for Adafruit displays
4+
#include <Adafruit_ST7789.h> // TFT-specific display library
5+
#include <Adafruit_ZeroDMA.h> // SAMD-specific DMA library
6+
#include <Adafruit_ImageReader.h> // ImageReturnCode type
7+
#include "DMAbuddy.h" // DMA-bug-workaround class
8+
9+
#if defined(GLOBAL_VAR) // #defined in .ino file ONLY!
10+
#define GLOBAL_INIT(X) = (X)
11+
#define INIT_EYESTRUCTS
12+
#else
13+
#define GLOBAL_VAR extern
14+
#define GLOBAL_INIT(X)
15+
#endif
16+
17+
#if defined(ADAFRUIT_MONSTER_M4SK_EXPRESS)
18+
#define NUM_EYES 2
19+
#include <Adafruit_seesaw.h>
20+
GLOBAL_VAR Adafruit_seesaw seesaw; // Controls some left-eye signals
21+
#define SEESAW_TFT_RESET_PIN 8 // Left eye TFT reset
22+
#define SEESAW_BACKLIGHT_PIN 5 // Left eye TFT backlight
23+
#define BACKLIGHT_PIN 21 // Right eye TFT backlight
24+
#define LIGHTSENSOR_PIN 2
25+
// Light sensor is not active by default. Use "lightSensor : 102" in config
26+
#else
27+
#define NUM_EYES 1
28+
#define BACKLIGHT_PIN 47
29+
#endif
30+
31+
// GLOBAL VARIABLES --------------------------------------------------------
32+
33+
GLOBAL_VAR uint32_t stackReserve GLOBAL_INIT(8192); // See image-loading code
34+
GLOBAL_VAR int eyeRadius GLOBAL_INIT(0); // 0 = Use default in loadConfig()
35+
GLOBAL_VAR int eyeDiameter; // Calculated from eyeRadius later
36+
GLOBAL_VAR int irisRadius GLOBAL_INIT(60); // Approx size in screen pixels
37+
GLOBAL_VAR int slitPupilRadius GLOBAL_INIT(0); // 0 = round pupil
38+
GLOBAL_VAR uint8_t eyelidIndex GLOBAL_INIT(0x00); // From table: learn.adafruit.com/assets/61921
39+
GLOBAL_VAR uint16_t eyelidColor GLOBAL_INIT(0x0000); // Expand eyelidIndex to 16-bit
40+
// mapRadius is the size of one quadrant of the polar-to-rectangular map,
41+
// in pixels. To cover the front hemisphere of the eye, this should be a
42+
// minimum of (eyeRadius * Pi / 2) -- but, to provide some coverage beyond
43+
// just the front hemisphere, the value of 'coverage' determines how far
44+
// this map wraps around the eye. 0.0 = no coverage, 0.5 = front hemisphere,
45+
// 1.0 = full sphere. Do not bother making this 1.0 -- the far back side of
46+
// the eye is never actually seen, since we're using a displacement map hack
47+
// and not actually rotating a sphere, plus the resulting map would take a
48+
// TON of RAM, probably more than we have. The default here, 0.6, provides
49+
// a good balance between coverage and RAM, only occasionally will you see
50+
// a crescent of back-of-eye color (and the sclera texture map can be
51+
// designed to blend into it). eyeRadius is calculated in loadConfig() as
52+
// eyeRadius * Pi * coverage -- if eyeRadius is 125 and coverage is 0.6,
53+
// mapRadius will be 236 pixels, and the resulting polar angle/dist maps
54+
// will total about 111K RAM.
55+
GLOBAL_VAR float coverage GLOBAL_INIT(0.6);
56+
GLOBAL_VAR int mapRadius; // calculated in loadConfig()
57+
GLOBAL_VAR int mapDiameter; // calculated in loadConfig()
58+
GLOBAL_VAR uint8_t *displace GLOBAL_INIT(NULL);
59+
GLOBAL_VAR uint8_t *polarAngle GLOBAL_INIT(NULL);
60+
GLOBAL_VAR int8_t *polarDist GLOBAL_INIT(NULL);
61+
GLOBAL_VAR uint8_t upperOpen[240];
62+
GLOBAL_VAR uint8_t upperClosed[240];
63+
GLOBAL_VAR uint8_t lowerOpen[240];
64+
GLOBAL_VAR uint8_t lowerClosed[240];
65+
GLOBAL_VAR char *upperEyelidFilename GLOBAL_INIT(NULL);
66+
GLOBAL_VAR char *lowerEyelidFilename GLOBAL_INIT(NULL);
67+
GLOBAL_VAR uint16_t lightSensorMin GLOBAL_INIT(0);
68+
GLOBAL_VAR uint16_t lightSensorMax GLOBAL_INIT(1023);
69+
GLOBAL_VAR float lightSensorCurve GLOBAL_INIT(1.0);
70+
GLOBAL_VAR float irisMin GLOBAL_INIT(0.45);
71+
GLOBAL_VAR float irisRange GLOBAL_INIT(0.35);
72+
73+
// Pin definition stuff will go here
74+
75+
GLOBAL_VAR int8_t lightSensorPin GLOBAL_INIT(-1);
76+
GLOBAL_VAR int8_t blinkPin GLOBAL_INIT(-1); // Manual both-eyes blink pin (-1 = none)
77+
78+
#if defined(ADAFRUIT_MONSTER_M4SK_EXPRESS)
79+
GLOBAL_VAR int8_t boopPin GLOBAL_INIT(A2);
80+
#else
81+
GLOBAL_VAR int8_t boopPin GLOBAL_INIT(-1);
82+
#endif
83+
GLOBAL_VAR uint32_t boopThreshold GLOBAL_INIT(17500);
84+
85+
86+
// EYE-RELATED STRUCTURES --------------------------------------------------
87+
88+
// Eyes are rendered column-at-a-time, using DMA to issue one column of
89+
// data while the next is being calculated, alternating between two column
90+
// structures (there would be barely enough RAM to buffer a whole 240x240
91+
// screen anyway). Each column being rendered/issued makes use of 1 to 3
92+
// linked DMA descriptors, ostensibly containing: 1) background pixels in
93+
// the eyelid area "below" the eye, 2) rendered pixels within the eye
94+
// itself (drawn in the renderBuf[] scanline buffer, allocated for 240
95+
// pixels to match the screen size, though usually only a portion will be
96+
// used, and 3) more background pixels in the eyelid area "above" the eye.
97+
#if NUM_EYES > 1
98+
#define NUM_DESCRIPTORS 1 // See note below
99+
#else
100+
#define NUM_DESCRIPTORS 3
101+
#endif
102+
// IMPORTANT NOTE: original plan (described above, with dynamic descriptor
103+
// list) was FOILED by a silicon bug (documented in the SAMD51 errata)
104+
// when using linked descriptors on multiple channels. The fix, for now,
105+
// is to skip the eyelid optimization and fully buffer/render each line,
106+
// with a single descriptor. This is NOT a problem with a single eye
107+
// (since only one channel) and we can still use the hack for HalloWing M4.
108+
typedef struct {
109+
uint16_t renderBuf[240]; // Pixel buffer
110+
DmacDescriptor descriptor[NUM_DESCRIPTORS]; // DMA descriptor list
111+
} columnStruct;
112+
113+
// A simple state machine is used to control eye blinks/winks:
114+
#define NOBLINK 0 // Not currently engaged in a blink
115+
#define ENBLINK 1 // Eyelid is currently closing
116+
#define DEBLINK 2 // Eyelid is currently opening
117+
typedef struct {
118+
uint8_t state; // NOBLINK/ENBLINK/DEBLINK
119+
uint32_t duration; // Duration of blink state (micros)
120+
uint32_t startTime; // Time (micros) of last state change
121+
} eyeBlink;
122+
123+
// Data for iris and sclera texture maps
124+
typedef struct {
125+
char *filename;
126+
float spin; // RPM * 1024.0
127+
uint16_t color;
128+
uint16_t *data;
129+
uint16_t width;
130+
uint16_t height;
131+
uint16_t startAngle; // INITIAL rotation 0-1023 CCW
132+
uint16_t angle; // CURRENT rotation 0-1023 CCW
133+
uint16_t mirror; // 0 = normal, 1023 = flip X axis
134+
} texture;
135+
136+
// Each eye then uses the following structure. Each eye must be on its own
137+
// SPI bus with distinct control lines (unlike the Uncanny Eyes code where
138+
// they take turns on one bus). Two of the column structures as described
139+
// above, then a lot of DMA nitty-gritty and animation state data.
140+
typedef struct {
141+
// These first values are initialized in the tables below:
142+
const char *name; // For loading per-eye configurables
143+
SPIClass *spi; // Pointer to corresponding SPI object
144+
int8_t cs; // CS pin #
145+
int8_t dc; // DC pin #
146+
int8_t rst; // RST pin # (-1 if using Seesaw)
147+
int8_t winkPin; // Manual eye wink control (-1 = none)
148+
// Remaining values are initialized in code:
149+
columnStruct column[2]; // Alternating column structures A/B
150+
Adafruit_ST7789 *display; // Pointer to display object
151+
DMAbuddy dma; // DMA channel object with fix() function
152+
DmacDescriptor *dptr; // DMA channel descriptor pointer
153+
uint32_t dmaStartTime; // For DMA timeout handler
154+
uint8_t colNum; // Column counter (0-239)
155+
uint8_t colIdx; // Alternating 0/1 index into column[] array
156+
bool dma_busy; // true = DMA transfer in progress
157+
bool column_ready; // true = next column is already rendered
158+
uint16_t pupilColor; // 16-bit 565 RGB, big-endian
159+
uint16_t backColor; // 16-bit 565 RGB, big-endian
160+
texture iris; // iris texture map
161+
texture sclera; // sclera texture map
162+
163+
// Stuff carried over from Uncanny Eyes code. It now needs to be
164+
// independent per-eye because we interleave between drawing the
165+
// two eyes scanline-by-line rather than drawing each eye in full.
166+
// This'll likely get cleaned up a little, but for now...
167+
eyeBlink blink;
168+
float eyeX, eyeY; // Save per-eye to avoid tearing
169+
float pupilFactor; // ditto
170+
float blinkFactor;
171+
float upperLidFactor, lowerLidFactor;
172+
} eyeStruct;
173+
174+
#ifdef INIT_EYESTRUCTS
175+
eyeStruct eye[NUM_EYES] = {
176+
#if defined(ADAFRUIT_MONSTER_M4SK_EXPRESS)
177+
// name spi cs dc rst wink
178+
{ "right", &SPI , 5, 6, 4, -1 },
179+
{ "left" , &SPI1, 9, 10, -1, -1 } };
180+
#elif defined(ADAFRUIT_HALLOWING_M4_EXPRESS)
181+
{ NULL , &SPI1, 44, 45, 46, -1 } };
182+
#else
183+
#error "This project supports Adafruit MONSTER M4SK and HALLOWING M4 only"
184+
#endif
185+
#else
186+
extern eyeStruct eye[];
187+
#endif
188+
189+
// FUNCTION PROTOTYPES -----------------------------------------------------
190+
191+
// Functions in file.cpp
192+
extern int file_setup(bool msc=true);
193+
extern void handle_filesystem_change();
194+
// This is set true when filesystem contents have changed.
195+
// Set true initially so the program starts with the "changed" task.
196+
extern bool filesystem_change_flag GLOBAL_INIT(true);
197+
extern void loadConfig(char *filename);
198+
extern ImageReturnCode loadEyelid(char *filename, uint8_t *minArray, uint8_t *maxArray, uint8_t init, uint32_t maxRam);
199+
extern ImageReturnCode loadTexture(char *filename, uint16_t **data, uint16_t *width, uint16_t *height, uint32_t maxRam);
200+
201+
// Functions in memory.cpp
202+
extern uint32_t availableRAM(void);
203+
extern uint32_t availableNVM(void);
204+
extern uint8_t *writeDataToFlash(uint8_t *src, uint32_t len);
205+
206+
// Functions in tablegen.cpp
207+
extern void calcDisplacement(void);
208+
extern void calcMap(void);
209+
extern float screen2map(int in);
210+
extern float map2screen(int in);

0 commit comments

Comments
 (0)