The firmware has never been flashed, and a real bug already reached the repo because of it: RD03E_FRAME_LEN was 5 for a 6-byte frame, so the footer check collided with the distance high byte and EVERY distance reading was garbage — always `lo | 0x5500`, about 218 metres, regardless of what the sensor saw. That was pure logic with no hardware dependency. It should have been catchable on a laptop, and there was simply no way to run the code. Extracted the hardware-free logic out of the three drivers — rd03e_parse, bmp280_compensate, mems_level — as moves rather than rewrites, carrying the explanatory comments along with the code they explain. The drivers now own only their bus I/O and call into the pure units, so nothing changes for the real device. `./run_tests.sh` builds them with gcc -Wall -Wextra -Werror plus a dependency-free assert harness: 175 checks, 0 failed, from a clean tree. Proven to catch the actual bug rather than assumed to: reintroducing FRAME_LEN 5 fails four checks, including one that reads "a simple-report frame is 6 bytes, not 5", plus the truncated-frame and 5-byte-window cases. Restored, green again. This does NOT make the firmware verified, and the README says so plainly — it is called a narrow exception and scoped to pure logic. Wiring, timing, real register behaviour and the reconstructed RD-03E frame format all still need the physical board. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
134 lines
4.6 KiB
C
134 lines
4.6 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`). This file owns only the I2S traffic;
|
|
// the RMS -> dBFS maths, the 24-bit-in-32-bit-slot shift and the honesty
|
|
// notes about both live in mems_level.h/.c, which is ESP-IDF-free and
|
|
// unit-tested on a host with plain gcc (see ../test/).
|
|
//
|
|
// "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 "mems_level.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 level and noise floor live in mems_level.h.
|
|
|
|
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;
|
|
}
|
|
|
|
// All level arithmetic is in the pure, host-tested unit.
|
|
double rms = mems_level_rms(s_sample_buf, n_samples);
|
|
double dbfs = mems_level_dbfs(rms);
|
|
|
|
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;
|
|
}
|