Skip to content

Commit

Permalink
Fixed a couple of incorrect equations in compensation calculations
Browse files Browse the repository at this point in the history
  • Loading branch information
asasine committed May 25, 2024
1 parent 689ac1f commit da1d253
Showing 1 changed file with 48 additions and 36 deletions.
84 changes: 48 additions & 36 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,14 @@ impl From<Address> for u8 {
}
}

/// Output from the BMP390 consists of ADC outputs.
///
/// These must be compensated using formulas from the datasheet to obtain the actual temperature and pressure values,
/// using coefficients stored in non-volatile memory (NVM).
///
/// # Datasheet
/// - Section 3.11 Output compensation.
/// - Appendix A: Computation formulae reference implementation.
#[derive(Debug, Clone, Copy, Format)]
struct CalibrationCoefficients {
par_t1: f32,
Expand All @@ -222,6 +230,8 @@ struct CalibrationCoefficients {
}

impl CalibrationCoefficients {
/// Read the calibration coefficients from the BMP390's NVM registers and convert them to into a set of
/// floating-point calibration coefficients for the formulas implemented in the compensation functions.
async fn try_from_i2c<I: I2c>(address: Address, i2c: &mut I) -> Result<Self, Error<I::Error>> {
let mut calibration_coefficient_regs = [0; 21];
i2c.write_read(
Expand All @@ -238,7 +248,8 @@ impl CalibrationCoefficients {
/// Calculate the calibration coefficients from the raw register data in registers [`Register::NVM_PAR_T1_0`] to
/// [`Register::NVM_PAR_P11`].
///
/// See: Datasheet Apendix A, Section 8.4
/// # Datasheet
/// Apendix A, Section 8.4
fn from_registers(data: &[u8; 21]) -> Self {
trace!("NVM_PAR: {=[u8]:#04x}", *data);
let nvm_par_t1: u16 = (data[1] as u16) << 8 | data[0] as u16;
Expand All @@ -263,50 +274,54 @@ impl CalibrationCoefficients {
par_p1: ((nvm_par_p1 as f32) - 16_384.0) / 1_048_576.0, // 2^14 / 2^20
par_p2: ((nvm_par_p2 as f32) - 16_384.0) / 536_870_912.0, // 2^14 / 2^29
par_p3: (nvm_par_p3 as f32) / 4_294_967_296.0, // 2^32
par_p4: (nvm_par_p4 as f32) / 137_438_953_427.0, // 2^37
par_p4: (nvm_par_p4 as f32) / 137_438_953_472.0, // 2^37
par_p5: (nvm_par_p5 as f32) / 0.125, // 2^-3
par_p6: (nvm_par_p6 as f32) / 64.0, // 2^6
par_p7: (nvm_par_p7 as f32) / 256.0, // 2^8
par_p8: (nvm_par_p8 as f32) / 32768.0, // 2^15
par_p9: (nvm_par_p9 as f32) / 81_474_976_710_656.0, //2^48
par_p10: (nvm_par_p10 as f32) / 81_474_976_710_656.0, // 2^48
par_p9: (nvm_par_p9 as f32) / 281_474_976_710_656.0, //2^48
par_p10: (nvm_par_p10 as f32) / 281_474_976_710_656.0, // 2^48
par_p11: (nvm_par_p11 as f32) / 36_893_488_147_419_103_232.0, // 2^65
}
}

/// Compensate a temperature reading according to calibration coefficients.
///
/// See: Datasheet Apendix A, Section 8.5
fn compensate_temperature(&self, temperature_uncompensated: i32) -> ThermodynamicTemperature {
/// # Datasheet
/// Apendix A, Section 8.5
fn compensate_temperature(&self, temperature_uncompensated: u32) -> ThermodynamicTemperature {
// This could be done in fewer expressions, but it's broken down for clarity and to match the datasheet
let uncompensated = temperature_uncompensated as f32;
let partial_1 = uncompensated - self.par_t1;
let partial_2 = partial_1 * self.par_t2;
let temperature = partial_2 + (partial_1 * partial_1) * self.par_t3;
let partial_data1 = uncompensated - self.par_t1;
let partial_data2 = partial_data1 * self.par_t2;
let temperature = partial_data2 + (partial_data1 * partial_data1) * self.par_t3;
ThermodynamicTemperature::new::<degree_celsius>(temperature)
}

/// Compensate a pressure reading according to calibration coefficients.
///
/// See: Datasheet Apendix A, Section 8.6
fn compensate_pressure(&self, temperature: ThermodynamicTemperature, pressure_uncompensated: i32) -> Pressure {
/// # Datasheet
/// Apendix A, Section 8.6
fn compensate_pressure(&self, temperature: ThermodynamicTemperature, pressure_uncompensated: u32) -> Pressure {
// This could be done in fewer expressions, but it's broken down for clarity and to match the datasheet
let uncompensated = pressure_uncompensated as f32;
let temperature = temperature.get::<degree_celsius>();
let partial_1 = self.par_p6 * temperature;
let partial_2 = self.par_p7 * temperature * temperature;
let partial_3 = self.par_p8 * temperature * temperature * temperature;
let partial_out1 = self.par_p5 + partial_1 + partial_2 + partial_3;

let partial_1 = self.par_p2 * temperature;
let partial_2 = self.par_p3 * temperature * temperature;
let partial_3 = self.par_p4 * temperature * temperature * temperature;
let partial_out2 = uncompensated * (self.par_p1 + partial_1 + partial_2 + partial_3);

let partial_1 = uncompensated * uncompensated;
let partial_2 = self.par_p9 + self.par_p10 * temperature;
let partial_3 = partial_1 * partial_2;
let partial_4 = partial_3 + temperature * temperature * temperature * temperature * self.par_p11;

let pressure = partial_out1 + partial_out2 + partial_4;
let partial_data1 = self.par_p6 * temperature;
let partial_data2 = self.par_p7 * temperature * temperature;
let partial_data3 = self.par_p8 * temperature * temperature * temperature;
let partial_out1 = self.par_p5 + partial_data1 + partial_data2 + partial_data3;

let partial_data1 = self.par_p2 * temperature;
let partial_data2 = self.par_p3 * temperature * temperature;
let partial_data3 = self.par_p4 * temperature * temperature * temperature;
let partial_out2 = uncompensated * (self.par_p1 + partial_data1 + partial_data2 + partial_data3);

let partial_data1 = uncompensated * uncompensated;
let partial_data2 = self.par_p9 + self.par_p10 * temperature;
let partial_data3 = partial_data1 * partial_data2;
let partial_data4 = partial_data3 + uncompensated * uncompensated * uncompensated * self.par_p11;

let pressure = partial_out1 + partial_out2 + partial_data4;
Pressure::new::<pascal>(pressure)
}

Expand Down Expand Up @@ -475,11 +490,8 @@ where
.map_err(Error::I2c)?;

// DATA_3 is the LSB, DATA_5 is the MSB
let temperature = i32::from(read[0]) | i32::from(read[1]) << 8 | i32::from(read[2]) << 16;
let temperature = self
.coefficients
.compensate_temperature(temperature);

let temperature = u32::from(read[0]) | u32::from(read[1]) << 8 | u32::from(read[2]) << 16;
let temperature = self.coefficients.compensate_temperature(temperature);
Ok(temperature)
}

Expand All @@ -502,10 +514,10 @@ where
trace!("DATA = {=[u8]:#04x}", read);

// pressure is 0:2 (XLSB, LSB, MSB), temperature is 3:5 (XLSB, LSB, MSB)
let temperature = i32::from(read[3]) | i32::from(read[4]) << 8 | i32::from(read[5]) << 16;
let temperature = u32::from(read[3]) | u32::from(read[4]) << 8 | u32::from(read[5]) << 16;
let temperature = self.coefficients.compensate_temperature(temperature);

let pressure = i32::from(read[0]) | i32::from(read[1]) << 8 | i32::from(read[2]) << 16;
let pressure = u32::from(read[0]) | u32::from(read[1]) << 8 | u32::from(read[2]) << 16;
let pressure = self.coefficients.compensate_pressure(temperature, pressure);

Ok(Measurement {
Expand Down Expand Up @@ -541,7 +553,7 @@ mod tests {

/// The [`Measurement::pressure`] value for [`PRESSURE_TEMPERATURE_BYTES`] when compensated by [`CalibrationCoefficients::default()`].
fn expected_pressure() -> Pressure {
Pressure::new::<pascal>(100_207.97)
Pressure::new::<pascal>(98370.55)
}

/// Bytes for the DATA registers (0x07 .. 0x09) for a temperature measurement.
Expand All @@ -553,7 +565,7 @@ mod tests {
}

fn expected_altitude() -> Length {
Length::new::<meter>(93.36266)
Length::new::<meter>(248.78754)
}

impl Default for CalibrationCoefficients {
Expand Down

0 comments on commit da1d253

Please sign in to comment.