2016-02-27

Arduino Yun 筆記:以Arduino Yun 製作的 MP3 播放器

Arduino Yun 比較傳統的 Arduino 主板,最大優勢在於同時兼具兩個開發環境:
  1. Atmega32u4 處理器:
     Arduino Leonardo ,具有 20 個數位、類比、PWM 腳位以及 3 種序列通訊功能(Serial、I2C 、SPI),並可堆疊相容的 Shields 擴充板。 
  2. Atheros AR9331 處理器:
    使用 OpenWRT Linux 作業系統並內建 USB Host、Ethernet、WiFi 、Micro SD slot等周邊,另提供有 Python 2.7 作為 Linux 端的
    程式開發。
兩者間以 Hardware Serial port 相連,並以 Arduino 的 Bridge 程式庫,作為相互利用彼此資源的軟體介面,尤其 Linux 系統功能強大,雖是輕量版的 OpenWRT,但可運用的範圍與彈性已遠超過傳統的 Arduino 主板。

這次以 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 連接在一起就可以由播放器直接讀取按鍵值。  
● 播放器 (Arduino Yun) 的功能分別由 Linux 端(AR9331)與 Arduino 端(ATmega32u4)所組成

(一)   Linux 端:透過建立一個 player.py 的 Python 程式,提供給 Arduino 端的 Sketch 使用 Process 物件呼叫執行以下兩個功能:
  1. 回傳 MP3 檔 ID3 Tag 資訊。
  2. 控制開始或停止 MP3 檔的播放。
 在 OpenWRT Linux 要以 USB 音效卡播放MP3音樂,須安裝 kmod-usb-audio 套件以及 madplay 播放程式:
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
( player.py )

#!/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)


(二)   Arduino 端 是整個裝置的控制中心,主要功能包含:
  1. 首先以 FileIO 類別讀取 Linux 端 MicroSD 卡內存放 MP3 檔的目錄,並透過以下的 getSongFileName()showSongInfo() 函式 ,將第一首 MP3 檔的 ID3 Tag 資訊顯示在 LCD 上。
  2.   
    // 讀取第 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);
    }
    
  3. 依據讀取 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 = ' ';
}




沒有留言:

張貼留言