mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-04 23:17:47 +00:00
6763521eff
This type should be used whenever a sensor (or actuator?) works with a
temperature. For example, this commit changes the signature:
ReadTemperature() (int32, error)
to the following:
ReadTemperature() (drivers.Temperature, error)
I believe this is much clearer in intent. It also makes it trivial to
introduce common conversions. For example, there are already Celsius()
and Fahrenheit() methods to convert to the given units, as a floating
point. More units could be added as needed, for example a CelsiusInt().
250 lines
7.7 KiB
Go
250 lines
7.7 KiB
Go
package bmp388
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"tinygo.org/x/drivers"
|
|
)
|
|
|
|
var (
|
|
errConfigWrite = errors.New("bmp388: failed to configure sensor, check connection")
|
|
errConfig = errors.New("bmp388: there is a problem with the configuration, try reducing ODR")
|
|
errCaliRead = errors.New("bmp388: failed to read calibration coefficient register")
|
|
errSoftReset = errors.New("bmp388: failed to perform a soft reset")
|
|
errNotConnected = errors.New("bmp388: not connected")
|
|
)
|
|
|
|
type Oversampling byte
|
|
type Mode byte
|
|
type OutputDataRate byte
|
|
type FilterCoefficient byte
|
|
|
|
// Config contains settings for filtering, sampling, and modes of operation
|
|
type Config struct {
|
|
Pressure Oversampling
|
|
Temperature Oversampling
|
|
Mode Mode
|
|
ODR OutputDataRate
|
|
IIR FilterCoefficient
|
|
}
|
|
|
|
// Device wraps the I2C connection and configuration values for the BMP388
|
|
type Device struct {
|
|
bus drivers.I2C
|
|
Address uint8
|
|
cali calibrationCoefficients
|
|
Config Config
|
|
}
|
|
|
|
type calibrationCoefficients struct {
|
|
// Temperature compensation
|
|
t1 uint16
|
|
t2 uint16
|
|
t3 int8
|
|
|
|
// Pressure compensation
|
|
p1 int16
|
|
p2 int16
|
|
p3 int8
|
|
p4 int8
|
|
p5 uint16
|
|
p6 uint16
|
|
p7 int8
|
|
p8 int8
|
|
p9 int16
|
|
p10 int8
|
|
p11 int8
|
|
}
|
|
|
|
// New returns a bmp388 struct with the default I2C address. Configure must also be called after instanting
|
|
func New(bus drivers.I2C) Device {
|
|
return Device{
|
|
bus: bus,
|
|
Address: Address,
|
|
}
|
|
}
|
|
|
|
// Configure can enable settings on the BMP388 and reads the calibration coefficients
|
|
func (d *Device) Configure(config Config) (err error) {
|
|
d.Config = config
|
|
|
|
if d.Config == (Config{}) {
|
|
d.Config.Mode = Normal
|
|
}
|
|
|
|
// Turning on the pressure and temperature sensors and setting the measurement mode
|
|
err = d.writeRegister(RegPwrCtrl, PwrPress|PwrTemp|byte(d.Config.Mode))
|
|
|
|
// Configure the oversampling, output data rate, and iir filter coefficient settings
|
|
err = d.writeRegister(RegOSR, byte(d.Config.Pressure|d.Config.Temperature<<3))
|
|
err = d.writeRegister(RegODR, byte(d.Config.ODR))
|
|
err = d.writeRegister(RegIIR, byte(d.Config.IIR<<1))
|
|
|
|
if err != nil {
|
|
return errConfigWrite
|
|
}
|
|
|
|
// Check if there is a problem with the given configuration
|
|
if d.configurationError() {
|
|
return errConfig
|
|
}
|
|
|
|
// Reading the builtin calibration coefficients and parsing them per the datasheet. The compensation formula given
|
|
// in the datasheet is implemented in floating point
|
|
buffer, err := d.readRegister(RegCali, 21)
|
|
if err != nil {
|
|
return errCaliRead
|
|
}
|
|
|
|
d.cali.t1 = uint16(buffer[1])<<8 | uint16(buffer[0])
|
|
d.cali.t2 = uint16(buffer[3])<<8 | uint16(buffer[2])
|
|
d.cali.t3 = int8(buffer[4])
|
|
|
|
d.cali.p1 = int16(buffer[6])<<8 | int16(buffer[5])
|
|
d.cali.p2 = int16(buffer[8])<<8 | int16(buffer[7])
|
|
d.cali.p3 = int8(buffer[9])
|
|
d.cali.p4 = int8(buffer[10])
|
|
d.cali.p5 = uint16(buffer[12])<<8 | uint16(buffer[11])
|
|
d.cali.p6 = uint16(buffer[14])<<8 | uint16(buffer[13])
|
|
d.cali.p7 = int8(buffer[15])
|
|
d.cali.p8 = int8(buffer[16])
|
|
d.cali.p9 = int16(buffer[18])<<8 | int16(buffer[17])
|
|
d.cali.p10 = int8(buffer[19])
|
|
d.cali.p11 = int8(buffer[20])
|
|
|
|
return nil
|
|
}
|
|
|
|
// Read the temperature registers and compute a compensation value for the temperature and pressure compensation
|
|
// calculations. This is not the temperature itself.
|
|
func (d *Device) tlinCompensate() (int64, error) {
|
|
rawTemp, err := d.readSensorData(RegTemp)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// pulled from C driver: https://github.com/BoschSensortec/BMP3-Sensor-API/blob/master/bmp3.c
|
|
partialData1 := rawTemp - (256 * int64(d.cali.t1))
|
|
partialData2 := int64(d.cali.t2) * partialData1
|
|
partialData3 := (partialData1 * partialData1)
|
|
partialData4 := partialData3 * int64(d.cali.t3)
|
|
partialData5 := (partialData2 * 262144) + partialData4
|
|
return partialData5 / 4294967296, nil
|
|
|
|
}
|
|
|
|
// ReadTemperature returns the temperature in milli degrees Celsius, i.e 24260 / 1000 = 24.26°C.
|
|
func (d *Device) ReadTemperature() (drivers.Temperature, error) {
|
|
|
|
tlin, err := d.tlinCompensate()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
temp := (tlin * 125) / 8192
|
|
return drivers.Temperature(temp), nil
|
|
}
|
|
|
|
// ReadPressure returns the pressure in centipascals, i.e 10132520 / 100 = 101325.20 Pa
|
|
func (d *Device) ReadPressure() (int32, error) {
|
|
|
|
tlin, err := d.tlinCompensate()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
rawPress, err := d.readSensorData(RegPress)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// code pulled from bmp388 C driver: https://github.com/BoschSensortec/BMP3-Sensor-API/blob/master/bmp3.c
|
|
partialData1 := tlin * tlin
|
|
partialData2 := partialData1 / 64
|
|
partialData3 := (partialData2 * tlin) / 256
|
|
partialData4 := (int64(d.cali.p8) * partialData3) / 32
|
|
partialData5 := (int64(d.cali.p7) * partialData1) * 16
|
|
partialData6 := (int64(d.cali.p6) * tlin) * 4194304
|
|
offset := (int64(d.cali.p5) * 140737488355328) + partialData4 + partialData5 + partialData6
|
|
partialData2 = (int64(d.cali.p4) * partialData3) / 32
|
|
partialData4 = (int64(d.cali.p3) * partialData1) * 4
|
|
partialData5 = (int64(d.cali.p2) - 16384) * tlin * 2097152
|
|
sensitivity := ((int64(d.cali.p1) - 16384) * 70368744177664) + partialData2 + partialData4 + partialData5
|
|
partialData1 = (sensitivity / 16777216) * rawPress
|
|
partialData2 = int64(d.cali.p10) * tlin
|
|
partialData3 = partialData2 + (65536 * int64(d.cali.p9))
|
|
partialData4 = (partialData3 * rawPress) / 8192
|
|
|
|
// dividing by 10 followed by multiplying by 10
|
|
// To avoid overflow caused by (pressure * partial_data4)
|
|
partialData5 = (rawPress * (partialData4 / 10)) / 512
|
|
partialData5 = partialData5 * 10
|
|
partialData6 = (int64)(uint64(rawPress) * uint64(rawPress))
|
|
partialData2 = (int64(d.cali.p11) * partialData6) / 65536
|
|
partialData3 = (partialData2 * rawPress) / 128
|
|
partialData4 = (offset / 4) + partialData1 + partialData5 + partialData3
|
|
compPress := ((uint64(partialData4) * 25) / uint64(1099511627776))
|
|
return int32(compPress), nil
|
|
}
|
|
|
|
// SoftReset commands the BMP388 to reset of all user configuration settings
|
|
func (d *Device) SoftReset() error {
|
|
err := d.writeRegister(RegCmd, SoftReset)
|
|
if err != nil {
|
|
return errSoftReset
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Connected tries to reach the bmp388 and check its chip id register. Returns true if it was able to successfully
|
|
// communicate over i2c and returns the correct value
|
|
func (d *Device) Connected() bool {
|
|
data, err := d.readRegister(RegChipId, 1)
|
|
return err == nil && data[0] == ChipId // returns true if i2c comm was good and response equals 0x50
|
|
}
|
|
|
|
// SetMode changes the run mode of the sensor, NORMAL is the one to use for most cases. Use FORCED if you plan to take
|
|
// measurements infrequently and want to conserve power. SLEEP will of course put the sensor to sleep
|
|
func (d *Device) SetMode(mode Mode) error {
|
|
d.Config.Mode = mode
|
|
return d.writeRegister(RegPwrCtrl, PwrPress|PwrTemp|byte(d.Config.Mode))
|
|
}
|
|
|
|
func (d *Device) readSensorData(register byte) (data int64, err error) {
|
|
|
|
if !d.Connected() {
|
|
return 0, errNotConnected
|
|
}
|
|
|
|
// put the sensor back into forced mode to get a reading, the sensor goes back to sleep after taking one read in
|
|
// forced mode
|
|
if d.Config.Mode != Normal {
|
|
err = d.SetMode(Forced)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
bytes, err := d.readRegister(register, 3)
|
|
if err != nil {
|
|
return
|
|
}
|
|
data = int64(bytes[2])<<16 | int64(bytes[1])<<8 | int64(bytes[0])
|
|
return
|
|
}
|
|
|
|
// configurationError checks the register error for the configuration error bit. The bit is cleared on read by the bmp.
|
|
func (d *Device) configurationError() bool {
|
|
data, err := d.readRegister(RegErr, 1)
|
|
return err == nil && (data[0]&0x04) != 0
|
|
}
|
|
|
|
func (d *Device) readRegister(register byte, len int) (data []byte, err error) {
|
|
data = make([]byte, len)
|
|
err = d.bus.ReadRegister(d.Address, register, data)
|
|
return
|
|
}
|
|
|
|
func (d *Device) writeRegister(register byte, data byte) error {
|
|
return d.bus.WriteRegister(d.Address, register, []byte{data})
|
|
}
|