Using lib_mic_array

Including in an application

lib_mic_array is intended to be used with XCommon CMake , the XMOS application build and dependency management system.

To use this library, include lib_mic_array in the application’s APP_DEPENDENT_MODULES list in CMakeLists.txt, for example:

set(APP_DEPENDENT_MODULES "lib_mic_array")

Applications should then include the mic_array.h header file.

Default usage model

The default model described in this section is the recommended approach for integrating the mic array into an application and is suitable for most use cases. Its constraints should be reviewed to determine whether it is compatible with the requirements of the target application (see Limitations of the default model).

Typical sequence of steps required to create and use a mic-array instance in an application:

The following sections describe the above steps in detail, followed by a code example demonstrating the mic array default usage model.

Identify hardware resources

The key hardware resources to be identified are the ports and clock blocks that will be used by the mic array unit. The ports correspond to the physical pins on which clocks and sample data will be signaled. Clock blocks are a type of hardware resource which can be attached to ports to coordinate the presentation and capture of signals on physical pins.

Clock blocks

While clock blocks may be more abstract than ports, their implications for this library are actually simpler. First, the mic array unit will need a way of taking the audio master clock and dividing it to produce a PDM sample clock. This can be accomplished with a clock block. This will be the clock block which the API documentation refers to as “Clock A”.

Second, if (and only if) the PDM microphones are being used in a Dual Data Rate (DDR) configuration a second clock block will be required. In a DDR configuration 2 microphones share a physical pin for output sample data, where one signals on the rising edge of the PDM clock and the other signals on the falling edge. The second clock block required in a DDR configuration is referred to as “Clock B” in the API documentation.

Each tile on an xcore.ai device has 5 clock blocks available. In code, a clock block is identified by its resource ID, which are given as the preprocessor macros XS1_CLKBLK_1 through XS1_CLKBLK_5.

Unlike ports, which are tied to specific physical pins, clock blocks are fungible. The application is free to use any clock block that has not already been allocated for another purpose.

Ports

Three ports are needed for the mic array component. As mentioned above, ports are physically tied to specific device pins, and so the correct ports must be identified for correct behaviour.

Note that while ports are physically tied to specific pins, this is not a 1-to-1 mapping. Each port has a port width (measured in bits) which is the number of pins which comprise the port. Further, the pin mappings for different ports overlap, with a single pin potentially belonging to multiple ports. When identifying the needed ports, take care that both the pin map (see the documentation for your xcore.ai package) and port width are correct.

The first port needed is a 1-bit port on which the audio master clock is received. In the documentation, this is usually referred to as p_mclk.

The second port needed is a 1-bit port on which the PDM clock will be signaled to the PDM mics. This port is referred to as p_pdm_clk.

The third port is that on which the PDM data is received. In an SDR configuration, the width of this port must be greater than or equal to the number of microphones. In a DDR configuration, twice this port width must be greater than or equal to the number of microphones. This port is referred to as p_pdm_mics.

XCORE applications are typically compiled with an “XN” file (with a “.xn” file extension). An XN file is an XML document which describes some information about the device package as well as some other helpful board-related information. The identification of ports may have already been done in the XN file. Following is a snippet from an XN file with mappings for the three ports described above:

...
<Tile Number="1" Reference="tile[1]">
  <!-- MIC related ports -->
  <Port Location="XS1_PORT_1G"  Name="PORT_PDM_CLK"/>
  <Port Location="XS1_PORT_1F"  Name="PORT_PDM_DATA"/>
  <!-- Audio ports -->
  <Port Location="XS1_PORT_1D"  Name="PORT_MCLK_IN_OUT"/>
  <Port Location="XS1_PORT_1C"  Name="PORT_I2S_BCLK"/>
  <Port Location="XS1_PORT_1B"  Name="PORT_I2S_LRCLK"/>
  <!-- Used for looping back clocks -->
  <Port Location="XS1_PORT_1N"  Name="PORT_NOT_IN_PACKAGE_1"/>
</Tile>
...

The first 3 ports listed, PORT_PDM_CLK, PORT_PDM_DATA and PORT_MCLK_IN_OUT are respectively p_pdm_clk, p_pdm_mics and p_mclk. The value in the Location attribute (e.g. XS1_PORT_1G) is the port name as you will find it in the package documentation.

In this case, either PORT_PDM_CLK or XS1_PORT_1G can be used in code to identify this port.

Other Resources

In addition to ports and clock blocks, there are also several other hardware resource types used by lib_mic_array which are worth considering. Running out of any of these will preclude the mic array from running correctly (if at all)

  • Threads - At least one hardware thread is required to run the mic array component. If PDM RX service is not running in the interrupt context (MIC_ARRAY_CONFIG_USE_PDM_ISR = 0), a separate thread is required to run it.

  • Compute - The mic array unit will require a fixed number of MIPS (millions of instructions per second) to perform the required processing. The exact requirement will depend on the configuration used.

  • Memory - The mic array requires a modest amount of memory for code and data. (see Resource usage).

  • Chanends - At least 4 chanends must be available for signalling between threads/sub-components.

Declare hardware resources

Once the ports and clock blocks to be used have been identified, these resources can be represented in code using a pdm_rx_resources_t struct. The following is an example of declaring resources in a DDR configuration. See pdm_rx_resources_t, PDM_RX_RESOURCES_SDR() and PDM_RX_RESOURCES_DDR() for more details.

pdm_rx_resources_t pdm_res = PDM_RX_RESOURCES_DDR(
                                PORT_MCLK_IN,
                                PORT_PDM_CLK,
                                PORT_PDM_DATA,
                                24576000,
                                3072000,
                                XS1_CLKBLK_1,
                                XS1_CLKBLK_2);

Initialise and start the mic array

Once the resources are identified and the pdm_rx_resources_t struct is populated, the application needs to call the mic array functions to initialise and start the mic array task.

Call mic_array_init() to initialise the mic array component. mic_array_init() accepts arguments for configuring the mic array component at run-time.

Additional configuration parameters defined in mic_array_conf_default.h specify the default compile-time configuration of the mic array component. These can be overridden by the application in a mic_array_conf.h file included by the application or through the application’s CMakeLists.txt. See the Configuration defines (mic_array_conf_default.h) section for details.

This is followed by a call to mic_array_start() which starts the mic array thread(s). mic_array_start() takes a single argument - the XCORE chanend used to transmit audio frames to subsequent stages of the processing pipeline. Typically, the call to mic_array_start() will be placed directly in a par {...} block along with other threads to be started on the same tile. mic_array_start() is a blocking call that runs the mic array thread(s) until the application signals shutdown using ma_shutdown().

Receive PCM frames

The application receives output PCM frames from the mic array task over an XCORE chanend. This is the other end of the XCORE chanend passed to mic_array_start(). A call to ma_frame_rx() with the chanend as an argument is typically called from another thread to receive PCM blocks from the mic array for further processing.

Shutdown

The application can to shut down the mic array task by calling ma_shutdown(). The shutdown request is sent over the same channel end that is used by ma_frame_rx(). Therefore, the application must ensure that ma_frame_rx() is not being called concurrently when invoking ma_shutdown().

Calling ma_shutdown() causes the mic-array threads to terminate (mic_array_start() returns). ma_shutdown() itself returns only after all mic-array threads have fully terminated. Once shut down, mic_array_init() and mic_array_start() can be called again to restart the mic array.

Note

Shutting down and restarting the mic array is the supported method for changing the output sample rate. The sample rate cannot be modified while the mic array is running; instead, call ma_shutdown(), reconfigure the desired rate, and then restart the mic array.

Example code

The code block below shows the application main() function containing calls to the mic array functions:

#include <platform.h>
#include <xs1.h>
#include "mic_array_task.h"


on tile[PORT_PDM_CLK_TILE_NUM] : port p_mclk = PORT_MCLK_IN_OUT;
on tile[PORT_PDM_CLK_TILE_NUM] : port p_pdm_clk = PORT_PDM_CLK;
on tile[PORT_PDM_CLK_TILE_NUM] : port p_pdm_data = PORT_PDM_DATA;
on tile[PORT_PDM_CLK_TILE_NUM] : clock clk_a = XS1_CLKBLK_1;
on tile[PORT_PDM_CLK_TILE_NUM] : clock clk_b = XS1_CLKBLK_2;

unsafe{
void receive_from_mics(chanend c_pcm)
{
  int32_t audio_frame[MIC_ARRAY_CONFIG_MIC_COUNT * 1]; // frame = 1 sample for each of the MIC_ARRAY_CONFIG_MIC_COUNT channels
  while(1)
  {
    ma_frame_rx(audio_frame, (chanend_t)c_pcm, MIC_ARRAY_CONFIG_MIC_COUNT, 1);
    // further processing of the pcm data...
  }
}

int main() {
  chan c_audio_frames;
  par {
    on tile[1]: {
      pdm_rx_resources_t pdm_res = PDM_RX_RESOURCES_DDR(p_mclk, p_pdm_clk, p_pdm_data, 24576000, 3072000, clk_a, clk_b);

      mic_array_init(&pdm_res, null, 48000);

      par {
        mic_array_start((chanend_t) c_audio_frames);
        receive_from_mics(c_audio_frames);
      }
    }
  }
  return 0;
}
}

Note

Code above does not demonstrate mic array shutdown. See the mic array shutdown example for usage of ma_shutdown().

Limitations of the default model

The default model for using the mic array, while easiest to integrate in an application, has the following constraints:

  • Fixed supported output sampling rates:

    Only 16 kHz, 32 kHz, and 48 kHz output sampling rates are supported. This is because the default decimation filters provided as part of the library (see Filters provided as part of lib_mic_array) are designed for a small set of decimation factors and they assume a fixed input PDM frequency of 3.072 MHz. See Custom decimation filters and app_custom_filter for using custom filters for the mic array (provided they are compatible with the TwoStageDecimator implementation)

  • Only one mic array instance:

    The default model does not support multiple independent, mic array instances

  • Single decimator thread:

    In the default model, the entire decimation process runs within a single hardware thread. For higher microphone counts, this may exceed the available compute capacity of one thread (see mic array MIPS requirement). In general, up to four microphones can be accommodated within a single thread, depending on the configuration. Higher channel counts therefore require splitting the decimation process across multiple hardware threads and using lib_mic_array in a customised configuration. The app_par_decimator example demonstrates a two-threaded decimator implementation.

  • Memory usage:

    The default API incurs an additional memory overhead compared to a custom instantiation of MicArray object. This overhead is primarily from wrapper code and inclusion of all provided filter coefficient sets, even when only a subset is used (see Memory usage (in bytes)). For memory‑constrained systems a custom configuration might be preferable.

For custom usage involving creating a MicArray object with potentially custom implementations of the component classes, refer to the advanced use methods described in Advanced usage