- C 92.5%
- Python 3.8%
- CMake 3.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| include | ||
| src | ||
| CMakeLists.txt | ||
| conanfile.py | ||
| README.md | ||
lf-inmp441
Reusable INMP441 I2S MEMS Microphone driver and FFT processor library for the Raspberry Pi Pico C SDK (RP2040 and RP2350), packaged as a Conan package.
This library is part of the Lost + Found suite of projects. It enables zero-jitter background audio acquisition via Programmable I/O (PIO) and Direct Memory Access (DMA), performs real-time Cooley-Tukey Radix-2 FFT and windowing, and provides an adaptive transient and pattern detection engine.
⚡ Key Capabilities
- Low-Level I2S DMA Engine: Integrates a custom PIO program that acts as the I2S master clock controller, fetching data directly from the microphone's Serial Data (SD) line. Background DMA double buffering ensures zero-jitter sampling with zero CPU overhead until a full audio frame is ready.
- Decoupled DSP Helpers: Exposes standalone, optimized floating-point DSP utilities including Hann window initialization, sample extraction/scaling, Cooley-Tukey Radix-2 FFT, magnitude computation, spectral centroid tracking, and spectral flatness calculation.
- Adaptive Event Detector: Includes a general-purpose frequency band transient detector. Instead of hardcoded absolute thresholds (which fail in noisy environments), it uses a leaky-integrator exponential moving average (EMA) to dynamically track the background noise floor. It evaluates events using signal-to-noise ratio (SNR) thresholds and total spectrum energy ratios.
🔌 Hardware Wiring Guide
The INMP441 is an I2S slave. The Pico (RP2040 or RP2350) acts as the I2S master and must generate the bit clock (SCK) and word select (WS/LRCK).
Important
The PIO clock generation code requires that the WS pin be connected to the GPIO pin immediately following the SCK pin (SCK pin + 1).
| INMP441 Pin | Marking | Function | Pico GP Pin | Connection Description |
|---|---|---|---|---|
| VDD | VDD | Power Input | 3V3 | 3.3V Power Supply (1.8V - 3.6V max) |
| GND | GND | Ground | GND | Ground reference (0V) |
| SCK | SCK | Serial Bit Clock | GP20 | generated by Pico |
| WS | WS | Word Select (LRCK) | GP21 | Frame sync (Must be SCK + 1) |
| SD | SD | Serial Data Out | GP22 | Digital audio data line to Pico |
| L/R | L/R | Channel Select | GND | Pull to GND for Left channel mono output |
🛠️ Integration Guide
Consumption via CMake (Subdirectory)
Copy the driver directory into your project as a subdirectory:
add_subdirectory(lib/lf-inmp441)
target_link_libraries(your_project PRIVATE
lf-inmp441
)
And in your C source files:
#include "lf_inmp441.h"
Consumption via Conan
Add the dependency to your conanfile.txt (or conanfile.py):
[requires]
lf-inmp441/0.0.1
Install the dependencies:
conan install . --output-folder=build --build=missing
Then link against it in CMakeLists.txt:
find_package(lf-inmp441 REQUIRED)
target_link_libraries(your_project PRIVATE lf-inmp441::lf-inmp441)
💻 Firmware Example
The following is a complete, self-contained firmware application that initializes the microphone driver, configures an adaptive low-frequency detector (tuned to identify transient structural thuds, like door knocks), computes spectral features (centroid and flatness), and logs a real-time ASCII visualizer.
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "lf_inmp441.h"
// Hardware Pin Definitions
#define MIC_SCK_PIN 20
#define MIC_WS_PIN (MIC_SCK_PIN+1) // Must be SCK + 1
#define MIC_DATA_PIN 22
#define LED_PIN 25 // Onboard LED
// Recording Configuration
#define SAMPLE_RATE 16000 // 16 kHz sample rate
#define FFT_SIZE 512 // 512-point FFT (covers 32ms window, 31.25 Hz bin width)
int main(void) {
stdio_init_all();
sleep_ms(2000); // Wait for serial monitor
printf("Starting INMP441 Advanced Pattern Recognition Demo...\n");
// Initialize Onboard LED
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
gpio_put(LED_PIN, 0);
// Initialize Driver Context
lf_inmp441_t mic;
lf_inmp441_config_t config = {
.pio_num = 0,
.sck_pin = MIC_SCK_PIN,
.data_pin = MIC_DATA_PIN,
.sample_rate = SAMPLE_RATE,
.fft_size = FFT_SIZE
};
if (lf_inmp441_init(&mic, &config) < 0) {
printf("Failed to initialize INMP441 driver!\n");
while (1) {
gpio_put(LED_PIN, 1);
sleep_ms(100);
gpio_put(LED_PIN, 0);
sleep_ms(100);
}
}
// Initialize DSP memory buffers
float hann_window[FFT_SIZE];
float fft_real[FFT_SIZE];
float fft_imag[FFT_SIZE];
float magnitudes[FFT_SIZE / 2];
lf_inmp441_dsp_init_hann_window(hann_window, FFT_SIZE);
// Configure the Adaptive Door-Knock Detector:
// Knocks peak in the low-frequency band (62.5 Hz to 250 Hz -> Bins 2 to 8).
lf_inmp441_detector_t knock_detector;
lf_inmp441_detector_config_t knock_cfg = {
.start_bin = 2, // 62.5 Hz
.end_bin = 8, // 250.0 Hz
.min_energy = 10000.0f, // Absolute magnitude threshold
.snr_threshold = 2.5f, // Signal energy must be 2.5x the tracked background noise floor
.min_ratio = 0.55f, // Low-frequency band must contain at least 55% of overall spectrum energy
.noise_alpha = 0.02f, // Background noise adaptation rate (EMA weight)
.debounce_ms = 800 // Ignore consecutive events for 800ms
};
lf_inmp441_detector_init(&knock_detector, &knock_cfg);
// Start Audio Acquisition
lf_inmp441_start(&mic);
printf("Acquisition started. Listening...\n");
while (1) {
// Retrieve next filled double-buffer (blocks until ready)
uint32_t *dma_buffer = lf_inmp441_get_sample_buffer(&mic, true);
// Decode, de-interleave, apply Hann window, and clear imaginary buffer
lf_inmp441_dsp_prepare_fft(dma_buffer, fft_real, fft_imag, hann_window, FFT_SIZE, true);
// Run radix-2 FFT in-place
lf_inmp441_dsp_fft(fft_real, fft_imag, FFT_SIZE);
// Calculate positive frequency bin magnitudes
lf_inmp441_dsp_compute_magnitudes(fft_real, fft_imag, magnitudes, FFT_SIZE);
// Track spectral metrics
float centroid = lf_inmp441_dsp_spectral_centroid(magnitudes, FFT_SIZE / 2, SAMPLE_RATE);
float flatness = lf_inmp441_dsp_spectral_flatness(magnitudes, FFT_SIZE / 2);
// Update the adaptive transient detector
float low_freq_energy = 0.0f;
float spectral_ratio = 0.0f;
bool is_knock = lf_inmp441_detector_update(&knock_detector, magnitudes, FFT_SIZE / 2, &low_freq_energy, &spectral_ratio);
// Output ASCII Spectrogram (first 16 bins, ~0Hz to 500Hz)
printf("\r[");
for (int i = 1; i <= 16; i++) {
int bar_length = (int)(magnitudes[i] / 4000.0f);
if (bar_length > 8) bar_length = 8;
for (int b = 0; b < 8; b++) {
if (b < bar_length) printf("#");
else printf(".");
}
if (i < 16) printf("|");
}
printf("] Centroid: %4.0fHz | Flatness: %.2f | Ratio: %.2f", centroid, flatness, spectral_ratio);
if (is_knock) {
gpio_put(LED_PIN, 1);
printf("\n -> [DETECTION] Door Knock Event Spotted! (Mag: %.1f, Noise Floor: %.1f) \n",
low_freq_energy, knock_detector.running_noise_floor);
} else {
gpio_put(LED_PIN, 0);
printf(" \r");
}
fflush(stdout);
}
// Clean up (never reached in this loop)
lf_inmp441_deinit(&mic);
return 0;
}
🛠️ Local Testing
To build or verify changes locally:
conan export .
This exports lf-inmp441/0.0.1 to the local cache, making it immediately available for any of your Pico projects configured with Conan dependencies.