5
5
// OOZE MASTER 3000: NeoPixel simulated liquid physics. Up to 7 NeoPixel
6
6
// strands dribble light, while an 8th strand "catches the drips."
7
7
// Designed for the Adafruit Feather M0 or M4 with matching version of
8
- // NeoPXL8 FeatherWing. This can be adapted for other M0 or M4 boards but
9
- // you will need to do your own "pin sudoku" and level shifting
10
- // (e.g. NeoPXL8 Friend breakout or similar ).
8
+ // NeoPXL8 FeatherWing, or for RP2040 boards including SCORPIO. This can be
9
+ // adapted for other M0, M4, RP2040 or ESP32-S3 boards but you will need to
10
+ // do your own "pin sudoku" & level shifting (e.g. NeoPXL8 Friend breakout).
11
11
// See here: https://learn.adafruit.com/adafruit-neopxl8-featherwing-and-library
12
12
// Requires Adafruit_NeoPixel, Adafruit_NeoPXL8 and Adafruit_ZeroDMA libraries.
13
13
14
14
#include < Adafruit_NeoPXL8.h>
15
15
16
- uint8_t dripColor[] = { 0 , 255 , 0 }; // Bright green ectoplasm
17
- #define PIXEL_PITCH ( 1.0 / 150.0 ) // 150 pixels/m
18
- #define ICE_BRIGHTNESS 0 // Icycle effect Brightness (0 to <100% )
16
+ # define PIXEL_PITCH ( 1.0 / 150.0 ) // 150 pixels/m
17
+ #define ICE_BRIGHTNESS 0 // Icycle effect Brightness (0 to <100%)
18
+ #define COLOR_ORDER NEO_GRB // NeoPixel color format (see Adafruit_NeoPixel )
19
19
20
- #define GAMMA 2.6
20
+ #define GAMMA 2.6 // For linear brightness correction
21
21
#define G_CONST 9.806 // Standard acceleration due to gravity
22
22
// While the above G_CONST is correct for "real time" drips, you can dial it back
23
23
// for a more theatric effect / to slow down the drips like they've still got a
24
24
// syrupy "drool string" attached (try much lower values like 2.0 to 3.0).
25
25
26
- // NeoPXL8 pin numbers (these are default connections on NeoPXL8 M0 FeatherWing)
26
+ // NeoPXL8 pin numbers
27
+ #if defined(ARDUINO_ADAFRUIT_FEATHER_RP2040_SCORPIO)
28
+ #define USE_HDR // RP2040 has enough "oomph" for HDR color!
29
+ int8_t pins[8 ] = { 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 };
30
+ #else
31
+ // These are default connections on NeoPXL8 M0 FeatherWing:
27
32
int8_t pins[8 ] = { PIN_SERIAL1_RX, PIN_SERIAL1_TX, MISO, 13 , 5 , SDA, A4, A3 };
28
-
29
33
// If using an M4 Feather & NeoPXL8 FeatherWing, use these values instead:
30
34
// int8_t pins[8] = { 13, 12, 11, 10, SCK, 5, 9, 6 };
31
-
35
+ # endif
32
36
33
37
typedef enum {
34
38
MODE_IDLE,
@@ -38,37 +42,88 @@ typedef enum {
38
42
MODE_DRIPPING
39
43
} dropState;
40
44
45
+ // A color palette allows one to "theme" a project. By default there's just
46
+ // one color, and all drips use only that. Setting up a color list, and then
47
+ // declaring a range of indices in the drip[] table later, allows some
48
+ // randomization while still keeping appearance within a predictable range.
49
+ // Each drip could be its own fixed color, or each could be randomly picked
50
+ // from a set of colors. Explained further in Adafruit Learning System guide.
51
+ // Q: WHY NOT JUST PICK RANDOM RGB COLORS?
52
+ // Because that would pick a lot of ugly or too-dark RGB combinations.
53
+ // WHY NOT RANDOM FULL-BRIGHTNESS HUES FROM THE ColorHSV() FUNCTION?
54
+ // Two reasons: First, to apply a consistent color theme to a project;
55
+ // Halloween, Christmas, fire, water, etc. Second, because NeoPixels
56
+ // have been around for over a decade and it's time we mature past the
57
+ // Lisa Frank stage of all-rainbows-all-the-time and consider matters of
58
+ // taste and restraint. If you WANT all rainbows, that's still entirely
59
+ // possile just by setting up a palette of bright colors!
60
+ uint8_t palette[][3 ] = {
61
+ { 0 , 255 , 0 }, // Bright green ectoplasm
62
+ };
63
+ #define NUM_COLORS (sizeof palette / sizeof palette[0 ])
64
+ // Note that color randomization does not pair well with the ICE_BRIGHTNESS
65
+ // effect; you'll probably want to pick one or the other: random colors
66
+ // (from palette) and no icicles, or fixed color (per strand or overall)
67
+ // with ice. Otherwise the color jump of the icicle looks bad and wrong.
68
+
69
+ // Optional "Carrie mode" -- if a pin is defined here, and a switch or button
70
+ // added between this pin and ground -- when active, each new drip is drawn
71
+ // using the last color in the palette table (and slowly returns to original
72
+ // color scheme when released). i.e. there might normally be pleasant wintry
73
+ // colors in the palette, then plop pure red at the end of the list and watch
74
+ // the fun unfold!
75
+ // #define CARRIE_PIN A2
76
+ // If you could use an extra ground pin for that, define that here; this
77
+ // is a signal ground only, for the switch, NOT for powering anything.
78
+ // #define CARRIE_GROUND A3
79
+
41
80
struct {
42
81
uint16_t length; // Length of NeoPixel strip IN PIXELS
43
82
uint16_t dribblePixel; // Index of pixel where dribble pauses before drop (0 to length-1)
44
83
float height; // Height IN METERS of dribblePixel above ground
84
+ uint16_t palette_min; // Lower color palette index for this strip
85
+ uint16_t palette_max; // Upper color palette index for this strip
45
86
dropState mode; // One of the above states (MODE_IDLE, etc.)
46
87
uint32_t eventStartUsec; // Starting time of current event
47
88
uint32_t eventDurationUsec; // Duration of current event, in microseconds
48
89
float eventDurationReal; // Duration of current event, in seconds (float)
49
90
uint32_t splatStartUsec; // Starting time of most recent "splat"
50
91
uint32_t splatDurationUsec; // Fade duration of splat
51
92
float pos; // Position of drip on prior frame
93
+ uint8_t color[3 ]; // RGB color (randomly picked from from palette[])
94
+ uint8_t splatColor[3 ]; // RGB color of "splat" (may be from prior drip)
52
95
} drip[] = {
53
96
// THIS TABLE CONTAINS INFO FOR UP TO 8 NEOPIXEL DRIPS
54
- { 16 , 7 , 0.157 }, // NeoPXL8 output 0: 16 pixels long, drip pauses at index 7, 0.157 meters above ground
55
- { 19 , 6 , 0.174 }, // NeoPXL8 output 1: 19 pixels long, pause at index 6, 0.174 meters up
56
- { 18 , 5 , 0.195 }, // NeoPXL8 output 2: etc.
57
- { 17 , 6 , 0.16 }, // NeoPXL8 output 3
58
- { 16 , 1 , 0.21 }, // NeoPXL8 output 4
59
- { 16 , 1 , 0.21 }, // NeoPXL8 output 5
60
- { 21 , 10 , 0.143 }, // NeoPXL8 output 6
97
+ { 16 , 7 , 0.157 , 0 , 0 }, // NeoPXL8 output 0: 16 pixels long, drip pauses at index 7, 0.157 meters above ground, use palette colors 0-0
98
+ { 19 , 6 , 0.174 , 0 , 0 }, // NeoPXL8 output 1: 19 pixels long, pause at index 6, 0.174 meters up
99
+ { 18 , 5 , 0.195 , 0 , 0 }, // NeoPXL8 output 2: etc.
100
+ { 17 , 6 , 0.16 , 0 , 0 }, // NeoPXL8 output 3
101
+ { 16 , 1 , 0.21 , 0 , 0 }, // NeoPXL8 output 4
102
+ { 16 , 1 , 0.21 , 0 , 0 }, // NeoPXL8 output 5
103
+ { 21 , 10 , 0.143 , 0 , 0 }, // NeoPXL8 output 6
61
104
// NeoPXL8 output 7 is normally reserved for ground splats
62
105
// You CAN add an eighth drip here, but then will not get splats
63
106
};
64
107
65
- #define N_DRIPS (sizeof drip / sizeof drip[0 ])
66
- int longestStrand = (N_DRIPS < 8 ) ? N_DRIPS : 0 ;
67
- Adafruit_NeoPXL8 *pixels;
108
+ #ifdef USE_HDR
109
+ Adafruit_NeoPXL8HDR *pixels = NULL ;
110
+ #else
111
+ Adafruit_NeoPXL8 *pixels = NULL ;
112
+ #endif
113
+ #define N_DRIPS (sizeof drip / sizeof drip[0 ])
114
+ int longestStrand = (N_DRIPS < 8 ) ? N_DRIPS : 0 ;
68
115
69
116
void setup () {
70
117
Serial.begin (9600 );
71
- randomSeed (analogRead (A0) + analogRead (A5));
118
+ randomSeed (analogRead (A0) + analogRead (A1));
119
+
120
+ #ifdef CARRIE_PIN
121
+ pinMode (CARRIE_PIN, INPUT_PULLUP);
122
+ #endif
123
+ #ifdef CARRIE_GROUND
124
+ pinMode (CARRIE_GROUND, OUTPUT);
125
+ digitalWrite (CARRIE_GROUND, LOW);
126
+ #endif
72
127
73
128
for (int i=0 ; i<N_DRIPS; i++) {
74
129
drip[i].mode = MODE_IDLE; // Start all drips in idle mode
@@ -78,16 +133,35 @@ void setup() {
78
133
drip[i].splatStartUsec = 0 ;
79
134
drip[i].splatDurationUsec = 0 ;
80
135
if (drip[i].length > longestStrand) longestStrand = drip[i].length ;
136
+ // Randomize initial color:
137
+ memcpy (drip[i].color , palette[random (drip[i].palette_min , drip[i].palette_max + 1 )], sizeof palette[0 ]);
138
+ memcpy (drip[i].splatColor , drip[i].color , sizeof palette[0 ]);
139
+ #ifdef CARRIE_PIN
140
+ // If "Carrie" switch is on, override above color with last palette entry
141
+ if (!digitalRead (CARRIE_PIN)) {
142
+ memcpy (drip[i].color , palette[NUM_COLORS - 1 ], sizeof palette[0 ]);
143
+ }
144
+ #endif
81
145
}
82
146
83
- pixels = new Adafruit_NeoPXL8 (longestStrand, pins, NEO_GRB);
147
+ #ifdef USE_HDR
148
+ pixels = new Adafruit_NeoPXL8HDR (longestStrand, pins, COLOR_ORDER);
149
+ if (!pixels->begin (true , 4 , true )) {
150
+ // HDR requires inordinate RAM! Blink onboard LED if there's trouble:
151
+ pinMode (LED_BUILTIN, OUTPUT);
152
+ for (;;) digitalWrite (LED_BUILTIN, (millis () / 500 ) & 1 );
153
+ }
154
+ pixels->setBrightness (65535 , GAMMA); // NeoPXL8HDR handles gamma correction
155
+ #else
156
+ pixels = new Adafruit_NeoPXL8 (longestStrand, pins, COLOR_ORDER);
84
157
pixels->begin ();
158
+ #endif
85
159
}
86
160
87
161
void loop () {
88
162
uint32_t t = micros (); // Current time, in microseconds
89
163
90
- float x; // multipurpose interim result
164
+ float x = 0.0 ; // multipurpose interim result
91
165
pixels->clear ();
92
166
93
167
for (int i=0 ; i<N_DRIPS; i++) {
@@ -104,6 +178,14 @@ void loop() {
104
178
drip[i].mode = MODE_OOZING; // Idle to oozing transition
105
179
drip[i].eventDurationUsec = random (800000 , 1200000 ); // 0.8 to 1.2 sec ooze
106
180
drip[i].eventDurationReal = (float )drip[i].eventDurationUsec / 1000000.0 ;
181
+ // Randomize next drip color from palette settings:
182
+ memcpy (drip[i].color , palette[random (drip[i].palette_min , drip[i].palette_max + 1 )], sizeof palette[0 ]);
183
+ #ifdef CARRIE_PIN
184
+ // If "Carrie" switch is on, override color with last palette entry
185
+ if (!digitalRead (CARRIE_PIN)) {
186
+ memcpy (drip[i].color , palette[NUM_COLORS - 1 ], sizeof palette[0 ]);
187
+ }
188
+ #endif
107
189
break ;
108
190
case MODE_OOZING:
109
191
if (drip[i].dribblePixel ) { // If dribblePixel is nonzero...
@@ -135,16 +217,17 @@ void loop() {
135
217
drip[i].eventDurationReal = (float )drip[i].eventDurationUsec / 1000000.0 ;
136
218
drip[i].splatStartUsec = drip[i].eventStartUsec ; // Splat starts now!
137
219
drip[i].splatDurationUsec = random (900000 , 1100000 );
220
+ memcpy (drip[i].splatColor , drip[i].color , sizeof palette[0 ]); // Save color for splat
138
221
break ;
139
222
}
140
223
}
141
224
142
225
// Render drip state to NeoPixels...
143
226
#if ICE_BRIGHTNESS > 0
144
227
// Draw icycles if ICE_BRIGHTNESS is set
145
- x = pow (( float )ICE_BRIGHTNESS * 0.01 , GAMMA) ;
228
+ x = ( float )ICE_BRIGHTNESS * 0.01 ;
146
229
for (int d=0 ; d<=drip[i].dribblePixel ; d++) {
147
- set (i, d, x);
230
+ set (i, i, d, x);
148
231
}
149
232
#endif
150
233
switch (drip[i].mode ) {
@@ -158,8 +241,7 @@ void loop() {
158
241
x = ((float )ICE_BRIGHTNESS * 0.01 ) +
159
242
x * (float )(100 - ICE_BRIGHTNESS) * 0.01 ;
160
243
#endif
161
- x = pow (x, GAMMA);
162
- set (i, 0 , x);
244
+ set (i, i, 0 , x);
163
245
break ;
164
246
case MODE_DRIBBLING_1:
165
247
// Point b moves from first to second pixel over event time
@@ -185,8 +267,7 @@ void loop() {
185
267
dtUsec = t - drip[i].splatStartUsec ; // Elapsed time, in microseconds, since start of splat
186
268
if (dtUsec < drip[i].splatDurationUsec ) {
187
269
x = 1.0 - sqrt ((float )dtUsec / (float )drip[i].splatDurationUsec );
188
- x = pow (x, GAMMA);
189
- set (7 , i, x);
270
+ set (7 , i, i, x);
190
271
}
191
272
}
192
273
}
@@ -235,15 +316,68 @@ void dripDraw(uint8_t dNum, float a, float b, bool fade) {
235
316
x * (float )(100 - ICE_BRIGHTNESS) * 0.01 ;
236
317
}
237
318
#endif
238
- x = pow (x, GAMMA);
239
- set (dNum, i, x);
319
+ set (dNum, dNum, i, x);
240
320
}
241
321
}
242
322
243
- // Set one pixel to a given brightness level (0.0 to 1.0)
244
- void set (uint8_t strand, uint8_t pixel, float brightness) {
245
- pixels->setPixelColor (pixel + strand * longestStrand,
246
- (int )((float )dripColor[0 ] * brightness + 0.5 ),
247
- (int )((float )dripColor[1 ] * brightness + 0.5 ),
248
- (int )((float )dripColor[2 ] * brightness + 0.5 ));
323
+ // Set one pixel to a given brightness level (0.0 to 1.0).
324
+ // Strand # and drip # are BOTH passed in because "splats" are always
325
+ // on drip 7 but colors come from drip indices.
326
+ void set (uint8_t strand, uint8_t d, uint8_t pixel, float brightness) {
327
+ #if !defined(USE_HDR) // NeoPXL8HDR does its own gamma correction, else...
328
+ brightness = pow (brightness, GAMMA);
329
+ #endif
330
+ if ((strand < 7 ) || (N_DRIPS >= 8 )) {
331
+ pixels->setPixelColor (pixel + strand * longestStrand,
332
+ (int )((float )drip[d].color [0 ] * brightness + 0.5 ),
333
+ (int )((float )drip[d].color [1 ] * brightness + 0.5 ),
334
+ (int )((float )drip[d].color [2 ] * brightness + 0.5 ));
335
+ } else {
336
+ pixels->setPixelColor (pixel + strand * longestStrand,
337
+ (int )((float )drip[d].splatColor [0 ] * brightness + 0.5 ),
338
+ (int )((float )drip[d].splatColor [1 ] * brightness + 0.5 ),
339
+ (int )((float )drip[d].splatColor [2 ] * brightness + 0.5 ));
340
+ }
249
341
}
342
+
343
+ // NeoPXL8HDR requires some background processing in a second thread.
344
+ // See NeoPXL8 library examples (NeoPXL8HDR/strandtest) for explanation.
345
+ // Currently this sketch only enables HDR if using Feather SCORPIO,
346
+ // but it could be useful for other RP2040s and for ESP32S3too.
347
+ #ifdef USE_HDR
348
+
349
+ #if defined(ARDUINO_ARCH_RP2040)
350
+
351
+ void loop1 () {
352
+ if (pixels) pixels->refresh ();
353
+ }
354
+
355
+ void setup1 () {
356
+ }
357
+
358
+ #elif defined(CONFIG_IDF_TARGET_ESP32S3)
359
+
360
+ void loop0 (void *param) {
361
+ for (;;) {
362
+ yield ();
363
+ if (pixels) pixels->refresh ();
364
+ }
365
+ }
366
+
367
+ #else // SAMD
368
+
369
+ #include " Adafruit_ZeroTimer.h"
370
+
371
+ Adafruit_ZeroTimer zerotimer = Adafruit_ZeroTimer(3 );
372
+
373
+ void TC3_Handler () {
374
+ Adafruit_ZeroTimer::timerHandler (3 );
375
+ }
376
+
377
+ void timerCallback (void ) {
378
+ if (pixels) pixels->refresh ();
379
+ }
380
+
381
+ #endif // end SAMD
382
+
383
+ #endif // end USE_HDR
0 commit comments