You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
devices/bme68x/calibration.go

230 lines
8.4 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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 2030 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
}