// Copyright 2026 The Periph Authors. All rights reserved. // Use of this source code is governed under the Apache License, Version 2.0 // that can be found in the LICENSE file. package bme68x import ( "fmt" ) // gasRangeR represents the gas sensor range index 0 - 15 type gasRangeR uint8 // gasConstant List of gas ranges and corresponding constants used for the resistance calculation // Table 16 of Datasheet var gasConstant = map[gasRangeR][]uint32{ 0: {2147483647, 4096000000}, 1: {2147483647, 2048000000}, 2: {2147483647, 1024000000}, 3: {2147483647, 512000000}, 4: {2147483647, 255744255}, 5: {2126008810, 127110228}, 6: {2147483647, 64000000}, 7: {2130303777, 32258064}, 8: {2147483647, 16016016}, 9: {2147483647, 8000000}, 10: {2143188679, 4000000}, 11: {2136746228, 2000000}, 12: {2147483647, 1000000}, 13: {2126008810, 500000}, 14: {2147483647, 250000}, 15: {2147483647, 125000}, } // SensorCalibration holds the calibration coefficients read from a BME680 sensor. // These values are used internally to convert raw sensor readings into // accurate temperature, pressure, humidity, and gas measurements. type SensorCalibration struct { t1 uint16 t2 int16 t3 int8 p1 uint16 p2, p4, p5, p8, p9 int16 p3, p6, p7 int8 p10 uint8 h1, h2 uint16 h3, h4, h5, h6, h7 int8 g1, g3 int8 g2 int16 resHeatVal, resHeatRange, switchingErr uint8 tFine int32 tempComp int32 pressureComp int32 humidityComp int32 } // InitCalibration loads the sensor's calibration data for accurate measurements. func (dev *Device) InitCalibration() error { // Read all calibration registers t, err := dev.regRead(regParT1, 2) if err != nil { return err } tp, err := dev.regRead(regParT2, 23) if err != nil { return err } h, err := dev.regRead(regParH2, 8) if err != nil { return err } g, err := dev.regRead(regParG2, 4) if err != nil { return err } r, err := dev.regRead(regResHeatVal, 5) if err != nil { return err } // Combined length check if len(t) < 2 || len(tp) < 23 || len(h) < 8 || len(g) < 4 || len(r) < 5 { return fmt.Errorf("calibration data incomplete: t=%d, tp=%d, h=%d, g=%d, r=%d", len(t), len(tp), len(h), len(g), len(r)) } // Populate calibration struct dev.c = SensorCalibration{} dev.c.t1 = uint16(t[1])<<8 | uint16(t[0]) dev.c.t2 = int16(tp[1])<<8 | int16(tp[0]) dev.c.t3 = int8(tp[2]) dev.c.p1 = uint16(tp[5])<<8 | uint16(tp[4]) dev.c.p2 = int16(tp[7])<<8 | int16(tp[6]) dev.c.p3 = int8(tp[8]) dev.c.p4 = int16(tp[11])<<8 | int16(tp[10]) dev.c.p5 = int16(tp[13])<<8 | int16(tp[12]) dev.c.p7 = int8(tp[14]) dev.c.p6 = int8(tp[15]) dev.c.p8 = int16(tp[19])<<8 | int16(tp[18]) dev.c.p9 = int16(tp[21])<<8 | int16(tp[20]) dev.c.p10 = tp[22] dev.c.h1 = uint16(h[2])<<4 | uint16(h[1]&0xF) dev.c.h2 = uint16(h[0])<<4 | (uint16(h[1]&0xF0) >> 4) dev.c.h3 = int8(h[3]) dev.c.h4 = int8(h[4]) dev.c.h5 = int8(h[5]) dev.c.h6 = int8(h[6]) dev.c.h7 = int8(h[7]) dev.c.g2 = int16(g[1])<<8 | int16(g[0]) dev.c.g1 = int8(g[2]) dev.c.g3 = int8(g[3]) dev.c.resHeatVal = r[0] dev.c.resHeatRange = (uint8(r[1]) & 0x30) >> 4 dev.c.switchingErr = r[4] return nil } // compensatedTemperature in degrees Celsius, BME680: Refer Integer Section 3.3.1 of Datasheet func (dev *Device) compensatedTemperature(tempAdc uint32) int32 { var1 := (int32(tempAdc) >> 3) - (int32(dev.c.t1) << 1) var2 := (var1 * int32(dev.c.t2)) >> 11 var3 := ((((var1 >> 1) * (var1 >> 1)) >> 12) * (int32(dev.c.t3) << 4)) >> 14 dev.c.tFine = var2 + var3 tempComp := ((dev.c.tFine * 5) + 128) >> 8 dev.c.tempComp = tempComp return dev.c.tempComp } // compensatedPressure in Pascal, Refer Integer Section 3.3.2 of Datasheet func (dev *Device) compensatedPressure(pressureAdc uint32) int32 { var1 := (dev.c.tFine >> 1) - 64000 var2 := ((((var1 >> 2) * (var1 >> 2)) >> 11) * int32(dev.c.p6)) >> 2 var2 = var2 + ((var1 * int32(dev.c.p5)) << 1) var2 = (var2 >> 2) + (int32(dev.c.p4) << 16) var1 = (((((var1 >> 2) * (var1 >> 2)) >> 13) * (int32(dev.c.p3) << 5)) >> 3) + ((int32(dev.c.p2) * var1) >> 1) var1 = var1 >> 18 var1 = ((32768 + var1) * int32(dev.c.p1)) >> 15 pressComp := 1048576 - int32(pressureAdc) pressComp = (pressComp - (var2 >> 12)) * (int32(3125)) if pressComp >= (1 << 30) { pressComp = (pressComp / var1) << 1 } else { pressComp = (pressComp << 1) / var1 } var1 = (int32(dev.c.p9) * (((pressComp >> 3) * (pressComp >> 3)) >> 13)) >> 12 var2 = ((pressComp >> 2) * int32(dev.c.p8)) >> 13 var3 := ((pressComp >> 8) * (pressComp >> 8) * (pressComp >> 8) * int32(dev.c.p10)) >> 17 dev.c.pressureComp = pressComp + ((var1 + var2 + var3 + (int32(dev.c.p7) << 7)) >> 4) return dev.c.pressureComp } // compensatedHumidity %RH, Refer Integer Section 3.3.3 of Datasheet func (dev *Device) compensatedHumidity(humidityAdc uint32) int32 { tempScaled := dev.c.tempComp var1 := int32(humidityAdc) - int32(dev.c.h1)<<4 - (((tempScaled * int32(dev.c.h3)) / (int32(100))) >> 1) var2 := (int32(dev.c.h2) * (((tempScaled * int32(dev.c.h4)) / (int32(100))) + (((tempScaled * ((tempScaled * int32(dev.c.h5)) / (int32(100)))) >> 6) / (int32(100))) + (int32(1 << 14)))) >> 10 var3 := var1 * var2 var4 := ((int32(dev.c.h6) << 7) + ((tempScaled * int32(dev.c.h7)) / (int32(100)))) >> 4 var5 := ((var3 >> 14) * (var3 >> 14)) >> 10 var6 := (var4 * var5) >> 1 dev.c.humidityComp = (((var3 + var6) >> 10) * (int32(1000))) >> 12 return dev.c.humidityComp } // compensatedGasSensor resistance in Ohms, Refer Integer Section 3.4.1 of Datasheet func (dev *Device) compensatedGasSensor(gasAdc uint32, gasRange gasRangeR) int32 { if dev.variant == VariantNameBME680 { var1 := ((1340 + (5 * int64(dev.c.switchingErr))) * (int64(gasConstant[gasRange][0]))) >> 16 var2 := int64(gasAdc<<15) - int64(1<<24) + var1 // Prevent Division by Zero if var2 == 0 { return 0 } gasRes := int32(((int64(gasConstant[gasRange][1]) * var1 >> 9) + (var2 >> 1)) / var2) return gasRes } return 0 } // multiplicationFactor maps the GAS_WAIT register bits <7:6> to the corresponding // gas sensor wait time multiplication factor as defined in the datasheet. // Bits <7:6> | Wait time factor (ms) var multiplicationFactor = map[uint8]uint8{ 1: 0 << 6, // 00 -> factor 1 4: 1 << 6, // 01 -> factor 4 16: 2 << 6, // 10 -> factor 16 64: 3 << 6, // 11 -> factor 64 } // gasWaitXCalculationForcedMode time between the beginning of the heat phase and the start of gas sensor resistance conversion // Bits <7:6> = multiplication factor (1, 4, 16, 64), bits <5:0> = wait time in 1 ms steps(0 to 63/252/1008/4032 depending on factor). // Refer Section 5.3.3.3 of datasheet func (dev *Device) gasWaitXCalculationForcedMode(profile uint8) uint8 { var waitMS = dev.cfg.GasProfiles[profile].HeatingDurationMs var scale uint16 = 0 switch { case waitMS >= 1 && waitMS <= 63: scale = 1 case waitMS >= 64 && waitMS <= 252: scale = 4 case waitMS >= 253 && waitMS <= 1008: scale = 16 case waitMS >= 1009 && waitMS <= 4032: scale = 64 default: // BME680: Section 3.3.5 - In practice, approximately 20–30 ms are necessary for the heater to reach // the intended target temperature scale = 4 waitMS = 100 } gasWaitX := multiplicationFactor[uint8(scale)] | uint8((waitMS+scale-1)/scale) return gasWaitX } // resHeatXCalculation convert the target temperature into a device specific target resistance before writing the resulting register code into the sensor // memory map. // Refer Section 3.3.5 of datasheet func (dev *Device) resHeatXCalculation(profile uint8) uint8 { // Max limit of Target is 400 °C if dev.cfg.GasProfiles[profile].TargetTempC > 400 { dev.cfg.GasProfiles[profile].TargetTempC = 400 } var1 := ((int32(dev.cfg.AmbientTempC) * int32(dev.c.g3)) / 10) << 8 var2 := (int32(dev.c.g1) + 784) * (((((int32(dev.c.g2) + 154009) * int32(dev.cfg.GasProfiles[profile].TargetTempC) * 5) / 100) + 3276800) / 10) var3 := var1 + (var2 >> 1) var4 := var3 / (int32(dev.c.resHeatRange) + 4) var5 := (131 * int32(dev.c.resHeatVal)) + 65536 resHeatX100 := ((var4 / var5) - 250) * 34 resHeatX := uint8((resHeatX100 + 50) / 100) // rounds to the nearest integer. return resHeatX }