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:
@@ -4,17 +4,20 @@
|
||||
// BMP280 datasheet (Bosch Sensortec, document rev 1.23) and ESP-IDF's
|
||||
// documented `driver/i2c_master.h` API surface, and reasoned about carefully,
|
||||
// but never compiled with a real ESP-IDF toolchain nor run against a real
|
||||
// sensor. Register addresses and the compensation formula below are
|
||||
// transcribed as directly as possible from the datasheet's section 3.11.1
|
||||
// (register map) and 3.11.3 (double-precision compensation formula
|
||||
// reference implementation) to minimize transcription risk, but a real
|
||||
// bring-up should sanity-check first readings against a known-good
|
||||
// reference (e.g. compare to a household thermometer/barometer).
|
||||
// sensor. Register addresses are transcribed as directly as possible from
|
||||
// the datasheet's section 3.11.1 (register map) to minimize transcription
|
||||
// risk, but a real bring-up should sanity-check first readings against a
|
||||
// known-good reference (e.g. compare to a household thermometer/barometer).
|
||||
//
|
||||
// This file owns only the I2C traffic. The calibration/ADC byte decoding
|
||||
// and the §3.11.3 compensation maths live in bmp280_compensate.h/.c, which
|
||||
// is ESP-IDF-free and unit-tested on a host with plain gcc (see ../test/).
|
||||
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
#include "bmp280.h"
|
||||
#include "bmp280_compensate.h"
|
||||
#include "driver/i2c_master.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
@@ -41,21 +44,6 @@ static const char *TAG = "bmp280";
|
||||
|
||||
#define STATUS_MEASURING_BIT 0x08
|
||||
|
||||
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;
|
||||
|
||||
static i2c_master_bus_handle_t s_bus = NULL;
|
||||
static i2c_master_dev_handle_t s_dev = NULL;
|
||||
static bmp280_calib_t s_calib;
|
||||
@@ -70,31 +58,13 @@ static esp_err_t read_regs(uint8_t reg, uint8_t *out, size_t len) {
|
||||
return i2c_master_transmit_receive(s_dev, ®, 1, out, len, 1000 /* ms */);
|
||||
}
|
||||
|
||||
static int16_t s16(uint8_t lsb, uint8_t msb) {
|
||||
return (int16_t)((uint16_t)msb << 8 | lsb);
|
||||
}
|
||||
static uint16_t u16(uint8_t lsb, uint8_t msb) {
|
||||
return (uint16_t)((uint16_t)msb << 8 | lsb);
|
||||
}
|
||||
|
||||
static esp_err_t read_calibration(void) {
|
||||
uint8_t buf[24]; // 0x88..0x9F
|
||||
uint8_t buf[BMP280_CALIB_LEN]; // 0x88..0x9F
|
||||
|
||||
esp_err_t err = read_regs(REG_CALIB00, buf, sizeof(buf));
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
s_calib.dig_T1 = u16(buf[0], buf[1]);
|
||||
s_calib.dig_T2 = s16(buf[2], buf[3]);
|
||||
s_calib.dig_T3 = s16(buf[4], buf[5]);
|
||||
s_calib.dig_P1 = u16(buf[6], buf[7]);
|
||||
s_calib.dig_P2 = s16(buf[8], buf[9]);
|
||||
s_calib.dig_P3 = s16(buf[10], buf[11]);
|
||||
s_calib.dig_P4 = s16(buf[12], buf[13]);
|
||||
s_calib.dig_P5 = s16(buf[14], buf[15]);
|
||||
s_calib.dig_P6 = s16(buf[16], buf[17]);
|
||||
s_calib.dig_P7 = s16(buf[18], buf[19]);
|
||||
s_calib.dig_P8 = s16(buf[20], buf[21]);
|
||||
s_calib.dig_P9 = s16(buf[22], buf[23]);
|
||||
bmp280_calib_from_regs(buf, &s_calib);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -162,36 +132,6 @@ fail:
|
||||
return err;
|
||||
}
|
||||
|
||||
// Bosch datasheet 3.11.3 double-precision reference compensation formulas,
|
||||
// transcribed near-verbatim (variable names kept close to the original so
|
||||
// it's checkable against the datasheet PDF side-by-side).
|
||||
|
||||
static double compensate_temperature(int32_t adc_T, double *out_t_fine) {
|
||||
double var1 = (((double)adc_T) / 16384.0 - ((double)s_calib.dig_T1) / 1024.0) * ((double)s_calib.dig_T2);
|
||||
double var2 = ((((double)adc_T) / 131072.0 - ((double)s_calib.dig_T1) / 8192.0) *
|
||||
(((double)adc_T) / 131072.0 - ((double)s_calib.dig_T1) / 8192.0)) * ((double)s_calib.dig_T3);
|
||||
*out_t_fine = var1 + var2;
|
||||
return (var1 + var2) / 5120.0; // degrees C
|
||||
}
|
||||
|
||||
static double compensate_pressure(int32_t adc_P, double t_fine) {
|
||||
double var1 = (t_fine / 2.0) - 64000.0;
|
||||
double var2 = var1 * var1 * ((double)s_calib.dig_P6) / 32768.0;
|
||||
var2 = var2 + var1 * ((double)s_calib.dig_P5) * 2.0;
|
||||
var2 = (var2 / 4.0) + (((double)s_calib.dig_P4) * 65536.0);
|
||||
var1 = (((double)s_calib.dig_P3) * var1 * var1 / 524288.0 + ((double)s_calib.dig_P2) * var1) / 524288.0;
|
||||
var1 = (1.0 + var1 / 32768.0) * ((double)s_calib.dig_P1);
|
||||
if (var1 == 0.0) {
|
||||
return 0.0; // avoid divide-by-zero per datasheet's own guard
|
||||
}
|
||||
double p = 1048576.0 - (double)adc_P;
|
||||
p = (p - (var2 / 4096.0)) * 6250.0 / var1;
|
||||
var1 = ((double)s_calib.dig_P9) * p * p / 2147483648.0;
|
||||
var2 = p * ((double)s_calib.dig_P8) / 32768.0;
|
||||
p = p + (var1 + var2 + ((double)s_calib.dig_P7)) / 16.0;
|
||||
return p; // Pa
|
||||
}
|
||||
|
||||
esp_err_t bmp280_read(sensor_reading_t *out, size_t max_out, size_t *out_count) {
|
||||
*out_count = 0;
|
||||
if (!s_ready) {
|
||||
@@ -222,16 +162,16 @@ esp_err_t bmp280_read(sensor_reading_t *out, size_t max_out, size_t *out_count)
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t raw[6];
|
||||
uint8_t raw[BMP280_RAW_LEN];
|
||||
err = read_regs(REG_PRESS_MSB, raw, sizeof(raw));
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
int32_t adc_P = ((int32_t)raw[0] << 12) | ((int32_t)raw[1] << 4) | (raw[2] >> 4);
|
||||
int32_t adc_T = ((int32_t)raw[3] << 12) | ((int32_t)raw[4] << 4) | (raw[5] >> 4);
|
||||
int32_t adc_P = 0, adc_T = 0;
|
||||
bmp280_adc_from_regs(raw, &adc_P, &adc_T);
|
||||
|
||||
double t_fine = 0.0;
|
||||
double temp_c = compensate_temperature(adc_T, &t_fine);
|
||||
double press_pa = compensate_pressure(adc_P, t_fine);
|
||||
double temp_c = bmp280_compensate_temperature(&s_calib, adc_T, &t_fine);
|
||||
double press_pa = bmp280_compensate_pressure(&s_calib, adc_P, t_fine);
|
||||
|
||||
size_t n = 0;
|
||||
memset(&out[n], 0, sizeof(out[n]));
|
||||
|
||||
Reference in New Issue
Block a user