// Circuit Playground Digital Fidget Spinner // ----------------------------------------- // Uses the Circuit Playground accelerometer to detect when the board // is flicked from one side or the other and animates the pixels spinning // around accordingly (with decay in speed to simulate friction). // Press one button to cycle through different color combinations, // and another button to cycle through 4 different types of animations // (single dot, dual dot, single sine wave, dual sine wave). // See the FidgetSpinner.h and PeakDetector.h files as tabs at the top // for more of the sketch code! // Author: Tony DiCola // License: MIT License (https://opensource.org/licenses/MIT) #include #include "FidgetSpinner.h" #include "PeakDetector.h" // Configure which accelerometer axis to check: // This should be one of the following values (uncomment only ONE): //#define AXIS CircuitPlayground.motionX() // X axis, good for Circuit Playground express (SAMD21). #define AXIS CircuitPlayground.motionY() // Y axis, good for Circuit Playground classic (32u4). //#define AXIS CircuitPlayground.motionZ() // Z axis, wacky--try it! // Configure how the axis direction compares to pixel movement direction: #define INVERT_AXIS true // Set to true or 1 to invert the axis direction vs. // pixel movement direction, or false/0 to disable. // If the pixels spin in the opposite direction of your // flick all the time then try changing this value. // Configure pixel brightness. // Set this to a value from 0 to 255 (minimum to maximum brightness). #define BRIGHTNESS 255 // Configure peak detector behavior: #define LAG 30 // Lag, how large is the buffer of filtered samples. // Must be an integer value! #define THRESHOLD 20.0 // Number of standard deviations above average a // value needs to be to be considered a peak. #define INFLUENCE 0.1 // Scale down peak values to this percent influence // when storing them back in the filtered values. // Should be a value from 0 to 1.0 where smaller // values mean peaks have less influence. // Configure spinner decay, i.e. how much it slows down. This should // be a value from 0 to 1 where smaller values cause the spinner to // slow down faster. #define DECAY 0.66 // Debounce times for peak detection and button presses. // You probably don't need to change these. #define PEAK_DEBOUNCE_MS 500 #define BUTTON_DEBOUNCE_MS 20 // Define combinations of colors. Each combo has a primary and secondary color // that are defined as 24-bit RGB values (like HTML colors, set them using hex // values). First define a struct to specify the types colors. Then define // the list of color combos. If you want to change color combos (like add or // remove them, skip down to the colors[] array below and change it. typedef struct { uint32_t primary; uint32_t secondary; } colorCombo; // Add or remove color combos here: colorCombo colors[] = { // Each color combo has the following form: // { .primary = 24-bit RGB color (use hex), .secondary = 24-bit RGB color }, // Make sure each combo ends in a comma EXCEPT for the last one! { .primary = 0xFF0000, .secondary = 0x000000 }, // Red to black { .primary = 0x00FF00, .secondary = 0x000000 }, // Green to black { .primary = 0x0000FF, .secondary = 0x000000 }, // Blue to black { .primary = 0xFF0000, .secondary = 0x00FF00 }, // Red to green { .primary = 0xFF0000, .secondary = 0x0000FF }, // Red to blue { .primary = 0x00FF00, .secondary = 0x0000FF } // Green to blue }; // Uncomment this to output a stream of debug information // from the accelerometer and peak detector. Use the serial // plotter to view the graph of these 4 values: // - Accelerometer y axis value (in m/s^2) // - Peak detector filtered y axis average // - Peak detector filtered y axis standard deviation // - Peak detector result, 0=no peak 1=positive peak, -1=negative peak #define DEBUG // Global state used by the sketch (you don't need to change this): PeakDetector peakDetector(LAG, THRESHOLD, INFLUENCE); FidgetSpinner fidgetSpinner(DECAY); uint32_t lastMS = millis(); uint32_t peakDebounce = 0; uint32_t buttonDebounce = 0; bool lastButton1; bool lastButton2; int currentAnimation = 0; int currentColor = 0; void setup() { // Initialize serial output for debugging and plotting. Serial.begin(115200); // Initialize Circuit Playground library and set accelerometer // to its widest +/-16G range. CircuitPlayground.begin(BRIGHTNESS); CircuitPlayground.setAccelRange(LIS3DH_RANGE_16_G); // Initialize starting button state. lastButton1 = CircuitPlayground.leftButton(); lastButton2 = CircuitPlayground.rightButton(); } void loop() { // Update time since last loop call. uint32_t currentMS = millis(); uint32_t deltaMS = currentMS - lastMS; // Time in milliseconds. float deltaS = deltaMS / 1000.0; // Time in seconds. lastMS = currentMS; // Grab the current accelerometer axis value and look for a sudden peak. float accel = AXIS; int result = peakDetector.detect(accel); // If in debug mode, print out the current acceleration and peak detector // state (average, standard deviation, and peak result). Use the serial // plotter to view this over time. #ifdef DEBUG Serial.print(accel); Serial.print(","); Serial.print(peakDetector.getAvg()); Serial.print(","); Serial.print(peakDetector.getStd()); Serial.print(","); Serial.println(result); #endif // If there was a peak and enough time has elapsed since the last peak // (i.e. to 'debounce' the noisey peak signal a bit) then start the spinner // moving at a velocity proportional to the accelerometer value. if ((result != 0) && (currentMS >= peakDebounce)) { peakDebounce = currentMS + PEAK_DEBOUNCE_MS; // Invert accel because accelerometer axis positive/negative is flipped // with respect to pixel positive/negative movement. if (INVERT_AXIS) { fidgetSpinner.spin(-accel); } else { fidgetSpinner.spin(accel); } } // Check if enough time has passed to test for button releases. if (currentMS >= buttonDebounce) { buttonDebounce = currentMS + BUTTON_DEBOUNCE_MS; // Check if any button was released. I.e. the last known button // state was pressed (true) and the current state is released (false). bool button1 = CircuitPlayground.leftButton(); bool button2 = CircuitPlayground.rightButton(); if (!button1 && lastButton1) { // Button 1 released! // Change to the next animation (looping back to start after 3 since // there are 4 total animations). currentAnimation = (currentAnimation + 1) % 4; } if (!button2 && lastButton2) { // Button 2 released! // Change to the next color index. currentColor = (currentColor + 1) % (sizeof(colors)/sizeof(colorCombo)); } lastButton1 = button1; lastButton2 = button2; } // Update the spinner position and draw the current animation frame. float pos = fidgetSpinner.getPosition(deltaS); switch (currentAnimation) { case 0: // Single dot. animateDots(pos, 1); break; case 1: // Two opposite dots. animateDots(pos, 2); break; case 2: // Sine wave with one peak. animateSine(pos, 1.0); break; case 3: // Sine wave with two peaks. animateSine(pos, 2.0); break; } } // Helper functions: void fillPixels(const uint32_t color) { // Set all the pixels on CircuitPlayground to the specified color. for (int i=0; i> 16) & 0xFF; uint8_t g0 = (c0 >> 8) & 0xFF; uint8_t b0 = c0 & 0xFF; uint8_t r1 = (c1 >> 16) & 0xFF; uint8_t g1 = (c1 >> 8) & 0xFF; uint8_t b1 = c1 & 0xFF; uint32_t r = int(lerp(x, x0, x1, r0, r1)); uint32_t g = int(lerp(x, x0, x1, g0, g1)); uint32_t b = int(lerp(x, x0, x1, b0, b1)); return (r << 16) | (g << 8) | b; } // Animation functions: void animateDots(float pos, int count) { // Simple discrete dot animation. Spins dots around the board based on the specified // spinner position. Count specifies how many dots to display, each one equally spaced // around the pixels (in practice any count that 10 isn't evenly divisible by will look odd). // Count should be from 1 to 10 (inclusive)! fillPixels(secondaryColor()); // Compute each dot's position and turn on the appropriate pixel. for (int i=0; i