Hi everyone,
I'm trying to implement a realtime lowpass FIR filter using the CMSIS library.
I'm essentially following their example here:
http://www.keil.com/pack/doc/CMSIS/DSP/html/group__FIRLPF.html
The only difference is that instead of running the filter on a block of samples, I want the filter to run sample by sample, (use 1 ADC sample and output 1 DAC sample) so I'm using a blocksize of 1.
So here's my *relevant* code. I have a lot of other code unrelated to the DSP side of things for the ADC and DAC which I know works. However, if you want the full code, I can post that as well.
#include "main.h" #include "stm32f4xx_hal.h" #include "arm_math.h" #define BLOCK_SIZE 1 #define NUM_TAPS 29 uint32_t ADC_Buffer[2]; uint32_t DAC_Buffer; float32_t signal_in = 0; float32_t signal_out = 0; float32_t totalval = 0; uint32_t blockSize = BLOCK_SIZE; arm_fir_instance_f32 S; static float32_t firStateF32[BLOCK_SIZE + NUM_TAPS - 1]; static float32_t firCoeffs[NUM_TAPS] = { -0.0018225230f, -0.0015879294f, +0.0000000000f, +0.0036977508f, +0.0080754303f, +0.0085302217f, -0.0000000000f, -0.0173976984f, -0.0341458607f, -0.0333591565f, +0.0000000000f, +0.0676308395f, +0.1522061835f, +0.2229246956f, +0.2504960933f, +0.2229246956f, +0.1522061835f, +0.0676308395f, +0.0000000000f, -0.0333591565f, -0.0341458607f, -0.0173976984f, -0.0000000000f, +0.0085302217f, +0.0080754303f, +0.0036977508f, +0.0000000000f, -0.0015879294f, -0.0018225230f }; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_TIM2_Init(); MX_ADC1_Init(); MX_ADC2_Init(); MX_DAC_Init(); if(HAL_ADC_Start(&hadc2) != HAL_OK) return 0; HAL_ADCEx_MultiModeStart_DMA(&hadc1, ADC_Buffer, 2); HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, &DAC_Buffer, 1, DAC_ALIGN_12B_R); HAL_ADC_Start_IT(&hadc1); HAL_TIM_Base_Start(&htim2); arm_fir_init_f32(&S, NUM_TAPS, (float32_t *)&firCoeffs[0], (float32_t *)&firStateF32[0], blockSize); while (1) { } } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if(hadc->Instance == ADC1) { ADC_Buffer[0] = ADC_Buffer[0] & 0x00000FFF; signal_in = (float32_t)ADC_Buffer[0]*3/4096; signal_in = signal_in - 0.5f; arm_fir_f32(&S, &signal_in, &signal_out, blockSize); signal_out = signal_out + 0.5f; totalval = signal_out*4096/3; DAC_Buffer = (uint32_t) totalval; //signal_out; } }
The problem is, I always get the same signal_out no matter how signal_in changes. After I step through the arm_fir_f32() function, signal_out is always -1.9764*10^-22.
What's more weird, is that in the watch window, all the firStateF32 values are 0. They never change.
The only thing I'm essentially doing differently from their example, is that I'm using a blocksize of 1.
Any ideas where I'm going wrong?
Thanks, Clint
Looking at the source code it looks like you need a minimum block size of 8. See the following lines in the source code of the arm_fir_f32:
blkCnt = blockSize >> 3; while (blkCnt > 0u) {
To make a continous filter you will need two input buffers and two output buffers. One set for capturing input samples and outputing calculated values while the other buffer set is being processed. Then swap the buffers after the block is calculated to process the new set of samples. The only problem is the latency will be higher with larger blocksizes.
Hi Jim,
That makes sense, and I would agree with you, but...
I ran their example, with BLOCK_SIZE defined as 1, and it passes their snr test at the end.
So that tells me you can run the filter function sample by sample, right?
Thanks for your insight Jim, Clint
Yes, I think your right. Looking at the code a block that does not have a size that is a multiple of 8 is handled later in the code. Since the algorithm can process 8 samples at a time, I think it would be a lot more efficient to have a block size that is a multiple of 8. What is your sample rate? Is the algorithm fast enough to process a sample before the next sample comes in?
The filter works now!
It seems like it was a timing problem as you suggested, as well as a conversion problem.
It's a problem for me if the output of the filter function is negative since I have to pass the DAC function a uint32_t value, so I added an offset of 1.5 to the float output of the filter and then converted that positive sum to the proper integer value to pass to the DAC.
After all this was working, it was taking a bit longer than the sampling time, so I decreased the sampling frequency from 40Khz to 20Khz. Now my effective bandwidth for the filtering application is 10Khz, but that's enough for voiceband applications.
I could probably reduce the algorithm time if I increased my blocksize as you suggested, but I'll look into that later.
Thanks for the help! Clint
So you managed to do sample by sample?
I want to make a simple filter. I want to get a sample by ADC, process and put the result by DAC.
At this point I'm doing the whole process "manually" (doing all the calculations) but wanted to try out the CMSIS libs.
I need help to better understand what you did and what the blocksize relation is in the filter calculation ...
Can you help me?
Thank you.
Hi David, I apologize for replying so late. Do you already have your ADC and DAC set up?
Hello.
Yes, all this setup is ready.
I already have a filter running at 20ksps (with whole process "manually").
So what I did is initialize the filter function, then call it after I get a sample, and make sure the function completes before the next sample.
Hello, I'm working on a similar thing, and I've got your same problem. Can you give me the full code (or just the man + callback function) of your working project? This would really help.
Thank you very much.