diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 628b2844..01e96d07 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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 ----- diff --git a/README.rst b/README.rst index 1cd63e19..b43c5b2b 100644 --- a/README.rst +++ b/README.rst @@ -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. diff --git a/doc/programming_guide/src/32k_freq_response.png b/doc/programming_guide/src/32k_freq_response.png new file mode 100644 index 00000000..92d5cebf Binary files /dev/null and b/doc/programming_guide/src/32k_freq_response.png differ diff --git a/doc/programming_guide/src/48k_freq_response.png b/doc/programming_guide/src/48k_freq_response.png new file mode 100644 index 00000000..ac62f4a3 Binary files /dev/null and b/doc/programming_guide/src/48k_freq_response.png differ diff --git a/doc/programming_guide/src/decimator_stages.rst b/doc/programming_guide/src/decimator_stages.rst index 232e6a4e..09fac522 100644 --- a/doc/programming_guide/src/decimator_stages.rst +++ b/doc/programming_guide/src/decimator_stages.rst @@ -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. \ No newline at end of file +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_array::StandardPdmRxService, + typename std::conditional, + mic_array::NopSampleFilter>::type, + mic_array::FrameOutputHandler>; + + +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 + diff --git a/doc/programming_guide/src/introduction.rst b/doc/programming_guide/src/introduction.rst index 6b755f76..f73622fb 100644 --- a/doc/programming_guide/src/introduction.rst +++ b/doc/programming_guide/src/introduction.rst @@ -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 diff --git a/doc/programming_guide/src/overview.rst b/doc/programming_guide/src/overview.rst index e7a6a4c4..9961f6d4 100644 --- a/doc/programming_guide/src/overview.rst +++ b/doc/programming_guide/src/overview.rst @@ -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 @@ -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 ******************* diff --git a/doc/programming_guide/src/resource_usage.rst b/doc/programming_guide/src/resource_usage.rst index 101af795..a926a673 100644 --- a/doc/programming_guide/src/resource_usage.rst +++ b/doc/programming_guide/src/resource_usage.rst @@ -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 | +------------+------+------+-------+-------+-------+-------+-------+ diff --git a/lib_mic_array/module_build_info b/lib_mic_array/module_build_info index 11b769a3..7d371ccf 100644 --- a/lib_mic_array/module_build_info +++ b/lib_mic_array/module_build_info @@ -1,4 +1,4 @@ -VERSION = 5.0.3 +VERSION = 5.2.0 DEPENDENT_MODULES = lib_xcore_math diff --git a/script/filter_design/design_filter.py b/script/filter_design/design_filter.py index 0050f7b0..2e8e2cfa 100644 --- a/script/filter_design/design_filter.py +++ b/script/filter_design/design_filter.py @@ -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" @@ -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() diff --git a/script/filter_design/filter_tools.py b/script/filter_design/filter_tools.py index b8d3b579..58396887 100644 --- a/script/filter_design/filter_tools.py +++ b/script/filter_design/filter_tools.py @@ -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 diff --git a/script/filter_design/plot_coeffs.py b/script/filter_design/plot_coeffs.py index 0d245a27..2473aa0d 100644 --- a/script/filter_design/plot_coeffs.py +++ b/script/filter_design/plot_coeffs.py @@ -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 diff --git a/settings.json b/settings.json index 88bbc2b5..c2b9a67f 100644 --- a/settings.json +++ b/settings.json @@ -1,5 +1,5 @@ { "title": "Mic Array Library", "project": "lib_mic_array", - "version": "5.1.0" + "version": "5.2.0" }