ESP32でDFPlayer制御する(02)

今回はMP3Playerの製作です。先ずは使用した部品の一覧

 タクトスイッチ 4個 
 スライドスイッチ 1個
 ロータリースイッチ 2個
 10kΩ 4個
 ステレオ・ミニジャック  1個
 基板 1枚
 Mini MP3プレーヤー 1個
 ESP32 1個

これらの部品を下記の様に配線。

今回書いたプログラム

getstart_02.ino

#include "Arduino.h"

HardwareSerial _serial(2); // RX, TX

uint8_t send_buf[10]= {0x7E, 0xFF, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEF};

#define w_time  0x200

#define dir_10_8    32
#define dir_10_4    33
#define dir_10_2    25
#define dir_10_1    26

#define dir_00_8    36
#define dir_00_4    39
#define dir_00_2    34
#define dir_00_1    35

#define repeat_bt   18
#define dsleep_bt   13

#define pilot_led   2

#define ad_rnd_pin  14

#define busy_chk    27

RTC_DATA_ATTR uint8_t RndCount = 0;
RTC_DATA_ATTR uint8_t m_total;
RTC_DATA_ATTR uint8_t s_play;
RTC_DATA_ATTR uint8_t trk_arry[100];
RTC_DATA_ATTR uint8_t folder_no;

void setup() 
{
    uint8_t get_buf[12];
    int a;
    esp_sleep_wakeup_cause_t wakeup_reason;
    
    Serial.begin(115200);
    Serial.println("initializing...");
  
    _serial.begin(9600);
    Serial.println("starting...");

    pinMode(dir_10_8, INPUT_PULLDOWN);
    pinMode(dir_10_4, INPUT_PULLDOWN);
    pinMode(dir_10_2, INPUT_PULLDOWN);
    pinMode(dir_10_1, INPUT_PULLDOWN);
    pinMode(dir_00_8, INPUT);
    pinMode(dir_00_4, INPUT);
    pinMode(dir_00_2, INPUT);
    pinMode(dir_00_1, INPUT);

    pinMode(repeat_bt, INPUT_PULLUP);
    pinMode(dsleep_bt, INPUT_PULLUP);
    pinMode(busy_chk, INPUT_PULLUP);
    
    pinMode(pilot_led, OUTPUT);
    digitalWrite(pilot_led, HIGH);

    folder_no = get_dir_no();
    randomSeed(analogRead(ad_rnd_pin)); 

    wakeup_reason = esp_sleep_get_wakeup_cause();

    while(!_serial.available()) delay(50);
    delay(500);
    while(_serial.available()) _serial.read(); 

    if(!wakeup_reason)
    {
        DFP_reset();
        DFP_set_vol(25);
    }
    else DFP_stop();

    if(wakeup_reason == 3)
    {
        //Wakeup caused by external signal using RTC_CNTL
        if(RndCount == 0)
        {
            RndCount = m_total;
            for(a = 0; a < RndCount; a++) trk_arry[a] = a;
            s_play = 0;
        }
    }
    else
    {
        //Wakeup was not caused by deep sleep    
        RndCount = m_total = DFP_get_trkno_fol(folder_no);
        Serial.println("Total: " + String(RndCount));
        for(a = 0; a < RndCount; a++) trk_arry[a] = a;
        s_play = 0;
    }

    play_rnd_trk();
    esp_sleep_enable_ext1_wakeup(0x8000000,ESP_EXT1_WAKEUP_ANY_HIGH);
  
    //Configure GPIO13 as ext0 wake up source for LOW logic level
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_13,0);
    
    //Go to sleep now
    while(digitalRead(busy_chk)) delay(50);
    delay(500);
    digitalWrite(pilot_led, LOW);
    Serial.println("Going to sleep now");
    esp_deep_sleep_start();
    Serial.println("This will never be printed");
}

void loop() 
{

}

uint8_t DFP_get_trkno_fol(uint8_t fol)
{
    uint8_t get_buf[12];
    
    DFP_fol_trk(fol, 0);
    DFP_stop();
    DFP_send_cmd(0x4e, fol);

    while(!_serial.available()) delay(50); 
    _serial.readBytes(get_buf,10);
    
    return get_buf[6];
}

void play_rnd_trk()
{
    int a;
    uint8_t rnd,m_data;
    
    if(digitalRead(repeat_bt))
    {
        m_data = s_play;
        s_play ++;
    }
    else
    {
        rnd = random(RndCount);   
        m_data = trk_arry[rnd];

        for(a =  rnd; a <  (RndCount-1); a ++)
            trk_arry[a] = trk_arry[a + 1];
            
    }

    RndCount --;    
    DFP_fol_trk(folder_no, m_data);
    delay(w_time);
}

uint8_t  get_dir_no(void)
{
    uint8_t a,b;

    a = 0;
    if(digitalRead(dir_10_1)) a |= 1;
    if(digitalRead(dir_10_2)) a |= 0x2;
    if(digitalRead(dir_10_4)) a |= 0x4;
    if(digitalRead(dir_10_8)) a |= 0x8;
    a *= 10;
    
    b = 0;
    if(digitalRead(dir_00_1)) b |= 1;
    if(digitalRead(dir_00_2)) b |= 0x2;
    if(digitalRead(dir_00_4)) b |= 0x4;
    if(digitalRead(dir_00_8)) b |= 0x8;

    return(a + b);
}

uint16_t DFP_checksum(uint8_t cmd, uint8_t m_data)
{
    return(0xffff - (0x104 + cmd + m_data));
}

void DFP_send_cmd(uint8_t cmd, uint8_t m_data)
{
    uint16_t csum = DFP_checksum(cmd, m_data);
    
    send_buf[3] = cmd;
    send_buf[5] = 0; 
    send_buf[6] = m_data;
    send_buf[7] = (csum >> 8) & 0xff;
    send_buf[8] = csum & 0xff;
    _serial.write(send_buf,10);
}

void DFP_set_vol(uint8_t m_data)
{
    DFP_send_cmd(0x06, m_data);
    delay(w_time);
}

void DFP_reset(void)
{
    int flg;
    uint8_t buf[12];

    DFP_send_cmd(0x0c,0x00);
    flg = 1;
    while(flg)
    {
        while(!_serial.available()) delay(50); 
        if(_serial.read() == 0x7e) flg = 0;
        delay(50); 
    }
    delay(w_time);
    _serial.readBytes(&buf[1],9);
    delay(w_time);
}

void DFP_fol_trk(uint8_t fol, uint8_t trk)
{
    uint16_t csum = DFP_checksum(0x0f, fol + trk);
    
    send_buf[3] = 0x0F;
    send_buf[5] = fol;
    send_buf[6] = trk;
    send_buf[7] = (csum >> 8) & 0xff;
    send_buf[8] = csum & 0xff;
    _serial.write(send_buf,10);
    delay(w_time);
}

void DFP_stop(void)
{
    DFP_send_cmd(0x16,0);
    delay(w_time);
}

void DFP_repeat_fol(uint8_t fol)
{
    DFP_send_cmd(0x17, fol);
    delay(w_time);
}

プログラムの流れ

電源投入またはESP32のリセットボタンを押された時の動作

  1. USBケーブルをESP32につなぐとESP32とDFPlayerの初期化が同時に開始されます。
  2. 初期化はESP32が先に終わる為、ESP32はDFPlayerの初期化終了を待ちます。
  3. DFPlayerの初期化が終了すると、メッセージが送られくるのでこれをESP32は読み出します。
  4. その後、DFPlayerにリセットをかけます
  5. DFPlayerのリセット完了を待ってDFPlayerからのメッセージを読み出し、音量を設置する。
  6. 再生するフォルダーの値を2つのロータリースイッチから読み取り。
  7. フォルダーを指定してそのファオルダーが保存する曲数を読み取り。
  8. スライドスイッチの状態で順次かランダムか判断します。
    • ランダムの場合は曲リストを作り乱数を用いて再生する曲を選びます。
    • 順次の場合はファイル名の先頭3文字で指定した順番に曲を選びます。
  9. 再生する曲をDFPlayerに送信する。
  10. DFPlayerが曲を受信し再生が始まるとDFPlayerの16ピンがHighになる。
  11. ESP32はそれを確認してDeep Sleepに入る。

電源オンのみなら上記4,5はいらないのですが、これらはリセットボタンを押された時に対応しています。ESP32のリセットボタンが押された時にDFPlayerもリセットをかけたいと思ったのですが、その時DFPlayerは既に電源がオンになっているのでちょっとその部分(上記4,5)の操作が複雑になりました。

曲再生の間ESP32はDeep Sleepしています。DFPlayerは曲の再生が終わると、終了メッセージの送信と16ピン(BUSY)をLowにします。ESP32はこの信号でDeep Sleepから復帰し次の曲を選択しDFPlayerに送っています。

  1. DFPlayerの16ピン(BUSY)がLowになるとESP32はDeep Sleepから復帰し、初期化を開始します。
  2. ESP32が初期化終了時点でDFPlayerはReady状態なので例のコマンド未受信のエラーメッセージがDFPlayerから送られて来ます。ESP32はこのメッセージを受取、読み出します。
  3. DFPlayerの16ピンからの復帰の場合、ESP32はDFPlayerのリセットは行いません。
  4. 次の曲を選択して再生するのですが最終曲が再生されたら、また最初の状態に戻して再生します。つまりループ再生となります

フォルダー選択はGPIO13に接続されているプッシュスイッチを使用して行います。このスイッチもDeep Sleepからの復帰に使用しています。

  1. ロータリースイッチで希望するフォルダーの値を設定後このボタンを押します。
  2. EPS32はDeep Sleepから復帰し初期化を始めます。
  3. 初期化後直ぐの処理は、DFPlayerの16ピン復帰と同じ(DFPlayerにリセットをかけない)
  4. その後の処理は、電源投入またはESP32のリセットボタンが押された時と同じ操作を行います。

その他、

  • DFPlayerのNext/Pre ボタンを残していますが、このボタンとESP32が連携していないのでこれらのボタンを使って曲を選択するとプログラムは正しい動作をしません。
  • これらのボタンは音量調整に使用する事が出来ます。音量はプログラムに関係ないので使用しても問題有りません。
  • イヤホンジャックの出力を車のAUXに接続して使用しますが、デバック用にスピーカ端子を出しています。デバッグはこれにスピーカーをつないで行いました。

アクリル板で簡単なケースを作って以下の様になりました

最後にSDカードですが下記の様に作成して下さい。

  • ルートディレクトリに数字2文字(00から99まで)のフォルダーを作成します。
  • ファルダーの中には先頭3文字(000から255まで)が数字になるファイルを保存します。
  • フォルダーと再生ファイルの選択はこの数字を使って行われます。

最後に

我ながら上手く出来たと思います。しっかり再生してとても良いのですが、ただ運転中にロータリースイッチを操作出来ません。設定機能は有るんですが再設定は車を止めないと出来ません。これはかなり残念です。今度はそれも考慮したMP3Playerを作りたいと思います。