Grove-MP3 V3-Music Playerを使用してみた【後編】

この記事は 「Seeed UG Advent Calendar 2019 - Qiita」の 5日目 の記事です。(埋まっていなかったのでいただきました)

さて前回は、Grove-MP3 V3-Music Playerに乗っているコントローラーのデータシートを見ながら基本的なコードができたので、今度は実際に音声の再生などをやってみましょう!

f:id:ueponx:20191229163033j:plain

www.seeedstudio.com

【参考】

uepon.hatenadiary.com

使ってみようと思うコマンド

前回のエントリーでもまとめましたが再掲です。

コマンド 意味 引数または戻り値
A2 SDカードからインデックス指定再生 ファイルのインデックス
A3 SDカードからファイル名指定再生 ファイル名
A4 SDカードからディレクトリ名とインデックス指定再生 ディレクトリ名、インデックス
AA 再生・一時停止 なし
AB 停止 なし
AC 一つ次の曲へ なし
AD 一つ前の曲へ なし
AE 音量調整 音量 0x00〜0x1Fの31段階
C1 現在の音量値取得 C1 XX
XX:0x00〜0x1Fの31段階
C2 現在の再生状態取得 C2 XX
XX:意味
01 play
02 Stop
02 Pause
C5 SDカードにある曲数取得 C5 XXXXX
XX:曲数

単に音を鳴らすだけであれば、最初にある3つの再生コマンド(A2、A3、A4)と、停止コマンド(AB)があればいいかなと思います。 ただ、再生コマンドの処理行うと次の処理に進んでしまうので(鳴らし終わったら次の処理をするわけではない)現在の再生状態の取得コマンド(C2)もあったほうがいいかなと思います。※音声の再生はコマンドを投げるまでは継続してくれるので鳴らしながら別の処理を行うのであれば状態取得は行わなくても大丈夫です。

再生系のコマンド

SDカードからインデックス指定再生(A2)

突然インデックスという言葉が出ますが、SDカードに入っている曲(データ)にインデックスを割り振っているのだと思います。なので、ファイル名に番号を振っても必ずしも一致するわけではないことは覚えておいたほうがいいかなと。

コマンドとしては

開始コード Length コマンド インデックス上位 インデックス下位 チェックサム 終了コード
0x7E 0x05 0xA2 0x?? 0x?? 0x?? 0xEF

ここでいうインデックスは1曲目であれば上位0x00、下位0x01という形で格納されます。

例えば、1曲目のデータを送信する場合には以下のようにコマンドを送信します。チェックサムは都度計算が必要です。

開始コード Length コマンド インデックス上位 インデックス下位 チェックサム 終了コード
0x7E 0x05 0xA2 0x00 0x01 0xA6 0xEF

返り値に関してはコマンドの成否、0x00(成功)/0x01(失敗)が返されます。オプションのリターン値はありません。

コマンドの成否0x00(成功)/0x01(失敗) オプションリターン値
0x?? なし

【コードサンプル】送信/受信処理は過去エントリー参照

void GroveMP3V3_IndexPlay(int index)
{
  char parameter[2];
  parameter[0] = index / 256;//インデックスの上位取得
  parameter[1] = index % 256;//インデックスの下位取得
  GroveMP3V3_WriteCommand(0xa2, parameter, sizeof(parameter));

  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode == 0x00) abort();
}

SDカードからファイル名指定再生(A3)

ファイル名の指定と言っても実は完全なファイル名が指定しなくても良いようになっています。 指定できるのは8文字のファイル名(拡張子を除く文字数)までが対応しています。実はプリフィックス指定ができるので、例えばprefix01.mp3というファイル場合にはprefixまでファイル名として指定すれば勝手にファイルを選択してくれるという機能があります。

コマンドとしては以下のようになります。

開始コード Length コマンド ファイル名(0) ファイル名(7) チェックサム 終了コード
0x7E 0x?? 0xA3 0x?? 0x?? 0x?? 0xEF

例えば、01.mp3を指定する場合には以下のような設定になります。(8文字以下であれば完全な名前指定可能です。)

開始コード Length コマンド ファイル名(0) ファイル名(1) チェックサム 終了コード
0x7E 0x05 0xA3 0x30 0x31 0x09 0xEF

返り値に関してはコマンドの成否、0x00(成功)/0x01(失敗)が返されます。オプションのリターン値はありません。

コマンドの成否0x00(成功)/0x01(失敗) オプションリターン値
0x?? なし

【コードサンプル】送信/受信処理は過去エントリー参照

void GroveMP3V3_FilePlay(U8 *parameter, int parameterSize)
{
  if (parameterSize > 8) abort();
  GroveMP3V3_WriteCommand(0xa3, parameter, sizeof(parameter));

  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode != 0x00) abort();
}

8文字以下のファイル名出ないといけないので、そのパラメータのチェックを追加しています。

SDカードからディレクトリ名とインデックス指定再生(A4)

ディレクトリ指定とインデックスの指定で再生するファイルを選ぶものになります。 ディレクトリ名の指定は5文字固定です。あとはインデックス番号を指定します。

コマンドとしては以下のようになります。

開始コード Length コマンド ファイル名(0) ファイル名(4) インデックス上位 インデックス下位 チェックサム 終了コード
0x7E 0x0A 0xA4 0x?? 0x?? 0x?? 0x?? 0x?? 0xEF

ここでいうインデックスは1曲目であれば上位0x00、下位0x01という形で格納されます。

【コードサンプル】送信/受信処理は過去エントリー参照

void GroveMP3V3_DirIndexPlay(U8 *parameter, int parameterSize, int index)
{
  if (parameterSize != 5) abort();
  char param[parameterSize+2];
  for(int i=0; i<parameterSize; i++) param[i] = parameter[i];
  param[parameterSize+0] = index / 256;
  param[parameterSize+1] = index % 256;
  
  GroveMP3V3_WriteCommand(0xa4, param, sizeof(param));

  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode != 0x00) abort();
}

ディレクトリ名が5文字固定なのでそのチェックを入れてあります。

制御系コマンド

再生系のコマンドは終わり後は制御系と状態取得系になります。続いては制御系です。

再生・一時停止(AA)

こちらは一旦停止と再生のトグル的なコマンドになります。

開始コード Length コマンド チェックサム 終了コード
0x7E 0x03 0xAA 0xAD 0xEF

返り値に関してはコマンドの成否、0x00(成功)/0x01(失敗)が返されます。オプションのリターン値はありません。

コマンドの成否0x00(成功)/0x01(失敗) オプションリターン値
0x?? なし

【コードサンプル】送信/受信処理は過去エントリー参照

void GroveMP3V3_Pause()
{
  GroveMP3V3_WriteCommand(0xaa, NULL, 0);
  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode != 0x00) abort();
}

停止(AB)

こちらは再生の停止コマンドになります。一時停止とは違い次の再生は最初殻となります。

開始コード Length コマンド チェックサム 終了コード
0x7E 0x03 0xAB 0xAE 0xEF

返り値に関してはコマンドの成否、0x00(成功)/0x01(失敗)が返されます。オプションのリターン値はありません。

コマンドの成否0x00(成功)/0x01(失敗) オプションリターン値
0x?? なし

【コードサンプル】送信/受信処理は過去エントリー参照

void GroveMP3V3_Stop()
{
  GroveMP3V3_WriteCommand(0xab, NULL, 0);
  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode != 0x00) abort();
}

一つ次の曲へ(AC)

ファイルの一覧をリストとして見たときの次の曲に行く処理を行います。ただし、このコマンドは曲の再生状態でないとうまく動作せず、一旦停止などしているとうまく機能しないようでした。また、リストの最後の曲の場合にはループ処理を行ってくれます。

開始コード Length コマンド チェックサム 終了コード
0x7E 0x03 0xAC 0xAF 0xEF

返り値に関してはコマンドの成否、0x00(成功)/0x01(失敗)が返されます。オプションのリターン値はありません。

コマンドの成否0x00(成功)/0x01(失敗) オプションリターン値
0x?? なし

【コードサンプル】送信/受信処理は過去エントリー参照

void GroveMP3V3_FastForward()
{
  GroveMP3V3_WriteCommand(0xac, NULL, 0);
  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode != 0x00) abort();
}

一つ前の曲へ(AD)

ファイルの一覧をリストとして見たときの前の曲に行く処理を行います(実質は巻き戻し)。ただし、このコマンドは曲の再生状態でないとうまく動作せず、一旦停止などしているとうまく機能しないようでした。また、リストの最初の曲の場合にはループ処理を行ってくれます。

開始コード Length コマンド チェックサム 終了コード
0x7E 0x03 0xAD 0xB0 0xEF

返り値に関してはコマンドの成否、0x00(成功)/0x01(失敗)が返されます。オプションのリターン値はありません。

コマンドの成否0x00(成功)/0x01(失敗) オプションリターン値
0x?? なし

【コードサンプル】送信/受信処理は過去エントリー参照

void GroveMP3V3_Rewind()
{
  GroveMP3V3_WriteCommand(0xad, NULL, 0);
  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode != 0x00) abort();
}

音量調整(AE)

出力のボリュームをコントロールします。音声は0~31(0x00~0x1F)までが指定できます。 音量の最大は31となります。

開始コード Length コマンド チェックサム 終了コード
0x7E 0x04 0xAE 0xAE 0xEF

返り値に関してはコマンドの成否、0x00(成功)/0x01(失敗)が返されます。オプションのリターン値はありません。

コマンドの成否0x00(成功)/0x01(失敗) オプションリターン値
0x?? なし

【コードサンプル】送信/受信処理は過去エントリー参照

void GroveMP3V3_VolumeControl(int volume)
{
  if (volume < 0) volume = 0;
  if (31 < volume) volume = 31;

  U8 parameter[] = { (U8)volume };
  GroveMP3V3_WriteCommand(0xae, parameter, sizeof(parameter));

  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode != 0x00) abort();
}

状態取得系コマンド

残るのは状態取得系のコマンドになります。これらのコマンドは返り値が少し特殊になるので注意が必要です。

現在の音量値取得(C1)

現在設定されている音量ボリューム値を取得します。

開始コード Length コマンド チェックサム 終了コード
0x7E 0x03 0xC1 0xC4 0xEF

返り値に関しては送信したコマンド(0xC1)が返され、オプションのリターン値は設定された音声ボリューム値(0x00~0x1F)が返されます。

送信コマンド 音声ボリューム値(0x00~0x1F)
0xC2 0x??

【コードサンプル】送信/受信処理は過去エントリー参照

int GroveMP3V3_QueryCurrentVolume()
{
  GroveMP3V3_WriteCommand(0xc1, NULL, 0);

  U8 operationCode;
  U8 currentVolume;
  if (!GroveMP3V3_ReadReturn(&operationCode, &currentVolume, 1)) abort();
  if (operationCode != 0xc1) abort();

  return currentVolume;
}

現在の再生状態取得(C2)

現在の動作状態(再生、停止、一時停止)取得します。このコマンドを使用して再生終了などをチェックする事ができます。ループで回す必要がありそうですが…

開始コード Length コマンド チェックサム 終了コード
0x7E 0x03 0xC2 0xC5 0xEF

返り値に関しては送信したコマンド(0xC2)が返され、オプションのリターン値は 現在の動作状態(0x01:再生、0x02:停止、0x03:一時停止)が返されます。

送信コマンド 音声ボリューム値(0x01:再生、0x02:停止、0x03:一時停止)
0xC2 0x??

【コードサンプル】送信/受信処理は過去エントリー参照

int GroveMP3V3_QueryCurrentOperationState()
{
  GroveMP3V3_WriteCommand(0xc2, NULL, 0);

  U8 operationCode;
  U8 currentOperationState;
  if (!GroveMP3V3_ReadReturn(&operationCode, &currentOperationState, 1)) abort();
  if (operationCode != 0xc2) abort();

  return currentOperationState;
}

SDカードにある曲数取得(C5)

現在のSDカードに格納されているファイル数を取得します。

開始コード Length コマンド チェックサム 終了コード
0x7E 0x03 0xC5 0xC8 0xEF

返り値に関しては送信したコマンド(0xC5)が返され、オプションのリターン値は SDカードに格納されたファイル数が返されます。ファイル数の値は2Byteで返されます。

送信コマンド ファイル数上位 ファイル数下位
0xC2 0x?? 0x??

【コードサンプル】送信/受信処理は過去エントリー参照

int GroveMP3V3_QueryTotalMusicFiles()
{
  GroveMP3V3_WriteCommand(0xc5, NULL, 0);

  U8 operationCode;
  U8 currentFiles[2];
  int count;
  if (!GroveMP3V3_ReadReturn(&operationCode, currentFiles, 2)) abort();
  if (operationCode != 0xc5) abort();

  count = currentFiles[0] * 256;
  count += currentFiles[1];
  
  return count;
}

まとめ

実際に動かしてみたのは以下となります。


Grove - MP3 V3 -Music Playerの動作サンプル

楽曲は以下のサイトのものを使用させて頂いております。 www.ne.jp

今回のサンプルプログラムをまとめたものをエントリーの一番最後につけておきます。

GitHubにも同じものを置いておきます。 github.com

おわりに

Grove-MP3 V3-Music Playerを無事に使用できるようになりました。 モジュール以外のところで引っかかりが多かったのですが、楽しく組めました。

【参考】

uepon.hatenadiary.com

【GroveMP3V3-sample.ino】

#include <SoftwareSerial.h>

SoftwareSerial SSerial(SCL, SDA);//I2Cポートでソフトウエアシリアルを使用
typedef uint8_t U8;

void setup()
{
  Serial.begin(9600);

  delay(200);
  GroveMP3V3_begin();

  Serial.print("VolumeControl=>");
  GroveMP3V3_VolumeControl(3);
  Serial.println(GroveMP3V3_QueryCurrentVolume());

  Serial.print("Total Number of MusicFiles=>");
  Serial.println(GroveMP3V3_QueryTotalMusicFiles());

  GroveMP3V3_FilePlay("01", 2);
  delay(10000);

  GroveMP3V3_DirIndexPlay("MUSIC", 5, 1);
  delay(10000);

  Serial.println("Play");
  GroveMP3V3_IndexPlay(3);
  //while (GroveMP3V3_QueryCurrentOperationState() == 1) delay(3000);//再生終了は左記のループでチェック
  delay(10000);

  Serial.println("Pause");
  GroveMP3V3_Pause();
  delay(3000);
  GroveMP3V3_Pause();
  delay(3000);

  Serial.println("FastForward");
  GroveMP3V3_FastForward();
  delay(10000);

  Serial.println("Rewind");
  GroveMP3V3_Rewind();
  delay(10000);

  Serial.println("Stop");
  GroveMP3V3_Stop();
}

void loop()
{
}

void GroveMP3V3_begin()
{
  SSerial.begin(9600);
}

void GroveMP3V3_IndexPlay(int index)
{
  char parameter[2];
  parameter[0] = index / 256;
  parameter[1] = index % 256;
  GroveMP3V3_WriteCommand(0xa2, parameter, sizeof(parameter));

  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode != 0x00) abort();
}

void GroveMP3V3_FilePlay(U8 *parameter, int parameterSize)
{
  if (parameterSize > 8) abort();
  GroveMP3V3_WriteCommand(0xa3, parameter, sizeof(parameter));

  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode != 0x00) abort();
}

void GroveMP3V3_DirIndexPlay(U8 *parameter, int parameterSize, int index)
{
  if (parameterSize > 5) abort();
  char param[parameterSize+2];
  for(int i=0; i<parameterSize; i++) param[i] = parameter[i];
  param[parameterSize+0] = index / 256;
  param[parameterSize+1] = index % 256;
  
  GroveMP3V3_WriteCommand(0xa4, param, sizeof(param));

  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode != 0x00) abort();
}

void GroveMP3V3_Pause()
{
  GroveMP3V3_WriteCommand(0xaa, NULL, 0);
  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode != 0x00) abort();
}

void GroveMP3V3_Stop()
{
  GroveMP3V3_WriteCommand(0xab, NULL, 0);
  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode != 0x00) abort();
}

void GroveMP3V3_FastForward()
{
  GroveMP3V3_WriteCommand(0xac, NULL, 0);
  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode != 0x00) abort();
}

void GroveMP3V3_Rewind()
{
  GroveMP3V3_WriteCommand(0xad, NULL, 0);
  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode != 0x00) abort();
}

void GroveMP3V3_VolumeControl(int volume)
{
  if (volume < 0) volume = 0;
  if (31 < volume) volume = 31;

  U8 parameter[] = { (U8)volume };
  GroveMP3V3_WriteCommand(0xae, parameter, sizeof(parameter));

  U8 operationCode;
  if (!GroveMP3V3_ReadReturn(&operationCode, NULL, 0)) abort();
  if (operationCode != 0x00) abort();
}

int GroveMP3V3_QueryCurrentVolume()
{
  GroveMP3V3_WriteCommand(0xc1, NULL, 0);

  U8 operationCode;
  U8 currentVolume;
  if (!GroveMP3V3_ReadReturn(&operationCode, &currentVolume, 1)) abort();
  if (operationCode != 0xc1) abort();

  return currentVolume;
}

int GroveMP3V3_QueryCurrentOperationState()
{
  GroveMP3V3_WriteCommand(0xc2, NULL, 0);

  U8 operationCode;
  U8 currentOperationState;
  if (!GroveMP3V3_ReadReturn(&operationCode, &currentOperationState, 1)) abort();
  if (operationCode != 0xc2) abort();

  return currentOperationState;
}

int GroveMP3V3_QueryTotalMusicFiles()
{
  GroveMP3V3_WriteCommand(0xc5, NULL, 0);

  U8 operationCode;
  U8 currentFiles[2];
  int count;
  if (!GroveMP3V3_ReadReturn(&operationCode, currentFiles, 2)) abort();
  if (operationCode != 0xc5) abort();

  count = currentFiles[0] * 256;//インデックスの上位
  count += currentFiles[1];//インデックスの下位

  return count;
}

void GroveMP3V3_WriteCommand(U8 commandCode, const U8 *parameter, int parameterSize)
{
  U8 length = 1 + 1 + parameterSize + 1;// Lengthを計算
  U8 sum = 0;// チェックサムの初期化
  // 以下チェックサムの計算
  sum += length;
  sum += commandCode;
  for (int i = 0; i < parameterSize; i++) sum += parameter[i];

  //シリアルポートへの書き込み
  SSerial.write(0x7e);// 開始コード
  SSerial.write(length);// Length
  SSerial.write(commandCode);// コマンド
  SSerial.write(parameter, parameterSize);// コマンド引数
  SSerial.write(sum);// チェックサム
  SSerial.write(0xef);// 終了コード
}

bool GroveMP3V3_ReadReturn(U8 *operationCode, U8 *returnValue, U8 returnValueSize)
{
  while (SSerial.available() < 1);// 受信待ち
  *operationCode = SSerial.read();// コマンドデータの受信

  for (int i = 0; i < returnValueSize; i++)
  {
    while (SSerial.available() < 1);// 受信待ち
    returnValue[i] = SSerial.read();// return値の受信
  }

  return true;
}