VS1053bを使う

製品に実装されているモジュール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の制御は大きく分けて

  1.  モジュール内のレジスタを設定する場合
  2.  曲データを送信する場合

の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”
  • その他すべて、”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 データを再生するのみなら、

  1. 初期化として、レジスタの0x00番に0x800を書き込む。
  2. レジスタの0x0B番にボリュームデータを書き込む。
  3. DREQ信号を見ながら曲データを送信する。

でOKと言う事です。最後のプログラムと音楽データ(”data.mp3″ “data.aac”)を下記に保存しました。