55// OOZE MASTER 3000: NeoPixel simulated liquid physics. Up to 7 NeoPixel
66// strands dribble light, while an 8th strand "catches the drips."
77// 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).
1111// See here: https://learn.adafruit.com/adafruit-neopxl8-featherwing-and-library
1212// Requires Adafruit_NeoPixel, Adafruit_NeoPXL8 and Adafruit_ZeroDMA libraries.
1313
1414#include < Adafruit_NeoPXL8.h>
1515
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 )
1919
20- #define GAMMA 2.6
20+ #define GAMMA 2.6 // For linear brightness correction
2121#define G_CONST 9.806 // Standard acceleration due to gravity
2222// While the above G_CONST is correct for "real time" drips, you can dial it back
2323// for a more theatric effect / to slow down the drips like they've still got a
2424// syrupy "drool string" attached (try much lower values like 2.0 to 3.0).
2525
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:
2732int8_t pins[8 ] = { PIN_SERIAL1_RX, PIN_SERIAL1_TX, MISO, 13 , 5 , SDA, A4, A3 };
28-
2933// If using an M4 Feather & NeoPXL8 FeatherWing, use these values instead:
3034// int8_t pins[8] = { 13, 12, 11, 10, SCK, 5, 9, 6 };
31-
35+ # endif
3236
3337typedef enum {
3438 MODE_IDLE,
@@ -38,37 +42,68 @@ typedef enum {
3842 MODE_DRIPPING
3943} dropState;
4044
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+
4168struct {
4269 uint16_t length; // Length of NeoPixel strip IN PIXELS
4370 uint16_t dribblePixel; // Index of pixel where dribble pauses before drop (0 to length-1)
4471 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
4574 dropState mode; // One of the above states (MODE_IDLE, etc.)
4675 uint32_t eventStartUsec; // Starting time of current event
4776 uint32_t eventDurationUsec; // Duration of current event, in microseconds
4877 float eventDurationReal; // Duration of current event, in seconds (float)
4978 uint32_t splatStartUsec; // Starting time of most recent "splat"
5079 uint32_t splatDurationUsec; // Fade duration of splat
5180 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)
5283} drip[] = {
5384 // 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
6192 // NeoPXL8 output 7 is normally reserved for ground splats
6293 // You CAN add an eighth drip here, but then will not get splats
6394};
6495
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 ;
68103
69104void setup () {
70105 Serial.begin (9600 );
71- randomSeed (analogRead (A0) + analogRead (A5 ));
106+ randomSeed (analogRead (A0) + analogRead (A3 ));
72107
73108 for (int i=0 ; i<N_DRIPS; i++) {
74109 drip[i].mode = MODE_IDLE; // Start all drips in idle mode
@@ -78,16 +113,29 @@ void setup() {
78113 drip[i].splatStartUsec = 0 ;
79114 drip[i].splatDurationUsec = 0 ;
80115 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 ]);
81119 }
82120
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);
84131 pixels->begin ();
132+ #endif
85133}
86134
87135void loop () {
88136 uint32_t t = micros (); // Current time, in microseconds
89137
90- float x; // multipurpose interim result
138+ float x = 0.0 ; // multipurpose interim result
91139 pixels->clear ();
92140
93141 for (int i=0 ; i<N_DRIPS; i++) {
@@ -104,6 +152,8 @@ void loop() {
104152 drip[i].mode = MODE_OOZING; // Idle to oozing transition
105153 drip[i].eventDurationUsec = random (800000 , 1200000 ); // 0.8 to 1.2 sec ooze
106154 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 ]);
107157 break ;
108158 case MODE_OOZING:
109159 if (drip[i].dribblePixel ) { // If dribblePixel is nonzero...
@@ -135,16 +185,17 @@ void loop() {
135185 drip[i].eventDurationReal = (float )drip[i].eventDurationUsec / 1000000.0 ;
136186 drip[i].splatStartUsec = drip[i].eventStartUsec ; // Splat starts now!
137187 drip[i].splatDurationUsec = random (900000 , 1100000 );
188+ memcpy (drip[i].splatColor , drip[i].color , sizeof palette[0 ]); // Save color for splat
138189 break ;
139190 }
140191 }
141192
142193 // Render drip state to NeoPixels...
143194#if ICE_BRIGHTNESS > 0
144195 // Draw icycles if ICE_BRIGHTNESS is set
145- x = pow (( float )ICE_BRIGHTNESS * 0.01 , GAMMA) ;
196+ x = ( float )ICE_BRIGHTNESS * 0.01 ;
146197 for (int d=0 ; d<=drip[i].dribblePixel ; d++) {
147- set (i, d, x);
198+ set (i, i, d, x);
148199 }
149200#endif
150201 switch (drip[i].mode ) {
@@ -158,8 +209,7 @@ void loop() {
158209 x = ((float )ICE_BRIGHTNESS * 0.01 ) +
159210 x * (float )(100 - ICE_BRIGHTNESS) * 0.01 ;
160211#endif
161- x = pow (x, GAMMA);
162- set (i, 0 , x);
212+ set (i, i, 0 , x);
163213 break ;
164214 case MODE_DRIBBLING_1:
165215 // Point b moves from first to second pixel over event time
@@ -185,8 +235,7 @@ void loop() {
185235 dtUsec = t - drip[i].splatStartUsec ; // Elapsed time, in microseconds, since start of splat
186236 if (dtUsec < drip[i].splatDurationUsec ) {
187237 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);
190239 }
191240 }
192241 }
@@ -235,15 +284,68 @@ void dripDraw(uint8_t dNum, float a, float b, bool fade) {
235284 x * (float )(100 - ICE_BRIGHTNESS) * 0.01 ;
236285 }
237286#endif
238- x = pow (x, GAMMA);
239- set (dNum, i, x);
287+ set (dNum, dNum, i, x);
240288 }
241289}
242290
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+ }
249309}
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