XCORE SDK
XCORE Software Development Kit
|
Class template for a typical bare-metal mic array unit. More...
#include <Prefab.hpp>
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... | |
![]() | |
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. | |
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
).
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.
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.
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
.
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.
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.
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:
Otherwise (typically), these can be set using BasicMicArray::SetPort(port_t)
and BasicMicArray::SetOutputChannel(chanend_t)
to set the port and channel respectively.
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.
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.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.
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,
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:
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:
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.
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.
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. |
|
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.
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.
p_pdm_mics | Port with PDM microphones |
c_frames_out | (non-streaming) chanend used to transmit frames. |
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()
.
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()
.
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.
c_frames_out | The channel to send audio frames on. |
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.
p_pdm_mics | The port to receive PDM data on. |
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()
.