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)
*/