From 95aed35d89e26b753f117141455a5230d9adcdbb Mon Sep 17 00:00:00 2001 From: Jason <83615043+JJassonn69@users.noreply.github.com> Date: Wed, 29 Apr 2026 19:21:35 +0545 Subject: [PATCH] mcu(bmp180): call cal-coefficient init at boot + watchdog cadence fix (AUDIT-CAL) The BMP180 driver had no public init method and never called readCalibrationCoefficients() from anywhere -- _calCoeff ran at the C++ in-class member-initializer defaults (all zeros) at runtime. Consequence chain: - computeB5(UT) short-circuited via 0/0 (Cortex-M7 SDIV with SCB->CCR.DIV_0_TRP=0 returns 0 silently -- system_stm32f7xx.c does not enable the trap) - getPressure() always tripped the `if (B4 == 0)` guard, returning the I2C-error sentinel (post-AUDIT-C17: INT32_MIN; pre-: 255) - health watchdog at main.cpp:758 fired ERROR_BMP180_COMM every main-loop iteration because last_bmp_check was only updated on the success path, so the 15 s rate-limit never engaged once the check started failing - error_count > 10 latched system_emergency_state = true (per the MCU-N1 fix), driving SAFE-MODE within ~25 s of every boot Fix: - Added BMP180::begin() public method: probes chip ID, then reads the 11 factory cal coefficients (registers 0xAA..0xBE step 2). Returns true only on full success; false on chip-ID mismatch or any I2C failure mid-loop. - main.cpp BAROMETER INIT calls myBMP.begin() with up to 3 retries (50 ms backoff) and sets a file-scope bmp180_operational flag. Altitude-baseline loop now gated on success -- failure path leaves RADAR_Altitude at 0.0f instead of letting pow(negative, fractional) propagate NaN into gps_data telemetry. - Health watchdog gates BMP180 check on bmp180_operational AND updates last_bmp_check regardless of the error path. A single bad pressure reading no longer tight-loops into SAFE-MODE; legit sensor failure now takes the intended ~150 s (10 errors x 15 s) before the MCU-N1 latch trips, giving the operator time to intervene. Verification: - new test_audit_cal_bmp180_begin.c, 3/3 PASS: T1 every coefficient loaded in order with correct signed/unsigned types T2 chip-mismatch and I2C-fail short-circuit semantics correct T3 regression demo: zero-cal computeB5 returns 0 for any UT (the silent-fail mode); datasheet cal reproduces 15.0 C - full MCU regression 33/33 PASS (was 32/32; +1 new test, 0 regressions) Bug introduced in 5fbe97f (initial upload of the driver from the Arduino enjoyneering79 BMP180 library -- the begin()/init pattern from the upstream Arduino version was lost in the STM32 port). Latent until this audit cycle. --- .../9_1_1_C_Cpp_Libraries/BMP180.cpp | 31 ++ .../9_1_1_C_Cpp_Libraries/BMP180.h | 1 + .../9_1_3_C_Cpp_Code/main.cpp | 62 +++- .../9_1_Microcontroller/tests/.gitignore | 1 + 9_Firmware/9_1_Microcontroller/tests/Makefile | 4 + .../tests/test_audit_cal_bmp180_begin.c | 310 ++++++++++++++++++ 6 files changed, 400 insertions(+), 9 deletions(-) create mode 100644 9_Firmware/9_1_Microcontroller/tests/test_audit_cal_bmp180_begin.c diff --git a/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/BMP180.cpp b/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/BMP180.cpp index 68f2c02..210880c 100644 --- a/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/BMP180.cpp +++ b/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/BMP180.cpp @@ -64,6 +64,37 @@ BMP180::BMP180(BMP180_RESOLUTION res_mode) } +/**************************************************************************/ +/* + begin() + + Probes the BMP180 over I2C, verifies the chip ID, and reads the 11 + factory calibration coefficients into _calCoeff. MUST be called once + after HAL_I2C is initialized and before any getTemperature/getPressure + call — without this, _calCoeff stays at zero defaults and computeB5 + short-circuits via 0/0 to 0, producing bogus output. + + NOT done in the constructor because the constructor runs at C++ + static-initialization time, before HAL_I2C_Init has been called from + main(). Calling I2C from a static initializer is fragile and would + silently fail. + + Returns true on success; false on I2C failure or chip-ID mismatch. + On failure, _calCoeff is left at its previous value (all zeros after + construction) so the caller can treat the sensor as absent and avoid + propagating NaN/wild values into downstream consumers. +*/ +/**************************************************************************/ +bool BMP180::begin(void) +{ + /* Probe + chip-ID check first — distinguishes "BMP180 not present" + * from "BMP180 present but I2C noisy" so the caller can act differently. */ + if (readDeviceID() != 180) return false; + + return readCalibrationCoefficients(); +} + + /**************************************************************************/ /* getPressure() diff --git a/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/BMP180.h b/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/BMP180.h index cb6269e..a244e87 100644 --- a/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/BMP180.h +++ b/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/BMP180.h @@ -137,6 +137,7 @@ class BMP180 { public: BMP180(BMP180_RESOLUTION = BMP180_ULTRAHIGHRES); //BMP180_ULTRAHIGHRES, by default + bool begin(void); //AUDIT-CAL: probe chip + read factory calibration; MUST be called once after I2C is up, before any getTemperature/getPressure call. Returns false on I2C failure or chip-ID mismatch — leaves _calCoeff untouched (so caller can treat sensor as absent). int32_t getPressure(void); //in Pa float getTemperature(void); //in °C int32_t getSeaLevelPressure(int16_t trueAltitude = 115); //in Pa, by default true altitude id 115 meters diff --git a/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/main.cpp b/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/main.cpp index ea3dd9a..b37aa55 100644 --- a/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/main.cpp +++ b/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/main.cpp @@ -176,6 +176,13 @@ BMP180_ULTRAHIGHRES - pressure oversampled 8 times & power consumption 12μA, l */ BMP180 myBMP(BMP180_ULTRAHIGHRES); float RADAR_Altitude; +/* AUDIT-CAL: latched true after a successful myBMP.begin() at boot. + * Gates the boot-time altitude-baseline loop and the runtime health + * watchdog so a missing or unresponsive BMP180 does NOT churn ERROR_BMP180_COMM + * every health-check pass and trip the error_count > 10 SAFE-MODE latch. + * Without this gate the radar self-shuts-down within ~25 s of boot whenever + * the sensor is absent or its calibration could not be read. */ +static bool bmp180_operational = false; //GPS double RADAR_Longitude = 0; @@ -753,15 +760,23 @@ SystemError_t checkSystemHealth(void) { } // 5. Check BMP180 Communication + // + // AUDIT-CAL fix: gate on bmp180_operational so an absent or boot-failed + // sensor does not churn ERROR_BMP180_COMM (which would trip + // error_count > 10 → SAFE-MODE latch within seconds). Also update + // last_bmp_check on BOTH success AND error paths — previously it was + // only assigned on success, so an out-of-range pressure caused the + // watchdog to re-fire every iteration of the main while(1) loop instead + // of the intended once-per-15s cadence. static uint32_t last_bmp_check = 0; - if (HAL_GetTick() - last_bmp_check > 15000) { + if (bmp180_operational && (HAL_GetTick() - last_bmp_check > 15000)) { double pressure = myBMP.getPressure(); + last_bmp_check = HAL_GetTick(); // commit the rate-limit window first if (pressure < 30000.0 || pressure > 110000.0 || isnan(pressure)) { current_error = ERROR_BMP180_COMM; DIAG_ERR("SYS", "Health check: BMP180 pressure out of range: %.0f", pressure); return current_error; } - last_bmp_check = HAL_GetTick(); } // 6. Check GPS Communication (30s grace period from boot / last valid fix) @@ -1835,13 +1850,42 @@ int main(void) ///////////////////////////////////BAROMETER BMP180//////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// DIAG_SECTION("BAROMETER INIT (BMP180)"); - DIAG("IMU", "Reading 5 barometer samples for altitude baseline"); - for(int i = 0; i<5 ; i++){ - float BMP_Perssure = myBMP.getPressure(); - RADAR_Altitude = 44330*(1-(pow((BMP_Perssure/101325),(1/5.255)))); - HAL_Delay(100); - } - DIAG("IMU", "Barometer init complete, RADAR_Altitude baseline set"); + + /* AUDIT-CAL fix: probe the BMP180 over I2C and read its 11 factory calibration + * coefficients. The driver was previously instantiated without this step, so + * `_calCoeff` ran at zero defaults — `computeB5()` short-circuited via 0/0 + * (Cortex-M7 SDIV with DIV_0_TRP=0 returns 0 silently), `getPressure()` always + * returned the I2C-error sentinel, the health watchdog fired + * ERROR_BMP180_COMM every iteration, and `error_count > 10` latched + * `system_emergency_state = true` within ~25 s of boot. Try up to 3 times + * with 50 ms backoff to ride out a noisy boot-time I2C bus. */ + bmp180_operational = false; + for (int attempt = 0; attempt < 3 && !bmp180_operational; attempt++) { + if (myBMP.begin()) { + bmp180_operational = true; + DIAG("IMU", "BMP180 begin() OK on attempt %d (cal coefficients loaded)", attempt + 1); + } else if (attempt < 2) { + DIAG_WARN("IMU", "BMP180 begin() failed attempt %d, retrying...", attempt + 1); + HAL_Delay(50); + } + } + + if (bmp180_operational) { + DIAG("IMU", "Reading 5 barometer samples for altitude baseline"); + for (int i = 0; i < 5; i++) { + float BMP_Perssure = myBMP.getPressure(); + RADAR_Altitude = 44330 * (1 - (pow((BMP_Perssure / 101325), (1 / 5.255)))); + HAL_Delay(100); + } + DIAG("IMU", "Barometer init complete, RADAR_Altitude baseline set"); + } else { + /* Sensor absent or cal could not be read. Leave RADAR_Altitude at its + * default (0.0f) so downstream gps_data telemetry sees a clean value + * instead of a NaN propagated from pow(negative, fractional). The + * watchdog will skip the BMP180 check (gated on bmp180_operational). */ + RADAR_Altitude = 0.0f; + DIAG_ERR("IMU", "BMP180 begin() FAILED after 3 attempts -- sensor will be marked absent; altitude baseline = 0.0 m; watchdog will skip BMP180 check"); + } /////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////ADF4382///////////////////////////////////// diff --git a/9_Firmware/9_1_Microcontroller/tests/.gitignore b/9_Firmware/9_1_Microcontroller/tests/.gitignore index 76ad7af..60b9b9f 100644 --- a/9_Firmware/9_1_Microcontroller/tests/.gitignore +++ b/9_Firmware/9_1_Microcontroller/tests/.gitignore @@ -24,6 +24,7 @@ test_gap3_emergency_stop_rails test_bug12_pa_cal_loop_inverted test_bug13_dac2_adc_buffer_mismatch test_audit_c17_bmp180_sentinel_and_cast +test_audit_cal_bmp180_begin test_gap3_iwdg_config test_gap3_temperature_max test_gap3_idq_periodic_reread diff --git a/9_Firmware/9_1_Microcontroller/tests/Makefile b/9_Firmware/9_1_Microcontroller/tests/Makefile index 1ab768a..a1c2394 100644 --- a/9_Firmware/9_1_Microcontroller/tests/Makefile +++ b/9_Firmware/9_1_Microcontroller/tests/Makefile @@ -73,6 +73,7 @@ TESTS_STANDALONE := test_bug12_pa_cal_loop_inverted \ test_mcu_a2_mag_declination \ test_mcu_a4_ocxo_warm_restart \ test_audit_c17_bmp180_sentinel_and_cast \ + test_audit_cal_bmp180_begin \ test_gap3_iwdg_config \ test_gap3_temperature_max \ test_gap3_idq_periodic_reread \ @@ -187,6 +188,9 @@ test_mcu_a4_ocxo_warm_restart: test_mcu_a4_ocxo_warm_restart.c test_audit_c17_bmp180_sentinel_and_cast: test_audit_c17_bmp180_sentinel_and_cast.c $(CC) $(CFLAGS) $< -lm -o $@ +test_audit_cal_bmp180_begin: test_audit_cal_bmp180_begin.c + $(CC) $(CFLAGS) $< -o $@ + # Gap-3 safety tests -- mock-only (needs spy log for GPIO sequence) test_gap3_emergency_stop_rails: test_gap3_emergency_stop_rails.c $(MOCK_OBJS) $(CC) $(CFLAGS) $(INCLUDES) $< $(MOCK_OBJS) -o $@ diff --git a/9_Firmware/9_1_Microcontroller/tests/test_audit_cal_bmp180_begin.c b/9_Firmware/9_1_Microcontroller/tests/test_audit_cal_bmp180_begin.c new file mode 100644 index 0000000..153c41f --- /dev/null +++ b/9_Firmware/9_1_Microcontroller/tests/test_audit_cal_bmp180_begin.c @@ -0,0 +1,310 @@ +/******************************************************************************* + * test_audit_cal_bmp180_begin.c + * + * AUDIT-CAL: BMP180 driver had no public init method and never called + * `readCalibrationCoefficients()` from anywhere — `_calCoeff` ran at the + * C++ in-class member-initializer defaults (all zeros) at runtime. + * + * Consequence: `computeB5(UT)` short-circuited via 0/0 (Cortex-M7 SDIV + * with `SCB->CCR.DIV_0_TRP=0` returns 0 silently) → `getPressure()` always + * tripped the `if (B4 == 0) return INT32_MIN;` guard. The health watchdog + * fired ERROR_BMP180_COMM every main-loop iteration (last_bmp_check was + * not updated on the error path), and `error_count > 10` latched + * `system_emergency_state = true` within ~25 s of boot. The radar + * self-shut-down for no real reason every time it powered on. + * + * Production fix: + * - Added public `bool BMP180::begin(void)` — verifies chip ID then reads + * the 11 factory calibration coefficients (AC1..MD at registers + * 0xAA..0xBE, every 2 bytes). Returns true only on full success. + * - main.cpp BAROMETER INIT calls myBMP.begin() with up to 3 retries; + * on success sets bmp180_operational=true, gates altitude baseline. + * - Health watchdog gates BMP180 check on bmp180_operational AND updates + * last_bmp_check regardless of error path (no more tight-loop). + * + * This test models the production cal-loading loop and asserts: + * T1: every coefficient register is read in order AC1..MD and written + * to the matching _calCoeff field with correct signed/unsigned type. + * T2: bool semantics — begin() returns true on full success; false on + * chip-ID mismatch (without invoking the cal loop); false if any of + * the 11 read16 calls fails (early termination). + * T3: regression demonstration — with all-zero _calCoeff (the broken + * pre-fix runtime state), computeB5 returns 0 for ANY UT input, + * which is exactly the silent-failure mode that caused getPressure + * to hit the B4==0 guard and watchdog to fire SAFE-MODE. + ******************************************************************************/ +#include +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------------- + * Mirror of BMP180_CAL_COEFF struct at BMP180.h:111-127. + * Field types match exactly — three signed pairs (AC1/2/3), three unsigned + * (AC4/5/6), then signed (B1/2, MB/C/D). The assignment in the cal loop + * uses implicit narrowing from uint16_t (read16 result) to the matching + * field type, so reproduce that here. + * ------------------------------------------------------------------------- */ +typedef struct { + int16_t AC1; + int16_t AC2; + int16_t AC3; + uint16_t AC4; + uint16_t AC5; + uint16_t AC6; + int16_t B1; + int16_t B2; + int16_t MB; + int16_t MC; + int16_t MD; +} BMP180_CAL; + +/* Calibration register addresses, BMP180.h:69-79. */ +#define REG_AC1 0xAA +#define REG_AC2 0xAC +#define REG_AC3 0xAE +#define REG_AC4 0xB0 +#define REG_AC5 0xB2 +#define REG_AC6 0xB4 +#define REG_B1 0xB6 +#define REG_B2 0xB8 +#define REG_MB 0xBA +#define REG_MC 0xBC +#define REG_MD 0xBE + +#define REG_CHIP_ID 0xD0 +#define BMP180_CHIP_ID 0x55 + +/* ------------------------------------------------------------------------- + * Mock I2C: programmed bytes per register, plus a "fail at register N" + * counter to simulate mid-loop I2C failure. + * ------------------------------------------------------------------------- */ +typedef struct { + uint16_t regs16[256]; /* programmed read16 value per register addr */ + uint8_t regs8[256]; /* programmed read8 value per register addr */ + int fail_after_n; /* -1 = never fail; else fail on (1+n)th read16 */ + int read16_calls; + int read8_calls; +} MockI2C; + +static bool mock_read16(MockI2C *m, uint8_t reg, uint16_t *out) +{ + m->read16_calls++; + if (m->fail_after_n >= 0 && m->read16_calls > m->fail_after_n) return false; + *out = m->regs16[reg]; + return true; +} + +static bool mock_read8(MockI2C *m, uint8_t reg, uint8_t *out) +{ + m->read8_calls++; + *out = m->regs8[reg]; + return true; +} + +/* ------------------------------------------------------------------------- + * Mirror of BMP180::readCalibrationCoefficients() at BMP180.cpp:237-294. + * Walks REG_AC1..REG_MD in 2-byte steps, calls read16 for each, dispatches + * to the right field. Returns false on first read16 failure. + * ------------------------------------------------------------------------- */ +static bool readCalibrationCoefficients(MockI2C *m, BMP180_CAL *cal) +{ + uint16_t value = 0; + for (uint8_t reg = REG_AC1; reg <= REG_MD; reg += 2) { + if (!mock_read16(m, reg, &value)) return false; + switch (reg) { + case REG_AC1: cal->AC1 = (int16_t)value; break; + case REG_AC2: cal->AC2 = (int16_t)value; break; + case REG_AC3: cal->AC3 = (int16_t)value; break; + case REG_AC4: cal->AC4 = value; break; + case REG_AC5: cal->AC5 = value; break; + case REG_AC6: cal->AC6 = value; break; + case REG_B1: cal->B1 = (int16_t)value; break; + case REG_B2: cal->B2 = (int16_t)value; break; + case REG_MB: cal->MB = (int16_t)value; break; + case REG_MC: cal->MC = (int16_t)value; break; + case REG_MD: cal->MD = (int16_t)value; break; + } + } + return true; +} + +/* Mirror of BMP180::readDeviceID at BMP180.cpp:217-223 — chip-ID probe. */ +static uint8_t readDeviceID(MockI2C *m) +{ + uint8_t id = 0; + if (!mock_read8(m, REG_CHIP_ID, &id)) return 0; + if (id == BMP180_CHIP_ID) return 180; + return 0; +} + +/* Mirror of new BMP180::begin() in BMP180.cpp. */ +static bool begin(MockI2C *m, BMP180_CAL *cal) +{ + if (readDeviceID(m) != 180) return false; + return readCalibrationCoefficients(m, cal); +} + +/* Mirror of BMP180::computeB5 at BMP180.cpp:393-399 — for the regression + * demonstration in T3. */ +static int32_t computeB5(const BMP180_CAL *cal, int32_t UT) +{ + int32_t X1 = ((UT - (int32_t)cal->AC6) * (int32_t)cal->AC5) >> 15; + /* Cortex-M7 SDIV with DIV_0_TRP=0 returns 0 on divide-by-zero — + * model that here so T3 reproduces the catastrophic silent-fail + * behavior accurately. */ + int32_t denom = X1 + (int32_t)cal->MD; + int32_t X2 = (denom == 0) ? 0 : (((int32_t)cal->MC << 11) / denom); + return X1 + X2; +} + +/* ------------------------------------------------------------------------- + * Bosch BMP180 datasheet sample calibration (Table 6). + * ------------------------------------------------------------------------- */ +static void program_datasheet_cal(MockI2C *m) +{ + memset(m->regs16, 0, sizeof(m->regs16)); + memset(m->regs8, 0, sizeof(m->regs8)); + m->regs16[REG_AC1] = (uint16_t)408; + m->regs16[REG_AC2] = (uint16_t)(int16_t)-72; + m->regs16[REG_AC3] = (uint16_t)(int16_t)-14383; + m->regs16[REG_AC4] = 32741; + m->regs16[REG_AC5] = 32757; + m->regs16[REG_AC6] = 23153; + m->regs16[REG_B1] = (uint16_t)6190; + m->regs16[REG_B2] = (uint16_t)4; + m->regs16[REG_MB] = (uint16_t)(int16_t)-32768; + m->regs16[REG_MC] = (uint16_t)(int16_t)-8711; + m->regs16[REG_MD] = (uint16_t)2868; + m->regs8[REG_CHIP_ID] = BMP180_CHIP_ID; + m->fail_after_n = -1; + m->read16_calls = 0; + m->read8_calls = 0; +} + +/* ------------------------------------------------------------------------- + * T1: every coefficient register is read; values land in the right fields + * with the right signedness. + * ------------------------------------------------------------------------- */ +static void test_t1_cal_loading(void) +{ + printf(" T1: begin() loads all 11 cal coefficients in order ... "); + MockI2C m = {0}; + BMP180_CAL cal = {0}; + program_datasheet_cal(&m); + + bool ok = begin(&m, &cal); + assert(ok == true); + + /* Exactly one read8 (chip-ID probe) + 11 read16 (one per coeff). */ + assert(m.read8_calls == 1); + assert(m.read16_calls == 11); + + /* Every field matches the programmed datasheet value with correct + * signed/unsigned interpretation. */ + assert(cal.AC1 == 408); + assert(cal.AC2 == -72); + assert(cal.AC3 == -14383); + assert(cal.AC4 == 32741); + assert(cal.AC5 == 32757); + assert(cal.AC6 == 23153); + assert(cal.B1 == 6190); + assert(cal.B2 == 4); + assert(cal.MB == -32768); + assert(cal.MC == -8711); + assert(cal.MD == 2868); + + printf("PASS\n"); +} + +/* ------------------------------------------------------------------------- + * T2: bool semantics for the three failure modes. + * ------------------------------------------------------------------------- */ +static void test_t2_failure_paths(void) +{ + printf(" T2: chip-mismatch / I2C-fail short-circuit semantics ... "); + MockI2C m = {0}; + + /* (a) Chip-ID mismatch: cal loop is NOT entered. */ + program_datasheet_cal(&m); + m.regs8[REG_CHIP_ID] = 0xAA; /* not 0x55 */ + BMP180_CAL cal_a = {0}; + bool ok_a = begin(&m, &cal_a); + assert(ok_a == false); + assert(m.read16_calls == 0); /* short-circuited at chip-ID check */ + + /* (b) I2C fails on the first cal read (after chip-ID). begin returns + * false; cal struct may have partial state but caller has been told + * "do not trust." */ + program_datasheet_cal(&m); + m.fail_after_n = 0; /* fail on the very next read16 */ + BMP180_CAL cal_b = {0}; + bool ok_b = begin(&m, &cal_b); + assert(ok_b == false); + assert(m.read16_calls >= 1); + + /* (c) I2C fails mid-loop (after 5 successful reads). */ + program_datasheet_cal(&m); + m.fail_after_n = 5; /* succeeds 5 times then fails */ + BMP180_CAL cal_c = {0}; + bool ok_c = begin(&m, &cal_c); + assert(ok_c == false); + assert(m.read16_calls == 6); /* 5 OK + 1 fail */ + /* Partial state: AC1..AC5 set, AC6..MD untouched. The bool=false return + * tells caller this struct is unsafe to use. */ + assert(cal_c.AC1 == 408); + assert(cal_c.AC2 == -72); + assert(cal_c.AC5 == 32757); + assert(cal_c.AC6 == 0); /* untouched after failure */ + assert(cal_c.MD == 0); + + printf("PASS\n"); +} + +/* ------------------------------------------------------------------------- + * T3: with zero-init _calCoeff (the runtime state under the original bug) + * computeB5 returns 0 for any UT — exactly the silent-fail mode that + * made getPressure return its sentinel and tripped the watchdog into + * SAFE-MODE within seconds of boot. + * ------------------------------------------------------------------------- */ +static void test_t3_zero_cal_silent_fail(void) +{ + printf(" T3: zero-cal computeB5 returns 0 for any UT (regression demo) ... "); + BMP180_CAL zero = {0}; + + /* Sweep raw UT across the full plausible range; with all-zero cal, + * every call returns 0 (X1=0, denom=0 → SDIV-by-zero=0, X2=0). */ + int hits = 0; + int total = 0; + for (int32_t UT = 0; UT <= 65535; UT += 1024) { + total++; + if (computeB5(&zero, UT) == 0) hits++; + } + assert(hits == total); + + /* Sanity: with the datasheet calibration, computeB5 reproduces the + * datasheet worked example (UT=27898 → B5=2399, T=15.0 °C). */ + BMP180_CAL good = { + 408, -72, -14383, 32741, 32757, 23153, 6190, 4, -32768, -8711, 2868 + }; + int32_t B5 = computeB5(&good, 27898); + /* Datasheet algorithm: T = (B5 + 8) >> 4; expected T = 150 (= 15.0 °C). */ + int32_t T_tenths = (B5 + 8) >> 4; + assert(T_tenths == 150); + + printf("PASS (zero-cal=%d/%d zeros, datasheet cal -> 15.0 C)\n", hits, total); +} + +int main(void) +{ + printf("=== AUDIT-CAL: BMP180 begin() initialization + chip-ID gate ===\n"); + + test_t1_cal_loading(); + test_t2_failure_paths(); + test_t3_zero_cal_silent_fail(); + + printf("=== ALL PASS ===\n"); + return 0; +}