XCORE SDK
XCORE Software Development Kit
Public Types | Public Member Functions | List of all members
mic_array::prefab::BasicMicArray< MIC_COUNT, FRAME_SIZE, USE_DCOE, MICS_IN > Class Template Reference

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

#include <Prefab.hpp>

Inheritance diagram for mic_array::prefab::BasicMicArray< MIC_COUNT, FRAME_SIZE, USE_DCOE, MICS_IN >:
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 > >

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 > >
 

Public Member Functions

constexpr BasicMicArray () noexcept
 No-argument constructor. More...
 
void Init ()
 Initialize the decimator.
 
 BasicMicArray (port_t p_pdm_mics, chanend_t c_frames_out)
 Initialzing constructor. More...
 
void SetPort (port_t p_pdm_mics)
 Set the PDM data port. More...
 
void SetOutputChannel (chanend_t c_frames_out)
 Set the audio frame output channel. More...
 
void PdmRxThreadEntry ()
 Entry point for PDM rx thread. More...
 
void InstallPdmRxISR ()
 Install the PDM rx ISR on the calling thread. More...
 
void UnmaskPdmRxISR ()
 Unmask interrupts on the calling thread. More...
 
- Public Member Functions inherited from 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 > >
 MicArray ()
 Construct a MicArray.
 
 MicArray (StandardPdmRxService< MIC_COUNT, MIC_COUNT, STAGE2_DEC_FACTOR > pdm_rx, std::conditional< USE_DCOE, DcoeSampleFilter< MIC_COUNT >, NopSampleFilter< MIC_COUNT > >::type sample_filter, FrameOutputHandler< MIC_COUNT, FRAME_SIZE, ChannelFrameTransmitter > output_handler)
 Construct a MicArray. More...
 
 MicArray (StandardPdmRxService< MIC_COUNT, MIC_COUNT, STAGE2_DEC_FACTOR > pdm_rx, FrameOutputHandler< MIC_COUNT, FRAME_SIZE, ChannelFrameTransmitter > output_handler)
 Construct a MicArray More...
 
void ThreadEntry ()
 Entry point for the decimation thread.
 

Additional Inherited Members

- Public Attributes inherited from 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 > >
StandardPdmRxService< MIC_COUNT, MIC_COUNT, STAGE2_DEC_FACTORPdmRx
 The PDM rx service. More...
 
TwoStageDecimator< MIC_COUNT, STAGE2_DEC_FACTOR, STAGE2_TAP_COUNTDecimator
 The Decimator. More...
 
std::conditional< USE_DCOE, DcoeSampleFilter< MIC_COUNT >, NopSampleFilter< MIC_COUNT > >::type SampleFilter
 The output filter. More...
 
FrameOutputHandler< MIC_COUNT, FRAME_SIZE, ChannelFrameTransmitterOutputHandler
 The output handler. More...
 
- Static Public Attributes inherited from 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 > >
static constexpr unsigned MicCount
 Number of microphone channels.
 

Detailed Description

template<unsigned MIC_COUNT, unsigned FRAME_SIZE, bool USE_DCOE, unsigned MICS_IN = MIC_COUNT>
class mic_array::prefab::BasicMicArray< MIC_COUNT, FRAME_SIZE, USE_DCOE, MICS_IN >

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

This prefab is likely the right starting point for most bare-metal (i.e. not using an RTOS) 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. The benefit of running PDM rx as a thread is decreased MIPS usage, but the drawback is that it consumes a core.

For the first and second stage decimation filters, this prefab uses the coefficients provided with this library. The first stage uses a (non-configurable) 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

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. Note that calls to ma_frame_rx() will block until a frame becomes available on the specified chanend.

Warning
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.

See

embed:rst
  :ref:`sample_filters` 

for more information about DC offset elimination.

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 dervice 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);
}
void SetOutputChannel(chanend_t c_frames_out)
Set the audio frame output channel.
Definition: Prefab.hpp:458
void SetPort(port_t p_pdm_mics)
Set the PDM data port.
Definition: Prefab.hpp:466

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.

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.
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;
...
MA_C_API // alias for 'extern "C"'
void app_mic_array_task()
{
mics.ThreadEntry();
}
void ThreadEntry()
Entry point for the decimation thread.
Definition: MicArray.hpp:141
void UnmaskPdmRxISR()
Unmask interrupts on the calling thread.
Definition: Prefab.hpp:490
void mic_array_pdm_clock_start(pdm_rx_resources_t *pdm_res)
Start the PDM and capture clock(s).
Collection of resources IDs required for PDM capture.
Definition: pdm_resources.h:32

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()
{
}
MA_C_API
void app_mic_array_task()
{
mics.ThreadEntry();
}
void PdmRxThreadEntry()
Entry point for PDM rx thread.
Definition: Prefab.hpp:474
Note
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.

Todo:
link to example apps?
Hardware resource usage
Todo:
Template Parameters
MIC_COUNTNumber of microphone channels.
FRAME_SIZENumber of samples in each output audio frame.
USE_DCOEWhether DC offset elimination should be used.

Constructor & Destructor Documentation

◆ BasicMicArray() [1/2]

template<unsigned MIC_COUNT, unsigned FRAME_SIZE, bool USE_DCOE, unsigned MICS_IN = MIC_COUNT>
constexpr mic_array::prefab::BasicMicArray< MIC_COUNT, FRAME_SIZE, USE_DCOE, MICS_IN >::BasicMicArray ( )
inlineconstexprnoexcept

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.

◆ BasicMicArray() [2/2]

template<unsigned MIC_COUNT, unsigned FRAME_SIZE, bool USE_DCOE, unsigned MICS_IN>
mic_array::prefab::BasicMicArray< MIC_COUNT, FRAME_SIZE, USE_DCOE, MICS_IN >::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_micsPort with PDM microphones
c_frames_out(non-streaming) chanend used to transmit frames.

Member Function Documentation

◆ InstallPdmRxISR()

template<unsigned MIC_COUNT, unsigned FRAME_SIZE, bool USE_DCOE, unsigned MICS_IN>
void mic_array::prefab::BasicMicArray< MIC_COUNT, FRAME_SIZE, USE_DCOE, MICS_IN >::InstallPdmRxISR

Install the PDM rx ISR on the calling thread.

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

◆ PdmRxThreadEntry()

template<unsigned MIC_COUNT, unsigned FRAME_SIZE, bool USE_DCOE, unsigned MICS_IN>
void mic_array::prefab::BasicMicArray< MIC_COUNT, FRAME_SIZE, USE_DCOE, MICS_IN >::PdmRxThreadEntry

Entry point for PDM rx thread.

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

Note
This call does not return.

◆ SetOutputChannel()

template<unsigned MIC_COUNT, unsigned FRAME_SIZE, bool USE_DCOE, unsigned MICS_IN>
void mic_array::prefab::BasicMicArray< MIC_COUNT, FRAME_SIZE, USE_DCOE, MICS_IN >::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_outThe channel to send audio frames on.

◆ SetPort()

template<unsigned MIC_COUNT, unsigned FRAME_SIZE, bool USE_DCOE, unsigned MICS_IN>
void mic_array::prefab::BasicMicArray< MIC_COUNT, FRAME_SIZE, USE_DCOE, MICS_IN >::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_micsThe port to receive PDM data on.

◆ UnmaskPdmRxISR()

template<unsigned MIC_COUNT, unsigned FRAME_SIZE, bool USE_DCOE, unsigned MICS_IN>
void mic_array::prefab::BasicMicArray< MIC_COUNT, FRAME_SIZE, USE_DCOE, MICS_IN >::UnmaskPdmRxISR

Unmask interrupts on the calling thread.

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


The documentation for this class was generated from the following file: