mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-05-20 23:02:01 +00:00
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:
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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/////////////////////////////////////
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 $@
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user