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.

Overcoming obstacles when using the Launchpad as a USB soundcard

Other Parts Discussed in Thread: TM4C1233H6PM

Hello fellow engineers,

I'm trying to implement a USB soundcard on the Tiva Launchpad (123gxl) to test if the Cortex-M4 is fast enough to do various signal processing related processing. As I'm fairly new to USB in general and therefore also to the USB stack from TI I struggle a bit with the implementation of the USB driver to get the audio data in the microcontroller. As this may be useful for somebode else, I want to ask a bit more extensive.

To achieve what I wanted I followed the instructions in the following manual (Tivaware USB Library User Manual, chapter 2.3) : http://www.ti.com.cn/cn/lit/ug/spmu297/spmu297.pdf

I included the following header files from the usblib which gave me the first error:

#include "driverlib/usb.h"
#include "usblib/usblib.h"
#include "usblib/device/usbdevice.h"
#include "usblib/device/usbdaudio.h"

The error stated that the compiler "expected a type identifier". It turned out that Code Composer Studio does not enable the GCC extensions of the ARM compiler by default. This has do be done manually in the Project Properties. Go to Build and open the ARM Compiler dropdown. There you will find the Advanced Options. Check the "Enable support for GCC extensions". Click OK and the compiler will recognize the pragma.

Next up: The prototypes and the tUSBAudioDevice struct to configure everything:

//The Callback function
uint32_t HandleUSBMessages(void *pvCBData, uint32_t ui32Event, uint32_t ui32MsgParam, void *pvMsgData);

//We do not need the instance but the driver does
static tAudioInstance g_sAudioInstance;

//Handle for the device
void *pvDevice;

// 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[] = {
(13 + 1) * 2,
USB_DTYPE_STRING,
'A', 0, 'u', 0, 'd', 0, 'i', 0, 'o', 0, ' ', 0, 'E', 0, 'x', 0, 'a', 0,
'm', 0, 'p', 0, 'l', 0, 'e', 0
};


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


// The interface description string.
const uint8_t g_pui8HIDInterfaceString[] = {
(15 + 1) * 2,
USB_DTYPE_STRING,
'A', 0, 'u', 0, 'd', 0, 'i', 0, 'o', 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[] = {
(20 + 1) * 2,
USB_DTYPE_STRING,
'A', 0, 'u', 0, 'd', 0, 'i', 0, 'o', 0, ' ', 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_ppui8StringDescriptors[] = {
g_pui8LangDescriptor,
g_pui8ManufacturerString,
g_pui8ProductString,
g_pui8SerialNumberString,
g_pui8HIDInterfaceString,
g_pui8ConfigString
};
#define NUM_STRING_DESCRIPTORS (sizeof(g_ppui8StringDescriptors) / \
sizeof(uint8_t *))


/******************************************************************************************************************/
tUSBDAudioDevice g_sAudioDevice = {
0xFFFF,						// The Vendor ID - Not important for personal use
0xFFFF,						// The product ID - Not important for personal use
"TI      ",					// The vendor string for your device (8 chars).
"Audio Device    ",			// The product string for your device (16 chars).
"1.00",						// The revision string for your device (4 chars BCD).
500,						// The power consumption of your device in milliamps.
USB_CONF_ATTR_SELF_PWR,		// The value to be passed to the host in the USB configuration descriptor's bmAttributes field.
HandleUSBMessages,			// A pointer to your control callback event handler.
g_ppui8StringDescriptors,	// A pointer to your string table.
NUM_STRING_DESCRIPTORS,		// The number of entries in your string table.
0xFF00,					// Maximum volume setting expressed as and 8.8 signed fixed point number.
0x0000,					// Minimum volume setting expressed as and 8.8 signed fixed point number.
0x0014					// Minimum volume step expressed as and 8.8 signed fixed point number.
};

Please note that the so called "Instance data" described in the manual will be added by the USB audio Device Init function so we do not have to set it here!

Now we need to configure the pins and enable the clock inside main():

ROM_SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ);
ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
ROM_GPIOPinTypeUSBAnalog(GPIO_PORTD_BASE, GPIO_PIN_4 | GPIO_PIN_5); pvDevice = USBDAudioInit(0, &g_sAudioDevice);
while(1){ };

This is where I get stuck. When I run the code to see if the device works properly I run into the FaultISR(). the callback function returns 0 and nothing is configured to transmit or receive audio date yet. Still - after the USBDAudioInit() it should enumerate and configure the device to the host (computer) even if the interrupts are still disabled.

The NVIC registers say that it is a bus fault with valid registers happening somewhere inside of USBAudioInit().

Inside this function there's another function called USBDAudioCompositeInit() (usbaudio.c). 

Inside of USBDAudioCompositeInit() is another function called USBLibDMAInit() which in turn has a function called USBControllerVersion(). This is, where the bus fault occurs. This is the function as I have it in my version of the TivaWare (most recent):

uint32_t
USBControllerVersion(uint32_t ui32Base)
{
    //
    // Return the type field of the peripheral properties.  This returns
    // zero for all parts that did not have a peripheral property.
    //
    return(HWREG(ui32Base + USB_O_PP) & USB_PP_TYPE_M);
}

This is where my knowledge ends. I simply can't see why there is a fault occuring? Did I forget to enable a clock to a peripheral somewhere?

I will continue to post here until the code runs and does something fancy like modulating a LED to the volume of the sound or using the DAC/ PWM to provide a simple 10bit playback. Maybe we can provide another example for the Tivaware. I missed a usb_dev_audio example :)

  • Hello Jan,

    Is the USB Clock Enabled using the SysCtlPeripheralEnable(SYSCTL_PERIPH_USB0) API call and has the USB Clock been started using SysCtlUSBPLLEnable()

    Regards

    Amit

  • Hello Amit,

    Oh snap! The simplest error can be avoided by looking at the right section of the code.

    Yes, the USB has of course to be enabled and properly configured to work reliably - Which I completely forgot!

    Thank you,

    Jan

  • Hello Jan

    Happy to help... Let me know if there is still any other issue!

    One thing to look for is when a Bus Fault occurs, do check the NVIC_FAULTSTAT and NVIC_FAULTADDR registers at 0xE000ED28 and 0xE000ED38 respectively.

    Regards

    Amit

  • Hello Amit and Jan,

    I am staying face to face with the problem You report as solved, unfortunately I can't solve it on my own. The main idea is to use a Stellaris Launchpad LM4F120 based on LM4F120H5QR / TM4C1233H6PM as USB audio device to capture some signals ie. from accelerometers.

    The problem is as always with proper USB device enumeration. The code is listed below. I merge the information from Your post, post http://e2e.ti.com/support/microcontrollers/stellaris_arm/f/471/t/201393.aspx?pi171693=4, usb_dev-bulk example and from http://www.ti.com/lit/ug/spmu297/spmu297.pdf. But I'm stopped and have no ideas what is wrong.

    #include <stdbool.h>
    #include <stdint.h>
    #include <inc/hw_ints.h>
    #include <inc/hw_memmap.h>
    #include <inc/hw_types.h>
    #include <driverlib/debug.h>
    #include <driverlib/fpu.h>
    #include <driverlib/gpio.h>
    #include <driverlib/interrupt.h>
    #include <driverlib/pin_map.h>
    #include <driverlib/sysctl.h>
    #include <driverlib/systick.h>
    #include <driverlib/timer.h>
    #include <driverlib/uart.h>
    #include <driverlib/rom.h>
    #include <driverlib/usb.h>
    #include <usblib/usblib.h>
    #include <usblib/usb-ids.h>
    #include <usblib/device/usbdevice.h>
    #include <usblib/device/usbdaudio.h>
    #include <utils/uartstdio.h>
    #include <utils/ustdlib.h>
    
    #define FLAG_CONNECTED          3
    
    volatile uint32_t g_ulFlags;
    
    //The Callback function
    uint32_t HandleUSBMessages(void *pvCBData, uint32_t ui32Event, uint32_t ui32MsgParam, void *pvMsgData)
    {
    		UARTprintf("AudioMessageHandler...\n");
    
    	    switch(ui32Event)
    	    {
    	        case USBD_AUDIO_EVENT_IDLE:
    	        	UARTprintf("USBD_AUDIO_EVENT_IDLE...\n");
    
    	        case USBD_AUDIO_EVENT_ACTIVE:
    	        {
    
    	            HWREGBITW(&g_ulFlags, FLAG_CONNECTED) = 1;
    	            UARTprintf("USBD_AUDIO_EVENT_ACTIVE...\n");
    	            break;
    	        }
    
    	        case USB_EVENT_DISCONNECTED:
    	        {
    	            HWREGBITW(&g_ulFlags, FLAG_CONNECTED) = 0;
    	        }
    	        default:
    	        {
    	            break;
    	        }
    	    }
    
    	    return(0);
    }
    
    //We do not need the instance but the driver does
    static tAudioInstance g_sAudioInstance;
    
    //Handle for the device
    void *pvDevice;
    
    // 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[] = {
    		(13 + 1) * 2,
    		USB_DTYPE_STRING,
    		'A', 0, 'u', 0, 'd', 0, 'i', 0, 'o', 0, ' ', 0, 'E', 0, 'x', 0, 'a', 0,
    		'm', 0, 'p', 0, 'l', 0, 'e', 0
    };
    
    // The serial number string.
    const uint8_t g_pui8SerialNumberString[] = {
    		(8 + 1) * 2,
    		USB_DTYPE_STRING,
    		'1', 0, '2', 0, '3', 0, '4', 0, '5', 0, '6', 0, '7', 0, '8', 0
    };
    
    // The interface description string.
    const uint8_t g_pui8HIDInterfaceString[] = {
    		(15 + 1) * 2,
    		USB_DTYPE_STRING,
    		'A', 0, 'u', 0, 'd', 0, 'i', 0, 'o', 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[] = {
    		(20 + 1) * 2,
    		USB_DTYPE_STRING,
    		'A', 0, 'u', 0, 'd', 0, 'i', 0, 'o', 0, ' ', 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
    };
    
    const uint8_t * const g_ppui8StringDescriptors[] = {
    		g_pui8LangDescriptor,
    		g_pui8ManufacturerString,
    		g_pui8ProductString,
    		g_pui8SerialNumberString,
    		g_pui8HIDInterfaceString,
    		g_pui8ConfigString
    };
    
    #define NUM_STRING_DESCRIPTORS (sizeof(g_ppui8StringDescriptors) / \
    sizeof(uint8_t *))
    
    tUSBDAudioDevice g_sAudioDevice = {
    		USB_VID_TI_1CBE,                     // The Vendor ID - Not important for personal use
    		USB_PID_AUDIO,                     // The product ID - Not important for personal use
    		"TI      ",                 // The vendor string for your device (8 chars).
    		"Audio Device    ",         // The product string for your device (16 chars).
    		"1.00",                     // The revision string for your device (4 chars BCD).
    		500,                        // The power consumption of your device in milliamps.
    		USB_CONF_ATTR_SELF_PWR,     // The value to be passed to the host in the USB configuration descriptor's bmAttributes field.
    		HandleUSBMessages,          // A pointer to your control callback event handler.
    		g_ppui8StringDescriptors,   // A pointer to your string table.
    		NUM_STRING_DESCRIPTORS,     // The number of entries in your string table.
    		0xFF00,                 // Maximum volume setting expressed as and 8.8 signed fixed point number.
    		0x0000,                 // Minimum volume setting expressed as and 8.8 signed fixed point number.
    		0x0014                  // Minimum volume step expressed as and 8.8 signed fixed point number.
    };
    
    void ConfigureUART(void)
    {
        ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
        ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
        ROM_GPIOPinConfigure(GPIO_PA0_U0RX);
        ROM_GPIOPinConfigure(GPIO_PA1_U0TX);
        ROM_GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
        UARTClockSourceSet(UART0_BASE, UART_CLOCK_PIOSC);
        UARTStdioConfig(0, 115200, 16000000);
        UARTprintf("UART...\n");
    }
    
    void systemInit()
    {
    	ROM_SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ);
    	ROM_FPULazyStackingEnable();
    	ConfigureUART();
    
        ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
        ROM_GPIOPinTypeUSBAnalog(GPIO_PORTD_BASE, GPIO_PIN_4 | GPIO_PIN_5);
    	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_USB0);
    	ROM_SysCtlUSBPLLEnable();
    
        ROM_IntMasterEnable();
    	UARTprintf("SYSTEM...\n");
    }
    
    void main(void)
    {
        systemInit();
    
        g_ulFlags = 0;
    
        USBStackModeSet(0, eUSBModeDevice, 0);
        UARTprintf("USB1...\n");
    
        pvDevice = USBDAudioInit(0, &g_sAudioDevice);
        UARTprintf("USB2...\n");
    
    	while(1)
    	{
    
    	}
    }

    Running program on the device connected to the host (PC) resulting in USB unknown device detection.

    When I pause the debug session program is in FaultISR. As Amit suggest I analyze the NVIC registers:

     
    NVIC_FAULT_STAT    0x00000400    Configurable Fault Status [Memory Mapped]   

    NVIC_FAULT_STAT_IMPRE    1    Imprecise Data Bus Error    

    NVIC_FAULT_ADDR    11100000000000001110110111111000    Fault Address    

    These data are not clear for me. I would be very grateful if You look on my code and give me some advices. The first question is about MCU, I use the LM4F120 launchpad (natively StellarisWare) but with TivaWare libs for newer ver. of launchpad is it problem? Should I install additional Generic USB Audio driver or is it default system driver? Is the procedures presented in my code executed in the correct order?

    Regards

    Janusz

  • Hello Janusz,

    I can reprocude your error just fine. It seems that the problem emerges from somewhere in the DMA enable sequence but I haven't had time to further dig into it. 

    It happens somewhere around here in the usbaudio.c file (enabling and assigning the DMA for an USB endpoint):

        psAudioDevice->sPrivateData.ui8OUTDMA =
            USBLibDMAChannelAllocate(psAudioDevice->sPrivateData.psDMAInstance,
                                     psAudioDevice->sPrivateData.ui8OUTEndpoint,
                                     ISOC_OUT_EP_MAX_SIZE,
                                     USB_DMA_EP_RX | USB_DMA_EP_TYPE_ISOC |
                                     USB_DMA_EP_DEVICE);
    

    I hope that helps others that can help ;)

    Also: The code looks/seems to be the same for the USBlib so it should work flawlessly with newer versions of the Tivaware. If you use this Device driver you will not need to install any drivers on the PC side. The USB specification declares a "Generic Sound Device" which this code uses.

    Jan

  • Hi Janusz and Jan,

    Did you ever get anywhere with solving the DMA problem? I'm running into the same issue with the Tivaware USB library whilst building a USB audio device using the Tiva-C Launchpad board.

    As you mentioned Jan, when step debugging USBDAudioInit I run into an error at USBLibDMAChannelAllocate (found at usbaudio.c:951) where the assembler code jumps to a seemingly random point in my program (the CAN0 interrupt handler) and immediately crashes.

    Expanding the USBLibDMAChannelAllocate macro shows that it's the pfnChannelAllocate function which is attempting to be called. My guess is that the address of this function is incorrect, but I have no idea why.

    Any ideas on how to fix?

    Thanks,

    Don
  • Hi Janusz and Jan,

    If you look a bit closer, you realize this thread had been about 2 years old.

    Many posters just here to solve their problems, and not checking this forum regularly (if at all). The chances they are answering are somehow slim.

    ... where the assembler code jumps to a seemingly random point in my program (the CAN0 interrupt handler) and immediately crashes.

    Well, that sounds suspiciously like a stack overflow. Or another kind of resource overflow / access error that trashes the stack.

  • Thanks f.m. Really appreciate you responding to this old thread. 

    I've narrowed the problem down to this line of code: 

    usblib/device/usbaudio.c:1096

    //
    // Get the DMA instance pointer.
    //
    psInst->psDMAInstance = USBLibDMAInit(0);

    Here's what happens: 

    USBLibDMAInit() completes successfully and returns the address of the tUSBDMAInstance. The address is: 0x200053AC. But this address is *not* assigned to psInst->psDMAInstance. Here's the associated assembler instruction:

    str        r2, [r3, #0x44]

    r2=0x200053AC

    r3=0x0000B598

    So the STR instruction attempts to write the value of 0x200053AC to the memory address 0x0000B5DC (which is r3+0x44). But this operation fails for some reason.

    Could it be that  0x0000B5DC is read-only? This would actually make sense since my linker script has the following section: 

    MEMORY { /* memory map of Tiva TM4C123GH6PM */
    
        ROM (rx)  : ORIGIN = 0x00000000, LENGTH = 0x00040000
        RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x00008000
    }

    The problem is, if I reduce the ROM LENGTH then the program doesn't fit in memory.

    If I'm correct about the above, why is it that psInst->psDMAInstance is read-only, and how could I make it writeable? 

    Many thanks, 

    Don

  • I found the problem. psInst points to &psAudioDevice->sPrivateData and psAudioDevice is declared as const. This makes it readonly. Removing the const solves the issue.

    Initially I thought this was my code but checking both the usb_dev_audio sample and the Tivaware USB Library User's Guide (p17) I can see that const is used in both examples. Unsure how either of the TI provided examples could actually work with this issue.

    Interesting the compiler *did* spot this issue with the following warning (I just didn't pay any attention to it):

    warning: passing argument 2 of 'USBDAudioInit' discards 'const' qualifier from pointer target type

    I guess the lessons are 1) don't trust the TI examples and 2) treat compiler warnings as errors, which can be enabled with -Werror compiler flag
  • Hello Don,

    sorry for not responding earlier! I have exams to pass at the moment. 

    I gave up on the issue since I did not know enough about the ecosystem back then. Which version of the Tivaware did you use?

    I'm using 2.1.2.111 and looking at the definition of tUSBDAudioDevice at usblib/device/usbaudio.h:206 I see the following:

    typedef struct
    {
        const uint16_t ui16VID;
        const uint16_t ui16PID;
        const char pcVendor[8];
        const char pcProduct[16];
        const char pcVersion[4];
        const uint16_t ui16MaxPowermA;
        const uint8_t ui8PwrAttributes;
        const tUSBCallback pfnCallback;
        const uint8_t * const *ppui8StringDescriptors;
        const uint32_t ui32NumStringDescriptors;
        const int16_t i16VolumeMax;
        const int16_t i16VolumeMin;
        const int16_t i16VolumeStep;
        tAudioInstance sPrivateData;
    }
    tUSBDAudioDevice;

    It's all const but the sPrivateData. Additionally, the code you presented did not include a const for g_sAudioDevice. Where exactly did you trace the problem to? when I follow the trail, psInst equals &psAudioDevice->sPrivateData which is given to USBDAudioCompositeInit from USBDAudioInit which we gave it through declaring g_sAudioDevice. Not even anything in sPrivateData is declared as const.

    Has it been fixed by now? I don't have a launchpad to check the functionality of the code, so I cannot tell if it works or not.

  • It was in my code, which was copied from the example given in the Tivaware USB Library user guide page 17. I am using the same version of the library as you -  2.1.2.111. 

    Unfortunately I just realised the launchpad doesn't have an I2S module which is required to interface with the SGTL5000 codec I'm using. Not sure what my options are here, perhaps the launchpad isn't a good board for USB audio development. 

  • Hey Don,

    Indeed, version 2.1.1.71 of the document contains a wrong definition of tUSBAudioDevice! On page 17 it says

    const tUSBDAudioDevice g_sAudioDevice = {
    
    // Stuff
    
    }

    where it should read

    tUSBDAudioDevice g_sAudioDevice = {
    
    // Stuff
    
    }

    If this comes to the attention of a Texas Instruments employee, please forward this for it to be fixed!

    yes, the complete Tiva C series is missing the I2S peripheral. You can, however, use two SPI interfaces to mimic an I2S interface. Please have a look here:

    www.ti.com/.../spma042b.pdf

    If the link above is no longer available, it's the Application Report SPMA042B titeled "Dual-SPI Emulating I 2S on Tiva™ C Series TM4C123x MCUs".

    I'm looking forward to continue the project and have a minimal working example at some point. Are you with me or will you abandon the Tiva series in favor of something with native I2S?

  • Hello einballimwasser ,

    I will try to see if we can fix this in the upcoming TivaWare....

    Regards
    Amit