Blame | Last modification | View Log | RSS feed
/*
VLSI Solution generic microcontroller example player / recorder for
VS1053.
v1.03 2012-12-11 HH Recording command 'p' was VS1063 only -> removed
Added chip type recognition
v1.02 2012-12-04 HH Command '_' incorrectly printed VS1063-specific fields
v1.01 2012-11-28 HH Untabified
v1.00 2012-11-27 HH First release
*/
#include "player.h"
#include "hal_SPI.h"
#include "hal_hardware_board.h"
#include "pff.h"
/* Download the latest VS1053a Patches package and its
vs1053b-patches-flac.plg. If you want to use the smaller patch set
which doesn't contain the FLAC decoder, use vs1053b-patches.plg instead.
The patches package is available at
http://www.vlsi.fi/en/support/software/vs10xxpatches.html */
//#include "vs1053b-patches.plg"
// Extracted plugin contents:
#define PLUGIN_SIZE 40
const unsigned short plugin[40] = { /* Compressed plugin */
0x0007, 0x0001, 0x8010, 0x0006, 0x001c, 0x3e12, 0xb817, 0x3e14, /* 0 */
0xf812, 0x3e01, 0xb811, 0x0007, 0x9717, 0x0020, 0xffd2, 0x0030, /* 8 */
0x11d1, 0x3111, 0x8024, 0x3704, 0xc024, 0x3b81, 0x8024, 0x3101, /* 10 */
0x8024, 0x3b81, 0x8024, 0x3f04, 0xc024, 0x2808, 0x4800, 0x36f1, /* 18 */
0x9811, 0x0007, 0x0001, 0x8028, 0x0006, 0x0002, 0x2a00, 0x040e,
};
/* We also want to have the VS1053b Ogg Vorbis Encoder plugin. To get more
than one plugin included, we'll have to include it in a slightly more
tricky way. To get the plugin included below, download the latest version
of the VS1053 Ogg Vorbis Encoder Application from
http://www.vlsi.fi/en/support/software/vs10xxapplications.html */
// #define SKIP_PLUGIN_VARNAME
// const unsigned int encoderPlugin[] = {
// #include "venc44k2q05.plg"
// };
// #undef SKIP_PLUGIN_VARNAME
/* VS1053b IMA ADPCM Encoder Fix, available at
http://www.vlsi.fi/en/support/software/vs10xxpatches.html */
// #define SKIP_PLUGIN_VARNAME
// const unsigned int imaFix[] = {
// #include "imafix.plg"
// };
// #undef SKIP_PLUGIN_VARNAME
#define FILE_BUFFER_SIZE 224 // Minimum 32!
#define SDI_MAX_TRANSFER_SIZE 32
#define SDI_END_FILL_BYTES_FLAC 12288
#define SDI_END_FILL_BYTES 2052
//#define REC_BUFFER_SIZE 256
#define REPORT_INTERVAL 4096
#define REPORT_INTERVAL_MIDI 512
#define min(a,b) (((a)<(b))?(a):(b))
enum AudioFormat {
afUnknown,
afRiff,
afOggVorbis,
afMp1,
afMp2,
afMp3,
afAacMp4,
afAacAdts,
afAacAdif,
afFlac,
afWma,
afMidi
} audioFormat = afUnknown;
const char *afName[] = {
"unknown",
"RIFF",
"Ogg",
"MP1",
"MP2",
"MP3",
"AAC MP4",
"AAC ADTS",
"AAC ADIF",
"FLAC",
"WMA",
"MIDI",
};
/*
* SCI Write
*/
void WriteSci(unsigned char addr, unsigned int data) {
unsigned char c;
do { // Wait until DREQ is high
c = VS10XX_DREQ_STAT();
} while (c == 0);
VS10XX_CS_LOW();
spiSendByte(0x02); // Write command code
spiSendByte(addr); // Write SCI register
spiSendByte((unsigned char)(data >> 8));
spiSendByte((unsigned char)(data & 0xFF));
VS10XX_CS_HIGH();
}
/*
* SCI Read
*/
unsigned int ReadSci(unsigned char addr) {
unsigned char buffer[2];
int ret;
unsigned char c;
do { // Wait until DREQ is high
c = VS10XX_DREQ_STAT();
} while (c == 0);
VS10XX_CS_LOW();
spiSendByte(0x03); // Write command code
spiSendByte(addr); // Write SCI register
spiReadFrame(buffer, 2);
ret = buffer[0] << 8;
ret |= buffer[1];
VS10XX_CS_HIGH();
return ret;
}
/*
* SDI Write
*/
int WriteSdi(unsigned char *data, unsigned char bytes) {
if (bytes > SDI_MAX_TRANSFER_SIZE)
return -1;
unsigned char c;
do { // Wait until DREQ is high
c = VS10XX_DREQ_STAT();
} while (c == 0);
VS10XX_DS_LOW();
spiSendFrame(data, (unsigned int)bytes);
VS10XX_DS_HIGH();
return 0;
}
/*
Read 32-bit increasing counter value from addr.
Because the 32-bit value can change while reading it,
read MSB's twice and decide which is the correct one.
*/
unsigned long ReadVS10xxMem32Counter(unsigned int addr) {
unsigned int msbV1, lsb, msbV2;
unsigned long res;
WriteSci(SCI_WRAMADDR, addr+1);
msbV1 = ReadSci(SCI_WRAM);
WriteSci(SCI_WRAMADDR, addr);
lsb = ReadSci(SCI_WRAM);
msbV2 = ReadSci(SCI_WRAM);
if (lsb < 0x8000U) {
msbV1 = msbV2;
}
res = ((unsigned long)msbV1 << 16) | lsb;
return res;
}
/*
Read 32-bit non-changing value from addr.
*/
unsigned long ReadVS10xxMem32(unsigned int addr) {
unsigned int lsb;
WriteSci(SCI_WRAMADDR, addr);
lsb = ReadSci(SCI_WRAM);
return lsb | ((unsigned long)ReadSci(SCI_WRAM) << 16);
}
/*
Read 16-bit value from addr.
*/
unsigned int ReadVS10xxMem(unsigned int addr) {
WriteSci(SCI_WRAMADDR, addr);
return ReadSci(SCI_WRAM);
}
/*
Write 16-bit value to given VS10xx address
*/
void WriteVS10xxMem(unsigned int addr, unsigned int data) {
WriteSci(SCI_WRAMADDR, addr);
WriteSci(SCI_WRAM, data);
}
/*
Write 32-bit value to given VS10xx address
*/
void WriteVS10xxMem32(unsigned int addr, unsigned long data) {
WriteSci(SCI_WRAMADDR, addr);
WriteSci(SCI_WRAM, (unsigned int)data);
WriteSci(SCI_WRAM, (unsigned int)(data>>16));
}
/* Note: code SS_VER=2 is used for both VS1002 and VS1011e */
const unsigned int chipNumber[16] = {
1001, 1011, 1011, 1003, 1053, 1033, 1063, 1103,
0, 0, 0, 0, 0, 0, 0, 0
};
void Set32(unsigned char *d, unsigned long n) {
unsigned int i;
for (i=0; i<4; i++) {
*d++ = (unsigned char)n;
n >>= 8;
}
}
void Set16(unsigned char *d, unsigned int n) {
unsigned int i;
for (i=0; i<2; i++) {
*d++ = (unsigned char)n;
n >>= 8;
}
}
/*
Loads a plugin.
This is a slight modification of the LoadUserCode() example
provided in many of VLSI Solution's program packages.
*/
void LoadPlugin(const unsigned short *d, unsigned short len) {
unsigned int i = 0;
while (i<len) {
unsigned short addr, n, val;
addr = d[i++];
n = d[i++];
if (n & 0x8000U) { /* RLE run, replicate n samples */
n &= 0x7FFF;
val = d[i++];
while (n--) {
WriteSci(addr, val);
}
} else { /* Copy run, copy n samples */
while (n--) {
val = d[i++];
WriteSci(addr, val);
}
}
}
}
enum PlayerStates {
psPlayback = 0,
psStopped
} playerState;
/*
* Executes a software reset
*/
void VS1053SoftwareReset(void) {
unsigned char c;
unsigned short oldMode = ReadSci(SCI_MODE);
WriteSci(SCI_MODE, oldMode | SM_RESET);
__delay_cycles(100);
do { // Wait until DREQ is high
c = VS10XX_DREQ_STAT();
} while (c == 0);
}
/*
This function plays back an audio file.
File playback:
1. Send an audio file to VS1053b
2. Read extra parameter value endFillByte
3. Send at least 2052 bytes of endFillByte
4. Set SCI_MODE bit SM_CANCEL
5. Send at least 32 bytes of endFillByte
6. Read SCI_MODE. If SM_CANCEL is set, goto step 5
If still set after 2048 bytes, do a software reset
Cancelling playback:
1. Send a portion of an audio file to VS1053b
2. Set SCI_MODE bit SM_CANCEL
3. Continue to send audio file, check SM_CANCEL every 32 bytes
If set, goto step 3
If still set after 2048 bytes, do a software reset
4. Read extra parameter value endFillByte
5. Send 2052 bytes of endFillByte
*/
void VS1053PlayFile(const char *fileName) {
unsigned char playBuf[FILE_BUFFER_SIZE];
unsigned short bytesRead; // Number of bytes read by pf_read()
unsigned short bytesInBuffer; // How many valid bytes in buffer left
unsigned short pos = 0; // File position
int endFillByte = 0; // What byte value to send after file
int playMode = ReadVS10xxMem(PAR_PLAY_MODE);
long nextReportPos = 0; // File pointer where to next collect/report
FRESULT res;
unsigned int i;
playerState = psPlayback; // Set state to normal playback
WriteSci(SCI_DECODE_TIME, 0); // Reset DECODE_TIME
res = pf_open(fileName);
if (res != FR_OK)
return;
/* Main playback loop */
// Read the entire file FILE_BUFFER_SIZE (32) bytes at a time
do {
unsigned char *bufP = playBuf;
// Read FILE_BUFFER_SIZE (32) bytes from the file into the buffer
res = pf_read(playBuf, FILE_BUFFER_SIZE, &bytesRead);
if (res != FR_OK)
break;
bytesInBuffer = bytesRead;
while (bytesInBuffer && playerState != psStopped) {
if (!(playMode & PAR_PLAY_MODE_PAUSE_ENA)) {
int bytesToWrite = min(SDI_MAX_TRANSFER_SIZE, bytesInBuffer);
// This is the heart of the algorithm: on the following line
// actual audio data gets sent to VS10xx.
WriteSdi(bufP, bytesToWrite);
bufP += bytesToWrite;
bytesInBuffer -= bytesToWrite;
pos += bytesToWrite;
}
/* If playback is going on as normal, see if we need to collect and
possibly report */
if (playerState == psPlayback && pos >= nextReportPos) {
nextReportPos += (audioFormat == afMidi || audioFormat == afUnknown) ?
REPORT_INTERVAL_MIDI : REPORT_INTERVAL;
/* It is important to collect endFillByte while still in normal
playback. If we need to later cancel playback or run into any
trouble with e.g. a broken file, we need to be able to repeatedly
send this byte until the decoder has been able to exit. */
endFillByte = ReadVS10xxMem(PAR_END_FILL_BYTE);
}
}
} while(bytesRead == FILE_BUFFER_SIZE && playerState != psStopped);
/* Earlier we collected endFillByte. Now, just in case the file was
broken, or if a cancel playback command has been given, write
SDI_END_FILL_BYTES (2052) bytes of endFillByte. */
for (i = 0; i < FILE_BUFFER_SIZE; i++) {
playBuf[i] = endFillByte;
}
for (i = 0; i < SDI_END_FILL_BYTES; i += SDI_MAX_TRANSFER_SIZE) {
WriteSdi(playBuf, SDI_MAX_TRANSFER_SIZE);
}
/* If the file actually ended, and playback cancellation was not
done earlier, do it now. */
if (playerState == psPlayback) {
pos = SDI_MAX_TRANSFER_SIZE;
unsigned short oldMode = ReadSci(SCI_MODE);
WriteSci(SCI_MODE, oldMode | SM_CANCEL);
// Write 32 bytes of endFillByte
WriteSdi(playBuf, SDI_MAX_TRANSFER_SIZE);
i = ReadSci(SCI_MODE);
while (i & SM_CANCEL) {
// Reset after sending 2048 bytes
if (pos > 2048) {
VS1053SoftwareReset();
break;
}
WriteSdi(playBuf, SDI_MAX_TRANSFER_SIZE);
pos += SDI_MAX_TRANSFER_SIZE;
i = ReadSci(SCI_MODE);
}
}
}
/*
* Main initialization function
*/
int VS1053Init(void) {
if (VSInitHardware() != 0)
return 1;
if (VSInitSoftware() != 0)
return 1;
return 0;
}
/*
Hardware Initialization for VS1053.
*/
int VSInitHardware(void) {
/* Write here your microcontroller code which puts VS10xx in hardware
reset and back (set xRESET to 0 for at least a few clock cycles,
then to 1). */
// // Chip Command Select (idle high)
// VS10XX_CS_PxOUT |= VS10XX_CS;
// VS10XX_CS_PxDIR |= VS10XX_CS;
//
// // Chip Data Select (idle high)
// VS10XX_DC_PxOUT |= VS10XX_DC;
// VS10XX_DC_PxDIR |= VS10XX_DC;
//
// // Reset (idle high)
// VS10XX_RESET_PxOUT |= VS10XX_RESET;
// VS10XX_RESET_PxDIR |= VS10XX_RESET;
//
// // DREQ Line
// VS10XX_DREQ_PxDIR &= ~VS10XX_DREQ;
CHIP_RESET_LOW();
__delay_cycles(10000);
CHIP_RESET_HIGH();
unsigned char c;
do { // Wait until DREQ is high
c = VS10XX_DREQ_STAT();
} while (c == 0);
return 0;
}
/*
Software Initialization for VS1053.
Note that you need to check whether SM_SDISHARE should be set in
your application or not.
*/
int VSInitSoftware(void) {
unsigned int ssVer;
unsigned char c;
do { // Wait until DREQ is high
c = VS10XX_DREQ_STAT();
} while (c == 0);
/* Initial SPI bus speed needs to be < 1.5Mhz */
halSPISetSpeedLow();
/* Start initialization with a dummy read, which makes sure our
microcontroller chips selects and everything are where they
are supposed to be and that VS10xx's SCI bus is in a known state. */
ReadSci(SCI_MODE);
/* First real operation is a software reset. After the software
reset we know what the status of the IC is. You need, depending
on your application, either set or not set SM_SDISHARE. See the
datasheet for details. */
// WriteSci(SCI_MODE, SM_SDINEW|SM_SDISHARE|SM_TESTS|SM_RESET);
WriteSci(SCI_MODE, SM_SDINEW|SM_RESET);
/* A quick sanity check: write to two registers, then test if we
get the same results. Note that if you use a too high SPI
speed, the MSB is the most likely to fail when read again. */
WriteSci(SCI_HDAT0, 0xABAD);
WriteSci(SCI_HDAT1, 0x1DEA);
if (ReadSci(SCI_HDAT0) != 0xABAD || ReadSci(SCI_HDAT1) != 0x1DEA) {
// There is something wrong with VS10xx
return 1;
}
/* Set the clock. Until this point we need to run SPI slow so that
we do not exceed the maximum speeds mentioned in
Chapter SPI Timing Diagram in the datasheet. */
WriteSci(SCI_CLOCKF, HZ_TO_SC_FREQ(12288000) | SC_MULT_53_35X | SC_ADD_53_10X);
// WriteSci(SCI_CLOCKF, 0x6000);
__delay_cycles(10000);
/* Now when we have upped the VS10xx clock speed, the microcontroller
SPI bus can run faster. Do that before you start playing or
recording files. */
halSPISetSpeedHigh();
/* A quick sanity check: write to two registers, then test if we
get the same results. Note that if you use a too high SPI
speed, the MSB is the most likely to fail when read again. */
WriteSci(SCI_HDAT0, 0xABAD);
WriteSci(SCI_HDAT1, 0x1DEA);
if (ReadSci(SCI_HDAT0) != 0xABAD || ReadSci(SCI_HDAT1) != 0x1DEA) {
// There is something wrong with VS10xx
return 1;
}
/* Check VS10xx type */
ssVer = ((ReadSci(SCI_STATUS) >> 4) & 15);
if (chipNumber[ssVer]) {
// Chip is VS%d\n", chipNumber[ssVer]);
if (chipNumber[ssVer] != 1053) {
// Incorrect chip
return 1;
}
} else {
// Unknown VS10xx SCI_MODE field
return 1;
}
/* Set up other parameters. */
// WriteVS10xxMem(PAR_CONFIG1, PAR_CONFIG1_AAC_SBR_SELECTIVE_UPSAMPLE);
/* Set volume level to max */
WriteSci(SCI_VOL, 0x0000);
/* Now it's time to load the proper patch set. */
LoadPlugin(plugin, sizeof(plugin)/sizeof(plugin[0]));
/* We're ready to go. */
return 0;
}