A typical USB Audio application#
This section provides a walk through of a typical USB Audio application. Where specific examples are required,
code is used from the application for XK-AUDIO-316-MC (app_usb_aud_xk_316_mc
).
Note
The applications in sw_usb_audio
use the “Codeless Programming Model” as documented in lib_xua
.
Briefly, the main()
function is used from lib_xua
with build-time defines in the application configuring
the framework provided by lib_xua
. Various functions from lib_xua
are then overridden to provide
customisation. See lib_xua
for full details.
Each application directory contains:
A
CMakeLists.txt
A
src
directory
The src
directory is arranged into two directories:
A
core
directory containing source items that must be made available to the USB Audio framework i.e.lib_xua
.An
extensions
directory that includes extensions to the framework such as external device configuration etc
The core
folder for each application contains:
A
.xn
file to describe the hardware platform the application will run onAn optional header file to customise the framework provided by
lib_xua
namedxua_conf.h
lib_xua
configuration#
The xua_conf.h
file contains all the build-time #defines
required to tailor the framework
provided by lib_xua
to the particular application at hand.
Typically these override default values in xua_conf_default.h
in lib_xua/api
.
Firstly in app_usb_aud_xk_316_mc
the xua_conf.h
file sets defines to determine overall capability. For this application
most of the optional interfaces are disabled by default. This is because the applications provide a large number build configurations
in the CMakeLists.txt
enabling various interfaces. For a product with a fixed specification this almost certainly would not be the case and setting
in this file may be the preferred option.
Note that ifndef
is used to check that the option is not already defined in the CMakeLists.txt
.
/* Enable/Disable MIDI - Default is MIDI off */
#ifndef MIDI
#define MIDI (0)
#endif
/* Enable/Disable S/PDIF output - Default is S/PDIF off */
#ifndef XUA_SPDIF_TX_EN
#define XUA_SPDIF_TX_EN (0)
#endif
/* Enable/Disable S/PDIF input - Default is S/PDIF off */
#ifndef XUA_SPDIF_RX_EN
#define XUA_SPDIF_RX_EN (0)
#endif
/* Enable/Disable ADAT output - Default is ADAT off */
#ifndef XUA_ADAT_TX_EN
#define XUA_ADAT_TX_EN (0)
#endif
/* Enable/Disable ADAT input - Default is ADAT off */
#ifndef XUA_ADAT_RX_EN
#define XUA_ADAT_RX_EN (0)
#endif
/* Enable/Disable Mixing core(s) - Default is on */
#ifndef MIXER
#define MIXER (1)
#endif
/* Set the number of mixes to perform - Default is 0 i.e mixing disabled */
#ifndef MAX_MIX_COUNT
#define MAX_MIX_COUNT (0)
#endif
/* Audio Class version - Default is 2.0 */
#ifndef AUDIO_CLASS
#define AUDIO_CLASS (2)
#endif
Next, the file defines properties of the audio channels including counts and arrangements. By default the application provides 8 analogue channels for input and output.
The total number of channels exposed to the USB host (set via NUM_USB_CHAN_OUT
and NUM_USB_CHAN_IN
) are calculated
based on the audio interfaces enabled. Again, this is due to the multiple build configurations in the application CMakeLists.txt
and likely to be hard-coded for a product.
/* Number of I2S channels to DACs*/
#ifndef I2S_CHANS_DAC
#define I2S_CHANS_DAC (8)
#endif
/* Number of I2S channels from ADCs */
#ifndef I2S_CHANS_ADC
#define I2S_CHANS_ADC (8)
#endif
/* Number of USB streaming channels - by default calculate by counting audio interfaces */
#ifndef NUM_USB_CHAN_IN
#define NUM_USB_CHAN_IN (I2S_CHANS_ADC + 2*XUA_SPDIF_RX_EN + 8*XUA_ADAT_RX_EN) /* Device to Host */
#endif
#ifndef NUM_USB_CHAN_OUT
#define NUM_USB_CHAN_OUT (I2S_CHANS_DAC + 2*XUA_SPDIF_TX_EN + 8*XUA_ADAT_TX_EN) /* Host to Device */
#endif
/*** Defines relating to channel arrangement/indices ***/
Channel indices/offsets are set based on the audio interfaces enabled. Channels are indexed from 0. Setting SPDIF_TX_INDEX
to 0
would cause the S/PDIF channels to duplicate analogue channels 0 and 1. Note, the offset for analogue channels is always 0.
/* Channel index of S/PDIF Tx channels: separate channels after analogue channels (if they fit) */
#ifndef SPDIF_TX_INDEX
#if (I2S_CHANS_DAC + 2*XUA_SPDIF_TX_EN) <= NUM_USB_CHAN_OUT
#define SPDIF_TX_INDEX (I2S_CHANS_DAC)
#else
#define SPDIF_TX_INDEX (0)
#endif
#endif
/* Channel index of S/PDIF Rx channels: separate channels after analogue channels */
#ifndef SPDIF_RX_INDEX
#define SPDIF_RX_INDEX (I2S_CHANS_ADC)
#endif
/* Channel index of ADAT Tx channels: separate channels after S/PDIF channels (if they fit) */
#ifndef ADAT_TX_INDEX
#define ADAT_TX_INDEX (I2S_CHANS_DAC + 2*XUA_SPDIF_TX_EN)
#endif
/* Channel index of ADAT Rx channels: separate channels after S/PDIF channels */
#ifndef ADAT_RX_INDEX
#define ADAT_RX_INDEX (I2S_CHANS_ADC + 2*XUA_SPDIF_RX_EN)
#endif
The file then sets some frequency related defines for the audio master clocks and the maximum sample-rate for the device.
/* Master clock defines (in Hz) */
#ifndef MCLK_441
#define MCLK_441 (512*44100) /* 44.1, 88.2 etc */
#endif
#ifndef MCLK_48
#define MCLK_48 (512*48000) /* 48, 96 etc */
#endif
/* Minumum sample frequency device runs at */
#ifndef MIN_FREQ
#define MIN_FREQ (44100)
#endif
/* Maximum sample frequency device runs at */
#ifndef MAX_FREQ
#define MAX_FREQ (192000)
#endif
Due to the multi-tile nature of the xcore architecture the framework needs to be informed as to which tile various interfaces should be placed on, for example USB, S/PDIF etc.
#define XUD_TILE (0)
#define PLL_REF_TILE (0)
#define AUDIO_IO_TILE (1)
#define MIDI_TILE (1)
The file also sets some defines for general USB IDs and strings. These are set for the XMOS reference design but vary per manufacturer:
#define VENDOR_ID (0x20B1) /* XMOS VID */
#ifndef PID_AUDIO_2
#define PID_AUDIO_2 (0x0016)
#endif
#ifndef PID_AUDIO_1
#define PID_AUDIO_1 (0x0017)
#endif
#ifndef DFU_PID
#if (AUDIO_CLASS == 1)
#define DFU_PID (0xD000 + PID_AUDIO_1)
#else
#define DFU_PID (0xD000 + PID_AUDIO_2)
#endif
#endif
#define PRODUCT_STR_A2 "XMOS xCORE.ai MC (UAC2.0)"
#define PRODUCT_STR_A1 "XMOS xCORE.ai MC (UAC1.0)"
For a full description of all the defines that can be set in xua_conf.h
see Configuration defines.
User functions#
In addition to the xua_conf.h
file, the application needs to provide implementations of some overridable user
functions in lib_xua
to provide custom functionality.
For app_usb_aud_xk_316_mc
the implementations can be found in src/extensions/audiohw.xc
and src/extensions/audiostream.xc
The two functions it overrides in audiohw.xc
are AudioHwInit()
and AudioHwConfig()
.
These are run from lib_xua
on startup and sample-rate change respectively.
Note, the default implementations in lib_xua
are empty.
These functions have parameters for sample frequency, sample depth, etc.
In the case of app_usb_aud_xk_316_mc
these functions configure the external DACs and ADCs via an I²C bus and configure
the xcore secondary PLL to generate the required master clock frequencies.
Due to the complexity of the hardware on the XK-AUDIO-316-MC the source code is not included here.
The application also overrides UserAudioStreamStart()
and UserAudioStreamStop()
.
These are called from lib_xua
when the audio
stream to the device is started or stopped respectively.
The application uses these functions to enable/disable the on board LEDs based on whether an audio
stream is active (input or output).
// Copyright 2022-2024 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#include <platform.h>
on tile[0]: out port p_leds = XS1_PORT_4F;
void UserAudioStreamStart(void)
{
/* Turn all LEDs on */
p_leds <: 0xF;
}
void UserAudioStreamStop(void)
{
/* Turn all LEDs off */
p_leds <: 0x0;
}
Note
A media player application may choose to keep an audio stream open and active and simply send zero data when paused.
The main program#
The main()
function is the entry point to an application. In the XMOS USB Audio Reference Design
software it is shared by all applications and is therefore part of the framework.
This section is largely informational as most developers should not need to modify the main()
function.
main()
is located in main.xc
in lib_xua
, this file contains:
A declaration of all the ports used in the framework. These clearly vary depending on the hardware platform the application is running on.
A
main()
function which declares some channels and then has apar
statement which runs the required cores in parallel.
Full documentation can be found in lib_xua.
The first items in the par
include running tasks for the Endpoint 0 implementaton and buffering
tasks for audio and endpoint buffering:
{
unsigned x;
thread_speed();
/* Attach mclk count port to mclk clock-block (for feedback) */
//set_port_clock(p_for_mclk_count, clk_audio_mclk);
#if(AUDIO_IO_TILE != XUD_TILE)
set_clock_src(clk_audio_mclk_usb, p_mclk_in_usb);
set_port_clock(p_for_mclk_count, clk_audio_mclk_usb);
start_clock(clk_audio_mclk_usb);
#else
/* Clock port from same clock-block as I2S */
/* TODO remove asm() */
asm("ldw %0, dp[clk_audio_mclk]":"=r"(x));
asm("setclk res[%0], %1"::"r"(p_for_mclk_count), "r"(x));
#endif
/* Endpoint & audio buffering cores - buffers all EP's other than 0 */
XUA_Buffer(
#if (NUM_USB_CHAN_OUT > 0)
c_xud_out[ENDPOINT_NUMBER_OUT_AUDIO], /* Audio Out*/
#endif
#if (NUM_USB_CHAN_IN > 0)
c_xud_in[ENDPOINT_NUMBER_IN_AUDIO], /* Audio In */
#endif
#if (NUM_USB_CHAN_OUT > 0) && ((NUM_USB_CHAN_IN == 0) || defined(UAC_FORCE_FEEDBACK_EP))
c_xud_in[ENDPOINT_NUMBER_IN_FEEDBACK], /* Audio FB */
#endif
#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
#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
/* Audio Interrupt - only used for interrupts on external clock change */
c_xud_in[ENDPOINT_NUMBER_IN_INTERRUPT],
c_clk_int,
#endif
c_sof, c_aud_ctl, p_for_mclk_count
#if (XUA_HID_ENABLED)
, c_xud_in[ENDPOINT_NUMBER_IN_HID]
#endif
, c_mix_out
#if (XUA_SYNCMODE == XUA_SYNCMODE_SYNC)
, c_audio_rate_change
#if (!XUA_USE_SW_PLL)
, i_pll_ref
#else
, c_sw_pll
#endif
#endif
);
//:
}
#endif
/* Endpoint 0 Core */
{
thread_speed();
XUA_Endpoint0( c_xud_out[0], c_xud_in[0], c_aud_ctl, c_mix_ctl, c_clk_ctl, c_EANativeTransport_ctrl, dfuInterface VENDOR_REQUESTS_PARAMS_);
}
It also runs a task for the USB interfacing thread (XUD_Main()
):
XUD_Main(c_xud_out, ENDPOINT_COUNT_OUT, c_xud_in, ENDPOINT_COUNT_IN,
The specification of the channel arrays connecting to this driver are described in the documentation for lib_xud.
The channels connected to XUD_Main()
are passed to the XUA_Buffer()
function which implements audio buffering and also
buffering for other Endpoints.
XUA_Buffer(
#if (NUM_USB_CHAN_OUT > 0)
c_xud_out[ENDPOINT_NUMBER_OUT_AUDIO], /* Audio Out*/
#endif
#if (NUM_USB_CHAN_IN > 0)
c_xud_in[ENDPOINT_NUMBER_IN_AUDIO], /* Audio In */
#endif
#if (NUM_USB_CHAN_OUT > 0) && ((NUM_USB_CHAN_IN == 0) || defined(UAC_FORCE_FEEDBACK_EP))
c_xud_in[ENDPOINT_NUMBER_IN_FEEDBACK], /* Audio FB */
#endif
#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
#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
/* Audio Interrupt - only used for interrupts on external clock change */
c_xud_in[ENDPOINT_NUMBER_IN_INTERRUPT],
c_clk_int,
#endif
c_sof, c_aud_ctl, p_for_mclk_count
#if (XUA_HID_ENABLED)
, c_xud_in[ENDPOINT_NUMBER_IN_HID]
#endif
, c_mix_out
#if (XUA_SYNCMODE == XUA_SYNCMODE_SYNC)
, c_audio_rate_change
#if (!XUA_USE_SW_PLL)
, i_pll_ref
#else
, c_sw_pll
#endif
#endif
);
A channel connects this buffering task to the audio driver which controls the I²S output. It also forwards and receives audio samples from other interfaces e.g. S/PDIF, ADAT, as required:
usb_audio_io(
#if (NUM_USB_CHAN_OUT > 0) || (NUM_USB_CHAN_IN > 0)
/* Connect audio system to XUA_Buffer(); */
c_mix_out
#else
/* Connect to XUA_Endpoint0() */
c_aud_ctl
#endif
#if (XUA_SPDIF_TX_EN) && (SPDIF_TX_TILE != AUDIO_IO_TILE)
, c_spdif_tx
#endif
#if (MIXER)
, c_mix_ctl
#endif
, c_spdif_rx, c_adat_rx, c_clk_ctl, c_clk_int
#if (XUD_TILE != 0) && (AUDIO_IO_TILE == 0) && (XUA_DFU_EN == 1)
, dfuInterface
#endif
#if (XUA_NUM_PDM_MICS > 0)
, c_pdm_pcm
#endif
#if (XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN)
, i_pll_ref
#endif
#if (XUA_SYNCMODE == XUA_SYNCMODE_SYNC)
, c_audio_rate_change
#endif
#if ((XUA_SPDIF_RX_EN || XUA_ADAT_RX_EN) && XUA_USE_SW_PLL)
, p_for_mclk_count_audio
, c_sw_pll
#endif
);
}
Finally, other tasks are created for various interfaces, for example, if MIDI is enabled a core is required to drive the MIDI input and output.
on tile[MIDI_TILE]:
{
thread_speed();
usb_midi(p_midi_rx, p_midi_tx, clk_midi, c_midi, 0);
}