Hello.
I need help to build a simple audio filter.
Currently my system (a 48MHz ARM M0+) can read the ADC at 20KHz and make an LPF FIR (20taps) at the frequency of 2KHz.
I need to make a BPF with a bandwidth of 100Hz centered at 1KHz.
Basically I'm using 2 cascade FIR filters.
First I calculate the LPF to 2KHz and then I apply a BPF FIR to 1KHz.
This cycle is repeated at the frequency of 5KHz, because only with this frequency of sampling I can get the BW of 100Hz.
Basically I first filter the entire signal to avoid aliasing above 2KHz and then sample at 5KHz to run the BPF.
I was able to make a 20taps FIR filter, in cycles of 20KHz, with a cut-off frequency of 2KHz.
Works well! The audio is filtered and have quality.
When I put the 2 filters together, the sound sucks and it looks like the BPF is not working
.
Can someone give me some help?
I'm using my own algorithm, but do you think using the CMSIS algorithm could be better?
Thanks!
What's your input signal? Is it already band-limited? Or do you have an anti-aliasing filter before the ADC?
Thank you for the reply.
Yes I did a 3rd order filter with Fc = 5KHz.
I can sample at 20KHz without aliasing problems.
These are the 2 main functions in my code.
I do not know if there will be any problem in using 2 cascaded FIR filters ...
All times are OK. I change a pin state in the various places and the ADC is read every 1/20KHz, the filter runs every 1/5KHz and the duration of the calculations does not exceed the time available. So everything seems to be right ...
#define LPF_ORDER 20
#define LPF_HALF_ORDER (LPF_ORDER/2)
#define FILTER_COEFF_MULT 13 //2^13
#define BPF_ORDER 50
#define BPF_ORDER_1 (BPF_ORDER - 1)
#define BPF_HALF_ORDER (BPF_ORDER/2)
//buffer com as últimas leituras da ADC
int16_t ADC_readings_array[LPF_ORDER];
//Index para executar buffer circular da ADC
uint8_t adc_buf_head;
//buffer com as últimas leituras da ADC
int16_t LPF_results_array[BPF_ORDER];
//ultimo resultado do calculo do FIR
int32_t fir_result;
//LOW-PASS FILTER
const int16_t LPF_2000Hz[LPF_HALF_ORDER] =
{ -14, -23, -12, 41, 155, 335, 565, 807, 1010, 1127 };
//BAND-PASS FILTER
const int16_t BPF_100Hz[BPF_HALF_ORDER] =
{ 3, -3, -18, -9, 35, 49, -25, -106, -41, 133, 161, -73, -273, -97, 285, 318, -133, -469, -156, 433, 456, -181, -604,
-190, 503 };
//Interrupt routine to read the ADC value
void Routine_20KHz(void)
{
//save the ADC reading to circular buffer
ADC_readings_array[adc_buf_head] = ADC_DMA[SIGNAL_ADC] - ADC_in_avg;
//update buffer head index
adc_buf_head++;
if (adc_buf_head >= LPF_ORDER)
adc_buf_head = 0;
}
//Function called every 1/5KHz
void FIR_cascade_filter(void)
{
int8_t i;
uint8_t circular_head;
int16_t ADC_order_buffer[LPF_ORDER];
int32_t fir_result;
//Update the ouput "DAC" with the last filter calculation to maintain syncronism
update_output(fir_result);
//*************************************** INIT LOW PASS FILTER****************************
//Order the ADC readings
circular_head = adc_buf_head;
for (i = 0; i < LPF_ORDER; i++)
{
ADC_order_buffer[i] = ADC_readings_array[circular_head];
circular_head++;
if (circular_head >= LPF_ORDER)
circular_head = 0;
}
//Reset fir_result variable
fir_result = 0;
//first part of coeffs
for (i = 0; i < LPF_HALF_ORDER; i++)
{
//multiplication and sum of filter
fir_result += ADC_order_buffer[i] * LPF_2000Hz[i];
}
//second part of coeffs
circular_head = LPF_HALF_ORDER;
for (i = (LPF_HALF_ORDER - 1); i >= 0; i--)
{
//multiplication and sum of filter
fir_result += ADC_order_buffer[circular_head] * LPF_2000Hz[i];
circular_head++;
}
//Divide filter result by 2^13
fir_result = fir_result >> FILTER_COEFF_MULT;
//*************************************** END LOW PASS FILTER****************************
//*************************************** INIT BAND PASS FILTER**************************
//order the LPF results
for (i = 0; i < (BPF_ORDER - 1); i++)
{
LPF_results_array[i + 1] = LPF_results_array[i];
}
//add the last calculation
LPF_results_array[0] = (int16_t) fir_result;
fir_result = 0;
//first part of coeffs
for (i = 0; i < BPF_HALF_ORDER; i++)
{
//multiplication and sum of filter
fir_result += LPF_results_array[i] * BPF_100Hz[i];
}
//second part of coeffs
circular_head = BPF_HALF_ORDER;
for (i = (BPF_HALF_ORDER - 1); i >= 0; i--)
{
//multiplication and sum of filter
fir_result += LPF_results_array[circular_head] * BPF_100Hz[i];
circular_head++;
}
//Divide filter result by 2^13
fir_result = fir_result >> FILTER_COEFF_MULT;
//*************************************** END BAND PASS FILTER**************************
}
You can test your filter in PC using C by creating suitable stimulus file or sample stream, and by analyzing its performance by plotting the output samples and some intermediate samples. Of course, you can do quick testing using Excel or OpenOffice Calc.
I will try it with excel...
Thank you for the reply.
Yes I did a 3rd order filter with Fc = 5KHz.
I can sample at 20KHz without aliasing problems.
In that case, why use cascading filters? You can make a bandpass FIR directly.
I will try it with excel...
yuck! no, use Matlab or Octave.
if you not using these tools by now, you not armed to debug DSP algorithms. its non negotiable debugging tools.
But will not I have aliasing?
I need to do sampling at 5KHz, which is the same frequency as my external filter cuts ...
In this case I need to have an external filter to cut at least 2.5KHz.
Am I wrong?
yuck! no, use Matlab or Octave.
if you not using these tools by now, you not armed to debug DSP algorithms. its non negotiable debugging tools.
I do not have any of them installed ...
I will install the Octave.
I used MATLAB for years and even octave, I have not switched to Python, which I like better.
Trampas
www.misfittech.net
This is a mess... 20 kHz, 5 kHz, 2.5 kHz, 1 kHz, 100 Hz. My eyes are rolling in my head when I try to follow this.
Dave_PT, could you PLEASE list the requirements and signals and limitations here?
Perhaps start from scratch? What you have etc.
Thanks (or rather, you should be thankful that someone is following this...)
OK ... from the beginning.
I want to make a FIR filter that has a passband of 100Hz centered at 1KHz (passband 950HZ <-> 1050HZ).
My input signal will be up to about 10KHz.
Before the ADC I put a 3rd order filter with Fc = 5KHz.
As proof that the basic operating principle is right, I can sample the signal at 20KHz and apply a 20-taps FIR LP Filter without problems.
I wanted to apply the filter in the next picture ... but the sampling frequency has to lower at least 5KHz. At this frequency I will have aliasing. So I had the idea to sample at 20KHz, make a LP Filter at 2KHz and then "feed" the BP Filter that would make 5KHz loops.
I understand your setup now.
So why not just do the bandpass filtering directly on your 20 ksps sample stream? Do you have computational limitations?
Yes ... I have and can do all the calculations within the loop time.
But with 20Ksps, I can not run any BP Filter with a 100Hz BW. The minimum with 20Ksps is around 500Hz. My old setup worked that way, but 500Hz is a very large BW.
Hello.
I've done some calculations using Octave, and now I see what's going on.
The problem is when I sample at 5KHz the signal that has passed through the low pass filter.
So, what is your suggestion?
What is the best way for me to have a 100Hz passband using what I have available?
I'd use an IIR filter. These need much less computing power for the same filter.
Thanks for the answer.
I'm going to try an IIR filter. With these filters are fewer calculation operations, but as it uses floats, each operation will take more time...
You don't have to run 2kHz LPF at 20ksps. You can run it at only 5ksps
Yes. That's what I'm doing.
My samples are made at 20KHz, but only at 5KHz I perform LPF followed by BPF ...
Yes, you're right.
But using FIR this does not work ...
Let's try again before I go to test the IIR filters.
1º
Sampling the signal at 20KHz (20ksps).
2º
A timer will change a flag (1/5KHz) so in my main program I can perform the filter.
3º
Calculate the 20tap low pass filter to 2KHz. For this I use the last 20 readings of the ADC (20ksps).
4º
After calculating the LPF, I "feed" the band pass filter so that it can then filter the signal and pass only the 100Hz that I want.
5th
After all the calculations, I wait for the new cycle to update my output. I do this to isolate the delays caused by calculations.
What am I doing wrong in the process?
I think it must be some calculation problem ...
Caution on ISR controlling filter loop. ISR should be very terse
in code, not call other functions (results in lots of stack push),
set a flag, and return to main() to process the filter. Make sure
other ISRs are inactive if at all possible to minimize sampling
jitter on output writes. Same can be said for input sampling de-
pending on how its implemented.
Regards, Dana.
the signal processing theory and specification is confused - in addition to getting sampling/decimation/anti-alliasing correct, many calcs need ADC bits and acceptable noise budget, both analog and for the DSP
5 kHz 3rd order analog low pass is feeble for 20k sample rate - (20/5)**3, only a factor of 64 attenuation for any 20 kHz signal component - 6 bits
sometimes theoretically poor anti-alias works if the signal before filtering has low enough higher frequency content - but you need numbers, what's the signal look like at 200 kHz sample rate? (or the 192k that many PC sound cards can do)
if all you want is to detect the narrow band @ 1 kHz then I'd move the analog anti-alias down to 2 kHz or even incorporate its cutoff (& Q) into the wanted notch filter design
biquad filters are named for the Quadratic s or z domain polynomials in both numerator and denominator - in general that means 6 coefficients and multiplies per each biquad
and fixed point biquads are fine, can use integer multipliers - but often need very long wordlengths for the accumulators
since that's still a lot of ops per biquad you may want several stages of Half Band FIR filtering with decimation
https://www.google.com/search?q=half+band+filter+theory&oq=half+band+filter+theory&aqs=chrome..69i57.2999j0j1&sourceid=chrome&ie=UTF-8https://www.mathworks.com/help/dsp/examples/fir-halfband-filter-design.html
Caution on ISR controlling filter loop. ISR should be very terse
in code, not call other functions (results in lots of stack push),
set a flag, and return to main() to process the filter. Make sure
other ISRs are inactive if at all possible to minimize sampling
jitter on output writes. Same can be said for input sampling de-
pending on how its implemented.
Regards, Dana.
There is only 2 ISRs: 1 for acquisition at 20ksps, 1 to start the filter calculation (1/5KHz).
In both routines there it's just enter, modify and exit.
Caution on ISR controlling filter loop. ISR should be very terse
in code, not call other functions (results in lots of stack push),
set a flag, and return to main() to process the filter. Make sure
other ISRs are inactive if at all possible to minimize sampling
jitter on output writes. Same can be said for input sampling de-
pending on how its implemented.
Regards, Dana.
There is only 2 ISRs: 1 for acquisition at 20ksps, 1 to start the filter calculation (1/5KHz).
In both routines there it's just enter, modify and exit.
I would do the filtering in the 20ksps interrupt. That way you don't waste time to store/recover the data. You need to process each sample anyway sooner or later. An option is to filter in 2 stages where you decimate to 1kHz first and filter that signal using a lower samplerate. BTW IIR doesn't need floating point. 32 bit integers will do just fine but you have to check overflow conditions. 14 bit coefficients help to give some extra headroom.
sorry but this is just wrong - this prescription is decimating without the required filtering - you will get aliasing
1º
Sampling the signal at 20KHz (20ksps).
2º
A timer will change a flag (1/5KHz) so in my main program I can perform the filter.
3º
Calculate the 20tap low pass filter to 2KHz. For this I use the last 20 readings of the ADC (20ksps).
again you need more problem definition info - the spectrum/level of frequency components in the raw signal, the required output resolution to design the ADC sampling and analog antialias filter frequencies and order, and to design the DSP processing
as an example if your actual input signal has the high frequency content rolling fast off enough to work with your current 5 kHz 3rd order filter in front of 20 kHz sampling ADC, then maybe reducing the anaolg filter fc by 4x would let you cut the sample frequency by 2x
then with FIR Half Band filtering and decimation to cut the sample rate to 2.5 kHz in 2 stages may let you save enough processing time to be able to do the 1 kHz bandpass
then with FIR Half Band filtering and decimation to cut the sample rate to 2.5 kHz in 2 stages may let you save enough processing time to be able to do the 1 kHz bandpass
Can you explain this part better?
I'm sure I'm doing a lot of confusion ...