Tag Archives: arduino

Arduino Hot Wheels Drag Strip Race Track

For my son’s second birthday I decided to introduce him to die-cast cars and what better way than building a drag strip race track with an electronic start gate, timing and race results?

While Hot Wheels does offer a 6-Lane Raceway and a number of other drag strip style tracks, they didn’t have the timing and electronic start gate that I knew an Arduino and some other bits could provide, so I set to work. The basic idea is was have a servo motor open the start gate by pulling down a hinged plate with dowel stoppers to release the cars and a photocell (photoresistor/LDR) pointed at an infrared LED on each track to detect each car crossing the finish line.

Some quick searching revealed a number of projects from which I could draw insight and inspiration, most useful were apachexmd’s Hot Wheels Track Timer and Robby C’s Ultimate Guide to Building a Hot Wheels Race Track. This demystified a number of points, I hadn’t really had any fun with die-cast cars since I was kid myself, turns out there are some oddities.

Tracks

One of the oddities is that Hot Wheels doesn’t offer any long track for sale, all of their tracks are sold in segments a foot or two long that must be connected — the only exception being a vintage 50-foot Track Pak Raceway sold at some point in the 70′s or 80′s which can be found for upwards of $100 currently. Another track option is BluTrack which is sold in many lengths but only in a two-lane configuration for some reason. I decided to buy the readily available Hot Wheels segments in the form of 4 Hot Wheels Track Builder Straight Track kits.

I had some 1/2″ x 8″ pine left over from framing a door I thought I would use to mount the track on. There are a number of methods folks have employed to mount the Hot Wheels track on wood, I decided to use a method mentioned on Robby C’s page which involves using screws to secure a stack of two different sized washers to the wood at intervals.

A small washer below a larger one provides the elevation that the track needs to slide onto the larger washer. The exact washers and screws I used can be seen in the gallery below, be sure to drill pilot holes for the screws and try to keep them straight, the straighter the screws are, the better they will fit in the washer and, in turn, the smaller the bump in the track will be. This method (at least with the sizes I used) does leave ever so slight bumps in the track once it’s fitted, but I felt it was slight enough that it wouldn’t adversely affect the races. I used one of these washer guides for each section of track positioned to sit in the center of the track, except for the finish line track where I placed one at either end.

The track has 4 main pieces (though one I cut into two to make it more modular). These four pieces are a small section of track for the finish line which has the results display attached, the main straightaway, the start incline and a support for the start incline. Following some of the ideas on Robby C’s page again I decided to use hinges to connect the start incline track to its support piece as well as to the straightaway allowing for an adjustable incline. If you use hinges with removable pins it allows for easier disassembly.

I ended up cutting the straightaway into two sections, and I’ll probably do the same with the incline so that I can make the entire track shorter and thus more palatable in the living room.

For the joint between the incline and the straightaway I made sure that no track joints would run across the curve allowing for the longest pieces of track at this joint and thus a smooth transition for cars from the incline to the straightaway.

The flat joints I cut where the tracks connect, this makes disassembly a bit easier, but I’m not sure it was the right choice as some of the wood warped and having a track joint and a wood joint at the same spot may not result in the smoothest run. Experiment with the hinges and see which orientation works best for your setup.

Start Gate

The start gate on this track uses a high torque servo to actuate a hinged plate that has 4 wood dowels inserted into it. The dowels feed up through routed slots in the wood and tracks to hold the cars at the start gate.

The Hot Wheels tracks can be cut easily with a sharp exacto, though it’s difficult to get the corners of cuts perfectly smooth. The track can also be drilled, one method is to drill an appropriate diameter hole at either end of the route you wish to cut and then connect the two holes with a single exacto cut on either edge.

To start the race the servo pulls a stiff wire which is connected to the hinge plate with a small L-bracket, this pulls the dowels back down through the routed slots and releases the cars. The slots I routed in the wood were 1/2″, the track slots were a little smaller than that, somewhere between 1/4″ and 1/2″ as the dowels themselves were 1/4″ diameter and needed some clearance to move smoothly. The dowels are simply friction fit into drilled holes.

It may not be obvious from the video and photos, but I drilled a small hole in the L bracket which fit the wire much better than the large screw holes. This prevented any extra travel of the wire at the hinge plate connection when the servo actuates.

The wire I used to connect the servo to the hinge plate is a malleable steel of some sort, when my son decides to push or pull on the start gate hinge or dowels it will bend this wire rather than stress the servo, after which I inform him that is not how it’s suppose to function and bend the wire straight again. For my setup a run-of-the-mill servo wasn’t strong enough to push and pull the 1/4″ wooden hinged plate so I picked up a high torque servo. If you made a lighter, thinner hinge plate you may be alright with a weaker servo.

Robby C mentions something about this type of drop-out start gate being not as fair as a gate that lifts rather than drops, but I’m not sure why that would be — in any case, my goal was not to create a track fit for the world cup of die-cast racing. A lift gate would have required more fabrication, so I opted for the simpler drop-out.

Finish Line

The finish line employs 4 photocells, also know as photoresistors or LDR’s (Light Dependent Resistors) which sit in an enclosure above the track in holes drilled into a piece 1/2″ piece of wood. These photocells point down through the drilled holes, through larger drilled holes in the enclosure at 4 infrared LED’s embedded in the wood under the track and aligned with holes in the track above. Placing the photocells in holes in the 1/2″ piece of wood keeps them focused on their respective infrared LED underneath without picking up a lot of ambient light.

While the infrared LED’s don’t produce any light visible to the naked eye, they are detected by the photocells and when a car passes over the infrared LED, the reading on the respective photocell drops dramatically and thus the Arudino brain can determine when each car passes the finish line by waiting for the photocell reading to drop.

Embedding the photocells into a piece of wood also allowed me to align that wood with holes I drilled in the track wood for the infrared LED’s before placing the wood, along with the photocells in the enclosure. Once I’d aligned the wood, I drilled holes in it for the photocells, wired up the photocells and placed that entire piece of wood into the enclosure. The holes I drilled in the enclosure were much larger and thus I had room to align the wood with the photocells to the infrared LED’s without having to worry about aligning exactly with the holes in the enclosure.

I wrote a specific Arduino sketch in order to align the photocells which wrote the photocell values to the computer via a serial connection. Since then I have added a debug mode to the race track, pressing both the start race and track reset buttons simultaneously will engage the debug mode where the Arduino will write the photocell values out to the 7-segment race result displays so that I can check the photocell alignments.

One of the issues I ran into was that it seemed the power needed to actuate the start gate servo would draw too much from the rest of the circuit causing the infrared LED’s to dim and trigger the photocells. To get around this I added a 150ms delay to allow the circuit to recover after opening the start gate.

Circuit

None of the electronics used in the project are very complex. I collected various components mostly from RobotShop, SparkFun, Creatron and locally.

These components and their circuits are all connected to the Arduino which runs the sketch at the bottom of this post. If I get a chance to draw one up, I’ll post a circuit diagram.

Car Storage

After purchasing a good starting set of cars to go along with the drag track I needed some way to store the hoard. It seemed likely that a generic storage container would do the trick as long as the compartment sizing matched. I wasn’t all that pleased with the purpose-build die-cast storage containers. A little bit of searching revealed this Creative Options Thread Organizer which fits 48 cars almost perfectly. It’s only half-full in the photo below, with the same number of compartments on the reverse side still to be populated.

Next Steps

While it turned into a race to finish this project in time for my son’s birthday, I had initially intended to integrate a Raspberry Pi to record race statistics and another fun function, but I’ll leave that to everyone’s imagination until I get around to implementing it. The whole setup could also use some Hot Wheels stickers.

Update

I’ve since removed a section from the start incline and the straightaway to reduce the overall size of the track in order to move it to a more permanent location (rather than across our living room). Below is a short video of my son operating the shortened track, he doesn’t seem to mind the change and still enjoys the track quite a bit.

Arduino Sketch


/* Hot Wheels Drag Strip v1.2 */

#include <Servo.h>
#include <SPI.h> // Include the Arduino SPI library

// SPI SS pins
const int displayPinLane1 = 7;
const int displayPinLane2 = 4;
const int displayPinLane3 = 6;
const int displayPinLane4 = 5; 

// Photocell pins
const int photocellPinLane1 = 2;
const int photocellPinLane2 = 3;
const int photocellPinLane3 = 4;
const int photocellPinLane4 = 5;

int photocellReadingLane1;
int photocellReadingLane2;
int photocellReadingLane3;
int photocellReadingLane4;

const int photocellThreshold = 4;

// Button pins
const int startButtonPin = 2;
const int resetButtonPin = 3;

const int gateServoPin = 14;

Servo gateServo;

// Servo positions
const int openGateServoPosition = 10;
const int closeGateServoPosition = 30;

int startButtonState = 0;
int resetButtonState = 0;

int raceStatus = 0;

int lane1Status = 0;
int lane2Status = 0;
int lane3Status = 0;
int lane4Status = 0;

int currentPlace = 0;

unsigned int counterLane1 = 0;  // These variables will count up to 65k
unsigned int counterLane2 = 0;
unsigned int counterLane3 = 0;
unsigned int counterLane4 = 0;  

String displayString;
char tempString[10];  // Will be used with sprintf to create strings

int animationTimer = 0;
bool showPlace = true;

void setup()
{
  gateServo.attach(gateServoPin);
  gateServo.write(closeGateServoPosition);

  pinMode(startButtonPin, INPUT);
  pinMode(resetButtonPin, INPUT);

  // -------- SPI initialization
  pinMode(displayPinLane1, OUTPUT);
  digitalWrite(displayPinLane1, HIGH);
  pinMode(displayPinLane2, OUTPUT);
  digitalWrite(displayPinLane2, HIGH);
  pinMode(displayPinLane3, OUTPUT);
  digitalWrite(displayPinLane3, HIGH);
  pinMode(displayPinLane4, OUTPUT);
  digitalWrite(displayPinLane4, HIGH); 

  // Begin SPI hardware
  SPI.begin();
  // Slow down SPI clock
  SPI.setClockDivider(SPI_CLOCK_DIV64); 

  clearDisplays();

  s7sSendStringSPI(displayPinLane1, "Ln 1");
  s7sSendStringSPI(displayPinLane2, "Ln 2");
  s7sSendStringSPI(displayPinLane3, "Ln 3");
  s7sSendStringSPI(displayPinLane4, "Ln 4");

  // High brightness
  setBrightnessSPI(displayPinLane1, 255);
  setBrightnessSPI(displayPinLane2, 255);
  setBrightnessSPI(displayPinLane3, 255);
  setBrightnessSPI(displayPinLane4, 255);
}

void loop()
{
  animationTimer++;

  if(animationTimer > 200) {
    if(showPlace == true) {
      showPlace = false;
    } else {
      showPlace = true;
    }
    animationTimer = 0;
  }

  photocellReadingLane1 = analogRead(photocellPinLane1);
  photocellReadingLane2 = analogRead(photocellPinLane2);
  photocellReadingLane3 = analogRead(photocellPinLane3);
  photocellReadingLane4 = analogRead(photocellPinLane4);  

  resetButtonState = digitalRead(resetButtonPin);
  startButtonState = digitalRead(startButtonPin);

  if(resetButtonState == HIGH && startButtonState == HIGH) {
    // Debug mode
    clearDisplays();

    s7sSendStringSPI(displayPinLane1, String(photocellReadingLane1)+"   ");
    s7sSendStringSPI(displayPinLane2, String(photocellReadingLane2)+"   ");
    s7sSendStringSPI(displayPinLane3, String(photocellReadingLane3)+"   ");
    s7sSendStringSPI(displayPinLane4, String(photocellReadingLane4)+"   ");

    delay(1000);
  } else if(resetButtonState == HIGH && raceStatus > 0) {
    raceStatus = 0;

    clearDisplays();

    s7sSendStringSPI(displayPinLane1, "Ln 1");
    s7sSendStringSPI(displayPinLane2, "Ln 2");
    s7sSendStringSPI(displayPinLane3, "Ln 3");
    s7sSendStringSPI(displayPinLane4, "Ln 4");

    lane1Status = 0;
    lane2Status = 0;
    lane3Status = 0;
    lane4Status = 0;

    counterLane1 = 0;
    counterLane2 = 0;
    counterLane3 = 0;
    counterLane4 = 0;

    currentPlace = 0;

    gateServo.write(closeGateServoPosition);
  } else {
    if (raceStatus == 0 && startButtonState == HIGH) {

      // start the race

      gateServo.write(openGateServoPosition);

      // wait for servo to drop
      delay(150);
      raceStatus = 1;
    } else if (raceStatus == 1) {

      // race is running

      int carsAcross = 0;

      if (photocellReadingLane1 < photocellThreshold && lane1Status == 0) {
        showPlace = true;
        currentPlace++;
        carsAcross++;
        lane1Status = currentPlace;
        gateServo.write(closeGateServoPosition);
      } 

      if (photocellReadingLane2 < photocellThreshold && lane2Status == 0) {
        showPlace = true;
        if(carsAcross == 0) {
          currentPlace++;
        }
        carsAcross++;
        lane2Status = currentPlace;
        gateServo.write(closeGateServoPosition);
      } 

      if (photocellReadingLane3 < photocellThreshold && lane3Status == 0) {
        showPlace = true;
        if(carsAcross == 0) {
          currentPlace++;
        }
        carsAcross++;
        lane3Status = currentPlace;
        gateServo.write(closeGateServoPosition);
      } 

      if (photocellReadingLane4 < photocellThreshold && lane4Status == 0) {
        showPlace = true;
        if(carsAcross == 0) {
          currentPlace++;
        }
        carsAcross++;
        lane4Status = currentPlace;
        gateServo.write(closeGateServoPosition);
      } 

      updateDisplay(displayPinLane1, lane1Status, counterLane1);
      updateDisplay(displayPinLane2, lane2Status, counterLane2);
      updateDisplay(displayPinLane3, lane3Status, counterLane3);
      updateDisplay(displayPinLane4, lane4Status, counterLane4);

      if (lane1Status == 0) {
        counterLane1++;
      }

      if (lane2Status == 0) {
        counterLane2++;
      }

      if (lane3Status == 0) {
        counterLane3++;
      }

      if (lane4Status == 0) {
        counterLane4++;
      }
    }
  }

  delay(10);  // This will make the display update at 100Hz.*/
}

void clearDisplays()
{
  clearDisplaySPI(displayPinLane1);
  clearDisplaySPI(displayPinLane2);
  clearDisplaySPI(displayPinLane3);
  clearDisplaySPI(displayPinLane4);
}

void updateDisplay(int displayPin, int laneStatus, unsigned int laneCounter) {

    // Magical sprintf creates a string for us to send to the s7s.
    //  The %4d option creates a 4-digit integer.
    sprintf(tempString, "%4d", laneCounter);

    // This will output the tempString to the S7S
    if(laneStatus == 0 || showPlace == false) {
      s7sSendStringSPI(displayPin, tempString);

      // Print the decimal at the proper spot
      if (laneCounter < 10000) {
          setDecimalsSPI(displayPin, 0b00000010);  // Sets digit 3 decimal on
      } else {
          setDecimalsSPI(displayPin, 0b00000100);
      }
    } else {
      setDecimalsSPI(displayPin, 0b000000000);

      if(laneStatus == 1) {
        s7sSendStringSPI(displayPin, "1st ");
      } else if(laneStatus == 2) {
        s7sSendStringSPI(displayPin, "2nd ");
      } else if(laneStatus == 3) {
        s7sSendStringSPI(displayPin, "3rd ");
      } else {
        s7sSendStringSPI(displayPin, "4th ");
      }
    }
}

// This custom function works somewhat like a serial.print.
//  You can send it an array of chars (string) and it'll print
//  the first 4 characters in the array.
void s7sSendStringSPI(int ssPin, String toSend)
{
  digitalWrite(ssPin, LOW);
  for (int i=0; i < 4; i++)
  {
    SPI.transfer(toSend[i]);
  }
  digitalWrite(ssPin, HIGH);
}

// Send the clear display command (0x76)
//  This will clear the display and reset the cursor
void clearDisplaySPI(int ssPin)
{
  digitalWrite(ssPin, LOW);
  SPI.transfer(0x76);  // Clear display command
  digitalWrite(ssPin, HIGH);
}

// Set the displays brightness. Should receive byte with the value
//  to set the brightness to
//  dimmest------------->brightest
//     0--------127--------255
void setBrightnessSPI(int ssPin, byte value)
{
  digitalWrite(ssPin, LOW);
  SPI.transfer(0x7A);  // Set brightness command byte
  SPI.transfer(value);  // brightness data byte
  digitalWrite(ssPin, HIGH);
}

// Turn on any, none, or all of the decimals.
//  The six lowest bits in the decimals parameter sets a decimal
//  (or colon, or apostrophe) on or off. A 1 indicates on, 0 off.
//  [MSB] (X)(X)(Apos)(Colon)(Digit 4)(Digit 3)(Digit2)(Digit1)
void setDecimalsSPI(int ssPin, byte decimals)
{
  digitalWrite(ssPin, LOW);
  SPI.transfer(0x77);
  SPI.transfer(decimals);
  digitalWrite(ssPin, HIGH);
}

A Maker Wedding

Initially I wasn’t sure how much our wedding was truly going to represent my fiancée and I, after all, we wanted our family and friends to enjoy themselves and feel included — as with any large event there are a lot of expectations to manage. After deciding to craft my own Edison-style light fixtures for our reception I realized that the occasion was, in addition to a celebration of our life-long commitment to each other, an opportunity for us to showcase our creativity and perhaps introduce some of our family and friends to aspects of ourselves they may not have known existed.

In retrospect we probably took on too much, but it allowed us to feel the occasion was a true reflection of ourselves — for me this meant soldering, stripping, crimping, twisting, programming and no small amount of brow furrowing. None of these projects could’ve come together without the help of my wonderful wife Ester, who not only said yes, but also collaborated throughout and trusted me to deliver on some very important aspects of our big day. In addition, a big thanks to my dear old Dad who took time to help me with the lengthy task of wiring the Edison fixtures and to the friends and family who helped us setup and teardown these, and other installations.

Animated Arduino LED matrix lounge table top

Vinyl “flexi” record wedding invitations

XBee remote relay as photobooth RF camera trigger

Bachelor party wireless Arduino accelerometer Stab-O-Meter

JQuery Animated Wedding Website


Various puppet arms available at Obscura Antiques & Oddities, New York

Maker Wedding: Animated Arduino LED matrix lounge table top


Finally got around to making an LED table top, as it turns out — for my wedding reception. We decided to have a lounge area and an LED coffee table seemed like the perfect centerpiece for it. I decided instead of making a full table that I would make a table top that fit onto an existing ottoman. I affixed the LED strips to a plywood board which had a 2″ raised frame with aluminium duct tape, to help with brightness.

Arranging the LED’s in a proper matrix turned out to be quite a job as the strips I used came pre-wired and there isn’t all that much length between LED’s on the strip. I ended up having to cut and re-splice the connection leads for each row of the 7 x 7 matrix, after that the construction went quickly. You could get around this by using a more modular LED strip solution, I initially had ShiftBrites slated for this project, but I made something else with them and when I got around to this table top there were much less expensive options available.

I created an outer frame with a bevel to support a glass top. Initially I went with plexiglass but it would bow in the middle with anything of weight on the table, I didn’t want to add supports as this would disrupt the light diffusion, so I opted for a piece of tempered glass (actually intended for table tops to boot).

Adding adhesive obscuring film to the glass didn’t have the diffusion effect I’d hoped for so I sandwiched a sheet of white tracing paper between the tempered glass and a similarly sized piece of plexiglass and this gave the soft white diffused look I wanted.

The issue of programming the animations took a little longer. I wanted to use the disco(esc) animations available from the fine folks responsible for the 1E Disco Dance Floor — which I also used in my ohDisco! app for iPad. These animations are 32 x 16 and can have hundreds of frames — too much to load completely into the Arduino’s memory. But I didn’t want to have to deal with reducing the animations to 7 x 7, or reducing their total frame count, as this would affect the quality and overall impression. Instead I opted to add an SD card reader to the setup which stores the animations. The 7 x 7 section of each frame is loaded on-demand from each animation file and displayed on the table, with this setup the Arduino has no memory problems whatsoever and with a little more code it could index and play animations from the SD card without the need for code changes.

Worth noting is that the SdFat library used to interface with the Seeedstudio SD Card Shield wouldn’t run reliably (or at all sometimes) on an ATmega128 so be sure to use a more powerful Arduino running an ATmega328.

Parts

Arduino Sketch


const int chipSelect = 10;

#include <SdFat.h>
#include "SPI.h"
#include "Adafruit_WS2801.h"

uint8_t dataPin  = 2;
uint8_t clockPin = 3;   

SdFat sd;
SdFile myFile;

int rows = 32;
int cols = 16;

long framesize = rows*cols*3;
long rowsize = cols*3;

int ledrows = 6;
int ledcols = 6;

int rep = 0;
long reps = 5;

int brightness = 15;
int delaytime = 40;

char* files[]={
  "pulsar.ddf",
  "snake.ddf",
  "inter3.ddf",
  "inter4.ddf",
  "inter5.ddf",
  "rings.ddf",
  "rings2.ddf",
  "rings3.ddf",
  "matrix.ddf"
  };

int fileCount = 9;

char* file;
int frame = 0;
int frames;

Adafruit_WS2801 strip = Adafruit_WS2801(50, dataPin, clockPin);

// strip to matrix addressing array
byte addressMatrix[7][7] = {
  1,2,3,4,5,6,7,
  14,13,12,11,10,9,8,
  15,16,17,18,19,20,21,
  28,27,26,25,24,23,22,
  29,30,31,32,33,34,35,
  42,41,40,39,38,37,36,
  43,44,45,46,47,48,49
};

void setup() {
  Serial.begin(9600);
  randomSeed(analogRead(0));
  delay(400);  // catch Due reset problem
  if (!sd.begin(chipSelect, SPI_FULL_SPEED))
    sd.initErrorHalt();

  file = files[ random(fileCount) ];

  strip.begin();
  strip.setPixelColor(0, 0, 0, 0);
  strip.show();
}

void loop() {
  if (!myFile.open(file, O_READ)) {
    sd.errorHalt("failed");
    rep = reps + 1;
    return;
  }

  // seek to next frame
  if(myFile.fileSize() < ((frame*framesize)+1))
  {
    myFile.close(); 

    frame = 0;
    rep = rep + 1;

    if(rep > reps)
    {
      rep = 0;
      file = files[ random(fileCount) ];
      Serial.println(file);
    }

    return;
  }
  else
  {
    myFile.seekSet(frame*framesize);
  }

  // adjust reps for number of frames
  if(frame == 0)
  {
    frames = myFile.fileSize()/framesize;
    reps = 750/frames;
    Serial.println(frames,DEC);
    Serial.println(reps,DEC);
  }

  int data;

  int column = 0;
  int row = 0;

  while (row <= ledrows)
  {
    while (column <= ledcols)
    {
      data = myFile.read();
      // read red
      int r = map(data,0,255,0,255);
      // read green
      data = myFile.read();
      int g = map(data,0,255,0,255);
      // read blue
      data = myFile.read();
      int b = map(data,0,255,0,255);

      // set pixel address
      byte address = addressMatrix[row][column];

      // set pixel color
      strip.setPixelColor(address, map(r,0,255,0,brightness), map(g,0,255,0,brightness), map(b,0,255,0,brightness));

      // next column
      column = column + 1;
    }

    // reset column count
    column = 0;

    // increment row
    row = row + 1;

    // skip extra pixels
    myFile.seekSet((frame*framesize)+(row*rowsize));
  }

  // turn off first pixel (7x7 matrix, 1 unused pixel)
  strip.setPixelColor(0, 0, 0, 0);

  // send current frame to strip
  strip.show();

  // close the file
  myFile.close();

  // increment frame
  frame = frame + 1;

  // rest
  delay(delaytime);
}

Maker Wedding: Bachelor party wireless accelerometer Stab-O-Meter

Since I had disassembled the Wine-O-Meter I’d made for a friend’s bachelor party I needed to come up with something else for my own, I wanted to do an updated strongman competition. I decided to put together a wireless accelerometer to hopefully measure the speed and impact of various activities such as swinging a baseball bat, a sledgehammer, a hatchet, a tennis racket — get the idea? Sort of like the measurement tools used on shows like MythBusters or Deadliest Warrior. Along the lines of the Wine-O-Meter I dubbed the project the Stab-O-Meter as measuring arm movements reminded me of one of my favourite Futurama characters, Roberto.


My plan was to use an Arduino to read an accelerometer and use a pair of XBees to wireless relay the information to a laptop. The laptop would be running a Processing sketch to handle the high score display, reset and current readings. It took a little bit to find the right Arudino code to read the LIS331 Triple Axis Accelerometer I’d selected but it worked well once I found it. I don’t know a whole heck of a lot about accelerometers, but this one measures g-forces on three axis, x, y and z. After some trial and error I decided to add all positive g-force readings together and then add all negative g-force readings together. If the positive total was higher I used that as the current amalgamated reading otherwise I used the absolute sum of the negative values. Comment if you’re aware of a better way to translate x, y, z g-forces into a single number representing the speed of the motion (see Hank’s comment below).

Hank Cowdog

A neg X Acc means acc along the negative X axis. The magnitude of the acc is the important measurement, so a better approach would be to sum the squares of each X,Y,Z component and then take the square root (as per the Pythagorean Theorem). This computes the magnitude of the Acc regardless of the direction (or orientation of the accelerometer chips).

result = sqrt(xAcc*xAcc + yAcc*yAcc + zAcc*zAcc);


The Arduino sent the single number amalgamated reading in realtime (or as close as possible) via it’s serial connection to a XBee which in turn wirelessly relayed the serial data to a laptop running a processing sketch to read and deal with the data. The Processing sketch displayed a realtime reading bar on the right, the highest reading yet recorded in large numbers in the center and a RESET button to clear the current highest reading. With this system each contestant could reset the high score using the RESET button or the spacebar and the proceed to swing a bat or stab a tree or whatnot to find they’re personal best, which was then ranked against other’s scores on a white board.

This part worked great, however in impact scenarios (actually hitting something) it was too easy to max out the sensor, which has a max of 24g, so we restricted our games to non-impact swings. I had added hand wrap to the sensor case in order to secure it to the implement of choice, however I quickly realized that it also needed a non-slip surface for grip, I epoxied some rubber salvaged from a guitar effect pedal. Even with the hand wrap and the rubber footing the first full-force swing with a baseball bat sent the sensor soaring into a neighbouring house — duct tape provided the necessary upgrade in grip, but downgrade in polish.

The video below is, aside from my Roberto impression, an early test using a preliminary Processing sketch and no cases for the components. When I get a chance I’ll record a video of the finished setup, perhaps as I demolish my garage this weekend. Yes, it’s an odd video, but that’s what YouTube is for, right?

Parts

Arduino Sketch

// 3-axis Accelerometer
// Sparkfun Electronics Triple Axis Accelerometer Breakout - LIS331
// Arduino UNO

/* Wiring:
    UNO LIS331

    3.3V VCC
    GND GND
    10 CS
    11 SDA/SDI
    12 SA0/SDO
    13 SCL/SPC
    */

#include <SPI.h>
#include <stdlib.h>
#include <stdio.h>

#define SS 10 // Serial Select -> CS on LIS331
#define MOSI 11 // MasterOutSlaveIn -> SDI
#define MISO 12 // MasterInSlaveOut -> SDO
#define SCK 13 // Serial Clock -> SPC on LIS331

#define SCALE 0.0007324; // approximate scale factor for full range (+/-24g)
// scale factor: +/-24g = 48G range. 2^16 bits. 48/65536 = 0.0007324

// global acceleration values
double xAcc, yAcc, zAcc;

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

  // Configure SPI
  SPI_SETUP();

  // Configure accelerometer
  Accelerometer_Setup();
}

void loop()
{
  readVal(); // get acc values and put into global variables

  int pos = 0;
  int neg = 0;

  if(xAcc > 0)
  {
    pos = pos + xAcc;
  }
  else
  {
    neg = neg + abs(xAcc);
  }

  if(yAcc > 0)
  {
    pos = pos + yAcc;
  }
  else
  {
    neg = neg + abs(yAcc);
  }

  if(zAcc > 0)
  {
    pos = pos + zAcc;
  }
  else
  {
    neg = neg + abs(zAcc);
  }

  int result = neg;

  if(pos > neg)
    result = pos;

  Serial.println(result,1);

   /*
    Serial.print(xAcc, 1);
    Serial.print(",");
    Serial.print(yAcc, 1);
    Serial.print(",");
    Serial.println(zAcc, 1);
  */

  delay(10);
}

// Read the accelerometer data and put values into global variables
void readVal()
{
  byte xAddressByteL = 0x28; // Low Byte of X value (the first data register)
  byte readBit = B10000000; // bit 0 (MSB) HIGH means read register
  byte incrementBit = B01000000; // bit 1 HIGH means keep incrementing registers
  // this allows us to keep reading the data registers by pushing an empty byte
  byte dataByte = xAddressByteL | readBit | incrementBit;
  byte b0 = 0x0; // an empty byte, to increment to subsequent registers

  digitalWrite(SS, LOW); // SS must be LOW to communicate
  delay(1);
  SPI.transfer(dataByte); // request a read, starting at X low byte
  byte xL = SPI.transfer(b0); // get the low byte of X data
  byte xH = SPI.transfer(b0); // get the high byte of X data
  byte yL = SPI.transfer(b0); // get the low byte of Y data
  byte yH = SPI.transfer(b0); // get the high byte of Y data
  byte zL = SPI.transfer(b0); // get the low byte of Z data
  byte zH = SPI.transfer(b0); // get the high byte of Z data
  delay(1);
  digitalWrite(SS, HIGH);

  // shift the high byte left 8 bits and merge the high and low
  int xVal = (xL | (xH <<8));
  int yVal = (yL | (yH <<8));
  int zVal = (zL | (zH <<8));

  // scale the values into G's
  xAcc = xVal * SCALE;
  yAcc = yVal * SCALE;
  zAcc = zVal * SCALE;
}

void SPI_SETUP()
{
  pinMode(SS, OUTPUT);

  // wake up the SPI bus
  SPI.begin();

  // This device reads MSB first:
  SPI.setBitOrder(MSBFIRST);

  /*
  SPI.setDataMode()
  Mode    Clock Polarity (CPOL) Clock Phase (CPHA)
  SPI_MODE0    0    0
  SPI_MODE1    0    1
  SPI_MODE2    1    0
  SPI_MODE3    1    1
  */
  SPI.setDataMode(SPI_MODE0);

  /*
  SPI.setClockDivider()
  sets SPI clock to a fraction of the system clock
  Arduino UNO system clock = 16 MHz
  Mode SPI Clock
  SPI_CLOCK_DIV2 8 MHz
  SPI_CLOCK_DIV4 4 MHz
  SPI_CLOCK_DIV8 2 MHz
  SPI_CLOCK_DIV16 1 MHz
  SPI_CLOCK_DIV32 500 Hz
  SPI_CLOCK_DIV64 250 Hz
  SPI_CLOCK_DIV128 125 Hz
  */

  SPI.setClockDivider(SPI_CLOCK_DIV16); // SPI clock 1000Hz
}

void Accelerometer_Setup()
{
  // Set up the accelerometer
  // write to Control register 1: address 20h
  byte addressByte = 0x20;
  /* Bits:
  PM2 PM1 PM0 DR1 DR0 Zen Yen Xen
  PM2PM1PM0: Power mode (001 = Normal Mode)
  DR1DR0: Data rate (00=50Hz, 01=100Hz, 10=400Hz, 11=1000Hz)
  Zen, Yen, Xen: Z enable, Y enable, X enable
  */
  byte ctrlRegByte = 0x37; // 00111111 : normal mode, 1000Hz, xyz enabled

  // Send the data for Control Register 1
  digitalWrite(SS, LOW);
  delay(1);
  SPI.transfer(addressByte);
  SPI.transfer(ctrlRegByte);
  delay(1);
  digitalWrite(SS, HIGH);

  delay(100);

  // write to Control Register 2: address 21h
  addressByte = 0x21;
  // This register configures high pass filter
  ctrlRegByte = 0x00; // High pass filter off

  // Send the data for Control Register 2
  digitalWrite(SS, LOW);
  delay(1);
  SPI.transfer(addressByte);
  SPI.transfer(ctrlRegByte);
  delay(1);
  digitalWrite(SS, HIGH);

  delay(100);

  // Control Register 3 configures Interrupts
  // Since I'm not using Interrupts, I'll leave it alone

  // write to Control Register 4: address 23h
  addressByte = 0x23;
  /* Bits:
  BDU BLE FS1 FS0 STsign 0 ST SIM
  BDU: Block data update (0=continuous update)
  BLE: Big/little endian data (0=accel data LSB at LOW address)
  FS1FS0: Full-scale selection (00 = +/-6G, 01 = +/-12G, 11 = +/-24G)
  STsign: selft-test sign (default 0=plus)
  ST: self-test enable (default 0=disabled)
  SIM: SPI mode selection(default 0=4 wire interface, 1=3 wire interface)
  */
  ctrlRegByte = 0x30; // 00110000 : 24G (full scale)

  // Send the data for Control Register 4
  digitalWrite(SS, LOW);
  delay(1);
  SPI.transfer(addressByte);
  SPI.transfer(ctrlRegByte);
  delay(1);
  digitalWrite(SS, HIGH);
}

Processing Sketch

 import pitaru.sonia_v2_9.*;
 import processing.serial.*;

 Sample beep;

 float high;
 int count;

 int inside = -1;
 int bx=850; // position in X of the up corner of the botton
 int by=460; // position in Y of the up corner of the botton
 int h=40;
 int w=100;

 float inByte=0;
 float drawByte=0;

 PFont f;

 Serial myPort;         // The serial port
 int xPos = 10;         // horizontal position of the graph

public void stop()
{
  Sonia.stop();
  super.stop();
}

 void setup () {
   // set the window size:
   size(1024, 550);

   high = 0;
   count = 0;

   f = createFont("Verdana",6,true);

   // List all the available serial ports
   println(Serial.list());
   // I know that the first port in the serial list on my mac
   // is always my  Arduino, so I open Serial.list()[0].
   // Open whatever port is the one you're using.
   myPort = new Serial(this, Serial.list()[0], 9600);
   // don't generate a serialEvent() unless you get a newline character:
   myPort.bufferUntil('\n');
   // set inital background:
   background(0);

  Sonia.start(this);
  beep = new Sample( "beep-02.wav" );
 }

void draw()
{
  if(keyPressed)
  {
    if(key == ' ')
    {
      high = inByte;
    }
  }

  background(0);

  //stroke(255,0,0);
  rect(xPos, 500 - inByte, xPos+20, inByte);

  textFont(f,25);
  fill(255);
  text(inByte, xPos - 10, 500 - inByte - 25);

  count = count + 1;

  if(count > 2)
  {
    count = 0;

    if(drawByte < high - 200)
    {
     beep.play();
     drawByte = drawByte + 100;
    }
    else if(drawByte < high - 10)
    {
       beep.play();
       drawByte = drawByte + 10;
    }
    else if(drawByte < high - 1)
    {
      beep.play();
      drawByte = drawByte + 1;
    }
    else if(drawByte < high - .1)
    {
      beep.play();
      drawByte = drawByte + .1;
    }
    else if(drawByte < high - .01)
    {
      beep.play();
      drawByte = drawByte + .01;
    }
    else if(drawByte < high - .001)
    {
      beep.play();
      drawByte = drawByte + .001;
    }
    else if(drawByte < high)
    {
      drawByte = high;
    }
  }

  if(drawByte > high)
  {
    drawByte = high;
  }

  textFont(f,140);
  fill(255);
  text(drawByte, 200, 325);

  rect(bx,by,w,h); // Button 

  textFont(f,25);
  fill(0);
  text("RESET", bx+10, by+30);
  fill(255);
}

void mousePressed(){
  if(!(((mouseX > (bx+w))
  ||(mouseY > (by+h)))
  ||((mouseX < bx)
  ||(mouseY < by))))
  {
      high = inByte;
  }
}

void serialEvent (Serial myPort) {
   String inString = myPort.readStringUntil('\n');

   if (inString != null)
   {
     // trim off any whitespace:
     inString = trim(inString);
     // convert to an int and map to the screen height:
     inByte = float(inString);
     inByte = map(inByte, 0, 1023, 0, 500);

     if(inByte > high)
     {
       high = inByte;
     }
   }
 }

Video: Triggering lights with guitar frequency levels

EQTrigger

Had an idea after catching this post on Hack a Day, why not use the frequency level values to trip a 120 volt relay? So I ordered some parts and did it. The audio analyzing chip, the MSGEQ7, is easily accessed using DFRobot’s DFR0126, which, being in Canada, I got from RobotShop. Connecting the breakout board to an Arduino Nano was a 5 minute job, sample code and a library is linked from the DFRobot product page. I initially used a potentiometer to input the threshold levels for the relay, but then realized I could use a momentary switch to sample the desired threshold and then use that to compare the real-time input to.

The circuit is simple, when a button (momentary stomp) is depressed, and we all get depressed sometimes, the code saves the input values from the audio analyzer. There are seven frequency bands it records, but I found only three or four of them are applicable to guitar, so ignore the lowest and perhaps the highest two. After a threshold has been recorded simply check the input against the recorded levels and trip the relay (or not).

I gave the thresholds a grace of 5 (on a theoretical input range of 0-1023), I may add a pot for this adjustment as it may vary based on guitar signal types. The result is quite versatile, you could have the relay turn off a mellow light and turn on a spastic light when the signal goes loud. If you pay close enough attention to EQ bands and levels you could trigger various lights based on a variety of guitar effects. This setup would also allow, albeit in a roundabout way, you to engage a guitar effect based on the frequency band levels, as long as the effect will pass-through without power then connecting it to the relay would engage the effect — or you could redesign this circuit to route some audio signals based on the input levels.


The pedal I stomp in the video is the MP-1 fuzz from Inductor Guitars, the EQTrigger pedal is connected to the extra output on a Boss TU-2 tuner and is reacting auto-magically to the change in guitar signal when I play louder or engage the fuzz.



Parts List

eqtrigger
Okay, so I didn’t spend a lot of time working out a clean circuit diagram — at least I didn’t use as much electrical tape in the diagram.

Arduino Sketch


#include <AudioAnalyzer.h>
Analyzer Audio = Analyzer(4,5,0);

int FreqVal[7];
int FreqThreshVal[7];

int switchPin = 3;
int switchValue = 0;

int relayPin = 2;

void setup()
{
  pinMode(relayPin, OUTPUT);
  pinMode(switchPin, INPUT);

  for(int i=0;i<7;i++)
    FreqThreshVal[i] = 512;

  //Serial.begin(57600);
  Audio.Init();
}

void loop()
{
  Audio.ReadFreq(FreqVal);//return 7 value of 7 bands pass filiter
                          //Frequency(Hz):63  160  400  1K  2.5K  6.25K  16K
                          //FreqVal[]:      0    1    2    3    4    5    6  

  switchValue = digitalRead(switchPin);  

  if(switchValue == HIGH)
  {
    for(int i=1;i<5;i++)
    {
       FreqThreshVal[i] = FreqVal[i];
       /*
       Serial.print(max((FreqVal[i]-100),0));

       if(i<6)
        Serial.print(",");
       else
        Serial.println(" SET ");
        */
    }
  }
  else
  {
    boolean thresholdMet = true;

    for(int i=1;i<5;i++)
    {
       //Serial.print(max((FreqVal[i]-100),0));

       if(FreqVal[i] < FreqThreshVal[i]-5)
         thresholdMet = false;

       /*
       if(i<6)
         Serial.print(",");
       else
         Serial.println(" READ ");
         */
    }  

    if(thresholdMet == true)
    {
      //Serial.println(" MET ");
      digitalWrite(relayPin, HIGH);
    }
    else
    {
      digitalWrite(relayPin, LOW);
    }
  }
}