STM32F4 FFT example
As you maybe know, STM32F4 is Cortex M4 with DSP instructions. This allows you to make a FFT with a few simple steps. For that purpose, I have made an example, on how to create FFT with STM32F4.
I recommend use my FFT library for future use. It is built on ARM DSP library with everything included for beginner.
When the ARM company issued Cortex-M4 core, it also published DSP libraries for mathematics and other stuff. And there are also FFT functions. When you’ve downloaded ST’s standard peripheral drivers, you also downloaded CMSIS (Cortex Microcontroller Software Interface Standard), which are designed for all Cortex-M4 processors from every company.
Note: Tutorial below for Keil DSP does not work anymore with my project. For that purpose I’ve update my project and include all DSP libraries inside. All other libraries are also included in project.
CMSIS libraries are also included in Keil uVision (5 and newest), you just need to enable them. Under “Manager Run-Time Environment” -> CMSIS select DSP. DSP or Digital Signal Processing is a library for “high mathematics” instructions, which are supported by Cortex-M4 with floating point unit.
Fast Fourier Transform – FFT
Very fast about FFT. FFT or Fast Fourier Transform is an algorithm to convert time based signal into frequency domain. In other words, you are able to know from which sinus components is some signal created.
Everything about FFT is described on Wikipedia.
Let’s explain things that we will need here. Example on the bottom is a simple FFT audio equlizer. It will show frequencies in your audio that you will connect to pin. Sound is sampled with 44.1kHz. We will also sampled our signal with about (~) 44.1kHz. To get proper frequency from signal, we need at least 2 samples from one period of highest frequency we want to detect. For our purpose, if we sample with 44.1kHz, then largest frequency you can sample correct is 22050Hz.
One parameter in FFT result is resolution, how good you can detect different frequencies. This depends on how many samples you take before you calculate FFT. In example below, I will take 256 samples for FFT calculating, but only 128 samples will be valid to display them. In our case, we have ~45450Hz sampling frequency and if we take 256 samples, we get resolution of 45450 / 256 = 177.5 Hz. What does it say to us?
We will get back an array, basically 256 length, but results from 0 – 127 are valid, results from 128 – 255 are the same results as first one, but in reverse order.
- You always have to take number of samples which are power of 2!
- I took 28 = 256 samples
- Maximal value of 1 FFT result depends on number of samples
- If you have only DC value on the input, then everything you will get is Output[0]
- If you create 256 samples, then it’s value will be 256
- If you create 1024 samples, then it’s value will be 1024
I will make a table on how to interpret results from FFT output. We have resolution of ~177.5Hz, so:
FFT sample | Frequency | Description |
---|---|---|
Output[0] | 0Hz | First parameter is always DC voltage in signal |
Output[1] | 177.5Hz | Amplitude of 177.5Hz frequency in signal |
Output[n] | n * 177.5Hz | n-th value of your frequency result |
Output[N – 1] = Output[127] | 127 * Resolution = 127 * ~177.5 = ~22542Hz | Maximal frequency you can analyze is always for one resolution less than half of sampling frequency -> 45450 / 2 – 177.5 = ~22547 |
Example
Example below works on STM32F429-Discovery board. This board has LCD on it, so it can be also a little bit graphical.
- Provides you a FFT functionality for Cortex-M4
- Displayed on LCD as graphical equalizer
- Samples with 45450Hz (every 22us) one sample with ADC
- Pin for ADC is PA0
- On pin PA5 is an output sinus signal of 10kHz.
- You can connect it to PA0 pin and you will get response on display
- If you do that, you will see that main bar is almost on the middle of LCD, because 10000Hz / 22750 = almost 0.5
- If you want connect sound to the board, then you have to connect it in a way like image below
- If nothing is connected to PA0 pin, then you will get Output(0) with maximum value and one bar will be displayed
- ADC is not driven with DMA, it’s with “delay” mode. It’s good to show you principle on FFT.
- In production, you should use at least DMA, maybe double buffered DMA
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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
/** * 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 <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]; float32_t Output[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) { arm_cfft_radix4_instance_f32 S; /* ARM CFFT module */ float32_t maxValue; /* Max FFT value is stored here */ uint32_t maxIndex; /* Index in Output array where max value is */ uint16_t i; /* 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, 10000); /* Initialize ADC, PA0 is used */ TM_ADC_Init(ADC1, ADC_Channel_0); /* Print on LCD */ TM_ILI9341_Puts(10, 10, "FFT graphic equlizer\nstm32f4-discovery.net", &TM_Font_11x18, ILI9341_COLOR_BLACK, ILI9341_COLOR_GREEN2); 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 */ for (i = 0; i < SAMPLES; i += 2) { /* Each 22us ~ 45kHz sample rate */ Delay(21); /* We assume that sampling and other stuff will take about 1us */ /* Real part, make offset by ADC / 2 */ Input[(uint16_t)i] = (float32_t)((float32_t)TM_ADC_Read(ADC1, ADC_Channel_0) - 2048.0); /* Imaginary part */ Input[(uint16_t)(i + 1)] = 0; } /* Initialize the CFFT/CIFFT module, intFlag = 0, doBitReverse = 1 */ arm_cfft_radix4_init_f32(&S, FFT_SIZE, 0, 1); /* Process the data through the CFFT/CIFFT module */ arm_cfft_radix4_f32(&S, Input); /* Process the data through the Complex Magniture Module for calculating the magnitude at each bin */ arm_cmplx_mag_f32(Input, Output, FFT_SIZE); /* Calculates maxValue and returns corresponding value */ arm_max_f32(Output, FFT_SIZE, &maxValue, &maxIndex); /* Display data on LCD */ for (i = 0; i < FFT_SIZE / 2; i++) { /* Draw FFT results */ DrawBar(30 + 2 * i, 220, FFT_BAR_MAX_HEIGHT, (uint16_t)maxValue, (float32_t)Output[(uint16_t)i], 0x1234, 0xFFFF ); } /* We want to turn led ON only when low frequencies are active */ /* Output[0] = Signals DC value */ if ((Output[1] + Output[2] + Output[3] + Output[4] + Output[5] + Output[6] + Output[7]) > 120) { TM_DISCO_LedOn(LED_GREEN); } else { TM_DISCO_LedOff(LED_GREEN); } } } |
If you have any problems with compiling project, I’ve added compiled “FFT.hex” file to project.
Download entire project with libraries and hex file below.
FFT sample project for STM32F429-Discovery board using LCD to display bars.
Recent comments