What is wrong with my biquad filter?

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

What is wrong with my biquad filter?

Jump to solution
2,788 Views
abdullahkahrama
Contributor IV

Hello,

I am trying to implement "Biquad Cascade IIR Low Pass Filter Using Direct Form I Structure" using ARM CMSIS-DSP library on FRDM-KL25Z board. The filter's properties can be seen in the first screenshot below.

What I am doing is sampling the ADC, converting it to Q15 format, storing it, filtering, converting back to decimal and playing it back using PWM (with TPM module) and filtering the PWM with a capacitor and a resistor to have the analog output.

I am giving a sinusoidal signal with an offset of VCC/2 to the ADC input. When I store it and play back what I store without any modification, without any filtering and such, I get an almost perfect mach between input and output. I can also do some small modifications like removing offset and multiplying the signal amplitude. You can see the results of these operations in the oscilloscope shots.

When I apply the filtering function arm_biquad_cascade_df1_q15( ), I get strange results with different coefficient settings, but nothing near what I like. I am using Eli Hughes' wonderful tool for calculating the coefficients.

What am I doing wrong?

filter.png

CMSIS.png

double.png

remove_offset.png

/* ###################################################################

**     Filename    : main.c

**     Project     : CMSIS_example

**     Processor   : MKL25Z128VLK4

**     Version     : Driver 01.01

**     Compiler    : GNU C Compiler

**     Date/Time   : 2015-02-07, 14:30, # CodeGen: 0

**     Abstract    :

**         Main module.

**         This module contains user's application code.

**     Settings    :

**     Contents    :

**         No public methods

**

** ###################################################################*/

/*!

** @file main.c

** @version 01.01

** @brief

**         Main module.

**         This module contains user's application code.

*/    

/*!

**  @addtogroup main_module main module documentation

**  @{

*/    

/* MODULE main */

/* Including needed modules to compile this module/procedure */

#include "Cpu.h"

#include "Events.h"

#include "ADC0.h"

#include "TPM2.h"

#include "PIT.h"

#include "PTA.h"

#include "TPM1.h"

/* Including shared modules, which are used for whole project */

#include "PE_Types.h"

#include "PE_Error.h"

#include "PE_Const.h"

#include "IO_Map.h"

/* User includes (#include below this line is not maintained by Processor Expert) */

void main_loop(void);

/*lint -save  -e970 Disable MISRA rule (6.3) checking. */

int main(void)

/*lint -restore Enable MISRA rule (6.3) checking. */

{

  /* Write your local variable definition here */

  /*** Processor Expert internal initialization. DON'T REMOVE THIS CODE!!! ***/

  PE_low_level_init();

  /*** End of Processor Expert internal initialization.                    ***/

  /* Write your code here */

  /* For example: for(;;) { } */

  main_loop();

  /*** Don't write any code pass this line, or it will be deleted during code generation. ***/

  /*** RTOS startup code. Macro PEX_RTOS_START is defined by the RTOS component. DON'T MODIFY THIS CODE!!! ***/

  #ifdef PEX_RTOS_START

    PEX_RTOS_START();                  /* Startup of the selected RTOS. Macro is defined by the RTOS component. */

  #endif

  /*** End of RTOS startup code.  ***/

  /*** Processor Expert end of main routine. DON'T MODIFY THIS CODE!!! ***/

  for(;;){}

  /*** Processor Expert end of main routine. DON'T WRITE CODE BELOW!!! ***/

} /*** End of main routine. DO NOT MODIFY THIS TEXT!!! ***/

/* END main */

/*!

** @}

*/

/*

** ###################################################################

**

**     This file was created by Processor Expert 10.3 [05.09]

**     for the Freescale Kinetis series of microcontrollers.

**

** ###################################################################

*/

/*

* main_loop.c

*

*  Created on: Feb 7, 2015

*      Author: Abdullah

*/

#include "Cpu.h"

#include "Events.h"

#include "ADC0.h"

#include "TPM2.h"

/* Including shared modules, which are used for whole project */

#include "PE_Types.h"

#include "PE_Error.h"

#include "PE_Const.h"

#include "IO_Map.h"

#include "arm_math.h"

#define FILTER_NUM_STAGES    1

#define FILTER_BLOCK_SIZE    32

uint8_t FIR_src_index = 0;

uint8_t enable_output = 0;

arm_biquad_casd_df1_inst_q15 filter_struct;

q15_t FIR_src[32] =

{ 0 };

q15_t FIR_src_buf[32] =

{ 0 };

q15_t FIR_dest[32] =

{ 0 };

q15_t FIR_coefficients[FILTER_NUM_STAGES * 6] =

{ 797, 0, 1594, 797, 28007, 1574 };

q15_t FIR_state[FILTER_NUM_STAGES * 4] =

{ 0 };

void main_loop(void)

{

    static uint8_t ctr = 0;

    filter_struct.numStages = FILTER_NUM_STAGES;

    filter_struct.pState = FIR_state;

    filter_struct.pCoeffs = FIR_coefficients;

    filter_struct.postShift = 0;

    arm_biquad_cascade_df1_init_q15(&filter_struct, filter_struct.numStages,

            filter_struct.pCoeffs, filter_struct.pState,

            filter_struct.postShift);

    while (1)

    {

        if ((FIR_src_index == 32) && (enable_output == 0))

        {

            for (ctr = 0; ctr < 32; ctr--)

            {

                FIR_src_buf[ctr] = FIR_src[ctr];

            }

            ADC0_SC1A |= ADC_SC1_AIEN_MASK;

            PIT_TCTRL0 |= ( PIT_TCTRL_TIE_MASK | PIT_TCTRL_TEN_MASK);

            FIR_src_index = 0;

            enable_output = 1;

            arm_biquad_cascade_df1_q15(&filter_struct, FIR_src_buf, FIR_dest,

                    FILTER_BLOCK_SIZE);

        }

    }

}

/* ###################################################################

**     Filename    : Events.c

**     Project     : CMSIS_example

**     Processor   : MKL25Z128VLK4

**     Component   : Events

**     Version     : Driver 01.00

**     Compiler    : GNU C Compiler

**     Date/Time   : 2015-02-07, 14:30, # CodeGen: 0

**     Abstract    :

**         This is user's event module.

**         Put your event handler code here.

**     Settings    :

**     Contents    :

**         Cpu_OnNMIINT - void Cpu_OnNMIINT(void);

**

** ###################################################################*/

/*!

** @file Events.c

** @version 01.00

** @brief

**         This is user's event module.

**         Put your event handler code here.

*/

/*!

**  @addtogroup Events_module Events module documentation

**  @{

*/

/* MODULE Events */

#include "Cpu.h"

#include "Events.h"

#include "arm_math.h"

#ifdef __cplusplus

extern "C"

{

#endif

/* User includes (#include below this line is not maintained by Processor Expert) */

extern uint8_t FIR_src_index;

extern uint8_t enable_output;

extern q15_t FIR_src[32];

extern q15_t FIR_dest[32];

PE_ISR(ADC0_Interrupt)

{

    static uint8_t dummy;

    // GPIOA_PTOR = (1<<2);

    // Normalize 0 <-> 255 to 65536 <-> 32768 (or in Q15: -0.99999 <-> 0.9999)

    dummy = ADC0_RA ;

    if (dummy > 126)

    {

        dummy -= 127;

        FIR_src[FIR_src_index] = (q15_t) (((int32_t) (dummy)) << 8);

    }

    else

    {

        dummy = 127 - dummy;

        FIR_src[FIR_src_index] = (q15_t) (((int32_t) (dummy)) << 8);

        FIR_src[FIR_src_index] += 0x8000;

    }

    FIR_src_index++;

    if (FIR_src_index >= 32)

    {

        ADC0_SC1A &= ~(ADC_SC1_AIEN_MASK);

        dummy = ADC0_RA;

        FIR_src_index = 32;

    }

}

PE_ISR(PIT_Interrupt)

{

    static uint8_t block_index = 0;

    static uint16_t dummy = 0;

    // GPIOA_PTOR = (1<<2);

    if (enable_output == 1)

    {

        // To modify the input signal, bypass the filter:

        //dummy = (uint16_t)(FIR_src[block_index]);

        // To use the filter, use filter output:

        dummy = (uint16_t)(FIR_dest[block_index]);

        if (dummy >= 0x8000)

        {

            dummy -= 0x8000;

            dummy >>= 8;

            dummy = (uint8_t) (127 - dummy);

        }

        else

        {

            dummy = (uint8_t) (dummy >> 8);

            dummy += 127;

        }

        // Uncomment below to double input signal amplitude.

        //dummy = dummy * 2 - 127;

        if (dummy > 255)

            dummy = 255;

        while (dummy != TPM1_C0V )TPM1_C0V = dummy;

        block_index++;

        if (block_index == 32)

        {

            block_index = 0;

            enable_output = 0;

            PIT_TCTRL0 &= (PIT_TCTRL_TIE_MASK);

        }

    }

    PIT_TFLG0 |= PIT_TFLG_TIF_MASK;

}

/* END Events */

#ifdef __cplusplus

} /* extern "C" */

#endif

/*!

** @}

*/

/*

** ###################################################################

**

**     This file was created by Processor Expert 10.3 [05.09]

**     for the Freescale Kinetis series of microcontrollers.

**

** ###################################################################

*/

Labels (1)
Tags (3)
1 Solution
1,423 Views
eli_hughes
Contributor V

Is the capture with the square wave an output of the filter?  If so,   it looks like there is saturation , etc going on.   

1.)

I would try running the process with trivial coefficients.   Save b[0] = 1.0 and everything else zero.    This should pass you signal from input to output.   IF that doesn't work,  then your data is not properly formatted into Q15.

You could also try b[0] = 0.5 to see your signal get cut in half.

View solution in original post

2 Replies
1,424 Views
eli_hughes
Contributor V

Is the capture with the square wave an output of the filter?  If so,   it looks like there is saturation , etc going on.   

1.)

I would try running the process with trivial coefficients.   Save b[0] = 1.0 and everything else zero.    This should pass you signal from input to output.   IF that doesn't work,  then your data is not properly formatted into Q15.

You could also try b[0] = 0.5 to see your signal get cut in half.

1,423 Views
abdullahkahrama
Contributor IV

Thanks Eli!

You are right, ADC readings are not properly passed to "arm_biquad_cascade_df1_q15( )" function.

The problem was with the following code:

for (ctr = 0; ctr < 32; ctr--)           

{

   FIR_src_buf[ctr] = FIR_src[ctr];            

}

I should have increment the counter in the for loop instead of decrementing it. I can now get the input signal on the output of the filter if I set b[0] to 1 (or 32767 in Q15 representation).

Also, when converting ADC to Q15, instead of adding an offset, I have done it as follows:

PE_ISR(ADC0_Interrupt)

{

    static uint16_t dummy;

    // Normalize 0 <-> 255 to 0 <-> 32767 (or in Q15: 0 <-> 0.9999)

    dummy = ADC0_RA ;

    FIR_src[FIR_src_index] = (q15_t) (dummy<<7);

    FIR_src_index++;

    if (FIR_src_index >= FILTER_BLOCK_SIZE)

    {

        ADC0_SC1A &= ~(ADC_SC1_AIEN_MASK);

        dummy = ADC0_RA;

        FIR_src_index = FILTER_BLOCK_SIZE;

    }

}

Recovering it is as follows:

PE_ISR(PIT_Interrupt)

{

    static uint8_t block_index = 0;

    static uint16_t dummy = 0;

    if (enable_output == 1)

    {

        dummy = (uint16_t) (FIR_dest[block_index]);

        dummy >>= 7;

        if (dummy > 255)

            dummy = 255;

        while (dummy != TPM1_C0V )TPM1_C0V = dummy;

        block_index++;

        if (block_index == FILTER_BLOCK_SIZE)

        {

            block_index = 0;

            enable_output = 0;

            PIT_TCTRL0 &= (PIT_TCTRL_TIE_MASK);

        }

    }

    PIT_TFLG0 |= PIT_TFLG_TIF_MASK;

}

0 Kudos