創作內容

6 GP

Arduino控制DFPlayer Mini Player之傻瓜篇

作者:[BOT]OTAKU│2020-05-02 12:34:05│巴幣:12│人氣:10693
壹、前言

讓Arduino撥放MP3或WAV的方式有很多種,如果拿"Arduino MP3 Player"這類關鍵字去餵狗,最大宗的答案莫非是DFPlayer Mini這個小零件,這零件呢可以裝上按鈕獨立使用,亦可被動地接收Serial Port的訊號,使用其他零組件如ESP8266或HC-05/06來控制,當然今天的主題是拿Arduino來做基本控制,就不多談了。

再來,用"Arduino DFPlayer Mini"之類的關鍵字去餵狗,沒錯,是可以得到很多的範例與教程,但是初心者順藤摸瓜後往往沒有頭緒,為什麼play()指令不會執行?為什麼要掛delay()把執行緒卡得死死的?

今天這篇說是基本篇,其實一點都不基本,因為我懶惰...這篇將會直接省略很多DFPlayer的簡介,因為網路上通通都有,寫也是多餘直接講重點。

貳、材料與方法

從基本開始,先用Arduino Uno,等翅膀硬了再去用nano或mini,達到真正迷你化的撥放器(其實大同小異啦,只是先練練身體)。從其他網路資源搜尋後相信心裡應該知道要準備什麼,你會需要:

1. Arduino Uno 一台
2. DFPlayer Mini 一組
3. 低容量的microSD一張,我是拿古早行車紀錄器退下來的Kingston 8GB來玩
4. 1K歐姆電阻一個,接在Arduino 用來TX的腳位可以減少雜音
5. 小喇叭一個,注意不是蜂鳴器(Buzzer)是喇叭 (Speaker ),因為在購物網用Arduino + 喇叭當關鍵字很容易噴出蜂鳴器,千萬不要買錯了,正常喇叭都會標示0.5W 8歐姆之類的規格,買那個才對。
6. 各種杜邦線、電線

電腦方面除了Arduino IDE之外,你還得準備幾首要撥放的mp3,將它們取名為 0001.mp3、0002.mp3等,其名稱開頭一定要是小於256的四碼數字,亦即0001~0255,後面則可以任意加上名稱註解,建議英文比較不會出錯,例如:
0001_The_First_Song.mp3
這格式是可以正常識別的。
將記憶卡格式化成FAT或FAT32格式後把他們裝進去就可以了,坊間有其他教程認為要創建一個叫做mp3的資料夾,然後把音樂放在裡面,我自己都試過,差別在使用的指令不同,都可以正常用。
還有,昏倒十系統已經無法將磁碟格式化成FAT格式了,需要使用如minitool Partition Wizard或EaseUS Partition Master之類的第三方軟體來做。

準備好軟體工作後,要來接硬體的東西了。
接線圖如下:


有個小重點:因為Arduino要和DFPlayer交換資訊,所以Arduino要用來當TX的腳位必須接在DFPlayer Mini的RX上,反過來,Arduino的RX要接在DFPlayer Mini的TX上,否則沒辦法用。
然後本文使用SoftwareSerial,所以TX和RX可以自己指定,就不侷限在Arduino Uno身上標著TX、RX的Pin 0、1上了,相當方便。

參、痛苦編程篇

有關DFPlayer Mini的Arduino函式庫相當多(感謝眾網友的點滴灌溉啊),比較常接觸到的為DFRobotDFPlayerMini.h 與 DFPlayerMini_Fast.h 這兩包,今天用前面那一包示範,後面那包雖然號稱可以使用isPlaying()這個函式回傳一組是否正在撥放的Boolean,不過個人發現用「硬」的方式比較直觀,除非腳位很不夠用不然來硬的比較爽,日後再談。

下載好這個函式庫並扔到Arduino IDE後,先打開"GetStarted"這個範例來瞧瞧。

=======<分隔線啦>=======

***************************************************
This example shows the basic function of library for DFPlayer.

Created 2016-12-07
By [Angelo qiao](Angelo.qiao@dfrobot.com)

GNU Lesser General Public License.
See <http://www.gnu.org/licenses/> for details.
All above must be included in any redistribution
****************************************************/

/***********Notice and Trouble shooting***************
1.Connection and Diagram can be found here
<https://www.dfrobot.com/wiki/index.php/DFPlayer_Mini_SKU:DFR0299#Connection_Diagram>
2.This code is tested on Arduino Uno, Leonardo, Mega boards.
****************************************************/

#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"

SoftwareSerial mySoftwareSerial(11, 10); // RX, TX
DFRobotDFPlayerMini myDFPlayer;
void printDetail(uint8_t type, int value);

void setup()
{
  mySoftwareSerial.begin(9600);
  Serial.begin(115200);
  
  Serial.println();
  Serial.println(F("DFRobot DFPlayer Mini Demo"));
  Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
  
  if (!myDFPlayer.begin(mySoftwareSerial)) {  //Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while(true){
      delay(0); // Code to compatible with ESP8266 watch dog.
    }
  }
  Serial.println(F("DFPlayer Mini online."));
  
  myDFPlayer.volume(10);  //Set volume value. From 0 to 30
  myDFPlayer.play(1);  //Play the first mp3
}

void loop()
{
  static unsigned long timer = millis();
  
  if (millis() - timer > 3000) {
    timer = millis();
    myDFPlayer.next();  //Play next mp3 every 3 second.
  }
  
  if (myDFPlayer.available()) {
    printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states.
  }
}

void printDetail(uint8_t type, int value){
  switch (type) {
    case TimeOut:
      Serial.println(F("Time Out!"));
      break;
    case WrongStack:
      Serial.println(F("Stack Wrong!"));
      break;
    case DFPlayerCardInserted:
      Serial.println(F("Card Inserted!"));
      break;
    case DFPlayerCardRemoved:
      Serial.println(F("Card Removed!"));
      break;
    case DFPlayerCardOnline:
      Serial.println(F("Card Online!"));
      break;
    case DFPlayerUSBInserted:
      Serial.println("USB Inserted!");
      break;
    case DFPlayerUSBRemoved:
      Serial.println("USB Removed!");
      break;
    case DFPlayerPlayFinished:
      Serial.print(F("Number:"));
      Serial.print(value);
      Serial.println(F(" Play Finished!"));
      break;
    case DFPlayerError:
      Serial.print(F("DFPlayerError:"));
      switch (value) {
        case Busy:
          Serial.println(F("Card not found"));
          break;
        case Sleeping:
          Serial.println(F("Sleeping"));
          break;
        case SerialWrongStack:
          Serial.println(F("Get Wrong Stack"));
          break;
        case CheckSumNotMatch:
          Serial.println(F("Check Sum Not Match"));
          break;
        case FileIndexOut:
          Serial.println(F("File Index Out of Bound"));
          break;
        case FileMismatch:
          Serial.println(F("Cannot Find File"));
          break;
        case Advertise:
          Serial.println(F("In Advertise"));
          break;
        default:
          break;
      }
      break;
    default:
      break;
  }
  
}

=======<分隔線啦>=======

由於Software Serial使用了Baud Rate 9600,所以Arduino與PC之間的Serial Port改成Baud Rate 115200,在COM Port監控視窗那邊記得要改Baud Rate,改成115200才會看到即時資訊喔。
這坨東西呢,會每三秒鐘換一首歌來撥放,同時會透過Serial Port發送撥放器即時訊息。
一開始就看這坨,一點都不像Get Started,相信第一次玩的人已經需要呼吸器了。
我們從零開始,介紹幾個最基本會瘋狂用到的指令:

volume()->控制音量,括弧內數值從0~30,代表沒聲音到最大聲。
volumeUp()->增加音量一格,括弧內不用數字就可以用。
volumeDown()->降低音量一格,括弧內不用數字就可以用。
play()  ->撥放特定某一首歌一次,括弧裡面為該歌的編號(需為正整數)
loop()  ->重複撥放特定某一首歌,括弧裡面為該歌的編號(需為正整數)
next() ->撥放下一首歌,括弧裡面不用指定數字就可以用

所以牛刀小試一下,如果我希望撥放第一首歌一次,我該怎麼寫呢?

=======<分隔線啦>=======

#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"

SoftwareSerial mySoftwareSerial(11, 10); // RX, TX
DFRobotDFPlayerMini myDFPlayer;

void setup()
{
  mySoftwareSerial.begin(9600);
  Serial.begin(115200);
  
  Serial.println();
  Serial.println(F("DFRobot DFPlayer Mini Demo"));
  Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
  
  if (!myDFPlayer.begin(mySoftwareSerial)) {  //Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while(true){
      delay(0); // Code to compatible with ESP8266 watch dog.
    }
  }
  Serial.println(F("DFPlayer Mini online."));
  
  myDFPlayer.volume(10);  //Set volume value. From 0 to 30
  myDFPlayer.play(1);  //Play the first mp3
}

void loop()
{

}

=======<分隔線啦>=======

沒了,就這樣?
沒錯,loop()裡面真的什麼都沒有。

那...如果我希望歌曲1和歌曲2輪流放,其他歌都不要呢?
我們來一段經典的錯誤範例

=======<分隔線啦>=======

//This is NOT a workable code.
//Demonstration Only!

#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"

SoftwareSerial mySoftwareSerial(11, 10); // RX, TX
DFRobotDFPlayerMini myDFPlayer;

void setup()
{
  mySoftwareSerial.begin(9600);
  Serial.begin(115200);
  
  Serial.println();
  Serial.println(F("DFRobot DFPlayer Mini Demo"));
  Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
  
  if (!myDFPlayer.begin(mySoftwareSerial)) {  //Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while(true){
      delay(0); // Code to compatible with ESP8266 watch dog.
    }
  }
  Serial.println(F("DFPlayer Mini online."));
  
  myDFPlayer.volume(10);  //Set volume value. From 0 to 30
}

void loop()
{
  myDFPlayer.play(1);
  myDFPlayer.play(2);
}

=======<分隔線啦>=======

聰明如你發現什麼問題了嗎?沒錯,完全沒有聲音,直到按下Arduino Uno身上的Reset鈕才會發出聲音一次。這到底是為什麼?
因為void loop()裡面的東西會在每一個CPU Cycle重複被執行,所以上面那段呢,可以視為Arduino拼命地打訊號給DFPlayer Mini,讓它無所適從。

終極概念:只有在想要傳訊息給DFPlayer的時候才傳,其他時間都封印住。

由此可猜,坊間常見的做法是什麼呢?沒錯,就是加上Delay();,看歌有幾秒就Delay幾秒,如下所示,假設0001和0002兩首歌分別為30秒和60秒:
=======<分隔線啦>=======

//Switching Between 2 Songs

#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"

SoftwareSerial mySoftwareSerial(11, 10); // RX, TX
DFRobotDFPlayerMini myDFPlayer;

void setup()
{
  mySoftwareSerial.begin(9600);
  Serial.begin(115200);
  
  Serial.println();
  Serial.println(F("DFRobot DFPlayer Mini Demo"));
  Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
  
  if (!myDFPlayer.begin(mySoftwareSerial)) {  //Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while(true){
      delay(0); // Code to compatible with ESP8266 watch dog.
    }
  }
  Serial.println(F("DFPlayer Mini online."));
  
  myDFPlayer.volume(10);  //Set volume value. From 0 to 30
}

void loop()
{
  myDFPlayer.play(1);
  delay(30000);
//The length should be as long as the song itself.
  myDFPlayer.play(2);
  delay(60000);
}

=======<分隔線啦>=======
大哥,可是這樣我每次要指定撥放某一首歌,同時要讓Arduino做其他事情,有那個Delay不就不行了嗎?況且每次要先算好一首歌有多久,很不科學啊,同時Arduino沒有Threading功能,怎麼辦?

首先介紹比較不科學也不好用的方法,我還是得先知道曲目1和2的總時間長度,在此用3秒鐘和5秒鐘來示範。

概念是這樣:在特定時間內只讓Arduino傳輸一次play指令,這可以用if迴圈構成指標寫法配合系統時間millis()來達成:

=======<分隔線啦>=======
//Demonstration
//Switching Between 2 different songs which are 5 and 3 sec.
#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"

SoftwareSerial mySoftwareSerial(11, 10); // RX, TX
DFRobotDFPlayerMini myDFPlayer;

int index = 1;
int sw = 3000;

void setup()
{
  mySoftwareSerial.begin(9600);
  Serial.begin(115200);
  
  Serial.println();
  Serial.println(F("DFRobot DFPlayer Mini Demo"));
  Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
  
  if (!myDFPlayer.begin(mySoftwareSerial)) {  //Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while(true){
      delay(0); // Code to compatible with ESP8266 watch dog.
    }
  }
  Serial.println(F("DFPlayer Mini online."));
  
  myDFPlayer.volume(10);  //Set volume value. From 0 to 30
}

void loop()
{
  static unsigned long timer = millis();
  
  if (millis() - timer > sw) {
      timer = millis();
      if (index == 2){
         index = 1;
         sw = 3000;
         Serial.println("Now sw =3000, index = 2");
    
        }
     else {
         index = 2;
          sw = 5000;
         Serial.println("Now sw=5000, index = 1");
         }
      myDFPlayer.play(index);
  }
}

=======<分隔線啦>=======
可是這樣還是好麻煩喔,有沒有什麼方式可以知道DFPlayer已經撥完指定的歌曲,執行下一道指令,不會被Delay()給耽誤呢?
當然有,今天要來硬的,來人啊!對DFPlayer用刑!(X
歐不是,請再準備一條杜邦線,DFPlayer mini的VCC腳位對面有一個Busy腳位,平常如果沒事的時候處於HIGH的狀態,反之若正在撥放,會是LOW的狀態。
知道了這點之後,吾人即可指定一隻針腳作為digitalRead()使用,利用if迴圈判斷該腳位是否為LOW,就能知道音樂是不是播完了。這樣就不用去了解歌曲到底有多長了,反正撥放完畢後DFPlayer Mini會主動輸出低電位,是不是很方便呢?
於是乎接線變成這樣:


一樣,用剛剛的範例來改寫:
=======<分隔線啦>=======
//Demonstration
//Switching Between 2 different songs with PIN busy on DFPlayer Mini
#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"

SoftwareSerial mySoftwareSerial(11, 10); // RX, TX
DFRobotDFPlayerMini myDFPlayer;

int index;

void setup()
{
  mySoftwareSerial.begin(9600);
  Serial.begin(115200);
  pinMode(12, INPUT);
  //指定PIN12作為INPUT讀取Busy的高低電位  ,並將PIN 12與DFPlayer的Busy腳位連接

  Serial.println();
  Serial.println(F("DFRobot DFPlayer Mini Demo"));
  Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
  
  if (!myDFPlayer.begin(mySoftwareSerial)) {  //Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while(true){
      delay(0); // Code to compatible with ESP8266 watch dog.
    }
  }
  Serial.println(F("DFPlayer Mini online."));
  
  myDFPlayer.volume(10);  //Set volume value. From 0 to 30
}

void loop()
{
  if (digitalRead(12)==HIGH){  
    if (index == 1){
    myDFPlayer.play(1);
    index = 2;
    }
    else {
   myDFPlayer.play(2);
   index = 1;
   }
  }
}
=======<分隔線啦>=======
是不是?
簡單很多齁。
知道了這個就可以做出更多組合了呢!


====更正啟事====
由於上傳圖片後才發現我畫錯了,所以直接改正程式碼RX、TX的部分
原始示範為(10, 11)作為RX、TX
本文以(11, 10)作為RX、TX
特此告知更正,謝謝大家
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=4768572
All rights reserved. 版權所有,保留一切權利

相關創作

留言共 8 篇留言

影子攔球員
想請問大大為什麼我的不管怎麼改serial port都是出現以下訊息呢?
Unable to begin:
1.Please recheck the connection!
2.Please insert the SD card!

05-21 21:52

[BOT]OTAKU
這個我遇過三種情況:
1. 杜邦線斷腸(真的,換一條就好了)
2. TX和RX接反(Software Serial指定的RX要接在DFPlayer的TX,反之亦然)
3. SD卡的問題(換一張SD卡或橡皮擦擦一擦金手指)
可以PO一下開發板的照片或接線圖比較好查看是哪裡錯了
05-22 10:23
影子攔球員
我今天早上試的時候成功了!
我昨天有查到好像這個模組的記憶卡跟tx rx 很容易接觸不良 (原本的是舊的 已經又買了兩組新的備用

05-22 10:38

[BOT]OTAKU
有看過老外PO開箱文說焊點不良,自己焊一焊就復活了
05-22 10:40

大大你好
想請教您一個問題
我照著您的第二個程式改出了一個可以隨機播放的程式
myDFPlayer.play( songnumber ); 那個songnumber 數值為一到十的隨機數
且我的記憶卡裡也有0001-0010十個音源檔案
問題出在當songnumber為雙數時是不會播放的
且檔案0001-0005會被分配至1、3、5、7、9 而檔案0006-0010就好像不存在依樣
Ex. songnumber = 5 機器 會撥出檔案0003 而 songnumber = 6 則不會撥出聲音
如果大大方便的話 再煩請替我解惑 謝謝

05-25 02:43

[BOT]OTAKU
您好,可以貼一下程式碼供參考嗎?05-25 10:44
poyu39
你好 我使用第二個範例程式,不插小喇叭都可以顯示online,但是一插上後就會報錯?

02-07 20:42

[BOT]OTAKU
範例2主要的概念是將void loop()裡面的東西全部拿掉,只留下void setup()裡面唯一的play(),試試看將第一個範例的void loop()裡面的東西全部刪掉,其餘的部分都不要動到,看看結果如何。02-10 01:14
路過的路人
請問一下大大,我在上傳大大貼文的最後一個程式時,上傳後只有非常小聲的沙---沙---聲
請問可以幫忙解惑嗎?喇叭已經測試過沒問題,GetStarted範例也能正常運行

08-04 21:42

[BOT]OTAKU
可能要來點試錯法了,先把第二個if迴圈給拿掉,index不要用了看看能不能循環撥放第一首歌,如果不行要利用GetStarted範例偵測一下BUSY腳位輸出長什麼樣子。手上剛好一無所有沒辦法測試。08-05 17:13
路過的路人
測試完了,今天測試的時候發現其實並不是沒聲音,只是跳太快而以
1號歌曲一播出0.幾秒便會直接跳到2號歌,二號歌曲也是一樣播0.幾秒便會跳回第一首
無限反覆,而且播放時dfplayer的紅燈會不停閃爍
但今日也發現用若是改用前面delay的方式能正常撥放

08-06 00:50

wei
請問為何我用範例的GetStarted可以成功撥放,
但是當我設定成撥放第一首歌一次的時候就沒有聲音,只會出現:
Unable to begin:
1.Please recheck the connection!
2.Please insert the SD card!

除此之外,player上面的藍色燈泡也沒有亮起來。
謝謝您的分享~

12-29 16:47

神聖彗星反射力量
我想請教一個問題
請問這個配置要如何連結多顆喇叭呢?
目前還屬於arduino初學者,要製作作品
找了很多文章都沒有找到要如何連結多顆喇叭
可以全部插在麵包板的同一排還是需要其他零件呢?

02-21 19:38

[BOT]OTAKU
HiHi!抱歉沒想到還有回應,太少上來看看了,想不到都隔了一年了才看到。02-24 12:00
[BOT]OTAKU
因為我也沒試過多個喇叭,且DFPlayer做好還是一組一個喇叭,最佳解應該是用Arduino控多個DFPlayer。02-24 12:00
[BOT]OTAKU
https://forum.arduino.cc/t/how-to-use-two-dfplayer/1030633/402-24 12:00
我要留言提醒:您尚未登入,請先登入再留言

6喜歡★kevin020792 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:耗時9年的L4D2車諾比... 後一篇:87也要用Mask RC...

追蹤私訊切換新版閱覽

作品資料夾

robert286 ლ(´•д• ̀ლ
ლ(´•д• ̀ლ看更多我要大聲說9小時前


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】