374 lines
7.9 KiB
C++
374 lines
7.9 KiB
C++
#ifndef VS1003_H
|
|
#define VS1003_H
|
|
|
|
#include "../../io/fastGPIO.h"
|
|
#include "../../io/SoftSPI.h"
|
|
#include "../../Debug.h"
|
|
//#include "IPlayable.h"
|
|
//#include "AudioData.h"
|
|
#include "../../data/ChunkBuffer.h"
|
|
|
|
|
|
/**
|
|
* control a VS1003 audio chip attached to the SPI port
|
|
*/
|
|
template <typename PlayBuf> class VS1003 {
|
|
|
|
private:
|
|
|
|
static constexpr const char* NAME = "VS1003";
|
|
|
|
//SoftSPI spi;
|
|
#define SPI_INIT() spi::init();
|
|
#define SPI_WRITE_8(byte) spi::writeByte(byte)
|
|
#define SPI_WRITE_16(word) spi::writeWord(word)
|
|
#define SPI_READ_8(byte) spi::readByte(byte)
|
|
#define SPI_READ_16(word) spi::readWord(word)
|
|
#define SPI_READ_WRITE_8(byte) spi::readWriteByte(byte)
|
|
#define SPI_READ_WRITE_16(word) spi::readWriteWord(word)
|
|
//#define SPI_DELAY() os_delay_us(10)
|
|
#define SPI_DELAY() //os_delay_us(10)
|
|
|
|
#define GET_DREQ() GPIO16_IN // PIN D0
|
|
|
|
#define MP3_CTRL_DESELECT() spi::chipDeselect();
|
|
#define MP3_CTRL_SELECT() spi::chipSelect();
|
|
#define MP3_DATA_DESELECT() GPIO5_H
|
|
#define MP3_DATA_SELECT() GPIO5_L
|
|
|
|
enum OP {
|
|
WRITE = 0x2,
|
|
READ = 0x3,
|
|
};
|
|
|
|
enum Register {
|
|
SCI_MODE = 0x0,
|
|
SCI_STATUS = 0x1,
|
|
SCI_BASS = 0x2,
|
|
SCI_CLOCKF = 0x3,
|
|
SCI_DECODE_TIME = 0x4,
|
|
SCI_AUDATA = 0x5,
|
|
SCI_WRAM = 0x6,
|
|
SCI_WRAMADDR = 0x7,
|
|
SCI_HDAT0 = 0x8,
|
|
SCI_HDAT1 = 0x9,
|
|
SCI_AIADDR = 0xa,
|
|
SCI_VOL = 0xb,
|
|
SCI_AICTRL0 = 0xc,
|
|
SCI_AICTRL1 = 0xd,
|
|
SCI_AICTRL2 = 0xe,
|
|
SCI_AICTRL3 = 0xf,
|
|
};
|
|
|
|
/**
|
|
* @brief Bitfield-combinable enum with all mode-flags that can be set for the VS1003.
|
|
*/
|
|
enum class Mode {
|
|
NONE = 0,
|
|
SM_DIFF = 1,
|
|
SM_SETTOZERO = 2,
|
|
SM_RESET = 4,
|
|
SM_OUTOFWAV = 8,
|
|
SM_POWERDOWN = 16,
|
|
SM_TESTS = 32,
|
|
SM_STREAM = 64,
|
|
SM_SETTOZERO2 = 128,
|
|
SM_DACT = 256,
|
|
SM_SDIORD = 512,
|
|
SM_SDISHARE = 1024,
|
|
SM_SDINEW = 2048,
|
|
SM_ADPCM = 4096,
|
|
SM_ADCPM_HP = 8192,
|
|
SM_LINE_IN = 16384,
|
|
};
|
|
|
|
public:
|
|
enum class RecordSource {
|
|
MICROPHONE,
|
|
LINE_IN
|
|
};
|
|
|
|
enum class PlayState {
|
|
STOPPED,
|
|
PLAYING,
|
|
RECORDING
|
|
};
|
|
|
|
public:
|
|
|
|
VS1003() {
|
|
init();
|
|
}
|
|
|
|
/** initialize the hardware */
|
|
void init() {
|
|
|
|
debugMod(NAME, "init()");
|
|
|
|
SPI_INIT();
|
|
|
|
GPIO5_OUTPUT_SET; // Pin D1 - XDCS
|
|
GPIO16_INPUT_SET; // Pin D0 - DREQ
|
|
|
|
this->state = PlayState::STOPPED;
|
|
|
|
awaitDREQ(); // Wait for the VS1003 to finish boot
|
|
|
|
reset();
|
|
|
|
}
|
|
|
|
/**
|
|
* @brief Set audio volume.
|
|
* @param Audio volume to set on both channels.
|
|
*/
|
|
void setVolume(const uint8_t vol) const {
|
|
debugMod1(NAME, "setVolume(%d)", vol);
|
|
const uint8_t aVol = 255 - vol;
|
|
const uint16_t values = (aVol << 0) | (aVol << 8); // left and right
|
|
writeRegister(SCI_VOL, values);
|
|
}
|
|
|
|
uint8_t getVolume() const {
|
|
const uint16_t values = readRegister(SCI_VOL);
|
|
return (255 - (values & 0xFF));
|
|
}
|
|
|
|
// void play(AudioData ad) {
|
|
// this->playable = ad;
|
|
// this->state = PlayState::PLAYING;
|
|
// }
|
|
|
|
// void appendUnmanaged(const uint8_t* data, const uint16_t size) {
|
|
// ChunkBuffer::Chunk* c = playBuf.wrap(data, size);
|
|
// playBuf.append(c);
|
|
// this->state = PlayState::PLAYING;
|
|
// }
|
|
|
|
void ICACHE_FLASH_ATTR record(RecordSource source) {
|
|
|
|
state = PlayState::RECORDING;
|
|
writeRegister(Register::SCI_CLOCKF, 0x4430); /* 2.0x 12.288MHz */
|
|
os_delay_us(100);
|
|
|
|
writeRegister(Register::SCI_AICTRL0, 12); /* Div -> 12=8kHz 8=12kHz 6=16kHz */
|
|
os_delay_us(100);
|
|
|
|
writeRegister(Register::SCI_AICTRL1, 0); /* Auto gain */
|
|
os_delay_us(100);
|
|
|
|
setVolume(255);
|
|
|
|
os_printf("mode: %d", readRegister(Register::SCI_MODE));
|
|
|
|
writeRegister(Register::SCI_MODE, readRegister(Register::SCI_MODE) | (int)Mode::SM_ADPCM | (int)Mode::SM_RESET);
|
|
os_delay_us(100);
|
|
|
|
os_printf("mode: %d", readRegister(Register::SCI_MODE));
|
|
|
|
|
|
// if(source == RecordSource::LINE_IN) {
|
|
// reset((Mode)((int)Mode::SM_LINE_IN | (int)Mode::SM_ADPCM));
|
|
// } else {
|
|
// reset(Mode::SM_ADPCM);
|
|
// }
|
|
|
|
awaitDREQ();
|
|
os_delay_us(100);
|
|
|
|
}
|
|
|
|
PlayState ICACHE_FLASH_ATTR getState() const {
|
|
return this->state;
|
|
}
|
|
|
|
void play() {
|
|
//if(this->state == PlayState::STOPPED) { reset(); } // makes things more stable
|
|
this->state = PlayState::PLAYING;
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR run() {
|
|
if(this->state == PlayState::PLAYING) {
|
|
if (playBuf.empty()) {
|
|
//uint8_t stopSequence[4] = {0};
|
|
//playChunk32(stopSequence, sizeof(stopSequence)); // end of stream
|
|
//this->state = PlayState::STOPPED;
|
|
} else {
|
|
//const ChunkBuffer::Data chunk = playBuf.get(32);
|
|
//playChunk32(chunk.ptr, chunk.size);
|
|
sendPlayBuffer();
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t getRecordData(uint8_t* buffer, uint32_t maxLength) {
|
|
|
|
uint32_t available = this->getNumAvailableRecordBytes();
|
|
// os_printf("avail: %d\r\n", available);
|
|
|
|
if (available < 256 || available >= 896) {return 0;}
|
|
|
|
|
|
int toRead = 256;
|
|
int read = 0;
|
|
while(read != toRead) {
|
|
const uint16_t data = readRegister(Register::SCI_HDAT0);
|
|
buffer[read] = (data >> 8); ++read;
|
|
buffer[read] = (data & 0xFF); ++read;
|
|
//os_delay_us(10);
|
|
}
|
|
|
|
return toRead;
|
|
|
|
|
|
// for (int i = 0; i < 4; ++i) {
|
|
// os_printf("%d ", buffer[i]);
|
|
// }
|
|
// os_printf("\r\n");
|
|
|
|
// uint32_t len = min(maxLength, available);
|
|
|
|
// for(int i = 0; i < len; ++i) {
|
|
// uint16_t data = readRegister(Register::SCI_HDAT0);
|
|
// buffer[i++] = (data >> 8);
|
|
// buffer[i] = (data & 0xFF);
|
|
// os_delay_us(5);
|
|
// }
|
|
|
|
|
|
|
|
}
|
|
|
|
PlayBuf& getPlayBuffer() {
|
|
return playBuf;
|
|
}
|
|
|
|
void stop() {
|
|
this->state = PlayState::STOPPED;
|
|
playBuf.clear();
|
|
//reset();
|
|
}
|
|
|
|
private:
|
|
|
|
uint32_t getNumAvailableRecordBytes() {
|
|
return (uint32_t)readRegister(Register::SCI_HDAT1) * sizeof(uint16_t);
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR playChunk32(const uint8_t* buffer, const uint32_t len) const {
|
|
debugMod1(NAME, "playChunk32() len:%d", len);
|
|
MP3_CTRL_DESELECT();
|
|
MP3_DATA_SELECT();
|
|
|
|
for(uint32_t i = 0; i < len; ++i) {
|
|
awaitDREQ();
|
|
SPI_WRITE_8(buffer[i]);
|
|
}
|
|
MP3_DATA_DESELECT();
|
|
}
|
|
|
|
void ICACHE_FLASH_ATTR sendPlayBuffer() {
|
|
MP3_CTRL_DESELECT();
|
|
MP3_DATA_SELECT();
|
|
int written = 0;
|
|
while(canConsume() && !playBuf.empty()) {
|
|
const uint8_t byte = playBuf.get();
|
|
if (byte == -1) {break;}
|
|
SPI_WRITE_8(byte);
|
|
++written;
|
|
}
|
|
if (written) {debugMod1(NAME, "sendPlayBuffer(): %d bytes", written);}
|
|
MP3_DATA_DESELECT();
|
|
}
|
|
|
|
/**
|
|
* @brief Write the given register.
|
|
* @param reg Register to write to.
|
|
* @param value Value to write into the given register.
|
|
*/
|
|
void writeRegister(const Register reg, const uint16_t value) const {
|
|
MP3_DATA_DESELECT();
|
|
MP3_CTRL_SELECT();
|
|
SPI_DELAY();
|
|
SPI_WRITE_8(OP::WRITE);
|
|
SPI_WRITE_8(reg);
|
|
SPI_WRITE_16(value); // hi-byte -> low byte
|
|
SPI_DELAY();
|
|
awaitDREQ();
|
|
MP3_CTRL_DESELECT();
|
|
}
|
|
|
|
/**
|
|
* @brief Read the given register.
|
|
* @param reg Register to read.
|
|
* @return The value of the register with the given address.
|
|
*/
|
|
uint16_t readRegister(const Register reg) const {
|
|
MP3_DATA_DESELECT();
|
|
MP3_CTRL_SELECT();
|
|
SPI_DELAY();
|
|
SPI_WRITE_8(OP::READ);
|
|
SPI_WRITE_8(reg);
|
|
const uint16_t res = SPI_READ_16();
|
|
SPI_DELAY();
|
|
awaitDREQ();
|
|
MP3_CTRL_DESELECT();
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* @brief Wait for the VS1003 to set DREQ to high (it wants data).
|
|
*/
|
|
inline void awaitDREQ() const {
|
|
debugMod(NAME, "awaitDREQ()");
|
|
while(!GET_DREQ()) {;}
|
|
//debugMod(NAME, "awaitDREQ() complete");
|
|
}
|
|
|
|
/**
|
|
* @brief VS1003 is able do consume data
|
|
* @return
|
|
*/
|
|
inline bool canConsume() const {
|
|
return GET_DREQ();
|
|
}
|
|
|
|
/**
|
|
* @brief Reset the VS1003 chip.
|
|
* @param modeMask Additional mask to or into the mode that is written to the VS1003's register.
|
|
*/
|
|
void reset(Mode modeMask = Mode::NONE) const {
|
|
|
|
debugMod(NAME, "reset()");
|
|
|
|
writeRegister(SCI_MODE, (2052 | (uint16_t)modeMask)); // TODO: remove SM_TESTS
|
|
os_delay_us(1);
|
|
awaitDREQ();
|
|
writeRegister(SCI_CLOCKF, 0x9800);
|
|
setVolume(0);
|
|
os_delay_us(1);
|
|
|
|
MP3_DATA_SELECT();
|
|
SPI_WRITE_8(0);
|
|
SPI_WRITE_8(0);
|
|
SPI_WRITE_8(0);
|
|
SPI_WRITE_8(0);
|
|
MP3_DATA_DESELECT();
|
|
|
|
os_delay_us(100);
|
|
MP3_CTRL_DESELECT();
|
|
|
|
debugMod(NAME, "reset() complete");
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
PlayState state = PlayState::STOPPED;
|
|
|
|
PlayBuf playBuf;
|
|
|
|
};
|
|
|
|
#endif // VS1003_H
|