// I2S MEMS microphone driver — see mems_mic.h for wiring and honesty notes. // // UNVERIFIED AGAINST REAL HARDWARE. Written against ESP-IDF's documented // `driver/i2s_std.h` API (the current idiomatic I2S driver, superseding the // older monolithic `driver/i2s.h`) and the INMP441 family's well-documented // output format: 24-bit signed PCM, MSB-first, left-justified in a 32-bit // I2S slot (Philips/standard I2S timing). The right-shift-by-8 used below // to recover the 24-bit sample from the 32-bit slot, and the dBFS // reference level (2^23, a 24-bit signed sample's full-scale magnitude), // are the commonly-documented values for this exact mic family — but // "commonly documented" is not "verified against this specific board," so // treat the very first real readings as a sanity check, not a given: talk // near the mic and confirm the reported level actually rises before // trusting it unattended. #include #include #include #include #include "mems_mic.h" #include "driver/i2s_std.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" static const char *TAG = "mems_mic"; // dBFS reference: full-scale magnitude of a 24-bit signed sample. #define FULL_SCALE_24BIT (8388608.0) // 2^23 static i2s_chan_handle_t s_rx_chan = NULL; static bool s_ready = false; static int32_t *s_sample_buf = NULL; // heap-allocated, MEMS_MIC_SAMPLES_PER_READ entries esp_err_t mems_mic_init(void) { s_sample_buf = (int32_t *)malloc(MEMS_MIC_SAMPLES_PER_READ * sizeof(int32_t)); if (s_sample_buf == NULL) { ESP_LOGE(TAG, "sample buffer allocation failed"); return ESP_ERR_NO_MEM; } i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(MEMS_MIC_I2S_PORT, I2S_ROLE_MASTER); esp_err_t err = i2s_new_channel(&chan_cfg, NULL, &s_rx_chan); if (err != ESP_OK) { ESP_LOGE(TAG, "i2s_new_channel failed: %s", esp_err_to_name(err)); free(s_sample_buf); s_sample_buf = NULL; return err; } i2s_std_config_t std_cfg = { .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(MEMS_MIC_SAMPLE_RATE_HZ), .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG( I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO), .gpio_cfg = { .mclk = I2S_GPIO_UNUSED, .bclk = MEMS_MIC_I2S_BCLK_GPIO, .ws = MEMS_MIC_I2S_WS_GPIO, .dout = I2S_GPIO_UNUSED, // RX-only channel, no data output pin .din = MEMS_MIC_I2S_DIN_GPIO, .invert_flags = { .mclk_inv = false, .bclk_inv = false, .ws_inv = false, }, }, }; // Left channel per this driver's documented default wiring (mic's L/R // pin tied to GND) -- change to I2S_STD_SLOT_RIGHT to match a mic // wired the other way. std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT; err = i2s_channel_init_std_mode(s_rx_chan, &std_cfg); if (err != ESP_OK) { ESP_LOGE(TAG, "i2s_channel_init_std_mode failed: %s", esp_err_to_name(err)); i2s_del_channel(s_rx_chan); s_rx_chan = NULL; free(s_sample_buf); s_sample_buf = NULL; return err; } err = i2s_channel_enable(s_rx_chan); if (err != ESP_OK) { ESP_LOGE(TAG, "i2s_channel_enable failed: %s", esp_err_to_name(err)); i2s_del_channel(s_rx_chan); s_rx_chan = NULL; free(s_sample_buf); s_sample_buf = NULL; return err; } s_ready = true; ESP_LOGI(TAG, "I2S mic init ok (%d Hz, port %d)", MEMS_MIC_SAMPLE_RATE_HZ, MEMS_MIC_I2S_PORT); return ESP_OK; } esp_err_t mems_mic_read(sensor_reading_t *out, size_t max_out, size_t *out_count) { *out_count = 0; if (!s_ready) { return ESP_ERR_INVALID_STATE; } if (max_out < 1) { return ESP_ERR_NO_MEM; } size_t bytes_to_read = MEMS_MIC_SAMPLES_PER_READ * sizeof(int32_t); size_t bytes_read = 0; esp_err_t err = i2s_channel_read(s_rx_chan, s_sample_buf, bytes_to_read, &bytes_read, pdMS_TO_TICKS(500)); if (err != ESP_OK) { ESP_LOGW(TAG, "i2s_channel_read failed: %s", esp_err_to_name(err)); return err; } size_t n_samples = bytes_read / sizeof(int32_t); if (n_samples == 0) { ESP_LOGD(TAG, "no I2S samples this cycle"); return ESP_OK; } // RMS over the block. The mic's 24-bit sample is left-justified in the // 32-bit I2S slot -- shift right 8 to recover it before squaring, so // the magnitude lines up with FULL_SCALE_24BIT below. double sum_sq = 0.0; for (size_t i = 0; i < n_samples; i++) { double sample = (double)(s_sample_buf[i] >> 8); sum_sq += sample * sample; } double rms = sqrt(sum_sq / (double)n_samples); // dBFS: 20*log10(rms / full_scale). A true-silent input gives rms=0, // which is -inf in dB -- clamp to a floor rather than emit a value the // JSON encoder/backend can't handle. double dbfs; if (rms < 1.0) { dbfs = -120.0; // effective noise floor } else { dbfs = 20.0 * log10(rms / FULL_SCALE_24BIT); if (dbfs < -120.0) dbfs = -120.0; } memset(&out[0], 0, sizeof(out[0])); strncpy(out[0].sensor_type, "evp", SENSOR_READING_TYPE_MAXLEN - 1); out[0].value = dbfs; strncpy(out[0].unit, "dbfs", SENSOR_READING_UNIT_MAXLEN - 1); out[0].metadata = NULL; *out_count = 1; return ESP_OK; }