Pages

20 October 2015

Henderizer v2, accelerometer controlled LED dance suit (2 of 2)

I wanted to create a separate post with some of the specific details for the nerds out there. The circuit was quite simple, as I mentioned in Part 1:

- pin 9: LED strip data in
- pin A4: SDA of adxl345
- pin A5: SDL of adxl345
- 5V connected to adxl345 and LED strip
- GND connected to adxl and LED strip

Here's some close ups. The four wires you see are for the adxl345. On finished boards, there would be 5 more coming into the board: LED ground/power/data and battery pack positive and ground. The back is just some jumpers to read the encoder button get power/ground.




On that note, I was originally going to use an LM8705 to scoot down a 4 x AA battery pack to 5V for the LED strips (I'd read you can damage them at more than 5V). Then, I stumbled on the mention of powering a standalone Arduino through the LM8705 with 7 - 16V. I was only giving it 6V. I'm not really sure what happens at less than 7V, but after reading around more, I opted to just solder a jumper in my 4 x AA pack between the first slot negative and the second slot negative so that I'd get 4.5V from three instead.

Other than that, I used a prototype solder board (5 x 7cm cut in half) for everything and other than the above components, I just had to re-create the fairly straightforward Arduino on a breadboard.

Here's the parts I ended up using:


component source price qty total
atmega328p eBay 2.00 1 3.31
16 MHz crystal mouser 0.30 1 0.30
22pF cap amazon 0.091 2 0.18
28 pin socket mouser 0.77 1 0.77
adxl345 ebay 3.26 1 3.26
pcb ebay 1.09 1 1.09
battery holder ax-man 1.50 1 1.50
rotary encoder mouser 1.44 1 1.07
encoder knob mouser 0.46 1 0.46
22ga solid wire amazon 22.00 - 0
26ga stranded wire amazon 22.00 0.05 1.10
WS2112b 60 LED/m RGB strips eBay 39.99 0.2 8.00
belt clip ax-man 0.50 1 0.5
total 21.54

I have to give a plug for tubelight, the eBay vendor I used for the LED strips. I found them to have the lowest cost ws2812b strips, period. So much so, that I contacted them after making my purchase (and about triple checking the specs) to make sure I was getting what I thought. I even included a link to the same item (but for much more) to verify the products were identical. They were, indeed.

I placed my order about 2mos in advance since the shipping window was huge. The day before the last day of the shipping estimate, tracking still said "origin is preparing shipment." Eeks. I took this to mean that they were still in China. I contacted tubelight and they DHL'd me a second set (I ordered 4 x 5m reels!) free. They asked if I was willing to resend on my second set (if and when it came) to other US buyers to save them having to re-send them. I was, but ultimately just bought the second set as well since my wife loves the idea of making some neat Christmas lights with them this year.

In any case, I simply insert the story as I was blown away that they'd go through that trouble and cost to make sure I hit the project deadline. Check them out!

The code is still a bit of a work in progress, but if you'd like to take a look, go for it!

#include "FastLED.h"
#include "Wire.h"
#include "Encoder.h"


// adxl device for wire.h
#define DEVICE (0x53)

// must use fastLED 3.1: https://github.com/FastLED/FastLED/tree/FastLED3.1
// fast led constants and initialize
#define DATA_PIN    9
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB
#define NUM_LEDS    56
CRGB leds[NUM_LEDS];


// adxl setup
byte _buff[6];
char POWER_CTL = 0x2D;  //Power Control Register
char DATA_FORMAT = 0x31;
char DATAX0 = 0x32; //X-Axis Data 0
char DATAX1 = 0x33; //X-Axis Data 1
char DATAY0 = 0x34; //Y-Axis Data 0
char DATAY1 = 0x35; //Y-Axis Data 1
char DATAZ0 = 0x36; //Z-Axis Data 0
char DATAZ1 = 0x37; //Z-Axis Data 1
uint8_t howManyBytesToRead = 6;

// encoder initialization
Encoder enc(3, 4);
int enc_switch = 2;

int readings[50];
int read_index = 0;
int totals = 0;;
int rolling_ave = 0;
int old_rolling_ave = 0;


// global variables
int bright = 200;
int cutoff = 40;
int mode = 0;

// rainbow_equal
int last = 0;
int top = 0;
double next = 0;
double len = 3;

// spinner
double all_time = 1;
double spin = 0;
int mean_x = 0;
int mean_y = 0;
int mean_z = 0;

int max_x = 200;
int max_y = 200;
int max_z = 200;

double which = 0;

// enc
int last_time;
int last_press;
int press;
int maxed = 200;

int x;
int y;
int z;

double count = 0;
int last_pop = 0;


void setup() {
  
  delay(2000);
  Serial.begin(9600); 

  // tell FastLED about the LED strip configuration
  FastLED.addLeds(leds, NUM_LEDS).setCorrection(TypicalLEDStrip).setDither(bright < 255);
  FastLED.setBrightness(bright);

  pinMode(2, INPUT_PULLUP);

  // join i2c bus (address optional for master)
  Wire.begin();        
  
  // Put the ADXL345 into +/- 4G range by writing the value 0x01
  // to the DATA_FORMAT register.
  // Think 0x00 = 2g, 00x1 = 4g, 00x2 = 8g, and 00x3 = 16g
  writeTo(DATA_FORMAT, 0x0B);

  //Put the ADXL345 into Measurement Mode by writing 0x08 to POWER_CTL register.
  writeTo(POWER_CTL, 0x08);
  
}

  
void loop()
{
  //read the acceleration data from the ADXL345
  readFrom(DATAX0, howManyBytesToRead, _buff); 

  // each axis reading comes in 10 bit resolution, ie 2 bytes.
  // Least Significat Byte first!!
  // Thus we are converting both bytes in to one int
  
  x = (((int)_buff[1]) << 8) | _buff[0];   
  y = (((int)_buff[3]) << 8) | _buff[2];
  z = (((int)_buff[5]) << 8) | _buff[4];

  // the next section takes a constant average of the accelerometer
  // and subtracts it from the current reading. this means that
  // whatever orientation you put it in, it will tend towards readings of 
  // zero for all axes. I found the adxl's I got to have quite varied
  // baselines, so this made sure that all of the suits would record an impulse
  // based on motion "away from however you were positioned last."
  mean_x = (x + mean_x) / 2;
  mean_y = (y + mean_y) / 2;
  mean_z = (z + mean_z) / 2;
  
  x = abs(x - mean_x);
  y = abs(y - mean_y);
  z = abs(z - mean_z);
  // I logged some motion and figured out the top end was around 250
  top = constrain(x + y + z, 0, 250); //constrain(max(x, max(y, z)), 0, 225);
  // I found that a moving average might be helpful to remove super close
  // instances of multiple accelerations above the threshold. I may tweak
  // it some more, but it definitely helped smooth things out
  readings[read_index] = top;
  read_index = read_index + 1;
  
  totals = 0;

  for(int i = 0; i < 60; i++){

    totals = readings[i] + totals;

  }

  rolling_ave = totals / 60;

  if(read_index > 59)
  {

    old_rolling_ave = rolling_ave;
    read_index = 0;

  }

  
  // for now, just the button is read. push it and it cycles the mode
  // and flashes a color based on which one you're setting.
  // eventually I'd like a long-press to set things like the acc cutoff
  if(!digitalRead(enc_switch)) {
    
      leds[0] = CHSV(mode * (255/4) + 1, 255, 150);
      FastLED.show();
      delay(500);
      mode = mode + 1;
      if(mode > 3) { mode = 0;}
      leds[0] = CHSV(mode * (255/3) + 1, 255, 150);
      FastLED.show();
      delay(1000);  
    }

  switch(mode)
  {
    case 0:                                               
      rainbow_equal();
      break;
      
    case 1:
      sparkler();
      break;
      
    case 2:
      spinner();
      break;

    case 3:
      rainbow_fade();
      break;
      
  }


}


void rainbow_equal()
{

  //top = max(x, max(y, z));
  //FastLED.setBrightness(200);

  if(len > 1) {

  len = len - (.012 * len);
  bright = map(len, 1, NUM_LEDS / 2, 10, 150);
  FastLED.setBrightness(bright);

}

if(top > cutoff) {

  last = len;

  len = map(top, cutoff, maxed, 8, NUM_LEDS / 2);

  if(last > len) {

    len = last + 3;

    if (len > NUM_LEDS / 2) {len = NUM_LEDS / 2;}

  }
}

next = next + 1;

int start = NUM_LEDS - len;

fill_rainbow(leds, len, next, 3);
fill_rainbow(leds + start, len, next, 3);
FastLED.show();
FastLED.clear();


next = next + 1;

delay(4);
}

void spinner()
{

  //next = 0;

  if(spin > 4) {

    spin = pow(spin, 0.996);
    bright = map(spin, 5, 35, 50, 255);
    
    next = next + spin;
    FastLED.setBrightness(bright);

  }


  else {
    
    next = next + 2;
  }

  //top = max(x, y); 

  if(top > cutoff & spin < 15) {

    spin = map(top, cutoff, maxed, 8, 35);
    bright = map(top, cutoff, maxed, 70, 200);

    next = next + spin;
    FastLED.setBrightness(bright);
    
  }

  if(next > 255) {

    next = next - 255; 

  }

  fill_rainbow(leds, NUM_LEDS / 2, next, 15);
  fill_rainbow(leds + (NUM_LEDS / 2), NUM_LEDS / 2, 255 - next, 15);
  //fill_rainbow(leds + 30, 15, next, 15);
  //fill_rainbow(leds + 45, 15, 255 - next, 15);
  FastLED.show();

  delay(3);

}

void sparkler() 
{


  FastLED.setBrightness(200);

  //top = max(x, max(y, z));

  int num = constrain(map(top, cutoff, maxed, 20, 200), 1, 100);

   while(num > 0) {

     int i = random8(0, NUM_LEDS - 1);                                           
     if (i < NUM_LEDS) leds[i] = CHSV(random8(), random8(50,255), random8(10, 200));
     num = num - 1;
   }
   
   for (int j = 0; j < NUM_LEDS; j++) leds[j].fadeToBlackBy(random8(20, 60));
   
   LEDS.show();                                                
   
   delay(50);

}


void rainbow_fade()
{


if(top > cutoff & rolling_ave > old_rolling_ave)
{


  count = map(top, cutoff, maxed, 50, 500);

  if(last > count) { count = last + 5; }

  if(count > 500) { count = 500; }

  if(millis() - last_pop > 750){
  
  last_pop = millis();

  if(x > y)
  {

    if(x > z)
    {

      if(y > z)
      {
        
        next = 0 + (y/x * 10);

      }
    
      else
      {

        next = 160 + (z/x * 10);

      }

    } 

    else
    {

      next = 160 + (x/z * 10);

    }

  }
      

  else
  {

    if(x > z)
    {

      next = 96 - (x/y * 10);      

    }    

    else
    {

      next = 96 + (z/y * 10);

    }

  }

}
  

}

last = count;

bright = constrain(map(count, 3, 500, 10, 225), 10, 225);

/*
if(count > last)
{

  FastLED.setBrightness(bright);

  fill_rainbow(leds, NUM_LEDS, next, 150 / (count + 1));
  FastLED.show();
  FastLED.clear();
  delay(50);

}
*/

if(count > 3)
{

  count = pow(count, 0.987);

}

else { count = 0; bright = 0; }


FastLED.setBrightness(bright);

fill_rainbow(leds, NUM_LEDS, next + 200/count, 1); //constrain(50 / (count + 1), 1, 20));
FastLED.show();
FastLED.clear();

delay(2);

}

////////// begin accelerometer reader //////////

void writeTo(byte address, byte val) {
  Wire.beginTransmission(DEVICE); // start transmission to device 
  Wire.write(address);             // send register address
  Wire.write(val);                 // send value to write
  Wire.endTransmission();         // end transmission
}

// Reads num bytes starting from address register on device in to _buff array
void readFrom(byte address, int num, byte _buff[]) {
  Wire.beginTransmission(DEVICE); // start transmission to device 
  Wire.write(address);             // sends address to read from
  Wire.endTransmission();         // end transmission

  Wire.beginTransmission(DEVICE); // start transmission to device
  Wire.requestFrom(DEVICE, num);    // request 6 bytes from device

  int i = 0;
  while(Wire.available())         // device may send less than requested (abnormal)
  { 
    _buff[i] = Wire.read();    // receive a byte
    i++;
  }

  Wire.endTransmission();         // end transmission
}

////////// end accelerometer reader //////////

void fadeLEDs(int fadeVal, int which){
  for (int i = 0; i < which; i++){
    leds[29 - i].fadeToBlackBy( fadeVal );
    leds[30 + i].fadeToBlackBy( fadeVal );

  }
}

No comments:

Post a Comment

formatting help: <i>italics</i>, <b>bold</b>