All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.devices.bosch.Bme280.kt Maven / Gradle / Ivy

@file:OptIn(ExperimentalUnsignedTypes::class)
@file:Suppress("SpellCheckingInspection")

package ch.softappeal.konapi.devices.bosch

import ch.softappeal.konapi.I2cDevice
import ch.softappeal.konapi.sleepMs

/*
    Combined digital humidity, pressure and temperature sensor

    Datasheet: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf
    API      : https://github.com/boschsensortec/BME280_SensorAPI/blob/master/bme280.c
 */

private class TemperatureCalib(
    val t1: Double,
    val t2: Double,
    val t3: Double,
)

private class PressureCalib(
    val p1: Double,
    val p2: Double,
    val p3: Double,
    val p4: Double,
    val p5: Double,
    val p6: Double,
    val p7: Double,
    val p8: Double,
    val p9: Double,
)

private class HumidityCalib(
    val h1: Double,
    val h2: Double,
    val h3: Double,
    val h4: Double,
    val h5: Double,
    val h6: Double,
)

private data class TemperatureCompensateResult(val temperaturInCelsius: Double, val tFine: Double)

private fun TemperatureCalib.compensate(t: Double): TemperatureCompensateResult {
    // API: compensate_temperature
    val var1 = (t / 16_384.0 - t1 / 1024.0) * t2
    var var2 = t / 131_072.0 - t1 / 8192.0
    var2 *= var2 * t3
    val tFine = var1 + var2
    return TemperatureCompensateResult(temperaturInCelsius = (tFine / 5120.0).coerceIn(-40.0, 85.0), tFine)
}

private fun PressureCalib.compensate(p: Double, tFine: Double): Double {
    // API: compensate_pressure
    var var1 = tFine / 2.0 - 64_000.0
    var var2 = var1 * var1 * p6 / 32_768.0
    var2 += var1 * p5 * 2.0
    var2 = var2 / 4.0 + p4 * 65_536.0
    val var3 = p3 * var1 * var1 / 524_288.0
    var1 = (var3 + p2 * var1) / 524_288.0
    var1 = (1.0 + var1 / 32_768.0) * p1
    return if (var1 <= 0.0) 30_000.0 else {
        var pressure = 1048_576.0 - p
        pressure = (pressure - var2 / 4096.0) * 6250.0 / var1
        var1 = p9 * pressure * pressure / 2147_483_648.0
        var2 = pressure * p8 / 32_768.0
        pressure += (var1 + var2 + p7) / 16.0
        pressure.coerceIn(30_000.0, 110_000.0)
    }
}

private fun HumidityCalib.compensate(h: Double, tFine: Double): Double {
    // API: compensate_humidity
    val var1 = tFine - 76_800.0
    val var2 = h4 * 64.0 + h5 / 16_384.0 * var1
    val var3 = h - var2
    val var4 = h2 / 65_536.0
    val var5 = 1.0 + h3 / 67_108_864.0 * var1
    var var6 = 1.0 + h6 / 67_108_864.0 * var1 * var5
    var6 *= var3 * var4 * var5
    return (var6 * (1.0 - h1 * var6 / 524_288.0)).coerceIn(0.0, 100.0)
}

public class Bme280(private val device: I2cDevice) {
    private val temperatureCalib: TemperatureCalib
    private val pressureCalib: PressureCalib
    private val humidityCalib: HumidityCalib

    private fun setupMode() {
        /*
            Datasheet:
                Table 7: Settings and performance for weather monitoring
                5.4.3 Register 0xF2 "ctrl_hum"
                    Changes to this register only become effective after a write operation to "ctrl_meas".
                5.4.5 Register 0xF4 "ctrl_meas"
         */
        device.write(
            0xF2U, // ctrl_hum
            0x01U // Humidity oversampling x1
        )
        device.write(
            0xF4U, // ctrl_meas
            0x01U.toUByte() or // Forced mode
                0x04U or // Pressure oversampling x1
                0x20U // Temperature oversampling x1
        )
    }

    init {
        device.write(0xE0U, 0xB6U) // reset
        sleepMs(300) // power-on delay
        check(device.read(0xD0U) == 0x60U.toUByte()) { "chipId isn't BME280" }
        setupMode()
        /*
            Datasheet:
                4.2.2 Trimming parameter readout:
                    The trimming parameters are programmed into the devices non-volatile memory (NVM) during
                    production and cannot be altered by the customer.
                Table 16: Compensation parameter storage, naming and data type
                Table 18: Memory map
            API:
                parse_temp_press_calib_data
                parse_humidity_calib_data
         */
        fun UByteArray.toShort(offset: Int) = ((this[offset + 1].toByte().toInt() shl 8) or this[offset].toInt()).toDouble()
        fun UByteArray.toUShort(offset: Int) = ((this[offset + 1].toInt() shl 8) or this[offset].toInt()).toDouble()
        val h1: Double
        with(device.read(0x88U, 26)) { // calib00to25
            temperatureCalib = TemperatureCalib(
                t1 = toUShort(0),
                t2 = toShort(2),
                t3 = toShort(4),
            )
            pressureCalib = PressureCalib(
                p1 = toUShort(6),
                p2 = toShort(8),
                p3 = toShort(10),
                p4 = toShort(12),
                p5 = toShort(14),
                p6 = toShort(16),
                p7 = toShort(18),
                p8 = toShort(20),
                p9 = toShort(22),
            )
            h1 = this[25].toInt().toDouble()
        }
        humidityCalib = with(device.read(0xE1U, 7)) { // calib26to32
            HumidityCalib(
                h1 = h1,
                h2 = toShort(0),
                h3 = this[2].toInt().toDouble(),
                h4 = ((this[3].toByte().toInt() shl 4) or (this[4].toInt() and 0x0F)).toDouble(),
                h5 = ((this[5].toByte().toInt() shl 4) or (this[4].toInt() shr 4)).toDouble(),
                h6 = this[6].toByte().toInt().toDouble(),
            )
        }
    }

    public data class Measurement(
        public val temperaturInCelsius: Double,
        public val pressureInPascal: Double,
        public val humidityInPercent: Double,
    )

    public fun measurement(): Measurement {
        setupMode()
        /*
            Datasheet:
                4. Data readout
                Table 29: Register 0xF7 ... 0xF9 "press"
                Table 30: Register 0xFA ... 0xFC "temp"
                Table 31: Register 0xFD ... 0xFE "hum"
                Table 18: Memory map
            API: parse_sensor_data
         */
        val adc = device.read(0xF7U, 8)
        fun toUShort(offset: Int) =
            ((adc[offset].toInt() shl 12) or (adc[offset + 1].toInt() shl 4) or (adc[offset + 2].toInt() shr 4)).toDouble()
        val (temperaturInCelsius, tFine) = temperatureCalib.compensate(toUShort(3))
        return Measurement(
            temperaturInCelsius = temperaturInCelsius,
            pressureInPascal = pressureCalib.compensate(toUShort(0), tFine),
            humidityInPercent = humidityCalib.compensate(((adc[6].toInt() shl 8) or adc[7].toInt()).toDouble(), tFine)
        )
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy