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,88 @@ 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+ #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+
4180struct {
4281 uint16_t length; // Length of NeoPixel strip IN PIXELS
4382 uint16_t dribblePixel; // Index of pixel where dribble pauses before drop (0 to length-1)
4483 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
4586 dropState mode; // One of the above states (MODE_IDLE, etc.)
4687 uint32_t eventStartUsec; // Starting time of current event
4788 uint32_t eventDurationUsec; // Duration of current event, in microseconds
4889 float eventDurationReal; // Duration of current event, in seconds (float)
4990 uint32_t splatStartUsec; // Starting time of most recent "splat"
5091 uint32_t splatDurationUsec; // Fade duration of splat
5192 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)
5295} drip[] = {
5396 // 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
61104 // NeoPXL8 output 7 is normally reserved for ground splats
62105 // You CAN add an eighth drip here, but then will not get splats
63106};
64107
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 ;
68115
69116void setup () {
70117 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
72127
73128 for (int i=0 ; i<N_DRIPS; i++) {
74129 drip[i].mode = MODE_IDLE; // Start all drips in idle mode
@@ -78,16 +133,35 @@ void setup() {
78133 drip[i].splatStartUsec = 0 ;
79134 drip[i].splatDurationUsec = 0 ;
80135 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
81145 }
82146
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);
84157 pixels->begin ();
158+ #endif
85159}
86160
87161void loop () {
88162 uint32_t t = micros (); // Current time, in microseconds
89163
90- float x; // multipurpose interim result
164+ float x = 0.0 ; // multipurpose interim result
91165 pixels->clear ();
92166
93167 for (int i=0 ; i<N_DRIPS; i++) {
@@ -104,6 +178,14 @@ void loop() {
104178 drip[i].mode = MODE_OOZING; // Idle to oozing transition
105179 drip[i].eventDurationUsec = random (800000 , 1200000 ); // 0.8 to 1.2 sec ooze
106180 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
107189 break ;
108190 case MODE_OOZING:
109191 if (drip[i].dribblePixel ) { // If dribblePixel is nonzero...
@@ -135,16 +217,17 @@ void loop() {
135217 drip[i].eventDurationReal = (float )drip[i].eventDurationUsec / 1000000.0 ;
136218 drip[i].splatStartUsec = drip[i].eventStartUsec ; // Splat starts now!
137219 drip[i].splatDurationUsec = random (900000 , 1100000 );
220+ memcpy (drip[i].splatColor , drip[i].color , sizeof palette[0 ]); // Save color for splat
138221 break ;
139222 }
140223 }
141224
142225 // Render drip state to NeoPixels...
143226#if ICE_BRIGHTNESS > 0
144227 // Draw icycles if ICE_BRIGHTNESS is set
145- x = pow (( float )ICE_BRIGHTNESS * 0.01 , GAMMA) ;
228+ x = ( float )ICE_BRIGHTNESS * 0.01 ;
146229 for (int d=0 ; d<=drip[i].dribblePixel ; d++) {
147- set (i, d, x);
230+ set (i, i, d, x);
148231 }
149232#endif
150233 switch (drip[i].mode ) {
@@ -158,8 +241,7 @@ void loop() {
158241 x = ((float )ICE_BRIGHTNESS * 0.01 ) +
159242 x * (float )(100 - ICE_BRIGHTNESS) * 0.01 ;
160243#endif
161- x = pow (x, GAMMA);
162- set (i, 0 , x);
244+ set (i, i, 0 , x);
163245 break ;
164246 case MODE_DRIBBLING_1:
165247 // Point b moves from first to second pixel over event time
@@ -185,8 +267,7 @@ void loop() {
185267 dtUsec = t - drip[i].splatStartUsec ; // Elapsed time, in microseconds, since start of splat
186268 if (dtUsec < drip[i].splatDurationUsec ) {
187269 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);
190271 }
191272 }
192273 }
@@ -235,15 +316,68 @@ void dripDraw(uint8_t dNum, float a, float b, bool fade) {
235316 x * (float )(100 - ICE_BRIGHTNESS) * 0.01 ;
236317 }
237318#endif
238- x = pow (x, GAMMA);
239- set (dNum, i, x);
319+ set (dNum, dNum, i, x);
240320 }
241321}
242322
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+ }
249341}
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