Skip to content

Commit 005a944

Browse files
Update Ooze Master 3000 w/HDR and colored drips
1 parent 0a1ee78 commit 005a944

File tree

1 file changed

+139
-37
lines changed

1 file changed

+139
-37
lines changed

OozeMaster3000/OozeMaster3000.ino

Lines changed: 139 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,34 @@
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:
2732
int8_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

3337
typedef 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+
4168
struct {
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

69104
void 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

87135
void 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

Comments
 (0)