勝手な電子工作・・

勝手なオリジナル電子工作に関する記事を書きます

睡眠タイマー付きMP3プレーヤをArduinoで作ってみる(・・?

f:id:a-tomi:20210126164901j:plain

巣籠りが長引き、運動不足が続くと眠りにくくなりがちです。

人の話を聴いていると、私は眠くなる癖(かつて会議で眠る癖がついたからかも^^;)があるため、最近は23:05から始まる「NHKラジオ深夜便」の話し声を耳元で聞きながら眠ったりします。小型のラジオについているスリープタイマーを30から40分にセットして、いつ消えたのかに気づかずに寝ています。

とはいっても、人の話し声よりは静かな音楽が良いという人は多いでしょうね。妻もそうですが。最近はYouTubeで「眠るための音楽」がやけにたくさん出ていますが、世界的に今はそういう傾向があるのかもしれません。しかし、PCなどをつけっぱなしで眠るわけにもいきませんので、MP3等に記録してかけるのが良いかも。AlexaやGoogleHomeでそういう音楽を頼むのも一法ですが、時々目が覚める音楽に変わっていたりもしますからね^^

ところが探しても睡眠タイマーがついているMP3プレーヤーは、ありそうで見当たりません。よし、それなら作るか・・。

うってつけのデバイスがDFPlayerミニのようです、たぶん。

MP3かWAVのファイルをマイクロSDに入れておけば再生しDecodeしDA変換、さらにそれを内蔵Digital-Amp.(3W)で鳴らしてくれるという、優れものに見えます。これならすぐできそうですが、海外ネットではなぜか驚くべき廉価です。送料込みで10個で1500円ほど、つまり1個150 円ほど!

f:id:a-tomi:20210126171902j:plain

ずいぶん小さいですね。この筐体にデコーダーもDACもアンプも内蔵しているとは凄い・・・

f:id:a-tomi:20210126172058j:plain

裏はこうなっています。ヘダーピンは最初からついてきます。

f:id:a-tomi:20210126172226j:plain

ほとんどがソフトウェアでできているとはいえ、よくぞこんな小さくまとめたこと!

音量調整やスリープタイマーをつけたいわけなので、マイコンからシリアル通信でコントロールします。

次のように回路を考えて、ArduinoIDEでプログラミングをします(UNOで作ってATmega328pだけを使うわけですが)。ハードウェアシリアルのデバッグメッセージを使いたいので、DFPlayerとの通信にはソフトウェアシリアルを使います。

f:id:a-tomi:20210126172459j:plain

上の回路でMOSI(master out, slave in)のラインだけに1KΩを入れるのは、DFPlayerは5V電源OKなのですが扱う信号レベルが3.3Vなので、Arduino(ATmega328p)から5V信号を送ることによる、まさかのLatch-up等を防ぐためです。逆にMISO(master in, slave out)のラインは抵抗は不要、むしろ入れてはいけませんね(5V駆動のArduino側でHighの受信信号が判定しにくくなるからです)。

UNOでプログラミングしてATmega328マイコンだけを取り出す一番簡単で勝手な方法は、次の記事の前のほうに詳しく載せてありますので、必要な方はご参照ください。

Zoom用かんたん操作ボタンをArduino-UNOで作る(その1) - 勝手な電子工作・・

なお、新たなATmega328pを使うときは事前にArduinoブートローダーを入れる必要がありますが、その方法はネットに沢山出ていますからここでは省略させていただきます

。DFPlayerのピン配置は次です。

f:id:a-tomi:20210126173302j:plain

そしてArduino-UNOで次のようにコーディングしました。DFplayerが再生している間はArduino-loopが動いていますが、この中にはDelay()を入れるわけにはいきません。なぜなら、そのDelayの間にDFPlayer側からArduinoのソフトウェアRxにくるMessageを見逃しそのタイムアウト多数反復によるDFPlayer異常終了を起こさせないためです。(2021.1.30 Bugを直しましたm(_ _)mスマン)

/***************************************************
  MP3 player with Sleep-timer & Sound-volume-control,
    using DFplayer's GNU-GPL as described below.
  How to use:
    Insert micro SD card containing mp3 or wav files
    in playing sequence. Sound-volume and sleep-timer
    (30,60 or 90 minutes) controls are available.
  Revisions:
    Initial Version-00 Jan.23,2021 (c)Akira Tominaga
    V01: Timer from VR to swithes.    Jan.24, 2021
    V02; setSlpt() function debugged. Jan.30, 2021
  ==================== GNU GPL ====================
  DFPlayer - A Mini MP3 Player for Arduino
  <https://www.dfrobot.com/product-1121.html>
  Examples by Angelo.qiao@dfrobot.com 2016-12-07 used,
  with GNU Lesser General Public License.
  See <http://www.gnu.org/licenses/> for details.
  The above must be included in any redistribution.
  ****************************************************/
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"
#define sRx 10            // MISO ( device's Tx )
#define sTx 11            // MOSI ( device's Rx )
SoftwareSerial sSer(sRx, sTx);
DFRobotDFPlayerMini dfP;

#define Tmr30 2           // sleep timer 30min
#define Tmr60 3           // sleep timer 60min
#define ledG 6            // green LED for Sleep-timer
unsigned long slpT;       // sleep timer in milli second
uint16_t vVol;            // sound volume of DFPlayer

void setup() { // *** Arduino setup ***
  sSer.begin(9600);
  Serial.begin(9600);
  pinMode(ledG, OUTPUT);  // LED to show Timer-on
  digitalWrite(ledG, HIGH);
  delay(300);             // indicate that I am awake
  digitalWrite(ledG, LOW);
  pinMode (Tmr30, INPUT_PULLUP); // timer for 30 min.
  pinMode (Tmr60, INPUT_PULLUP); // timer for 60min
  Serial.println(F("Starting DFP"));
  while (!dfP.begin(sSer)) {
    Serial.print(".");
    delay(100);
  }
  Serial.println(F("DFP connected"));
  setSlpT();              // chk & set sleep-timer
  Serial.print(F("Sleep-timer mS = "));
  Serial.println(slpT);
  setVol();               // chk & set sound volume
  Serial.print(F("Sound Vol. = "));
  Serial.println(vVol);
  // Set Equalizer by deleting required line's "//"
  //  dfP.EQ(DFPLAYER_EQ_NORMAL);
  //  dfP.EQ(DFPLAYER_EQ_POP);
  //  dfP.EQ(DFPLAYER_EQ_ROCK);
  //  dfP.EQ(DFPLAYER_EQ_JAZZ);
  //  dfP.EQ(DFPLAYER_EQ_CLASSIC);
  dfP.EQ(DFPLAYER_EQ_BASS);
  //
  dfP.enableLoop();       // enable loop of files
  Serial.println(F("Loop enabled"));
  dfP.play(1);            // start the first file
  Serial.println(F("file #1 started"));
}

void loop() { // *** Arduino loop ***
  // No delays allowed to avoid Rx time-out
  static unsigned long chkT = millis(); // get ms passed
  if (millis() - chkT > 50) { // if 50mS have been passed,
    chkT = millis();      // then set new time to chkT
    setVol();             // check and set sound volume
    setSlpT();            // set and check sleep-timer
  }
  if (dfP.available()) {  // if message from DFPlayer,
    prtMsg(dfP.readType(), dfP.read()); // then print it
  }
}

// *** Set and check Sleep-Timer setSlpT() ***
void setSlpT(void) {
#define noSleep 2000000000 // 2 Billion mS(about 23days)
  static uint8_t vTmr; // sleep-timer (minute)  updt*V02
  vTmr = 0;					isrt*V02
  if (digitalRead(Tmr30) == LOW) { // if Tmr30 Sw on
    vTmr = vTmr + 30;     // then add 30 minutes
  }
  if (digitalRead(Tmr60) == LOW) { // if Tmr60 Sw on
    vTmr = vTmr + 60;     // then add 60 minutes
  }
  static unsigned long lTmr = vTmr; // change attribute
  slpT = lTmr * 60 * 1000 + 10000; // calculate mS+10sec
  digitalWrite(ledG, HIGH); // show Sleep-timer is on
  if (vTmr == 0) {        // if no timer Sw set
    slpT = noSleep;       // then set a very long timer
    digitalWrite(ledG, LOW); // show Sleep-timer is off
  }
  if (slpT < millis()) {  // if sleep time arrived,
    Serial.println(F("Sleep-timer expired"));
    digitalWrite(ledG, LOW); // off the Timer LED
    dfP.pause();           // then stop playing
    while (true) {}        // and do nothing
  }
}

// *** Check and set sound Volume setVol() ***
void setVol(void) {
#define aVol 0          // analog-input for sound volume
#define vMax 30         // maximum DFP sound volume
  static uint8_t vVsv;    // save area for vVol
  vVol = (uint16_t)(analogRead(aVol) * vMax / 1023);
  if (vVol != vVsv) {
    dfP.volume(vVol);     //volume
    vVsv = vVol;          // save the volume value
  }
}

// *** Print messsages from DFP prtMsg(type,val) ***
void prtMsg(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(" Finished #"));
      Serial.println(value);
      // do some if to avoid Stack at any cost ^^
      dfP.next();  // play next music
      break;
    case DFPlayerError:
      Serial.print(F("DFPlayerError:"));
      switch (value) {
        case Busy:
          Serial.println(F("No card")); break;
        case Sleeping:
          Serial.println(F("Sleeping"));  break;
        case SerialWrongStack:
          Serial.println(F("Wrong stack")); break;
        case CheckSumNotMatch:
          Serial.println(F("Check-sum error")); break;
        case FileIndexOut:
          Serial.println(F("File-index error")); break;
        case FileMismatch:
          Serial.println(F("Cannot find file")); break;
        case Advertise:
          Serial.println(F("In advertise")); break;
        default:
          break;
      }
      break;
    default: break;
  }
}

スリープタイマーは、2つのスイッチを使って30分か60分か90分が選べます。 上のスケッチでは、消費メモリーはデータ、スケッチともに2割しか使いません。UNOで余裕たっぷりですね。マイクロSDカードには次のようにテスト音楽を8つ入れています(フォルダーmp3を作りその中にmp3ファイルを8つ入れてある)。そして早速テストしているのが冒頭に掲げた写真です。

f:id:a-tomi:20210126202041j:plain

ちなみに、マイクロSDカードは大32GBまででFAT16形式とFAT32形式をサポート。フォルダーは99個まで、各フォルダー内の曲数は255までです。推奨フォルダー名は数字の00から99、曲名は数字の001から255(拡張子は当然ながら.mp3または.wav)ですが、順序さえわかれば他の短い英数字名をつけても問題ありません。

 

上のプログラムを実行した結果、Serial.モニターへの出力は次のようになりました。

Starting DFP
DFP connected
Sleep-timer mS = 1810000
Sound Vol. = 7
Loop enabled
file #1 started
 Finished #1
Stack wrong!
 Finished #2
Stack wrong!
 Finished #3
Stack wrong!
 Finished #4
Stack wrong!
 Finished #5
Stack wrong!
 Finished #6
Stack wrong!
 Finished #7
Stack wrong!
 Finished #8
Stack wrong!
 Finished #1
Stack wrong!
 Finished #2
Stack wrong!
 Finished #3
Stack wrong!
 Finished #4
Stack wrong!

  設定通りに曲1~8の演奏を繰り返し、設定したタイマーで演奏をやめます。上のリストのとおり、曲と曲の間にエラー(Wrong Stack)が1度ずつでています。コードを確認してDFPlayerミニのデータシートをみると、なかなかわかりにくいですがどうやらシリアル通信上の問題のようで、リトライされるので結果的には問題なさそう。

そこまでは万事よいのですが、ここからがあれ?

良い音が再生されるので感心していました。

そして音量を下げてみるとありゃ、カツッ、カツッ、・・と大きなノイズがでています。完全に周期的で、スピーカーラインをオシロでトリガーをかけて観察してみました。ノイズ多数回を記録したのが次です。

みると主に130mS毎に大ノイズが生じています。DFPlayerを別の個体に代えてみても、どれも完全に同じです!おお、個体のもんだいではない!

f:id:a-tomi:20210126175523j:plain

これはいったい何でしょう。

Arduinoとの接続?でしょうか。しかし接続はグランド以外にはソフトウェアシリアル通信線2本、それに当初はArduinoからの5Vラインだけです。

まずはシリアル線を点検。

f:id:a-tomi:20210126175807j:plain

ここにはノイズの元は全く見当たりません。

然れば残りは電源のみ?まさかね・・・

デジタルアンプ3Wなので電流をたくさん食うわけではない(実際80mA程度)ですが、それなら電源をArduinoからとらずに5VDCアダプター(これは容量2Aもある)から別に供給してみます。なんと、それでもノイズがやみません。DFPlayer側の電源端子間を試しにCを外してオシロ(AC接続)で見ると次。

f:id:a-tomi:20210126175850j:plain

 おお、まさしく正確に130mS毎に鋭いノイズが載っているではありませんか!

なんということでしょうか??DFPlayerが出しているに相違ないでしょう。

それでは・・と10μFのセラミックコンデンサーをいれても全く効果なし。ではと次に100μFのケミコンを入れると弱まりました。そこでこの際、470μFを入れてみましたら次のように大幅に低減しました。

f:id:a-tomi:20210126180654j:plain

考えてみればここはインピーダンスの低い電源なので、この場合は小さな容量ではこのノイズを消すには効き目が少ないわけですね。よほどひどいノイズを出しているのでしょう。

さて、これでスピーカーからはノイズが全く出なくなりすっきりです。DFPlayerの仕様にはサンプリング最大48KHz、24bitーDAC、ダイナミックレンジ90dB、SN比85dBとあり、そこらへんのPlayerと比べカタログ上は遜色ありませんね^^

早速こんどはUNOを外してATmega328Pで全体を試作。

f:id:a-tomi:20210126180933j:plain

おっ、ノイズもなくきれいな音でうまく動くじゃないですか!

ただし、DFPlayer側の電源には大容量のキャパシターを入れたので、DFplayer側の電源オンをしたあとでマイコン側の電源を入れています。まあ、これはArduinoスケッチのSetup()内にDelayを入れることで、その分だけ開始時刻を遅らせれば済むことですね。また、電源としては、おそらく三端子レギュレータ2つを使えば、1つの元電源から供給できるでしょう。

この新たなブレッドボード配線の詳細は次です。

f:id:a-tomi:20210126181338j:plain

いよいよ第1号機をちゃんと作るか・・・と思いながら、稼働状況を更によく観察したら、スリープタイマーが入っていることを表示する緑色のLEDが、不思議なことに6.6秒毎に1回だけ50mSだけ消灯するのです。たいへん規則的ですが、音には全く何も関係がないのです。これはいったい何なのでしょうか?

論理上は、点灯したらタイマー時間が終わり、演奏終了するまではこのLED消灯なしで常時点灯していなければならないつもりですが。。。いったい何が影響しているのでしょうか。

そして、UNOでの試作機もこれと同様によく観察すると、なんと全く同じことが起きているのです!

以下2021.1.30 更新させていただきました!:

というわけで、おかしなことだなあと考え込んで、本番機の製作にはとりかかれない状態でした。本日、やっと週末がきて念入りにチェックしたら、タイマーをセットする関数の中にstatic変数初期化のバグを発見して直しました^^;

 ところが、さらに別のことに気づきました。1週間経ったら、ブレッドボードのちょっとしたゆるみだけでDFPlayerが発振するようです。

海外ネットでみつけたノイズ問題(上記のようにツールで調べたケースはなかなか見当たらないようですが)」・・・。Beep音でなくBep Bep Bep Bep、、、

audio - DFPlayer Noise: Researched, Tried, and Bep Bep Bep Bep Bep - Arduino Stack Exchange

この際ということで、本日色々と試すと、DFPlayerはシリアル通信でコントロールしようとする限り、ちょっとしたことで発振が起きやすいようです。

「勝手な電子工作」としてはそこで簡単にはあきらめません。それなら他のもっと安定した方法を考えよう・・・とりあえずシリアル通信を使わない方法を考え、試しにArduino-UNOでコントロールしてみました。ブレッドボードで色々試した限り、それだとノイズ問題は起きずとても安定している感じです。

電源をUNOから共有しても大丈夫ですし、またなんと!上に書いた470μFを外してしまっても問題がないのに気づきました。つまり、月並みにシリアル通信で制御しようすると弱みがありそうなDFPlayerなのですが、そういうわけで意外にしぶとくできているなあと、改めて見直してしまいました^^

f:id:a-tomi:20210126182354j:plain

 

結局はその新しい案で本番機を作ろうと思います。基板など早く作りたいところですが、せっかくここまで読んでいただいたので、「その2」と題して先にその記事を載せてからにしようと思います。少々お待ちくださいね。

 

2021.2.2追記:「その2」に新しい案で作った記事を書いておきました。この勝手なやりかたならとても安定したものが作れてバッチリです。

睡眠タイマー付きMP3プレイヤーをArduinoで作る(その2)これでOK! - 勝手な電子工作・・

 

というわけで、この記事の中ではシリアル通信でのコントロールでしたが、どなたかのご参考になれば幸いです。

 

©2021 Akira Tominaga, All rights reserved.