Library 62- Fast Fourier Transform (FFT) for STM32F4xx
Here is an example of Fast Fourier Transform on STM32F4xx devices. Today, I was looking something on ARM DSP documentation and I saw that some functions for FFT used in my example are deprecated and will be removed in future.
That was the main reason I decided to make a library for FFT on STM32F4xx.
To use this library, some third-party libraries are also required. All these required files can be found in STM32F4xx Standard peripheral drivers and DSP instructions provided from ST.com from their website. Now is version 1.5.1 of these drivers and also .lib file is included for ARM MATH for Cortex-M4 Little-Endian with Floating point instructions.
Point of this library is that user don’t actually need to know how FFT works in real life. You just have to know, that calculated FFT is always half of size in length than data, which are used for calculation, because ARM DSP library uses real and imaginary part in input array, so input array is twice of size of output array.
Library
Features
- Calculate FFT result with float 32 type of variable
- Calculate max value of your FFT result
- Works with highly optimized ARM DSP library
- Support for variable FFT size
- Allows 16, 32, 64, 128, 256, 512, 1024, 2048 or 4096 samples for FFT size
Dependencies
- CMSIS
- STM32F4xx
- TM
- defines.h
- defines.h
- ARM DSP
- arm_const_structs.c, available in “CMSIS\DSP_Lib\Source\CommonTables”
- arm_cortexM4lf_math.lib for ARM compiler, available in “CMSIS\Lib\ARM”
CMSIS folder can be found if you download these libraries directly from ARM.com or if you download Standard Perihperal Drivers for STM32F4xx from ST.com site.
ARM MATH
ARM MATH is a library provided from ARM and is the same for all Cortex families, except that you have to provide some informations to library.
In your global compiler defines, you should add these 2 lines:
1 2 3 4 5 |
/* FPU present on STM32F4xx device */ #define __FPU_PRESENT 1 /* Use ARM MATH for Cortex-M4 */ #define ARM_MATH_CM4 |
FFT Samples count
ARM FFT library allows you to use specific number of samples for data calculation.
These values can be every number which is power of 2 from 2^4 and 2^12. So, 9 different FFT length options. These number are passed into function when you initialize FFT with my library.
FFT input/output buffers
FFT works in a way that you first fill input buffer with samples and then you process them and you got samples in output buffer. Complex (CFFT) Fast Fourier Transform, which is also used behind the scenes in my library so uses real and imaginary part in input buffer and only real part is calculated to output buffer.
For this reason, input buffer HAVE TO be 2 * FFT_Size in length and output buffer HAVE TO be FFT_Size in length where FFT_Size is the same as FFT_Samples count explained above.
Library is able to use malloc() to allocate memory for you. You just need to enable this feature when you initialize FFT module and everything will be done for you. You just have to make sure that you have enough HEAP memory available for malloc, otherwise malloc will fail and you will not get anything.
For example, if you have 512 length FFT size, then input buffer must be 2 * 512 = 1024 samples of float 32 and output buffer is 512 samples of float 32. In common, this is 1536 samples of float32 which is 4-bytes long in memory. So, together this would be 6144 Bytes of HEAP memory.
Functions and enumerations
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
/** * @defgroup TM_FFT_Typedefs * @brief Library Typedefs * @{ */ /** * @brief FFT main structure for 32-bit float */ typedef struct { float32_t* Input; /*!< Pointer to data input buffer. Its length must be 2 * FFT_Size */ float32_t* Output; /*!< Pointer to data output buffer. Its length must be FFT_Size */ uint16_t FFT_Size; /*!< FFT size in units of samples. This parameter can be a value of 2^n where n is between 4 and 12 */ uint8_t UseMalloc; /*!< Set to 1 when malloc is used for memory allocation for buffers. Meant for private use */ uint16_t Count; /*!< Number of samples in buffer when using @ref TM_FFT_AddToBuffer function. Meant for private use */ const arm_cfft_instance_f32* S; /*!< Pointer to @ref arm_cfft_instance_f32 structure. Meant for private use */ float32_t MaxValue; /*!< Max value in FTT result after calculation */ uint32_t MaxIndex; /*!< Index in output array where max value happened */ } TM_FFT_F32_t; /** * @} */ /** * @defgroup TM_FFT_Functions * @brief Library Functions * @{ */ /** * @brief Initializes and prepares FFT structure for signal operations * @param *FFT: Pointer to empty @ref TM_FFT_F32_t structure for FFT * @param FFT_Size: Number of samples to be used for FFT calculation * This parameter can be a value of 2^n where n is between 4 and 12, so any power of 2 between 16 and 4096 * @param use_malloc: Set parameter to 1, if you want to use HEAP memory and @ref malloc to allocate input and output buffers * @note It is recommended to use malloc for allocation, because FFT input and output buffers differs in length * @retval Initialization status: * - 0: Initialized OK, ready to use * - 1: Input FFT SIZE is not valid * - 2: Malloc failed with allocating input data buffer * - 3: Malloc failed with allocating output data buffer. If input data buffer is allocated, it will be free if this is returned. */ uint8_t TM_FFT_Init_F32(TM_FFT_F32_t* FFT, uint16_t FFT_Size, uint8_t use_malloc); /** * @brief Sets input and output buffers for FFT calculations * @note Use this function only if you set @arg use_malloc parameter to zero in @ref TM_FFT_Init_F32 function * @param *FFT: Pointer to @ref TM_FFT_F32_t structure where buffers will be set * @param *InputBuffer: Pointer to buffer of type float32_t with FFT_Size * 2 length * @param *OutputBuffer: Pointer to buffer of type float32_t with FFT_Size length * @retval None */ void TM_FFT_SetBuffers_F32(TM_FFT_F32_t* FFT, float32_t* InputBuffer, float32_t* OutputBuffer); /** * @brief Adds new sample to input buffer in FFT array * @param *FFT: Pointer to @ref TM_FFT_F32_t structure where new sample will be added * @param sampleValue: A new sample to be added to buffer, real part. Imaginary part will be set to 0 * @retval FFT calculation status: * - 0: Input buffer is not full yet * - > 0: Input buffer is full and samples are ready to be calculated */ uint8_t TM_FFT_AddToBuffer(TM_FFT_F32_t* FFT, float32_t sampleValue); /** * @brief Processes and calculates FFT from InputBuffer and saves data to Output buffer * @note This function also calculates max value and max index in array where max value happens * @param *FFT: Pointer to @ref TM_FFT_F32_t where FFT calculation will happen */ void TM_FFT_Process_F32(TM_FFT_F32_t* FFT); /** * @brief Free input and output buffers * @note This function has sense only, if you used @ref malloc for memory allocation when you called @ref TM_FFT_Init_F32 function * @param *FFT: Pointer to @ref TM_FFT_F32_t structure where buffers will be free * @retval None */ void TM_FFT_Free_F32(TM_FFT_F32_t* FFT); /** * @brief Gets max value from already calculated FFT result * @param FFT: Pointer to @ref TM_FFT_F32_t structure where max value should be checked * @retval None * @note Defined as macro for faster execution */ #define TM_FFT_GetMaxValue(FFT) ((FFT)->MaxValue) /** * @brief Gets max index where max value happens from already calculated FFT result * @param FFT: Pointer to @ref TM_FFT_F32_t structure where max index at max value should be checked * @retval None * @note Defined as macro for faster execution */ #define TM_FFT_GetMaxIndex(FFT) ((FFT)->MaxIndex) /** * @brief Gets FFT size in units of samples length * @param FFT: Pointer to @ref TM_FFT_F32_t structure where FFT size will be checked * @retval FFT size * @note Defined as macro for faster execution */ #define TM_FFT_GetFFTSize(FFT) ((FFT)->FFT_Size) /** * @brief Gets FFT result value from output buffer at given index * @param FFT: Pointer to @ref TM_FFT_F32_t structure where FFT output sample will be returned * @param index: Index in buffer where result will be returned. Valid input is between 0 and FFT_Size - 1 * @retval Value at given index * @note Defined as macro for faster execution */ #define TM_FFT_GetFromBuffer(FFT, index) ((FFT)->Output[(uint16_t)(index)]) /** * @} */ |
Example
The example works the same as my first FFT example provided on link at the beginning of post. Only library is here and everything looks more nicer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
/** * Keil project example for FFT on STM32F4 device. * * Works on STM32F429-Discovery board because it has LCD * * @author Tilen Majerle * @email tilen@majerle.eu * @website http://stm32f4-discovery.net * @ide Keil uVision 5 * @packs STM32F4xx Keil packs version 2.2.0 or greater required * @stdperiph STM32F4xx Standard peripheral drivers version 1.4.0 or greater required * * Notes: * - Under "Options for target" > "C/C++" > "Define" you must add 2 defines (I've already add them): * - ARM_MATH_CM4 * - __FPU_PRESENT=1 */ /* Include core modules */ #include "stm32f4xx.h" /* Include my libraries here */ #include "defines.h" #include "tm_stm32f4_delay.h" #include "tm_stm32f4_ili9341_ltdc.h" #include "tm_stm32f4_adc.h" #include "tm_stm32f4_disco.h" #include "tm_stm32f4_sdram.h" #include "tm_stm32f4_dac_signal.h" #include "tm_stm32f4_fft.h" #include <stdio.h> /* Include arm_math.h mathematic functions */ #include "arm_math.h" /* FFT settings */ #define SAMPLES (512) /* 256 real party and 256 imaginary parts */ #define FFT_SIZE (SAMPLES / 2) /* FFT size is always the same size as we have samples, so 256 in our case */ #define FFT_BAR_MAX_HEIGHT 120 /* 120 px on the LCD */ /* Global variables */ float32_t Input[SAMPLES]; /*!< Input buffer is always 2 * FFT_SIZE */ float32_t Output[FFT_SIZE]; /*!< Output buffer is always FFT_SIZE */ /* Draw bar for LCD */ /* Simple library to draw bars */ void DrawBar(uint16_t bottomX, uint16_t bottomY, uint16_t maxHeight, uint16_t maxValue, float32_t value, uint16_t foreground, uint16_t background) { uint16_t height; height = (uint16_t)((float32_t)value / (float32_t)maxValue * (float32_t)maxHeight); if (height == maxHeight) { TM_ILI9341_DrawLine(bottomX, bottomY, bottomX, bottomY - height, foreground); } else if (height < maxHeight) { TM_ILI9341_DrawLine(bottomX, bottomY, bottomX, bottomY - height, foreground); TM_ILI9341_DrawLine(bottomX, bottomY - height, bottomX, bottomY - maxHeight, background); } } int main(void) { TM_FFT_F32_t FFT; /*!< FFT structure */ uint16_t i; uint32_t frequency = 10000; /* Initialize system */ SystemInit(); /* Delay init */ TM_DELAY_Init(); /* Initialize LED's on board */ TM_DISCO_LedInit(); /* Initialize LCD */ TM_ILI9341_Init(); TM_ILI9341_Rotate(TM_ILI9341_Orientation_Landscape_1); /* Initialize DAC2, PA5 for fake sinus, use TIM4 to generate DMA */ TM_DAC_SIGNAL_Init(TM_DAC2, TIM4); /* Set sinus with 10kHz */ TM_DAC_SIGNAL_SetSignal(TM_DAC2, TM_DAC_SIGNAL_Signal_Sinus, frequency); /* Initialize ADC, PA0 is used */ TM_ADC_Init(ADC1, ADC_Channel_0); /* Print something on LCD */ TM_ILI9341_Puts(10, 10, "FFT example STM32F4xx\nstm32f4-discovery.net", &TM_Font_11x18, ILI9341_COLOR_BLACK, ILI9341_COLOR_GREEN2); /* Init FFT, FFT_SIZE define is used for FFT_SIZE, samples count is FFT_SIZE * 2, don't use malloc for memory allocation */ TM_FFT_Init_F32(&FFT, FFT_SIZE, 0); /* We didn't used malloc for allocation, so we have to set pointers ourself */ /* Input buffer must be 2 * FFT_SIZE in length because of real and imaginary part */ /* Output buffer must be FFT_SIZE in length */ TM_FFT_SetBuffers_F32(&FFT, Input, Output); while (1) { /* This part should be done with DMA and timer for ADC treshold */ /* Actually, best solution is double buffered DMA with timer for ADC treshold */ /* But this is only for show principle on how FFT works */ /* Fill buffer until function returns 1 = Buffer full and samples ready to be calculated */ while (!TM_FFT_AddToBuffer(&FFT, (TM_ADC_Read(ADC1, ADC_Channel_0) - (float32_t)2048.0))) { /* Little delay, fake 45kHz sample rate for audio */ Delay(21); } /* Do FFT on signal, values at each bin and calculate max value and index where max value happened */ TM_FFT_Process_F32(&FFT); /* Display data on LCD */ for (i = 0; i < TM_FFT_GetFFTSize(&FFT); i++) { /* Draw FFT results */ DrawBar(30 + 2 * i, 220, FFT_BAR_MAX_HEIGHT, TM_FFT_GetMaxValue(&FFT), TM_FFT_GetFromBuffer(&FFT, i), 0x1234, 0xFFFF ); } } } |
Project is available on Github, download library below.
Recent comments