cancel
Showing results for 
Search instead for 
Did you mean: 

How to perform the FFT of a signal using the STM32F7 Discovery board

ConfusedContrarian
Associate III

Hi, apologies for the slightly long post, I'm hoping someone could hopefully point me in the right direction here. I'm trying to perform the FFT of a 100Khz signal using the STM32 Discovery board but I'm relatively new to the STM32 DSP in general. I've read up on a few things over the past few weeks and I've been trying to get a basic implementation working. Additionally, I've been following the basic working example here as well and adapted it to the F7 board. My main issue seems to fall into two parts:

  • Implementing the delay for my given Frequency
  • And Using DMA to fill the buffer with no empty values in the buffer.

My main code looks like this:

#include "main.h"
#include "stm32f7xx_hal.h"
#include <math.h>
#include "arm_math.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
 
#define FFT_INVERSE_FLAG        ((uint8_t)0)
#define FFT_Normal_OUTPUT_FLAG  ((uint8_t)1)
#define SAMPLES						2048 			
#define FFT_Length   1024
 
uint16_t uhADCxConvertedValue;
 
uint16_t index_fill_adc_buffer = 0;
float32_t FFT_Input_f32[FFT_Length*2];
float32_t FFT_Output_f32[FFT_Length];
 
uint16_t index_input_buffer = 0;
float32_t FFT_Input_f32[FFT_Length*2];
float32_t FFT_Output_f32[FFT_Length];
 
ADC_HandleTypeDef hadc1;
TIM_HandleTypeDef htim1;
 
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_ADC1_Init(void);
static void MX_TIM1_Init(void);
 
 
int main(void)
{
     SCB_EnableICache();
   SCB_EnableDCache();
 
  /* MCU Configuration----------------------------------------------------------*/
 
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
 
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  MX_TIM1_Init();
 
  HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&uhADCxConvertedValue, 1);
				 
  while (1)
  {
			
	//  the ARM CFFT Module Struct
	 arm_cfft_radix4_instance_f32 FFT_struct;
 
 
	// Fill FFT input buffer with the required data from ADCConvertedValue
	for(index_input_buffer = 0; index_input_buffer < FFT_Length * 2; index_input_buffer+=2)
	{
		HAL_TIM_Base_Start(&htim1);		// Call Delay
		
		FFT_Input_f32[(uint16_t) index_input_buffer] = (float32_t)uhADCxConvertedValue / (float32_t) 4096.0;
		
		// Imaginary Part 
		FFT_Input_f32[(uint16_t)(index_input_buffer + 1)] = 0;		
	}
	
		// Initialise CFFT MOdule, set intFlag = 0, and doBitReverse = 1 
	// Watch Eli Hughes ARM CMSIS DSP video for explanation
	arm_cfft_radix4_init_f32(&FFT_struct, FFT_Length, FFT_INVERSE_FLAG, FFT_Normal_OUTPUT_FLAG);
	
		// Then process data through the CFFT/CIFFT Module
	arm_cfft_radix4_f32(&FFT_struct, FFT_Input_f32);
	
	/* Process the data through the Complex Magniture Module for calculating the magnitude at each bin */
	arm_cmplx_mag_f32(FFT_Input_f32, FFT_Output_f32, FFT_Length);
	
      // Remove DC Offset
	FFT_Output_f32[0] = 0;
	
	// Calculate Max value and return the value:
	arm_max_f32(FFT_Output_f32, FFT_Length, &maxValue, &maxIndex);
			
  }
 
}

My ADC is set to 620 Ksps and is initialised like this:

static void MX_ADC1_Init(void)
{
 
  ADC_ChannelConfTypeDef sConfig;
 
    /**Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) 
    */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T1_TRGO;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DMAContinuousRequests = ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
 
    /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. 
    */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_56CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
 
}

And I belive the Prescaler and Period values for timer are correct as well for a 10us delay given that Timer 1 is connected to the ABP1 timer which runs at 108MHz according to CubeMX

static void MX_TIM1_Init(void)
{
 
  TIM_ClockConfigTypeDef sClockSourceConfig;
  TIM_MasterConfigTypeDef sMasterConfig;
 
  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 0;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 1079;  // APB1 clock is HCLK/2 for 100 Khz signal. 
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
 
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
 
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_UPDATE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
 
}

My DMA is also set to circular mode with High priority and FIFO disabled. I find that the buffer I'm using to store my ADC values is missing values from the adc for some of the elements in the array for the real part. My maxIndex value seems to be random and jumps all around the place. Modifying the DSPDEMO to use a signal from the ADCs as an input doesn't seem to help much either as my max index value is always shown to be in the 360 - 380thth element of a 1024 bin FFT output. which works out to be around 35 - 40 KHz. 

I'm probably not doing something right with the timer. I would appreciate any help and if someone could point me in the right direction

8 REPLIES 8
cameronf
Senior

I would verify that your timer is in-fact coordinating the reading of your sample buffer with the actual sample rate (I'm assuming that's what it's doing, let me know if I misinterpreted). To do that you could time your for loop and verify it takes ~20ms to copy your samples into your fft buffer. If it's mis-timed and you're combining data from 2 different sample runs, the FFT result will probably get screwed up. Another option where you wouldn't have to keep this coordinated is to do a loop where in each loop iteration you do one DMA run in normal mode instead of circular, then process that data, and you don't have to worry about where your DMA is currently writing to.

If you care only about magnitude and don't need any phase info, the ARM DSP library also has functions for real ffts instead of complex ffts which could simplify things.

S.Ma
Principal

make the timer trigger an output channel gpio, then wire it to the ADC trigger input which will give you a signal to monitor or trigger for the oscilloscope. Make sure the trigger period is longer than the ADC conversion time...

Next, make the cyclical buffer bigger than what you need, more than double, so when you get the half transfer (half full) buffer, you have the time it takes to fill this half to perform the FFT before it's overwritten. toggle some GPIO to check how long the FFT takes (if doing continuously and not using a time sampling window).

Thanks for the reply, I've stripped down to basics to confirm my timer works. I'm using ADC_ConvtCallback to toggle a GPIO pin. and as you can see it roughly takes 20ms.0690X00000895hUQAQ.png One thing I do notice is, the buffer is filled when using DC values (connected a DC power supply) but when I connect a signal through a frequncy generator this isn't the case

AvaTar
Lead

I did similar things on the F4, based on the SPL.

I used a DMA TC interrupt, where I copied the ADC input data (of length FFT_Length) to a second buffer where I did the actual transformation. You just need to be ahead of DMA, and copy the values before they get overwritten. Data type conversion (uint12 -> float) was done on the fly.

And yes, do a check if you get the FFT done in time - I'm having my doubts.

You can use a real FFT instead complex, as suggested, or reduce the FFT length. Turning on full optimization (-O3) for the FFT code worked fine for me (then), and reduced execution times to about one quarter, compared to non-optimized code.

Thanks for the advice. I've been busy with other parts of my project so I've been unable to work on this as often as I'd like. I've been doing some debugging and I can confirm the following:

  • The Timer triggers at the given period frequency which is good news.
  • The ADC also carries out a conversion and the length is in line with what I expect which is also good news.

I now need to work on coying the data to another buffer before perfoming an FFT. I'm currently using a CFFT as I will eventually be working with signals where I'll be interested in the phase which is why I've chosen to usee the CFFT module.

Any chance you are able to share your project online so I can use as a reference?

This was actually veryu helpful and helped me figure out my ARR and PSC values were double what I needed, thanks!

AvaTar
Lead

Find attached an incomplete example (based on the SPL, though).

It contains the code to trigger a ADC conversion regularly by TIM3, and transfer the results via DMA.

In the TC interrupt, the ADC values are prepared (copied & converted) for the CFFT.

That specific application I took it from ran on the Mikromedia F407 board, resources (clocks, ADC inputs) might differ in your case.

Thank you very much. I have solved my issue I believe. Turns out I was dealing with a somewhat faulty function generator.