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>
110 lines
4.7 KiB
C
110 lines
4.7 KiB
C
// MEMS mic RMS -> dBFS tests.
|
|
//
|
|
// These prove the arithmetic: that a full-scale block reads ~0 dBFS, that
|
|
// silence reads the -120 floor rather than -inf or NaN (which would poison
|
|
// the JSON payload the backend receives), and that the level rises
|
|
// monotonically with amplitude. They prove nothing about whether the
|
|
// right-shift-by-8 matches this specific module's real bit alignment —
|
|
// that needs a mic.
|
|
|
|
#include "../main/mems_level.h"
|
|
#include "test_util.h"
|
|
|
|
#include <math.h>
|
|
#include <stddef.h>
|
|
|
|
#define N 256
|
|
|
|
void test_mems_level(void) {
|
|
SUITE("mems_level");
|
|
|
|
// A 24-bit sample sits left-justified in the 32-bit slot, so the raw
|
|
// slot value for full scale is 2^23 << 8.
|
|
const int32_t full_scale_slot = (int32_t)(8388607 << 8); // 2^23 - 1, shifted up
|
|
|
|
// --- full scale reads ~0 dBFS ----------------------------------------
|
|
{
|
|
int32_t buf[N];
|
|
for (size_t i = 0; i < N; i++) buf[i] = full_scale_slot;
|
|
double rms = mems_level_rms(buf, N);
|
|
CHECK_NEAR(rms, 8388607.0, 1.0, "full-scale slots recover the 24-bit magnitude");
|
|
double dbfs = mems_level_dbfs(rms);
|
|
CHECK_NEAR(dbfs, 0.0, 0.01, "full-scale input is ~0 dBFS (got %.4f)", dbfs);
|
|
CHECK(dbfs <= 0.0, "dBFS never exceeds 0 for an in-range input");
|
|
}
|
|
|
|
// --- silence reads the floor, not -inf or NaN ------------------------
|
|
{
|
|
int32_t buf[N];
|
|
for (size_t i = 0; i < N; i++) buf[i] = 0;
|
|
double rms = mems_level_rms(buf, N);
|
|
CHECK(rms == 0.0, "an all-zero block has zero RMS");
|
|
double dbfs = mems_level_dbfs(rms);
|
|
CHECK(dbfs == MEMS_DBFS_FLOOR, "silence clamps to the -120 floor (got %.4f)", dbfs);
|
|
CHECK(!isinf(dbfs), "silence must not be -inf");
|
|
CHECK(!isnan(dbfs), "silence must not be NaN");
|
|
|
|
// Sub-LSB dither in the padding bits still counts as silence
|
|
// because the >>8 discards it.
|
|
int32_t buf2[N];
|
|
for (size_t i = 0; i < N; i++) buf2[i] = (int32_t)(i % 256); // padding bits only
|
|
double dbfs2 = mems_level_dbfs(mems_level_rms(buf2, N));
|
|
CHECK(dbfs2 == MEMS_DBFS_FLOOR, "sub-LSB noise stays at the floor (got %.4f)", dbfs2);
|
|
}
|
|
|
|
// --- halving amplitude drops the level by ~6 dB ----------------------
|
|
{
|
|
int32_t loud[N], quiet[N];
|
|
for (size_t i = 0; i < N; i++) {
|
|
loud[i] = (int32_t)(4194304 << 8); // 2^22, i.e. -6 dBFS
|
|
quiet[i] = (int32_t)(2097152 << 8); // 2^21, i.e. -12 dBFS
|
|
}
|
|
double d_loud = mems_level_dbfs(mems_level_rms(loud, N));
|
|
double d_quiet = mems_level_dbfs(mems_level_rms(quiet, N));
|
|
CHECK_NEAR(d_loud, -6.0206, 0.001, "2^22 is -6 dBFS (got %.4f)", d_loud);
|
|
CHECK_NEAR(d_quiet, -12.0412, 0.001, "2^21 is -12 dBFS (got %.4f)", d_quiet);
|
|
CHECK_NEAR(d_loud - d_quiet, 6.0206, 0.001, "halving amplitude costs ~6 dB");
|
|
}
|
|
|
|
// --- negative samples contribute the same energy as positive ---------
|
|
{
|
|
int32_t pos[N], neg[N], alt[N];
|
|
for (size_t i = 0; i < N; i++) {
|
|
pos[i] = (int32_t)(1000000 << 8);
|
|
neg[i] = (int32_t)(-(1000000 << 8));
|
|
alt[i] = (i % 2) ? (int32_t)(1000000 << 8) : (int32_t)(-(1000000 << 8));
|
|
}
|
|
double rp = mems_level_rms(pos, N);
|
|
double rn = mems_level_rms(neg, N);
|
|
double ra = mems_level_rms(alt, N);
|
|
CHECK_NEAR(rp, 1000000.0, 1.0, "positive DC block RMS");
|
|
CHECK_NEAR(rn, 1000000.0, 1.0, "negative DC block has the same RMS (sign-independent)");
|
|
CHECK_NEAR(ra, 1000000.0, 1.0, "an alternating square wave has the same RMS");
|
|
}
|
|
|
|
// --- level rises monotonically with amplitude ------------------------
|
|
{
|
|
double prev = -1000.0;
|
|
for (int shift = 4; shift <= 23; shift++) {
|
|
int32_t buf[N];
|
|
int32_t mag = (int32_t)1 << shift;
|
|
for (size_t i = 0; i < N; i++) buf[i] = mag << 8;
|
|
double dbfs = mems_level_dbfs(mems_level_rms(buf, N));
|
|
CHECK(dbfs > prev, "level rises with amplitude at 2^%d (%.4f <= %.4f)", shift, dbfs, prev);
|
|
CHECK(dbfs >= MEMS_DBFS_FLOOR && dbfs <= 0.0,
|
|
"level stays inside [%.1f, 0] at 2^%d (got %.4f)", MEMS_DBFS_FLOOR, shift, dbfs);
|
|
CHECK(!isnan(dbfs) && !isinf(dbfs), "level is finite at 2^%d", shift);
|
|
prev = dbfs;
|
|
}
|
|
}
|
|
|
|
// --- degenerate inputs ------------------------------------------------
|
|
{
|
|
int32_t buf[1] = { 0 };
|
|
CHECK(mems_level_rms(NULL, 8) == 0.0, "NULL sample buffer yields 0 RMS, not a crash");
|
|
CHECK(mems_level_rms(buf, 0) == 0.0, "an empty block yields 0 RMS, not a divide by zero");
|
|
CHECK(mems_level_dbfs(mems_level_rms(buf, 0)) == MEMS_DBFS_FLOOR,
|
|
"an empty block reports the floor");
|
|
}
|
|
}
|