The XMOS I2S library provides software defined, industry-standard, I2S (Integrated Interchip Sound) components that allows you to stream audio between devices using xCORE GPIO ports.
I2S is a specific type of PCM digital audio communication using a serial clock (sometimes refered as bit clock) line, word clock line and at least one multiplexed data line.
The library includes features such as I2S master (newly termed controller), I2S slave (newly termed target), and TDM master components. This application note uses the library to create an I2S master digital loopback.
Application block diagram
The main application fits within one thread with a remote I2C task to
configure the audio hardware remotely from the other tile.
The lib_board_support library, which includes I2C, takes care of the
audio hardware setup.
The I2S task calls back to the i2s_loopback task, and the processing in
the i2s_loopback task occurs in-between the I/O operations of I2S.
XMOS applications use the xcommon-cmake build and dependency management system. This is bundled with the XMOS XTC tools.
To start using the I2S, include lib_i2s as a dependent module in the
application’s CMakeLists.txt file:
set(APP_DEPENDENT_MODULES "lib_i2s")
This demo also uses the I2C library (lib_i2c) which lib_board_support
includes as a dependent module.
The application uses I2C to configure the audio CODECs.
Consequently, the application’s CMakeLists.txt includes both lib_i2s and
lib_board_support as dependent modules.
APP_DEPENDENT_MODULES definitionset(APP_DEPENDENT_MODULES "lib_board_support(1.3.0)"
"lib_i2s(6.0.1)")
All xC files which declare the application main() function need to include
platform.h.
XMOS xcore specific defines for declaring and initialising hardware appear in
xs1.h.
#include <platform.h>
#include <xs1.h>
The i2s.h file defines the I2S library functions.
This header must be included to use the library.
#include "i2s.h"
Another include gives access to the the board setup code. It varies depending on the board in use.
#include "xk_audio_316_mc_ab/board.h"
#include "xk_evk_xu316/board.h"
An I2S interface requires both clock and data pins in order to
communicate with the audio CODEC device.
On an xcore the pins are controlled by ports.
The ports used by the I2S library are declared on the tile on which they reside. Their declaration includes each port’s direction and buffered nature. Three 1-bit ports are used for the I2S clock signals.
on tile[1]: in port p_mclk = PORT_MCLK_IN;
on tile[1]: buffered out port:32 p_lrclk = PORT_I2S_LRCLK;
on tile[1]: out port p_bclk = PORT_I2S_BCLK;
This loopback application uses 1-bit ports for input and output.
The number of input and output ports, i.e., the value of NUM_I2S_LINES depends on the board in use.
on tile[1]: buffered out port:32 p_dac[NUM_I2S_LINES] = {PORT_I2S_DAC0, PORT_I2S_DAC1, PORT_I2S_DAC2, PORT_I2S_DAC3};
on tile[1]: buffered in port:32 p_adc[NUM_I2S_LINES] = {PORT_I2S_ADC0 ,PORT_I2S_ADC1, PORT_I2S_ADC2, PORT_I2S_ADC3};
on tile[1]: buffered out port:32 p_dac[NUM_I2S_LINES] = {PORT_I2S_DAC_DATA};
on tile[1]: buffered in port:32 p_adc[NUM_I2S_LINES] = {PORT_I2S_ADC_DATA};
The xcore also provides clock block hardware to efficiently generate
a clock signal that can either be driven out of a port or used
to control a port.
This application uses one clock block.
on tile[1]: clock bclk = XS1_CLKBLK_1;
The main() function in the program sets up the tasks in the application.
Firstly, it declares interfaces and channels.
XC interface and channel provides a means for concurrent tasks to communicate with each
other.
This application includes an interface for the I2S master and an interface/channel
for the I2C master.
interface i2s_frame_callback_if i_i2s;
An interface or channel is used in different board for the I2C master.
When running on an XK-AUDIO-316-MC board, it includes an interface for the I2C master also.
interface i2c_master_if i_i2c[1]; // Cross tile interface
On the XK-EVK-XU316 board, the application uses a channel instead.
chan c_init; // Cross tile channel
The rest of the main() function starts all the tasks in parallel using the
xC par construct:
par {
on tile[0]: {
xk_audio_316_mc_ab_board_setup(hw_config); // Setup must be done on tile[0]
xk_audio_316_mc_ab_i2c_master(i_i2c); // Run I2C master server task to allow control from tile[1]
}
on tile[1]: {
interface i2s_frame_callback_if i_i2s;
par {
// The application - loopback the I2S samples - note callbacks are inlined so does not take a thread
[[distribute]] i2s_loopback(i_i2s, i_i2c[0]);
i2s_frame_master(i_i2s, p_dac, NUM_I2S_LINES, p_adc, NUM_I2S_LINES, DATA_BITS, p_bclk, p_lrclk, p_mclk, bclk);
}
}
}
par {
on tile[0]: {
xk_evk_xu316_AudioHwRemote(c_init); // Run I2C master task to allow control from tile[1]
}
on tile[1]: {
interface i2s_frame_callback_if i_i2s;
xk_evk_xu316_AudioHwChanInit(c_init);
par {
// The application - loopback the I2S samples - note callbacks are inlined so does not take a thread
[[distribute]] i2s_loopback(i_i2s);
i2s_frame_master(i_i2s, p_dac, NUM_I2S_LINES, p_adc, NUM_I2S_LINES, DATA_BITS, p_bclk, p_lrclk, p_mclk, bclk);
}
}
}
This code starts the I2S master, the I2C master, the board setup logic, and the loopback application.
The call to the i2s_loopback task in the par is marked with the
[[distribute]] attribute, and the corresponding i2s_loopback() function
is marked with the [[distributable]] attribute.
These attributes mean that the i2s_loopback task will run on an existing
logical core if possible rather than creating a new one.
In this case it will share the logical core used by the I2S master.
All of the audio hardware is setup using functions in lib_board_support.
The previous inclusion of board.h from the xk_audio_316_mc_ab
directory targets the hardware setup to the XU316 Multichannel Audio board(XK-AUDIO-316-MC)
and the xk_audio_316_mc_ab directory targets the XCORE.AI Evaluation Kit(XK-EVK-XU316).
These lines perform some board-specific initialisation and start the I2C task.
xk_audio_316_mc_ab_board_setup(hw_config); // Setup must be done on tile[0]
Note
The XK-EVK-XU316 board does not need board-specific initialisation.
The hw_config struct specifies the hardware configuration.
In this case, it sets up the xcore to be an I2S master with the following
settings:
#define SAMPLE_FREQUENCY 48000
#define MASTER_CLOCK_FREQUENCY 24576000
#define DATA_BITS 32
#define CHANS_PER_FRAME 2
#define NUM_I2S_LINES 4
#define SAMPLE_FREQUENCY 48000
#define MASTER_CLOCK_FREQUENCY 24576000
#define DATA_BITS 32
#define CHANS_PER_FRAME 2
#define NUM_I2S_LINES 1
Note
lib_board_support only support changing master clock frequency in XK-EVK-XU316 Board
The following functions, called from the i2s_loopback task, complete the
initialisation and configuration of the ADCs and DACs:
xk_audio_316_mc_ab_AudioHwInit(i_i2c, hw_config);
xk_audio_316_mc_ab_AudioHwConfig(i_i2c, hw_config, SAMPLE_FREQUENCY, MASTER_CLOCK_FREQUENCY, 0, DATA_BITS, DATA_BITS);
xk_evk_xu316_AudioHwInit(hw_config);
xk_evk_xu316_AudioHwConfig(SAMPLE_FREQUENCY, MASTER_CLOCK_FREQUENCY, 0, DATA_BITS, DATA_BITS);
For full documentation of the lib_board_support API, please refer to the following link:
lib_board_support.
The I2S loopback task provides the function of a digital loopback so that all I2S samples received by the device will be forwarded on.
The task itself is declared as a [[distributable]] function
allowing it to share a logical core with other tasks.
This xC feature can be enabled for any task with the form:
// Task initialization code here
while(1) {
select {
// Event cases here
}
}
The function takes a number of arguments:
[[distributable]]
void i2s_loopback(server i2s_frame_callback_if i_i2s, client i2c_master_if i_i2c)
[[distributable]]
void i2s_loopback(server i2s_frame_callback_if i_i2s)
The interface to the I2S master, server i2s_frame_callback_if i_i2s,
provides a set of callback functions.
The I2S master will call these functions as needed.
The i2s_loopback task uses the I2C master interface,
client i2c_master_if i_i2c, to configure the CODECs (ADCs and DACs)
remotely.
The main loop in the i2s_loopback task handles the I2S interface calls.
while (1) {
select {
case i_i2s.init(i2s_config_t &?i2s_config, tdm_config_t &?tdm_config):
i2s_config.mode = I2S_MODE_I2S;
i2s_config.mclk_bclk_ratio = (MASTER_CLOCK_FREQUENCY / (SAMPLE_FREQUENCY * CHANS_PER_FRAME * DATA_BITS));
... // Audio HW Config for the board
break;
case i_i2s.receive(size_t n_chans, int32_t in_samps[n_chans]):
for (int i = 0; i < n_chans; i++){
samples[i] = in_samps[i]; // copy samples
}
break;
case i_i2s.send(size_t n_chans, int32_t out_samps[n_chans]):
for (int i = 0; i < n_chans; i++){
out_samps[i] = samples[i]; // copy samples
}
break;
case i_i2s.restart_check() -> i2s_restart_t restart:
restart = I2S_NO_RESTART; // Keep on looping
break;
} // End select
} // End while (1)
The I2S master library calls the init() method before it starts
any data streaming.
This call allows the application to reset and configure the audio CODECs,
for example when the sample rate changes.
The receive() interface method is called when the master has
received a frame of audio samples (all channels in one sample period).
The receive() method stores the samples in the samples array.
The I2S master calls the send() interface method when it needs a new
frame of samples to send.
In this case the application simply returns the frame of samples previously
received.
Finally, the restart_check() method is called by the I2S master
once per frame.
It allows the application to control restart or shutdown of the I2S master.
In this case the application continues to run forever and so always returns
I2S_NO_RESTART.
The following section assumes you have downloaded and installed the XMOS XTC tools. See the README file for required version. Installation instructions can be found here. Be sure to pay attention to the section Installation of required third-party tools.
The application uses the xcommon-cmake build system as bundled with the XTC tools.
The AN00162_i2s_loopback_demo software zip-file should be downloaded and
unzipped to a chosen directory.
To configure the build, run the following from an XTC command prompt:
cd an00162
cd app_an00162
cmake -G "Unix Makefiles" -B build
All required dependencies are included in the software download. However, if any are missing, they will be downloaded by the build system.
Finally, the application binaries can be built using xmake:
xmake -j -C build
The application uses approximately 3 kiB on Tile[0] and 7 kiB on Tile[1] (each tile has 512 kiB available).
Please refer to the XU316 Multichannel Audio board and XCORE.AI Evaluation Kit hardware platform documentation.
The demo is designed to run on the XU316 Multichannel Audio board and XCORE.AI Evaluation Kit. To run the demo:
Connect a USB cable from your host to the DEBUG connector.
Connect a USB cable from your host to the USB DEVICE connector.
Connect a sound source to the 3.5mm line in. Channels 1-2, 3-4, 5-6 or 7-8 can be used.
Connect headphones or speakers to the corresponding line out.
To run the application return to the app_an00162 directory and run the
following command:
xrun app_an00162_xk_audio_316_mc/bin/app_an00162_xk_audio_316_mc.xe
xrun app_an00162_xk_evk_xu316/bin/app_an00162_xk_evk_xu316.xe
You should hear the audio connected to the analog input jacks looped back to the output jacks.
XMOS Tools User Guide
XMOS xcore Programming Guide
XMOS Libraries
I2S Protocol