This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TM4C129, USBDMSCInit crashes for USB MSC Device

Other Parts Discussed in Thread: EK-TM4C1294XL

I am attempting to configure our TM4C1294NCPDT-based board to appear as a USB Mass Storage Device (so that its on-board flash chip will be made visible to an attached PC).  I have successfully used the TivaWare USB CDC device driver to make this board appear as a "virtual com port", so I know the USB hardware is working.  Everything is fine until the code reaches

USBDMSCInit(0, (tUSBDMSCDevice *)&g_sMSCDevice);

at which point the program freezes and the board needs to be reset.

The flash chip is known to be working; our application uses it.  Just to be sure, I replaced the access functions for the USB MSC driver with dummy functions (write does nothing; read returns all FFs).  The problem persists.

Unfortunately there is no MSC Device example in the TM4C129 Launchpad TivaWare (only MSC Host).  I've adapted our code from the working CDC example (for the enumeration data structures) and the examples given in the TivaWare USB Library User's Guide.  Code is attached below.  Questions: (a) Can anyone suggest what I am doing wrong?  (b) Can anyone point me to MSC Device example code for the TM4C129 Launchpad?  (If I can get it working on the Launchpad, I can get it working on our board.)

My current code follows.  The PRODUCTION value is set to 1 for our board, 0 for Launchpad.  The code below has a "return" just before the call to USBDMSCInit -- this returns and our application runs normally.  If the "return" is omitted the application following this does not continue.

/****h* boltmeter/usb_msc
 * NAME
 *  usb_msc
 * SYNOPSIS
 *  Provides the following functions:
 *      TBD
 * 
 *  Provides the following interrupt service routines:
 *      TBD
 * 
 * DESCRIPTION
 *  This module provides USB communications with a host PC,
 *  via the USB Mass Storage Class driver.
 *
 *  Ref. TivaWare(tm) USB Library User's Guide, section 2.21.
 * HISTORY
 *  
 *******
 */

#include <stdbool.h>
#include <stdint.h>
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_gpio.h"
#include "driverlib/gpio.h"
#include "driverlib/pin_map.h"
#include "driverlib/rom.h"
#include "driverlib/rom_map.h"
#include "driverlib/sysctl.h"
#include "driverlib/systick.h"
#include "driverlib/usb.h"

#include "usblib/usblib.h"
#include "usblib/usbmsc.h"
#include "usblib/usb-ids.h"
#include "usblib/device/usbdevice.h"
#include "usblib/device/usbdcomp.h"
#include "usblib/device/usbdmsc.h"
#include "utils/ustdlib.h"

#include "EcFAT/EcFAT.h"
#include "usb_msc.h"

//****************************************************************************
//
// The system tick rate expressed both as ticks per second and a millisecond
// period.
//
//****************************************************************************
#define SYSTICKS_PER_SECOND 100
#define SYSTICK_PERIOD_MS (1000 / SYSTICKS_PER_SECOND)

//****************************************************************************
//
// Global system tick counter
//
//****************************************************************************
volatile uint32_t g_ui32SysTickCount = 0;

//****************************************************************************
//
// Interrupt handler for the system tick counter.
//
//****************************************************************************
void
SysTickIntHandler(void)
{
    //
    // Update our system time.
    //
    g_ui32SysTickCount++;
}


//****************************************************************************
//
// Global flag indicating that a USB configuration has been set.
//
//****************************************************************************
static volatile bool g_bUSBConfigured = false;

//*****************************************************************************
//
// The languages supported by this device.
//
//*****************************************************************************
const uint8_t g_pui8LangDescriptor[] =
{
    4,
    USB_DTYPE_STRING,
    USBShort(USB_LANG_EN_US)
};

//*****************************************************************************
//
// The manufacturer string.
//
//*****************************************************************************
const uint8_t g_pui8ManufacturerString[] =
{
    (17 + 1) * 2,
    USB_DTYPE_STRING,
    'T', 0, 'e', 0, 'x', 0, 'a', 0, 's', 0, ' ', 0, 'I', 0, 'n', 0, 's', 0,
    't', 0, 'r', 0, 'u', 0, 'm', 0, 'e', 0, 'n', 0, 't', 0, 's', 0,
};

//*****************************************************************************
//
// The product string.
//
//*****************************************************************************
const uint8_t g_pui8ProductString[] =
{
    2 + (19 * 2),
    USB_DTYPE_STRING,
    'M', 0, 'a', 0, 's', 0, 's', 0, ' ', 0, 'S', 0, 't', 0, 'o', 0,
    'r', 0, 'a', 0, 'g', 0, 'e', 0, ' ', 0, 'D', 0, 'e', 0, 'v', 0,
    'i', 0, 'c', 0, 'e', 0
};

//*****************************************************************************
//
// The serial number string.
//
//*****************************************************************************
const uint8_t g_pui8SerialNumberString[] =
{
    2 + (8 * 2),
    USB_DTYPE_STRING,
    '8', 0, '7', 0, '6', 0, '5', 0, '4', 0, '3', 0, '2', 0, '1', 0
};

//*****************************************************************************
//
// The control interface description string.
//
//*****************************************************************************
const uint8_t g_pui8ControlInterfaceString[] =
{
    2 + (21 * 2),
    USB_DTYPE_STRING,
    'A', 0, 'C', 0, 'M', 0, ' ', 0, 'C', 0, 'o', 0, 'n', 0, 't', 0,
    'r', 0, 'o', 0, 'l', 0, ' ', 0, 'I', 0, 'n', 0, 't', 0, 'e', 0,
    'r', 0, 'f', 0, 'a', 0, 'c', 0, 'e', 0
};

//*****************************************************************************
//
// The configuration description string.
//
//*****************************************************************************
const uint8_t g_pui8ConfigString[] =
{
    2 + (26 * 2),
    USB_DTYPE_STRING,
    'S', 0, 'e', 0, 'l', 0, 'f', 0, ' ', 0, 'P', 0, 'o', 0, 'w', 0,
    'e', 0, 'r', 0, 'e', 0, 'd', 0, ' ', 0, 'C', 0, 'o', 0, 'n', 0,
    'f', 0, 'i', 0, 'g', 0, 'u', 0, 'r', 0, 'a', 0, 't', 0, 'i', 0,
    'o', 0, 'n', 0
};

//*****************************************************************************
//
// The descriptor string table.
//
//*****************************************************************************
const uint8_t * const g_pui8StringDescriptors[] =
{
    g_pui8LangDescriptor,
    g_pui8ManufacturerString,
    g_pui8ProductString,
    g_pui8SerialNumberString,
    g_pui8ControlInterfaceString,
    g_pui8ConfigString
};

#define NUM_STRING_DESCRIPTORS (sizeof(g_pui8StringDescriptors) /             \
                                sizeof(uint8_t *))


/****s* usb_msc/g_sMSCDevice
 * 
 * DESCRIPTION
 *   Mass Storage device structure
 ******
 */

const tUSBDMSCDevice g_sMSCDevice =
{
    //
    // Vendor ID.
    //
    USB_VID_TI_1CBE,
    //
    // Product ID.
    //
    USB_PID_MSC,
    //
    // Vendor Information.
    //
    "TI      ",
    //
    // Product Identification.
    //
    "Mass Storage    ",
    //
    // Revision.
    //
    "1.00",
    //
    // 500mA.
    500,
    //
    // Bus Powered.
    //
    USB_CONF_ATTR_SELF_PWR,
    //
    // A list of string descriptors and the number of descriptors.
    //
    g_pui8StringDescriptors,
    NUM_STRING_DESCRIPTORS,
    //
    // The media access functions.
    //
    {
        USBDMSCStorageOpen,
        USBDMSCStorageClose,
        USBDMSCStorageRead,
        USBDMSCStorageWrite,
        USBDMSCStorageNumBlocks,
        USBDMSCStorageBlockSize
    },
    //
    // The event notification call back function.
    //
    USBDMSCEventCallback,
};

//*****************************************************************************
//
// MASS STORAGE API (Interface to EcFAT block driver)
// "assumes fixed block sizes of 512 bytes for the media."  Sec. 2.21.2.4
//
// Use ECF_GetVolumeInformation, ECF_ReadSector and ECF_WriteSector
// to employ the EcFAT wear-leveling function.
//
//*****************************************************************************

/*
 * pfnOpen This function is used to initialize and open the physical 
 * drive number associated with the parameter ui32Drive . The function 
 * returns zero if the drive could not be opened for some reason. In the 
 * case of removable device like an SD card this function must return
 * zero if the SD card is not present. The function returns a pointer 
 * to data that should be passed to other APIs or returns 0 if no drive 
 * was found.
 */

void * USBDMSCStorageOpen (uint32_t ui32Drive)
{
    return NULL;        // assume the flash device is open
}

/*
 * pfnClose This function closes the drive number in use by the mass 
 * storage class device. The pvDrive is the pointer that was returned 
 * from a call to pfnOpen . This function is used to close the physical 
 * drive number associated with the parameter pvDrive . This function
 * returns 0 if the drive was closed successfully and any other value 
 * indicates a failure.
 */

void USBDMSCStorageClose (void *pvDrive)
{
    return;     // for now, leave the device open for UT-B application
}

/*
 * pfnBlockRead This function reads a block of data from a device 
 * opened by the pfnOpen call. The pvDrive parameter is the pointer 
 * that was returned from the original call to pfnOpen . The pui8Data 
 * parameter is the buffer that data will be written into. The data area
 * pointed to by pui8Data must be at least ui32NumBlocks * Block Size 
 * bytes to prevent overwriting data. The ui32Sector is the block address 
 * to read and ui32NumBlocks is the number of blocks to read. This 
 * function returns the number of bytes that were read from the and
 * placed into the pui8Data buffer.
 */

#define BLOCKSIZE 512
#include <string.h>

uint32_t USBDMSCStorageRead (void *pvDrive, uint8_t *pui8Data,
                             uint32_t ui32Sector, uint32_t ui32NumBlocks)
{
    memset(pui8Data, 0xff, ui32NumBlocks*BLOCKSIZE);
    return ui32NumBlocks*BLOCKSIZE;
#if 0
    uint32_t sectorsize;
    uint32_t count = 0;
    
    sectorsize = USBDMSCStorageBlockSize(pvDrive);
    
    while (ui32NumBlocks > 0) {
        if (ECFERR_SUCCESS != ECF_ReadSector(NULL, ui32Sector, pui8Data, 0)) 
            return (count);     /* number of bytes successfully read */
        count += sectorsize;        /* we've read this many more bytes */
        pui8Data += sectorsize;     /* advance buffer address */
        ui32Sector++;               /* advance sector number */
        ui32NumBlocks--;            /* decrement block counter */
    }
  
   return (count);     /* return number of bytes successfully read */
#endif
}

/*
 * pfnBlockWrite This function is use to write blocks to a physical 
 * device from the buffer pointed to by the pui8Data buffer. The
 * pvDrive parameter is the pointer that was returned from the original 
 * call to pfnOpen . The pui8Data is the pointer to the data to write 
 * to the storage device and ui32NumBlocks is the number of blocks to 
 * write. The ui32Sector parameter is the sector number used to write 
 * the block. If the number of blocks is greater than one then the 
 * block address increments and writes to the next block until 
 * ui32NumBlocks * Block Size bytes are written. This function returns 
 * the number of bytes that were written to the device.
 */

uint32_t USBDMSCStorageWrite (void *pvDrive, uint8_t *pui8Data,
                              uint32_t ui32Sector, uint32_t ui32NumBlocks)
{
    return 0;
#if 0
    uint32_t sectorsize;
    uint32_t count = 0;
    
    sectorsize = USBDMSCStorageBlockSize(pvDrive);
    
    while (ui32NumBlocks > 0) {
        if (ECFERR_SUCCESS != ECF_WriteSector(NULL, ui32Sector, pui8Data, 0)) 
            return (count);     /* number of bytes successfully written */
        count += sectorsize;        /* count the number of bytes written */
        pui8Data += sectorsize;     /* advance buffer address */
        ui32Sector++;               /* advance sector number */
        ui32NumBlocks--;            /* decrement block counter */
    }
    return (count);     /* return number of bytes successfully written */
#endif
}

/*
 * pfnNumBlocks This function returns the total number of blocks on a 
 * physical device based on the pvDrive parameter. The pvDrive parameter
 * is the pointer that was returned from the original call to pfnOpen .
 */

uint32_t USBDMSCStorageNumBlocks (void *pvDrive)
{
    return 4096;
#if 0
    uint16_t SectorSize;
    uint32_t NumberOfSectors;
    if (ECFERR_SUCCESS == 
        ECF_GetVolumeInformation(NULL, &SectorSize, &NumberOfSectors)) {
        return NumberOfSectors;
    } else {
        return 0;
    }
#endif
}

/*
 * pfnBlockSize This function returns the block size for a physical 
 * device based on the pvDrive parameter. The pvDrive parameter is the 
 * pointer that was returned from the original call to pfnOpen .
 */

uint32_t USBDMSCStorageBlockSize (void *pvDrive)
{
    return BLOCKSIZE;
#if 0
    uint16_t SectorSize;
    uint32_t NumberOfSectors;
    if (ECFERR_SUCCESS == ECF_GetVolumeInformation(NULL, &SectorSize, &NumberOfSectors)) {
        return SectorSize;
    } else {
        return 0;
    }
#endif
}
        
/****f* usb_msc/USBDMSCEventCallback
 *
 * DESCRIPTION
 *  "The application’s event call back function provides the application 
 *  with notifications of changes in the USB mass storage class. The 
 *  application can use this information to update it’s own state. The
 *  events may occur in rapid succession and the application must be 
 *  careful not to spend much time in this function as it is called 
 *  from a interrupt handler. The application should expect many calls 
 *  to this function during USB transfers."  Sec. 2.21.3
 * NOTES
 *  "The mass storage class implementation does not require any run time 
 *  calls once it is initialized. This is because all interaction with 
 *  the mass storage class occur through the callback function that is
 *  provided to the USB library’s mass storage class interface." Sec. 2.21
 ******
 */

uint32_t USBDMSCEventCallback(void *pvCBData, uint32_t ui32Event,
                              uint32_t ui32MsgParam, void *pvMsgData)
{
    switch(ui32Event)
    {
        //
        // Writing to the device.
        //
        case USBD_MSC_EVENT_WRITING:
        {
            //
            // Handle write case.
            //
            break;
        }
        //
        // Reading from the device.
        //
        case USBD_MSC_EVENT_READING:
        {
            //
            // Handle read case.
            //
            break;
        }
        case USBD_MSC_EVENT_IDLE:
        {
            //
            // Handle idle case.
            //
            break;
        }
        default: break;
    }
    return(0);
}
        

/****f* usbcomms/USB_MSC_Init
 * NAME
 *  USB_MSC_Init
 * SYNOPSIS
 *  
 * DESCRIPTION
 *  Initializes the USB port for MSC (mass storage) communications.
 *  Also initializes the Systick timer.
 * NOTES
 *  Assumes 120 MHz system clock.
 *  Based loosely on USB_Init from usbcomms.c.
 ******
 */

void USB_MSC_Init(uint32_t ui32SysClock)
{
    void *pvMSCDevice;  // pointer to device data, returned by Init

    //
    // Not configured initially.
    //
    g_bUSBConfigured = false;

    //
    // Enable the peripherals used in this example.
    //
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_USB0);

    /***************** from PinoutSet in pinout.c ***********************/
    //
    // PB0-1/PD6-7/PL6-7 are used for USB.
    // PQ4 can be used as a power fault detect on the Launchpad but it 
    // is not the hardware peripheral power fault input pin.
    // PD7 is used for this function on the production board.
    // Note that USB0PFLT is only used in USB Host mode, which we
    // are not using.
    //
        ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
        ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
        ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOL);
    
        HWREG(GPIO_PORTD_BASE + GPIO_O_LOCK) = GPIO_LOCK_KEY;
        HWREG(GPIO_PORTD_BASE + GPIO_O_CR) = 0xff;
        ROM_GPIOPinConfigure(GPIO_PD6_USB0EPEN);
#if PRODUCTION == 2   /* TESTING */
        ROM_GPIOPinConfigure(GPIO_PD7_USB0PFLT);
        ROM_GPIOPinTypeUSBDigital(GPIO_PORTD_BASE, GPIO_PIN_6 | GPIO_PIN_7);
#else
        ROM_GPIOPinTypeUSBDigital(GPIO_PORTD_BASE, GPIO_PIN_6);
#endif
        ROM_GPIOPinTypeUSBAnalog(GPIO_PORTB_BASE, GPIO_PIN_0 | GPIO_PIN_1);
        ROM_GPIOPinTypeUSBAnalog(GPIO_PORTL_BASE, GPIO_PIN_6 | GPIO_PIN_7);

#if PRODUCTION == 0
        // configure PQ4 as GPIO input for power fault
        // ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOQ);
        // removed 5 aug 16 bjr  ROM_GPIOPinTypeGPIOInput(GPIO_PORTQ_BASE, GPIO_PIN_4);
#endif
        
    /********************************************************************/

    //
    // Enable the system tick.  IS THIS NEEDED?
    //
    SysTickPeriodSet(ui32SysClock / SYSTICKS_PER_SECOND);
    SysTickIntEnable();
    SysTickEnable();

    //
    // No Buffers need to be initialized for the Mass Storage Class driver.
    //

    // *** this line from usb_dev_msc.c example ***
    // Set the USB stack mode to Device mode with VBUS monitoring.
    //
    USBStackModeSet(0, eUSBModeDevice, 0);
    
#ifdef COMPOSITE_USB_DEVICE
    //
    FOR FUTURE USE:
    // Initialize the USB controller as a composite device.
    //
    pvMSCDevice = USBDCompositeInit(0, &g_sCompDevice, DESCRIPTOR_DATA_SIZE,
                      g_pucDescriptorData);
#else
    //
    // Pass our device information to the USB library and place the device
    // on the bus.
    //

    // usec(5000000);
    return;
    
    pvMSCDevice = USBDMSCInit(0, (tUSBDMSCDevice *)&g_sMSCDevice);
#endif    
    
    if (pvMSCDevice != 0) g_bUSBConfigured = true;  /* IS THIS NEEDED? */
}

/*
 * To remove the mass storage device from the USB bus:
 *    USBDMSCTerm(pvMSCDevice)
 */

  • Hello Brad,

    Generally speaking the on-board 1MB flash is insufficient for most practical purpose. One of the things I do notice, is that when using it as a MSC class device, the windows PC would try to format it to add Fat File system information in the the sector-0 possibly causing the device flash to get corrupted.
    Now when you reset the board, then does the application work or again gets stuck?

    Regards
    Amit
  • Amit, thank you for the reply. I'm sorry I didn't make it clear that the board has a 32MB SPI Flash chip which we are trying to make visible via USB. We are not using the on-chip 1MB flash for this function. Also, the SPI Flash is already formatted with a FAT file system -- we are using EcFAT, and this is tested and known to be working. (There are already files on the flash chip, left there by our application -- we're just trying to get the USB MSC working now.)

    I am now running the test software on a TM4C129 Launchpad, with the same results (locks up in USBDMSCInit). When I press reset, the program restarts and gets stuck again in the same place.
  • Hello Brad

    Then you would need to refer to the example for DK-TM4C129x which uses an external SPI flash to turn the system into a MSC Device.

    D:\ti\TivaWare_C_Series-2.1.3.156\examples\boards\dk-tm4c129x\usb_dev_msc

    Regards
    Amit
  • Amit, thank you for the pointer to the DK-TM4C129x example. I cannot run that example "as is" because I do not have the hardware it requires, but I can compare it to my own code. One difference is that the example defines functions RxHandler and TxHandler, which don't appear to do anything. I've added those, and my unit still halts.

    Another difference is that usb_dev_msc.c configures and enables the uDMA channel. Is that required for the USB MSC driver? (It was not required for the USB CDC driver, and the User's Guide make no mention of it.) Or is it only used for the SPI flash?
  • Hello Brad,

    Mass storage class uses the DMA drivers of the USB and it would make data transfer far much easier.

    Regards
    Amit
  • Aha. That may be the problem; our application also uses the DMA, and it is possible that they are using the same channel. Can you tell me what DMA resources the MSC uses, or where I can find this information? (It would have been helpful if this had been mentioned in the documentation.)
  • Hello Brad,

    I don't remember from top of my head but it is between the external memory and the internal memory.

    Regards
    Amit
  • Amit, I'm not sure that DMA is the source of the problem. I have temporarily disabled the part of our application that configures and uses DMA, and USBDMSCInit is still failing.

    I have some more information. Using the Launchpad, I have found that the program is stuck in FaultISR. This seems to be happening within the function USBDMSCCompositeInit, which is called by USBDMSCInit.
  • Hello Brad,

    Since, I do not have your hardware, it is difficult for me to reproduce the issue. Can you send a trimmed down version of your project to which I can attach an external serial flash on the EK-TM4C1294XL?

    Regards
    Amit
  • Hello Amit, I am tackling this from a different direction. I have modified the DK-TM4C129x example to run on the EK-TM4C1294XL Launchpad (mostly by removing all the references to the graphics display, and substituting instead text messages sent through the UART). I have "stubbed" the serial flash I/O to dummy functions. With this setup, I *am* able to run the USB MSC Device, and have it enumerated on the PC. So the driver library seems to be ok, and the hardware works -- what remains is to find the conflict between the example code, and our application.
  • Hello Brad,

    Great work. What is the system clock source you are using on your board?

    Regards
    Amit
  • Amit, by a process of elimination, I seem to have narrowed the problem down to the USB data structures.  When I switched from the structures I had created (based on the working CDC example), to the usb_msc_structs.c in the MSC example, the initialization started working.  Later I will do a line-by-line comparison of the two, to find out where I introduced an error.  It's working at the moment, though I still have our application's use of the DMA controller disabled.  I need to re-enable that and ensure that it coexists peacefully with the USB DMA.


    We have the system clock set to 120 MHz, generated by the internal PLL from a 24 MHz crystal.

  • Hello Brad

    Thanks for the confirmation on the clock source. My concern was that it is the PIOSC being used because of USB enumeration was failing.

    Regards
    Amit
  • A further update: I have got the board working with both our DMA, and the USB driver, active simultaneously. I think the problem was that our driver was reinitializing the uDMA with a different Control Table address.
  • Hello Brad,

    Glad to hear that the issue is now resolved (if you can confirm)!

    Regards
    Amit
  • Yes, I can confirm that the issue is resolved. Thanks for pointing me to the DK-TM4C129x example. That, plus the information that the MSC driver uses the uDMA, were the keys to solving this. (Please suggest, to whoever handles such things, that the TivaWare USB documentation be revised to indicate that the MSC driver uses uDMA.)
  • Hello Brad,

    The usblib is a structural driver. So it is upto users to decide whether to use or not use the sub-features

    Regards
    Amit
  • Hello Brad,
    I'm glad to hear, you've solved the Problem. I have a similar Problem with the MSC function of the TM4C1294. Could you share the final, working source code?
  • Sorry for late reply; I was away for the weekend.  I will check with the client to see what can be shared.

  • Thank you for your reply.
    I've allready found the Problem in my Project. Finally it was a missing ISR method. But I also have changed a lot of other things on my way to a working solution.
    But I think, it would be helpful for other users who want to implement USB, if you share your working code. :-)