DigitalJoel

2016/09/05

Robotic Xylophone – The Arduino Side

Filed under: arduino, development — digitaljoel @ 9:37 pm

As I said in my previous post, I was working on a project with a neighbor to create a bell kit that was controlled by an Arduino mega and used solenoids and magnets to strike the correct bells in the correct timing to make music.

The Java program in the previous post creates an event stream file in a binary format.  It’s not the most efficient, but it’s easier than parsing midi on the Arduino.  I’m sure I could have done it in 3 bytes per event instead of 4, but it is what it is.

Most of the code in the Arduino sample has to do with reading the event stream from an SD card.  Once it’s read, it simply inspects the event and performs one of three actions.

  1. Set a pin to HIGH
  2. Set a pin to LOW
  3. delay N milliseconds.

All the hard work of making the song was already done in the Java application.

The one problem I have had is that if there are too many solenoids fired at the same time the Arduino simply resets.  My guess is it’s because the solenoids power supply is drawing from the Arduino power supply.  If we were to give the solenoids a separate power supply then I believe it would work without issue.  This didn’t manifest itself until my daughter played a nice, jazzy version of silver bells.

Following is the documented code.  Sorry for whitespace problems, wordpress doesn’t make it easy…

/*
robotic xylophone player

created Nov 2015
by Joel Weight

SD card reading code based on ListFiles sample
with following credits:

created   Nov 2010
by David A. Mellis
modified 9 Apr 2012
by Tom Igoe
modified 2 Feb 2014
by Scott Fitzgerald
*/

#include <SPI.h>
#include <SD.h>

File root;

// SD information
int SDPIN = 53;

// delay identifier
int DELAY = 255;

// struct representing the Event that is written by the java file
struct Event {
  byte pin;
  byte value;
  int duration;
};

Event* playEvents = 0;
int playEventsSize = 0;

// called by arduino to setup everything.
void setup()
{
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }

  Serial.print("Initializing SD card...");
  // On the Ethernet Shield, CS is pin 4. It's set as an output by default.
  // Note that even if it's not used as the CS pin, the hardware SS pin
  // (10 on most Arduino boards, 53 on the Mega) must be left as an output
  // or the SD library functions will not work.
  pinMode(SDPIN, OUTPUT);

  // initialize our output pins, 2-13 and 22-41, and make sure they are set low.
  for ( int i = 2; i < 14; i++ ) {
    pinMode(i, OUTPUT);
    digitalWrite( i, LOW );
  }
  for ( int i = 22; i < 42; i++ ) {
    pinMode( i, OUTPUT);
    digitalWrite( i, LOW );
  }

  // more checking for reading the file from the SD card reader.
  if (!SD.begin(SDPIN)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");

  root = SD.open("/");

  // debug code to print out the files in the root directory
  printDirectory(root, 0);

  Serial.println("done initializing!");
}

// Given an Event array and the number of Events, iterate through them
// processing each event sequentially.
void playSong( struct Event events[], int len ) {
  Serial.print( "song length: " );
  Serial.println( len);
  for ( int i = 0; i < len; i++ ) {
Event e = events[i];
// if it's a DELAY event, then simply delay for that amount of time.
if ( e.pin == DELAY ) {
Serial.println( "delay" );
delay( e.duration );
}
// otherwise write the specified value to the pin.
else {
printEvent( e );
digitalWrite( e.pin, e.value );
}
}
}

// debug method to print an event.
void printEvent( Event e ) {
Serial.print( e.pin );
Serial.print( " " );
Serial.print( e.value );
Serial.print( " " );
Serial.println( e.duration );
}

// method called over and over by arduino, which means we will loop through the song
// until the power is cut.
void loop() {
// load the song from the file.
// hard coded file name at this point.  Could put a UI on this or use
// hardware buttons to cycle from one song to the next, but that was more
// work than I was ready to do at this point.
loadSong( "/Joel.jwf" );
// once we have loaded the song, give a 5 second countdown.
for ( int i = 5; i < 0; i-- ) {
    Serial.println( i );
    delay(1000);
  }
  // and finally play the song.
  playSong( playEvents, playEventsSize );
}

// Helper to load the custom binary file that was written to the SD card by the java app.
void loadSong( const char* fileName ) {
  File file = SD.open(fileName);
  Serial.print( "Loading song " );
  Serial.println( file.name() );
  if ( file ) {
    if ( playEvents != 0) {
      delete [] playEvents;
    }
    // no real handling for a malformed file. Don't write a malformed file.
    playEventsSize = file.size()/sizeof(Event);
    playEvents = new Event[playEventsSize];
    Serial.print( "File contains " );
    Serial.print( playEventsSize );
    Serial.println( " events." );
    int i = 0;
    while ( file.available()) {
      playEvents[i++] = readEvent(file);
    }
    Serial.print( "Done loading file with " );
    Serial.print( playEventsSize );
    Serial.println( " events in it." );
  }
  else {
    Serial.println( "Unable to load file." );
  }
}

// Helper to read a single event from an opened file.  Handles parsing the format
// of byte, byte, int.
Event readEvent(File file ) {
  Event result;
  byte bytes[4];
  file.readBytes( bytes, sizeof(Event));
  result.pin = bytes[0];
  result.value = bytes[1];
  int duration = ((int)(bytes[2] << 8 ) | (int)bytes[3]);
  result.duration = duration;
  Serial.print( "Event read: " );
  printEvent( result );
  return result;
}

// Debug method to print the contents of a directory.
void printDirectory(File dir, int numTabs) {
   while(true) {

     File entry =  dir.openNextFile();
     if (! entry) {
       // no more files
       break;
     }
     for (uint8_t i=0; i<numTabs; i++) {
       Serial.print('\t');
     }
     Serial.print(entry.name());
     if (entry.isDirectory()) {
       Serial.println("/");
       printDirectory(entry, numTabs+1);
     } else {
       // files have sizes, directories do not
       Serial.print("\t\t");
       Serial.println(entry.size(), DEC);
     }
     entry.close();
   }
}

 

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.