Skip to content

Commit a3f079b

Browse files
Merge pull request #2508 from PaintYourDragon/main
Update Ooze Master 3000 w/HDR and colored drips
2 parents ab72e5d + d9c4351 commit a3f079b

File tree

4 files changed

+718
-37
lines changed

4 files changed

+718
-37
lines changed

OozeMaster3000/OozeMaster3000.ino

Lines changed: 171 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,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+
4180
struct {
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

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

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

rfm-poi/.feather_rp2040_tinyusb.test.only

Whitespace-only changes.

0 commit comments

Comments
 (0)