ESP32+VS1003Bで作るmp3プレーヤー

ESP32-WROOM-32D、VS1003B(MP3デコーダモジュール)、SDカードモジュールからなる簡単なmp3プレーヤーを制作しました。

【開発環境】
VisualStudioCode + PlatformIO

【結線】
![connection.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/919305/828ac698-4791-cb20-00fa-12051aa258e9.jpeg)

【ソースコード】
・プレーヤー部

```c++:player.cpp
#include <SPI.h>
#include <SD.h>
#include <Arduino.h>
#include <FreeRTOS.h>

#include "VS1003Bmp3.hpp"

// SD
#define SD_SCK  (18)
#define SD_MISO (19)
#define SD_MOSI (23)
#define SD_SS   ( 5)
SPIClass vSpi(VSPI);

volatile unsigned long cnt = 0;
#define SD_BUFF_SIZE   (32 * 64)
#define SD_BUFF_NUM (16) 
unsigned char sdBuff[SD_BUFF_NUM][SD_BUFF_SIZE];
unsigned char dummyBuff[8];
int readBuffNum = 0;
int writeBuffNum = 0;
typedef enum {
  BUFF_EMPTY,
  BUFF_READING,
  BUFF_WRITING,
  BUFF_FULL,
} buff_status_t;
int buffStatus[SD_BUFF_NUM] = {BUFF_EMPTY};

hw_timer_t *timer = NULL;   // 音声再生用
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

VS1003B::mp3 *m;

File gRoot;
int gFileCnt = 0;
#define PLAY_LIST_MAX (256)
char *playList[PLAY_LIST_MAX] = {0};

void IRAM_ATTR timerCallbackFunc() {
  portENTER_CRITICAL_ISR(&timerMux);
  size_t written = 32;
  size_t accumWritten = 0;

  if(buffStatus[readBuffNum] == BUFF_FULL && cnt == 0) {
    buffStatus[readBuffNum] = BUFF_READING;
  }
  if(buffStatus[readBuffNum] == BUFF_READING) {
    do {
      m->soundOut((const unsigned char *)&sdBuff[readBuffNum][cnt], written);

      cnt += written;
      accumWritten += written;
    } while(accumWritten == 32);

    if(cnt >= SD_BUFF_SIZE) {
      buffStatus[readBuffNum] = BUFF_EMPTY;
      int tmp_readBuffNum = readBuffNum;
      ++tmp_readBuffNum %= SD_BUFF_NUM;
      if(buffStatus[tmp_readBuffNum] == BUFF_FULL) {
        cnt = 0;
        ++readBuffNum %= SD_BUFF_NUM;
      }
    }
  }
  portEXIT_CRITICAL_ISR(&timerMux);
}

// プレイリストにファイルパスを格納
void getFileNames(char **playList, File dir) {
  for(;;) {
    File item = dir.openNextFile();
    if(!item || gFileCnt >= PLAY_LIST_MAX) {
      break;
    } 

    if(item.isDirectory()) {
      // 特定ディレクトリは中に入らないよう除外
      if(strcmp(item.name(), "/System Volume Information") != 0 &&
         strcmp(item.name(), "/hidden") != 0) {
        getFileNames(playList, item);
      }
    } else {
      playList[gFileCnt] = (char *)malloc(256);
      strcpy(playList[gFileCnt], item.name()); 
      gFileCnt++;
    }
  }
}

// プレイリスト作成
void makePlayList(char **playList) {
  File root = SD.open("/");
  getFileNames(playList, root);
  root.close();
}

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

  m = new VS1003B::mp3(HSPI, 14, 12, 13);

  vSpi.end();
  vSpi.begin(SD_SCK, SD_MISO, SD_MOSI, SD_SS);
  //vSpi.setFrequency(15000000);
  vSpi.setDataMode(SPI_MODE1);
  if(!SD.begin(SD_SS, vSpi, 15000000)) {
    Serial.println("SD card mount failed!!");
  }
  makePlayList(playList);

  for(int i = 0; i < 256; i++) {
    if(playList[i]) {
      Serial.printf("%s\r\n", playList[i]);
    }
  }

  // タイマー割り込み設定 
  timer = timerBegin(1, 4, true);
  timerAttachInterrupt(timer, timerCallbackFunc, true);
  timerAlarmWrite(timer, 558 << 5, true);
  timerAlarmDisable(timer);
}

void loop() {
  // プレイリストのインデックス
  static int fileIndex = 0;
  // 新しいファイルの始まり
  static bool first = true;
   bool flg_waitForBufferFilled = true;
  
  File file = SD.open(playList[fileIndex], FILE_READ);

  if(!file) {
    Serial.println("FILE_READ error...");
  } else {
    // 再生データ長を取得
    int rest = file.size();
    cnt = 0;
    readBuffNum = 0;
    writeBuffNum = 0;
    memset(buffStatus, BUFF_EMPTY, sizeof(buffStatus));

    while(rest > 0) {
      if(first) {
        first = false;
        // バッファ全面にデータを読み込んでおく
        do {
          file.read(sdBuff[writeBuffNum], rest < SD_BUFF_SIZE ? rest : SD_BUFF_SIZE);
          rest -= SD_BUFF_SIZE;
          ++writeBuffNum;
        } while(writeBuffNum < SD_BUFF_NUM);

        for(int i = 0; i < SD_BUFF_NUM; i++) {
          buffStatus[i] = BUFF_FULL;
        }
        readBuffNum = 0;
        writeBuffNum = SD_BUFF_NUM / 2;

        if(flg_waitForBufferFilled) {
          // 再生開始
          timerAlarmEnable(timer);
          flg_waitForBufferFilled = false;
        }
      }
      // 空のバッファにデータ読み込み
      if(buffStatus[writeBuffNum] == BUFF_EMPTY) {
        buffStatus[writeBuffNum] = BUFF_WRITING;
        while(!file.read(sdBuff[writeBuffNum], rest < SD_BUFF_SIZE ? rest : SD_BUFF_SIZE)) {
          //Serial.printf("sd read() error. rest:%d\r\n", rest);
        }
        rest -= SD_BUFF_SIZE;
        buffStatus[writeBuffNum] = BUFF_FULL;
        //Serial.printf("RB:%d, WB:%d\r\n", readBuffNum, writeBuffNum);
      } else {
        ++writeBuffNum %= SD_BUFF_NUM;
      }
    }
    ++fileIndex %= gFileCnt;
  }
  delay(1);
}
```
・DACモジュールドライバ部

```c++:VS1003Bmp3.hpp
#ifndef _H_VS1003B_MP3_H_
#define _H_VS1003B_MP3_H_

#include <SPI.h>
#include <Arduino.h>
#include <FreeRTOS.h>

namespace VS1003B {
  // direct gpio
  #define GPIO_0TO31SET_REG   *((volatile unsigned long *)GPIO_OUT_W1TS_REG)
  #define GPIO_0TO31CLR_REG   *((volatile unsigned long *)GPIO_OUT_W1TC_REG)
  #define GPIO_0TO32RD_REG    *((volatile unsigned long *)GPIO_IN_REG)

  // pin connections with VS1003B
  #define VS1003B_SCK   (14)
  #define VS1003B_MISO  (12)
  #define VS1003B_MOSI  (13)

  #define VS1003B_XCS   (22)
  #define VS1003B_XDCS  (27)
  #define VS1003B_XRST  (26)
  #define VS1003B_DREQ  (25)

  // SCI_MODE
  #define SM_DIFF       (0b000000000000001)
  #define SM_SETTOZERO  (0b000000000000010)
  #define SM_RESET      (0b000000000000100)
  #define SM_OUTOFWAV   (0b000000000001000)
  #define SM_PDOWN      (0b000000000010000)
  #define SM_TESTS      (0b000000000100000)
  #define SM_STREAM     (0b000000001000000)
  #define SM_SETTOZERO2 (0b000000010000000)
  #define SM_DACT       (0b000000100000000)
  #define SM_SDIORD     (0b000001000000000)
  #define SM_SDISHARE   (0b000010000000000)
  #define SM_SDINEW     (0b000100000000000)
  #define SM_ADPCM      (0b001000000000000)
  #define SM_ADPCM_HP   (0b010000000000000)
  #define SM_LINE_IN    (0b100000000000000)

  // SCI registers
  #define SR_MODE           (0x00)
  #define SR_STATUS         (0x01)
  #define SR_BASS           (0x02)
  #define SR_CLOCKF         (0x03)
  #define SR_DECODE_TIME    (0x04)
  #define SR_AUDATA         (0x05)
  #define SR_WRAM           (0x06)
  #define SR_WRAMADDR       (0x07)
  #define SR_HDAT0          (0x08)
  #define SR_HDAT1          (0x09)
  #define SR_AIADDR         (0x0a)
  #define SR_VOL            (0x0b)
  #define SR_AICTRL0        (0x0c)
  #define SR_AICTRL1        (0x0d)
  #define SR_AICTRL2        (0x0e)
  #define SR_AICTRL3        (0x0f)

  // task state
  #define VS1003B_STOPPED   (0)
  #define VS1003B_PLAYING   (1)
  volatile int vs1003bState = VS1003B_STOPPED;

  class mp3 {
      private:
      void sciWrite(unsigned char addr, unsigned short data) {
        GPIO_0TO31SET_REG = 1 << VS1003B_XDCS;
        GPIO_0TO31CLR_REG = 1 << VS1003B_XCS;
        
        vsSpi->write(0x02);   // write instruction
        vsSpi->write(addr);
        vsSpi->write16(data);
        while(!(GPIO_0TO32RD_REG & (1 << VS1003B_DREQ))) {
          ;
        }
        GPIO_0TO31SET_REG = 1 << VS1003B_XCS;
      }
      void sdiWriteBytes(const unsigned char *pdata, int size) {
        GPIO_0TO31CLR_REG = 1 << VS1003B_XDCS;
        vsSpi->writeBytes((unsigned char *)pdata, size);
        GPIO_0TO31SET_REG = 1 << VS1003B_XDCS;
      }
      public:
      SPIClass *vsSpi;
      mp3(int cSpi, int pinSck, int pinMiso, int pinMosi) {
        pinMode(VS1003B_XCS, OUTPUT);
        pinMode(VS1003B_XDCS, OUTPUT);
        pinMode(VS1003B_XRST, OUTPUT);
        pinMode(VS1003B_DREQ, INPUT);

        GPIO_0TO31SET_REG = (1 << VS1003B_XCS) | (1 << VS1003B_XDCS);
        vsSpi = new SPIClass(cSpi); // whether VSPI or HSPI.
        vsSpi->end();
        vsSpi->begin(pinSck, pinMiso, pinMosi, -1);

        while(!(GPIO_0TO32RD_REG & (1 << VS1003B_DREQ))) {
          delay(1);
        }

        sciWrite(SR_AUDATA, 44100 + 1); // set the sampling rate(44.1KHz) and stereo flag(1).
        sciWrite(SR_CLOCKF, 0x8000);    // set clock multiplier(x3.0), addition(0.0x) and frequency(12.288MHz).
        sciWrite(SR_VOL, 0x2727);
        sciWrite(SR_MODE, SM_RESET | SM_DIFF | SM_SDINEW);

        vsSpi->setFrequency(36864000 / 7);  // (XTAL * 3) / 7 : max       
      }
      void soundOut(const unsigned char *mp3buff, int size) {
        for(int i = 0; i < size; i += 32) {
    
          sdiWriteBytes(&mp3buff[0], 32);
          while(!(GPIO_0TO32RD_REG & (1 << VS1003B_DREQ))) {
            ;
          }
        }
      }
  };

} /* namespace VS1003B */
#endif /* _H_VS1003B_MP3_H_ */

```

```c++:platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino

lib_deps =
  # RECOMMENDED
  # Accept new functionality in a backwards compatible manner and patches


; enlarge flash capacity:
board_build.partitions = no_ota.csv
; set frequency to 240MHz
board_build.f_cpu = 240000000L
; for debugger
monitor_speed = 115200
upload_speed = 921600
debug_tool = minimodule

upload_port = COM5
```