PDM to PCM Filter Design¶
Introduction¶
Conversion of the 1 bit PDM signal at a high sample rate to a 32 bit PCM signal at a lower sample rate requires several decimation stages. Each filter should remove the frequencies that will alias into the final passband, this typically means attenuation by at least 80dB. There are 3 types of stage, all of which are implemented as FIR filters:
First stage: moving average filter, decimation of PDM signal by 32.
Middle stages (optional): half band filter, decimation by 2
Final stage: low pass filter, decimation to final sample rate.
A 2 stage filter has a simpler implementation, but a 3 or more stage filter needs fewer computations for the same performance. Python scripts to aid the design are provided in this folder.
Example designs¶
Functions to design 2 and 3 stage decimation filters can be found in
design_filters.py. This also contains several example designs:
good_2_stage_filter: decimation from 3.072 MHz to 16 kHz using 2 stages.small_2_stage_filter: as above, but using fewer filter taps in stage 2. This saves memory, at the cost of reduced alias suppression and early passband rolloff.good_3_stage_filter: similar performance togood_2_stage_filter, but over 3 stages instead of 2. This results in fewer computations.good_32k_filter: decimation from 3.072 MHz to 32 kHz using 2 stages.
Each example design returns coefficients in a packed format of
[stage][coefficients, decimation_ratio]. The filter coefficients are
returned as either float or int. If int_coeffs=True, stage 1 will be int16
and subsequent stages will be int32.
Calling python ./python/filter_design/design_filter.py will generate a
.pkl file for each example. These can be run though python/stage1.py to
convert the coefficients to hexadecimal values for use in
lib_mic_array/src/etc/stage1_fir_coef.c. More details on this process are
in python/README.rst
Plotting utilities¶
plot_coeffs.py contains filter response plotting utilities for individual
stages (plot_stage) and the combined response (plot_filters). Both can
input and output axis handles for comparing filter designs.
When plotting a single stage using plot_stage, the a plot is generated
showing the filter taps in the time domain, and the frequency response of the
filter. The filter response shows the frequency response of the filter at
the input sampling frequency. Frequencies that will be discarded by subsequent
stages are greyed out, frequencies that will alias into the final passband are
left white. These frequencies should have the most attenuation.
The passband response shows the filter response at the output sampling frequency. The decimated response includes all the aliases, the summed level of these is shown in green. The alias level in the final passband should be as low as possible. The passband ripple is the filter response, shown on a zoomed scale.
plot_filters shows a summary of the individual stages, and the combined
filter response. Passband roll off in stage 1 can be corrected stage 2, giving
a flat combined response.
Designing custom filters¶
Tools for designing custom decimation filter are provided. Generally, at least 80dB of alias attenuation is recommended. More taps is better, but will use more memory and CPU, and may have rounding issues in Stage 1. Some design considerations for each stage are detailed below
Stage 1¶
The stage 1 filter starts with a 32 tap moving average filer (np.ones(32)),
which is convolved with itself ma_stages times. This gives a frequency
response with strong nulls at frequencies that will alias into the final
passband. More stages will give more attenuation and widen the nulls. The
number of taps for the final filter is (31*ma_stages) + 1.
The coefficient
values for 4 or fewer stages fits within the range of int16, so no rounding
is required. For 5 or more stages, the range of coefficient values exceeds
int16 range, so scaling and rounding is required which will affect the
filter response and increase the level of the aliases. As each stage convolves
np.ones(32) with the previous stage, a good solution is to scale and round
the coefficients at an intermediate stage, allowing the final stages to be
convolved without additional rounding.
Final Stage¶
The ideal final stage filter would be a brickwall antialiasing filter (i.e. 8kHz for a 16kHz output). A realistic implementation must balance the flatness of the passband with the amount of alias attenuation. More taps allows for a steeper slope, increased alias attenuation and less compromise, but requires more memory and compute.
The filter is designed using the window method,
typically using a Kaiser window which allows for good control of the filter
slopes (other windows can be specified). The target filter response compensates
for any rolloff in previous stages to give a flat combined filter response,
generally this results in around 1dB rise at higher frequencies. The C
implementation requires the coefficients to be int32, scaling and rounding
to this is not usually an issue.
Optional middle stages¶
Extra decimation stages can be added after the initial MA filter. This means the final stage filter runs at a lower sample rate, and so needs fewer taps for the same performance. Middle stages are expected to decimate by 2, allowing a half band filter to be used. These filters have a very large transition band as only frequencies that alias into the final passband need to be attenuated, and so require very few taps to implement effectively.
If (n_taps+1) % 4 == 0,
then every other tap can be zero. This is achieved by designing the filter at
the output sample rate, then upsampling it to the input sample rate by
inserting zeros between the taps. Coefficients are int32, so scaling and
rounding is not usually an issue.