[第七課] DFPlayer開聲試啼 (UART)

當你開始踏上路途,路就會自己展現。"As you start to walk on the way, the way appears." ~Rumi
嗯,嵌入式系統的設計,必須學會『勇於嘗試,發揮各組件的特色。』;就像只會用高檔的食材稱不上大廚,而能將一般當季時令的食材,用到淋漓盡致的必定是名廚。這就是說:通常,Maker不會去開發系統裡的每一項關鍵組件;而是基於系統的需求,去想辦法找到合用的組件,設法將其進行完美的連接。

這組件『怎麼用?』只是起步。 DFPlayer Mini 是一個16-Pin的模組,接上電與4歐姆的喇叭;放入含有MP3音樂的SD卡,用模組上就有2個按鍵觸發,就可以聽到音樂的播放。如果,你的設計需求只是這樣,那其他組件就都不需要了,裝個外殼不就是所謂的 "MP3 Player" 了嗎!

就是因為這樣的系統需求太過陽春,而「可以選曲」、「可以控制播放與暫停」與「可以設定音量大小與模式」,這些才是ACCP的基本需求,『怎麼做到?』就是下一步。

所幸, DFPlayer Mini 有提供一組UART介面(Rx,Tx),來接受主控端下達預設的各項命令,與回應的訊息。使用上,和先前其他模組 RTC OLED 大致相同,先引入該程序與並在 setup() 開啟連接:

#include "DFRobotDFPlayerMini.h"
/* MP3Player via UART*/
DFRobotDFPlayerMini myDFPlayer;
......
while(!Serial1);
bool tt = myDFPlayer.begin(Serial1);
delay(1000);
if (!tt) {  //Use softwareSerial to communicate with mp3.
    sl3 = "-MP3/SD failed..";
    ss = "System is some fault...";
    digitalWrite(LED_R, LOW);
} else {
    sl3 = "-MP3/SD is ready..";
    delay(1000);
    myDFPlayer.setTimeOut(1000); // 設定通訊逾時為500ms
    myDFPlayer.volume(vol);  //Set volume value. From 0 to 30
    myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD);
    myDFPlayer.EQ(DFPLAYER_EQ_NORMAL); //
    myDFPlayer.playFolder(1, 1);
}

在連接完成後,開始進行初值的設定。 在這裡 myDFPlayer.playFolder(1, 1) 就是順利連接上時,聽到的第一次音樂播放;播放在Folder 01 裡的 001xxx.mp3 。往後,在ACCP系統裡90%都只會使用這個命令。是不是很直覺的控制方式,但要注意的如果下的指令是有要收回應訊息,必須至少等ㄧ秒 delay(1000) 再去收訊息。更詳盡的說明,請參考其 Wiki

說到這,可以下課了吧!?當然可以啊!......

但,就看你有沒有當名廚的決心?如果有,那請再看下去!

解決了,可以用主控來指揮 DFPlayer Mini 來播放。但是,下了指令卻沒有聽到任何聲音,怎麼辦?還有,當歌曲播完時,主控能知道嗎?

其實, DFPlayer Mini 提供的範例,並沒有詳盡的控制流程說明;在其播放音樂的期間,你不去詢問,是沒有辦法知道目前的狀況;而只有在播放完畢時,才會有訊息丟出來。

另外,我們自己編的MP3檔名的編號,和放進SD卡內時 DFPlayer Mini 索引的FAT檔案編號是不一致的;也就是說在Folder 01 裡的 001xxx.mp3 ,並不是它所認知的編號#1。

怎麼解決?』就是精進的下一步。在 <setPlayer.ino> ,是加料對 DFPlayer Mini 的設定與狀況檢知:

  •  setEQ  是在每次有新的X-DISC播放時,依其設定調整EQ的模式。

  •  smartVol  是可以根據現在的時間,適當調整一開始時的音量大小。

  •  chkState  是主動詢問播放器目前的狀態:0 :NO_TRACK; 1 :PLAYING; 2 :PAUSE; 3 :STOP; -1 :NACK;
解決上面的第一個問題,就是在 playFolder 後,等待一秒後進行 chkState

myDFPlayer.playFolder(numFolder, numTrack);delay(1000);
if (chkState() > 0) {
    f_nowPlaying = true;
    ss = "play all folder.."; draw(TRK, 0x01);
    sysDelay = 2000;
} else { //no such track;
    f_nowPlaying = false;
    ss = "Can't play.."; draw(NG_ERR, 0x03);
    sysDelay = 500;
}
而第二個問題,就是在  NOP  狀態下,順便看一下播放器有無回應的訊息:

case NOP: {
  lowPowerHandler();
  digitalWrite(LED_R, HIGH);
  digitalWrite(LED_B, HIGH);
  rfOff();
  if (myDFPlayer.available()) {
     delay(100);
     uint8_t type = myDFPlayer.readType();
     int vv = myDFPlayer.read();
                          ...... 
     switch (type) {
       case DFPlayerPlayFinished: {
          f_nowPlaying = false;
          st = FW_VERSION;
          ss="Ready to scan..[B:" + String(battLife) + "%]";
          draw(SCAN, 0xFF);
          sysDelay = 500;
          break;
       }
       default: {
          break;
       }
     }
  }
 ......

就是在發現播放器播完曲子,回應 DFPlayerPlayFinished 訊息時,讓系統回復 f_nowPlaying = false 到正常的狀態。

當然,在想要精進的這段過程,相信更能體會『柳暗花明又一村』的喜悅了。

留言

熱門文章