0,0 → 1,524 |
/* |
|
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; |
} |