diff --git a/OozeMaster3000/OozeMaster3000.ino b/OozeMaster3000/OozeMaster3000.ino index 491ce1d1c..c86d323aa 100644 --- a/OozeMaster3000/OozeMaster3000.ino +++ b/OozeMaster3000/OozeMaster3000.ino @@ -5,30 +5,34 @@ // OOZE MASTER 3000: NeoPixel simulated liquid physics. Up to 7 NeoPixel // strands dribble light, while an 8th strand "catches the drips." // Designed for the Adafruit Feather M0 or M4 with matching version of -// NeoPXL8 FeatherWing. This can be adapted for other M0 or M4 boards but -// you will need to do your own "pin sudoku" and level shifting -// (e.g. NeoPXL8 Friend breakout or similar). +// NeoPXL8 FeatherWing, or for RP2040 boards including SCORPIO. This can be +// adapted for other M0, M4, RP2040 or ESP32-S3 boards but you will need to +// do your own "pin sudoku" & level shifting (e.g. NeoPXL8 Friend breakout). // See here: https://learn.adafruit.com/adafruit-neopxl8-featherwing-and-library // Requires Adafruit_NeoPixel, Adafruit_NeoPXL8 and Adafruit_ZeroDMA libraries. #include -uint8_t dripColor[] = { 0, 255, 0 }; // Bright green ectoplasm -#define PIXEL_PITCH (1.0 / 150.0) // 150 pixels/m -#define ICE_BRIGHTNESS 0 // Icycle effect Brightness (0 to <100%) +#define PIXEL_PITCH (1.0 / 150.0) // 150 pixels/m +#define ICE_BRIGHTNESS 0 // Icycle effect Brightness (0 to <100%) +#define COLOR_ORDER NEO_GRB // NeoPixel color format (see Adafruit_NeoPixel) -#define GAMMA 2.6 +#define GAMMA 2.6 // For linear brightness correction #define G_CONST 9.806 // Standard acceleration due to gravity // While the above G_CONST is correct for "real time" drips, you can dial it back // for a more theatric effect / to slow down the drips like they've still got a // syrupy "drool string" attached (try much lower values like 2.0 to 3.0). -// NeoPXL8 pin numbers (these are default connections on NeoPXL8 M0 FeatherWing) +// NeoPXL8 pin numbers +#if defined(ARDUINO_ADAFRUIT_FEATHER_RP2040_SCORPIO) +#define USE_HDR // RP2040 has enough "oomph" for HDR color! +int8_t pins[8] = { 16, 17, 18, 19, 20, 21, 22, 23 }; +#else +// These are default connections on NeoPXL8 M0 FeatherWing: int8_t pins[8] = { PIN_SERIAL1_RX, PIN_SERIAL1_TX, MISO, 13, 5, SDA, A4, A3 }; - // If using an M4 Feather & NeoPXL8 FeatherWing, use these values instead: //int8_t pins[8] = { 13, 12, 11, 10, SCK, 5, 9, 6 }; - +#endif typedef enum { MODE_IDLE, @@ -38,10 +42,35 @@ typedef enum { MODE_DRIPPING } dropState; +// A color palette allows one to "theme" a project. By default there's just +// one color, and all drips use only that. Setting up a color list, and then +// declaring a range of indices in the drip[] table later, allows some +// randomization while still keeping appearance within a predictable range. +// Each drip could be its own fixed color, or each could be randomly picked +// from a set of colors. Explained further in Adafruit Learning System guide. +// Q: WHY NOT JUST PICK RANDOM RGB COLORS? +// Because that would pick a lot of ugly or too-dark RGB combinations. +// WHY NOT RANDOM FULL-BRIGHTNESS HUES FROM THE ColorHSV() FUNCTION? +// Two reasons: First, to apply a consistent color theme to a project; +// Halloween, Christmas, fire, water, etc. Second, because NeoPixels +// have been around for over a decade and it's time we mature past the +// Lisa Frank stage of all-rainbows-all-the-time and consider matters of +// taste and restraint. If you WANT all rainbows, that's still entirely +// possile just by setting up a palette of bright colors! +uint8_t palette[][3] = { + { 0, 255, 0 }, // Bright green ectoplasm +}; +// Note that color randomization does not pair well with the ICE_BRIGHTNESS +// effect; you'll probably want to pick one or the other: random colors +// (from palette) and no icicles, or fixed color (per strand or overall) +// with ice. Otherwise the color jump of the icicle looks bad and wrong. + struct { uint16_t length; // Length of NeoPixel strip IN PIXELS uint16_t dribblePixel; // Index of pixel where dribble pauses before drop (0 to length-1) float height; // Height IN METERS of dribblePixel above ground + uint16_t palette_min; // Lower color palette index for this strip + uint16_t palette_max; // Upper color palette index for this strip dropState mode; // One of the above states (MODE_IDLE, etc.) uint32_t eventStartUsec; // Starting time of current event uint32_t eventDurationUsec; // Duration of current event, in microseconds @@ -49,26 +78,32 @@ struct { uint32_t splatStartUsec; // Starting time of most recent "splat" uint32_t splatDurationUsec; // Fade duration of splat float pos; // Position of drip on prior frame + uint8_t color[3]; // RGB color (randomly picked from from palette[]) + uint8_t splatColor[3]; // RGB color of "splat" (may be from prior drip) } drip[] = { // THIS TABLE CONTAINS INFO FOR UP TO 8 NEOPIXEL DRIPS - { 16, 7, 0.157 }, // NeoPXL8 output 0: 16 pixels long, drip pauses at index 7, 0.157 meters above ground - { 19, 6, 0.174 }, // NeoPXL8 output 1: 19 pixels long, pause at index 6, 0.174 meters up - { 18, 5, 0.195 }, // NeoPXL8 output 2: etc. - { 17, 6, 0.16 }, // NeoPXL8 output 3 - { 16, 1, 0.21 }, // NeoPXL8 output 4 - { 16, 1, 0.21 }, // NeoPXL8 output 5 - { 21, 10, 0.143 }, // NeoPXL8 output 6 + { 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 + { 19, 6, 0.174, 0, 0 }, // NeoPXL8 output 1: 19 pixels long, pause at index 6, 0.174 meters up + { 18, 5, 0.195, 0, 0 }, // NeoPXL8 output 2: etc. + { 17, 6, 0.16 , 0, 0 }, // NeoPXL8 output 3 + { 16, 1, 0.21 , 0, 0 }, // NeoPXL8 output 4 + { 16, 1, 0.21 , 0, 0 }, // NeoPXL8 output 5 + { 21, 10, 0.143, 0, 0 }, // NeoPXL8 output 6 // NeoPXL8 output 7 is normally reserved for ground splats // You CAN add an eighth drip here, but then will not get splats }; -#define N_DRIPS (sizeof drip / sizeof drip[0]) -int longestStrand = (N_DRIPS < 8) ? N_DRIPS : 0; -Adafruit_NeoPXL8 *pixels; +#ifdef USE_HDR +Adafruit_NeoPXL8HDR *pixels = NULL; +#else +Adafruit_NeoPXL8 *pixels = NULL; +#endif +#define N_DRIPS (sizeof drip / sizeof drip[0]) +int longestStrand = (N_DRIPS < 8) ? N_DRIPS : 0; void setup() { Serial.begin(9600); - randomSeed(analogRead(A0) + analogRead(A5)); + randomSeed(analogRead(A0) + analogRead(A3)); for(int i=0; i longestStrand) longestStrand = drip[i].length; + // Randomize initial color: + memcpy(drip[i].color, palette[random(drip[i].palette_min, drip[i].palette_max + 1)], sizeof palette[0]); + memcpy(drip[i].splatColor, drip[i].color, sizeof palette[0]); } - pixels = new Adafruit_NeoPXL8(longestStrand, pins, NEO_GRB); +#ifdef USE_HDR + pixels = new Adafruit_NeoPXL8HDR(longestStrand, pins, COLOR_ORDER); + if (!pixels->begin(true, 4, true)) { + // HDR requires inordinate RAM! Blink onboard LED if there's trouble: + pinMode(LED_BUILTIN, OUTPUT); + for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1); + } + pixels->setBrightness(65535, GAMMA); // NeoPXL8HDR handles gamma correction +#else + pixels = new Adafruit_NeoPXL8(longestStrand, pins, COLOR_ORDER); pixels->begin(); +#endif } void loop() { uint32_t t = micros(); // Current time, in microseconds - float x; // multipurpose interim result + float x = 0.0; // multipurpose interim result pixels->clear(); for(int i=0; i 0 // Draw icycles if ICE_BRIGHTNESS is set - x = pow((float)ICE_BRIGHTNESS * 0.01, GAMMA); + x = (float)ICE_BRIGHTNESS * 0.01; for(int d=0; d<=drip[i].dribblePixel; d++) { - set(i, d, x); + set(i, i, d, x); } #endif switch(drip[i].mode) { @@ -158,8 +209,7 @@ void loop() { x = ((float)ICE_BRIGHTNESS * 0.01) + x * (float)(100 - ICE_BRIGHTNESS) * 0.01; #endif - x = pow(x, GAMMA); - set(i, 0, x); + set(i, i, 0, x); break; case MODE_DRIBBLING_1: // Point b moves from first to second pixel over event time @@ -185,8 +235,7 @@ void loop() { dtUsec = t - drip[i].splatStartUsec; // Elapsed time, in microseconds, since start of splat if(dtUsec < drip[i].splatDurationUsec) { x = 1.0 - sqrt((float)dtUsec / (float)drip[i].splatDurationUsec); - x = pow(x, GAMMA); - set(7, i, x); + set(7, i, i, x); } } } @@ -235,15 +284,68 @@ void dripDraw(uint8_t dNum, float a, float b, bool fade) { x * (float)(100 - ICE_BRIGHTNESS) * 0.01; } #endif - x = pow(x, GAMMA); - set(dNum, i, x); + set(dNum, dNum, i, x); } } -// Set one pixel to a given brightness level (0.0 to 1.0) -void set(uint8_t strand, uint8_t pixel, float brightness) { - pixels->setPixelColor(pixel + strand * longestStrand, - (int)((float)dripColor[0] * brightness + 0.5), - (int)((float)dripColor[1] * brightness + 0.5), - (int)((float)dripColor[2] * brightness + 0.5)); +// Set one pixel to a given brightness level (0.0 to 1.0). +// Strand # and drip # are BOTH passed in because "splats" are always +// on drip 7 but colors come from drip indices. +void set(uint8_t strand, uint8_t d, uint8_t pixel, float brightness) { +#if !defined(USE_HDR) // NeoPXL8HDR does its own gamma correction, else... + brightness = pow(brightness, GAMMA); +#endif + if ((strand < 7) || (N_DRIPS >= 8)) { + pixels->setPixelColor(pixel + strand * longestStrand, + (int)((float)drip[d].color[0] * brightness + 0.5), + (int)((float)drip[d].color[1] * brightness + 0.5), + (int)((float)drip[d].color[2] * brightness + 0.5)); + } else { + pixels->setPixelColor(pixel + strand * longestStrand, + (int)((float)drip[d].splatColor[0] * brightness + 0.5), + (int)((float)drip[d].splatColor[1] * brightness + 0.5), + (int)((float)drip[d].splatColor[2] * brightness + 0.5)); + } } + +// NeoPXL8HDR requires some background processing in a second thread. +// See NeoPXL8 library examples (NeoPXL8HDR/strandtest) for explanation. +// Currently this sketch only enables HDR if using Feather SCORPIO, +// but it could be useful for other RP2040s and for ESP32S3too. +#ifdef USE_HDR + +#if defined(ARDUINO_ARCH_RP2040) + +void loop1() { + if (pixels) pixels->refresh(); +} + +void setup1() { +} + +#elif defined(CONFIG_IDF_TARGET_ESP32S3) + +void loop0(void *param) { + for(;;) { + yield(); + if (pixels) pixels->refresh(); + } +} + +#else // SAMD + +#include "Adafruit_ZeroTimer.h" + +Adafruit_ZeroTimer zerotimer = Adafruit_ZeroTimer(3); + +void TC3_Handler() { + Adafruit_ZeroTimer::timerHandler(3); +} + +void timerCallback(void) { + if (pixels) pixels->refresh(); +} + +#endif // end SAMD + +#endif // end USE_HDR