We are running a survey to help us improve the experience for all of our members. If you see the survey appear, please take the time to tell us about your experience if you can.
Hi,
I am trying to implement IIR filter using CMSIS library and "arm_biquad_cascade_df1_q15" function.
First of all I already made a design with "arm_biquad_cascade_df2T_f32" function but for my new project I don't have a MCU with FPU so I need to use Q15 format(I am using LPC4074).
C code:
first I init my filter
arm_biquad_cascade_df1_init_q15(S, numStages, pCoeffs, pState, postShift);
in my first test I tried a very simple filter Using Matlab "filterbuilder" tool - Highpass FS = 64000Hz Fstop = 60Hz Fpass = 500Hz attenuation = 10dB ripple = 1dB
filter type = IIR Elliptic Direct Form I SOS Data type = Fixed point
With this design Matlab coef are : b0: 0.9881 b1: -0.9881 b2: 0 a0: 1.0000 a1: -0.9762 a2: 0
Transforming them into Q15 format (* 32768): b0: 32378 b1: -32378 b2: 0 a0: 32768 a1: -31988 a2: 0
numStages = 1 Postshift = 0
pState is q15_t array with numstages*4 dimension
pCoef is my coefiscient array:
q15_t pCoeffs[numStages*6] = { 32378, //b0 0, //facilities use of 16-bit SIMD instructions on the Cortex-M4 -32378, //b1 0, //b2 31988, //a1 0 //a2 };
As mentioned in CMSIS documentation, depending on the tool equation for filter coefiscients "a" coef must be negated (I noticed with Matlab I have to negate them)
Then I call my filter:
arm_biquad_cascade_df1_q15 (S, sample, result, blocksize);
where result and sample are q15_t array (sample are my ADC samples .. ^^') and blocksize is set to 8
with THIS filter everything works fine .. I put a sin wave with magnitude of 200 and an offset of 2048 my RMS result after filtering is 141 (as expected) and If I put a constant, the RMS result is 0
Then I tried to change my ripple, I set 0.1 instead of 1
Matlab coef: (b0 to a2) 0.97552490234375 -1.95086669921875 0.97552490234375 1 -1.971923828125 0.97265625
Q15 coef (* 32768 / 2 to scale my coef between 1 and -1 as mentionned in CMSIS documentation) 15983 -31963 15983 16384 -32308 15936
My coef array:
q15_t pCoeffs[numStages*6] = { 15983, 0, -31963 , 15983, 32308, -15936 };
and postshift = 1
With these coef. that doesn't work anymore For example, if I put a constant in my samples (all set to 200) I got a final RMS value of 1277 (that means that my filter results are not null, with a high pass filter, all my filter result should be null and my RMS should be null too)
I noticed that each time b2 and a2 are not equal to 0, my result is wrong When they are equal to 0 (even for higher order filter) that works.
What did I miss ?
Regards
a0: 1.000 Transforming them into Q15 format (* 32768): a0: 32768 Just in case: I hope you're aware that 32768 is out-of-range for a q15_t. If you actually put that value of 32768 into source code, the code will see -32768 instead. I won't even begin to analyze what consequences that may have...
Thank for your reply. Yes, You are right. I did not notice that because a0 is never used in the library =) I just copy/paste from Matlab The pCoeffs array contains coefficient I actually use
I always use quantized coefficient so I am sure this case won't happen.
Ok, I think that it is working .. I don't really know why but ..
Here is my final solution.
Getting ADC sample (12 bits ADC) and convert them into Q15 (-1 1):
float tmp; tmp = (((float)getADC())/2048)-1; arm_float_to_q15(&tmp, &ADCSample[index++], 1);
When I have got BLOCKSIZE sample I filter them
arm_biquad_cascade_df1_q15 (S, sampleIn, resultFilter, blocksize);
Then I convert the final result back to initial range (-2048 2047) and apply filter gain.
arm_q15_to_float(resultFilter, finalValues, blocksize); for(i = 0 ; i < blocksize ; ++i){ finalValues[i] *= (2048 * FILTERGAIN); }
Then I can compute my RMS
For coefficients, I finally use "fdatool" from Matlab, using quantized coefficient and macro found in Keil AN 221
#define F2Q14(x) ((q15_t)((float32_t)x * 16384UL))
I directly use floating quantized coefficient (without any conversion) so the coefficients array looks like
q15_t _pCoeffs[numStages*6] = { F2Q14( 1 ), /* section 1 B0 */ F2Q14(0), F2Q14(0.848876953125), /* section 1 B1 */ F2Q14(1), /* section 1 B2 */ F2Q14(1.9149169921875 ), /* section 1 -A1 */ F2Q14( - 0.92364501953125 ), /* section 1 -A2 */ F2Q14(1), /* section 2 B0 */ F2Q14( 0.0), F2Q14(-1.9989013671875 ), /* section 2 B1 */ F2Q14(1), /* section 2 B2 */ F2Q14(0.78826904296875 ), /* section 2 -A1 */ F2Q14(- 0.4776611328125 ) /* section 2 -A2 */ };
POSTSHIFT = 1
It works fine for a second order bandpass filter. Next I am going to try with my final filter (24th order) and I will confirm if everything works fine.
#tmp = (((float)getADC())/2048)-1; arm_float_to_q15(&tmp, &ADCSample[index++], 1);
That looks rather wasteful. Why not just
ADCSample[index++] = (getADC() - 2048) << 4
?
Hmmmm... but isn't that the wrong macro? That looks like it translates to Q14 (but stored in a q15_t), rather than an actual Q15 representation.
Just because I copy/paste from keil example. Do not have time to "clean"/optimise code at the moment. Absolutely need a working example first "/ (but of course your implementation seems much much better)
Q14 because my coefficient are higher than 1 (between -2 2) so I need to rescale them between -1 and 1
"That looks rather wasteful."
Yes, the ADC value is already an integer of the proper format, but with fewer number of fractional bits and with the bits not shifted to the correct position.
The ADC gives a value 0..4095, i.e. 4096-1. The value 4096 is what should be represented as "1.0" if the ADC reads just unsigned data but the ADC can never reach "1.0".
If the ADC value is intended to represent a +/- signal, then the -2048 is needed to adjust the unsigned value from the ADC register into a signed value -2048 .. +2047 which should then be scaled.
Yep ! That works !
Here is how I proceed:
C code
Scale the ADC (your implementation gives the same result as KEIL AN implementation but yours is really better for time and code optimisation, thank you :) ) (ADC samples must be scalled, I can't get a good result without scaling them)
sample64k[index++] = (getADC() - 2048) << 4;
Then I can apply my first filter (bandpass 24th order @64ksps) using CMSIS library
arm_biquad_cascade_df1_q15(S64k, sample64k, result64k, blocksize);
Downsampling data, apply my second filter (bandpass 5th order @8ksps) using CMSIS library
arm_biquad_cascade_df1_q15(S8k, sample8k, result8k, blocksize);
Then I get data back, rescale them to fit initial range and I apply filters gain
for(i = 0 ; i < blocksize ; ++i){ finalResult[i] = ((float)(result8k[i] >> 4)) * GAIN64K * GAIN8K; }
For filters coefficient I finally use 'filterbuilder' from Matlab
using direct form 1 Fixed point data Product length 32bits - 2 numerator / 30 denominator Accumulator length 64bits - 34 numerator / 30 denominator (I have to specify accum and product length to be the same as CMSIS library, without this modification filters do not work for higher order, this was my major mistake in IIR filter implementation)
This implementation works perfectly (I had better results with floating point filters implementation but unable to use them in this application "/)
Thank you for your help.
It seems to be italic - and not monospaced?!
Is it to do with the '#' at the start...?
define F2Q14(x) ((q15_t)((float32_t)x * 16384UL))
# define F2Q14(x) ((q15_t)((float32_t)x * 16384UL))
It seems to be italic - and not monospaced?! It was actually both, to indicate that it was a citation of code. Looks like the engine doesn't really handle that combination, because it if it, the following would line up:
+--------------+ ! some text ! ! itatlic text ! +--------------+