Back to top

USB Audio Software Design Guide

Version: 6.5.1rc9.a Date: May 12, 2014Download
VersionReleased
6.15.2rc1Apr 06, 2016 download
6.12.6rc0.aJul 06, 2015 download view
6.6.0rc5.aJun 02, 2014 download view
6.5.1rc9.aMay 12, 2014 download view
6.5.1rc7.aMay 02, 2014 download view

USB Audio Programming Guide

The following sections provide a guide on how to program the USB audio software platform including instructions for building and running programs and creating your own custom USB audio applications.

Getting Started

Building and Running

To build, select the relavant project (e.g. app_usb_aud_l1) in the Project Explorer and click the Build icon.

To install the software, open the xTIMEcomposer Studio and follow these steps:

  1. Choose File > Import.
  2. Choose General > Existing Projects into Workspace and click Next.
  3. Click Browse next to Select archive file and select the file firmware ZIP file.
  4. Make sure the projects you want to import are ticked in theProjects list. Import all the components and whichever applications you are interested in.
  5. Click Finish.

To build, select the relevant project (e.g. app_usb_aud_l1) in the Project Explorer and click the Build icon.

From the command line, you can follow these steps:

  1. To install, unzip the package zip.
  2. To build, change into the relevant application directory (e.g. app_usb_aud_l1) and execute the command:
    xmake all
    

The main Makefile for the project is in the app directory (e.g. app_usb_aud_l1). This file specifies build options and used modules. The Makefile uses the common build infrastructure in module_xmos_common. This system includes the source files from the relevant modules and is documented withinmodule_xmos_common.

Installing the application onto flash

To upgrade the firmware you must, firstly:

  1. Plug the USB Audio board into your computer.
  2. Connect the xTAG-2 to the USB Audio board and plug the xTAG-2 into your PC or Mac.

To upgrade the flash from xTIMEcomposer Studio, follow these steps:

  1. Start xTIMEcomposer Studio and open a workspace.
  2. Choose File > Import > C/XC > C/XC Executable.
  3. Click Browse and select the new firmware (XE) file.
  4. Click Next and Finish.
  5. A Debug Configurations window is displayed. Click Close.
  6. Choose Run > Flash Configurations.
  7. Double-click xCORE application to create a new Flash configuration.
  8. Browse for the XE file in the Project andC/XC Application boxes.
  9. Ensure the xTAG-2 device appears in the target list.
  10. Click Flash.

From the command line:

  1. Open the XMOS command line tools (Desktop Tools Prompt) and execute the following command:
    xflash <binary>.xe
    

Project Structure

Applications and Modules

The code is split into several module directories. The code for these modules can be included by adding the module name to theUSED_MODULES define in an application Makefile:

Modules used by USB Audio

module_xud

Low level USB device library

module_usb_shared

Common code for USB applications

module_usb_device

Common code for USB device applications

module_usb_audio

Common code for USB audio applications

module_spdif_tx

S/PDIF transmit code

module_spdif_rx

S/PDIF receive code

module_adat_rx

ADAT receive code

module_usb_midi

MIDI I/O code

module_dfu

Device Firmware Upgrade code

There are multiple application directories that contain Makefiles that build into executables:

USB Audio Reference Applications

app_usb_aud_l1

USB Audio 2.0 Reference Design application

app_usb_aud_l2

USB Audio 2.0 Multichannel Reference Design application

app_usb_aud_skc_u16

U16 SliceKit with Audio Slice application

app_usb_aud_xk_u8_2c

Multi-function Audio board application

app_usb_aud_skc_su1

DJ kit application

Build Configurations

Due to the flexibility of the framework there are many different build options. For example input and output channel count, Audio Class version, interface types etc. A “build configuration” is a set of build options that combine to produce a certain feature set.

Build configurations are listed in the application makefile with their associated options, they can be built within the xTIMEComposer GUI or via the command like as follows:

xmake CONFIG=<config name>

When a reference design application is compiled using “build all” (xmake all on commane line) all configurations are automatically built.

A naming scheme is employed to link a feature set to build config/binaries. This scheme is described in the next section.

Configuration Naming Scheme

This section describes the naming scheme for the default configurations (and therefore binaries) generated for each build configuration

Each relevant build option is assigned a position in the string, with a character denoting the options value (normally ‘x’ is used to denote “off” or “disabled”)

For example, the table below lists the build options for the single tile L-Series Reference Design.

Single tile L-Series build options

Build Option Name

Options

Denoted by

Audio Class Version

1 or 2

1 or 2

Audio Input

on or off

i or x

Audio Output

on or off

o or x

MIDI

on or off

m or x

S/PDIF Output

on or off

s or x

For example a binary named 2ioxs would indicate Audio Class 2.0 with input and output enabled, MIDI disabled, SPDIF output enabled.

Validated Build Options

It is not possible for all possible build configuration permutations to be exhaustively tested. XMOS therefore test a subset of build configurations for proper behaviour, these are based on popular device configurations.

Please see the various reference design sections for relevant validated build configurations.

A USB Audio Application

This section provides a walk through of the single tile USB Audio Reference Design (L-Series) example, which can be found in the app_usb_aud_l1 directory.

In each application directory the src directory is arranged into two folders:

#. An core directory containing source items that must be made available to the USB Audio framework

  1. An extensions directory that includes extensions to the framework such as CODEC config etc

The core folder for each application contains:

  1. A .xn file to describe the hardware platform the app will run on
  2. A custom defines file: customdefines.h for framework configuration

Custom Defines

The customdefines.h file contains all the #defines required to tailor the USB audio framework to the particular application at hand. Typically these over-ride default values in devicedefines.hin module_usb_audio.

First there are defines to determine overall capability. For this appliction S/PDIF output and DFU are enabled. Note that ifndef is used to check that the option is not already defined in the makefile.


/* Enable/Disable MIDI - Default is MIDI off */
#ifndef MIDI
#define MIDI               (0)
#endif

/* Enable/Disable SPDIF - Default is SPDIF on */
#ifndef SPDIF
#define SPDIF              (1)
#endif


Next, the file defines the audio properties of the application. This application has stereo in and stereo out with an S/PDIF output that duplicates analogue channels 1 and 2 (note channels are indexed from 0):

/* Number of USB streaming channels - Default is 2 in 2 out */
#ifndef NUM_USB_CHAN_IN
#define NUM_USB_CHAN_IN    (2)         /* Device to Host */
#endif
#ifndef NUM_USB_CHAN_OUT
#define NUM_USB_CHAN_OUT   (2)         /* Host to Device */
#endif

/* Number of IS2 chans to DAC..*/
#ifndef I2S_CHANS_DAC
#define I2S_CHANS_DAC      (2)
#endif

/* Number of I2S chans from ADC */
#ifndef I2S_CHANS_ADC
#define I2S_CHANS_ADC      (2)
#endif

/* Index of SPDIF TX channel (duplicated DAC channels 1/2) */
#define SPDIF_TX_INDEX     (0)


The file then sets some defines for the master clocks on the hardware and the maximum sample-rate for the device.


/* Master clock defines (in Hz) */
#define MCLK_441           (256*44100)   /* 44.1, 88.2 etc */
#define MCLK_48            (512*48000)   /* 48, 96 etc */

/* Maximum frequency device runs at */
#ifndef MAX_FREQ
#define MAX_FREQ           (192000)
#endif


Finally, there are some general USB identification defines to be set. These are set for the XMOS reference design but vary per manufacturer:

#define VENDOR_ID          (0x20B1) /* XMOS VID */
#define PID_AUDIO_2        (0x0002) /* L1 USB Audio Reference Design PID */
#define PID_AUDIO_1        (0x0003) /* L1 USB Audio Reference Design PID */

For a full description of all the defines that can be set incustomdefines.h see Configuration Defines

Configuration Functions

In addition to the custom defines file, the application needs to provide implementations of user functions that are specific to the application.

For app_usb_aud_l1 the implementations can be found in audiohw.xc.

Firstly, code is required to initialise the external audio hardware. In the case of the CODEC on the L1 Refence Design board there is no required action so the funciton is left empty:

void AudioHwInit(chanend ?c_codec)
{
    return;
}

On every sample-rate change a call is made to AudioHwConfig(). In the case of the CODEC on the L1 Reference Design baord the CODEC must be reset and set the relevant clock input from the two oscillators on the board.

Both the CODEC reset line and clock selection line are attached to the 32 bit port 32A. This is accessed through the port32A_peek and port32A_out functions:

#define PORT32A_PEEK(X) {asm("peek %0, res[%1]":"=r"(X):"r"(XS1_PORT_32A));}
#define PORT32A_OUT(X)  {asm("out res[%0], %1"::"r"(XS1_PORT_32A),"r"(X));}


/* Configures the CODEC for the required sample frequency.
 * CODEC reset and frequency select are connected to port 32A
 *
 * Port 32A is shared with other functionality (LEDs etc) so we
 * access via inline assembly. We also take care to retain the
 * state of the other bits.
 */
void AudioHwConfig(unsigned samFreq, unsigned mClk, chanend ?c_codec, unsigned dsdMode,
    unsigned samRes_DAC, unsigned samRes_ADC)
{
    timer t;
    unsigned time;
    unsigned tmp;

    /* Put codec in reset and set master clock select appropriately */

    /* Read current port output */
    PORT32A_PEEK(tmp);

    /* Put CODEC reset line low */
    tmp &= (~P32A_COD_RST);

    if ((samFreq % 22050) == 0)
    {
        /* Frequency select low for 441000 etc */
        tmp &= (~P32A_CLK_SEL);
    }
    else //if((samFreq % 24000) == 0)
    {
        /* Frequency select high for 48000 etc */
        tmp |= P32A_CLK_SEL;
    }

    PORT32A_OUT(tmp);

    /* Hold in reset for 2ms */
    t :> time;
    time += 200000;
    t when timerafter(time) :> int _;

    /* Codec out of reset */
    PORT32A_PEEK(tmp);
    tmp |= P32A_COD_RST;
    PORT32A_OUT(tmp);
}

Finally, the application has functions for audio streaming start/stop that enable/disable an LED on the board (also on port 32A):

#include <xs1.h>
#include "port32A.h"

/* Functions that handle functions that must occur on stream
 * start/stop e.g. DAC mute/un-mute.These need implementing
 * for a specific design.
 *
 * Implementations for the L1 USB Audio Reference Design
 */

/* Any actions required for stream start e.g. DAC un-mute - run every
 * stream start.
 *
 * For L1 USB Audio Reference Design we illuminate LED B (connected
 * to port 32A)
 *
 * Since this port is shared with other functionality inline assembly
 * is used to access the port resource.
 */
void UserAudioStreamStart(void)
{
    int x;

    /* Peek at current port value using port 32A resource ID */
    asm("peek %0, res[%1]":"=r"(x):"r"(XS1_PORT_32A));

    x |= P32A_LED_B;

    /* Output to port */
    asm("out res[%0], %1"::"r"(XS1_PORT_32A),"r"(x));
}

/* Any actions required on stream stop e.g. DAC mute - run every
 * stream stop
 * For L1 USB Audio Reference Design we extinguish LED B (connected
 * to port 32A)
 */
void UserAudioStreamStop(void)
{
    int x;

    asm("peek %0, res[%1]":"=r"(x):"r"(XS1_PORT_32A));
    x &= (~P32A_LED_B);
    asm("out res[%0], %1"::"r"(XS1_PORT_32A),"r"(x));
}


The main program

The main() function is shared across all applications is therefore part of the framework. It is located in sc_usb_audio and contains:

  • A declaration of all the ports used in the framework. These vary depending on the PCB an application is running on.
  • A main function which declares some channels and then has apar statement which runs the required cores in parallel.

The framework supports devices with multiple tiles so it uses the on tile[n]: syntax.

The first core run is the XUD library:

#if (AUDIO_CLASS==2)
        XUD_Manager(c_xud_out, ENDPOINT_COUNT_OUT, c_xud_in, ENDPOINT_COUNT_IN,
            c_sof, epTypeTableOut, epTypeTableIn, p_usb_rst,
            clk, 1, XUD_SPEED_HS, c_usb_test, pwrConfig);
#else
        XUD_Manager(c_xud_out, ENDPOINT_COUNT_OUT, c_xud_in, ENDPOINT_COUNT_IN,
            c_sof, epTypeTableOut, epTypeTableIn, p_usb_rst,
            clk, 1, XUD_SPEED_FS, c_usb_test, pwrConfig);
#endif


The make up of the channel arrays connecting to this driver are described in Component API.

The channels connected to the XUD driver are fed into the buffer and decouple cores:

            buffer(c_xud_out[ENDPOINT_NUMBER_OUT_AUDIO],/* Audio Out*/
                c_xud_in[ENDPOINT_NUMBER_IN_AUDIO],     /* Audio In */
                c_xud_in[ENDPOINT_NUMBER_IN_FEEDBACK],      /* Audio FB */
#ifdef MIDI
                c_xud_out[ENDPOINT_NUMBER_OUT_MIDI],  /* MIDI Out */ // 2
                c_xud_in[ENDPOINT_NUMBER_IN_MIDI],    /* MIDI In */  // 4
                c_midi,
#endif
#ifdef IAP
                c_xud_out[ENDPOINT_NUMBER_OUT_IAP],   /* iAP Out */
                c_xud_in[ENDPOINT_NUMBER_IN_IAP],     /* iAP In */
#ifdef IAP_INT_EP
                c_xud_in[ENDPOINT_NUMBER_IN_IAP_INT], /* iAP Interrupt In */
#endif
                c_iap,
#endif
#if defined(SPDIF_RX) || defined(ADAT_RX)
                /* Audio Interrupt - only used for interrupts on external clock change */
                c_xud_in[ENDPOINT_NUMBER_IN_INTERRUPT],
#endif
                c_sof, c_aud_ctl, p_for_mclk_count
#ifdef HID_CONTROLS
                , c_xud_in[ENDPOINT_NUMBER_IN_HID]
#endif
#ifdef CHAN_BUFF_CTRL
                , c_buff_ctrl
#endif
            );

        {
            thread_speed();
            decouple(c_mix_out, null
#ifdef CHAN_BUFF_CTRL
                , c_buff_ctrl
#endif
            );
        }

These then connect to the audio driver which controls the I2S output and S/PDIF output (if enabled). If S/PDIF output is enabled, this component spawns into two cores as opposed to one.

        {
            thread_speed();
#ifdef MIXER
            audio(c_mix_out, c_dig_rx, c_aud_cfg, c_adc);
#else
            audio(c_aud_in, c_dig_rx, c_aud_cfg, c_adc);
#endif
        }

Finally, if MIDI is enabled you need a core to drive the MIDI input and output. The MIDI core also optionally handles authentication with Apple devices. Due to licensing issues this code is only available to Apple MFI licensees. Please contact XMOS for details.

        on tile[MIDI_TILE]:
        {
            thread_speed();
            usb_midi(p_midi_rx, p_midi_tx, clk_midi, c_midi, 0, null, null, null, null);
        }

Adding Custom Code

The flexibility of the USB audio solution means that you can modify the reference applications to change the feature set or add extra functionality. Any part of the software can be altered with the exception of the XUD library.

The reference designs have been verified against a variety of host OS types, across different samples rates. However, modifications to the code may invalidate the results of this verification and you are strongly encouraged to fully re- test the resulting software.

The general steps are:

  1. Make a copy of the eclipse project or application directory (.e.g. app_usb_aud_l1 or app_usb_aud_l2) you wish to base your code on, to a separate directory with a different name.
  2. Make a copy of any modules you wish to alter (most of the time you probably do not want to do this). Update the Makefile of your new application to use these new custom modules.
  3. Make appropriate changes to the code, rebuild and reflash the device for testing.

Once you have made a copy, you need to:

  1. Provide a .xn file for your board (updating the TARGETvariable in the Makefile appropriately).
  2. Update device_defines.h with the specific defines you wish to set.
  3. Update main.xc.
  4. Add any custom code in other files you need.

The following sections show some example changes with a high level overview of how to change the code.

Example: Changing output format

You may wish to customize the digital output format e.g. for a CODEC that expects sample data left justified with respect to the word clock.

To do this you need to alter the main audio driver loop inaudio.xc. After the alteration you need to re-test the functionality. The XMOS Timing Analyzer can help guarantee that your changes do not break the timing requirement of this core.

Example: Adding DSP to output stream

To add some DSP requires an extra core of computation, so some existing functionality needs to be removed (e.g. S/PDIF). Follow these steps to update the code:

  1. Remove some functionality using the defines inConfiguration Defines to free up a core.
  2. Add another core to do the DSP. This core will probably have three XC channels: one channel to receive samples from decoupler core and another to output to the audio driver—this way the core ‘intercepts’ audio data on its way to the audio driver; the third channel can receive control commands from Endpoint 0.
  3. Implement the DSP on this core. This needs to be synchronous (i.e. for every sample received from the decoupler, a sample needs to be outputted to the audio driver).