This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

ARM CMSIS IIR Q15 filter problem

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
    


    ?

    #define F2Q14(x)  ((q15_t)((float32_t)x * 16384UL))
    


    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))
    
    # 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 !
    +--------------+