Files
qtalker---/firmware/esp32p4-sensor-node/main/mems_mic.c
Indiana 31f3f91801 fix: four real firmware defects found in adversarial review
rd03e.c: RD03E_FRAME_LEN was 5 but the frame's own documented layout
(header + gesture + distance_lo + distance_hi + footer[2]) is 6 bytes.
The footer check read buf[i+3], colliding with the distance high byte at
that same index — so every frame that validated at all was forced to have
distance_cm = lo | 0x5500 (~218m) regardless of what the sensor reported.
Distance readings were garbage 100% of the time, not intermittently.

mems_mic.c: i2s_del_channel() was missing on 2 of 3 init failure paths,
leaking the channel handle.

bmp280.c: the I2C bus/device handles leaked on 4 of 5 init failure paths;
added a fail label that releases both.

app_main.c: sensors now init before Wi-Fi bring-up, matching the rationale
sensor_driver.h already documents (a hanging sensor bus must not be able to
block network bring-up).

rtlsdr_experimental.c: rtlsdr_exp_stop() waited 500ms before
usb_host_uninstall(), but the daemon task blocks up to 1000ms inside
usb_host_lib_handle_events() before re-checking its running flag — the
delay must exceed that or teardown races a live daemon task.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 15:47:21 +00:00

152 lines
5.4 KiB
C

// 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 <string.h>
#include <stdbool.h>
#include <math.h>
#include <stdlib.h>
#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;
}