Commit 9112451e authored by papp's avatar papp
Browse files


parent 044fbf34
// "Prank" example sketch for Lilypad MP3 Player
// Mike Grusin, SparkFun Electronics
// This was a prank we pulled on "According To" Pete Dokter.
// While Pete was spending the week at Disneyland with his kids,
// we attached the LilyPad MP3 Player to the big speakers in his
// office. The below sketch waits a given number of hours,
// then plays the audio files on the card while _very slowly_
// ramping up the volume to just barely noticeable. The audio
// file we used was "It's a Small World After All". You can
// of course use whatever you like (hopefully something more
// humane).
// Uses the SdFat library by William Greiman, which is supplied
// with this archive, or download from
// Uses the SFEMP3Shield library by Bill Porter, which is supplied
// with this archive, or download from
// License:
// We use the "beerware" license for our firmware. You can do
// ANYTHING you want with this code. If you like it, and we meet
// someday, you can, but are under no obligation to, buy me a
// (root) beer in return.
// Have fun!
// -your friends at SparkFun
// Revision history:
// 1.0 initial release MDG 2013/4/1
// We'll need a few libraries to access all this hardware!
#include <SPI.h> // To talk to the SD card and MP3 chip
#include <SdFat.h> // SD card file system
#include <SFEMP3Shield.h> // MP3 decoder chip
// LilyPad MP3 Player pin definitions:
#define TRIG1 A0
#define ROT_LEDG A1
#define SHDN_GPIO1 A2
#define ROT_B A3
#define TRIG2_SDA A4
#define TRIG3_SCL A5
#define RIGHT A6
#define LEFT A7
#define TRIG5_RXI 0
#define TRIG4_TXO 1
#define MP3_DREQ 2
#define ROT_A 3
#define ROT_SW 4
#define ROT_LEDB 5
#define MP3_CS 6
#define MP3_DCS 7
#define MP3_RST 8
#define SD_CS 9
#define ROT_LEDR 10
#define MOSI 11
#define MISO 12
#define SCK 13
// Create library objects:
SFEMP3Shield MP3player;
SdFat sd;
SdFile file;
char track[13];
// If you would like debugging information sent to the
// serial port, set debugging = true. This will require
// the use of triggers 4 and 5 (but this sketch doesn't
// need them).
boolean debugging = true;
void setup()
unsigned char result;
// If serial port debugging is inconvenient, you can connect
// a LED to the red channel of the rotary encoder to see any
// startup error codes:
digitalWrite(ROT_LEDR,HIGH); // HIGH = off
// Turn the amplifier chip off and start the MP3 chip
// in MP3 mode:
digitalWrite(SHDN_GPIO1,LOW); // MP3 mode / amp off
// Keep the VS1053 MP3 chip powered down while we're waiting
// to save power:
pinMode(MP3_RST, OUTPUT);
digitalWrite(MP3_RST,LOW); // keep VS1053 in reset
if (debugging)
Serial.println(F("Lilypad MP3 Player prank sketch"));
// Wait until we're ready to play the prank:
if (debugging) Serial.print("waiting... ");
delay(60000L * 60L * 16L); // wait 16 hours
if (debugging) Serial.println("done");
// Turn on the MP3 chip:
digitalWrite(MP3_RST,HIGH); // VS1053 active
// Initialize the SD card:
if (debugging) Serial.print(F("initialize SD card... "));
result = sd.begin(SD_CS, SPI_HALF_SPEED); // 1 for success
if (result != 1) // Problem initializing the SD card
if (debugging) Serial.print(F("error, halting"));
errorBlink(1); // Halt forever, blink LED if present.
if (debugging) Serial.println(F("success!"));
// Start up the MP3 library:
if (debugging) Serial.print(F("initialize MP3 chip... "));
result = MP3player.begin(); // 0 or 6 for success
// Check the result, see the library readme for error codes.
if ((result != 0) && (result != 6)) // Problem starting up
if (debugging) Serial.print(F("error code "));
if (debugging) Serial.print(result);
if (debugging) Serial.print(F(", halting."));
errorBlink(result); // Halt forever, blink red LED if present.
if (debugging) Serial.println(F("success!"));
// Change to the root directory of the SD card:
// Set the VS1053 volume. 0 is loudest, 255 is lowest:
MP3player.setVolume(255,255); // all the way off
// Turn on the amplifier chip:
void loop()
static unsigned long next = 0L;
static unsigned int volume = 255;
// Play the files on the SD card. Over and over and over...
if (!MP3player.isPlaying()) // if not playing...
// Very slowly increase the volume. Because the volume register
// is logarithmic, we do this -slower- as it gets louder so that
// it doesn't quickly increase at the end. The below values will
// take about 1.5 hours to increase from off to barely noticeable.
// The following if() statement is like an alarm clock;
// we set "next" to be a future millis clock time, and when the
// millis clock reaches that value, the if() statement will run.
if (millis() > next)
// Increase the volume, but don't let it rise above 110dB.
// (You can make this 0 if you eventually want it to reach
// full volume.)
if (volume > 110) volume--;
MP3player.setVolume(volume, volume);
// The below line resets the alarm clock value to a new
// time in the future. The values in the pow() equation
// will take about 1.5 hours to increase from nothing to
// barely noticeable.
next = millis() + (pow(((255-volume)/14),2) * 1000L);
// For testing, you might want to comment out the above
// line, and uncomment the below line (this will raise
// the volume in less than a minute rather than taking hours.)
// next = millis() + 100L;
if (debugging) Serial.print("volume: ");
if (debugging) Serial.print(volume);
if (debugging) Serial.print("delay: ");
if (debugging) Serial.println(pow(((255-volume)/14),2) * 1000L);
void errorBlink(int blinks)
// The following function will blink the red LED in the rotary
// encoder (optional) a given number of times and repeat forever.
// This is so you can see any startup error codes without having
// to use the serial monitor window.
int x;
while(true) // Loop forever
for (x=0; x < blinks; x++) // Blink the given number of times
digitalWrite(ROT_LEDR,LOW); // Turn LED ON
digitalWrite(ROT_LEDR,HIGH); // Turn LED OFF
delay(1500); // Longer pause between blink-groups
void getNextTrack()
// Get the next playable track (check extension to be
// sure it's an audio file)
while(isPlayable() != true);
void getNextFile()
// Get the next file (which may be playable or not)
int result = (file.openNext(sd.vwd(), O_READ));
// If we're at the end of the directory,
// loop around to the beginning:
if (!result)
boolean isPlayable()
// Check to see if a filename has a "playable" extension.
// This is to keep the VS1053 from locking up if it is sent
// unplayable data.
char *extension;
extension = strrchr(track,'.');
if (
(strcasecmp(extension,"MP3") == 0) ||
(strcasecmp(extension,"WAV") == 0) ||
(strcasecmp(extension,"MID") == 0) ||
(strcasecmp(extension,"MP4") == 0) ||
(strcasecmp(extension,"WMA") == 0) ||
(strcasecmp(extension,"FLA") == 0) ||
(strcasecmp(extension,"OGG") == 0) ||
(strcasecmp(extension,"AAC") == 0)
return true;
return false;
void startPlaying()
int result;
if (debugging)
Serial.print(F("playing "));
result = MP3player.playMP3(track);
if (debugging)
Serial.print(F(" result "));
void stopPlaying()
if (debugging) Serial.println(F("stopping playback"));
// "Rotary_Encoder_Demo"
// Example sketch for the Lilypad MP3 Player
// Mike Grusin, SparkFun Electronics
// This sketch demonstrates (only) the use of the rotary encoder
// hardware on the Lilypad MP3 Player board from Sparkfun. If you
// want to use the rotary encoder in your own sketches, this code
// will give you a starting point.
// This sketch will run on a LilyPad MP3 Player board (with
// a rotary encoder installed) without modifications.
// If you're not using the LilyPad MP3 Player board, connect
// a rotary encoder to your Arduino using these pins:
// Rotary encoder pin A to digital pin 3*
// Rotary encoder pin B to analog pin 3
// Rotary encoder pin C to ground
// This sketch implements software debounce, but you can further
// improve performance wby placing 0.1uF capacitors between
// A and ground, and B and ground.
// If you wish to use the RGB LED and button functions of
// SparkFun part number COM-10982, use the following connections:
// Rotary encoder pin 1 (red cathode) to digital pin 10
// Rotary encoder pin 2 (green cathode) to analog pin 1
// Rotary encoder pin 3 (button) to digital pin 4
// Rotary encoder pin 4 (blue cathode) to digital pin 5
// Rotary encoder pin 5 (common anode) to VCC (3.3V or 5V)
// Note that because this is a common anode device,
// the pushbutton requires an external 1K-10K pullDOWN resistor
// to operate.
// * Pins marked with an asterisk should not change because
// they use interrupts on that pin. All other pins can change,
// see the constants below.
// Run this sketch with the serial monitor window set to 9600 baud.
// The I/O pins used by the rotary encoder hardware are set up to
// automatically call interrupt functions (rotaryIRQ and buttonIRQ)
// each time the rotary encoder changes states.
// The rotaryIRQ function transparently maintains a counter that
// increments or decrements by one for each detent ("click") of
// the rotary encoder knob. This function also sets a flag
// (rotary_change) to true whenever the counter changes. You can
// check this flag in your main loop() code and perform an action
// when the knob is turned.
// The buttonIRQ function does the same thing for the pushbutton
// built into the rotary encoder knob. It will set flags for
// button_pressed and button_released that you can monitor in your
// main loop() code. There is also a variable for button_downtime
// which records how long the button was held down.
// There is also code in the main loop() that keeps track
// of whether the button is currently being held down and for
// how long. This is useful for "hold button down for five seconds
// to power off"-type situations, which cannot be handled by
// interrupts alone because no interrupts will be called until
// the button is actually released.
// Uses the PinChangeInt library by Lex Talionis,
// download from
// License:
// We use the "beerware" license for our firmware. You can do
// ANYTHING you want with this code. If you like it, and we meet
// someday, you can, but are under no obligation to, buy me a
// (root) beer in return.
// Have fun!
// -your friends at SparkFun
// Revision history:
// 1.0 initial release MDG 2013/01/24
// Load the PinChangeInt (pin change interrupt) library
#include <PinChangeInt.h>
// LilyPad MP3 pin defines. Not all of these are used in this
// sketch; the unused pins are commented out:
//#define TRIG1 A0
#define ROT_LEDG A1 // green LED
//#define SHDN_GPIO1 A2
#define ROT_B A3 // rotary B
//#define TRIG2_SDA A4
//#define TRIG3_SCL A5
//#define RIGHT A6
//#define LEFT A7
//#define TRIG5_RXI 0
//#define TRIG4_TXO 1
//#define MP3_DREQ 2
#define ROT_A 3 // rotary A
#define ROT_SW 4 // rotary puhbutton
#define ROT_LEDB 5 // blue LED
//#define MP3_CS 6
//#define MP3_DCS 7
//#define MP3_RST 8
//#define SD_CS 9
#define ROT_LEDR 10 // red LED
//#define MOSI 11
//#define MISO 12
//#define SCK 13
// RGB LED colors (for common anode LED, 0 is on, 1 is off)
#define OFF B111
#define RED B110
#define GREEN B101
#define YELLOW B100
#define BLUE B011
#define PURPLE B010
#define CYAN B001
#define WHITE B000
// Global variables that can be changed in interrupt routines
volatile int rotary_counter = 0; // current "position" of rotary encoder (increments CW)
volatile boolean rotary_change = false; // will turn true if rotary_counter has changed
volatile boolean button_pressed = false; // will turn true if the button has been pushed
volatile boolean button_released = false; // will turn true if the button has been released (sets button_downtime)
volatile unsigned long button_downtime = 0L; // ms the button was pushed before release
void setup()
// Set up all the I/O pins. Unused pins are commented out.
// pinMode(TRIG1, INPUT);
// digitalWrite(TRIG1, HIGH); // turn on weak pullup
// pinMode(MP3_CS, OUTPUT);
// pinMode(SHDN_GPIO1, OUTPUT);
pinMode(ROT_B, INPUT);
digitalWrite(ROT_B, HIGH); // turn on weak pullup
// pinMode(TRIG2_SDA, INPUT);
// digitalWrite(TRIG1, HIGH); // turn on weak pullup
// pinMode(TRIG3_SCL, INPUT);
// digitalWrite(TRIG1, HIGH); // turn on weak pullup
// pinMode(TRIG5_RXI, INPUT);
// digitalWrite(TRIG5_RXI, HIGH); // turn on weak pullup
// pinMode(TRIG4_TXO, INPUT);
// digitalWrite(TRIG4_TXO, HIGH); // turn on weak pullup
pinMode(ROT_A, INPUT);
digitalWrite(ROT_A, HIGH); // turn on weak pullup
pinMode(ROT_SW, INPUT);
// The rotary switch is common anode with external pulldown, do not turn on pullup
// pinMode(MP3_DREQ, INPUT);
// pinMode(MP3_DCS, OUTPUT);
// pinMode(MP3_RST, OUTPUT);
// pinMode(SD_CS, OUTPUT);
// pinMode(MOSI, OUTPUT);
// pinMode(MISO, INPUT);
// pinMode(SCK, OUTPUT);
//digitalWrite(SHDN_GPIO1, LOW); // Shut down Amplifier chip
//digitalWrite(MP3_RST, LOW); // Shut down MP3 chip
Serial.begin(9600); // Use serial for debugging
Serial.println("rotary encoder testing");
// We use the standard external interrupt pin for the rotary,
// but we'll use the pin change interrupt library for the button.
PCintPort::attachInterrupt(ROT_SW, &buttonIRQ, CHANGE);
void buttonIRQ()
// Process rotary encoder button presses and releases, including
// debouncing (extra "presses" from noisy switch contacts).
// If button is pressed, the button_pressed flag is set to true.
// (Manually set this to false after handling the change.)
// If button is released, the button_released flag is set to true,
// and button_downtime will contain the duration of the button
// press in ms. (Set this to false after handling the change.)
// Raw information from PinChangeInt library:
// Serial.print("pin: ");
// Serial.print(PCintPort::arduinoPin);
// Serial.print(" state: ");
// Serial.println(PCintPort::pinState);
static boolean button_state = false;
static unsigned long start, end;
if ((PCintPort::pinState == HIGH) && (button_state == false))
// Button was up, but is currently being pressed down
// Discard button presses too close together (debounce)
start = millis();
if (start > (end + 10)) // 10ms debounce timer
button_state = true;
button_pressed = true;
else if ((PCintPort::pinState == LOW) && (button_state == true))
// Button was down, but has just been released
// Discard button releases too close together (debounce)
end = millis();
if (end > (start + 10)) // 10ms debounce timer
button_state = false;
button_released = true;
button_downtime = end - start;
void rotaryIRQ()
// Process input from the rotary encoder.
// The rotary "position" is held in rotary_counter, increasing for CW rotation (changes by one per detent).
// If the position changes, rotary_change will be set true. (You may manually set this to false after handling the change).
// This function will automatically run when rotary encoder input A transitions in either direction (low to high or high to low)
// By saving the state of the A and B pins through two interrupts, we'll determine the direction of rotation
// int rotary_counter will be updated with the new value, and boolean rotary_change will be true if there was a value change
// Based on concepts from Oleg at circuits@home (
// Unlike Oleg's original code, this code uses only one interrupt and has only two transition states;
// it has less resolution but needs only one interrupt, is very smooth, and handles switchbounce well.
static unsigned char rotary_state = 0; // current and previous encoder states
rotary_state <<= 2; // remember previous state
rotary_state |= (digitalRead(ROT_A) | (digitalRead(ROT_B) << 1)); // mask in current state
rotary_state &= 0x0F; // zero upper nybble
if (rotary_state == 0x09) // from 10 to 01, increment counter. Also try 0x06 if unreliable
rotary_change = true;
else if (rotary_state == 0x03) // from 00 to 11, decrement counter. Also try 0x0C if unreliable
rotary_change = true;
void loop()
// "Static" variables are initalized once the first time
// that loop runs, but they keep their values through
// successive loops.
static unsigned char x = 0;
static boolean button_down = false;
static unsigned long int button_down_start, button_down_time;