Skip to content

NOTES

FFT — Fast Fourier Transform

Transform signals from time domain to frequency domain for spectral analysis and peak detection. Based on the Cooley-Tukey Radix-2 algorithm; \(N\) must be a power of 2.


Algorithm

From DFT to FFT

Discrete Fourier Transform (DFT):

\[ X[k] = \sum_{n=0}^{N-1} x[n] \cdot e^{-j \frac{2\pi kn}{N}} \]

  • Frequency resolution: \(\Delta f = f_s / N\)
  • Bin frequency: \(f_k = k \cdot \Delta f\), bin 0 = DC, bin N/2 = Nyquist

FFT exploits twiddle factor \(W_N^r = e^{-j 2\pi r / N}\) symmetry, reducing \(O(N^2)\) to \(O(N \log N)\).

  • DFT


    \(O(N^2)\)
    \(N = 1024\) → 1,048,576 ops

  • FFT


    \(O(N \log N)\)
    \(N = 1024\) → 10,240 ops

Algorithm Steps

Step 1 — Bit-Reversal Permutation

Reorder input by binary-reversed indices so butterfly operands are adjacent in memory (enables in-place computation).

Example \(N=8\): 0(000)→0, 1(001)→4(100), 2(010)→2(010), 3(011)→6(110)

Step 2 — Butterfly Stages

\(m = \log_2 N\) stages, \(N/2\) butterflies each. Stage \(\ell\) (stride \(s=2^\ell\)):

\[ \begin{aligned} X'[k] &= X[k] + WN^r \cdot X[k + s/2] \\ X'[k + s/2] &= X[k] - WN^r \cdot X[k + s/2] \end{aligned} \]

Step 3 — Windowing (optional)

\(x_w[n] = x[n] \cdot w[n]\), reduces spectral leakage.

Key Takeaways

Resolution vs Leakage

Larger \(N\) improves resolution. Non-integer-period truncation causes spectral leakage; windowing helps but widens the main lobe.

Zero Padding ≠ Real Resolution

Padding zeros interpolates the spectrum visually but adds no real frequency resolution.

Amplitude Correction

Divide windowed magnitudes by the window's coherent gain for correct amplitudes.


API Reference

Initialization

tiny_error_t tiny_fft_init(int fft_size);      // call once; fft_size must be power of 2
tiny_error_t tiny_fft_deinit(void);            // free resources

Forward / Inverse Transform

// Forward FFT: real input → complex spectrum
// output_fft must be ≥ input_len * 2, format: [Re0, Im0, Re1, Im1, ...]
tiny_error_t tiny_fft_f32(const float *input, int input_len,
                          float *output_fft, tiny_fft_window_t window);

// Inverse FFT: complex spectrum → real output
tiny_error_t tiny_fft_ifft_f32(const float *input_fft, int fft_len,
                               float *output);

Window enum: TINY_FFT_WINDOW_NONE · TINY_FFT_WINDOW_HANNING · TINY_FFT_WINDOW_HAMMING · TINY_FFT_WINDOW_BLACKMAN

Spectrum Analysis

// Magnitude: |X[k]| = sqrt(Re² + Im²)
tiny_error_t tiny_fft_magnitude_f32(const float *fft_result, int fft_len,
                                    float *magnitude);

// Power spectrum: P[k] = |X[k]|² / N
tiny_error_t tiny_fft_power_spectrum_f32(const float *fft_result, int fft_len,
                                         float *power);

Frequency Detection

// Find dominant frequency (skips DC, parabolic interpolation for accuracy)
tiny_error_t tiny_fft_find_peak_frequency(const float *power_spectrum,
    int fft_len, float sample_rate, float *peak_freq, float *peak_power);

// Find top N frequencies (local peak detection + 2-bin merging + interpolation)
tiny_error_t tiny_fft_find_top_frequencies(const float *power_spectrum,
    int fft_len, float sample_rate, int top_n,
    float *frequencies, float *powers);

Workflow

tiny_fft_init(256);                              // 1. One-time init

float input[256];                                // 2. Acquire signal
float spectrum[512];                             // complex: 256×2

tiny_fft_f32(input, 256, spectrum,               // 3. Windowed FFT
             TINY_FFT_WINDOW_HANNING);

float power[256];
tiny_fft_power_spectrum_f32(spectrum, 256, power); // 4. Power spectrum

float freq, mag;
tiny_fft_find_peak_frequency(power, 256,          // 5. Peak frequency
                             100.0f, &freq, &mag);

tiny_fft_deinit();                                // 6. Cleanup