STM32 Timer-Triggered ADC Sampling with DMA for FFT Analysis
Signal Analysis Parameters
To analyze a signal using FFT, several key parameters must be defined:
- Target signal frequency: f
- Target signal period: T = 1/f
- Sampling frequency: fs
- Sampling period: Ts = 1/fs
- Total sample points: NPT
- Total sampling time: t = NPT × Ts
- Frequency resolution: f0 = fs/NPT
- Captured signal periods: NT = t/T
- Samples per signal period: fs/f
Practical Example Configuration
For a 1 kHz signal sampled at 32 kHz with 256 points:
Signal frequency: f = 1000 Hz
Signal period: T = 0.001 s
Sampling frequency: fs = 32000 Hz
Sampling period: Ts = 31.25 μs
Total samples: NPT = 256
Total time: t = 8 ms
Frequency resolution: f0 = 125 Hz
Captured periods: NT = 8
Samples per period: 32
Frequency Resolution Considerations
The frequency resolution f0 = 125 Hz determines the spacing between frequency bins in the FFT spectrum. Signals at multiples of 125 Hz will appear as peaks in the spectrum. The actual amplitude is twice the displayed value due to symmetrical positive and negative frequency components.
Hardware Configuration
Timer Setup for ADC Triggering
Configure a timer to generate periodic triggers matching the desired sampling frequency:
// Timer configuration for 32 kHz sampling
void Timer_Config(void)
{
TIM_HandleTypeDef timer_handle;
timer_handle.Instance = TIM2;
timer_handle.Init.Prescaler = 0;
timer_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
timer_handle.Init.Period = (SystemCoreClock / 32000) - 1;
timer_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&timer_handle);
HAL_TIM_Base_Start(&timer_handle);
}
ADC Configuration
Set up ADC for external trigger, single conversion mode:
void ADC_Config(void)
{
ADC_HandleTypeDef adc_handle;
ADC_ChannelConfTypeDef channel_config;
adc_handle.Instance = ADC1;
adc_handle.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;
adc_handle.Init.ContinuousConvMode = DISABLE;
adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
adc_handle.Init.NbrOfConversion = 1;
adc_handle.Init.ScanConvMode = DISABLE;
HAL_ADC_Init(&adc_handle);
channel_config.Channel = ADC_CHANNEL_0;
channel_config.Rank = 1;
channel_config.SamplingTime = ADC_SAMPLETIME_3CYCLES;
HAL_ADC_ConfigChannel(&adc_handle, &channel_config);
}
DMA Configuration
Configure DMA for peripheral-to-memory transfer with memory address increment:
void DMA_Config(void)
{
DMA_HandleTypeDef dma_handle;
dma_handle.Instance = DMA1_Channel1;
dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;
dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;
dma_handle.Init.MemInc = DMA_MINC_ENABLE;
dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
dma_handle.Init.Mode = DMA_NORMAL;
dma_handle.Init.Priority = DMA_PRIORITY_LOW;
HAL_DMA_Init(&dma_handle);
__HAL_LINKDMA(&adc_handle, DMA_Handle, dma_handle);
}
Complete Implementation Example
Header File (adc_dma.h)
#ifndef ADC_DMA_H
#define ADC_DMA_H
#include "stm32f4xx.h"
#define SAMPLE_COUNT 256
extern uint16_t sample_buffer[SAMPLE_COUNT];
void ADC_DMA_Init(void);
void Trigger_Sampling(void);
#endif
ADC and DMA Implementation
#include "adc_dma.h"
uint16_t sample_buffer[SAMPLE_COUNT];
void ADC_DMA_Init(void)
{
GPIO_InitTypeDef gpio_config;
DMA_InitTypeDef dma_config;
ADC_InitTypeDef adc_config;
ADC_CommonInitTypeDef adc_common_config;
// Enable clocks
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE);
// Configure GPIO for analog input
gpio_config.GPIO_Pin = GPIO_Pin_3;
gpio_config.GPIO_Mode = GPIO_Mode_AN;
gpio_config.GPIO_Speed = GPIO_Speed_100MHz;
gpio_config.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOF, &gpio_config);
// ADC common configuration
adc_common_config.ADC_Mode = ADC_Mode_Independent;
adc_common_config.ADC_Prescaler = ADC_Prescaler_Div4;
adc_common_config.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
adc_common_config.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
ADC_CommonInit(&adc_common_config);
// ADC configuration
adc_config.ADC_Resolution = ADC_Resolution_12b;
adc_config.ADC_ScanConvMode = DISABLE;
adc_config.ADC_ContinuousConvMode = DISABLE;
adc_config.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
adc_config.ADC_DataAlign = ADC_DataAlign_Right;
adc_config.ADC_NbrOfConversion = 1;
ADC_Init(ADC3, &adc_config);
// Configure ADC channel
ADC_RegularChannelConfig(ADC3, ADC_Channel_9, 1, ADC_SampleTime_84Cycles);
// DMA configuration
dma_config.DMA_Channel = DMA_Channel_2;
dma_config.DMA_PeripheralBaseAddr = (uint32_t)&(ADC3->DR);
dma_config.DMA_Memory0BaseAddr = (uint32_t)sample_buffer;
dma_config.DMA_DIR = DMA_DIR_PeripheralToMemory;
dma_config.DMA_BufferSize = SAMPLE_COUNT;
dma_config.DMA_PeripheralInc = DMA_PINC_DISABLE;
dma_config.DMA_MemoryInc = DMA_MINC_ENABLE;
dma_config.DMA_PeripheralDataSize = DMA_PDATAALIGN_HALFWORD;
dma_config.DMA_MemoryDataSize = DMA_MDATAALIGN_HALFWORD;
dma_config.DMA_Mode = DMA_Mode_Normal;
dma_config.DMA_Priority = DMA_Priority_Medium;
DMA_Init(DMA2_Stream0, &dma_config);
// Enable peripherals
DMA_Cmd(DMA2_Stream0, ENABLE);
ADC_DMACmd(ADC3, ENABLE);
ADC_Cmd(ADC3, ENABLE);
}
void Trigger_Sampling(void)
{
// Restart DMA for new sampling sequence
DMA_Cmd(DMA2_Stream0, DISABLE);
DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);
ADC_ClearITPendingBit(ADC3, ADC_IT_OVR);
DMA_Cmd(DMA2_Stream0, ENABLE);
}
FFT Processing
// Buffer declarations
int32_t input_buffer[SAMPLE_COUNT];
int32_t output_buffer[SAMPLE_COUNT];
uint32_t magnitude_buffer[SAMPLE_COUNT/2];
void Process_FFT(void)
{
// Prepare input data (shift ADC values to high 16 bits)
for(uint16_t i = 0; i < SAMPLE_COUNT; i++) {
input_buffer[i] = ((int16_t)sample_buffer[i]) << 16;
}
// Perform FFT
cr4_fft_256_stm32(output_buffer, input_buffer, SAMPLE_COUNT);
// Calculate magnitude spectrum
for(uint16_t i = 0; i < SAMPLE_COUNT/2; i++) {
int32_t real = output_buffer[i] & 0xFFFF;
int32_t imag = output_buffer[i] >> 16;
magnitude_buffer[i] = sqrt(real*real + imag*imag);
}
}
Key Implementation Notes
- Timer period calculation: (SystemCoreClock / sampling_frequency) - 1
- ADC sampling time must be sufficient for signal settling
- DMA should be configured in normal mode for single acquisitions
- FFT input data requires proper scaling and formtating
- Frequency bin indexing corresponds to f0 × bin_number