製品に実装されているモジュールVS1053bは、Ogg Vorbis、MP3、AAC、WMA等をデコードするモジュールです。今回はESP32を使用して製品に実装されたSDカードに保存された曲データをこのVS1053を使って演奏してみたいと思います。
ハード
VS1053及びSDカードのインターフェイスはSPIです。VS1053の資料を元にESP32と下記の接続を下記の様に設定しました。
- SPI信号:SCK, MOSI, MISO はVS1053,SDカード共通。
- SDカードのCSをGPIO05に設定。
- VS1053には、XCS,XDCS,DREQ,X_RESET, Dummyが必要。
- (VS1053の電源(5V)をESP32から取っていますが独立して供給した方がベター)
先ずはSDカード
ESP32とSDカードとは下記の様に配線しています
- SCK : GPIO18
- MOSI: GPIO23
- MISO: GPIO19
- CS: GPIO05
ESP32のSPIデフォルトに合わせているので下記のプログラムでSDカードの読み書きが出来ます。実行するとSDカードに ”test.txt” ファイルを作成し、シリアルモニターに ”VS1053 SD TEST !!” と表示します。問題無くSDカードを動かす事が出来ました。
SD_TEST.ino
#include <Arduino.h>
#include <FS.h>
#include <SD.h>
//For SD
#define CLK 18
#define MISO 19
#define MOSI 23
#define CS 5
void setup()
{
File fp;
String str;
Serial.begin(115200);
delay(500);
if(SD.begin()) Serial.println("SD OK !!");
fp = SD.open("/test.txt","w");
fp.println("VS1053 SD TEST !!");
fp.close();
fp = SD.open("/test.txt","r");
str = fp.readStringUntil('\n');
fp.close();
Serial.println(str);
while(1) ;
}
void loop()
{
}
次はVS1053
VS1053もSPIなのですが、通信の仕方が若干特殊です。VS1053の制御は大きく分けて
- モジュール内のレジスタを設定する場合
- 曲データを送信する場合
の2つに分けられます。レジスタを設定する場合は、予め”XCS”ピンを、曲データを送信する場合は予め”XDCS”ピンをLowにして両者を切り分けます。この様な操作が必要な為、どちらもCS信号として指定出来ません。CSピンとして全く関係無いピンを指定して、必要に応じてユーザーがXCS,XDCSをアクティブにする方式を取ります。
その他の信号ピンは
- DREQ: VS1053の状態を示す。High -> Idle, Low -> Busy
- レジスタの設定、データの書き込みを行う前に必ずチェックします。
- この信号がHighの時のみVS1053にアクセス出来ます。
- DREQは
- レジスタにアクセスした場合は必ずLowになります。
- 曲データを送る場合、VS1053のバッファーが一杯(約2048バイト)になるとLowになります。
- バッファーに空きが出来るとHighになりますが、この場合最低でも32バイト送信出来る様です。
- X_RESET: リセット信号。この信号はハードリセットです。
となっています。
レジスタの読み込み
Read
uint16_t VLSIReadReg(byte vAddress)
{
uint16_t wValue;
while(!digitalRead(DREQ)) NOP();
digitalWrite(XCS,LOW);
SPI.write(0x03); // Read
SPI.write(vAddress); // Register address
((byte*)&wValue)[1] = SPI.transfer(0xFF); // 16 bit high byte
((byte*)&wValue)[0] = SPI.transfer(0xFF); // 16 bit low byte
digitalWrite(XCS,HIGH);
return wValue;
}
- 1行: 読み込みたいレジスタの番号が引数
- 5行: Busyチェック
- 6行: レジスタ関係なので、XCSをアクティブにする。
- 7行: 読み込みは0x03を送信
- 8行: 読み込みたいレジスタ番号を送信
- 9,10行: レジスタの値の取り込み
- 11行: XCSをインアクティブにする。
- 12行: 読み込んだ値を返す。
レジスタの書き込み
Write
void VLSIWriteReg(byte vAddress, uint16_t wValue)
{
while(!digitalRead(DREQ)) NOP();
digitalWrite(XCS,LOW);
SPI.write(0x02); // Write
SPI.write(vAddress); // Register address
SPI.write(((byte*)&wValue)[1]); // 16 bit write high byte
SPI.write(((byte*)&wValue)[0]); // 16 bit write low byte
digitalWrite(XCS,HIGH);
}
- 1行: 書き込みたいレジストの番号と値が引数
- 3行: Busyチェック
- 4行: レジスタ関係なので、XCSをアクティブにする。
- 5行: 書き込みは0x02を送信
- 6行: 書き込みたいレジスタ番号を送信
- 7,8行: 値を送信
- 9行: XCSをインアクティブにする。
データの書き込み
Date Write
while(!digitalRead(DREQ)) NOP();
digitalWrite(XDCS,LOW);
SPI.write(0x53);
SPI.write(0xEF);
---------------
---------------
---------------
SPI.write(0x00);
digitalWrite(XDCS,HIGH);
- 1行: Busyチェック
- 4行: データ関係なので、XDCSをアクティブにする。
- 3〜8行: 音楽データの書き込み
- 9行: XDCSをインアクティブにする。
サンプルプログラム
先ずはVS1053のテストモードでサイン波を出すプログラムを書いて見ます。これでレジスタの読み書き、データの書き込みが出来る事を確認します。
vs1053_test.ino
#include <Arduino.h>
#include <SPI.h>
//For VS1053b PIN
#define XCS 17
#define XDCS 22
#define DUMMY 33
#define DREQ 39
#define XRESET 21
//VS1053 register
#define VLSI_MODE 0x00
#define VLSI_VOL 0x0B
//For SD
#define CLK 18
#define MISO 19
#define MOSI 23
#define SS 5
void setup()
{
Serial.begin(115200);
delay(500);
digitalWrite(XCS,HIGH);
digitalWrite(XDCS,HIGH);
digitalWrite(XRESET,HIGH);
pinMode(XCS, OUTPUT);
pinMode(XDCS, OUTPUT);
pinMode(XRESET, OUTPUT);
pinMode(DREQ,INPUT);
SPI.begin(CLK, MISO, MOSI, DUMMY);
// Reset VS1053
digitalWrite(XRESET,LOW);
delay(500);
digitalWrite(XRESET,HIGH);
delay(500);
VLSIWriteReg(VLSI_MODE, 0x820);
Serial.print("\nVS1053: ");
Serial.println(VLSIReadReg(VLSI_MODE),HEX);
VLSI_SetVolume(0x30,0x30);
Serial.print("Star VS1053 TEST ");
digitalWrite(XCS,HIGH);
while(!digitalRead(DREQ)) NOP();
digitalWrite(XDCS,LOW);
SPI.write(0x53);
SPI.write(0xEF);
SPI.write(0x6E);
SPI.write(0x8d);
SPI.write(0x00);
SPI.write(0x00);
SPI.write(0x00);
SPI.write(0x00);
digitalWrite(XDCS,HIGH);
while(1) ;
}
void loop()
{
}
void VLSI_SetVolume(byte v_data_l, byte v_data_r)
{
uint16_t vol_data;
vol_data = (v_data_l << 8) | v_data_r;
VLSIWriteReg(VLSI_VOL, vol_data);
}
uint16_t VLSIReadReg(byte vAddress)
{
uint16_t wValue;
while(!digitalRead(DREQ)) NOP();
digitalWrite(XCS,LOW);
SPI.write(0x03); // Read
SPI.write(vAddress); // Register address
((byte*)&wValue)[1] = SPI.transfer(0xFF); // 16 bit high byte
((byte*)&wValue)[0] = SPI.transfer(0xFF); // 16 bit low byte
digitalWrite(XCS,HIGH);
return wValue;
}
void VLSIWriteReg(byte vAddress, uint16_t wValue)
{
while(!digitalRead(DREQ)) NOP();
digitalWrite(XCS,LOW);
SPI.write(0x02); // Write
SPI.write(vAddress); // Register address
SPI.write(((byte*)&wValue)[1]); // 16 bit write high byte
SPI.write(((byte*)&wValue)[0]); // 16 bit write low byte
digitalWrite(XCS,HIGH);
}
- 26から34行: 各ポートの設定
- 36行: SPIの設定。チップセレクトにDUMMYを指定してSPIを初期化する。
- 39から42行: VS1053のリセット
- 44行: VS1053の初期化。資料(VS1053の資料)37ページにレジスターの一覧が有ります。
- 多くのレジスタが有りますが、MP3やAACファイルの再生のみなら、0x0(MODE)と0xB(VOL)の設定のみでOKです。
- MODEレジスタはその名の通りVS1053のモードを設定するレジスタです。レジスタの内容は下記の通り
- 通常設定が必要なのは、15ビット(SM_CLK_RANGE)、11ビット(VS1002 native SPI modes)。今回はテストモードを使用するので、5ビット(Allow SDI tests)も設定します。
- 15ビット(SM_CLK_RANGE)
- :基板の発振器の周波数を指定します。今回の発振器は12.288と刻印があるので、”0”を指定。
- 11ビット(VS1002 native SPI modes)
- yes で ”1”
- 5ビット(Allow SDI tests)
- allowedで ”1”
- 15ビット(SM_CLK_RANGE)
- その他すべて、”0”として書き込む値は0x820となります。
- 46,47行: ここで書き込んだ値を確認の為、読み込んでいます。
- 49行: ボリュームの設定。左右別々に設定出来ます。0が最大。0xFEが最小です。
- 52行: TESTの開始。先ずレジスタ設定モードをインアクティブ
- 53行: Busyチェック
- 54行: データ送信用にXDCSをアクティブ。
- 55から62行: これらのデータが連続で送られるとVS1053はTESTモードに入る。サイン波を発生する。資料の66ページ辺りに周波数の計算方法が有ります。58行の0x8dを変えると周波数が変わります。
- 63行: データ送信終了。XDCSをインアクティブ。
ピーという音が聞こえればOKです。
この2つどうやって繋ぐ?
1つのSPIバスを2つのモジュールが共有しているのでチップセレクト信号(CS)でそれら切り替えて使う必要が有ります。これを実現するために2つSPIのインスタンスを作成しています。
- For SD Card
- #include<SD.h>を行った時点で、1組の SD、 SPI のインスタンスが作成されます。
- SDカード用にはこれを使用します。
- SD.begin()と引数無しでこの関数を使うとSPI関係の信号ピンはESP32のデフォルトになります。
- 今回は配線をデフォルトに合わせているのでこれでSDカードが動作する事は確認済み。
- For VS1053
- 上記のSPIとは違うSPIをSPI2として宣言する。宣言は SPIClass SPI2(VSPI); で行う。
- SPI2.begin(CLK, MISO, MOSI, DUMMY); と初期化を行う。 チップセレクトだけがSPIとは違う別のSPIインスタンスとなる。
- XCS,XDCS信号とこのSPI2を使ってVS1053を制御する。
簡単に言うと、以下の通り。
- SDカード用のプログラムはそのまま
- VS1053用は、 SPIClass SPI2(VSPI); を宣言し、プログラムのSPIの箇所をすべてSPI2に置き換える
これを元に書いたプログラムが下記です。SDカードの保存された、”data.mp3″と”data.aac”の再生を繰り返すプログラムです。
demo.ino
#include <Arduino.h>
#include <FS.h>
#include <SD.h>
//For VS1053b PIN
#define XCS 17
#define XDCS 22
#define DUMMY 33
#define DREQ 39
#define XRESET 21
//VS1053 register
#define VLSI_MODE 0x00
#define VLSI_VOL 0x0B
//For SD
#define CLK 18
#define MISO 19
#define MOSI 23
#define SS 5
SPIClass SPI2(VSPI);
void setup()
{
File fp;
Serial.begin(115200);
delay(500);
pinMode(XCS, OUTPUT);
pinMode(XDCS, OUTPUT);
pinMode(XRESET, OUTPUT);
digitalWrite(XDCS,HIGH);
digitalWrite(XCS,HIGH);
digitalWrite(XRESET,HIGH);
pinMode(DREQ,INPUT);
if(SD.begin()) Serial.println("SD OK !!");
SPI2.begin(CLK, MISO, MOSI, DUMMY);
// Reset VS1053
digitalWrite(XRESET,LOW);
delay(500);
digitalWrite(XRESET,HIGH);
delay(500);
VLSIWriteReg(VLSI_MODE, 0x820);
Serial.print("\nVS1053: ");
Serial.println(VLSIReadReg(VLSI_MODE),HEX);
VLSI_SetVolume(0x30,0x30);
}
void loop()
{
File fp;
byte buf[2100];
int a,b;
Serial.println("Stat MP3 Sample");
fp = SD.open("/data.mp3","r");
while(a = fp.read(buf,2000))
{
digitalWrite(XDCS,LOW);
for(b = 0; b < a; b ++)
{
while(!digitalRead(DREQ)) ;
SPI2.write(buf[b]);
}
digitalWrite(XDCS,HIGH);
}
fp.close();
Serial.println("End\n");
Serial.println("Stat AAC Sample");
fp = SD.open("/data.aac","r");
while(a = fp.read(buf,2000))
{
digitalWrite(XDCS,LOW);
for(b = 0; b < a; b ++)
{
while(!digitalRead(DREQ)) ;
SPI2.write(buf[b]);
}
digitalWrite(XDCS,HIGH);
}
fp.close();
Serial.println("End\n");
}
void VLSI_SetVolume(byte v_data_l, byte v_data_r)
{
uint16_t vol_data;
vol_data = (v_data_l << 8) | v_data_r;
VLSIWriteReg(VLSI_VOL, vol_data);
}
uint16_t VLSIReadReg(byte vAddress)
{
uint16_t wValue;
while(!digitalRead(DREQ)) NOP();
digitalWrite(XCS,LOW);
SPI2.write(0x03); // Read
SPI2.write(vAddress); // Register address
((byte*)&wValue)[1] = SPI2.transfer(0xFF); // 16 bit high byte
((byte*)&wValue)[0] = SPI2.transfer(0xFF); // 16 bit low byte
digitalWrite(XCS,HIGH);
return wValue;
}
void VLSIWriteReg(byte vAddress, uint16_t wValue)
{
while(!digitalRead(DREQ)) NOP();
digitalWrite(XCS,LOW);
SPI2.write(0x02); // Write
SPI2.write(vAddress); // Register address
SPI2.write(((byte*)&wValue)[1]); // 16 bit write high byte
SPI2.write(((byte*)&wValue)[0]); // 16 bit write low byte
digitalWrite(XCS,HIGH);
}
最後に
VS1053は多くの機能を持ったICですが、この製品を使って MP3 AAC データを再生するのみなら、
- 初期化として、レジスタの0x00番に0x800を書き込む。
- レジスタの0x0B番にボリュームデータを書き込む。
- DREQ信号を見ながら曲データを送信する。
でOKと言う事です。最後のプログラムと音楽データ(”data.mp3″ “data.aac”)を下記に保存しました。