この記事は 「Seeed UG Advent Calendar 2019 - Qiita」の 4日目 の記事です。(埋まっていなかったのでいただきました)
今年一年は木魚ネタばかりやっていたようなので、そろそろ少し違うこともやろうと思っていました。 ようやく今年の作業も一段落という感じになってきたので、溜まっていた作業を消化しようかなろと思います。
先日Bazaarをみていたら新しいMP3PlayerのGroveモジュールを見つけました(以下V3と表記)。
まだ日本ではVer2.0が販売されていますが、海の向こうではVer3が発売しているようです。
掲載時とは修正がありましたのでご注意ください。
SeeedさんのBazaarの商品も技適関連が申請によって使用が緩和されているので、購入のハードルが下がっているようですが、自分みたいな営業に近い業務
ではなかなか時間の捻出なども制限されてしまうので、いくら申請がかんたんでも億劫になってしまいます。
ですが、このモジュールであれば技適も関係ないしって感じで。使ってみようかなと説明ページを読んでみると以下のような機能のようです。
- Supports MP3 format audio files
- Sampling rate: 8~48KHz / bit rate: 8~320Kbps
- Support up to 32GB TF card
- Support speaker and earphone output audio at the same time
- Compatible with 3.3V and 5V platform.
- Support 32-level volume adjustment
ちなみにこれまでのV2.0は以下のような感じでした。
- General operations on audio files
- On-board micro-SD slot and 3.5mm audio jack
- Support sample rate of 8/11.025/12/16/22.05/24/32/44.1/48(KHz)
- 24-bit DAC output, 90 dB(at Max.) dynamic output range, signal-noise ratio at 85 dB
- MP3, WMW and WAV audio formats and FAT16, FAT32 files system supported
- Embed 10 levels of equalization in total
基板の形状が変わったようですが、一番大きな違いは電源が5V
も3.3V
も使用可能になったということかもしれません。
JST2.0 speaker portやPlay/Pauseボタンがついた点も大きな違いといえます。このあたりは開発時には割と嬉しい機能だったり。
V3では制御チップがWT2003S-20SS
になっているので制御も変わったかなと思ったらあれ情報はデータシートを見ないといけないみたい。ぐえ
データシートを読んでみた
基本部分
基本的な動きとしては、これまでと大差なくGroveコネクタに接続をしてシリアルデータを送信ものになります。
初めて中国語のデータシートをみましたが、Googleさんの翻訳に助けられて無事に内容はわかった感じです。
制御チップはフラッシュ、SDカード、USBに格納されたMP3データを読み出すことができるので、それぞれ再生用のコマンドがあります。ただ、今回のGrove モジュールではSDカードのみを考えれば良いのでそこだけを抜き出します。また、ディレクトリ構造にも対応しているのですが、文字数(5文字)も限られ、凝ったこともしないのでその部分は考えないことにします。
基本的なコマンドの構成は以下のようになります。
開始コード(0x7E)+Length(このバイトからチェックサムまでのバイト数)+コマンド(1バイト)+コマンドで必要となるデータ(0から数バイト)+チェックサム+終了コード(0xFE)
このバイト列を送信することで、命令を実行することができます。
そして、命令が実行されると以下のコードが返されます。
【修正がありました】
コマンド(送信したものと同じものが送信コマンドの成否まず返され、その後にリターンコードが返されます。
リターンコード送信コマンドの成否には00(OK)、01(Fail)、02(ファイルが存在しない)などがあります。
リターンコードは存在するものとしないものがありますが、送信コマンド毎に異なっているようです。
まとめるとこんな感じになります。
【送信データのフォーマット】
意味 | 内容 |
---|---|
開始コード | 0x7E |
Length | このバイトからチェックサムまでのバイト数 |
コマンド | 1バイト |
コマンドで必要となるデータ | コマンドによってバイト数は異なる(0〜数バイト) |
チェックサム | Lengthからチェックサム前までの論理和の下位1バイト |
終了コード | 0xFE |
【受信データのフォーマット】
(修正がありました)
意味 | 内容 |
---|---|
コマンド | 送信したコマンドの成否データ(1バイト)00:OK01:Fail02:ファイルが存在しないなど(内容はコマンドによって違うようです) |
リターンコード | コマンドによって異なります。 |
あと忘れていましたが、シリアルポートのビットレートは9600bps
となります。
コマンド一覧
使用しそうなコマンドを抜き出してみました。
コマンド | 意味 | 引数または戻り値 |
---|---|---|
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:曲数 |
テスト用のコード
GroveモジュールなのでSeeeduinoでテストしてみようと思います。
今回はGroveをソフトウエアシリアルで接続しますので以下にしておきます。
#include <SoftwareSerial.h> SoftwareSerial SSerial(SCL, SDA); //I2Cポートでソフトウエアシリアルを使用
SeeeduinoであればUART用のポートを使用してもいいのですが、今回のコードはシリアルポートに頻繁にアクセスするためボードへの アップロードが失敗してしまいます。突然アップロードが失敗するのでかなり混乱するのですが、以下のエントリーが参考になりました。
D0、D1を使ったシリアル通信を頻繁に使用するコードではこういうことが結構あるみたいです。今回はGroveコネクタの接続をUARTポートから外すことで 解決しますが、それではなかなか開発も辛いので、今回はI2Cポートを使用してソフトウエアシリアルとして使用しています。下図の③が書かれている ポートになります。
【参考:Seeedduinoのピンアサイン】
送信部分
シリアルへの送信処理のコードはこんな感じになります。 処理は大きく分けて以下のようになります。
- Lengthの計算
- チェックサムの計算
- シリアルポートへの書き込み
【送信部分のソースコード】
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; }
おわりに
コードのアップロードのトラブルでちょっと時間がかかってしまいました。 内容も少し長くなってしまったので【後編】に続きます。