mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
370 lines
11 KiB
Go
370 lines
11 KiB
Go
// Package bme280 provides a driver for the BME280 digital combined
|
|
// humidity and pressure sensor by Bosch.
|
|
//
|
|
// Datasheet:
|
|
// https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf
|
|
package bme280
|
|
|
|
import (
|
|
"math"
|
|
"time"
|
|
|
|
"tinygo.org/x/drivers"
|
|
"tinygo.org/x/drivers/internal/legacy"
|
|
)
|
|
|
|
// calibrationCoefficients reads at startup and stores the calibration coefficients
|
|
type calibrationCoefficients struct {
|
|
t1 uint16
|
|
t2 int16
|
|
t3 int16
|
|
p1 uint16
|
|
p2 int16
|
|
p3 int16
|
|
p4 int16
|
|
p5 int16
|
|
p6 int16
|
|
p7 int16
|
|
p8 int16
|
|
p9 int16
|
|
h1 uint8
|
|
h2 int16
|
|
h3 uint8
|
|
h4 int16
|
|
h5 int16
|
|
h6 int8
|
|
}
|
|
|
|
type Oversampling byte
|
|
type Mode byte
|
|
type FilterCoefficient byte
|
|
type Period byte
|
|
|
|
// Config contains settings for filtering, sampling, and modes of operation
|
|
type Config struct {
|
|
Pressure Oversampling
|
|
Temperature Oversampling
|
|
Humidity Oversampling
|
|
Period Period
|
|
Mode Mode
|
|
IIR FilterCoefficient
|
|
}
|
|
|
|
// Device wraps an I2C connection to a BME280 device.
|
|
type Device struct {
|
|
bus drivers.I2C
|
|
Address uint16
|
|
calibrationCoefficients calibrationCoefficients
|
|
Config Config
|
|
}
|
|
|
|
// New creates a new BME280 connection. The I2C bus must already be
|
|
// configured.
|
|
//
|
|
// This function only creates the Device object, it does not touch the device.
|
|
func New(bus drivers.I2C) Device {
|
|
return Device{
|
|
bus: bus,
|
|
Address: Address,
|
|
}
|
|
}
|
|
|
|
// ConfigureWithSettings sets up the device for communication and
|
|
// read the calibration coefficients.
|
|
//
|
|
// The default configuration is the Indoor Navigation settings
|
|
// from the BME280 datasheet.
|
|
func (d *Device) Configure() {
|
|
d.ConfigureWithSettings(Config{})
|
|
}
|
|
|
|
// ConfigureWithSettings sets up the device for communication and
|
|
// read the calibration coefficients.
|
|
//
|
|
// The default configuration if config is left at defaults is
|
|
// the Indoor Navigation settings from the BME280 datasheet.
|
|
func (d *Device) ConfigureWithSettings(config Config) {
|
|
d.Config = config
|
|
|
|
// If config is not initialized, use Indoor Navigation defaults.
|
|
if d.Config == (Config{}) {
|
|
d.Config = Config{
|
|
Mode: ModeNormal,
|
|
Period: Period0_5ms,
|
|
Temperature: Sampling2X,
|
|
Humidity: Sampling1X,
|
|
Pressure: Sampling16X,
|
|
IIR: Coeff16,
|
|
}
|
|
}
|
|
|
|
var data [24]byte
|
|
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CALIBRATION, data[:])
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var h1 [1]byte
|
|
err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_CALIBRATION_H1, h1[:])
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var h2lsb [7]byte
|
|
err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_CALIBRATION_H2LSB, h2lsb[:])
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
d.calibrationCoefficients.t1 = readUintLE(data[0], data[1])
|
|
d.calibrationCoefficients.t2 = readIntLE(data[2], data[3])
|
|
d.calibrationCoefficients.t3 = readIntLE(data[4], data[5])
|
|
d.calibrationCoefficients.p1 = readUintLE(data[6], data[7])
|
|
d.calibrationCoefficients.p2 = readIntLE(data[8], data[9])
|
|
d.calibrationCoefficients.p3 = readIntLE(data[10], data[11])
|
|
d.calibrationCoefficients.p4 = readIntLE(data[12], data[13])
|
|
d.calibrationCoefficients.p5 = readIntLE(data[14], data[15])
|
|
d.calibrationCoefficients.p6 = readIntLE(data[16], data[17])
|
|
d.calibrationCoefficients.p7 = readIntLE(data[18], data[19])
|
|
d.calibrationCoefficients.p8 = readIntLE(data[20], data[21])
|
|
d.calibrationCoefficients.p9 = readIntLE(data[22], data[23])
|
|
|
|
d.calibrationCoefficients.h1 = h1[0]
|
|
d.calibrationCoefficients.h2 = readIntLE(h2lsb[0], h2lsb[1])
|
|
d.calibrationCoefficients.h3 = h2lsb[2]
|
|
d.calibrationCoefficients.h6 = int8(h2lsb[6])
|
|
d.calibrationCoefficients.h4 = 0 + (int16(h2lsb[3]) << 4) | (int16(h2lsb[4] & 0x0F))
|
|
d.calibrationCoefficients.h5 = 0 + (int16(h2lsb[5]) << 4) | (int16(h2lsb[4]) >> 4)
|
|
|
|
d.Reset()
|
|
|
|
legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_CONFIG, []byte{byte(d.Config.Period<<5) | byte(d.Config.IIR<<2)})
|
|
legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_HUMIDITY_ADDR, []byte{byte(d.Config.Humidity)})
|
|
|
|
// Normal mode, start measuring now
|
|
if d.Config.Mode == ModeNormal {
|
|
legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_MEAS_ADDR, []byte{
|
|
byte(d.Config.Temperature<<5) |
|
|
byte(d.Config.Pressure<<2) |
|
|
byte(d.Config.Mode)})
|
|
}
|
|
}
|
|
|
|
// Connected returns whether a BME280 has been found.
|
|
// It does a "who am I" request and checks the response.
|
|
func (d *Device) Connected() bool {
|
|
data := []byte{0}
|
|
legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
|
|
return data[0] == CHIP_ID
|
|
}
|
|
|
|
// Reset the device
|
|
func (d *Device) Reset() {
|
|
legacy.WriteRegister(d.bus, uint8(d.Address), CMD_RESET, []byte{0xB6})
|
|
}
|
|
|
|
// SetMode can set the device to Sleep, Normal or Forced mode
|
|
//
|
|
// Calling this method is optional, Configure can be used to set the
|
|
// initial mode if no mode change is desired. This method is most
|
|
// useful to switch between Sleep and Normal modes.
|
|
func (d *Device) SetMode(mode Mode) {
|
|
d.Config.Mode = mode
|
|
|
|
legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_MEAS_ADDR, []byte{
|
|
byte(d.Config.Temperature<<5) |
|
|
byte(d.Config.Pressure<<2) |
|
|
byte(d.Config.Mode)})
|
|
}
|
|
|
|
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
|
|
func (d *Device) ReadTemperature() (int32, error) {
|
|
data, err := d.readData()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
temp, _ := d.calculateTemp(data)
|
|
return temp, nil
|
|
}
|
|
|
|
// ReadPressure returns the pressure in milli pascals mPa
|
|
func (d *Device) ReadPressure() (int32, error) {
|
|
data, err := d.readData()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
_, tFine := d.calculateTemp(data)
|
|
pressure := d.calculatePressure(data, tFine)
|
|
return pressure, nil
|
|
}
|
|
|
|
// ReadHumidity returns the relative humidity in hundredths of a percent
|
|
func (d *Device) ReadHumidity() (int32, error) {
|
|
data, err := d.readData()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
_, tFine := d.calculateTemp(data)
|
|
humidity := d.calculateHumidity(data, tFine)
|
|
return humidity, nil
|
|
}
|
|
|
|
// ReadAltitude returns the current altitude in meters based on the
|
|
// current barometric pressure and estimated pressure at sea level.
|
|
// Calculation is based on code from Adafruit BME280 library
|
|
//
|
|
// https://github.com/adafruit/Adafruit_BME280_Library
|
|
func (d *Device) ReadAltitude() (alt int32, err error) {
|
|
mPa, _ := d.ReadPressure()
|
|
atmP := float32(mPa) / 100000
|
|
alt = int32(44330.0 * (1.0 - math.Pow(float64(atmP/SEALEVEL_PRESSURE), 0.1903)))
|
|
return
|
|
}
|
|
|
|
// convert2Bytes converts two bytes to int32
|
|
func convert2Bytes(msb byte, lsb byte) int32 {
|
|
return int32(readUint(msb, lsb))
|
|
}
|
|
|
|
// convert3Bytes converts three bytes to int32
|
|
func convert3Bytes(msb byte, b1 byte, lsb byte) int32 {
|
|
return int32(((((uint32(msb) << 8) | uint32(b1)) << 8) | uint32(lsb)) >> 4)
|
|
}
|
|
|
|
// readUint converts two bytes to uint16
|
|
func readUint(msb byte, lsb byte) uint16 {
|
|
return (uint16(msb) << 8) | uint16(lsb)
|
|
}
|
|
|
|
// readUintLE converts two little endian bytes to uint16
|
|
func readUintLE(msb byte, lsb byte) uint16 {
|
|
temp := readUint(msb, lsb)
|
|
return (temp >> 8) | (temp << 8)
|
|
}
|
|
|
|
// readIntLE converts two little endian bytes to int16
|
|
func readIntLE(msb byte, lsb byte) int16 {
|
|
return int16(readUintLE(msb, lsb))
|
|
}
|
|
|
|
// readData does a burst read from 0xF7 to 0xF0 according to the datasheet
|
|
// resulting in an slice with 8 bytes 0-2 = pressure / 3-5 = temperature / 6-7 = humidity
|
|
func (d *Device) readData() (data [8]byte, err error) {
|
|
if d.Config.Mode == ModeForced {
|
|
// Write the CTRL_MEAS register to trigger a measurement
|
|
legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_MEAS_ADDR, []byte{
|
|
byte(d.Config.Temperature<<5) |
|
|
byte(d.Config.Pressure<<2) |
|
|
byte(d.Config.Mode)})
|
|
|
|
time.Sleep(d.measurementDelay())
|
|
}
|
|
|
|
err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_PRESSURE, data[:])
|
|
if err != nil {
|
|
println(err)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// calculateTemp uses the data slice and applies calibrations values on it to convert the value to milli degrees
|
|
// it also calculates the variable tFine which is used by the pressure and humidity calculation
|
|
func (d *Device) calculateTemp(data [8]byte) (int32, int32) {
|
|
|
|
rawTemp := convert3Bytes(data[3], data[4], data[5])
|
|
|
|
var1 := (((rawTemp >> 3) - (int32(d.calibrationCoefficients.t1) << 1)) * int32(d.calibrationCoefficients.t2)) >> 11
|
|
var2 := (((((rawTemp >> 4) - int32(d.calibrationCoefficients.t1)) * ((rawTemp >> 4) - int32(d.calibrationCoefficients.t1))) >> 12) * int32(d.calibrationCoefficients.t3)) >> 14
|
|
|
|
tFine := var1 + var2
|
|
T := (tFine*5 + 128) >> 8
|
|
return (10 * T), tFine
|
|
}
|
|
|
|
// calculatePressure uses the data slice and applies calibrations values on it to convert the value to milli pascals mPa
|
|
func (d *Device) calculatePressure(data [8]byte, tFine int32) int32 {
|
|
|
|
rawPressure := convert3Bytes(data[0], data[1], data[2])
|
|
|
|
var1 := int64(tFine) - 128000
|
|
var2 := var1 * var1 * int64(d.calibrationCoefficients.p6)
|
|
var2 = var2 + ((var1 * int64(d.calibrationCoefficients.p5)) << 17)
|
|
var2 = var2 + (int64(d.calibrationCoefficients.p4) << 35)
|
|
var1 = ((var1 * var1 * int64(d.calibrationCoefficients.p3)) >> 8) + ((var1 * int64(d.calibrationCoefficients.p2)) << 12)
|
|
var1 = ((int64(1) << 47) + var1) * int64(d.calibrationCoefficients.p1) >> 33
|
|
|
|
if var1 == 0 {
|
|
return 0 // avoid exception caused by division by zero
|
|
}
|
|
p := int64(1048576 - rawPressure)
|
|
p = (((p << 31) - var2) * 3125) / var1
|
|
var1 = (int64(d.calibrationCoefficients.p9) * (p >> 13) * (p >> 13)) >> 25
|
|
var2 = (int64(d.calibrationCoefficients.p8) * p) >> 19
|
|
|
|
p = ((p + var1 + var2) >> 8) + (int64(d.calibrationCoefficients.p7) << 4)
|
|
p = (p / 256)
|
|
return int32(1000 * p)
|
|
}
|
|
|
|
// calculateHumidity uses the data slice and applies calibrations values on it to convert the value to relative humidity in hundredths of a percent
|
|
func (d *Device) calculateHumidity(data [8]byte, tFine int32) int32 {
|
|
|
|
rawHumidity := convert2Bytes(data[6], data[7])
|
|
|
|
h := float32(tFine) - 76800
|
|
|
|
if h == 0 {
|
|
println("invalid value")
|
|
}
|
|
|
|
var1 := float32(rawHumidity) - (float32(d.calibrationCoefficients.h4)*64.0 +
|
|
(float32(d.calibrationCoefficients.h5) / 16384.0 * h))
|
|
|
|
var2 := float32(d.calibrationCoefficients.h2) / 65536.0 *
|
|
(1.0 + float32(d.calibrationCoefficients.h6)/67108864.0*h*
|
|
(1.0+float32(d.calibrationCoefficients.h3)/67108864.0*h))
|
|
|
|
h = var1 * var2
|
|
h = h * (1 - float32(d.calibrationCoefficients.h1)*h/524288)
|
|
return int32(100 * h)
|
|
|
|
}
|
|
|
|
// measurementDelay returns how much time each measurement will take
|
|
// on the device.
|
|
//
|
|
// This is used in forced mode to wait until a measurement is complete.
|
|
func (d *Device) measurementDelay() time.Duration {
|
|
const MeasOffset = 1250
|
|
const MeasDur = 2300
|
|
const HumMeasOffset = 575
|
|
const MeasScalingFactor = 1000
|
|
|
|
// delay is based on over-sampling rate - this table converts from
|
|
// setting to number samples
|
|
sampleRateConv := []int{0, 1, 2, 4, 8, 16}
|
|
|
|
tempOsr := 16
|
|
if d.Config.Temperature <= Sampling16X {
|
|
tempOsr = sampleRateConv[d.Config.Temperature]
|
|
}
|
|
|
|
presOsr := 16
|
|
if d.Config.Temperature <= Sampling16X {
|
|
presOsr = sampleRateConv[d.Config.Pressure]
|
|
}
|
|
|
|
humOsr := 16
|
|
if d.Config.Temperature <= Sampling16X {
|
|
humOsr = sampleRateConv[d.Config.Humidity]
|
|
}
|
|
|
|
max_delay := ((MeasOffset + (MeasDur * tempOsr) +
|
|
((MeasDur * presOsr) + HumMeasOffset) +
|
|
((MeasDur * humOsr) + HumMeasOffset)) / MeasScalingFactor)
|
|
|
|
return time.Duration(max_delay) * time.Millisecond
|
|
}
|