mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-09 15:07:14 +00:00
mcu(bmp180): replace in-band sentinel + fix uint16->int16 narrowing (AUDIT-C17)
BMP180_ERROR=255 was an in-band sentinel returned by uint16_t I/O helpers
(read16, readRawTemperature) on I2C failure. 255 is also a valid uint16
register reading (0x00FF appears across the calibration block and is
reachable as a raw temperature/pressure sample), so a sensor failure was
indistinguishable from a real reading.
getTemperature() additionally narrowed the uint16_t raw read to int16_t
before passing to computeB5(). Raw bit-patterns >= 0x8000 (reachable across
the BMP180 -40..+85 C operating window) flipped to negative int16_t and
sign-extended into computeB5(), producing temperature errors of order
100s of C (e.g. -347 C instead of +51 C for raw UT = 0x8000).
Fix:
- Internal I/O helpers (read8/read16/readRawTemperature/readRawPressure)
now return bool and pass the value through an out-param. None of the
new sentinels collide with valid sensor output:
* getTemperature -> NaN on error
* getPressure -> INT32_MIN on error
* getSeaLevelPressure -> INT32_MIN on error
- getTemperature() keeps raw as uint16_t and widens value-preservingly
via (int32_t)raw before computeB5().
- readRawPressure() reads XLSB through the bool-out-param contract;
previously OR'd in 0xFF on I2C fail, silently corrupting the LSB.
Verification: test_audit_c17_bmp180_sentinel_and_cast 4/4 PASS, including
datasheet UT=27898 -> 15.0 C reproduction and 64/64 finite outputs across
a full uint16 sweep (vs 32/32 collapses in the upper half under the buggy
narrowing). Full MCU regression 32/32 PASS.
Caller-side: no external code references BMP180_ERROR; main.cpp's existing
range check at the health-watchdog catches INT32_MIN via the < 30000.0
branch.
This commit is contained in:
@@ -76,8 +76,7 @@ BMP180::BMP180(BMP180_RESOLUTION res_mode)
|
||||
/**************************************************************************/
|
||||
int32_t BMP180::getPressure(void)
|
||||
{
|
||||
int32_t UT = 0;
|
||||
int32_t UP = 0;
|
||||
int32_t UP_signed = 0;
|
||||
int32_t B3 = 0;
|
||||
int32_t B5 = 0;
|
||||
int32_t B6 = 0;
|
||||
@@ -88,13 +87,16 @@ int32_t BMP180::getPressure(void)
|
||||
uint32_t B4 = 0;
|
||||
uint32_t B7 = 0;
|
||||
|
||||
UT = readRawTemperature(); //read uncompensated temperature, 16-bit
|
||||
if (UT == BMP180_ERROR) return BMP180_ERROR; //error handler, collision on i2c bus
|
||||
/* AUDIT-C17: uint16_t raw_UT is widened to int32_t value-preserving;
|
||||
* uint32_t raw_UP fits in int32_t (19-bit max). */
|
||||
uint16_t raw_UT = 0;
|
||||
uint32_t raw_UP = 0;
|
||||
|
||||
UP = readRawPressure(); //read uncompensated pressure, 19-bit
|
||||
if (UP == BMP180_ERROR) return BMP180_ERROR; //error handler, collision on i2c bus
|
||||
if (!readRawTemperature(&raw_UT)) return INT32_MIN; //I2C error sentinel (cannot collide with valid reading)
|
||||
if (!readRawPressure(&raw_UP)) return INT32_MIN;
|
||||
|
||||
B5 = computeB5(UT);
|
||||
B5 = computeB5((int32_t)raw_UT);
|
||||
UP_signed = (int32_t)raw_UP;
|
||||
|
||||
/* pressure calculation */
|
||||
B6 = B5 - 4000;
|
||||
@@ -107,9 +109,9 @@ int32_t BMP180::getPressure(void)
|
||||
X2 = ((int32_t)_calCoeff.bmpB1 * ((B6 * B6) >> 12)) >> 16;
|
||||
X3 = ((X1 + X2) + 2) >> 2;
|
||||
B4 = ((uint32_t)_calCoeff.bmpAC4 * (X3 + 32768L)) >> 15;
|
||||
B7 = (UP - B3) * (50000UL >> _resolution);
|
||||
|
||||
if (B4 == 0) return BMP180_ERROR; //safety check, avoiding division by zero
|
||||
B7 = (UP_signed - B3) * (50000UL >> _resolution);
|
||||
|
||||
if (B4 == 0) return INT32_MIN; //safety check, avoiding division by zero
|
||||
|
||||
if (B7 < 0x80000000) pressure = (B7 * 2) / B4;
|
||||
else pressure = (B7 / B4) * 2;
|
||||
@@ -133,10 +135,16 @@ int32_t BMP180::getPressure(void)
|
||||
/**************************************************************************/
|
||||
float BMP180::getTemperature(void)
|
||||
{
|
||||
int16_t rawTemperature = readRawTemperature();
|
||||
/* AUDIT-C17: was `int16_t rawTemperature = readRawTemperature();`
|
||||
* which silently narrowed uint16_t→int16_t. Bit-patterns ≥ 0x8000 (reachable
|
||||
* across the BMP180 -40..+85 °C window) became negative int16_t and
|
||||
* sign-extended to large negative int32_t inside computeB5(), producing
|
||||
* temperature errors of order 100s of °C. Keep raw as uint16_t and widen
|
||||
* to int32_t value-preservingly. */
|
||||
uint16_t rawTemperature = 0;
|
||||
if (!readRawTemperature(&rawTemperature)) return NAN; //I2C error sentinel (cannot collide with any valid float reading)
|
||||
|
||||
if (rawTemperature == BMP180_ERROR) return BMP180_ERROR; //error handler, collision on i2c bus
|
||||
return (float)((computeB5(rawTemperature) + 8) >> 4) / 10;
|
||||
return (float)((computeB5((int32_t)rawTemperature) + 8) >> 4) / 10;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
@@ -162,8 +170,8 @@ int32_t BMP180::getSeaLevelPressure(int16_t trueAltitude)
|
||||
{
|
||||
int32_t pressure = getPressure();
|
||||
|
||||
if (pressure == BMP180_ERROR) return BMP180_ERROR;
|
||||
return (pressure / pow(1.0 - (float)trueAltitude / 44330, 5.255));
|
||||
if (pressure == INT32_MIN) return INT32_MIN; //propagate I2C error sentinel
|
||||
return (pressure / pow(1.0 - (float)trueAltitude / 44330, 5.255));
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
@@ -194,7 +202,9 @@ void BMP180::softReset(void)
|
||||
/**************************************************************************/
|
||||
uint8_t BMP180::readFirmwareVersion(void)
|
||||
{
|
||||
return read8(BMP180_GET_VERSION_REG);
|
||||
uint8_t v = 0;
|
||||
read8(BMP180_GET_VERSION_REG, &v); //best-effort telemetry; v stays 0 on I2C error
|
||||
return v;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
@@ -206,8 +216,10 @@ uint8_t BMP180::readFirmwareVersion(void)
|
||||
/**************************************************************************/
|
||||
uint8_t BMP180::readDeviceID(void)
|
||||
{
|
||||
if (read8(BMP180_GET_ID_REG) == BMP180_CHIP_ID) return 180;
|
||||
return false;
|
||||
uint8_t id = 0;
|
||||
if (!read8(BMP180_GET_ID_REG, &id)) return 0; //I2C error
|
||||
if (id == BMP180_CHIP_ID) return 180;
|
||||
return 0; //chip mismatch
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
@@ -224,13 +236,11 @@ uint8_t BMP180::readDeviceID(void)
|
||||
/**************************************************************************/
|
||||
bool BMP180::readCalibrationCoefficients()
|
||||
{
|
||||
int32_t value = 0;
|
||||
uint16_t value = 0;
|
||||
|
||||
for (uint8_t reg = BMP180_CAL_AC1_REG; reg <= BMP180_CAL_MD_REG; reg++)
|
||||
{
|
||||
value = read16(reg);
|
||||
|
||||
if (value == BMP180_ERROR) return false; //error handler, collision on i2c bus
|
||||
if (!read16(reg, &value)) return false; //AUDIT-C17: bool out-param signals I2C error without colliding with valid uint16 cal byte (e.g. 0x00FF)
|
||||
|
||||
switch (reg)
|
||||
{
|
||||
@@ -290,16 +300,16 @@ bool BMP180::readCalibrationCoefficients()
|
||||
Reads raw/uncompensated temperature value, 16-bit
|
||||
*/
|
||||
/**************************************************************************/
|
||||
uint16_t BMP180::readRawTemperature(void)
|
||||
bool BMP180::readRawTemperature(uint16_t* out)
|
||||
{
|
||||
/* send temperature measurement command */
|
||||
if (write8(BMP180_START_MEASURMENT_REG, BMP180_GET_TEMPERATURE_CTRL) != true) return BMP180_ERROR; //error handler, collision on i2c bus
|
||||
if (!write8(BMP180_START_MEASURMENT_REG, BMP180_GET_TEMPERATURE_CTRL)) return false; //I2C error
|
||||
|
||||
/* set measurement delay */
|
||||
HAL_Delay(5);
|
||||
|
||||
/* read result */
|
||||
return read16(BMP180_READ_ADC_MSB_REG); //reads msb + lsb
|
||||
/* read result (msb + lsb); read16 sets *out only on success */
|
||||
return read16(BMP180_READ_ADC_MSB_REG, out);
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
@@ -309,10 +319,11 @@ uint16_t BMP180::readRawTemperature(void)
|
||||
Reads raw/uncompensated pressure value, 19-bits
|
||||
*/
|
||||
/**************************************************************************/
|
||||
uint32_t BMP180::readRawPressure(void)
|
||||
bool BMP180::readRawPressure(uint32_t* out)
|
||||
{
|
||||
uint8_t regControl = 0;
|
||||
uint32_t rawPressure = 0;
|
||||
uint16_t msb_lsb = 0;
|
||||
uint8_t xlsb = 0;
|
||||
|
||||
/* convert resolution to register control */
|
||||
switch (_resolution)
|
||||
@@ -335,7 +346,7 @@ uint32_t BMP180::readRawPressure(void)
|
||||
}
|
||||
|
||||
/* send pressure measurement command */
|
||||
if (write8(BMP180_START_MEASURMENT_REG, regControl) != true) return BMP180_ERROR; //error handler, collision on i2c bus
|
||||
if (!write8(BMP180_START_MEASURMENT_REG, regControl)) return false; //I2C error
|
||||
|
||||
/* set measurement delay */
|
||||
switch (_resolution)
|
||||
@@ -357,17 +368,15 @@ uint32_t BMP180::readRawPressure(void)
|
||||
break;
|
||||
}
|
||||
|
||||
/* read result msb + lsb */
|
||||
rawPressure = read16(BMP180_READ_ADC_MSB_REG); //16-bits
|
||||
if (rawPressure == BMP180_ERROR) return BMP180_ERROR; //error handler, collision on i2c bus
|
||||
|
||||
/* read result xlsb */
|
||||
rawPressure <<= 8;
|
||||
rawPressure |= read8(BMP180_READ_ADC_XLSB_REG); //19-bits
|
||||
/* read msb+lsb and xlsb separately; signal failure on either */
|
||||
if (!read16(BMP180_READ_ADC_MSB_REG, &msb_lsb)) return false;
|
||||
if (!read8(BMP180_READ_ADC_XLSB_REG, &xlsb)) return false; //AUDIT-C17: previously OR'd in sentinel 0xFF on I2C fail, silently corrupting LSB
|
||||
|
||||
uint32_t rawPressure = ((uint32_t)msb_lsb << 8) | xlsb; //19-bits before shift
|
||||
rawPressure >>= (8 - _resolution);
|
||||
|
||||
return rawPressure;
|
||||
*out = rawPressure;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
@@ -396,20 +405,21 @@ int32_t BMP180::computeB5(int32_t UT)
|
||||
Reads 8-bit value over I2C
|
||||
*/
|
||||
/**************************************************************************/
|
||||
uint8_t BMP180::read8(uint8_t reg)
|
||||
bool BMP180::read8(uint8_t reg, uint8_t* out)
|
||||
{
|
||||
uint8_t data = 0;
|
||||
HAL_StatusTypeDef status;
|
||||
|
||||
|
||||
// Write register address
|
||||
status = HAL_I2C_Master_Transmit(&hi2c3, BMP180_ADDRESS, ®, 1, I2C_TIMEOUT);
|
||||
if (status != HAL_OK) return BMP180_ERROR;
|
||||
|
||||
if (status != HAL_OK) return false;
|
||||
|
||||
// Read data from register
|
||||
status = HAL_I2C_Master_Receive(&hi2c3, BMP180_ADDRESS, &data, 1, I2C_TIMEOUT);
|
||||
if (status != HAL_OK) return BMP180_ERROR;
|
||||
|
||||
return data;
|
||||
if (status != HAL_OK) return false;
|
||||
|
||||
*out = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
@@ -420,24 +430,22 @@ uint8_t BMP180::read8(uint8_t reg)
|
||||
*/
|
||||
/**************************************************************************/
|
||||
|
||||
uint16_t BMP180::read16(uint8_t reg)
|
||||
bool BMP180::read16(uint8_t reg, uint16_t* out)
|
||||
{
|
||||
uint8_t data[2] = {0, 0};
|
||||
uint16_t value = 0;
|
||||
HAL_StatusTypeDef status;
|
||||
|
||||
|
||||
// Write register address
|
||||
status = HAL_I2C_Master_Transmit(&hi2c3, BMP180_ADDRESS, ®, 1, I2C_TIMEOUT);
|
||||
if (status != HAL_OK) return BMP180_ERROR;
|
||||
|
||||
if (status != HAL_OK) return false;
|
||||
|
||||
// Read 2 bytes from register
|
||||
status = HAL_I2C_Master_Receive(&hi2c3, BMP180_ADDRESS, data, 2, I2C_TIMEOUT);
|
||||
if (status != HAL_OK) return BMP180_ERROR;
|
||||
|
||||
if (status != HAL_OK) return false;
|
||||
|
||||
// Combine bytes (MSB first)
|
||||
value = (data[0] << 8) | data[1];
|
||||
|
||||
return value;
|
||||
*out = ((uint16_t)data[0] << 8) | data[1];
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -101,7 +101,13 @@ BMP180_CAL_REG;
|
||||
/* misc */
|
||||
#define BMP180_CHIP_ID 0x55 //id number
|
||||
|
||||
#define BMP180_ERROR 255 //returns 255, if communication error is occurred
|
||||
/* AUDIT-C17: in-band sentinel BMP180_ERROR=255 removed.
|
||||
* 255 is a perfectly valid uint16_t reading from any cal-coefficient or
|
||||
* raw-temperature/pressure register, so a sensor failure could not be
|
||||
* distinguished from a real reading. I/O helpers now return bool and pass
|
||||
* the value through an out-param; getTemperature() returns NaN on error,
|
||||
* getPressure()/getSeaLevelPressure() return INT32_MIN. None of these
|
||||
* sentinels collide with valid sensor output. */
|
||||
|
||||
#define BMP180_ADDRESS (0x77 << 1) // HAL requires 7-bit address shifted left by 1 bit
|
||||
|
||||
@@ -145,11 +151,11 @@ class BMP180
|
||||
uint8_t _resolution;
|
||||
|
||||
bool readCalibrationCoefficients(void);
|
||||
uint16_t readRawTemperature(void);
|
||||
uint32_t readRawPressure(void);
|
||||
bool readRawTemperature(uint16_t* out);
|
||||
bool readRawPressure(uint32_t* out);
|
||||
int32_t computeB5(int32_t UT);
|
||||
uint8_t read8(uint8_t reg);
|
||||
uint16_t read16(uint8_t reg);
|
||||
bool read8(uint8_t reg, uint8_t* out);
|
||||
bool read16(uint8_t reg, uint16_t* out);
|
||||
bool write8(uint8_t reg, uint8_t control);
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ test_gap3_emergency_stop_rails
|
||||
# TESTS_STANDALONE
|
||||
test_bug12_pa_cal_loop_inverted
|
||||
test_bug13_dac2_adc_buffer_mismatch
|
||||
test_audit_c17_bmp180_sentinel_and_cast
|
||||
test_gap3_iwdg_config
|
||||
test_gap3_temperature_max
|
||||
test_gap3_idq_periodic_reread
|
||||
|
||||
@@ -72,6 +72,7 @@ TESTS_STANDALONE := test_bug12_pa_cal_loop_inverted \
|
||||
test_mcu_a6_recovery_dispatch \
|
||||
test_mcu_a2_mag_declination \
|
||||
test_mcu_a4_ocxo_warm_restart \
|
||||
test_audit_c17_bmp180_sentinel_and_cast \
|
||||
test_gap3_iwdg_config \
|
||||
test_gap3_temperature_max \
|
||||
test_gap3_idq_periodic_reread \
|
||||
@@ -183,6 +184,9 @@ test_mcu_a2_mag_declination: test_mcu_a2_mag_declination.c
|
||||
test_mcu_a4_ocxo_warm_restart: test_mcu_a4_ocxo_warm_restart.c
|
||||
$(CC) $(CFLAGS) $< -o $@
|
||||
|
||||
test_audit_c17_bmp180_sentinel_and_cast: test_audit_c17_bmp180_sentinel_and_cast.c
|
||||
$(CC) $(CFLAGS) $< -lm -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 $@
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
/*******************************************************************************
|
||||
* test_audit_c17_bmp180_sentinel_and_cast.c
|
||||
*
|
||||
* AUDIT-C17: BMP180 driver had two latent bugs in its temperature path:
|
||||
*
|
||||
* (a) BMP180_ERROR=255 was an in-band sentinel returned by uint16_t
|
||||
* read16()/readRawTemperature() on I2C error. 255 is also a valid
|
||||
* uint16_t register reading (0x00FF appears across the calibration
|
||||
* coefficient block and is reachable as a raw temperature/pressure
|
||||
* sample). Sensor failure was indistinguishable from a real reading.
|
||||
*
|
||||
* (b) getTemperature() narrowed the uint16_t raw value to int16_t before
|
||||
* calling computeB5(), which takes int32_t. Bit-patterns ≥ 0x8000
|
||||
* (reachable across the BMP180 -40..+85 °C operating window) flipped
|
||||
* to negative int16_t and sign-extended into computeB5(), producing
|
||||
* temperature errors of order 100s of °C.
|
||||
*
|
||||
* Production fix:
|
||||
* - I/O helpers (read8/read16/readRawTemperature/readRawPressure) now
|
||||
* return bool and pass the value through an out-param. getTemperature
|
||||
* returns NaN on error; getPressure/getSeaLevelPressure return
|
||||
* INT32_MIN. None of these sentinels collide with valid sensor output.
|
||||
* - getTemperature() keeps raw as uint16_t and widens to int32_t
|
||||
* value-preservingly: `(int32_t)raw_uint16` instead of `(int16_t)raw`.
|
||||
*
|
||||
* This test models the corrected math (computeB5 + getTemperature) plus the
|
||||
* casting choices and asserts:
|
||||
* T1: bool out-param signaling is distinguishable from any valid uint16
|
||||
* (incl. 0x00FF, 0x8000, 0xFFFF — all of which collided with the old
|
||||
* BMP180_ERROR=255 OR-with-narrowing scheme).
|
||||
* T2: corrected widen-cast yields the Bosch-reference result for a
|
||||
* calibrated sample (datasheet example UT=27898 -> 15.0 °C).
|
||||
* T3: the buggy narrowing cast produces catastrophically wrong output
|
||||
* for raw UT = 0x8000 (regression guard — flipping the rawTemperature
|
||||
* declaration back to int16_t would re-trigger it).
|
||||
* T4: full-range sweep — no raw uint16 in [0, 65535] should produce
|
||||
* NaN/error from the corrected pipeline; under the buggy pipeline the
|
||||
* upper half of the range collapses to negative output.
|
||||
******************************************************************************/
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Bosch BMP180 datasheet example calibration (Table 6, datasheet rev 2.5)
|
||||
* ------------------------------------------------------------------------- */
|
||||
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;
|
||||
|
||||
static const BMP180_CAL DATASHEET_CAL = {
|
||||
.AC1 = 408, .AC2 = -72, .AC3 = -14383,
|
||||
.AC4 = 32741, .AC5 = 32757, .AC6 = 23153,
|
||||
.B1 = 6190, .B2 = 4,
|
||||
.MB = -32768, .MC = -8711, .MD = 2868,
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Mirrors BMP180::computeB5(int32_t UT) at BMP180.cpp:393-399.
|
||||
* Identical math; the only thing this test varies is the caller's choice of
|
||||
* cast on the way in.
|
||||
* ------------------------------------------------------------------------- */
|
||||
static int32_t computeB5(const BMP180_CAL *cal, int32_t UT)
|
||||
{
|
||||
int32_t X1 = ((UT - (int32_t)cal->AC6) * (int32_t)cal->AC5) >> 15;
|
||||
int32_t X2 = ((int32_t)cal->MC << 11) / (X1 + (int32_t)cal->MD);
|
||||
return X1 + X2;
|
||||
}
|
||||
|
||||
/* CORRECTED path: keep raw as uint16_t, widen to int32_t value-preservingly.
|
||||
* Mirrors the patched BMP180::getTemperature() at BMP180.cpp:136-148. */
|
||||
static float getTemperature_fixed(const BMP180_CAL *cal, uint16_t raw)
|
||||
{
|
||||
return (float)((computeB5(cal, (int32_t)raw) + 8) >> 4) / 10.0f;
|
||||
}
|
||||
|
||||
/* BUGGY path: narrow uint16_t → int16_t before widening to int32_t.
|
||||
* Mirrors the original BMP180::getTemperature() at HEAD ea2615e. */
|
||||
static float getTemperature_buggy(const BMP180_CAL *cal, uint16_t raw)
|
||||
{
|
||||
int16_t narrowed = (int16_t)raw;
|
||||
return (float)((computeB5(cal, (int32_t)narrowed) + 8) >> 4) / 10.0f;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Mock I/O helper modeling the new bool-out-param contract.
|
||||
* ------------------------------------------------------------------------- */
|
||||
typedef struct {
|
||||
bool i2c_will_fail;
|
||||
uint16_t programmed_value;
|
||||
} MockI2C;
|
||||
|
||||
static bool mock_readRawTemperature(MockI2C *m, uint16_t *out)
|
||||
{
|
||||
if (m->i2c_will_fail) return false;
|
||||
*out = m->programmed_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* OLD contract (regression model): in-band BMP180_ERROR=255 sentinel,
|
||||
* uint16_t return. 255 is a valid reading; we cannot distinguish a real
|
||||
* raw=255 reading from a sensor failure. */
|
||||
#define OLD_BMP180_ERROR 255
|
||||
static uint16_t mock_readRawTemperature_old(MockI2C *m)
|
||||
{
|
||||
if (m->i2c_will_fail) return OLD_BMP180_ERROR;
|
||||
return m->programmed_value;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* T1: sentinel separability under the new contract.
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void test_t1_sentinel_separability(void)
|
||||
{
|
||||
printf(" T1: bool-out-param sentinel separability ... ");
|
||||
|
||||
MockI2C m;
|
||||
uint16_t value;
|
||||
|
||||
/* The exact set called out in the audit memo: every reading that
|
||||
* collides with BMP180_ERROR=255 under the old in-band scheme. */
|
||||
const uint16_t collision_cases[] = { 0, 1, 254, 255, 256, 32767, 32768, 65535 };
|
||||
const size_t N = sizeof(collision_cases) / sizeof(collision_cases[0]);
|
||||
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
m.i2c_will_fail = false;
|
||||
m.programmed_value = collision_cases[i];
|
||||
value = 0xDEAD;
|
||||
bool ok = mock_readRawTemperature(&m, &value);
|
||||
assert(ok == true);
|
||||
assert(value == collision_cases[i]);
|
||||
}
|
||||
|
||||
/* I2C-error path: bool=false, out untouched. */
|
||||
m.i2c_will_fail = true;
|
||||
value = 0xDEAD;
|
||||
bool ok = mock_readRawTemperature(&m, &value);
|
||||
assert(ok == false);
|
||||
assert(value == 0xDEAD); /* out-param NOT clobbered on error */
|
||||
|
||||
/* Regression demonstration: under the OLD contract, raw=255 and an I2C
|
||||
* fault produce the same return value, so the caller cannot tell them
|
||||
* apart. This is the bug the new contract eliminates. */
|
||||
m.i2c_will_fail = false;
|
||||
m.programmed_value = 255;
|
||||
uint16_t v_real = mock_readRawTemperature_old(&m);
|
||||
m.i2c_will_fail = true;
|
||||
uint16_t v_fault = mock_readRawTemperature_old(&m);
|
||||
assert(v_real == v_fault); /* old contract: indistinguishable */
|
||||
|
||||
printf("PASS\n");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* T2: datasheet reference value reproduces under the corrected cast.
|
||||
*
|
||||
* Bosch BMP180 datasheet (Section 3.5, "Calculating pressure and
|
||||
* temperature") worked example: with the calibration above and
|
||||
* UT=27898, the expected temperature is 15.0 °C.
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void test_t2_datasheet_reference(void)
|
||||
{
|
||||
printf(" T2: datasheet UT=27898 -> 15.0 °C (fixed cast) ... ");
|
||||
float t = getTemperature_fixed(&DATASHEET_CAL, 27898);
|
||||
assert(fabsf(t - 15.0f) < 0.05f);
|
||||
printf("PASS (got %.2f °C)\n", (double)t);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* T3: regression guard for the narrowing bug.
|
||||
*
|
||||
* For raw UT = 0x8000 (32768), the corrected cast yields ~+51 °C; the
|
||||
* buggy narrow-cast yields ~-347 °C. The two paths must diverge by
|
||||
* hundreds of °C — that is exactly the operational hazard.
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void test_t3_narrowing_regression(void)
|
||||
{
|
||||
printf(" T3: raw UT=0x8000 fixed vs buggy diverge by >100 °C ... ");
|
||||
|
||||
float t_fixed = getTemperature_fixed(&DATASHEET_CAL, 0x8000);
|
||||
float t_buggy = getTemperature_buggy(&DATASHEET_CAL, 0x8000);
|
||||
|
||||
/* Fixed path lands in a plausible (if hot) range. */
|
||||
assert(t_fixed > 30.0f && t_fixed < 80.0f);
|
||||
|
||||
/* Buggy path is wildly negative — far outside any real sensor range. */
|
||||
assert(t_buggy < -100.0f);
|
||||
|
||||
/* The catastrophic divergence is the actual regression signal. */
|
||||
assert(fabsf(t_fixed - t_buggy) > 100.0f);
|
||||
|
||||
printf("PASS (fixed=%.1f, buggy=%.1f, delta=%.1f °C)\n",
|
||||
(double)t_fixed, (double)t_buggy,
|
||||
(double)fabsf(t_fixed - t_buggy));
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* T4: full uint16 range sweep — fixed path stays finite + monotonic-ish;
|
||||
* buggy path collapses across the 0x8000 boundary.
|
||||
* ------------------------------------------------------------------------- */
|
||||
static void test_t4_full_range_sweep(void)
|
||||
{
|
||||
printf(" T4: full uint16 sweep — fixed path finite, buggy collapses ... ");
|
||||
|
||||
int total_samples = 0;
|
||||
int upper_half_samples = 0;
|
||||
int buggy_collapses = 0;
|
||||
int fixed_finite = 0;
|
||||
|
||||
/* Sample raw values across the full uint16 range every 1024 LSB —
|
||||
* enough to exercise the 0x8000 boundary without spamming the log. */
|
||||
for (uint32_t raw32 = 0; raw32 <= 0xFFFF; raw32 += 1024) {
|
||||
uint16_t raw = (uint16_t)raw32;
|
||||
float t_fix = getTemperature_fixed(&DATASHEET_CAL, raw);
|
||||
float t_bug = getTemperature_buggy(&DATASHEET_CAL, raw);
|
||||
|
||||
total_samples++;
|
||||
if (isfinite(t_fix)) fixed_finite++;
|
||||
|
||||
/* Boundary crossing: at raw>=0x8000, the buggy path goes negative
|
||||
* (wildly) while the fixed path keeps climbing. */
|
||||
if (raw >= 0x8000) {
|
||||
upper_half_samples++;
|
||||
if (t_bug < -50.0f) buggy_collapses++;
|
||||
}
|
||||
}
|
||||
|
||||
assert(fixed_finite == total_samples); /* every sample finite under fixed path */
|
||||
assert(buggy_collapses == upper_half_samples); /* every upper-half sample collapsed under buggy path */
|
||||
|
||||
printf("PASS (fixed_finite=%d/%d, buggy_collapses=%d/%d upper-half)\n",
|
||||
fixed_finite, total_samples, buggy_collapses, upper_half_samples);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("=== AUDIT-C17: BMP180 sentinel separability + signed-cast fix ===\n");
|
||||
|
||||
test_t1_sentinel_separability();
|
||||
test_t2_datasheet_reference();
|
||||
test_t3_narrowing_regression();
|
||||
test_t4_full_range_sweep();
|
||||
|
||||
printf("=== ALL PASS ===\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user