NOTES¶
FIR — Finite Impulse Response Filter
Always stable, no feedback, linear phase possible. Designed via the window method, implemented as convolution.
Algorithm¶
Design Flow (Window Method)¶
\[ \text{Ideal freq response} \xrightarrow{\text{IFFT}} \text{Infinite impulse response} \xrightarrow{\text{window truncation}} \text{FIR coeffs} \;(h[0], \dots, h[M-1]) \]
Step 1 — Specify cutoff \(f_c\), transition width \(\Delta f\), stopband attenuation
Step 2 — Generate ideal impulse response (low-pass example):
\[ hd[n] = \frac{\sin(\omegac (n - M/2))}{\pi (n - M/2)}, \quad \omegac = 2\pi fc / f_s \]
HP/BP/BS via frequency shift from the LP prototype.
Step 3 — Window truncation: \(h[n] = h_d[n] \cdot w[n]\)
-
Rectangular
Narrowest main lobe, -13 dB sidelobe
Sharpest transition -
Hanning
Medium main lobe, -31 dB
General purpose -
Hamming
Medium main lobe, -41 dB
Speech processing -
Blackman
Widest main lobe, -57 dB
High attenuation
Filtering¶
Batch (direct form):
\[ y[n] = \sum_{k=0}^{M-1} h[k] \cdot x[n - k], \quad n = 0, \dots, L-1 \]
This is convolution — accelerated by ESP-DSP on ESP32.
Real-time streaming
Allocate a delay line on init, call `tinyfirprocess_sample` per sample. Ideal for edge devices.
Always stable
FIR has zeros only (no poles), all poles at origin → unconditional stability.
Linear phase
Symmetric coefficients (\(h[k] = h[M-1-k]\)) → linear phase response. Essential when phase preservation matters.
API Reference¶
Design¶
int tiny_fir_design_lowpass(float *coeffs, int max_taps,
float cutoff_norm, tiny_fir_window_t window);
// HP / BP / BS — similar
Normalized frequency: \(f{\text{norm}} = f / (f_s / 2)\), range [0, 1]}
Batch Filtering¶
tiny_error_t tiny_fir_filter_f32(const float *input, int len,
const float *coeffs, int num_taps,
float *output);
Real-time Filtering¶
typedef struct {
float *coefficients;
int num_taps;
float *delay_line; // internal state
// ...
} tiny_fir_state_t;
tiny_error_t tiny_fir_init(tiny_fir_state_t *state,
const float *coeffs, int num_taps);
tiny_error_t tiny_fir_deinit(tiny_fir_state_t *state);
float tiny_fir_process_sample(tiny_fir_state_t *state, float input);
void tiny_fir_reset(tiny_fir_state_t *state);
Notes¶
More taps = more compute
Each output sample needs \(M\) MACs. On ESP32-S3 @ 240 MHz, \(M=128\) costs ~0.5 ms.
Transition width = window main lobe width
Sharp transition → Rectangular (high sidelobes). High attenuation → Blackman (wide transition).
Normalized frequency
Design functions use [0, 1] where 1 = Nyquist (\(f_s/2\)). Keeps sampling rate and cutoff independent.