C++ API Reference#

MicArray#

template<unsigned MIC_COUNT, class TDecimator, class TPdmRx, class TSampleFilter, class TOutputHandler>
class MicArray#

Represents the microphone array component of an application.

Like many classes in this library, FrameOutputHandler uses the Curiously Recurring Template Pattern.

Template Parameters:
  • MIC_COUNT

    The number of microphones to be captured by the MicArray’s PdmRx component. For example, if using a 4-bit port to capture 6 microphone channels in a DDR configuration (because there are no 3 or 6 pin ports) MIC_COUNT should be 8, because that’s how many must be captured, even if two of them are stripped out before passing audio frames to subsequent application stages.

  • TDecimator – Type for the decimator. See Decimator.

  • TPdmRx – Type for the PDM rx service used. See PdmRx.

  • TSampleFilter – Type for the output filter used. See SampleFilter.

  • TOutputHandler – Type for the output handler used. See OutputHandler.

Public Functions

inline MicArray()#

Construct a MicArray.

This constructor uses the default constructor for each of its components, PdmRx, Decimator, SampleFilter, and OutputHandler.

inline MicArray(TPdmRx pdm_rx, TSampleFilter sample_filter, TOutputHandler output_handler)#

Construct a MicArray.

This constructor uses the default constructor for its Decimator component.

The remaining components are initialized with the supplied objects.

Parameters:
  • pdm_rx – The PDM rx object.

  • sample_filter – The SampleFilter object.

  • output_handler – The OutputHandler object.

inline MicArray(TPdmRx pdm_rx, TOutputHandler output_handler)#

Construct a MicArray

This constructor uses the default constructor for its Decimator and SampleFilter components.

The remaining components are initialized with the supplied objects.

Parameters:
  • pdm_rx – The PDM rx object.

  • output_handler – The OutputHandler object.

void ThreadEntry()#

Entry point for the decimation thread.

This function does not return. It loops indefinitely, collecting blocks of PDM data from PdmRx (which must have already been started), uses Decimator to filter and decimate the sample stream to the output sample rate, applies any post-processing with SampleFilter, and then delivers the stream of output samples through OutputHandler.

Public Members

TPdmRx PdmRx#

The PDM rx service.

The template parameter TPdmRx is the concrete class implementing the microphone array’s PDM rx service, which is responsible for collecting PDM samples from a port and delivering them to the decimation thread.

TPdmRx is only required to implement one function, GetPdmBlock():

uint32_t* GetPdmBlock();

GetPdmBlock() returns a pointer to a block of PDM data, formatted as expected by the decimator. GetPdmBlock() is called from the decimator thread and is expected to block until a new full block of PDM data is available to be decimated.

For example, StandardPdmRxService::GetPdmBlock() waits to receive a pointer to a block of PDM data from a streaming channel. The pointer is sent from the PdmRx interrupt (or thread) when the block has been completed. This is used for capturing PDM data from a port.

TDecimator Decimator#

The Decimator.

The template parameter TDecimator is the concrete class implementing the microphone array’s decimation procedure. TDecimator is only required to implement one function, ProcessBlock():

void ProcessBlock(
    int32_t sample_out[MIC_COUNT],
    uint32_t pdm_block[BLOCK_SIZE]);

ProcessBlock() takes a block of PDM samples via its pdm_block parameter, applies the appropriate decimation logic, and outputs a single (multi-channel) sample sample via its sample_out parameter. The size and formatting of the PDM block expected by the decimator depends on its particular implementation.

A concrete class based on the mic_array::TwoStageDecimator class template is used in the prefab::BasicMicArray prefab.

TSampleFilter SampleFilter#

The output filter.

The template parameter TSampleFilter is the concrete class implementing the microphone array’s sample filter component. This component can be used to apply additional non-decimating, non-interpolating filtering of samples. TSampleFilter() is only required to implement one function, Filter():

void Filter(int32_t sample[MIC_COUNT]);

Filter() takes a single (multi-channel) sample from the decimator component’s output and may update the sample in-place.

For example a sample filter based on the DcoeSampleFilter class template applies a simple first-order IIR filter to the output of the decimator, in order to elminate the DC component of the audio signals.

If no additional filtering is required, the NopSampleFilter class template can be used for TSampleFilter, which leaves the sample unmodified. In this case, it is expected that the call to NopSampleFilter::Filter() will ultimately get completely eliminated at build time. That way no addition run-time compute or memory costs need be introduced for the additional flexibility.

Even though TDecimator and TSampleFilter both (possibly) apply filtering, they are separate components of the MicArray because they are conceptually independent.

A concrete class based on either the DcoeSampleFilter class template or the NopSampleFilter class template is used in the prefab::BasicMicArray prefab, depending on the USE_DCOE parameter of that class template.

TOutputHandler OutputHandler#

The output handler.

The template parameter TOutputHandler is the concrete class implementing the microphone array’s output handler component. After the PDM input stream has been decimated to the appropriate output sample rate, and after any post-processing of that output stream by the sample filter, the output samples must be delivered to another thread for any additional processing. It is the responsibility of this component to package and deliver audio samples to subsequent processing stages.

TOutputHandler is only required to implement one function, OutputSample():

void OutputSample(int32_t sample[MIC_COUNT]);

OutputSample() is called exactly once for each mic array output sample. OutputSample() may block if necessary until the subsequent processing stage ready to receive new data. However, the decimator thread (in which OutputSample() is called) as a whole has a real-time constraint - it must be ready to pull the next block of PDM data while it is available.

A concrete class based on the FrameOutputHandler class template is used in the prefab::BasicMicArray prefab.

Public Static Attributes

static constexpr unsigned MicCount = MIC_COUNT#

Number of microphone channels.

BasicMicArray#

template<unsigned MIC_COUNT, unsigned FRAME_SIZE, bool USE_DCOE, unsigned MICS_IN = MIC_COUNT>
class BasicMicArray : public mic_array::MicArray<MIC_COUNT, TwoStageDecimator<MIC_COUNT, STAGE2_DEC_FACTOR, STAGE2_TAP_COUNT>, StandardPdmRxService<MIC_COUNT, MIC_COUNT, STAGE2_DEC_FACTOR>, std::conditional<USE_DCOE, DcoeSampleFilter<MIC_COUNT>, NopSampleFilter<MIC_COUNT>>::type, FrameOutputHandler<MIC_COUNT, FRAME_SIZE, ChannelFrameTransmitter>>#

Class template for a typical bare-metal mic array unit.

This prefab is likely the right starting point for most applications.

With this prefab, the decimator will consume one device core, and the PDM rx service can be run either as an interrupt, or as an additional thread. Normally running as an interrupt is recommended.

For the first and second stage decimation filters, this prefab uses the coefficients provided with this library. The first stage uses a decimation factor of 32, and the second stage is configured to use a decimation factor of 6.

To get 16 kHz audio output from the BasicMicArray prefab, then, the PDM clock must be configured to 3.072 MHz (3.072 MHz / (32 * 6) = 16 kHz).

Sub-Components

Being derived from mic_array::MicArray, an instance of BasicMicArray has 4 sub-components responsible for different portions of the work being done. These sub-components are PdmRx, Decimator, SampleFilter and OutputHandler. See the documentation for MicArray for more details about these.

Template Parameters Details

The template parameter MIC_COUNT is the number of microphone channels to be processed and output.

The template parameter FRAME_SIZE is the number of samples in each output frame produced by the mic array. Frame data is communicated using the API found in mic_array/frame_transfer.h.

Typically ma_frame_rx() will be the right function to use in a receiving thread to retrieve audio frames. ma_frame_rx() receives audio frames with shape (MIC_COUNT,FRAME_SIZE), meaning that all samples corresponding to a given channel will end up in a contiguous block of memory. Instead of ma_frame_rx(), ma_frame_rx_transpose() can be used to swap the dimensions, resulting in the shape (FRAME_SIZE, MIC_COUNT).

Note that calls to ma_frame_rx() or ma_frame_rx_transpose() will block until a frame becomes available on the specified chanend.

If the receiving thread is not waiting to retrieve the audio frame from the mic array when it becomes available, the pipeline may back up and cause samples to be dropped. It is the responsibility of the application developer to ensure this does not happen.

The boolean template parameter USE_DCOE indicates whether the DC offset elimination filter should be applied to the output of the second stage decimator. DC offset elimination is an IIR filter intended to ensure audio samples on each channel tend towards zero-mean.

For more information about DC offset elimination, see Sample Filters

.

If USE_DCOE is false, no further filtering of the second stage decimator’s output will occur.

The template parameter MICS_IN indicates the number of microphone channels to be captured by the PdmRx component of the mic array unit. This will often be the same as MIC_COUNT, but in some applications, MIC_COUNT microphones must be physically connected to an XCore port which is not MIC_COUNT (SDR) or MIC_COUNT/2 (DDR) bits wide.

In these cases, capturing the additional channels (likely not even physically connected to PDM microphones) is unavoidable, but further processing of the additional (junk) channels can be avoided by using MIC_COUNT < MICS_IN. The mapping which tells the mic array unit how to derive output channels from input channels can be configured during initialization by calling StandardPdmRxService::MapChannels() on the PdmRx sub-component of the BasicMicarray.

If the application uses an SDR microphone configuration (i.e. 1 microphone per port pin), then MICS_IN must be the same as the port width. If the application is running in a DDR microphone configuration, MICS_IN must be twice the port width. MICS_IN defaults to MIC_COUNT.

Allocation

Before a mic array unit can be started or initialized, it must be allocated.

Instances of BasicMicArray are self-contained with respect to memory, needing no external buffers to be supplied by the application. Allocating an instance is most easily accomplished by simply declaring the mic array unit. An example follows.

#include "mic_array/cpp/Prefab.hpp"
...
using AppMicArray = mic_array::prefab::BasicMicArray<MICS,SAMPS,DCOE>;
AppMicArray mics;

Here, mics is an allocated mic array unit. The example (and all that follow) assumes the macros used for template parameters are defined elsewhere.

Initialization

Before a mic array unit can be started, it must be initialized.

BasicMicArray reads PDM samples from an XCore port, and delivers frames of audio data over an XCore channel. To this end, an instance of BasicMicArray needs to be given the resource IDs of the port to be read and the chanend to transmit frames over. This can be accomplished in either of two ways.

If the resource IDs for the port and chanend are available as the mic array unit is being allocated, one option is to explicitly construct the BasicMicArray instance with the required resource IDs using the two-argument constructor:

using AppMicArray = mic_array:prefab::BasicMicArray<MICS,SAMPS,DCOE>;
AppMicArray mics(PORT_PDM_MICS, c_frames_out);

Otherwise (typically), these can be set using BasicMicArray::SetPort(port_t) and BasicMicArray::SetOutputChannel(chanend_t) to set the port and channel respectively.

AppMicArray mics;
...
void app_init(port_t p_pdm_mics, chanend_t c_frames_out)
{
 mics.SetPort(p_pdm_mics);
 mics.SetOutputChannel(p_pdm_mics);
}

Next, the ports and clock block(s) used by the PDM rx service need to be configured appropriately. This is not accomplished directly through the BasicMicArray object. Instead, a pdm_rx_resources_t struct representing these hardware resources is constructed and passed to mic_array_resources_configure(). See the documentation for pdm_rx_resources_t and mic_array_resources_configure() for more details.

Finally, if running BasicMicArray’s PDM rx service within an ISR, before the mic array unit can be started, the ISR must be installed. This is accomplished with a call to BasicMicArray::InstallPdmRxISR(). Installing the ISR will not unmask it.

Begin Processing (PDM rx ISR)

After it has been initialized, starting the mic array unit with the PDM rx service running as an ISR, three steps are required.

First, the PDM clock must be started. This is accomplished with a call to mic_array_pdm_clock_start(). The same pdm_rx_resources_t that was passed to mic_array_resources_configure() is given as an argument here.

Second, the PDM rx ISR that was installed during initialization must be unmasked. This is accomplished by calling BasicMicArray::UnmaskPdmRxISR() on the mic array unit.

Finally, the mic array processing thread must be started. The entry point for the mic array thread is BasicMicArray::ThreadEntry().

A typical pattern will include all three of these steps in a single function which wraps the mic array thread entry point.

AppMicArray mics;
pdm_rx_resources_t pdm_res;
...
MA_C_API  // alias for 'extern "C"'
void app_mic_array_task()
{
 mic_array_pdm_clock_start(&pdm_res);
 mics.UnmaskPdmRxISR();
 mics.ThreadEntry();
}

Using this pattern, app_mic_array_task() is a C-compatible function which can be called from a multi-tile main() in an XC file. Then, app_mic_array_task() is called directly from a par {...} block. For example,

main(){
 ...
 par {
   on tile[1]: {
     ... // Do initialization stuff
     
     par {
       app_mic_array_task();
       ...
       other_thread_on_tile1(); // other threads
     }
   }
 }
}

Begin Processing (PDM Rx Thread)

The procedure for running the mic array unit with the PDM rx component running as a stand-alone thread is much the same with just a couple key differences.

When running PDM rx as a thread, no call to BasicMicArray::UnmaskPdmRxISR() is necessary. Instead, the application spawns a second thread (the first being the mic array processing thread) using BasicMicArray::PdmRxThreadEntry() as the entry point.

mic_array_pdm_clock_start() must still be called, but here the requirement is that it be called from the hardware thread on which the PDM rx component is running (which, of course, cannot be the mic array thread).

A typical application with a multi-tile XC main() will provide two C-compatible functions - one for each thread:

MA_C_API
void app_pdm_rx_task()
{
 mic_array_pdm_clock_start(&pdm_res);
 mics.PdmRxThreadEntry();
}

MA_C_API
void app_mic_array_task()
{
 mics.ThreadEntry();
}

Notice that app_mic_array_task() above is a thin wrapper for mics.ThreadEntry(). Unfortunately, because the type of mics is a C++ class, mics.ThreadEntry() cannot be called directly from an XC file (including the one containing main()). Further, because a C++ class template was used, this library cannot provide a generic C-compatible call wrapper for the methods on a MicArray object. This unfortunately means it is necessary in some cases to create a thin wrapper such as app_mic_array_task().

The threads are spawned from XC main using a par {...} block:

main(){
 ...
 par {
   on tile[1]: {
     ... // Do initialization stuff
     
     par {
       app_mic_array_task();
       app_pdm_rx_task();
       ...
       other_thread_on_tile1(); // other threads
     }
   }
 }
}

Real-Time Constraint

Once the PDM rx thread is launched or the PDM rx interrupt has been unmasked, PDM data will start being collected and reported to the decimator thread. The application then must start the decimator thread within one output sample time (i.e. sample time for the output of the second stage decimator) to avoid issues.

Once the mic array processing thread is running, the real-time constraint is active for the thread consuming the mic array unit’s output, and it must waiting to receive an audio frame within one frame time.

Examples

This library comes with examples which demonstrate how a mic array unit is used in an actual application. If you are encountering difficulties getting BasicMicArray to work, studying the provided examples may help.

Note

BasicMicArray::InstallPdmRxISR() installs the ISR on the hardware thread that calls the method. In most cases, installing it in the same thread as the decimator is the right choice.

Template Parameters:
  • MIC_COUNT – Number of microphone channels.

  • FRAME_SIZE – Number of samples in each output audio frame.

  • USE_DCOE – Whether DC offset elimination should be used.

Public Types

using TParent = MicArray<MIC_COUNT, TwoStageDecimator<MIC_COUNT, STAGE2_DEC_FACTOR, STAGE2_TAP_COUNT>, StandardPdmRxService<MICS_IN, MIC_COUNT, STAGE2_DEC_FACTOR>, typename std::conditional<USE_DCOE, DcoeSampleFilter<MIC_COUNT>, NopSampleFilter<MIC_COUNT>>::type, FrameOutputHandler<MIC_COUNT, FRAME_SIZE, ChannelFrameTransmitter>>#

TParent is an alias for this class template from which this class template inherits.

Public Functions

inline constexpr BasicMicArray() noexcept#

No-argument constructor.

This constructor allocates the mic array and nothing more.

Call BasicMicArray::Init() to initialize the decimator.

Subsequent calls to BasicMicArray::SetPort() and BasicMicArray::SetOutputChannel() will also be required before any processing begins.

void Init()#

Initialize the decimator.

BasicMicArray(port_t p_pdm_mics, chanend_t c_frames_out)#

Initialzing constructor.

If the communication resources required by BasicMicArray are known at construction time, this constructor can be used to avoid further initialization steps.

This constructor does not install the ISR for PDM rx, and so that must be done separately if PDM rx is to be run in interrupt mode.

Parameters:
  • p_pdm_mics – Port with PDM microphones

  • c_frames_out – (non-streaming) chanend used to transmit frames.

void SetPort(port_t p_pdm_mics)#

Set the PDM data port.

This function calls this->PdmRx.Init(p_pdm_mics).

This should be called during initialization.

Parameters:

p_pdm_mics – The port to receive PDM data on.

void SetOutputChannel(chanend_t c_frames_out)#

Set the audio frame output channel.

This function calls this->OutputHandler.FrameTx.SetChannel(c_frames_out).

This must be set prior to entrying the decimator task.

Parameters:

c_frames_out – The channel to send audio frames on.

void PdmRxThreadEntry()#

Entry point for PDM rx thread.

This function calls this->PdmRx.ThreadEntry().

Note

This call does not return.

void InstallPdmRxISR()#

Install the PDM rx ISR on the calling thread.

This function calls this->PdmRx.InstallISR().

void UnmaskPdmRxISR()#

Unmask interrupts on the calling thread.

This function calls this->PdmRx.UnmaskISR().

PdmRxService#

template<unsigned BLOCK_SIZE, class SubType>
class PdmRxService#

Collects PDM sample data from a port.

Derivatives of this class template are intended to be used for the TPdmRx template parameter of MicArray, where it represents the MicArray::PdmRx component of the mic array.

An object derived from PdmRxService collects blocks of PDM samples from a port and makes them available to the decimation thread as the blocks are completed.

PdmRxService is a base class using CRTP. Subclasses extend PdmRxService providing themselves as the template parameter SubType.

This base class provides the logic for aggregating PDM data taken from a port into blocks, and a subclass is required to provide methods SubType::ReadPort(), SubType::SendBlock() and SubType::GetPdmBlock().

SubType::ReadPort() is responsible for reading 1 word of data from p_pdm_mics. See StandardPdmRxService::ReadPort() as an example.

SubType::SendBlock() is provided a block of PDM data as a pointer and is responsible for signaling that to the subsequent processing stage. See StandardPdmRxService::SendBlock() as an example.

ReadPort() and SendBlock() are used by PdmRxService itself (when running as a thread, rather than ISR).

SubType::GetPdmBlock() responsible for receiving a block of PDM data from SubType::SendBlock() as a pointer, deinterleaving the buffer contents, and returning a pointer to the PDM data in the format expected by the mic array unit’s decimator component. See StandardPdmRxService::GetPdmBlock() as an example.

GetPdmBlock() is called by the decimation thread. The pair of functions, SendBlock() and GetPdmBlock() facilitate inter-thread communication, SendBlock() being called by the transmitting end of the communication channel, and GetPdmBlock() being called by the receiving end.

Template Parameters:
  • BLOCK_SIZE – Number of words of PDM data per block.

  • SubType – Subclass of PdmRxService actually being used.

Public Functions

void SetPort(port_t p_pdm_mics)#

Set the port from which to collect PDM samples.

void ProcessNext()#

Perform a port read and if a new block has completed, signal.

void ThreadEntry()#

Entry point for PDM processing thread.

This function loops forever, calling ProcessNext() with each iteration.

Public Static Attributes

static constexpr unsigned BlockSize = BLOCK_SIZE#

Number of words of PDM data per block.

Typically (e.g. TwoStageDecimator) BLOCK_SIZE will be exactly the number of words of PDM samples required to produce exactly one new output sample for the mic array unit’s output stream.

Once BlockSize words have been read into one of the block_data, buffers, PDM rx will signal to the decimator thread that new PDM data is available for processing.

StandardPdmRxService#

struct pdm_rx_isr_context_t#

PDM rx interrupt configuration and context.

Public Members

port_t p_pdm_mics#

Port on which PDM samples are received.

uint32_t *pdm_buffer[2]#

Pointers to a pair of buffers used for storing captured PDM samples.

The buffers themselves are allocated by an instance of mic_array::PdmRxService. The idea is that while the PDM rx ISR is filling one buffer, the decimation thread is busy processing the contents of the other buffer. If the real-time constraint is maintained, the decimation thread will be finished with the contents of its buffer before the PDM rx ISR fills the other buffer. Once full, the PDM rx ISR does a double buffer pointer swap and hands the newly-filled buffer to the decimation thread.

unsigned phase#

Tracks the completeness of the buffer currently being filled.

Each read of samples from p_pdm_mics gives one word of data. This variable tracks how many more port reads are required before the current buffer has been filled.

unsigned phase_reset#

The number of words to read from p_pdn_mics to fill a buffer.

chanend_t c_pdm_data#

Streaming chanend the PDM rx ISR uses to signal the decimation thread that another buffer is full and ready to be processed.

The streaming channel itself is allocated by mic_array::StandardPdmRxService, which owns the other end of the channel.

unsigned credit#

Used for detecting when the real-time constraint is violated by the decimation thread.

Each time the decimation thread is given a block of PDM data to process, credit is reset to 2. Each time the PDM rx ISR hands a block of PDM data to the decimation thread, this is decremented.

Deadlock Condition

mic_array::StandardPdmRxService uses a streaming channel to facilitate communication between the two execution contexts used by the mic array, the decimation thread and the PDM rx ISR. A streaming channel is used because it allows the contexts to operate asynchronously.

A channel has a 2 word buffer, and as long as there is room in the buffer, an OUT instruction putting a word (in this case, a pointer) into the channel is guaranteed not to block. This is important because the PDM rx ISR is typically configured on the same hardware thread as the decimation thread.

If a thread is blocked on an OUT instruction to a channel, in order to unblock the thread, an IN must be issued on the other end of that channel. But because the PDM rx ISR is blocked, it cannot hand control back to the decimation thread, which means the decimation thread can never issue an IN instruction to unblock the ISR. The result is a deadlock.

Unfortunately, there is no way for a thread to query a chanend to determine whether it will block if an OUT instruction is issued. That is why credit is used. Before issuing an OUT to c_pdm_data, the PDM rx ISR checks whether credit is non-zero. If so, the ISR issues the OUT instruction as normal and decrements credit.

If credit is zero, the default behavior of PDM rx ISR is to raise an exception (ET_ECALL). This reflects the idea that it is generally better if system-breaking errors loudly announce themselves (at least by default). If using mic_array::StandardPdmRxService, this behavior can be changed by passing false in a call to mic_array::StandardPdmRxService::AssertOnDroppedBlock(), which will allow blocks of PDM data to be silently dropped (while still avoiding a permanent deadlock).

unsigned missed_blocks#

Controls and records anti-deadlock behavior.

If the PDM rx ISR finds that credit is 0 when it’s time to send a filled buffer to the decimation thread, it uses missed_blocks to control whether the PDM rx ISR should raise an exception or silently drop the block of PDM data.

If missed_blocks is -1 (its default value) an exception is raised. Otherwise missed_blocks is used to record the number of blocks that have been quietly dropped.

pdm_rx_isr_context_t pdm_rx_isr_context#

Configuration and context of the PDM rx ISR when mic_array::StandardPdmRxService is used in interrupt mode.

pdm_rx_isr (pdm_rx_isr.S) directly allocates this object as configuration and state parameters required by that interrupt routine.

static inline void enable_pdm_rx_isr(const port_t p_pdm_mics)#

Configure port to use pdm_rx_isr as an interrupt routine.

This function configures p_pdm_mics to use pdm_rx_isr as its interrupt vector and enables the interrupt on the current hardware thread.

This function does NOT unmask interrupts.

Parameters:

p_pdm_mics – Port resource to enable ISR on.

template<unsigned CHANNELS_IN, unsigned CHANNELS_OUT, unsigned SUBBLOCKS>
class StandardPdmRxService : public mic_array::PdmRxService<CHANNELS_IN * SUBBLOCKS, StandardPdmRxService<CHANNELS_IN, CHANNELS_OUT, SUBBLOCKS>>#

PDM rx service which uses a streaming channel to send a block of data by pointer.

This class can run the PDM rx service either as a stand-alone thread or through an interrupt.

Inter-context Transfer

A streaming channel is used to transfer control of the PDM data block between execution contexts (i.e. thread->thread or ISR->thread).

The mic array unit receives blocks of PDM data from an instance of this class by calling GetPdmBlock(), which blocks until a new PDM block is available.

Layouts

The buffer transferred by SendBlock() contains CHANNELS_IN*SUBBLOCKS words of PDM data for CHANNELS_IN microphone channels. The words are stored in reverse order of arrival.

See mic_array::deinterleave_pdm_samples() for additional details on this format.

Within GetPdmBlock() (i.e. mic array thread) the PDM data block is deinterleaved and copied to another buffer in the format required by the decimator component, which is returned by GetPdmBlock(). This buffer contains samples for CHANNELS_OUT microphone channels.

Channel Filtering

In some cases an application may be required to capture more microphone channels than should actually be processed by subsequent processing stages (including the decimator component). For example, this may be the case if 4 microphone channels are desired but only an 8 bit wide port is physically available to capture the samples.

This class template has a parameter both for the number of channels to be captured by the port (CHANNELS_IN), as well as for the number of channels that are to be output for consumption by the MicArray’s decimator component (CHANNELS_OUT).

When the PDM microphones are in an SDR configuration, CHANNELS_IN must be the width (in bits) of the XCore port to which the microphones are physically connected. When in a DDR configuration, CHANNELS_IN must be twice the width (in bits) of the XCore port to which the microphones are physically connected.

CHANNELS_OUT is the number of microphone channels to be consumed by the mic array’s decimator component (i.e. must be the same as the MIC_COUNT template parameter of the decimator component). If all port pins are connected to microphones, this parameter will generally be the same as CHANNELS_IN.

Channel Index (Re-)Mapping

The input channel index of a microphone depends on the pin to which it is connected. Each pin connected to a port has a bit index for that port, given in the ‘Signal Description and GPIO’ section of your package’s datasheet.

Suppose an N-bit port is used to capture microphone data, and a microphone is connected to bit B of that port. In an SDR microphone configuration, the input channel index of that microphone is B, the same as the port bit index.

In a DDR configuration, that microphone will be on either input channel index B or B+N, depending on whether that microphone is configured for in-phase capture or out-of-phase capture.

Sometimes it may be desirable to re-order the microphone channel indices. This is likely the case, for example, when CHANNELS_IN > CHANNELS_OUT.

By default output channels are mapped from the input channels with the same index. If CHANNELS_IN > CHANNELS_OUT, this means that the input channels with the highest CHANNELS_IN-CHANNELS_OUT indices are dropped by default.

The MapChannel() and MapChannels() methods can be used to specify a non-default mapping from input channel indices to output channel indices. It takes a pointer to a CHANNELS_OUT-element array specifying the input channel index for each output channel.

Template Parameters:
  • CHANNELS_IN – The number of microphone channels to be captured by the port.

  • CHANNELS_OUT – The number of microphone channels to be delivered by this StandardPdmRxService instance.

  • SUBBLOCKS – The number of 32-sample sub-blocks to be captured for each microphone channel.

Public Functions

uint32_t ReadPort()#

Read a word of PDM data from the port.

Returns:

A uint32_t containing 32 PDM samples. If MIC_COUNT >= 2 the samples from each port will be interleaved together.

void SendBlock(uint32_t block[CHANNELS_IN * SUBBLOCKS])#

Send a block of PDM data to a listener.

Parameters:

block – PDM data to send.

void Init(port_t p_pdm_mics)#

Initialize this object with a channel and port.

Parameters:

p_pdm_mics – Port to receive PDM data on.

void MapChannels(unsigned map[CHANNELS_OUT])#

Set the input-output mapping for all output channels.

By default, input channel index k maps to output channel index k.

This method overrides that behavior for all channels, re-mapping each output channel such that output channel k is derived from input channel map[k].

Note

Changing the channel mapping while the mic array unit is running is not recommended.

Parameters:

map – Array containing new channel map.

void MapChannel(unsigned out_channel, unsigned in_channel)#

Set the input-output mapping for a single output channel.

By default, input channel index k maps to output channel index k.

This method overrides that behavior for a single output channel, configuring output channel out_channel to be derived from input channel in_channel.

Note

Changing the channel mapping while the mic array unit is running is not recommended.

Parameters:
  • out_channel – Output channel index to be re-mapped.

  • in_channel – New source channel index for out_channel.

void InstallISR()#

Install ISR for PDM reception on the current core.

Note

This does not unmask interrupts.

void UnmaskISR()#

Unmask interrupts on the current core.

uint32_t *GetPdmBlock()#

Get a block of PDM data.

Because blocks of PDM samples are delivered by pointer, the caller must either copy the samples or finish processing them before the next block of samples is ready, or the data will be clobbered.

Note

This is a blocking call.

Returns:

Pointer to block of PDM data.

void AssertOnDroppedBlock(bool doAssert)#

Set whether dropped PDM samples should cause an assertion.

If doAssert is set to true (default), the PDM rx ISR will raise an exception (ET_CALL) if it is ready to deliver a PDM block to the mic array thread when the mic array thread is not ready to receive it. If false, dropped blocks can be tracked through pdm_rx_isr_context.missed_blocks.

TwoStageDecimator#

template<unsigned MIC_COUNT, unsigned S2_DEC_FACTOR, unsigned S2_TAP_COUNT>
class TwoStageDecimator#

First and Second Stage Decimator.

This class template represents a two stage decimator which converts a stream of PDM samples to a lower sample rate stream of PCM samples.

Concrete implementations of this class template are meant to be used as the TDecimator template parameter in the MicArray class template.

Template Parameters:
  • MIC_COUNT – Number of microphone channels.

  • S2_DEC_FACTOR – Stage 2 decimation factor.

  • S2_TAP_COUNT – Stage 2 tap count.

Public Functions

void Init(const uint32_t *s1_filter_coef, const int32_t *s2_filter_coef, const right_shift_t s2_filter_shr)#

Initialize the decimator.

Sets the stage 1 and 2 filter coefficients. The decimator must be initialized before any calls to ProcessBlock().

s1_filter_coef points to a block of coefficients for the first stage decimator. This library provides coefficients for the first stage decimator; see mic_array/etc/filters_default.h.

s2_filter_coef points to an array of coefficients for the second stage decimator. This library provides coefficients for the second stage decimator where the second stage decimation factor is 6; see mic_array/etc/filters_default.h.

s2_filter_shr is the final right-shift applied to the stage 2 filter’s accumulator prior to output. See lib_xcore_math’s documentation of filter_fir_s32_t for more details.

Parameters:
  • s1_filter_coef

    Stage 1 filter coefficients.

    This points to a block of coefficients for the first stage decimator. This library provides coefficients for the first stage decimator.

    See stage1_coef.

  • s2_filter_coef

    Stage 2 filter coefficients.

    This points to a block of coefficients for the second stage decimator. This library provides coefficients for the second stage decimator.

    See stage2_coef.

  • s2_filter_shr

    Stage 2 filter right-shift.

    This is the output shift used by the second stage decimator.

    See stage2_shr.

void ProcessBlock(int32_t sample_out[MIC_COUNT], uint32_t pdm_block[BLOCK_SIZE])#

Process one block of PDM data.

Processes a block of PDM data to produce an output sample from the second stage decimator.

pdm_block contains exactly enough PDM samples to produce a single output sample from the second stage decimator. The layout of pdm_block should (effectively) be:

struct {
  struct {
    // lower word indices are older samples.
    // less significant bits in a word are older samples.
    uint32_t samples[S2_DEC_FACTOR];
  } microphone[MIC_COUNT]; // mic channels are in ascending order
} pdm_block;

A single output sample from the second stage decimator is computed and written to sample_out[].

Parameters:
  • sample_out – Output sample vector.

  • pdm_block – PDM data to be processed.

Public Members

unsigned DecimationFactor = S2_DEC_FACTOR#

Stage 2 decimator decimation factor.

unsigned TapCount = S2_TAP_COUNT#

Stage 2 decimator tap count.

const uint32_t *filter_coef#

Pointer to filter coefficients for Stage 1

uint32_t pdm_history[MIC_COUNT][8]#

Filter state (PDM history) for stage 1 filters.

filter_fir_s32_t filters[MIC_COUNT]#

Stage 2 FIR filters

int32_t filter_state[MIC_COUNT][S2_TAP_COUNT] = {{0}}#

Stage 2 filter stage.

Public Static Attributes

static constexpr unsigned BLOCK_SIZE = MIC_COUNT * S2_DEC_FACTOR#

Size of a block of PDM data in words.

static constexpr unsigned MicCount = MIC_COUNT#

Number of microphone channels.

static const struct mic_array::TwoStageDecimator::[anonymous] Stage2#

Stage 2 decimator parameters

SampleFilter#

NopSampleFilter#

template<unsigned MIC_COUNT>
class NopSampleFilter#

SampleFilter which does nothing.

To be used as the TSampleFilter template parameter of MicArray when no post-decimation filtering is desired.

Calls to NopSampleFilter::Filter() are intended to be optimized out at compile time.

Template Parameters:

MIC_COUNT – Number of microphone channels.

Public Functions

inline void Filter(int32_t sample[MIC_COUNT])#

Do nothing.

DcoeSampleFilter#

template<unsigned MIC_COUNT>
class DcoeSampleFilter#

Filter which applies DC Offset Elimination (DCOE).

To be used as the TSampleFilter template parameter of MicArray when DCOE is desired as post-processing after the decimation filter.

The filter is a simple first-order IIR filter which applies the following filter equation:

R = 255.0 / 256.0
y[t] = R * y[t-1] + x[t] - x[t-1]

Template Parameters:

MIC_COUNT – Number of microphone channels.

Public Functions

void Init()#

Initialize the filter states.

The filter states must be initialized prior to calls to Filter().

void Filter(int32_t sample[MIC_COUNT])#

Apply DCOE filter on samples.

sample is an array of samples to be filtered, and is updated in-place.

The filter states must have been initialized with a call to Init() prior to calling this function.

Parameters:

sample – Samples to be filtered. Updated in-place.

OutputHandler#

An OutputHandler is a class which meets the requirements to be used as the TOutputHandler template parameter of the MicArray class template. The basic requirement is that it have a method:

This method is how the mic array communicates its output with the rest of the application’s audio processing pipeline. MicArray calls this method once for each mic array output sample.

See MicArray::OutputHandler for more details.

FrameOutputHandler#

template<unsigned MIC_COUNT, unsigned SAMPLE_COUNT, template<unsigned, unsigned> class FrameTransmitter, unsigned FRAME_COUNT = 1>
class FrameOutputHandler#

OutputHandler implementation which groups samples into non-overlapping multi-sample audio frames and sends entire frames to subsequent processing stages.

This class template can be used as an OutputHandler with the MicArray class template. See MicArray::OutputHandler.

Classes derived from this template collect samples into frames. A frame is a 2 dimensional array with one index corresponding to the audio channel and the other index corresponding to time step, e.g.:

int32_t frame[MIC_COUNT][SAMPLE_COUNT];

Each call to OutputSample() adds the sample to the current frame, and then iff the frame is full, uses its FrameTx component to transfer the frame of audio to subsequent processing stages. Only one of every SAMPLE_COUNT calls to OutputSample() results in an actual transmission to subsequent stages.

With FrameOutputHandler, the thread receiving the audio will generally need to know how many microphone channels and how many samples to expect per frame (although, strictly speaking, that depends upon the chosen FrameTransmitter implementation).

Template Parameters:
  • MIC_COUNT

    The number of audio channels in each sample and each frame.

  • SAMPLE_COUNT – Number of samples per frame.

    The SAMPLE_COUNT template parameter is the number of samples assembled into each audio frame. Only completed frames are transmitted to subsequent processing stages. A SAMPLE_COUNT value of 1 effectively disables framing, transmitting one sample for each call made to OutputSample.

  • FrameTransmitter

    The concrete type of the FrameTx component of this class.

    Like many classes in this library, FrameOutputHandler uses the Curiously Recurring Template Pattern.

  • FRAME_COUNT

    The number of frame buffers an instance of FrameOutputHandler should cycle through. Unless audio frames are communicated with subsequent processing stages through shared memory, the default value of 1 is usualy ideal.

Public Functions

inline FrameOutputHandler()#

Construct new FrameOutputHandler.

The default no-argument constructor for FrameTransmitter is used to create FrameTx.

inline FrameOutputHandler(FrameTransmitter<MIC_COUNT, SAMPLE_COUNT> frame_tx)#

Construct new FrameOutputHandler.

Uses the provided FrameTransmitter to send frames.

Parameters:

frame_tx – Frame transmitter for sending frames.

void OutputSample(int32_t sample[MIC_COUNT])#

Add new sample to current frame and output frame if filled.

Parameters:

sample – Sample to be added to current frame.

Public Members

FrameTransmitter<MIC_COUNT, SAMPLE_COUNT> FrameTx#

FrameTransmitter used to transmit frames to the next stage for processing.

FrameTransmitter is the CRTP type template parameter used in this class to control how frames of audio data are communicated with subsequent pipeline stages.

The type supplied for FrameTransmitter must be a class template with two integer template parameters, corresponding to this class’s MIC_COUNT and SAMPLE_COUNT template parameters respectively, indicating the shape of the frame object to be transmitted.

The FrameTransmitter type is required to implement a single method:

void OutputFrame(int32_t frame[MIC_COUNT][SAMPLE_COUNT]);

OutputFrame() is called once for each completed audio frame and is responsible for the details of how the frame’s data gets communicated to subsequent stages. For example, the ChannelFrameTransmitter class template uses an XCore channel to send samples to another thread (by value).

Alternative implementations might use shared memory or an RTOS queue to transmit the frame data, or might even use a port to signal the samples directly to an external DAC.

ChannelFrameTransmitter#

template<unsigned MIC_COUNT, unsigned SAMPLE_COUNT>
class ChannelFrameTransmitter#

Frame transmitter which transmits frame over a channel.

This class template is meant for use as the FrameTransmitter template parameter of FrameOutputHandler.

When using this frame transmitter, frames are transmitted over a channel using the frame transfer API in mic_array/frame_transfer.h.

Usually, a call to ma_frame_rx() (with the other end of c_frame_out as argument) should be used to receive the frame on another thread.

If the receiving thread is not waiting to receive the frame when OutputFrame() is called, that method will block until the frame has been transmitted. In order to ensure there are no violations of the mic array’s real-time constraints, the receiver should be ready to receive a frame as soon as it becomes available.

Frames can be transmitted between tiles using this class.

Note

While OutputFrame() is blocking, it will not prevent the PDM rx interrupt from firing.

Template Parameters:
  • MIC_COUNT – Number of audio channels in each frame.

  • SAMPLE_COUNT – Number of samples per frame.

Public Functions

inline ChannelFrameTransmitter()#

Construct a ChannelFrameTransmitter.

If this constructor is used, SetChannel() must be called to configure the channel over which frames are transmitted prior to any calls to OutputFrame().

inline ChannelFrameTransmitter(chanend_t c_frame_out)#

Construct a ChannelFrameTransmitter.

The supplied value of c_frame_out must be a valid chanend.

Parameters:

c_frame_out – Chanend over which frames will be transmitted.

void SetChannel(chanend_t c_frame_out)#

Set channel used for frame transfers.

The supplied value of c_frame_out must be a valid chanend.

Parameters:

c_frame_out – Chanend over which frames will be transmitted.

chanend_t GetChannel()#

Get the chanend used for frame transfers.

Returns:

Channel to be used for frame transfers.

void OutputFrame(int32_t frame[MIC_COUNT][SAMPLE_COUNT])#

Transmit the specified frame.

See ChannelFrameTransmitter for additional details.

Parameters:

frame – Frame to be transmitted.

Misc#

template<unsigned MIC_COUNT>
void mic_array::deinterleave_pdm_samples(uint32_t *samples, unsigned s2_dec_factor)#

Deinterleave the channels of a block of PDM data.

PDM samples received on a port are shifted into a 32-bit buffer in such a way that the samples for each microphone channel are all interleaved with one another. The first stage decimator, however, requires these to be separated.

samples must point to a buffer containing (MIC_COUNT*s2_dec_factor) words of PDM data. Because the decimation factor for the first stage decimator is a fixed value of 32, 32 PDM samples from each microphone is enough to produce one output sample (a MIC_COUNT-element vector) from the first stage decimator. 32*s2_dec_factor PDM samples for each of the MIC_COUNT microphone channels is then exactly what is required to produce a single output sample from the second stage decimator.

The PDM data will be deinterleaved in-place.

On input, the format of the buffer to which samples points is assumed to be such that the following function will extract (only) the kth sample for microphone channel n (where k is a time index, not a memory index):

Input Format

unsigned get_sample(uint32_t* samples, 
                    unsigned MIC_COUNT, unsigned s2_dec_factor, 
                    unsigned n, unsigned k)
{
  const end_word = MIC_COUNT * s2_dec_factor - 1; // chronologically first
  const unsigned samp_per_word = 32 / MIC_COUNT;
  const words_from_end = k / samp_per_word;
  const uint32_t word_val = samples[end_word-words_from_end];
  const unsigned bit_offset = (k % end_word) + n;
  return (word_val >> bit_offset) & 1;
}

Here, the words of samples are stored in reverse order (older samples are at higher word indices), and within a word the oldest samples are the least significant bits. The LSb of a word is always microphone channel 0, and the MSb of a word is always microphone channel MIC_COUNT-1.

Upon return, the format of the buffer to which samples points will be such that the following function will extract (only) the kth sample for microphone channel n:

Output Format

unsigned get_sample(uint32_t* samples, 
                    unsigned MIC_COUNT, unsigned s2_dec_factor, 
                    unsigned n, unsigned k)
{
  const unsigned subblock = (s2_dec_factor-1)-(k/32);
  const unsigned word_val = samples[subblock * MIC_COUNT + n];
  return (word_val >> (k%32)) & 1;
}

Here, each word contains samples from only a single channel, with words at higher addresses containing older samples. samples[0] contains the newest samples for microphone channel 0, and samples[MIC_COUNT-1] contains the newest samples for microphone channel MIC_COUNT-1. samples[MIC_COUNT] contains the next-oldest set of samples for channel 0, and so on.

Template Parameters:

MIC_COUNT

Number of channels represented in PDM data.

One of

{1,2,4,8}

Parameters:
  • samples – Pointer to block of PDM samples.

  • s2_dec_factor – Stage2 decimator decimation factor.