Prefab.hpp¶
-
template<unsigned MIC_COUNT, unsigned FRAME_SIZE, bool USE_DCOE, unsigned MICS_IN = MIC_COUNT>
class mic_array::prefab::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 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 to3.072 MHz
(3.072 MHz / (32 * 6) = 16 kHz
).An instance of
BasicMicArray
has 4 sub-components responsible for different portions of the work being done. These sub-components arePdmRx
,Decimator
,SampleFilter
andOutputHandler
. See the documentation forMicArray
for more details about these.- Sub-Components
The template parameter
MIC_COUNT
is the number of microphone channels to be processed and output.- Template Parameters Details
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 inmic_array/frame_transfer.h
. Typicallyma_frame_rx()
will be the right function to use in a receiving thread to retrieve audio frames. Note that calls toma_frame_rx()
will block until a frame becomes available on the specified chanend.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
for more information about DC offset elimination.If
USE_DCOE
isfalse
, 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 thePdmRx
component of the mic array unit. This will often be the same asMIC_COUNT
, but in some applications,MIC_COUNT
microphones must be physically connected to an xCore port which is notMIC_COUNT
(SDR) orMIC_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 callingStandardPdmRxService::MapChannels()
on thePdmRx
sub-component of theBasicMicarray
.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 toMIC_COUNT
.Before a mic array unit can be started or initialized, it must be allocated.
- Allocation
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.Before a mic array unit can be started, it must be initialized.
- Initialization
BasicMicArray
reads PDM samples from an xCore port, and delivers frames of audio data over an xCore channel. To this end, an instance ofBasicMicArray
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)
andBasicMicArray::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, apdm_rx_resources_t
struct representing these hardware resources is constructed and passed tomic_array_resources_configure()
. See the documentation forpdm_rx_resources_t
andmic_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 toBasicMicArray::InstallPdmRxISR()
. Installing the ISR will not unmask it.After it has been initialized, starting the mic array unit with the PDM rx service running as an ISR, three steps are required.
- Begin Processing (PDM rx ISR)
First, the PDM clock must be started. This is accomplished with a call to
mic_array_pdm_clock_start()
. The samepdm_rx_resources_t
that was passed tomic_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-tilemain()
in an XC file. Then,app_mic_array_task()
is called directly from apar {...}
block. For example,main(){ ... par { on tile[1]: { ... // Do initialization stuff par { app_mic_array_task(); ... other_thread_on_tile1(); // other threads } } } }
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.
- Begin Processing (PDM Rx Thread)
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) usingBasicMicArray::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(); }
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 } } } }
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.
- Real-Time Constraint
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.
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.- Examples
- Todo:
link to example apps?
- Todo:
- Hardware resource usage
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.
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.Note
Notice that
app_mic_array_task()
above is a thin wrapper formics.ThreadEntry()
. Unfortunately, because the type ofmics
is a C++ class,mics.ThreadEntry()
cannot be called directly from an XC file (including the one containingmain()
). Further, because a C++ class template was used, this library cannot provide a generic C-compatible call wrapper for the methods on aMicArray
object. This unfortunately means it is necessary in some cases to create a thin wrapper such asapp_mic_array_task()
.- 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 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()
andBasicMicArray::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()
.