用Lilypad製作音板

Arduino平臺的設計非常靈活,使用Uno等單個開發板就可以創建出無數專案。但是,專為執行特定任務而設計的專用板對某些專案來說尤其有用。為了展示這一點,我們將用the LilyPad MP3(一種變體Arduino開發板)來構建一個音板。

變體開發板的工作原理

開源軟體平臺允許開發人員檢查和修改他們的代碼。同樣,像Arduino這樣的開源硬體平臺允許協力廠商構建他們自己版本的相容板。Arduino板的LilyPad系列就是這樣的一種變體。該板專為電子紡織品設計,可以縫製到織物專案中。這使其成為服裝和智能服裝的理想選擇。

LilyPad MP3是LilyPad系列的另一個子變體,包括一個內置SD卡讀卡器、一個身歷聲音訊放大器和3.5毫米耳機插孔,以及五個預程式設計過的觸發器輸入。無需任何編碼設置,您最多可以連接五個觸發器來播放預先錄製的音效檔。如果您曾在迪士尼見過可以播放一組預先編寫好的語句的星球大戰衝鋒隊,那麼可以告訴您LilyPad MP3的功能與其非常相似。

雖然LilyPad MP3可以像任何Arduino板那樣程式設計,但是該項目的優點是您可以在不修改任何現有代碼的情況下對音板進行設置。它可以向您證明為您的專案儘早選擇合適的開發板有多麼值得。使用普通的Arduino Uno需要有一個外部擴展板才能具有與LilyPad MP3相同的功能,而且體積會更大。由於大多數Arduino板都可以與大部分草圖相容,因此找到滿足您需求的外形尺寸可以為您省去大量的時間和麻煩。

所需組件

對於本專案,我們將演示LilyPad MP3如何在麵包板上工作的,其實就是讓線路能夠正常工作。首先,您需要一個LilyPad MP3

您還需要1-5個按鈕(只需要一個,但是LilyPad最多支持5個)、一個揚聲器(或耳機)和3.5毫米電纜。

由於LilyPad旨在用於服裝,您可以選擇使用一些線和針將設備縫製到外套、衣服或任何其他您想要裝上音板的地方。使用導電佈線可以讓您將電線直接編織到衣服的面料中。

代碼

如上所述,LilyPad MP3上默認觸發器草圖的代碼預裝在了每塊板上。您不需要上傳此檔,但查看這些代碼來瞭解工作原理會很有幫助。

// “Trigger” example sketch for Lilypad MP3 Player
// Mike Grusin, SparkFun Electronics
// http://www.sparkfun.com

// This sketch (which is preloaded onto the board by default),
// will play a specific audio file when one of the five trigger
// inputs (labeled T1 – T5) is momentarily grounded.

// You can place up to five audio files on the micro-SD card.
// These files should have the desired trigger number (1 to 5)
// as the first character in the filename. The rest of the
// filename can be anything you like. Long file names will work,
// but will be translated into short 8.3 names. We recommend using
// 8.3 format names without spaces, but the following characters
// are OK: .$%’-_@~`!(){}^#&. The VS1053 can play a variety of
// audio formats, see the datasheet for information.

// By default, a new trigger will interrupt a playing file, except
// itself. (In other words, a new trigger won’t restart an
// already-playing file). You can easily change this behavior by
// modifying the global variables “interrupt” and “interruptself”
// below.

// This sketch can output serial debugging information if desired
// by changing the global variable “debugging” to true. Note that
// this will take away trigger inputs 4 and 5, which are shared
// with the TX and RX lines. You can keep these lines connected to
// trigger switches and use the serial port as long as the triggers
// are normally open (not grounded) and remain ungrounded while the
// serial port is in use.

// Uses the SdFat library by William Greiman, which is supplied
// with this archive, or download from http://code.google.com/p/sdfatlib/

// Uses the SFEMP3Shield library by Bill Porter, which is supplied
// with this archive, or download from http://www.billporter.info/

// 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 2012/11/01

// We’ll need a few libraries to access all this hardware!

#include // To talk to the SD card and MP3 chip
#include // SD card file system
#include // MP3 decoder chip

// Constants for the trigger input pins, which we’ll place
// in an array for convenience:

const int TRIG1 = A0;
const int TRIG2 = A4;
const int TRIG3 = A5;
const int TRIG4 = 1;
const int TRIG5 = 0;
int trigger[5] = {TRIG1, TRIG2, TRIG3, TRIG4, TRIG5};

// And a few outputs we’ll be using:

const int ROT_LEDR = 10; // Red LED in rotary encoder (optional)
const int EN_GPIO1 = A2; // Amp enable + MIDI/MP3 mode select
const int SD_CS = 9; // Chip Select for SD card

// Create library objects:

SFEMP3Shield MP3player;
SdFat sd;

// Set debugging = true if you’d like status messages sent
// to the serial port. Note that this will take over trigger
// inputs 4 and 5. (You can leave triggers connected to 4 and 5
// and still use the serial port, as long as you’re careful to
// NOT ground the triggers while you’re using the serial port).

boolean debugging = false;

// Set interrupt = false if you would like a triggered file to
// play all the way to the end. If this is set to true, new
// triggers will stop the playing file and start a new one.

boolean interrupt = true;

// Set interruptself = true if you want the above rule to also
// apply to the same trigger. In other words, if interrupt = true
// and interruptself = false, subsequent triggers on the same
// file will NOT start the file over. However, a different trigger
// WILL stop the original file and start a new one.

boolean interruptself = false;

// We’ll store the five filenames as arrays of characters.
// “Short” (8.3) filenames are used, followed by a null character.

char filename[5][13];

void setup()
{
int x, index;
SdFile file;
byte result;
char tempfilename[13];

// Set the five trigger pins as inputs, and turn on the
// internal pullup resistors:

for (x = 0; x <= 4; x++) { pinMode(trigger[x], INPUT); digitalWrite(trigger[x], HIGH); } // If serial port debugging is inconvenient, you can connect // a LED to the red channel of the rotary encoder to blink // startup error codes: pinMode(ROT_LEDR, OUTPUT); digitalWrite(ROT_LEDR, HIGH); // HIGH = off // The board uses a single I/O pin to select the // mode the MP3 chip will start up in (MP3 or MIDI), // and to enable/disable the amplifier chip: pinMode(EN_GPIO1, OUTPUT); digitalWrite(EN_GPIO1, LOW); // MP3 mode / amp off // If debugging is true, initialize the serial port: // (The ‘F’ stores constant strings in flash memory to save RAM) if (debugging) { Serial.begin(9600); Serial.println(F(“Lilypad MP3 Player trigger sketch”)); } // Initialize the SD card; SS = pin 9, half speed at first 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. } else 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 “)); Serial.print(result); Serial.print(F(“, halting.”)); } errorBlink(result); // Halt forever, blink red LED if present. } else if (debugging) Serial.println(F(“success!”)); // Now we’ll access the SD card to look for any (audio) files // starting with the characters ‘1’ to ‘5’: if (debugging) Serial.println(F(“reading root directory”)); // Start at the first file in root and step through all of them: sd.chdir(“/”, true); while (file.openNext(sd.vwd(), O_READ)) { // get filename file.getFilename(tempfilename); // Does the filename start with char ‘1’ through ‘5’? if (tempfilename[0] >= ‘1’ && tempfilename[0] <= ‘5’)
{
// Yes! subtract char ‘1’ to get an index of 0 through 4.

index = tempfilename[0] – ‘1’;

// Copy the data to our filename array.

strcpy(filename[index], tempfilename);

if (debugging) // Print out file number and name
{
Serial.print(F(“found a file with a leading “));
Serial.print(index + 1);
Serial.print(F(“: “));
Serial.println(filename[index]);
}
}
else if (debugging)
{
Serial.print(F(“found a file w/o a leading number: “));
Serial.println(tempfilename);
}

file.close();
}

if (debugging)
Serial.println(F(“done reading root directory”));

if (debugging) // List all the files we saved:
{
for(x = 0; x <= 4; x++)
{
Serial.print(F(“trigger “));
Serial.print(x + 1);
Serial.print(F(“: “));
Serial.println(filename[x]);
}
}

// Set the VS1053 volume. 0 is loudest, 255 is lowest (off):

MP3player.setVolume(10, 10);

// Turn on the amplifier chip:

digitalWrite(EN_GPIO1, HIGH);
delay(2);
}

void loop()
{
int t; // current trigger
static int last_t; // previous (playing) trigger
int x;
byte result;

// Step through the trigger inputs, looking for LOW signals.
// The internal pullup resistors will keep them HIGH when
// there is no connection to the input.

// If serial debugging is on, only check triggers 1-3,
// otherwise check triggers 1-5.

for(t = 1; t <= (debugging ? 3 : 5); t++)
{
// The trigger pins are stored in the inputs[] array.
// Read the pin and check if it is LOW (triggered).

if (digitalRead(trigger[t – 1]) == LOW)
{
// Wait for trigger to return high for a solid 50ms
// (necessary to avoid switch bounce on T2 and T3
// since we need those free for I2C control of the
// amplifier)

x = 0;
while(x < 50)
{
if (digitalRead(trigger[t – 1]) == HIGH)
x++;
else
x = 0;
delay(1);
}

if (debugging)
{
Serial.print(F(“got trigger “));
Serial.println(t);
}

// Do we have a valid filename for this trigger?
// (Invalid filenames will have 0 as the first character)

if (filename[t – 1][0] == 0)
{
if (debugging)
Serial.println(F(“no file with that number”));
}
else // We do have a filename for this trigger!
{
// If a file is already playing, and we’ve chosen to
// allow playback to be interrupted by a new trigger,
// stop the playback before playing the new file.

if (interrupt && MP3player.isPlaying() && ((t != last_t) || interruptself))
{
if (debugging)
Serial.println(F(“stopping playback”));

MP3player.stopTrack();
}

// Play the filename associated with the trigger number.
// (If a file is already playing, this command will fail
// with error #2).

result = MP3player.playMP3(filename[t – 1]);

if (result == 0) last_t = t; // Save playing trigger

if(debugging)
{
if(result != 0)
{
Serial.print(F(“error “));
Serial.print(result);
Serial.print(F(” when trying to play track “));
}
else
{
Serial.print(F(“playing “));
}
Serial.println(filename[t – 1]);
}
}
}
}
}

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
delay(250);
digitalWrite(ROT_LEDR, HIGH); // Turn LED OFF
delay(250);
}
delay(1500); // Longer pause between blink-groups
}
}

雖然逐行看下來代碼實在太多了(而且很多都在本項目中用不到),但是有幾個關鍵點值得強調。首先,有一些函數是Bill Porter的MP3播放機庫的一部分。這些函數通過VS1053B解碼晶片控制音訊檔。這些獨特的函數包括:

setVolume():該函數可以調節輸出音量的級別。
isPlaying():該函數可用於確定當前是否正在播放MP3(可用於中斷或停止播放)。
stopTrack():可以停止當前正在播放的曲目。
playMP3():該函數用於播放SD卡中的檔。默認草圖設置為播放與輸入所連接觸發器匹配的檔。

要在LilyPad上設置音軌,您最多可以將五個音訊檔添加到微SD卡中。儘管LilyPad的名稱是MP3,但是它支持多種音訊檔案格式。您可以在此處找到所支持的格式列表。您可以隨意命名檔,只要檔案名以1到5之間的數位開頭即可。

默認草圖將播放與啟動的觸發器對應的音訊檔。觸發器1(在LilyPad上標記為T1)將啟動以1開頭的音訊檔。觸發器2將播放以2開頭的檔,以此類推。只要您正確命名音訊檔,就不必對代碼進行任何處理。

接線

將所有元件連接到LilyPad很簡單。將微SD卡插入微SD卡槽,然後將揚聲器或一副耳機插入耳機插孔。如果您想將揚聲器直接連接到開發板,LilyPad還具有用於左右揚聲器的正負極觸點。
接下來,將按鈕連接到您要使用的每個觸發器。按鈕的一側連接到GND,而另一側連接到您要啟動的觸發器觸點。LilyPad上的觸發器觸點中有一個大圓形觸點和一個緊鄰大觸點的較小觸點。您可以將電線焊接到較小的觸點,或將電線纏繞在較大的觸點上。
按下按鈕,此時應該會觸發音訊檔的播放。確認接線正常後,您就可以嘗試使用導電佈線將接線縫入織物中了。