test: make firmware logic bugs catchable without hardware (Workstream F)
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>
This commit is contained in:
66
firmware/esp32p4-sensor-node/main/bmp280_compensate.h
Normal file
66
firmware/esp32p4-sensor-node/main/bmp280_compensate.h
Normal file
@@ -0,0 +1,66 @@
|
||||
// Bosch BMP280 compensation maths + calibration/ADC decoding — PURE LOGIC.
|
||||
//
|
||||
// ESP-IDF-free by design: no I2C, no esp_err_t, no FreeRTOS, no logging.
|
||||
// Only <stdint.h>/<stddef.h>, so it compiles and is testable on a host
|
||||
// with plain gcc (see ../test/). `bmp280.c` does the I2C traffic and calls
|
||||
// in here for every byte-order decision and every line of arithmetic.
|
||||
//
|
||||
// The formulas are Bosch datasheet (rev 1.23) §3.11.3's double-precision
|
||||
// reference implementation, transcribed near-verbatim, with the variable
|
||||
// names kept close to the original so it's checkable against the datasheet
|
||||
// PDF side-by-side. The register map decoded below is §3.11.1.
|
||||
//
|
||||
// What host tests can prove here: byte-order/packing of the calibration
|
||||
// block and the 20-bit ADC words, and that the arithmetic behaves
|
||||
// sanely and monotonically. What they CANNOT prove: that this exact
|
||||
// silicon returns the register contents we assume.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Calibration block is 24 bytes at 0x88..0x9F (dig_T1..dig_P9).
|
||||
#define BMP280_CALIB_LEN 24
|
||||
// Burst measurement read is 6 bytes from 0xF7: press(3) + temp(3).
|
||||
#define BMP280_RAW_LEN 6
|
||||
|
||||
typedef struct {
|
||||
uint16_t dig_T1;
|
||||
int16_t dig_T2;
|
||||
int16_t dig_T3;
|
||||
uint16_t dig_P1;
|
||||
int16_t dig_P2;
|
||||
int16_t dig_P3;
|
||||
int16_t dig_P4;
|
||||
int16_t dig_P5;
|
||||
int16_t dig_P6;
|
||||
int16_t dig_P7;
|
||||
int16_t dig_P8;
|
||||
int16_t dig_P9;
|
||||
} bmp280_calib_t;
|
||||
|
||||
// Decode the 24-byte calibration block. Each coefficient is stored
|
||||
// little-endian (LSB first) in the register map.
|
||||
void bmp280_calib_from_regs(const uint8_t buf[BMP280_CALIB_LEN], bmp280_calib_t *out);
|
||||
|
||||
// Decode the 6-byte burst read into the two 20-bit ADC words. Pressure
|
||||
// comes first (0xF7..0xF9), then temperature (0xFA..0xFC); each is
|
||||
// MSB/LSB/XLSB with the XLSB's top nibble carrying the low 4 bits.
|
||||
void bmp280_adc_from_regs(const uint8_t raw[BMP280_RAW_LEN], int32_t *out_adc_P, int32_t *out_adc_T);
|
||||
|
||||
// Returns degrees C, and writes the shared `t_fine` intermediate that the
|
||||
// pressure compensation needs.
|
||||
double bmp280_compensate_temperature(const bmp280_calib_t *c, int32_t adc_T, double *out_t_fine);
|
||||
|
||||
// Returns Pa. Returns exactly 0.0 when the datasheet's own divide-by-zero
|
||||
// guard trips (var1 == 0, i.e. an all-zero / unread calibration block).
|
||||
double bmp280_compensate_pressure(const bmp280_calib_t *c, int32_t adc_P, double t_fine);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user