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.
This commit is contained in:
Jason
2026-04-29 19:21:35 +05:45
parent 4b142166be
commit 95aed35d89
6 changed files with 400 additions and 9 deletions

View File

@@ -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() getPressure()

View File

@@ -137,6 +137,7 @@ class BMP180
{ {
public: public:
BMP180(BMP180_RESOLUTION = BMP180_ULTRAHIGHRES); //BMP180_ULTRAHIGHRES, by default 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 int32_t getPressure(void); //in Pa
float getTemperature(void); //in °C float getTemperature(void); //in °C
int32_t getSeaLevelPressure(int16_t trueAltitude = 115); //in Pa, by default true altitude id 115 meters int32_t getSeaLevelPressure(int16_t trueAltitude = 115); //in Pa, by default true altitude id 115 meters

View File

@@ -176,6 +176,13 @@ BMP180_ULTRAHIGHRES - pressure oversampled 8 times & power consumption 12μA, l
*/ */
BMP180 myBMP(BMP180_ULTRAHIGHRES); BMP180 myBMP(BMP180_ULTRAHIGHRES);
float RADAR_Altitude; 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 //GPS
double RADAR_Longitude = 0; double RADAR_Longitude = 0;
@@ -753,15 +760,23 @@ SystemError_t checkSystemHealth(void) {
} }
// 5. Check BMP180 Communication // 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; 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(); double pressure = myBMP.getPressure();
last_bmp_check = HAL_GetTick(); // commit the rate-limit window first
if (pressure < 30000.0 || pressure > 110000.0 || isnan(pressure)) { if (pressure < 30000.0 || pressure > 110000.0 || isnan(pressure)) {
current_error = ERROR_BMP180_COMM; current_error = ERROR_BMP180_COMM;
DIAG_ERR("SYS", "Health check: BMP180 pressure out of range: %.0f", pressure); DIAG_ERR("SYS", "Health check: BMP180 pressure out of range: %.0f", pressure);
return current_error; return current_error;
} }
last_bmp_check = HAL_GetTick();
} }
// 6. Check GPS Communication (30s grace period from boot / last valid fix) // 6. Check GPS Communication (30s grace period from boot / last valid fix)
@@ -1835,13 +1850,42 @@ int main(void)
///////////////////////////////////BAROMETER BMP180//////////////////////////////// ///////////////////////////////////BAROMETER BMP180////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////
DIAG_SECTION("BAROMETER INIT (BMP180)"); DIAG_SECTION("BAROMETER INIT (BMP180)");
DIAG("IMU", "Reading 5 barometer samples for altitude baseline");
for(int i = 0; i<5 ; i++){ /* AUDIT-CAL fix: probe the BMP180 over I2C and read its 11 factory calibration
float BMP_Perssure = myBMP.getPressure(); * coefficients. The driver was previously instantiated without this step, so
RADAR_Altitude = 44330*(1-(pow((BMP_Perssure/101325),(1/5.255)))); * `_calCoeff` ran at zero defaults — `computeB5()` short-circuited via 0/0
HAL_Delay(100); * (Cortex-M7 SDIV with DIV_0_TRP=0 returns 0 silently), `getPressure()` always
} * returned the I2C-error sentinel, the health watchdog fired
DIAG("IMU", "Barometer init complete, RADAR_Altitude baseline set"); * 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///////////////////////////////////// ///////////////////////////////////////ADF4382/////////////////////////////////////

View File

@@ -24,6 +24,7 @@ test_gap3_emergency_stop_rails
test_bug12_pa_cal_loop_inverted test_bug12_pa_cal_loop_inverted
test_bug13_dac2_adc_buffer_mismatch test_bug13_dac2_adc_buffer_mismatch
test_audit_c17_bmp180_sentinel_and_cast test_audit_c17_bmp180_sentinel_and_cast
test_audit_cal_bmp180_begin
test_gap3_iwdg_config test_gap3_iwdg_config
test_gap3_temperature_max test_gap3_temperature_max
test_gap3_idq_periodic_reread test_gap3_idq_periodic_reread

View File

@@ -73,6 +73,7 @@ TESTS_STANDALONE := test_bug12_pa_cal_loop_inverted \
test_mcu_a2_mag_declination \ test_mcu_a2_mag_declination \
test_mcu_a4_ocxo_warm_restart \ test_mcu_a4_ocxo_warm_restart \
test_audit_c17_bmp180_sentinel_and_cast \ test_audit_c17_bmp180_sentinel_and_cast \
test_audit_cal_bmp180_begin \
test_gap3_iwdg_config \ test_gap3_iwdg_config \
test_gap3_temperature_max \ test_gap3_temperature_max \
test_gap3_idq_periodic_reread \ 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 test_audit_c17_bmp180_sentinel_and_cast: test_audit_c17_bmp180_sentinel_and_cast.c
$(CC) $(CFLAGS) $< -lm -o $@ $(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) # 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) test_gap3_emergency_stop_rails: test_gap3_emergency_stop_rails.c $(MOCK_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) $< $(MOCK_OBJS) -o $@ $(CC) $(CFLAGS) $(INCLUDES) $< $(MOCK_OBJS) -o $@

View File

@@ -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 <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* -------------------------------------------------------------------------
* 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;
}