Subversion Repositories Code-Repo

Rev

Rev 251 | Blame | Compare with Previous | 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         224

#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;
}