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,68 @@ 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
+ // Note that color randomization does not pair well with the ICE_BRIGHTNESS
64
+ // effect; you'll probably want to pick one or the other: random colors
65
+ // (from palette) and no icicles, or fixed color (per strand or overall)
66
+ // with ice. Otherwise the color jump of the icicle looks bad and wrong.
67
+
41
68
struct {
42
69
uint16_t length; // Length of NeoPixel strip IN PIXELS
43
70
uint16_t dribblePixel; // Index of pixel where dribble pauses before drop (0 to length-1)
44
71
float height; // Height IN METERS of dribblePixel above ground
72
+ uint16_t palette_min; // Lower color palette index for this strip
73
+ uint16_t palette_max; // Upper color palette index for this strip
45
74
dropState mode; // One of the above states (MODE_IDLE, etc.)
46
75
uint32_t eventStartUsec; // Starting time of current event
47
76
uint32_t eventDurationUsec; // Duration of current event, in microseconds
48
77
float eventDurationReal; // Duration of current event, in seconds (float)
49
78
uint32_t splatStartUsec; // Starting time of most recent "splat"
50
79
uint32_t splatDurationUsec; // Fade duration of splat
51
80
float pos; // Position of drip on prior frame
81
+ uint8_t color[3 ]; // RGB color (randomly picked from from palette[])
82
+ uint8_t splatColor[3 ]; // RGB color of "splat" (may be from prior drip)
52
83
} drip[] = {
53
84
// 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
85
+ { 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
86
+ { 19 , 6 , 0.174 , 0 , 0 }, // NeoPXL8 output 1: 19 pixels long, pause at index 6, 0.174 meters up
87
+ { 18 , 5 , 0.195 , 0 , 0 }, // NeoPXL8 output 2: etc.
88
+ { 17 , 6 , 0.16 , 0 , 0 }, // NeoPXL8 output 3
89
+ { 16 , 1 , 0.21 , 0 , 0 }, // NeoPXL8 output 4
90
+ { 16 , 1 , 0.21 , 0 , 0 }, // NeoPXL8 output 5
91
+ { 21 , 10 , 0.143 , 0 , 0 }, // NeoPXL8 output 6
61
92
// NeoPXL8 output 7 is normally reserved for ground splats
62
93
// You CAN add an eighth drip here, but then will not get splats
63
94
};
64
95
65
- #define N_DRIPS (sizeof drip / sizeof drip[0 ])
66
- int longestStrand = (N_DRIPS < 8 ) ? N_DRIPS : 0 ;
67
- Adafruit_NeoPXL8 *pixels;
96
+ #ifdef USE_HDR
97
+ Adafruit_NeoPXL8HDR *pixels = NULL ;
98
+ #else
99
+ Adafruit_NeoPXL8 *pixels = NULL ;
100
+ #endif
101
+ #define N_DRIPS (sizeof drip / sizeof drip[0 ])
102
+ int longestStrand = (N_DRIPS < 8 ) ? N_DRIPS : 0 ;
68
103
69
104
void setup () {
70
105
Serial.begin (9600 );
71
- randomSeed (analogRead (A0) + analogRead (A5 ));
106
+ randomSeed (analogRead (A0) + analogRead (A3 ));
72
107
73
108
for (int i=0 ; i<N_DRIPS; i++) {
74
109
drip[i].mode = MODE_IDLE; // Start all drips in idle mode
@@ -78,16 +113,29 @@ void setup() {
78
113
drip[i].splatStartUsec = 0 ;
79
114
drip[i].splatDurationUsec = 0 ;
80
115
if (drip[i].length > longestStrand) longestStrand = drip[i].length ;
116
+ // Randomize initial color:
117
+ memcpy (drip[i].color , palette[random (drip[i].palette_min , drip[i].palette_max + 1 )], sizeof palette[0 ]);
118
+ memcpy (drip[i].splatColor , drip[i].color , sizeof palette[0 ]);
81
119
}
82
120
83
- pixels = new Adafruit_NeoPXL8 (longestStrand, pins, NEO_GRB);
121
+ #ifdef USE_HDR
122
+ pixels = new Adafruit_NeoPXL8HDR (longestStrand, pins, COLOR_ORDER);
123
+ if (!pixels->begin (true , 4 , true )) {
124
+ // HDR requires inordinate RAM! Blink onboard LED if there's trouble:
125
+ pinMode (LED_BUILTIN, OUTPUT);
126
+ for (;;) digitalWrite (LED_BUILTIN, (millis () / 500 ) & 1 );
127
+ }
128
+ pixels->setBrightness (65535 , GAMMA); // NeoPXL8HDR handles gamma correction
129
+ #else
130
+ pixels = new Adafruit_NeoPXL8 (longestStrand, pins, COLOR_ORDER);
84
131
pixels->begin ();
132
+ #endif
85
133
}
86
134
87
135
void loop () {
88
136
uint32_t t = micros (); // Current time, in microseconds
89
137
90
- float x; // multipurpose interim result
138
+ float x = 0.0 ; // multipurpose interim result
91
139
pixels->clear ();
92
140
93
141
for (int i=0 ; i<N_DRIPS; i++) {
@@ -104,6 +152,8 @@ void loop() {
104
152
drip[i].mode = MODE_OOZING; // Idle to oozing transition
105
153
drip[i].eventDurationUsec = random (800000 , 1200000 ); // 0.8 to 1.2 sec ooze
106
154
drip[i].eventDurationReal = (float )drip[i].eventDurationUsec / 1000000.0 ;
155
+ // Randomize next drip color from palette settings:
156
+ memcpy (drip[i].color , palette[random (drip[i].palette_min , drip[i].palette_max + 1 )], sizeof palette[0 ]);
107
157
break ;
108
158
case MODE_OOZING:
109
159
if (drip[i].dribblePixel ) { // If dribblePixel is nonzero...
@@ -135,16 +185,17 @@ void loop() {
135
185
drip[i].eventDurationReal = (float )drip[i].eventDurationUsec / 1000000.0 ;
136
186
drip[i].splatStartUsec = drip[i].eventStartUsec ; // Splat starts now!
137
187
drip[i].splatDurationUsec = random (900000 , 1100000 );
188
+ memcpy (drip[i].splatColor , drip[i].color , sizeof palette[0 ]); // Save color for splat
138
189
break ;
139
190
}
140
191
}
141
192
142
193
// Render drip state to NeoPixels...
143
194
#if ICE_BRIGHTNESS > 0
144
195
// Draw icycles if ICE_BRIGHTNESS is set
145
- x = pow (( float )ICE_BRIGHTNESS * 0.01 , GAMMA) ;
196
+ x = ( float )ICE_BRIGHTNESS * 0.01 ;
146
197
for (int d=0 ; d<=drip[i].dribblePixel ; d++) {
147
- set (i, d, x);
198
+ set (i, i, d, x);
148
199
}
149
200
#endif
150
201
switch (drip[i].mode ) {
@@ -158,8 +209,7 @@ void loop() {
158
209
x = ((float )ICE_BRIGHTNESS * 0.01 ) +
159
210
x * (float )(100 - ICE_BRIGHTNESS) * 0.01 ;
160
211
#endif
161
- x = pow (x, GAMMA);
162
- set (i, 0 , x);
212
+ set (i, i, 0 , x);
163
213
break ;
164
214
case MODE_DRIBBLING_1:
165
215
// Point b moves from first to second pixel over event time
@@ -185,8 +235,7 @@ void loop() {
185
235
dtUsec = t - drip[i].splatStartUsec ; // Elapsed time, in microseconds, since start of splat
186
236
if (dtUsec < drip[i].splatDurationUsec ) {
187
237
x = 1.0 - sqrt ((float )dtUsec / (float )drip[i].splatDurationUsec );
188
- x = pow (x, GAMMA);
189
- set (7 , i, x);
238
+ set (7 , i, i, x);
190
239
}
191
240
}
192
241
}
@@ -235,15 +284,68 @@ void dripDraw(uint8_t dNum, float a, float b, bool fade) {
235
284
x * (float )(100 - ICE_BRIGHTNESS) * 0.01 ;
236
285
}
237
286
#endif
238
- x = pow (x, GAMMA);
239
- set (dNum, i, x);
287
+ set (dNum, dNum, i, x);
240
288
}
241
289
}
242
290
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 ));
291
+ // Set one pixel to a given brightness level (0.0 to 1.0).
292
+ // Strand # and drip # are BOTH passed in because "splats" are always
293
+ // on drip 7 but colors come from drip indices.
294
+ void set (uint8_t strand, uint8_t d, uint8_t pixel, float brightness) {
295
+ #if !defined(USE_HDR) // NeoPXL8HDR does its own gamma correction, else...
296
+ brightness = pow (brightness, GAMMA);
297
+ #endif
298
+ if ((strand < 7 ) || (N_DRIPS >= 8 )) {
299
+ pixels->setPixelColor (pixel + strand * longestStrand,
300
+ (int )((float )drip[d].color [0 ] * brightness + 0.5 ),
301
+ (int )((float )drip[d].color [1 ] * brightness + 0.5 ),
302
+ (int )((float )drip[d].color [2 ] * brightness + 0.5 ));
303
+ } else {
304
+ pixels->setPixelColor (pixel + strand * longestStrand,
305
+ (int )((float )drip[d].splatColor [0 ] * brightness + 0.5 ),
306
+ (int )((float )drip[d].splatColor [1 ] * brightness + 0.5 ),
307
+ (int )((float )drip[d].splatColor [2 ] * brightness + 0.5 ));
308
+ }
249
309
}
310
+
311
+ // NeoPXL8HDR requires some background processing in a second thread.
312
+ // See NeoPXL8 library examples (NeoPXL8HDR/strandtest) for explanation.
313
+ // Currently this sketch only enables HDR if using Feather SCORPIO,
314
+ // but it could be useful for other RP2040s and for ESP32S3too.
315
+ #ifdef USE_HDR
316
+
317
+ #if defined(ARDUINO_ARCH_RP2040)
318
+
319
+ void loop1 () {
320
+ if (pixels) pixels->refresh ();
321
+ }
322
+
323
+ void setup1 () {
324
+ }
325
+
326
+ #elif defined(CONFIG_IDF_TARGET_ESP32S3)
327
+
328
+ void loop0 (void *param) {
329
+ for (;;) {
330
+ yield ();
331
+ if (pixels) pixels->refresh ();
332
+ }
333
+ }
334
+
335
+ #else // SAMD
336
+
337
+ #include " Adafruit_ZeroTimer.h"
338
+
339
+ Adafruit_ZeroTimer zerotimer = Adafruit_ZeroTimer(3 );
340
+
341
+ void TC3_Handler () {
342
+ Adafruit_ZeroTimer::timerHandler (3 );
343
+ }
344
+
345
+ void timerCallback (void ) {
346
+ if (pixels) pixels->refresh ();
347
+ }
348
+
349
+ #endif // end SAMD
350
+
351
+ #endif // end USE_HDR
0 commit comments