制御用関数の作成(01)

今回は、DFPlayerの制御用関数を作成して行きます。

制御用コードの確認

制御コードはチェックサムが有る10バイトと無い8バイトの2種類有ります。前回試しに書いたコードはチェックサム無しの8バイトコードです。今回はチェックサム有りで関数を書くことにしました。10バイトコードの中身は以下の通り。

バイト説明チェックサム
 第1バイト  スタートバイト  0x7E  
 第2バイト バージョン 0xFF  使用(固定)  
 第3バイト コマンドの長さ 0x06 使用(固定) 
 第4バイト コマンドコード 使用
 第5バイト Feedback 0x00 使用(固定) 
 第6バイト パラメータ上位8バイト  使用
 第7バイト パラメータ下位8バイト 使用
 第8バイト チェックサム上位8バイト
 第9バイト チェックサム下位8バイト 
 第10バイト エンドバイト 0xEF  

チェックサムは、第2,3,4,5,6,7バイトを使用して以下の様に計算します。

Checksum (2 bytes) = 0xFFFF–(Ver.+Length+CMD+Feedback+Para_MSB+Para_LSB)+1

第2,3,5は固定なので

Checksum (2 bytes) = 0xFFFF–(0xFF + 0x06 + CMD+0x00 +Para_MSB+Para_LSB)+1

Checksum (2 bytes) = 0xFFFF–(0x104+ CMD+Para_MSB+Para_LSB)

で計算出来ます。コマンドコードとそのコマンドで指定するパラメータを足して、0xFFFFから引けはOKです。チェックサムの上位8バイトを第8バイトに下位8バイトを第9バイトに代入します。最後10バイト目に0xEFを入れて10バイト制御コードが完成です。

フォルダー内の曲を連続再生するなら

フォルダー内の曲を連続再生するだけなら以下の関数でOKです。

 コマンド 説明関数名
 0x06 ボリュームの設定 DFP_set_vol(uint8_t m_data)
 0x0F フォルダーと曲名を指定して再生 DFP_fol_trk(uint8_t fol, uint8_t trk) 
 0x16 ストップ DFP_stop(void)
 0x17 フォルダー内の曲の連続再生と繰り返し  DFP_repeat_fol(uint8_t fol)

正確にはこの他にチェックサムの計算、データの送信関数も必要が。

  • DFP_checksum(uint8_t cmd, uint8_t m_data):チェックサムを計算する関数
  • DFP_send_cmd(uint8_t cmd, uint8_t m_data):10バイトのコードを作成し送信する関数
getstart_01.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

void setup() 
{
    uint8_t buf[12];
    int a;
    
    Serial.begin(115200);
    Serial.println("initializing...");
  
    _serial.begin(9600);
    Serial.println("start");

    delay(2000);

    DFP_set_vol(20);
    DFP_repeat_fol(0);

    Serial.println("end");

    while(1) ;    
}

void loop() 
{

}

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_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);
}

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);
}

SDに保存するデータについて

SDカードにデータを保存する場合の注意事項。

  • 100個のフォルダを持つ事が出来ます。
  • フォルダ名は二桁の数字(1バイトコード)を用いて、”00”から”99”とする。
  • 各フォルダーは256個のファイルを持つことが出来る。
  • フォルダーを指定してその中のファイルを再生する場合、再生される順番はフォルダーに曲データが保存された順番となる。

曲を意図した順番で聞きたいなら、その順番で曲をフォルダにコピーする必要が有ります。

SDカードのルートディレクトリに曲ファイルを数曲持った”00”というフォルダーを作成し、それをDFPlayerにセットして、その後上記のプログラムを実行しました。問題無く曲が再生されました。

順次再生ではなく、ランダム再生したい

順次再生だけでは物足りない。ランダム再生もしたい所です。マニュアルを調べたのですが残念ながらコマンドとしてフォルダー内の曲をランダム再生するコマンドは有りませんでした。でも下記のコマンドを見つけました。

  • フォルダー内のファイルの数を返すコマンド: 0x4E

これと曲を指定して再生するコマンドを組み合わせればランダム再生が出来そうです。(フォルダの何曲入っているか分かれば、乱数を使って再生する曲を指定すればランダム再生になる。)

DFPlayerからのデータ形式

今までのコードはESP32からDFPlayerへの送信だけでした。このコマンド、”0x4E”はコマンドを送信してからデータを受信する必要が有ります。そこでDFPlayerからの返答データの形式について調べてみました。

バイト説明
 第1バイト  スタートバイト  0x7E 
 第2バイト バージョン 0xFF
 第3バイト コマンドの長さ 0x06
 第4バイト コマンドコード
 第5バイト Feedback 0x00
 第6バイト パラメータ上位8バイト
 第7バイト パラメータ下位8バイト
 第8バイト チェックサム上位8バイト
 第9バイト チェックサム下位8バイト 
 第10バイト エンドバイト 0xEF

構成は送信の時と殆ど同じです。重要なのは第4,6,7バイトです。

  • 第4バイト:リクエストしたコマンドコード
  • 第6,7バイト:要求された値

これらのデータを元に

  • 返答データの第4バイトをチェックして要求したコマンドと同じか確認
  • 同じなら第6,7バイトの値を返答データと判断する。

を行いながらデータを処理します。

DFPlayerからの返信が受け取れない

曲数を返すコマンド、”0x4E”の関数を書いて実行したのですが、どうも思った値が帰って来ません。使用したプログラムは以下の通り。

  • ”0x4E”を22行目で実行
  • 24行で返答を待って
  • 25行で読み込み。
  • 以下でその値を表示。

今回はフォルダ”0”を指定。そのフォルダには33個のデータが保存されています。期待している返答データは

0x7E, 0xFF, 0x06, 0x4E, 0x00, 0x00, 0x21, XX, XX,0xEF

ですが、モニターに表示されるデータは以下の通りでした。

第4バイトのコマンドコードが、0x40。第6,7バイトのデータが0,3。予定はコマンドは、”0x4E”。データは 00,21 です。何回か実行しましたが同じ結果でした。

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

void setup() 
{
    uint8_t buf[12];
    int a;
    
    Serial.begin(115200);
    Serial.println("initializing...");
  
    _serial.begin(9600);
    Serial.println("start");

    delay(2000);

    DFP_send_cmd(0x4e, 0);

    while(!_serial.available()) delay(50);
    _serial.readBytes(buf,10);
    
    for(a = 0; a < 9; a ++)
    {
        Serial.print(buf[a],HEX);
        Serial.print(", ");
    }
    Serial.println(buf[9],HEX);
        
    Serial.println("end");

    while(1) ;    
}

void loop() 
{

}

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_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);
}

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);
}

コマンドコード”0x40”は、マニュアルを見るとエラーコードだと分かりました。またデータ部の03は、DFplayerがコマンドの受信を完了していないというものでした。

チェックサムも間違っていない。他のコマンドは送信出来ているので送信に間違いが有るとは思え無い。このコマンドのみ特別な処理が必要なのかとも思いましたが、その様な事はマニュアルには書いて有りませんでした。

理由が分かりました

今回DFPlayerの電源はESP32から取っています。どうもこれが原因の様です。ESP32のプログラムをArduinoで開発する時ですが、以下の様に行っています。

  • ESP32にUSBコネクターを接続。
  • ESP32から電源を取っているDFPlayerもこの時点で電源ON。初期化を始め、1,2秒で完了。
  • ESP32のソースがコンパイルされて実行される頃には初期化すでに完了でReady状態
  • ESP32がプログラムを実行し通信初期化をする時に信号線の状態を変える。(ここは想像)
  • これをDFPlayerはコマンドの開始と判断しコマンドを待つがコマンドは来ない。
  • DFPlayerがタイムアウト。コマンド未達のエラーをESP32に送る。
  • ESP32はこのメッセージを受信しバッファーに入れる。
  • その後、ESP32がフォルダ内に有るファイル数の問い合わせを行う。
  • DFPlayerは数をESP32に返信する。
  • ESP32はこのデーターを受信してバッファーの先程受信したデータの後に保存する。
  • この状態でESP32は受信データ読みに行く。多分バッファーの最初の10バイトが読まれる。
  • 最初10バイトはファイルの数では無くエラーメッセージ。

これが正しいとするともう一度10バイト読み込めばコマンドコード、”0x4E”の返答を読めるはずです。そこでプログラムを変更して確認してみました。

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

void setup() 
{
    uint8_t buf[12];
    int a;
    
    Serial.begin(115200);
    Serial.println("initializing...");
  
    _serial.begin(9600);
    Serial.println("start");

    delay(2000);

    DFP_send_cmd(0x4e, 0);

    Serial.println("1st");
    print_data();
    Serial.println("2nd");
    print_data();

    Serial.println("end");
   
    while(1) ;    
}

void loop() 
{

}

void print_data()
{
    int a;
    uint8_t buf[12];
    
    while(!_serial.available()) delay(50);
    _serial.readBytes(buf,10);

    for(a = 0; a < 9; a ++)
    {
        Serial.print(buf[a],HEX);
        Serial.print(", ");
    }
    Serial.println(buf[9],HEX);
        
}

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_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);
}

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);
}

実行結果は以下の通り。予想通り、コマンドコード”0x4E”のデータがありました。

思った通りと喜んだのですが、曲数が、”0x17” つまり23曲と本来の数(33曲)と違う事に気付きました。これも何回やっても同じ結果でした。試しにフォルダーを幾つか作成しフォルダーを変えながら実行したのですが、全て同じ値、”0x17”を返して来ます。

原因分かりました

コマンド、”0x4E”にバグが有るようです。マニュアルには、”指定したフォルダーに含まれる曲数を返す” と有りますが、実際には現在のフォルダーの曲数を返す様です。またコマンド、”0x4E”を実行する時に使用するフォルダー番号は無視され、何を指定しても現在のフォルダーに保存されている曲数を返すようです。曲数を調べたいフォルダーをどの様にして現在のフォルダーにするか。それは、調べたいフォルダー内の曲を再生し直ぐに停止すれば良い様です。その後コマンド、”0x4E”を行えば正しい曲数を返す事が分かりました。

getstart_03.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

void setup() 
{
    uint8_t buf[12];
    int a;
    
    Serial.begin(115200);
    Serial.println("initializing...");
  
    _serial.begin(9600);
    Serial.println("start");

    delay(2000);

    DFP_fol_trk(0, 0);
    DFP_stop();
    DFP_send_cmd(0x4e, 0);

    Serial.println("1st");
    print_data();
    Serial.println("2nd");
    print_data();

    Serial.println("end");
   
    while(1) ;    
}

void loop() 
{

}

void print_data()
{
    int a;
    uint8_t buf[12];
    
    while(!_serial.available()) delay(50);
    _serial.readBytes(buf,10);

    for(a = 0; a < 9; a ++)
    {
        Serial.print(buf[a],HEX);
        Serial.print(", ");
    }
    Serial.println(buf[9],HEX);
}

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_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);
}

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);
}

プログラムでは、目的のフォルダー”00”に何曲有るか調べています。

  • 22行:DFP_fol_trk(0, 0);関数でフォルダー”00”の0番目の曲を再生する。
  • 23行:再生を直ぐ止める。
  • 24行:コマンド、”0x4E”を実行する。
  • それ以降:データの表示

プログラムの実行結果。シリアルモニターに以下の様に表示されます。

2ndの下の10バイトがコマンド、”0x4E”の返答。これを見るとコマンドが、”0x4E”。データが、00,21。つまり33。(前回は23)。想定通りの値が帰って来ました。これでマニュアルとは違いますがとりあえず指定したフォルダーに保存されている曲数を得る事が出来る様になりました。

ファイル保存上の注意その2

今回、フォルダーと曲名を指定して実行するコマンド、”0x0F”を使用しました。指定したファイルを再生するにはファイルを以下の様に予め保存する必要が有ります。

  • ファイル名の先頭に数字3桁を追加する。DFPlayerはそれを用いてファイルを判断する。
  • 3桁の数字は、”000”から”255”。256個ファイルを識別出来る。
  • 判断に先頭3文字のみが使われるので ”000” と ”000_XXX” は同じファイルと判断される(2つを識別出来ない)
  • ファイル名に2バイトコードを使用出来るが、最初の3桁は1バイトコード。

関数、”DFP_fol_trk(uint8_t fol, uint8_t trk)”はフォルダーとファイルを指定して再生する関数。第一引数がフォルダー、第2引数がファイルとなります。引数は符号無し8ビットの数ですが、SDカードの中のフォルダーとファイルは

  • フォルダー: 名前は”00”から”99”までの2文字。
  • ファイル: 名前の先頭3文字が ”000”から”255” で始まる256個

厄介なのがフォルダー内を連続で再生するコマンド、”0x17”との違いです。”0x17”の場合再生する順番はファイル名に関係無く、そのフォルダーに保存された順番となる事です。例えば、フォルダーに、”000” ”001” ”002”というファイルが有ったとします。そして保管された(コピーされた)順番が、”000” ”002” ”001” だったら、この関数で再生される順番は、”000” ”002” ”001” の順となります。

また、”0x17”は再生にファイル名を使用していません。つまりファイルの先頭が3桁の数字で有る必要が有りません。(ファイル名の制限が無い)。

逆に、2つのコマンドで順番通り再生したい場合、ファイル名を、”000”から始めて、その順番でフォルダーに保管する必要が有ります。

次回は

DFPLayerとの送受信が出来る様になりました。次回はハードをちょっと追加してMP3Playerを作って行きます。