Skip to content

Commit

Permalink
Merge pull request #209 from xmos/release/v5.2.0
Browse files Browse the repository at this point in the history
Release v5.2.0 to main
  • Loading branch information
ed-xmos authored Sep 11, 2023
2 parents beef6eb + 34e6c86 commit 28cc9b1
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 12 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
lib_mic_array change log
========================

5.2.0
-----

* Added 48 kHz decimator design script
* Added documentation to cover 32 kHz and 48 kHz deciamtors

5.1.0
-----

Expand Down
3 changes: 2 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ The CHANGELOG contains information about the current and previous versions.
Related application notes
.........................

None
See the `demos/` subdirectory for simple examples. Also, `sln_voice` uses this library extensively and contains
multiple examples both bare-metal and under the RTOS. See https://github.com/xmos/sln_voice/tree/develop/examples.
Binary file added doc/programming_guide/src/32k_freq_response.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/programming_guide/src/48k_freq_response.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
117 changes: 116 additions & 1 deletion doc/programming_guide/src/decimator_stages.rst
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,119 @@ C++ API directly.
Note that both the first and second stage filters are implemented using
fixed-point arithmetic which requires the coefficients to be presented in a
particular format. The Python scripts `stage1.py` and `stage2.py`, provided with
this library, can be used to help with this formatting. See the associated README for usage details.
this library, can be used to help with this formatting. See the associated README for usage details.


Configuring for 32 kHz or 48 kHz output
***************************************

Filter design scripts are provided to support higher output sampling rates than the default 16 kHz.

Both stage 1 and stage 2 need to be updated because the first stage needs a higher
cut off frequency before samples are passed to the downsample by three (32 kHz) or two (48 kHz) second stage
decimator.

From the command line, follow these instructions::

python filter_design/design_filter.py # generate the filter .pkl files
python stage1.py good_32k_filter_int.pkl # convert the .pkl file to a C style array for stage 1
python stage2.py good_32k_filter_int.pkl # convert the .pkl file to a C style array for stage 2

.. note::
Use `good_48k_filter_int.pkl` instead of `good_32k_filter_int.pkl` to support 48 kHz.


Next copy the output from last two scripts into a source file. This could be your `mic_array.cpp`
file which launches the mic array tasks. It may look something like this::

#define MIC_ARRAY_32K_STAGE_1_TAP_COUNT 148
#define MIC_ARRAY_32K_STAGE_1_FILTER_WORD_COUNT 128
static const uint32_t WORD_ALIGNED stage1_32k_coefs[MIC_ARRAY_32K_STAGE_1_FILTER_WORD_COUNT]
{
.... the coeffs
};

#define MIC_ARRAY_32K_STAGE_2_TAP_COUNT 96
static constexpr right_shift_t stage2_32k_shift = 3;

static const int32_t WORD_ALIGNED stage2_32k_coefs[MIC_ARRAY_32K_STAGE_2_TAP_COUNT] = {
.... the coeffs
};

The new decimation object must now be declared that references your new filter coefficients.
Again, this example is for 32 kHz output since the decimation factor is 3.::

using TMicArray = mic_array::MicArray<mic_count,
mic_array::TwoStageDecimator<mic_count,
3,
MIC_ARRAY_32K_STAGE_2_TAP_COUNT>,
mic_array::StandardPdmRxService<MIC_ARRAY_CONFIG_MIC_IN_COUNT,
mic_count,
3>,
typename std::conditional<MIC_ARRAY_CONFIG_USE_DC_ELIMINATION,
mic_array::DcoeSampleFilter<mic_count>,
mic_array::NopSampleFilter<mic_count>>::type,
mic_array::FrameOutputHandler<mic_count,
MIC_ARRAY_CONFIG_SAMPLES_PER_FRAME,
mic_array::ChannelFrameTransmitter>>;


Next you need to change how you initialise and run the mic array task to reference your new
mic array custom object. Normally the following code would be used in `ma_init()`::

mics.Init();
mics.SetPort(pdm_res.p_pdm_mics);
mic_array_resources_configure(&pdm_res, MIC_ARRAY_CONFIG_MCLK_DIVIDER);
mic_array_pdm_clock_start(&pdm_res);

however if you wish to use custom filters then the initialisation would look like this::

mics.Decimator.Init(stage1_32k_coefs, stage2_32k_coefs, stage2_32k_shift);
mics.PdmRx.Init(pdm_res.p_pdm_mics);
mic_array_resources_configure(&pdm_res, MIC_ARRAY_CONFIG_MCLK_DIVIDER);
mic_array_pdm_clock_start(&pdm_res);


Finally, the `ma_task()` function needs to be changed from the default way of calling::

mics.SetOutputChannel(c_frames_out);
mics.InstallPdmRxISR();
mics.UnmaskPdmRxISR();
mics.ThreadEntry();

to using the custom version of the object::

mics.OutputHandler.FrameTx.SetChannel(c_frames_out);
mics.PdmRx.InstallISR();
mics.PdmRx.UnmaskISR();
mics.ThreadEntry();


The increased sample rate will place a higher MIPS burden on the processor. The typical
MIPS usage (see section :ref:`resource_usage`) is in the order of 11 MIPS per channel
using a 16 kHz output decimator.

Increasing the output sample rate to 32 kHz using the same length filters will increase
processor usage per channel to approximately 13 MIPS rising to 15.6 MIPS for 48 kHz.

Increasing the filer lengths to 148 and 96 for stages 1 and 2 respectively at 48 kHz
will increase processor usage per channel to around 20 MIPS.

Filter Characteristics for `good_32k_filter_int.pkl`
''''''''''''''''''''''''''''''''''''''''''''''''''''

The plot below indicates the frequency response of the first and second stages of the
provided 32 kHz filters as well as the cascaded overall response. Note that the
overall combined response provides a nice flat passband.

.. image:: 32k_freq_response.png

Filter Characteristics for `good_48k_filter_int.pkl`
''''''''''''''''''''''''''''''''''''''''''''''''''

The plot below indicates the frequency response of the first and second stages of the
provided 48 kHz filters as well as the cascaded overall response. Note that the
overall combined response provides a nice flat passband.

.. image:: 48k_freq_response.png

2 changes: 1 addition & 1 deletion doc/programming_guide/src/introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ See :ref:`getting_started` to get going.

.. note::

Version 5.0 does not currently support XS2 or XS1 devices.
Version 5.0 does not currently support XS2 or XS1 devices. Please use version 4.5.0 if you need support for these devices: https://github.com/xmos/lib_mic_array/releases/tag/v4.5.0


Find the latest version of ``lib_mic_array`` on `GitHub
Expand Down
7 changes: 6 additions & 1 deletion doc/programming_guide/src/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ Capabilities
* Second stage has fully configurable tap count and decimation factor
* Custom filter coefficients can be used for either stage
* Reference filter with total decimation factor of 192 is provided (16 kHz
output sample rate w/ 3.072 MHz PDM clock)
output sample rate w/ 3.072 MHz PDM clock).
* Filter generation scripts and examples are included to support 32 kHz and 48 kHz.

* Supports 1-, 4- and 8-bit ports.
* Supports 1 to 16 microphones
Expand Down Expand Up @@ -74,6 +75,10 @@ PDM rx is typically run within an interrupt on the same hardware core as the
decimation thread, but it can also be run as a separate thread in cases where
many channels result in a high processing load.

Likewise, the decimators may be split into multiple parallel hardware threads
in the case where the processing load exceeds the MIPS available in a single
thread.

Step 1: PDM Capture
*******************

Expand Down
4 changes: 2 additions & 2 deletions doc/programming_guide/src/resource_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ applications in ``demo/measure_mips``.
| PDM Freq | S2DF | S2TC | PdmRx | 1 mic | 2 mic | 4 mic | 8 mic |
| | | | | MIPS | MIPS | MIPS | MIPS |
+============+======+======+=======+=======+=======+=======+=======+
| 3.072 MHz | 6 | 65 | ISR | 10.65 | 22.00 | TBD | TBD |
| 3.072 MHz | 6 | 65 | ISR | 10.65 | 22.00 | 43.70 | N/A |
+------------+------+------+-------+-------+-------+-------+-------+
| 3.072 MHz | 6 | 65 |Thread | 9.33 | 19.37 | TBD | TBD |
| 3.072 MHz | 6 | 65 |Thread | 9.33 | 19.37 | 38.48 | 75.90 |
+------------+------+------+-------+-------+-------+-------+-------+
| 6.144 MHz | 6 | 65 | ISR | 21.26 | 43.89 | TBD | TBD |
+------------+------+------+-------+-------+-------+-------+-------+
Expand Down
2 changes: 1 addition & 1 deletion lib_mic_array/module_build_info
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION = 5.0.3
VERSION = 5.2.0

DEPENDENT_MODULES = lib_xcore_math

Expand Down
48 changes: 48 additions & 0 deletions script/filter_design/design_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,44 @@ def good_32k_filter(int_coeffs: bool):
return coeffs


def good_48k_filter(int_coeffs: bool):
fs_0 = 3072000
decimations = [32, 2]

# stage 1 parameters
ma_stages = 5

# stage 2 parameters
cutoff = 20000
transition_bandwidth = 1000
taps_2 = 96
fir_window = ("kaiser", 6.5)
stage_2 = stage_params(cutoff, transition_bandwidth, taps_2, fir_window)

coeffs = design_2_stage(fs_0, decimations, ma_stages, stage_2, int_coeffs=int_coeffs)

return coeffs


def small_48k_filter(int_coeffs: bool):
fs_0 = 3072000
decimations = [32, 2]

# stage 1 parameters
ma_stages = 5

# stage 2 parameters
cutoff = 20000
transition_bandwidth = 1000
taps_2 = 64
fir_window = ("kaiser", 8)
stage_2 = stage_params(cutoff, transition_bandwidth, taps_2, fir_window)

coeffs = design_2_stage(fs_0, decimations, ma_stages, stage_2, int_coeffs=int_coeffs)

return coeffs


def main():
coeffs = small_2_stage_filter(int_coeffs=True)
out_path = "small_2_stage_filter_int.pkl"
Expand All @@ -333,10 +371,20 @@ def main():
out_path = "good_3_stage_filter_int.pkl"
ft.save_packed_filter(out_path, coeffs)

# For more info on 32kHz and 48kHz design, see
# https://xmosjira.atlassian.net/wiki/spaces/CONF/pages/3805052950/32k+and+48k+Hz+lib+mic+array

coeffs = good_32k_filter(int_coeffs=True)
out_path = "good_32k_filter_int.pkl"
ft.save_packed_filter(out_path, coeffs)

coeffs = good_48k_filter(int_coeffs=True)
out_path = "good_48k_filter_int.pkl"
ft.save_packed_filter(out_path, coeffs)

coeffs = small_48k_filter(int_coeffs=True)
out_path = "small_48k_filter_int.pkl"
ft.save_packed_filter(out_path, coeffs)

if __name__ == "__main__":
main()
12 changes: 10 additions & 2 deletions script/filter_design/filter_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,16 @@ def moving_average_filter_int16(decimation_ratio, n_stages, optimise_scaling=Tru
# with the next stage. Use a 2 stage MA again for the final convolution
rounding_stages = n_stages - 3
if n_stages > 5:
# rounding could add up to len(b) to the value, compensate scaling
max_value = (32767.0 - len(b)*rounding_stages)
# rounding could add up to len(b) to the value, compensate scaling,
# not all values have been tested, so some may fail
if n_stages in [5]:
max_value = (32767.0 - len(b))
elif n_stages in [7, 8]:
# hand tuned to get lucky on the rounding, there's probably a nicer way
max_value = (32767.0 - 7*len(b))
else:
# sometimes adds up to more than len(b), add extra to be safe
max_value = (32767.0 - len(b)*n_stages)
else:
max_value = 32767.0
scaling = np.max(b)/max_value
Expand Down
8 changes: 6 additions & 2 deletions script/filter_design/plot_coeffs.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,12 @@ def plot_filters(coeffs: list, fs_0, axs=None):

def main():
# load default coefficients & decimation factors
coeffs = np.load(Path(Path(__file__).parent, "..", "..", "tests", "signal", "BasicMicArray", "default_filters.pkl"),
allow_pickle=True)
path = Path(Path(__file__).parent, "..", "..", "tests", "signal", "BasicMicArray", "default_filters.pkl")

# optionally load custom filters
# path = Path(Path(__file__).parent, "..", "good_32k_filter_int.pkl")
# path = Path(Path(__file__).parent, "..", "good_48k_filter_int.pkl")
coeffs = np.load(path, allow_pickle=True)

# sample rates and decimations
fs_0 = 3072000
Expand Down
2 changes: 1 addition & 1 deletion settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"title": "Mic Array Library",
"project": "lib_mic_array",
"version": "5.1.0"
"version": "5.2.0"
}

0 comments on commit 28cc9b1

Please sign in to comment.