/** * 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 } } } //=================================================================