commit b91d9cb52fd409017ea17a318f7c8c0a56d2ddec Author: Neale Pickett Date: Sun Dec 18 09:40:53 2022 -0700 Three modes diff --git a/HsvConverter.cpp b/HsvConverter.cpp new file mode 100644 index 0000000..0a04085 --- /dev/null +++ b/HsvConverter.cpp @@ -0,0 +1,112 @@ +//////////////////////////////////////////////////////////// +// +// Copyright (c) <2019> Simon Gauvin (gauvinsimon@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//////////////////////////////////////////////////////////// + +#include "HsvConverter.h" + +namespace HsvConverter +{ + namespace + { + uint8_t getColorValueFromSaturation(uint8_t color, uint8_t saturation) + { + uint8_t colorAddition = static_cast((255 - saturation)) * (255 - color) / 255; + return color + colorAddition; + } + } // namespace + + void getRgbFromHSV(uint16_t hue, uint8_t saturation, uint8_t value, + uint8_t& red, uint8_t& green, uint8_t& blue) + { + static constexpr uint16_t maxHue = 6 * 255; + hue %= maxHue; + + // This variable is to know on which part of the rgb graph we are + // 0 = [0, 59], 1 = [60, 119] ... 5 = [0, 359] + // https://en.wikipedia.org/wiki/HSL_and_HSV#/media/File:HSV-RGB-comparison.svg + uint8_t graphSection = hue / 255; + + const uint8_t valueInGraphPortion = hue % 255; // Aka hue % 60 + uint32_t r, g, b; + switch (graphSection) + { + case 0: // 0 to 59 degrees + red = value; + green = getColorValueFromSaturation(valueInGraphPortion, saturation) * static_cast(value) / 255; + blue = (255 - saturation) * static_cast(value) / 255; + break; + case 1: // 60 to 119 degrees + red = getColorValueFromSaturation(255 - valueInGraphPortion, saturation) * static_cast(value) / 255; + green = value; + blue = (255 - saturation) * static_cast(value) / 255; + break; + case 2: // 120 to 179 degrees + red = (255 - saturation) * static_cast(value) / 255; + green = value; + blue = getColorValueFromSaturation(valueInGraphPortion, saturation) * static_cast(value) / 255; + break; + case 3: // 180 to 239 degrees + red = (255 - saturation) * static_cast(value) / 255; + green = getColorValueFromSaturation(255 - valueInGraphPortion, saturation) * static_cast(value) / 255; + blue = value; + break; + case 4:// 240 to 299 degrees + red = getColorValueFromSaturation(valueInGraphPortion, saturation) * static_cast(value) / 255; + green = (255 - saturation) * static_cast(value) / 255; + blue = value; + break; + case 5: // 300 to 360 degrees + red = value; + green = (255 - saturation) * static_cast(value) / 255; + blue = getColorValueFromSaturation(255 - valueInGraphPortion, saturation) * static_cast(value) / 255; + break; + } + } + + uint32_t getColorFromHSV(uint16_t hue, uint8_t saturation, uint8_t value) + { + uint8_t r, g, b; + getRgbFromHSV(hue, saturation, value, r, g, b); + return getColorFromRgb(r, g, b); + } + + uint32_t getColorFromRgb(uint8_t red, uint8_t green, uint8_t blue) + { + return (static_cast(red) << 16) | (static_cast(green) << 8) | blue; + } + + uint8_t getRedValueFromColor(const uint32_t& color) + { + return (color & 0xFF0000) >> 16; + } + + uint8_t getGreenValueFromColor(const uint32_t& color) + { + return (color & 0xFF00) >> 8; + } + + uint8_t getBlueValueFromColor(const uint32_t& color) + { + return color & 0xFF; + } +} // namespace HsvConverter diff --git a/HsvConverter.h b/HsvConverter.h new file mode 100644 index 0000000..9982f6b --- /dev/null +++ b/HsvConverter.h @@ -0,0 +1,77 @@ +//////////////////////////////////////////////////////////// +// +// Copyright (c) <2019> Simon Gauvin (gauvinsimon@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//////////////////////////////////////////////////////////// + +#pragma once + +#include + +namespace HsvConverter +{ + /// \brief Get the RGB values from hue, saturation and value. + /// \param hue The hue of the color. The hue goes from 0 to 1529, 0 beeing 0 degrees and 1529 beeing 359.764... degrees. + /// This means that each slice of 60 degrees has 255 values. + /// \param saturation The saturation of the color. The saturation goes from 0 to 255, 0 beeing 0% and 255 beeing 100%. + /// \param value The value of the color. The value goes from 0 to 255, 0 beeing 0% and 255 beeing 100%. + /// \param [out] red The variable in which the red value will be written. + /// \param [out] green The variable in which the green value will be written. + /// \param [out] blue The variable in which the blue value will be written. + void getRgbFromHSV(uint16_t hue, uint8_t saturation, uint8_t value, + uint8_t& red, uint8_t& green, uint8_t& blue); + + /// \brief Get the color from hue, saturation and value. + /// \param hue The hue of the color. The hue goes from 0 to 1529, 0 beeing 0 degrees and 1529 beeing 359.764... degrees. + /// This means that each slice of 60 degrees has 255 values. + /// \param saturation The saturation of the color. The saturation goes from 0 to 255, 0 beeing 0% and 255 beeing 100%. + /// \param value The value of the color. The value goes from 0 to 255, 0 beeing 0% and 255 beeing 100%. + /// \return The concatenated RGB values organised like so: 0#R#G#B, where each component is 8 bits. + /// This is also known as the HEX of a color. + uint32_t getColorFromHSV(uint16_t hue, uint8_t saturation, uint8_t value); + + /// \brief Concatenate the RGB values into a single variable. + /// \param red The red component of the color. + /// \param green The green component of the color. + /// \param blue The blue component of the color. + /// \return The concatenated RGB values organised like so: 0#R#G#B, where each component is 8 bits. + /// This is also known as the HEX of a color. + uint32_t getColorFromRgb(uint8_t red, uint8_t green, uint8_t blue); + + /// \brief Extract the red component of a color + /// \param color The color from which the extraction is made. + /// \return The red component of the color. + uint8_t getRedValueFromColor(const uint32_t& color); + + /// \brief Extract the green component of a color + /// \param color The color from which the extraction is made. + /// \return The green component of the color. + uint8_t getGreenValueFromColor(const uint32_t& color); + + /// \brief Extract the blue component of a color + /// \param color The color from which the extraction is made. + /// \return The blue component of the color. + uint8_t getBlueValueFromColor(const uint32_t& color); + + static constexpr uint16_t maxHue = 6 * 255 - 1; + static constexpr uint8_t maxSaturation = 255; + static constexpr uint8_t maxValue = 255; +} // namespace HsvConverter diff --git a/pixie-princess.ino b/pixie-princess.ino new file mode 100644 index 0000000..7a73476 --- /dev/null +++ b/pixie-princess.ino @@ -0,0 +1,182 @@ +// SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include "HsvConverter.h" + +int ledMode = 0; //FIRST ACTIVE MODE + +// Number of Pixies in the fiber optics +#define NUM_PIXIES 5 + +// Use the built-in UART for buttery-smooth serial updates +#define pixieSerial Serial1 + +// Milliseconds of stillness to reset calibration +#define STILL_TIME 5000 + +// How much jitter the accelerometer has +#define ACCEL_JITTER 160 + +// Sparkle probability is 1 over this. Lower number = more frequent. +#define SPARKLE_PROB 30 + +Adafruit_Pixie strip = Adafruit_Pixie(NUM_PIXIES, &pixieSerial); +Adafruit_MSA311 accel = Adafruit_MSA311(); + +void setup() +{ + pinMode(LED_BUILTIN, OUTPUT); + Serial.begin(115200); + + accel.begin(); + accel.setPowerMode(MSA301_NORMALMODE); + accel.setDataRate(MSA301_DATARATE_1000_HZ); + accel.setBandwidth(MSA301_BANDWIDTH_500_HZ); + accel.setRange(MSA301_RANGE_2_G); + accel.setResolution(MSA301_RESOLUTION_14); + accel.enableInterrupts(true, true); // enable single/double tap + accel.enableAutoRange(true); + + pixieSerial.begin(115200); + strip.setBrightness(0x60); + strip.show(); // Initialize all pixels to off +} + +// Global hue, sat, val, color +uint16_t hue = 0x00; +uint16_t sat = 0xff; +uint16_t val = 0xff; +uint32_t color = 0; + +// Global tick count +uint32_t ticks = 0; + +void loop() { + static bool enabled = true; + static uint8_t mode = 0; + + uint8_t motionstat = accel.getMotionInterruptStatus(); + switch (motionstat & 0b00110000) { + case 0b00100000: // Single Tap + mode++; + break; + case 0b00010000: // Double Tap + enabled = !enabled; + break; + } + + calibrated_read(); + hue = (hue + HsvConverter::maxHue + accel.y) % HsvConverter::maxHue; + val = 0xa0 - (accel.x << 1); + color = HsvConverter::getColorFromHSV(hue, sat, val); + + if (enabled) { + switch (mode) { + case 0: + chase(); + break; + case 1: + sparkle(); + break; + case 2: + disco_fever(); + break; + default: + mode = 0; + } + } else { + strip.clear(); + } + strip.show(); + + hello(); + + delay(10); + ticks++; +} + +/** + * Blink the built-in LED at startup + */ +void hello() { + if (ticks >= 500) { + return; + } + bool on = (ticks%10 == 0); +#ifdef SEEED_XIAO_M0 + on = !on; +#endif + digitalWrite(LED_BUILTIN, on); +} + +void disco_fever() { + static uint16_t count = 0; + strip.setPixelColor(0, color); +} + +void chase() { + static uint8_t which = 0; + + if (ticks % 30 == 0) { + which = (which + 1) % NUM_PIXIES; + } + + strip.clear(); + strip.setPixelColor(which, color); +} + +void sparkle() { + if (ticks % 5) { + return; + } + + for (uint16_t i = 0; i < NUM_PIXIES; i++) { + uint32_t color = 0; + if (random(SPARKLE_PROB) == 0) { + color = HsvConverter::getColorFromHSV(hue, sat >> 3, val); + } + strip.setPixelColor(i, color); + } +} + +void calibrated_read() { + static int16_t calibration[3] = {0}; + static int16_t lastvals[3] = {0}; + static unsigned long still_since = 0; + int16_t a[3] = {0}; + bool is_still = true; + + accel.read(); + a[0] = accel.x - calibration[0]; + a[1] = accel.y - calibration[1]; + a[2] = accel.z - calibration[2]; + + is_still = is_still && (abs(a[0] - lastvals[0]) < ACCEL_JITTER); + is_still = is_still && (abs(a[1] - lastvals[1]) < ACCEL_JITTER); + is_still = is_still && (abs(a[2] - lastvals[2]) < ACCEL_JITTER); + + if (is_still || !still_since) { + if (!still_since || (millis() - still_since > STILL_TIME)) { + Serial.printf("Recalibrating\n"); + calibration[0] += a[0]; + calibration[1] += a[1]; + calibration[2] += a[2]; + still_since = millis(); + } + } else { + still_since = millis(); + } + + lastvals[0] = a[0]; + lastvals[1] = a[1]; + lastvals[2] = a[2]; + + accel.x = a[0] / ACCEL_JITTER; + accel.y = a[1] / ACCEL_JITTER; + accel.z = a[2] / ACCEL_JITTER; +} \ No newline at end of file