- Atmega32u4 處理器:
同 Arduino Leonardo ,具有 20 個數位、類比、PWM 腳位以及 3 種序列通訊功能(Serial、I2C 、SPI),並可堆疊相容的 Shields 擴充板。 - Atheros AR9331 處理器:
使用 OpenWRT Linux 作業系統並內建 USB Host、Ethernet、WiFi 、Micro SD slot等周邊,另提供有 Python 2.7 作為 Linux 端的程式開發。
這次以 Arduino Yun 製作的 MP3 播放器,即運用 Linux 對 USB 裝置的支援能力,使用 USB 音效卡播放 Micro SD 卡內的 MP3 檔,並透過 I2C 外接 Arduino Uno + LCD Keypad shield 作為控制面板,可以顯示 MP3 檔 ID3 tag 的曲目與演出者資訊,以及透過操作按鍵逐一瀏覽及選擇歌曲播放,功能如同一般的 MP3 播放器。
系統架構
如上圖,整體裝置共分成兩個部份:- 上面是控制器,作為操作面板,包含 Arduino UNO 加上 LCD Keypad Shield。
- 下面是播放器,負責控制 MP3 播放以及提供 ID3 Tag 資訊,包含 Arduino Yun 加上 USB音效卡與喇叭。
- 兩者間以 I2C 方式連接,播放器是 Master,控制器為 Slave。
- I2C 接線方式:由 Arduino Yun 的 D2(SDA)、 D3(SCL)、5V、GND 對應連接 Arduino UNO 的 A4、A5、Vin( 或 5V )、GND。
- 此外,LCD Keypad shield 上面的 5 個按鍵是透過 analogRead(A0 pin) 的值來判斷,所以將兩者的 A0 pin 連接在一起就可以由播放器直接讀取按鍵值。
(一) Linux 端:透過建立一個 player.py 的 Python 程式,提供給 Arduino 端的 Sketch 使用 Process 物件呼叫執行以下兩個功能:
- 回傳 MP3 檔 ID3 Tag 資訊。
- 控制開始或停止 MP3 檔的播放。
opkg update
opkg install kmod-usb-audio madplay
※ player.py 的程式碼如下,共有三種執行選項:
- 回傳指定 MP3 檔 ID3 Tag 的 曲目(title) 與 演出者(performer)資訊:
./player.py -info "MP3 檔名" - 開始播放指定的 MP3 檔:
./player.py -play "MP3 檔名" - 停止 MP3 檔的播放:
./player.py -stop
#!/usr/bin/python
import id3reader, os, signal, sys
mp3opt = "-a -5"
if len(sys.argv) > 1:
if sys.argv[1] == "-info":
try:
id3 = id3reader.Reader(sys.argv[2])
print id3.getValue("title")
print id3.getValue("performer") + " - " + id3.getValue("album")
except IOError:
print
print
elif sys.argv[1] == "-play":
os.system("madplay " + mp3opt + " \"" + sys.argv[2] + "\" &> /dev/null")
elif sys.argv[1] == "-stop":
pid = os.popen("pgrep madplay").read()
os.kill(int(pid), signal.SIGKILL)
- 首先以 FileIO 類別讀取 Linux 端 MicroSD 卡內存放 MP3 檔的目錄,並透過以下的 getSongFileName() 與 showSongInfo() 函式 ,將第一首 MP3 檔的 ID3 Tag 資訊顯示在 LCD 上。
// 讀取第 songNo 首 MP3 檔的檔名 void getSongFileName() { songFileName = ""; File dirH = FileSystem.open("/mnt/sda1/project3/mp3"); dirH.rewindDirectory(); for(int xi=1; xi<=songNo; xi++) { File fileH = dirH.openNextFile(); if(fileH) { if(xi == songNo) { songFileName = fileH.name(); } fileH.close(); } } dirH.close(); } // 呼叫 player.py 程式讀取指定 MP3 檔的 ID3 tags 資訊並顯示在 LCD shield void showSongInfo() { Process p; p.runShellCommand("/mnt/sda1/project3/player.py -info \"" + songFileName + "\""); String songTitle = p.readStringUntil('\n'); String performer = p.readStringUntil('\n'); p.close(); if((playing.running()) && (songNo == playing_songNo)) { songTitle = "*" + songTitle; } clearMsgOnLCD(100); char printMsg[16]; songTitle.toCharArray(printMsg,16); showMsgOnLCD(CMD_DISPLAY_ON_LINE1, printMsg, 100); performer.toCharArray(printMsg,16); showMsgOnLCD(CMD_DISPLAY_ON_LINE2, printMsg, 100); }
- 依據讀取 LCD Keypad Shield 的按鍵值,執行相對應的功能:
- 瀏覽上一首/下一首,並透過上面的 getSongFileName() 與 showSongInfo() 函式 ,將目前的 MP3 檔的 ID3 Tag 資訊顯示在 LCD 上。
- 播放 :先以 Process.running() 函式判斷目前是否正在播放歌曲中,若是的話,則以 Process.runShellCommand() 函式呼叫 player.py -stop 中止播放,接著再以 Process.runShellCommandAsynchronously() 函式呼叫 player.py -play "MP3 檔名" 開始播放。
else if(getKey == PLAY_SONG) { if(playing.running()) { Process p; p.runShellCommand("/mnt/sda1/project3/player.py -stop"); p.close(); } playing.runShellCommandAsynchronously("/mnt/sda1/project3/player.py -play \"" + songFileName + "\""); playing_songNo = songNo; getSongFileName(); showSongInfo(); }
● 控制器 (Arduino UNO + LCD Keypad Shield) 的功能是作為使用者的操作介面,提供以 I2C Save 方式接收來自 I2C Master 裝置( 播放器 ) 所傳送過來的字串資訊,以顯示於 16x2 的 LCD 面板。
(Arduino_I2CSlave_LCDKeypad_Shield.ino)
#include <LCD4Bit_mod.h>
#include <Wire.h>
#define LCD_SHIELD 0x06
#define CMD_DISPLAY_ON_LINE1 0x01
#define CMD_DISPLAY_ON_LINE2 0x02
#define CMD_CLEAR 0x03
LCD4Bit_mod lcd = LCD4Bit_mod(2); // 2 Lines to display
void displayLCD(int xline, char* xtext) {
char xmsg[80];
sprintf(xmsg, "<Line %d> %s", xline, xtext);
Serial.println(xmsg);
lcd.cursorTo(xline, 0);
lcd.printIn(xtext);
}
void setup() {
lcd.init();
Wire.begin(LCD_SHIELD); // I2C Slave address
Wire.onReceive(receiveEvent);
lcd.clear();
lcd.printIn("Ready ...");
Serial.begin(9600);
Serial.println("Ready..");
}
void loop() {
delay(100);
}
void receiveEvent(int howMany) {
int idx;
char text[17];
byte cmd = Wire.read();
if(cmd == CMD_CLEAR) {
lcd.clear();
}
else if((cmd == CMD_DISPLAY_ON_LINE1) || (cmd == CMD_DISPLAY_ON_LINE2)) {
while(Wire.available()) {
char x=Wire.read();
delay(1);
if(idx<16) {
text[idx] = x;
idx++;
}
else break;
}
if(cmd == CMD_DISPLAY_ON_LINE1)
displayLCD(1, text);
else
displayLCD(2, text);
}
cmd = ' ';
}
沒有留言:
張貼留言