ESP32-WROOM-32D、VS1003B(MP3デコーダモジュール)、SDカードモジュールからなる簡単なmp3プレーヤーを制作しました。
【開発環境】
VisualStudioCode + PlatformIO
【ソースコード】
・プレーヤー部
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モジュールドライバ部
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_ */
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