/**
* This is a driver library for VS1053 MP3 Codec Breakout
* (Ogg Vorbis / MP3 / AAC / WMA / FLAC / MIDI Audio Codec Chip).
* Adapted for Espressif ESP8266 and ESP32 boards.
*
* version 1.0.1
*
* Licensed under GNU GPLv3
* Copyright © 2018
*
* @authors baldram, edzelf, MagicCube, maniacbug
*
* Development log:
* - 2011: initial VS1053 Arduino library
* originally written by J. Coliz (github: @maniacbug),
* - 2016: refactored and integrated into Esp-radio sketch
* by Ed Smallenburg (github: @edzelf)
* - 2017: refactored to use as PlatformIO library
* by Marcin Szalomski (github: @baldram | twitter: @baldram)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License or later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "MMBasic_Includes.h"
#include "Hardware_Includes.h"
#include "VS1053.h"
#include "vs1053b-patches.h"
#define LOG(...)
extern void __not_in_flash_func(spi_write_fast)(spi_inst_t *spi, const uint8_t *src, size_t len);
extern void __not_in_flash_func(spi_finish)(spi_inst_t *spi);
//#define LOG printf
#define _BV( bit ) ( 1<<(bit) )
uint8_t cs_pin; // Pin where CS line is connected
uint8_t dcs_pin; // Pin where DCS line is connected
uint8_t dreq_pin; // Pin where DREQ line is connected
uint8_t reset_pin; // Pin where DREQ line is connected
uint8_t curvol; // Current volume setting 0..100%
int8_t curbalance = 0; // Current balance setting -100..100
uint8_t endFillByte; // Byte to send when stopping song
const uint8_t vs1053_chunk_size = 32;
// SCI Register
const uint8_t SCI_MODE = 0x0;
const uint8_t SCI_STATUS = 0x1;
const uint8_t SCI_BASS = 0x2;
const uint8_t SCI_CLOCKF = 0x3;
const uint8_t SCI_DECODE_TIME = 0x4; // current decoded time in full seconds
const uint8_t SCI_AUDATA = 0x5;
const uint8_t SCI_WRAM = 0x6;
const uint8_t SCI_WRAMADDR = 0x7;
const uint8_t SCI_AIADDR = 0xA;
const uint8_t SCI_VOL = 0xB;
const uint8_t SCI_AICTRL0 = 0xC;
const uint8_t SCI_AICTRL1 = 0xD;
const uint8_t SCI_num_registers = 0xF;
// SCI_MODE bits
const uint8_t SM_SDINEW = 11; // Bitnumber in SCI_MODE always on
const uint8_t SM_RESET = 2; // Bitnumber in SCI_MODE soft reset
const uint8_t SM_CANCEL = 3; // Bitnumber in SCI_MODE cancel song
const uint8_t SM_TESTS = 5; // Bitnumber in SCI_MODE for tests
const uint8_t SM_LINE1 = 14; // Bitnumber in SCI_MODE for Line input
const uint8_t SM_STREAM = 6; // Bitnumber in SCI_MODE for Streaming Mode
const uint8_t SM_LAYER12 = 1;
const uint16_t ADDR_REG_GPIO_DDR_RW = 0xc017;
const uint16_t ADDR_REG_GPIO_VAL_R = 0xc018;
const uint16_t ADDR_REG_GPIO_ODATA_RW = 0xc019;
const uint16_t ADDR_REG_I2S_CONFIG_RW = 0xc040;
#define xmit_multi(a,b) spi_write_blocking((AUDIO_SPI==1 ? spi0 : spi1),a,b);
static BYTE __not_in_flash_func(xchg)(BYTE data_out){
BYTE data_in=0;
spi_write_read_blocking((AUDIO_SPI==1 ? spi0 : spi1),&data_out,&data_in,1);
return data_in;
}
uint8_t stdmax(int a, int b){
if(a>b)return a;
return b;
}
int stdmap(int v){
v=(v*80)/100;
if(v==0)return 0xFE;
else return 100-v;
}
void await_data_request() {
while (!(gpio_get(dreq_pin))) {
}
}
void control_mode_on() {
gpio_put(dcs_pin,GPIO_PIN_SET);
gpio_put(cs_pin,GPIO_PIN_RESET);
}
void control_mode_off() {
gpio_put(cs_pin,GPIO_PIN_SET);
}
void __not_in_flash_func(data_mode_on()) {
gpio_put(cs_pin,GPIO_PIN_SET);
gpio_put(dcs_pin,GPIO_PIN_RESET);
}
void __not_in_flash_func(data_mode_off()) {
gpio_put(dcs_pin,GPIO_PIN_SET);
}
bool data_request() {
return (gpio_get(dreq_pin) == 1);
}
uint16_t read_register(uint8_t _reg){
uint16_t result;
control_mode_on();
xchg(3);
xchg(_reg);
// Note: transfer16 does not seem to work
result = (xchg(0xFF) << 8) | // Read 16 bits data
(xchg(0xFF));
await_data_request(); // Wait for DREQ to be HIGH again
control_mode_off();
return result;
}
void writeRegister(uint8_t _reg, uint16_t _value){
control_mode_on();
xchg(2); // Write operation
xchg(_reg); // Register to write (0..0xF)
xchg(_value>>8);
xchg(_value & 0xFF);
// SPI.write16(_value); // Send 16 bits data
await_data_request();
control_mode_off();
}
void __not_in_flash_func(sdi_send_buffer)(uint8_t *data, size_t len) {
size_t chunk_length; // Length of chunk 32 byte or shorter
gpio_put(cs_pin,GPIO_PIN_SET);
gpio_put(dcs_pin,GPIO_PIN_RESET);
while (len) // More to do?
{
while (!(gpio_get(dreq_pin))) {
}
chunk_length = len;
if (len > vs1053_chunk_size) {
chunk_length = vs1053_chunk_size;
}
len -= chunk_length;
if(PinDef[Option.AUDIO_CLK_PIN].mode & SPI0SCK)spi_write_fast(spi0, data, chunk_length);
else spi_write_fast(spi1, data, chunk_length);
// xmit_multi(data, chunk_length);
data += chunk_length;
}
if(PinDef[Option.AUDIO_CLK_PIN].mode & SPI0SCK)spi_finish(spi0);
else spi_finish(spi1);
gpio_put(dcs_pin,GPIO_PIN_SET);
}
void sdi_send_fillers(size_t len) {
size_t chunk_length; // Length of chunk 32 byte or shorter
data_mode_on();
while (len) // More to do?
{
await_data_request(); // Wait for space available
chunk_length = len;
if (len > vs1053_chunk_size) {
chunk_length = vs1053_chunk_size;
}
len -= chunk_length;
while (chunk_length--) {
xchg(endFillByte);
}
}
data_mode_off();
}
void wram_write(uint16_t address, uint16_t data) {
writeRegister(SCI_WRAMADDR, address);
writeRegister(SCI_WRAM, data);
}
uint16_t wram_read(uint16_t address) {
writeRegister(SCI_WRAMADDR, address); // Start reading from WRAM
return read_register(SCI_WRAM); // Read back result
}
uint16_t VS1053free(void){
uint16_t wrp, rdp; // VS1053b read and write pointers
writeRegister(SCI_WRAMADDR, 0x5A7D); // Start reading from WRAM
wrp = read_register(SCI_WRAM);
rdp = read_register(SCI_WRAM);
return (wrp-rdp) & 1023;
}
bool testComm(const char *header) {
// Test the communication with the VS1053 module. The result wille be returned.
// If DREQ is low, there is problably no VS1053 connected. Pull the line HIGH
// in order to prevent an endless loop waiting for this signal. The rest of the
// software will still work, but readbacks from VS1053 will fail.
int i; // Loop control
uint16_t r1, r2, cnt = 0;
uint16_t delta = 300; // 3 for fast SPI
uSec(20000);
if (!gpio_get(dreq_pin)) {
error("VS1053 not properly installed!");
// Allow testing without the VS1053 module
// pinMode(dreq_pin, INPUT_PULLUP); // DREQ is now input with pull-up
return false; // Return bad result
}
// Further TESTING. Check if SCI bus can write and read without errors.
// We will use the volume setting for this.
// Will give warnings on serial output if DEBUG is active.
// A maximum of 20 errors will be reported.
if (strstr(header, "Fast")) {
delta = 30; // Fast SPI, more loops
}
LOG("%s", header); // Show a header
for (i = 0; (i < 0xFFFF) && (cnt < 20); i += delta) {
writeRegister(SCI_VOL, i); // Write data to SCI_VOL
r1 = read_register(SCI_VOL); // Read back for the first time
r2 = read_register(SCI_VOL); // Read back a second time
if (r1 != r2 || i != r1 || i != r2) // Check for 2 equal reads
{
error("VS1053 read failure");
cnt++;
uSec(10000);
}
}
return (cnt == 0); // Return the result
}
void VS1053reset(uint8_t _reset_pin){
reset_pin=_reset_pin;
PinSetBit(PINMAP[reset_pin],LATCLR);
uSec(20000);
PinSetBit(PINMAP[reset_pin],LATSET);
PinSetBit(PINMAP[dreq_pin],CNPUSET);
uSec(100000);
}
void VS1053(uint8_t _cs_pin, uint8_t _dcs_pin, uint8_t _dreq_pin, uint8_t _reset_pin){
dreq_pin=_dreq_pin;
cs_pin=_cs_pin;
dcs_pin=_dcs_pin;
reset_pin=_reset_pin;
PinSetBit(PINMAP[reset_pin],LATCLR);
uSec(20000);
PinSetBit(PINMAP[reset_pin],LATSET);
PinSetBit(PINMAP[dreq_pin],CNPUSET);
uSec(100000);
LOG("\r\n");
LOG("Reset VS1053...\r\n");
LOG("End reset VS1053...\r\n");
// gpio_put(cs_pin,GPIO_PIN_SET); // Back to normal again
// gpio_put(dcs_pin,GPIO_PIN_SET);
// uSec(500000);
// Init SPI in slow mode ( 0.2 MHz )
// SET_SPI_CLK(display_details[device].speed, display_details[device].CPOL, display_details[device].CPHASE);
spi_init((AUDIO_SPI==1 ? spi0 : spi1), 8000);
spi_set_format((AUDIO_SPI==1 ? spi0 : spi1), 8, 0,0, SPI_MSB_FIRST);
// printDetails("Right after reset/startup");
uSec(20000);
// printDetails("20 msec after reset");
if (testComm("Slow SPI,Testing VS1053 read/write registers...\r\n")) {
//softReset();
// Switch on the analog parts
writeRegister(SCI_AUDATA, 44101); // 44.1kHz stereo
// The next clocksetting allows SPI clocking at 5 MHz, 4 MHz is safe then.
writeRegister(SCI_CLOCKF, 0xE000); // Normal clock settings multiplyer 3.0 = 12.2 MHz
// SPI Clock to 4 MHz. Now you can set high speed SPI clock.
spi_init((AUDIO_SPI==1 ? spi0 : spi1), 5400000);
// PInt(spi_get_baudrate(AUDIO_SPI==1 ? spi0 : spi1));PRet();
spi_set_format((AUDIO_SPI==1 ? spi0 : spi1), 8, 0,0, SPI_MSB_FIRST);
uSec(5000);
writeRegister(SCI_MODE, _BV(SM_SDINEW) | _BV(SM_LINE1) | _BV(SM_LAYER12));
testComm("Fast SPI, Testing VS1053 read/write registers again...\r\n");
uSec(5000);
await_data_request();
endFillByte = wram_read(0x1E06) & 0xFF;
LOG("endFillByte is %X\r\n", endFillByte);
//printDetails("After last clocksetting") ;
uSec(5000);
}
}
void setVolume(uint8_t vol) {
// Set volume. Both left and right.
// Input value is 0..100. 100 is the loudest.
uint8_t valueL, valueR; // Values to send to SCI_VOL
curvol = vol; // Save for later use
valueL = vol;
valueR = vol;
if (curbalance < 0) {
valueR = stdmax(0, vol + curbalance);
} else if (curbalance > 0) {
valueL = stdmax(0, vol - curbalance);
}
valueL = stdmap(valueL); // 0..100% to left channel
valueR = stdmap(valueR); // 0..100% to right channel
writeRegister(SCI_VOL, (valueL << 8) | valueR); // Volume left and right
}
void setVolumes(int valueL, int valueR) {
valueL = stdmap(valueL); // 0..100% to left channel
valueR = stdmap(valueR); // 0..100% to right channel
int value=((valueL << 8) | valueR);
writeRegister(SCI_VOL, value); // Volume left and right
}
void setBalance(int8_t balance) {
if (balance > 100) {
curbalance = 100;
} else if (balance < -100) {
curbalance = -100;
} else {
curbalance = balance;
}
}
void setTone(uint8_t *rtone) { // Set bass/treble (4 nibbles)
// Set tone characteristics. See documentation for the 4 nibbles.
uint16_t value = 0; // Value to send to SCI_BASS
int i; // Loop control
for (i = 0; i < 4; i++) {
value = (value << 4) | rtone[i]; // Shift next nibble in
}
writeRegister(SCI_BASS, value); // Volume left and right
}
uint8_t getVolume() { // Get the currenet volume setting.
return curvol;
}
int8_t getBalance() { // Get the currenet balance setting.
return curbalance;
}
void startSong() {
sdi_send_fillers(10);
}
void __not_in_flash_func(playChunk)(uint8_t *data, size_t len) {
sdi_send_buffer(data, len);
}
void stopSong() {
uint16_t modereg; // Read from mode register
int i; // Loop control
sdi_send_fillers(2052);
uSec(10000);
writeRegister(SCI_MODE, _BV(SM_SDINEW) | _BV(SM_CANCEL));
for (i = 0; i < 200; i++) {
sdi_send_fillers(32);
modereg = read_register(SCI_MODE); // Read status
if ((modereg & _BV(SM_CANCEL)) == 0) {
sdi_send_fillers(2052);
LOG("Song stopped correctly after %d msec\r\n", i * 10);
return;
}
uSec(10000);
}
printDetails("Song stopped incorrectly!");
}
void LoadUserCode(void) {
int i;
for (i=0;i> 4);
}
/**
* Provides current decoded time in full seconds (from SCI_DECODE_TIME register value)
*
* When decoding correct data, current decoded time is shown in SCI_DECODE_TIME
* register in full seconds. The user may change the value of this register.
* In that case the new value should be written twice to make absolutely certain
* that the change is not overwritten by the firmware. A write to SCI_DECODE_TIME
* also resets the byteRate calculation.
*
* SCI_DECODE_TIME is reset at every hardware and software reset. It is no longer
* cleared when decoding of a file ends to allow the decode time to proceed
* automatically with looped files and with seamless playback of multiple files.
* With fast playback (see the playSpeed extra parameter) the decode time also
* counts faster. Some codecs (WMA and Ogg Vorbis) can also indicate the absolute
* play position, see the positionMsec extra parameter in section 10.11.
*
* @see VS1053b Datasheet (1.31) / 9.6.5 SCI_DECODE_TIME (RW)
*
* @return current decoded time in full seconds
*/
uint16_t getDecodedTime() {
return read_register(SCI_DECODE_TIME);
}
/**
* Clears decoded time (sets SCI_DECODE_TIME register to 0x00)
*
* The user may change the value of this register. In that case the new value
* should be written twice to make absolutely certain that the change is not
* overwritten by the firmware. A write to SCI_DECODE_TIME also resets the
* byteRate calculation.
*/
void clearDecodedTime() {
writeRegister(SCI_DECODE_TIME, 0x00);
writeRegister(SCI_DECODE_TIME, 0x00);
}
/**
* Fine tune the data rate
*/
void adjustRate(long ppm2) {
writeRegister(SCI_WRAMADDR, 0x1e07);
writeRegister(SCI_WRAM, ppm2);
writeRegister(SCI_WRAM, ppm2 >> 16);
// oldClock4KHz = 0 forces adjustment calculation when rate checked.
writeRegister(SCI_WRAMADDR, 0x5b1c);
writeRegister(SCI_WRAM, 0);
// Write to AUDATA or CLOCKF checks rate and recalculates adjustment.
writeRegister(SCI_AUDATA, read_register(SCI_AUDATA));
}
/**
* Load the latest generic firmware patch
*/
void loadDefaultVs1053Patches() {
LoadUserCode();
LOG("Loaded latest patch\r\n");
};
#define PLUGIN_SIZE 28
const unsigned short plugin[28] = { /* Compressed plugin */
0x0007, 0x0001, 0x8050, 0x0006, 0x0014, 0x0030, 0x0715, 0xb080, /* 0 */
0x3400, 0x0007, 0x9255, 0x3d00, 0x0024, 0x0030, 0x0295, 0x6890, /* 8 */
0x3400, 0x0030, 0x0495, 0x3d00, 0x0024, 0x2908, 0x4d40, 0x0030, /* 10 */
0x0200, 0x000a, 0x0001, 0x0050,
};
void RTLoadUserCode(void) {
int i = 0;
while (i= 0xE0) {
sendMIDI(data1);
sendMIDI(data2);
} else {
sendMIDI(data1);
}
data_mode_off() ;
}
//Send a MIDI note-on message. Like pressing a piano key
//channel ranges from 0-15
void noteOn(uint8_t channel, uint8_t note, uint8_t attack_velocity) {
talkMIDI( (0x90 | channel), note, attack_velocity);
}
//Send a MIDI note-off message. Like releasing a piano key
void noteOff(uint8_t channel, uint8_t note, uint8_t release_velocity) {
talkMIDI( (0x80 | channel), note, release_velocity);
}
void miditest(int test) {
RTLoadUserCode();
uSec(100000);
if(test==0)return;
talkMIDI(0xB0, 0x07, 120); //0xB0 is channel message, set channel volume to near max (127)
if(test==1){
//Demo Basic MIDI instruments, GM1
//=================================================================
MMPrintString("Basic Instruments\r\n");
talkMIDI(0xB0, 0, 0x00); //Default bank GM1
//Change to different instrument
for(int instrument = 0 ; instrument < 127 ; instrument++) {
CheckAbort();
MMPrintString(" Instrument: ");
PInt(instrument);PRet();
talkMIDI(0xC0, instrument, 0); //Set instrument number. 0xC0 is a 1 data byte command
//Play notes from F#-0 (30) to F#-5 (90):
for (int note = 30 ; note < 40 ; note++) {
MMPrintString("N:");
PInt(note);PRet();
//Note on channel 1 (0x90), some note value (note), middle velocity (0x45):
noteOn(0, note, 127);
uSec(200000);
//Turn off the note with a given off/release velocity
noteOff(0, note, 127);
uSec(50000);
}
uSec(100000); //uSec between instruments
}
} else if(test==2){
//Demo GM2 / Fancy sounds
//=================================================================
MMPrintString("Demo Fancy Sounds\r\n");
talkMIDI(0xB0, 0, 0x78); //Bank select drums
//For this bank 0x78, the instrument does not matter, only the note
for(int instrument = 30 ; instrument < 31 ; instrument++) {
CheckAbort();
MMPrintString(" Instrument: ");
PInt(instrument);PRet();
talkMIDI(0xC0, instrument, 0); //Set instrument number. 0xC0 is a 1 data byte command
//Play fancy sounds from 'High Q' to 'Open Surdo [EXC 6]'
for (int note = 27 ; note < 87 ; note++) {
MMPrintString("N:");
PInt(note);PRet();
//Note on channel 1 (0x90), some note value (note), middle velocity (0x45):
noteOn(0, note, 127);
uSec(50000);
//Turn off the note with a given off/release velocity
noteOff(0, note, 127);
uSec(50000);
}
uSec(100000); //uSec between instruments
}
} else if(test==3){
//Demo Melodic
//=================================================================
MMPrintString("Demo Melodic? Sounds\r\n");
talkMIDI(0xB0, 0, 0x79); //Bank select Melodic
//These don't sound different from the main bank to me
//Change to different instrument
for(int instrument = 27 ; instrument < 87 ; instrument++) {
CheckAbort();
MMPrintString(" Instrument: ");
PInt(instrument);PRet();
talkMIDI(0xC0, instrument, 0); //Set instrument number. 0xC0 is a 1 data byte command
//Play notes from F#-0 (30) to F#-5 (90):
for (int note = 30 ; note < 40 ; note++) {
MMPrintString("N:");PRet();
PInt(note);
//Note on channel 1 (0x90), some note value (note), middle velocity (0x45):
noteOn(0, note, 127);
uSec(500000);
//Turn off the note with a given off/release velocity
noteOff(0, note, 127);
uSec(50000);
}
uSec(100000); //uSec between instruments
}
}
}
//=================================================================