# Trying to implement a digital IIR filter why is this happening?

hope everyone is staying safe.

I am trying to implement a digital filter that I made from a continuous transfer function onto a STM32 microcontroller. Using the CMSIS DSP functions found here Filtering Functions

Instead I am getting a PWM looking signal.

The way I obtained the digital filter are as followed:

• Made a low-pass 2nd Order Butter-worth filter with a Fc @ 1kHz using the sallen - key configuration in the continuous domain
• Converted the 2nd Order on matlab using 'c2d' with the tustin method @ 44.410kHz sampling rate
• Converted the digital filter into a difference equation to be implemented

$$Continuous\>Transfer\>Function:\\ H(s) = \frac{3.9401e^{7}}{s^2+8889s+3.94e^7}$$

$$Discrete\>Transfer\>Function: \\H(z) = \frac{0.0045196(z+1)^2}{z^2-1.801z+0.8189}$$

$$Difference\>Equation: \\y(n) = 0.00452_{x(n)}+0.009039_{x(n-1)}+0.00452_{x(n-2)}+1.801_{y(n-1)}-0.8189_{y(n-2)}$$

• B0 = 0.00452
• B1 = 0.009039
• B2 = 0.00452
• A1 = 1.801
• A2 = -0.8189

Pictures of current implementation:

Yellow is the input to the ADC & Green is Output from the DAC Zoomed in on the DAC output Code:

#include "main.h"
#include <stdint.h>
#include "arm_math.h"

void init_Interrupt(void);
void init_Clock(void);
void init_Interrupt(void);
void init_DAC(void);
void init_GPIO_Test(void);
void init_Debug(void);
void init_Timer(void);

char buffer = "ADC Value:     \n\r";

typedef struct PLL{
uint8_t PLLN;
uint8_t PLLR;
uint8_t PLLM;
uint8_t PLLSAI1N;
uint8_t PLLSAI1R;
} PLL;

PLL find_PLL(uint32_t, uint32_t);
PLL CFGR;

uint8_t escape = 0;
uint8_t half_transfer_complete = 0;
uint8_t transfer_complete = 0;
uint32_t PLLN_MAX = 86;
uint32_t PLLSAI1N_MAX = 86;
uint32_t PLLR_MAX = 8;
uint32_t PLLSAI1R_MAX = 8;
uint32_t PLLM_MAX = 8;
uint32_t CPU_Speed = 80000000;
uint16_t ADC_Value; //  Hold 8 Samples

float iir_coeffs = {0.00452, 0.009039, 0.00452, 1.801, -0.8189}; //B0, B1, B2, A1, A2
float iir_mono_state;

void DMA2_Channel3_IRQHandler(void){

if (((DMA2->ISR) & (DMA_ISR_HTIF3)) != 0){
half_transfer_complete = 1;
DMA2->IFCR |= DMA_IFCR_CHTIF3;
} else if (((DMA2->ISR) & (DMA_ISR_TCIF3)) != 0){
transfer_complete = 1;
DMA2->IFCR |= DMA_IFCR_CTCIF3;
}
}

int main(void) {

init_Clock();
//init_Debug();
init_DAC();
init_GPIO_Test();
init_Interrupt();
init_Timer();

while (1) {

if (half_transfer_complete == 1){

GPIOA->BSRR |= GPIO_BSRR_BS0;
TIM6 ->CR1 |= TIM_CR1_CEN;
half_transfer_complete = 0;
}

if (transfer_complete == 1){
GPIOA->BSRR |= GPIO_BSRR_BR0;

transfer_complete = 0;
}
};
}

PLL find_PLL(uint32_t CPU_Speed, uint32_t ADC_Speed) {

PLL settings;

for (int PLLN = 8; PLLN <= PLLN_MAX; PLLN ++){
if (escape == 1){
break;
}
for (int PLLM = 1; PLLM <= PLLM_MAX; PLLM ++){
if (escape == 1){
break;
}
for (int PLLR = 2; PLLR <= PLLR_MAX ; PLLR +=2){
if ((((4000000/PLLM) * PLLN) >= 64000000) & (((4000000/PLLM) * PLLN) <= 344000000)){
if (((4000000/PLLM) >= 4000000) & ((4000000/PLLM) <= 16000000)) {
if (((((4000000/PLLM)*PLLN)/PLLR) >= 8000000) & ((((4000000/PLLM)*PLLN)/PLLR) <= 80000000)){
uint32_t PLL_CALC = (((4000000/PLLM)*PLLN)/PLLR);
if (PLL_CALC == CPU_Speed){
settings.PLLM = PLLM;
settings.PLLR = PLLR;
settings.PLLN = PLLN;
escape = 1;
break;
}
}
}
}

}
}
}

escape = 0;

for (int PLLSAI1N = 8; PLLSAI1N <= PLLSAI1N_MAX; PLLSAI1N ++){
if (escape == 1){
break;
}
for (int PLLSAI1R = 2; PLLSAI1R <= PLLSAI1R_MAX; PLLSAI1R += 2){
if ((((4000000/settings.PLLM) * PLLSAI1N) >= 64000000) & (((4000000/settings.PLLM) * PLLSAI1N) <= 344000000)){
if (((((4000000/settings.PLLM)*PLLSAI1N)/PLLSAI1R) >= 8000000) & ((((4000000/settings.PLLM)*PLLSAI1N)/PLLSAI1R) <= 80000000)){
uint32_t PLLSAI1_CALC = (((4000000/settings.PLLM)*PLLSAI1N)/PLLSAI1R);
settings.PLLSAI1R = PLLSAI1R;
settings.PLLSAI1N = PLLSAI1N;
escape = 1;
break;
}
}

}
}
}
return settings;
}

//Pin - A6

RCC   -> AHB2ENR |= RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_ADCEN;
RCC   -> AHB1ENR |= RCC_AHB1ENR_DMA2EN;

GPIOA -> MODER &= ~GPIO_MODER_MODE7;
GPIOA -> MODER |=  GPIO_MODER_MODE7_Analog; //PIN A6

// 16-bit @ Sampling ~44.410kHZ
//Holding 8 samples at a time
// 16-bit = 2 byte * 8 =  16 bytes
DMA2_Channel3 -> CCR |= (DMA_CCR_PSIZE_16_Bit) |
(DMA_CCR_MSIZE_16_Bit)   |
(DMA_CCR_MINC)           |
(DMA_CCR_CIRC)           |
(DMA_CCR_TCIE)           |
(DMA_CCR_HTIE)           |
(DMA_CCR_PL_Very_High);
DMA2_CSELR    -> CSELR &= ~DMA_CSELR_C3S;
DMA2_Channel3 -> CNDTR |= 0x08;
DMA2_Channel3 -> CCR |= DMA_CCR_EN;

;

;

}

void init_Clock() {

// |----------------------------------- WAIT STATE: 0 -----------------------------------|
if (CPU_Speed <= 16000000) {

FLASH -> ACR &= ~FLASH_ACR_LATENCY_Msk;
FLASH -> ACR |= FLASH_ACR_LATENCY_0WS;

if ((FLASH -> ACR & FLASH_ACR_LATENCY_0WS) != FLASH_ACR_LATENCY_0WS){
//ERROR: System didn't change wait states properly
} else{
//Success
}

// |----------------------------------- WAIT STATE: 1 -----------------------------------|
} else if (CPU_Speed <= 32000000){

FLASH -> ACR &= ~FLASH_ACR_LATENCY_Msk;
FLASH -> ACR |= FLASH_ACR_LATENCY_1WS;

if ((FLASH -> ACR & FLASH_ACR_LATENCY_1WS) != FLASH_ACR_LATENCY_1WS){
//ERROR: System didn't change wait states properly
} else{
//Success
}

// |----------------------------------- WAIT STATE: 2 -----------------------------------|
} else if (CPU_Speed <= 48000000){

FLASH -> ACR &= ~FLASH_ACR_LATENCY_Msk;
FLASH -> ACR |= FLASH_ACR_LATENCY_2WS;

if ((FLASH -> ACR & FLASH_ACR_LATENCY_2WS) != FLASH_ACR_LATENCY_2WS){
//ERROR: System didn't change wait states properly
} else{
//Success
}

// |----------------------------------- WAIT STATE: 3 -----------------------------------|
} else if (CPU_Speed <= 64000000){

FLASH -> ACR &= ~FLASH_ACR_LATENCY_Msk;
FLASH -> ACR |= FLASH_ACR_LATENCY_3WS;

if ((FLASH -> ACR & FLASH_ACR_LATENCY_3WS) != FLASH_ACR_LATENCY_3WS){
//ERROR: System didn't change wait states properly
} else{
//Success
}

// |----------------------------------- WAIT STATE: 4 -----------------------------------|
} else if (CPU_Speed <= 80000000){

FLASH -> ACR &= ~FLASH_ACR_LATENCY_Msk;
FLASH -> ACR |= FLASH_ACR_LATENCY_4WS;

if ((FLASH -> ACR & FLASH_ACR_LATENCY_4WS) != FLASH_ACR_LATENCY_4WS){
//ERROR: System didn't change wait states properly
} else{
//Success
}

} else{
//Error: Clock Speed too high
}

RCC -> CFGR |= RCC_CFGR_SW_PLL;
PWR -> CR1  &= ~PWR_CR1_VOS_Msk;
PWR -> CR1  |= PWR_CR1_VOS_0;
RCC -> CR   |= RCC_CR_MSIRGSEL | RCC_CR_MSIRANGE_6;

// |----------------------------------- PLLCFGR: R -----------------------------------|
if (CFGR.PLLR == 2){
RCC -> PLLCFGR &= ~RCC_PLLCFGR_PLLR_Msk;
RCC -> PLLCFGR |= RCC_PLLCFGR_PLLR_2;
} else if (CFGR.PLLR == 4){
RCC -> PLLCFGR &= ~RCC_PLLCFGR_PLLR_Msk;
RCC -> PLLCFGR |= RCC_PLLCFGR_PLLR_4;
} else if (CFGR.PLLR == 6){
RCC -> PLLCFGR &= ~RCC_PLLCFGR_PLLR_Msk;
RCC -> PLLCFGR |= RCC_PLLCFGR_PLLR_6;
} else if (CFGR.PLLR == 8){
RCC -> PLLCFGR &= ~RCC_PLLCFGR_PLLR_Msk;
RCC -> PLLCFGR |= RCC_PLLCFGR_PLLR_8;
}

// |----------------------------------- PLLCFGR: M -----------------------------------|
if (CFGR.PLLM == 1){
RCC -> PLLCFGR &= ~RCC_PLLCFGR_PLLM_Msk;
} else {
RCC -> PLLCFGR &= ~RCC_PLLCFGR_PLLM_Msk;
RCC -> PLLCFGR |= (CFGR.PLLM-1) << RCC_PLLCFGR_PLLM_Pos;
}

// |----------------------------------- PLLCFGR: N -----------------------------------|
RCC -> PLLCFGR &= ~(RCC_PLLCFGR_PLLN_Msk);
RCC -> PLLCFGR |= ((CFGR.PLLN) << RCC_PLLCFGR_PLLN_Pos) | (RCC_PLLCFGR_PLLREN) | (RCC_PLLCFGR_PLLSRC_MSI);

// |----------------------------------- PLLSAI1CFGR: R -----------------------------------|
if (CFGR.PLLSAI1R == 2){
RCC -> PLLSAI1CFGR &= ~RCC_PLLSAI1CFGR_PLLSAI1R_Msk;
RCC -> PLLSAI1CFGR |= RCC_PLLSAI1CFGR_PLLSAI1R_2;
} else if (CFGR.PLLSAI1R == 4){
RCC -> PLLSAI1CFGR &= ~RCC_PLLSAI1CFGR_PLLSAI1R_Msk;
RCC -> PLLSAI1CFGR |= RCC_PLLSAI1CFGR_PLLSAI1R_4;
} else if (CFGR.PLLSAI1R == 6){
RCC -> PLLSAI1CFGR &= ~RCC_PLLSAI1CFGR_PLLSAI1R_Msk;
RCC -> PLLSAI1CFGR |= RCC_PLLSAI1CFGR_PLLSAI1R_6;
} else if (CFGR.PLLSAI1R == 8){
RCC->PLLSAI1CFGR &= ~RCC_PLLSAI1CFGR_PLLSAI1R_Msk;
RCC->PLLSAI1CFGR |= RCC_PLLSAI1CFGR_PLLSAI1R_8;
}

// |----------------------------------- PLLSAI1CFGR: N -----------------------------------|
RCC -> PLLSAI1CFGR &= ~(RCC_PLLSAI1CFGR_PLLSAI1N_Msk);
RCC -> PLLSAI1CFGR |= RCC_PLLSAI1CFGR_PLLSAI1REN | (CFGR.PLLSAI1N << RCC_PLLSAI1CFGR_PLLSAI1N_Pos);

RCC -> CR |= RCC_CR_PLLON;
while ((RCC->CR & RCC_CR_PLLRDY) == 0)
;
RCC -> CR |= RCC_CR_PLLSAI1ON;
while ((RCC -> CR & RCC_CR_PLLSAI1RDY) == 0)
;
if ((RCC -> CFGR & RCC_CFGR_SWS_PLL) != RCC_CFGR_SWS_PLL ) {
//Error: Clock Didn't switch
}
}

void init_DAC(){

//Pin A3
RCC   -> APB1ENR1 |= RCC_APB1ENR1_DAC1EN;
RCC   -> AHB2ENR  |= RCC_AHB2ENR_GPIOAEN;
GPIOA -> MODER    &= ~GPIO_MODER_MODE4;
GPIOA -> MODER    |= GPIO_MODER_MODE4_Analog;
DAC1  -> CR       |= DAC_CR_EN1;
}

void init_Interrupt(){

NVIC_EnableIRQ(DMA2_Channel3_IRQn);
NVIC_SetPriority(DMA2_Channel3_IRQn,0);
}

void init_GPIO_Test(){

RCC   -> AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
GPIOA -> MODER &= ~GPIO_MODER_MODE0;
GPIOA -> MODER |= GPIO_MODER_MODE0_Gen_Purpose;
}

short counter = 0;

buffer[14-counter] = (adcValue % 10) + '0';
counter++;
}

if (counter == 0){

buffer = ' ';
buffer = ' ';
buffer = ' ';

} else if (counter == 1){
buffer = ' ';
buffer = ' ';
buffer = ' ';

} else if (counter == 2){
buffer = ' ';
buffer = ' ';

} else if (counter == 3){
buffer = ' ';
}

counter = 0;
}

void init_Debug(){

RCC -> APB1ENR1 |= RCC_APB1ENR1_USART2EN;
RCC -> AHB1ENR  |= RCC_AHB1ENR_DMA1EN;
RCC -> AHB2ENR  |= RCC_AHB2ENR_GPIOAEN;
RCC -> CCIPR    |= RCC_CCIPR_USART2SEL_System_Clock;

GPIOA -> MODER  &= ~GPIO_MODER_MODE2;
GPIOA -> MODER  |= GPIO_MODER_MODE2_Alt_Function;
GPIOA -> AFR |= GPIO_AFRL_AFSEL2_USART2;

DMA1_Channel7 -> CCR  |= DMA_CCR_PL_High     |
DMA_CCR_MSIZE_8_Bit |
DMA_CCR_PSIZE_8_Bit |
DMA_CCR_MINC        |
DMA_CCR_CIRC        |
DMA_CCR_DIR;
DMA1_CSELR    -> CSELR |= DMA_CSELR_C7S_USART2;
DMA1_Channel7 -> CNDTR  = 0x14; // 20
DMA1_Channel7 -> CMAR   = (uint32_t)buffer;
DMA1_Channel7 -> CPAR   = (uint32_t)&USART2 -> TDR;
DMA1_Channel7 -> CCR  |= DMA_CCR_EN;

USART2 -> CR1 &= ~USART_CR1_M1 | ~USART_CR1_OVER16;
USART2 -> CR1 |= USART_CR1_TE;
USART2 -> CR3 |= USART_CR3_DMAT;
USART2 -> BRR = 0x208D;
USART2 -> CR1 |= USART_CR1_UE;
}

void init_Timer(){

RCC -> AHB1ENR  |= RCC_AHB1ENR_DMA1EN;
RCC -> APB1ENR1 |= RCC_APB1ENR1_TIM6EN;

DMA1_Channel3 -> CCR |= DMA_CCR_PL_Very_High |
DMA_CCR_MSIZE_16_Bit |
DMA_CCR_PSIZE_16_Bit |
DMA_CCR_MINC         |
DMA_CCR_CIRC         |
DMA_CCR_DIR;
DMA1_Channel3 -> CNDTR  = 0x08;
DMA1_Channel3 -> CPAR   = (uint32_t)&DAC1->DHR12R1;
DMA1_CSELR    -> CSELR |= DMA_CSELR_C3S_TIM_6_UP;
DMA1_Channel3 -> CCR   |= DMA_CCR_EN;

TIM6 -> DIER |= TIM_DIER_UDE;
TIM6 -> ARR   = 0x708;
TIM6 -> PSC   = 0x0;

}


UPDATE 1:

Changed :

Now this is the output:  Now, I am not sure why is so jumpy like that.

UPDATE 2: Based On Hilmar suggestions:

• "By fixing this you just moved the problem to a different spot. You will get integers from your DMA so somewhere you need to do INT -> FLOAT -> INT conversions."

All it did was reduce the amplitude of the output signal. I guess this make sense as it truncates from float -> int • "Confirm you can write a "output equals input" passthrough program. Make sure there are no drop outs or framing issues and that the HW is properly initialized & configured. This is also usful for benchmarking you baseline CPU load."

Not entirely sure if I follow this through correctly, however all I did was Data into the ADC, same data out from DAC. A simple passthrough. • "Your block size is very small so your interrupt rate is very high. Depedning on how much interrupt overhead you have, the processor may not be able to keep up"

By the looks of it, just makes it more unstable with a block size now of 4 -> 500 and holding samples from 8 -> 1000 • "A pointer cast is not the same as a real type conversion. Ints are represented as two's complement and floats per IEEE 754. The same bit pattern mean different things."

I just took what he said as testing. Not sure if this is correct as he mentioned casting wasnt the proper way from going INT ->FLOAT and vice versa, however casting were used.

while (1) {

if (half_transfer_complete == 1){

for (int i = 0; i < 5; i++){
}

GPIOA->BSRR |= GPIO_BSRR_BS0;

for (int i = 0; i < 5; i++){
}
TIM6 ->CR1 |= TIM_CR1_CEN;
half_transfer_complete = 0;
}

if (transfer_complete == 1){

for (int i = 5; i < 10; i++){
}
GPIOA->BSRR |= GPIO_BSRR_BR0;
for (int i = 5; i < 10; i++){
}
transfer_complete = 0;
}
}
} UPDATE 3: Measure the speed of the IIR Function. If you saw my previous answer, i was mistaken. The actual time it takes the function to execute is 1.5uS @ 80MHz and each callback function last for 45uS @ 80MHz.

I believe the problem is the timing of which everything starts, but still have no idea how to fix this

• I guess the accuracy of you filter coefficients and calculation is not enough, Maybe try double
– Mike
Sep 15, 2020 at 4:42
• Hmmm just caused a warning, as the CMSIS functions are expecting a float. Question then I am getting a warning saying "Description Path Resource Location Type passing argument 2 of 'arm_biquad_cascade_df1_f32' from incompatible pointer type [-Wincompatible-pointer-types] /project/Core/Src main.c line 82 C/C++ Problem"
– Leoc
Sep 15, 2020 at 4:44
• @Mike check updated post!
– Leoc
Sep 15, 2020 at 5:00
• I don't know that the STM FPU supports doubles. Sep 17, 2020 at 17:50
• Ahaha amazing little chip
– Leoc
Sep 17, 2020 at 17:51

1. It looks you already found the first bug: https://arm-software.github.io/CMSIS_5/DSP/html/arm__biquad__cascade__df1__f32_8c.html the function needs float arrays not int arrays
2. By fixing this you just moved the problem to a different spot. You will get integers from your DMA so somewhere you need to do INT -> FLOAT -> INT conversions.
3. Your block size is very small so your interrupt rate is very high. Depedning on how much interrupt overhead you have, the processor may not be able to keep up

In general it's useful to debug this in seperate steps.

1. Confirm you can write a "output equals input" passthrough program. Make sure there are no drop outs or framing issues and that the HW is properly initialized & configured. This is also usful for benchmarking you baseline CPU load.
2. Do something very simple and well undestood. Like "scale by half". Do this first in "native" ADC and DAC data formats and then in the data type you want to do your actual processing in
3. Now insert the desired processing. Verify with a few cases where the output is known. If the actual processing is farily complicated, verify the code of the processing function FIRST in an off-line test rig with known test vectors and result vectors before a dropping it into a real time application. Measure your CPU load.
• I dont really follow your number 2. convert from INT -> FLOAT -> INT. Isnt that already implemented? ADC DMA takes the ADC peripheral and stores it in a (uint32_t) cast into the float ADC_Value array. I would say thats INT -> FLOAT. Then one the half complete callback is called the timer DMA triggers and then takes float ADC_Value_Output and cast it into a (uint32_t) to be shoved into the DAC
– Leoc
Sep 15, 2020 at 14:59
– Leoc
Sep 16, 2020 at 1:30
• A pointer cast is not the same as a real type conversion. Ints are represented as two's complement and floats per IEEE 754. The same bit pattern mean different things. A pointer cast just tells the compiler what data type to interpret a bit pattern at a specfic address but it actually doesn't change the bits. You need something like valFloat = ((float)valInt)/scaleFloat; and valInt = (int) (scaleFloat*floatVal + 0.5)); where adding 0.5 takes care of proper rounding. Sep 16, 2020 at 5:46
• Alright,Ill try this out. Thank you and report back with any news
– Leoc
Sep 16, 2020 at 15:26
• Okay updated main post
– Leoc
Sep 16, 2020 at 15:56

Okay, so I actually and finally figured it out.

The issue was the DMA from the ADC sending data out as an INT and the DSP function requires a float then the timer DMA wanted an INT to be sent back out

The way I figured it out was

1. Double check what Hilmar said at the post above.
2. Wrote a simple 'for loop' that converted INT -> FLOAT and FLOAT -> INT

for (int i = 0; i < 2; i++) { ADC_Value_f[i] = (float)ADC_Value[i]; }
DSP FUNCTION

for (int i = 2; i < 4; i++) { ADC_Value_Output[i] = (int)ADC_Value_Output_f[i]; }

and it worked!