Monday, March 11, 2019

MP3 Working, on to the Schematic

I was fairly convinced that MP3 quality problems were due to data rate, that is I wasnt feeding the VS1053 fast enough.  So I played with a lot of things in the loop to increase the idle time.  But nothing worked, no matter what I did, the quality never changed.  Then I remembered that the FLASH SPI clock was still running at 200K from when I was just trying to get it to work.  Because of the resistors in the PIC32MX470 curiosity board, going much above 1MHz causes the waveforms to degrade.  (I need to jump these on the SPI bus.)  After moving the SPI clock up to 1MHz, I get near CD quality, at least for my poor hearing (too many years in Naval aviation). 

I still need to optimize the idle time, since there are still motors, LEDs and the BLE interface to service.  But that come with time.  Right now I am working on the schematic, so I can get the PCBs out to fab and built.

Monday, March 4, 2019

MP3 is Working, Maybe

Last time I described my basic plan of attack to solve this problem with the MP3.  There has been some progress.

The VS1053/1063 allows for a sweeping frequency tone output, which I didnt think was working.  After putting a scope on it, I saw that it was.  The frequency starts very low and slowly increases.  It was taking almost 60 seconds to get to an audible range. (After 20+ years in Naval Aviation, my audible range is not what it used to be.)  After seeing this on the scope, I finally heard it.  So the VS1053 is working.

I still could not get the MP3 file to play.  So I moved the Click board to an HPC Curiosity board where I had direct control of the SPI on a PIC18F47K40.  After fighting a K40 errata error I forgot about, I finally was able to play a very short MP3 file, 1695 bytes.  The MP3 file was embedded in the code.

I then moved back to the PIC32MX470 with the embedded MP3 file.  Still no sound.  Now I started putting printf statements everywhere to watch the buffer action.  I had double buffered the file.  The VS1053 will accept at least 32 bytes if DREQ is high. Thus I use a small buffer (we will call Buffer32) to transfer the file .  Thus I always transfer 32 bytes to the VS1053.  But to cut down on overhead, I read from the file in 512 byte chunks, except the first time where it is 1024 bytes, into another buffer (we will call Buffer1024).  The idea is to read 512 bytes in to Buffer1024 every time the pointer crosses 512 or 1024.  Then the play function reads out of Buffer1024 into Buffer32 and sends this to the VS1053. 

Well the buffer pointers weren't even close to doing what I wanted.  Just really dumb errors.  After cleaning that up, I am now listening to Tube Snake Boogie by ZZ Top.  The quality is not great, probably some problem with the data flow is not fast enough yet.  But at least the basics are working.

The driver needs a lot of cleaning up and it may be awhile before I get there.I am committed to publishing this driver so either watch here or the Microchip forums for more info.

Saturday, March 2, 2019

MP3 Output is Hard to Get

I always debate about writing about trials and tribulations of getting something to work.  Mostly I just wait until I solve the problem.  But it has been a while, so here goes.

I have the VS1053/VS1063 driver coded.  And as far as I can tell from the logic analyzer, the data is being transferred correctly.  The driver is doing what it is supposed to do.  I see the file open, the file size determined, and then the buffers are filled with data and transferred to the mp3 player.  When the all of the file data is transferred, the player gracefully shuts down.  Also the mfg start up routine is working, in that I can read and write registers and I get the results I expect.

So next I coded some of the chip test routines that are provided natively in the chip.  Baiscally it is supposed to generate a sine wave.  Cant hear anything, but will look at it with a scope today. 

This seemed simple and the logic analyzer gave me confidence it was working.  Next step is to peruse some other samples of working code looking for a gotcha.  If that doesn't work, I have a self contained program, mp3 file included as a constant, that works on a PIC18F,  Pull out the HPC curiosity board and see if I can get it to work there.

Tuesday, February 26, 2019

HID Problem Solved - For Now

I found the issue keeping the HID interface from working.  Every time you receive a HID report, you need to rearm the read interface, which I knew and thought was implemented.  This also needs to happen at the beginning in initialization.  There were several calls to his in the switch statements, but either the code never passed through these case statements or did so at the wrong time.  I put printf statements in the HID event handler and the USB task case statements so I could watch the startup process.  The only HID report that was happening was Set IDLE and then the interface would hang.  So I added the rearm in that event and it all works now.  The PC program and the PIC32 device exchange messages and the PIC32 device is initialized.  Not sure that is the correct place for it, but that is where it is for now.

I coded the MP3 player portion and ran some tests.  When commanded the, PIC32 loads the file, reads it in and sends it to the VS1053.  When it is done, the file is closed and the USB is reconnected.The player code was hung up trying to stop the VS1053, but for a first run, not bad.  Now I need to verify that VS1053 is initialized correctly and the data is going to the right ports.  Time for the logic analyzer.

Observation:
I have noticed that the demo apps are not consistent in implementation.  I have been through about 5 or 6 of them now as I worked on getting the SPI to work, SST26 FAT, a basic HID and then HID and MSD.  They are good at showing functionality, may not be as good at robust production code.  And when you combine two different apps to get combination functionality, you can run into some quirks.

Sunday, February 24, 2019

Mostly Working MP3 Player (VS1053) and HID Problems

In the last week I have made good progress on the VS1053/1063 MP3 Harmony style driver.  I am talking to it, have completed the software initialization and load the patches.  The next step is to implement the player.  At first this is going to be direct processor control.  Eventually down the road, I will convert to DMA, but I need working software first.  Once I am finished with this I will publish the driver here and on the Microchip Forums so other people can get head start on implementing a hardware version of an MP3 player.

For the next step of getting the player to actually work, I need to bring the file system on line and temporarily disconnect the USB MSD (for now).  So to do this I decided to use the HID interface and the PC program I have.  When I last looked at this, Windows had recognized the HID device, the generic HID test I have could read all the configuration data.  My PC program saw it was connected and would send a message.  Some crazy output started, but I had seen that before when the PC and the HID device got into a loop of not recognizing the data they were sending.  So I moved on to the MP3 driver because I thought that was more important.

Well there was more to it.  There always is.  The HID events are not firing after the first one.  I went back to the simple HID test program I did and it works as expected.  The HID events fire and data moves between them.  So the first thought is the MSD portion is getting in the way somehow.  The MSD works fine.  I have compared the HID test program and the HID_MSD program I am now testing and cannot find a difference.  I may have to start from scratch again, except build the HID interface fist and then add the MSD.  Not sure what I am going to do yet, but the next few days are going to be interesting.


Thursday, February 14, 2019

Basic Brick Controller

Last night I integrated the HID device and my basic packet dispatcher routines.  My basic software structure is as HID packets come in, they are loaded into processing queue.  The HID packet size is set to 64, but in my protocol, the packet size is limited to 20.  (Does not apply to text strings coming back from the Brick Controller.)  These are just control packets except for when a script is downloaded, but that is a maximum of 256 bytes.  Then serial queue is processed and the commands are executed.  All of this seems to work now.

Anywho, the development board connected to the PC, the Brick Buddy PC software recognized the device and started downloading configuration data from the Brick Controller.  At the same time the MSD SPI Flash drive connected and was available for use.  So I have basic HID/Brick Controller functionality and a USB Mass Storage Device.

My next task is to see if I can get the VS1053 MP3 player to work.

Tuesday, February 12, 2019

Working USB Mass Storage Device on SST26

I have a working example of the USB MSD on SST26 SPI Flash.  It is right at 8MB.  I have done read and write testing, while it is a little slow (SPI clock is 200KHz) it is completely functional.  Next step is see if I can raise the SPI clock speed on the PIC32MX470 Curiosity board.  While I do this I am also working on implementing the HID functionality.  Once I have HID functionality, I should be able to implement the current Brick Controller functionality on the Curiosity board.

Here is a more of detailed list of what I changed in the SST25 driver to make it work with SST26 flash.  A zip file with the changes is located here, drv_sst25.zip.  GoDaddy, who is the website host, changes the file name to some random number string to make it unique.  Rename to the link name, will make it easier to track the file.

You should also diff this with the framework one to see if I changed anything else and forgot about it.  More than likely that happened.   I have not included the .h file,  drv_sst25_local.h  that was modified either.  There were additions to the states and commands opcodes.  The big change that caused major havoc for days was cmdParamsLen in the DRV_SST25_OBJ structure.  It was cast as a byte and with 4 byte write commands (command plus 3 address bytes) and 256 byte page writes, it was rolling over to just 4 commands bytes and thus nothing happened.  I have changed this to uint32_t.  Again diff the one in the zip file with the framework one.

1.  I changed this table to include the Flash Type SST25 or SST26, as determined from the JEDEC ID read.

/***********************************************************************************************************/
/* Table mapping the supported Flash ID's to their sizes. All parts must support the JEDEC-ID Read command
   The SST25VF010A is included for backwards compatibility reasons, but is not supported since
   the 25VF010A flash part does not support the JEDEC-ID Read command.  Thus there is no way to read
   ID the part. */
/***********************************************************************************************************/
const uint32_t gSstFlashIdSizeTable [DRV_SST25_NUM_DEVICE_SUPPORTED][3] = {
    {0x25, 0x49, 0x020000}, /* SST25VF010A - 1 MBit */
    {0x25, 0x8C, 0x040000}, /* SST25VF020B - 2 MBit */
    {0x25, 0x8D, 0x080000}, /* SST25VF040B - 4 MBit */
    {0x25, 0x8E, 0x100000}, /* SST25VF080B - 8 MBit */
    {0x25, 0x41, 0x200000}, /* SST25VF016B - 16 MBit */
    {0x25, 0x4B, 0x800000}, /* SST25VF064C - 64 MBit */
   
    {0x26, 0x41, 0x200000}, /* SST26VF016B    - 16 MBit */
    {0x26, 0x42, 0x400000}, /* SST26VF032B/BA - 32 MBit */
    {0x26, 0x43, 0x800000}  /* SST26VF064b/BA - 64 MBit */
};

2.  I changed the GetFlashSize function to look at the flash type first. The JEDEC ID read function loads all three bytes of the JEDEC ID into the FlashID variable of the DRV_SST25_OBJ.  The GetGeometry function then sets a Boolean in this object to indicate SST26 or SST25.  That variable is passed to this function which reads the table above to get the size of the flash. 

/* This function returns the flash size in bytes for the specified deviceId. A
 * zero is returned if the device id is not supported. */
static uint32_t DRV_SST25_GetFlashSize
(
    uint8_t deviceId,
    bool isSST26Device
)
{
    uint8_t i, beginIndex,endIndex;

    if (isSST26Device == true)
    {
        beginIndex = DRV_SST25_NUM_DEVICE_SUPPORTED - SST26_DEVICE_COUNT;
        endIndex = DRV_SST25_NUM_DEVICE_SUPPORTED;
    }
    else
    {
        beginIndex = 0;
        endIndex = SST25_DEVICE_COUNT;
    }
    for (i = beginIndex; i < endIndex; i++)
    {
        if (deviceId == gSstFlashIdSizeTable[i][1])
        {
            return gSstFlashIdSizeTable[i][2];
        }
    }

    return 0;
}




3.  This change is one of the bigger ones.  Right now it rejects anything that is not a MicroChip FLASH.  Then it checks for SST26 or SST25 and stores this in the DRV_SST25_OBJ boolean isSST26.  Then it checks the table for the flash size.  The next sections are switch statements that populate the geometry table based on the Flash found.  I did a case statement for each flash device so there would be no question of the parameters used.  This also provides for future expansion.  The remaining code is unchanged.  For now I left in the old code,commented out.

/* This function updates the driver object's geometry information for the flash
 * device. */
static bool DRV_SST25_UpdateGeometry
(
    DRV_SST25_OBJ *dObj,
    uint8_t deviceId
)
{
    uint32_t flashSize;
   
    if (dObj->flashId[0] != 0xBF)
    {
        return false;  // chip is not a microchip SST
    }
    dObj->isSST26 = (dObj->flashId[1] == 0x26);  // is this a SST26 type device
    deviceId = dObj->flashId[2];
    flashSize = DRV_SST25_GetFlashSize (deviceId, dObj->isSST26);  // get flash size
    if (flashSize == 0)
    {
        return false;  // chip was not in table
    }

    /* Read block size and number of blocks */
    /* All Flash memories are 1 byte in read block size */
    dObj->mediaGeometryTable[0].blockSize = 1;
    dObj->mediaGeometryTable[0].numBlocks = flashSize;

    /* Build switch statements with what each flash device needs for writing */
    if (dObj->isSST26)
    {
        dObj->flashFunctions.unlock = DRV_SST26_UnlockFlash;
        switch (deviceId)
        {
            case 0x41:  // SST26VF016B
            {
                dObj->mediaGeometryTable[1].blockSize = DRV_SST25_PAGE_SIZE;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 8;
                dObj->flashFunctions.write = DRV_SST25_WritePageProgram;
                dObj->opCodes.write = DRV_SST25_CMD_PAGE_PROGRAM;
                break;
            }
            case 0x42:  // SST26VF032B
            {
                dObj->mediaGeometryTable[1].blockSize = DRV_SST25_PAGE_SIZE;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 8;
                dObj->flashFunctions.write = DRV_SST25_WritePageProgram;
                dObj->opCodes.write = DRV_SST25_CMD_PAGE_PROGRAM;
                break;
            }
            case 0x43:  // SST26VF064B
            {
                dObj->mediaGeometryTable[1].blockSize = DRV_SST25_PAGE_SIZE;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 8;
                dObj->flashFunctions.write = DRV_SST25_WritePageProgram;
                dObj->opCodes.write = DRV_SST25_CMD_PAGE_PROGRAM;
                break;
            }
            default:
                break;
        }
    }
    else // flash part is SST25
    {
        dObj->flashFunctions.unlock = DRV_SST25_UnlockFlash;
        switch (deviceId)
        {
            case 0x8C:  // SST25VF020B
            {
                dObj->mediaGeometryTable[1].blockSize = 2;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 1;
                dObj->flashFunctions.write = DRV_SST25_WriteAutoAddressIncrement;
                dObj->opCodes.write = DRV_SST25_CMD_AAI_PROGRAM;
                break;
            }
            case 0x8D:  // SST25VF040B
            {
                dObj->mediaGeometryTable[1].blockSize = 2;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 1;
                dObj->flashFunctions.write = DRV_SST25_WriteAutoAddressIncrement;
                dObj->opCodes.write = DRV_SST25_CMD_AAI_PROGRAM;
                break;
            }
            case 0x8E:  // SST25VF080B
            {
                dObj->mediaGeometryTable[1].blockSize = 2;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 1;
                dObj->flashFunctions.write = DRV_SST25_WriteAutoAddressIncrement;
                dObj->opCodes.write = DRV_SST25_CMD_AAI_PROGRAM;
                break;
            }
            case 0x41:  // SST25VF016B
            {
                dObj->mediaGeometryTable[1].blockSize = 2;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 1;
                dObj->flashFunctions.write = DRV_SST25_WriteAutoAddressIncrement;
                dObj->opCodes.write = DRV_SST25_CMD_AAI_PROGRAM;
                break;
            }
            case 0x4B:  // SST25VF064C
            {
                dObj->mediaGeometryTable[1].blockSize = DRV_SST25_PAGE_SIZE;
                dObj->mediaGeometryTable[1].numBlocks = flashSize >> 8;
                dObj->flashFunctions.write = DRV_SST25_WritePageProgram;
                dObj->opCodes.write = DRV_SST25_CMD_PAGE_PROGRAM;
                break;
            }
            default:
                break;
        }
    }

//    if (deviceId == 0x4B)
//    {
//        /* SST25VF064C */
//        /* Write block size and number of blocks */
//        dObj->mediaGeometryTable[1].blockSize = DRV_SST25_PAGE_SIZE;
//        dObj->mediaGeometryTable[1].numBlocks = flashSize >> 8;
//        dObj->flashFunctions.write = DRV_SST25_WritePageProgram;
//    }
//    else
//    {
//        /* Write block size and number of blocks */
//        dObj->mediaGeometryTable[1].blockSize = 2;
//        dObj->mediaGeometryTable[1].numBlocks = flashSize >> 1;
//
//        dObj->flashFunctions.write = DRV_SST25_WriteAutoAddressIncrement;
//        if (deviceId == 0x49)
//        {
//            /* SST25VF010A */
//            dObj->opCodes.write = DRV_SST25_CMD_AAI_PROGRAM1;
//        }
//        else
//        {
//            dObj->opCodes.write = DRV_SST25_CMD_AAI_PROGRAM;
//        }
//    }

    /* Erase block size and number of blocks */
    dObj->mediaGeometryTable[2].blockSize = DRV_SST25_ERASE_SECTOR_SIZE;
    dObj->mediaGeometryTable[2].numBlocks = flashSize >> 12;

    /* Update the Media Geometry Main Structure */
    dObj->mediaGeometryObj.mediaProperty = (SYS_FS_MEDIA_READ_IS_BLOCKING | SYS_FS_MEDIA_WRITE_IS_BLOCKING),

    /* Number of read, write and erase entries in the table */
    dObj->mediaGeometryObj.numReadRegions = 1,
    dObj->mediaGeometryObj.numWriteRegions = 1,
    dObj->mediaGeometryObj.numEraseRegions = 1,
    dObj->mediaGeometryObj.geometryTable = (SYS_FS_MEDIA_REGION_GEOMETRY *)&dObj->mediaGeometryTable;

    return true;
}

NOTE: From the above three changes it should be easy to add any flash to the table by adjust the size the of the table.  Then the GetGeometry function switch/case statements would be modified to accommodate the discovered flash. 

4.  Finally are the case statements that control the state machine. 

This function

static DRV_SPI_BUFFER_EVENT DRV_SST25_ReadFlashId
 

I changed the size of the read to 3 to conform to the JEDEC ID read.  I also changed the write to 4 just so I could see the ID come back on the logic analyzer.  It is not necessary, but it is a nice debug tool.

This function

static DRV_SPI_BUFFER_EVENT DRV_SST25_UnlockFlash

I did not change.  I had no way to check it, but it should still work with SST25 Flash.  It is selected when the state machine determines it is SST25.

This function I added and is selected when the state machine determines it is SST26

static DRV_SPI_BUFFER_EVENT DRV_SST26_UnlockFlash
(
    void *driverObj
)
{
    DRV_SPI_BUFFER_EVENT event = DRV_SPI_BUFFER_EVENT_ERROR;
    DRV_SST25_OBJ *dObj = (DRV_SST25_OBJ *)driverObj;

    switch (dObj->subState)
    {
        case DRV_SST26_WREN_CMD_SQIORST:
            {
                dObj->cmdParams[0] = dObj->opCodes.writeEnable;
                dObj->disableCs = true;
                DRV_SPI_BufferAddWrite2(dObj->spiDriverHandle, &dObj->cmdParams[0], 1, 0, dObj, &dObj->spiBufferHandle);

                if (dObj->spiBufferHandle != DRV_SPI_BUFFER_HANDLE_INVALID)
                {
                    dObj->subState = DRV_SST26_WREN_STATUS_CHECK_SQIORST;  // and fall through
                    event = DRV_SPI_BUFFER_EVENT_PENDING;
                }
                else
                {
                    /* Failed to queue the write transfer request. */
                    break;
                }
            }

        case DRV_SST26_WREN_STATUS_CHECK_SQIORST:
            {
                event = DRV_SPI_BufferStatus(dObj->spiBufferHandle);
                if (event == DRV_SPI_BUFFER_EVENT_COMPLETE)
                {
                    dObj->subState = DRV_SST26_SEND_CMD_SQIORST;  // and fall through
                    event = DRV_SPI_BUFFER_EVENT_PENDING;                   
                }
                else if (event == DRV_SPI_BUFFER_EVENT_ERROR)
                    {
                        /* Error will be handled in the caller function. */
                    }
                    else
                    {
                        /* Continue to remain in the same state. */
                        event = DRV_SPI_BUFFER_EVENT_PENDING;
                        break;
                    }
            }

        case DRV_SST26_SEND_CMD_SQIORST:
            {
                dObj->cmdParams[0] = dObj->opCodes.sqioReset;
                dObj->disableCs = true;
                DRV_SPI_BufferAddWrite2(dObj->spiDriverHandle, &dObj->cmdParams[0], 1, 0, dObj, &dObj->spiBufferHandle);
                if (dObj->spiBufferHandle != DRV_SPI_BUFFER_HANDLE_INVALID)
                {
                    dObj->subState = DRV_SST26_CMD_STATUS_CHECK_SQIORST;  // and fall through
                    event = DRV_SPI_BUFFER_EVENT_PENDING;
                }
                else
                {
                    /* Failed to queue the write transfer request. */
                    break;
                }
            }

        case DRV_SST26_CMD_STATUS_CHECK_SQIORST:
            {
                event = DRV_SPI_BufferStatus(dObj->spiBufferHandle);
                if (event == DRV_SPI_BUFFER_EVENT_COMPLETE)
                {
                    dObj->subState = DRV_SST26_WREN_CMD_UNLOCK;  // and fall through
                    event = DRV_SPI_BUFFER_EVENT_PENDING;                   
                }
                else if (event == DRV_SPI_BUFFER_EVENT_ERROR)
                    {
                        /* Error will be handled in the caller function. */
                    }
                    else
                    {
                        /* Continue to remain in the same state. */
                        event = DRV_SPI_BUFFER_EVENT_PENDING;
                        break;
                    }
            }
       
        case DRV_SST26_WREN_CMD_UNLOCK:
            {
                dObj->cmdParams[0] = dObj->opCodes.writeEnable;
                dObj->disableCs = true;
                DRV_SPI_BufferAddWrite2(dObj->spiDriverHandle, &dObj->cmdParams[0], 1, 0, dObj, &dObj->spiBufferHandle);

                if (dObj->spiBufferHandle != DRV_SPI_BUFFER_HANDLE_INVALID)
                {
                    dObj->subState = DRV_SST26_WREN_STATUS_CHECK_UNLOCK;  // and fall through
                    event = DRV_SPI_BUFFER_EVENT_PENDING;
                }
                else
                {
                    /* Failed to queue the write transfer request. */
                    break;
                }
            }

        case DRV_SST26_WREN_STATUS_CHECK_UNLOCK:
            {
                event = DRV_SPI_BufferStatus(dObj->spiBufferHandle);
                if (event == DRV_SPI_BUFFER_EVENT_COMPLETE)
                {
                    dObj->subState = DRV_SST26_SEND_CMD_UNLOCK;  // and fall through
                    event = DRV_SPI_BUFFER_EVENT_PENDING;                   
                }
                else if (event == DRV_SPI_BUFFER_EVENT_ERROR)
                    {
                        /* Error will be handled in the caller function. */
                    }
                    else
                    {
                        /* Continue to remain in the same state. */
                        event = DRV_SPI_BUFFER_EVENT_PENDING;
                        break;
                    }
            }

        case DRV_SST26_SEND_CMD_UNLOCK:
            {
                dObj->cmdParams[0] = dObj->opCodes.globalUnlock;
                dObj->disableCs = true;
                DRV_SPI_BufferAddWrite2(dObj->spiDriverHandle, &dObj->cmdParams[0], 1, 0, dObj, &dObj->spiBufferHandle);
                if (dObj->spiBufferHandle != DRV_SPI_BUFFER_HANDLE_INVALID)
                {
                    dObj->subState = DRV_SST26_CMD_STATUS_CHECK_UNLOCK;  // and fall through
                    event = DRV_SPI_BUFFER_EVENT_PENDING;
                }
                else
                {
                    /* Failed to queue the write transfer request. */
                    break;
                }
            }

        case DRV_SST26_CMD_STATUS_CHECK_UNLOCK:
            {
                event = DRV_SPI_BufferStatus(dObj->spiBufferHandle);
                break;
            }
    }

    return event;
}



So what this does differently than the SST25 unlock function.  First it executes a SQIO Reset, ensuring that the device is in SPI mode and not SQIO mode.  Then it does a Global Unlock command.  Each of these is preceded by a write enable command.

Finally this function starts it off 

void DRV_SST25_Tasks

This opens the SPI driver, then reads the flash to determine if it is SST25 or SST26.  The UpdateGeometry function picks the correct unlock sequence and this is then executed.  Then it follows the normal state machine from then on.


Hoepfully this will make it easier for others to follow how the modifications were made.  Again I did not test the SST25 portion, so I may have done something to break this without knowing.  If you find it pass it along and I will update this section.