Lost + Found INMP441 I2S MEMS microphone driver and FFT processor library for Raspberry Pi Pico C SDK
  • C 92.5%
  • Python 3.8%
  • CMake 3.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-01 19:53:27 -07:00
include Initial commit: Add lf-inmp441 driver library 2026-08-01 19:13:10 -07:00
src Initial commit: Add lf-inmp441 driver library 2026-08-01 19:13:10 -07:00
CMakeLists.txt Initial commit: Add lf-inmp441 driver library 2026-08-01 19:13:10 -07:00
conanfile.py Fix Conan package structure by placing CMakeLists.txt at root 2026-08-01 19:53:27 -07:00
README.md Initial commit: Add lf-inmp441 driver library 2026-08-01 19:13:10 -07:00

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.