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
View all questions in Keil forum