Zufar Dinie
Published © GPL3+

Magic Staff Stick

A gesture-controlled Magic Staff Stick that lights up with different styles when you make certain gestures.

BeginnerFull instructions provided2 hours1,546
Magic Staff Stick

Things used in this project

Hardware components

5v addressable LED strip
make sure it is a 5v addressable led strip
×1
Arduino 101
Arduino 101
×1
Jumper wires (generic)
Jumper wires (generic)
get a set of all the types
×1
battery case holder
I'm using a 3 slot AA battery case holder
×1
male barrel jack adapter
×1
female barrel jack adapter
×1
AA batteries
you need 3 AA batteries
×1

Story

Read more

Code

my code

Arduino
#include "CurieIMU.h"
#include <Adafruit_NeoPixel.h>
#define PIN 6  // what pin are the NeoPixels connected to?
Adafruit_NeoPixel strip = Adafruit_NeoPixel(15 , PIN, NEO_GRB + NEO_KHZ800);  //  the strip is 60 pixels long.

int tr = 0;  //Some variables to hold color target and color current for smoothing...
int tg = 0;
int tb = 0;
int r = 0;
int g = 0;
int b = 0;

long int globaltimer = 0;  // timers to keep track of gestures vs time...for things like "if timer hasn't passed 0.5sec, AND there's two taps, then..."
long int gesturetimer = 0;
long int ledtimer = 0;

int fade = 10;  // how quickly lights fade.  Used for smoothing

int tap = 0;  //couter for vertical impulses

int lr = 0;   //couter for left/right impulses

int fb = 0;   //couter for forward/back impulses

int gesture = 0;  // 
int state = 0;  // for our switch case...  this will keep track of each movement in a series.





void setup() {
  // put your setup code here, to run once:
  //Serial.begin(9600);

  globaltimer = millis();  // start timekeepers at current time
  gesturetimer = millis();
  ledtimer = millis();

  /* Initialise the IMU */
  CurieIMU.begin();
  CurieIMU.attachInterrupt(eventCallback);

  /* Enable Shock Detection */
  CurieIMU.setDetectionThreshold(CURIE_IMU_SHOCK, 1500); // 1.5g = 1500 mg
  CurieIMU.setDetectionDuration(CURIE_IMU_SHOCK, 50);   // milliseconds of spike required to call interupt
  CurieIMU.interrupts(CURIE_IMU_SHOCK);

  strip.begin();  //  intialize neopixel strip
  strip.show();   // Initialize all pixels to 'off'
}

void loop() {
  // put your main code here, to run repeatedly:


  //basic filter- the IMU registers multiple shocks from rebound and counteraction.  This tries to capture the dominant shock in each gesture.

  if (millis() - globaltimer > 170) {  //  this tries to find the dominant axis of movement for each shock..  Compares the sum of tap, left-right, and front-back movements, and picks the largest.
    if ((tap > lr) && (tap > fb)) {
      Serial.println("tap");
      gesture = 1;
      tap = 0; lr = 0; fb = 0; //  reset values after a movement is classified.
    }
    else if ((lr > fb) && (lr > tap)) {
      Serial.println("lr");
      gesture = 2;
      tap = 0; lr = 0; fb = 0;
    }
    else if ((fb > lr) || (fb > tap)) {
      Serial.println("fb");
      gesture = 3;
      tap = 0;  lr = 0; fb = 0;
    }
  }


  if (millis() - globaltimer > 1000) {  //timeoutreset
    globaltimer = millis() - 170;
tr = 0; tg=0; tb = 0;
    state = 0;
    //gesture=0;
  }
  if (millis() - gesturetimer > 1000) {
    gesturetimer = millis() - 350;
tr = 0; tg = 0; tb = 0;
    state = 0;
     //gesture=0;
  }

  switch (state) {   // This tracks gestures

    case 0: {  // no gestures recorded yet...  listen for tap.  If there's one, go to case 1.
        if (millis() - gesturetimer > 350) {  
          if (gesture == 1) {
            state = 1;
            gesture = 0;
            gesturetimer = millis();
          }
        }

        break;
      }
    case 1: {  // one tap recorded.  If a second tap happens, play a quick flash then go to step 2.
        if (millis() - gesturetimer > 350) {  
          if (gesture == 1) {
            r=10;g=10;b=10;  //feedback flash
            state = 2;
            gesture = 0;
            gesturetimer = millis();
            
          }
        }

        break;
      }
    case 2: {  // Switch point -  two taps recorded.  The three spells diverge here based on the next gesture.  If it's a tap, go to case 3.  If it's L/R, go to case 4. If it's Front/Back, go to case 5.
        
        if (millis() - gesturetimer > 350) {
          if (gesture == 1) {
            state = 3;
            gesture = 0;
            gesturetimer = millis();
          }
          if (gesture == 2) {
            state = 4;
            gesture = 0;
            gesturetimer = millis();
          }
          if (gesture == 3) {
            state = 5;
            gesture = 0;
            gesturetimer = millis();
          }
        }

        break;
      }
    case 3: {  //  three taps recorded...  we're in the tap spell, turn the staff red and listen for the final "tap" to set off the spell.
      tr = 20; tg = 0; tb = 0;
      globaltimer = millis()-250;
      if (millis() - gesturetimer > 350) {
        if (gesture == 1) {
          state = 0;
          Serial.println("tapspell!");
          tapspell();
        }
      }
        break;
      }
    case 4: {  //  two taps and a L or R recorded...  we're in the leftspell, turn the staff blue and listen for the final "tap" to set off the spell.
      tr = 0; tg = 0; tb = 20;
      globaltimer = millis()-250;
      if (millis() - gesturetimer > 350) {;
        if (gesture == 1) {
          state = 0;
          Serial.println("leftspell!");
          leftspell();
        }
      }

        break;
      }
    case 5: {  //  two taps and a forward or back recorded...  we're in the forwardspell, turn the staff green and listen for the final "tap" to set off the spell.
      tr = 0; tg = 20; tb = 0;
      globaltimer = millis()-250;
      if (millis() - gesturetimer > 350) {
        if (gesture == 1) {
          state = 0;
          Serial.println("forwardspell!");
          forwardspell();
        }
      }

        break;
      }
defaut: {
        break;
      }
  }
  //Serial.println(tr);
  
if (millis()-ledtimer > fade){   // only do this next step periodically every (fade value) milliseconds.  Unlike the "delay()" function, this allows other things to happen in the program in between updates.

   // color smoothing.  Actual color moves toward target color...  If target is more than curent, move up, if less, move down.
  if (tr > r + 1) {
    r++;
  }
  if (tg > g + 1) {
    g++;
  }
  if (tb > b + 1) {
    b++;
  }
  if (tr < r) {
    r--;
  }
  if (tg < g) {
    g--;
  }
  if (tb < b) {
    b--;
  }
  // turn all the LEDS to the current r, g, b values.
  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, r, g, b);
  }
  strip.show();
  ledtimer=millis();
}

}

// When a shock is detected, the following code interrupts the loop.

static void eventCallback(void)
{
  if (CurieIMU.getInterruptStatus(CURIE_IMU_SHOCK)) {
    if (CurieIMU.shockDetected(X_AXIS, POSITIVE)) {
      tap++;
      globaltimer = millis();
    }

    if (CurieIMU.shockDetected(X_AXIS, NEGATIVE)) {   // for now, just classifying shocks based on their axis.  positive and negative shocks both are the same.
      tap++;
      globaltimer = millis();
    }

    if (CurieIMU.shockDetected(Y_AXIS, POSITIVE)) {
      lr++;
      globaltimer = millis();
    }

    if (CurieIMU.shockDetected(Y_AXIS, NEGATIVE)) {
      lr++;
      globaltimer = millis();
    }

    if (CurieIMU.shockDetected(Z_AXIS, POSITIVE)) {
      fb++;
      globaltimer = millis();
    }

    if (CurieIMU.shockDetected(Z_AXIS, NEGATIVE)) {

      fb++;
      globaltimer = millis();
    }
  }
}


void tapspell() {
  // red theatre lights
  theaterChase(strip.Color(127, 20, 0), 20);
  theaterChase(strip.Color(127, 20, 50), 55);
  strip.show();
}

void leftspell() {
  theaterChase(strip.Color(0, 0, 100), 20);
  theaterChase(strip.Color(0, 30, 120), 75);
  strip.show();
}

void forwardspell() {
  theaterChase(strip.Color(0, 127, 0), 30);
  theaterChase(strip.Color(0, 127, 90), 55);
  strip.show();
}


// Spell functions for lights 
// From the Adafruit Neopixel Strandtest Example
// Theatre-style crawling lights.
void theaterChase(uint32_t c, uint8_t wait) {
  for (int j = 0; j < 10; j++) { //do 10 cycles of chasing
    for (int q = 0; q < 3; q++) {
      for (uint16_t i = 0; i < strip.numPixels(); i = i + 3) {
        strip.setPixelColor(i + q, c);  //turn every third pixel on
      }
      strip.show();

      delay(wait);

      for (uint16_t i = 0; i < strip.numPixels(); i = i + 3) {
        strip.setPixelColor(i + q, 0);      //turn every third pixel off
      }
    }
  }
}

 

Credits

Lucas Ainsworth

Posted by Zufar Dinie
Thanks to Only me, Zufardinie.

Comments