1
0
mirror of https://github.com/hybridgroup/gobot.git synced 2025-04-24 13:48:49 +08:00

FEATURE: bmxy8z use ReadBlockData

This commit is contained in:
Thomas Kohler 2022-09-21 19:35:01 +02:00
parent be288943be
commit 6f970f7f6c
9 changed files with 973 additions and 758 deletions

View File

@ -4,12 +4,27 @@ import (
"bytes"
"encoding/binary"
"errors"
"log"
)
const bme280RegisterControlHumidity = 0xF2
const bme280RegisterHumidityMSB = 0xFD
const bme280RegisterCalibDigH1 = 0xa1
const bme280RegisterCalibDigH2LSB = 0xe1
const bme280Debug = true
type BME280HumidityOversampling uint8
const (
bme280RegCalibDigH1 = 0xA1
bme280RegCalibDigH2LSB = 0xE1
bme280RegControlHumidity = 0xF2
bme280RegHumidityMSB = 0xFD
// bits 0, 1, 3 of control humidity register
BME280CtrlHumidityNoMeasurement BME280HumidityOversampling = 0x00 // no measurement (value will be 0x08 0x00 0x00)
BME280CtrlHumidityOversampling1 BME280HumidityOversampling = 0x01
BME280CtrlHumidityOversampling2 BME280HumidityOversampling = 0x02
BME280CtrlHumidityOversampling4 BME280HumidityOversampling = 0x03
BME280CtrlHumidityOversampling8 BME280HumidityOversampling = 0x04
BME280CtrlHumidityOversampling16 BME280HumidityOversampling = 0x05 // same as 0x06, 0x07
)
type bmeHumidityCalibrationCoefficients struct {
h1 uint8
@ -28,7 +43,8 @@ type bmeHumidityCalibrationCoefficients struct {
//
type BME280Driver struct {
*BMP280Driver
hc *bmeHumidityCalibrationCoefficients
humCalCoeffs *bmeHumidityCalibrationCoefficients
ctrlHumOversamp BME280HumidityOversampling
}
// NewBME280Driver creates a new driver with specified i2c interface.
@ -40,28 +56,89 @@ type BME280Driver struct {
// i2c.WithAddress(int): address to use with this driver
//
func NewBME280Driver(c Connector, options ...func(Config)) *BME280Driver {
b := &BME280Driver{
BMP280Driver: NewBMP280Driver(c),
hc: &bmeHumidityCalibrationCoefficients{},
d := &BME280Driver{
BMP280Driver: NewBMP280Driver(c),
humCalCoeffs: &bmeHumidityCalibrationCoefficients{},
ctrlHumOversamp: BME280CtrlHumidityOversampling16,
}
d.afterStart = d.initializationBME280
// this loop is for options of this class, all options of base class BMP280Driver
// must be added in this class for usage
for _, option := range options {
option(b)
option(d)
}
// TODO: expose commands to API
return b
return d
}
// Start initializes the BME280 and loads the calibration coefficients.
func (d *BME280Driver) Start() (err error) {
bus := d.GetBusOrDefault(d.connector.GetDefaultBus())
address := d.GetAddressOrDefault(bmp180Address)
if d.connection, err = d.connector.GetConnection(address, bus); err != nil {
return err
// WithBME280PressureOversampling option sets the oversampling for pressure.
// Valid settings are of type "BMP280PressureOversampling"
func WithBME280PressureOversampling(val BMP280PressureOversampling) func(Config) {
return func(c Config) {
if d, ok := c.(*BME280Driver); ok {
d.ctrlPressOversamp = val
} else if bme280Debug {
log.Printf("Trying to set pressure oversampling for non-BME280Driver %v", c)
}
}
}
// WithBME280TemperatureOversampling option sets oversampling for temperature.
// Valid settings are of type "BMP280TemperatureOversampling"
func WithBME280TemperatureOversampling(val BMP280TemperatureOversampling) func(Config) {
return func(c Config) {
if d, ok := c.(*BME280Driver); ok {
d.ctrlTempOversamp = val
} else if bme280Debug {
log.Printf("Trying to set temperature oversampling for non-BME280Driver %v", c)
}
}
}
// WithBME280IIRFilter option sets the count of IIR filter coefficients.
// Valid settings are of type "BMP280IIRFilter"
func WithBME280IIRFilter(val BMP280IIRFilter) func(Config) {
return func(c Config) {
if d, ok := c.(*BME280Driver); ok {
d.confFilter = val
} else if bme280Debug {
log.Printf("Trying to set IIR filter for non-BME280Driver %v", c)
}
}
}
// WithBME280HumidityOversampling option sets the oversampling for humidity.
// Valid settings are of type "BME280HumidityOversampling"
func WithBME280HumidityOversampling(val BME280HumidityOversampling) func(Config) {
return func(c Config) {
if d, ok := c.(*BME280Driver); ok {
d.ctrlHumOversamp = val
} else if bme280Debug {
log.Printf("Trying to set humidity oversampling for non-BME280Driver %v", c)
}
}
}
// Humidity returns the current humidity in percentage of relative humidity
func (d *BME280Driver) Humidity() (humidity float32, err error) {
d.mutex.Lock()
defer d.mutex.Unlock()
var rawH uint32
if rawH, err = d.rawHumidity(); err != nil {
return 0.0, err
}
humidity = d.calculateHumidity(rawH)
return
}
func (d *BME280Driver) initializationBME280() (err error) {
// call the initialization routine of base class BMP280Driver, which do:
// * initializes temperature and pressure calibration coefficients
// * set the control register
// * set the configuration register
if err := d.initialization(); err != nil {
return err
}
@ -73,26 +150,17 @@ func (d *BME280Driver) Start() (err error) {
return nil
}
// Humidity returns the current humidity in percentage of relative humidity
func (d *BME280Driver) Humidity() (humidity float32, err error) {
var rawH uint32
if rawH, err = d.rawHumidity(); err != nil {
return 0.0, err
}
humidity = d.calculateHumidity(rawH)
return
}
// read the humidity calibration coefficients.
func (d *BME280Driver) initHumidity() (err error) {
var coefficients []byte
if coefficients, err = d.read(bme280RegisterCalibDigH1, 1); err != nil {
var hch1 byte
if hch1, err = d.connection.ReadByteData(bme280RegCalibDigH1); err != nil {
return err
}
buf := bytes.NewBuffer(coefficients)
binary.Read(buf, binary.BigEndian, &d.hc.h1)
buf := bytes.NewBuffer([]byte{hch1})
binary.Read(buf, binary.BigEndian, &d.humCalCoeffs.h1)
if coefficients, err = d.read(bme280RegisterCalibDigH2LSB, 7); err != nil {
coefficients := make([]byte, 7)
if err = d.connection.ReadBlockData(bme280RegCalibDigH2LSB, coefficients); err != nil {
return err
}
buf = bytes.NewBuffer(coefficients)
@ -102,32 +170,32 @@ func (d *BME280Driver) initHumidity() (err error) {
var addrE5 byte
var addrE6 byte
binary.Read(buf, binary.LittleEndian, &d.hc.h2) // E1 ...
binary.Read(buf, binary.BigEndian, &d.hc.h3) // E3
binary.Read(buf, binary.BigEndian, &addrE4) // E4
binary.Read(buf, binary.BigEndian, &addrE5) // E5
binary.Read(buf, binary.BigEndian, &addrE6) // E6
binary.Read(buf, binary.BigEndian, &d.hc.h6) // ... E7
binary.Read(buf, binary.LittleEndian, &d.humCalCoeffs.h2) // E1 ...
binary.Read(buf, binary.BigEndian, &d.humCalCoeffs.h3) // E3
binary.Read(buf, binary.BigEndian, &addrE4) // E4
binary.Read(buf, binary.BigEndian, &addrE5) // E5
binary.Read(buf, binary.BigEndian, &addrE6) // E6
binary.Read(buf, binary.BigEndian, &d.humCalCoeffs.h6) // ... E7
d.hc.h4 = 0 + (int16(addrE4) << 4) | (int16(addrE5 & 0x0F))
d.hc.h5 = 0 + (int16(addrE6) << 4) | (int16(addrE5) >> 4)
d.humCalCoeffs.h4 = 0 + (int16(addrE4) << 4) | (int16(addrE5 & 0x0F))
d.humCalCoeffs.h5 = 0 + (int16(addrE6) << 4) | (int16(addrE5) >> 4)
d.connection.WriteByteData(bme280RegisterControlHumidity, 0x3F)
// The 'ctrl_hum' register sets the humidity data acquisition options of
// The 'ctrl_hum' register (0xF2) sets the humidity data acquisition options of
// the device. Changes to this register only become effective after a write
// operation to 'ctrl_meas'. Read the current value in, then write it back
// operation to 'ctrl_meas' (0xF4). So we read the current value in, then write it back
d.connection.WriteByteData(bme280RegControlHumidity, uint8(d.ctrlHumOversamp))
var cmr uint8
cmr, err = d.connection.ReadByteData(bmp280RegisterControl)
cmr, err = d.connection.ReadByteData(bmp280RegCtrl)
if err == nil {
err = d.connection.WriteByteData(bmp280RegisterControl, cmr)
err = d.connection.WriteByteData(bmp280RegCtrl, cmr)
}
return err
}
func (d *BME280Driver) rawHumidity() (uint32, error) {
ret, err := d.read(bme280RegisterHumidityMSB, 2)
if err != nil {
ret := make([]byte, 2)
if err := d.connection.ReadBlockData(bme280RegHumidityMSB, ret); err != nil {
return 0, err
}
if ret[0] == 0x80 && ret[1] == 0x00 {
@ -158,14 +226,14 @@ func (d *BME280Driver) calculateHumidity(rawH uint32) float32 {
return 0 // TODO err is 'invalid data' from Bosch - include errors or not?
}
x := float32(rawH) - (float32(d.hc.h4)*64.0 +
(float32(d.hc.h5) / 16384.0 * h))
x := float32(rawH) - (float32(d.humCalCoeffs.h4)*64.0 +
(float32(d.humCalCoeffs.h5) / 16384.0 * h))
y := float32(d.hc.h2) / 65536.0 *
(1.0 + float32(d.hc.h6)/67108864.0*h*
(1.0+float32(d.hc.h3)/67108864.0*h))
y := float32(d.humCalCoeffs.h2) / 65536.0 *
(1.0 + float32(d.humCalCoeffs.h6)/67108864.0*h*
(1.0+float32(d.humCalCoeffs.h3)/67108864.0*h))
h = x * y
h = h * (1 - float32(d.hc.h1)*h/524288)
h = h * (1 - float32(d.humCalCoeffs.h1)*h/524288)
return h
}

View File

@ -3,93 +3,68 @@ package i2c
import (
"bytes"
"errors"
"strings"
"testing"
"gobot.io/x/gobot"
"gobot.io/x/gobot/gobottest"
)
// this ensures that the implementation is based on i2c.Driver, which implements the gobot.Driver
// and tests all implementations, so no further tests needed here for gobot.Driver interface
var _ gobot.Driver = (*BME280Driver)(nil)
// --------- HELPERS
func initTestBME280Driver() (driver *BME280Driver) {
driver, _ = initTestBME280DriverWithStubbedAdaptor()
return
}
func initTestBME280DriverWithStubbedAdaptor() (*BME280Driver, *i2cTestAdaptor) {
func initTestBME280WithStubbedAdaptor() (*BME280Driver, *i2cTestAdaptor) {
adaptor := newI2cTestAdaptor()
return NewBME280Driver(adaptor), adaptor
}
// --------- TESTS
func TestNewBME280Driver(t *testing.T) {
// Does it return a pointer to an instance of BME280Driver?
var bme280 interface{} = NewBME280Driver(newI2cTestAdaptor())
_, ok := bme280.(*BME280Driver)
var di interface{} = NewBME280Driver(newI2cTestAdaptor())
d, ok := di.(*BME280Driver)
if !ok {
t.Errorf("NewBME280Driver() should have returned a *BME280Driver")
}
gobottest.Refute(t, d.Driver, nil)
gobottest.Assert(t, strings.HasPrefix(d.Name(), "BMP280"), true)
gobottest.Assert(t, d.defaultAddress, 0x77)
gobottest.Assert(t, d.ctrlPwrMode, uint8(0x03))
gobottest.Assert(t, d.ctrlPressOversamp, BMP280PressureOversampling(0x05))
gobottest.Assert(t, d.ctrlTempOversamp, BMP280TemperatureOversampling(0x01))
gobottest.Assert(t, d.ctrlHumOversamp, BME280HumidityOversampling(0x05))
gobottest.Assert(t, d.confFilter, BMP280IIRFilter(0x00))
gobottest.Refute(t, d.calCoeffs, nil)
}
func TestBME280Driver(t *testing.T) {
bme280 := initTestBME280Driver()
gobottest.Refute(t, bme280.Connection(), nil)
func TestBME280Options(t *testing.T) {
// This is a general test, that options are applied in constructor by using the common WithBus() option and
// least one of this driver. Further tests for options can also be done by call of "WithOption(val)(d)".
d := NewBME280Driver(newI2cTestAdaptor(), WithBus(2),
WithBME280PressureOversampling(0x01),
WithBME280TemperatureOversampling(0x02),
WithBME280IIRFilter(0x03),
WithBME280HumidityOversampling(0x04))
gobottest.Assert(t, d.GetBusOrDefault(1), 2)
gobottest.Assert(t, d.ctrlPressOversamp, BMP280PressureOversampling(0x01))
gobottest.Assert(t, d.ctrlTempOversamp, BMP280TemperatureOversampling(0x02))
gobottest.Assert(t, d.confFilter, BMP280IIRFilter(0x03))
gobottest.Assert(t, d.ctrlHumOversamp, BME280HumidityOversampling(0x04))
}
func TestBME280DriverStart(t *testing.T) {
bme280, adaptor := initTestBME280DriverWithStubbedAdaptor()
adaptor.i2cReadImpl = func(b []byte) (int, error) {
// Simulate returning a single byte for the
// ReadByteData(bmp280RegisterControl) call in Start()
return 1, nil
}
gobottest.Assert(t, bme280.Start(), nil)
}
func TestBME280StartConnectError(t *testing.T) {
d, adaptor := initTestBME280DriverWithStubbedAdaptor()
adaptor.Testi2cConnectErr(true)
gobottest.Assert(t, d.Start(), errors.New("Invalid i2c connection"))
}
func TestBME280DriverStartWriteError(t *testing.T) {
bme280, adaptor := initTestBME280DriverWithStubbedAdaptor()
adaptor.i2cWriteImpl = func([]byte) (int, error) {
return 0, errors.New("write error")
}
gobottest.Assert(t, bme280.Start(), errors.New("write error"))
}
func TestBME280DriverStartReadError(t *testing.T) {
bme280, adaptor := initTestBME280DriverWithStubbedAdaptor()
adaptor.i2cReadImpl = func(b []byte) (int, error) {
return 0, errors.New("read error")
}
gobottest.Assert(t, bme280.Start(), errors.New("read error"))
}
func TestBME280DriverHalt(t *testing.T) {
bme280 := initTestBME280Driver()
gobottest.Assert(t, bme280.Halt(), nil)
}
func TestBME280DriverMeasurements(t *testing.T) {
bme280, adaptor := initTestBME280DriverWithStubbedAdaptor()
func TestBME280Measurements(t *testing.T) {
bme280, adaptor := initTestBME280WithStubbedAdaptor()
adaptor.i2cReadImpl = func(b []byte) (int, error) {
buf := new(bytes.Buffer)
// Values produced by dumping data from actual sensor
if adaptor.written[len(adaptor.written)-1] == bmp280RegisterCalib00 {
if adaptor.written[len(adaptor.written)-1] == bmp280RegCalib00 {
buf.Write([]byte{126, 109, 214, 102, 50, 0, 54, 149, 220, 213, 208, 11, 64, 30, 166, 255, 249, 255, 172, 38, 10, 216, 189, 16})
} else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH1 {
} else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH1 {
buf.Write([]byte{75})
} else if adaptor.written[len(adaptor.written)-1] == bmp280RegisterTempData {
} else if adaptor.written[len(adaptor.written)-1] == bmp280RegTempData {
buf.Write([]byte{129, 0, 0})
} else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH2LSB {
} else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH2LSB {
buf.Write([]byte{112, 1, 0, 19, 1, 0, 30})
} else if adaptor.written[len(adaptor.written)-1] == bme280RegisterHumidityMSB {
} else if adaptor.written[len(adaptor.written)-1] == bme280RegHumidityMSB {
buf.Write([]byte{111, 83})
}
copy(b, buf.Bytes())
@ -101,16 +76,16 @@ func TestBME280DriverMeasurements(t *testing.T) {
gobottest.Assert(t, hum, float32(51.20179))
}
func TestBME280DriverInitH1Error(t *testing.T) {
bme280, adaptor := initTestBME280DriverWithStubbedAdaptor()
func TestBME280InitH1Error(t *testing.T) {
bme280, adaptor := initTestBME280WithStubbedAdaptor()
adaptor.i2cReadImpl = func(b []byte) (int, error) {
buf := new(bytes.Buffer)
// Values produced by dumping data from actual sensor
if adaptor.written[len(adaptor.written)-1] == bmp280RegisterCalib00 {
if adaptor.written[len(adaptor.written)-1] == bmp280RegCalib00 {
buf.Write([]byte{126, 109, 214, 102, 50, 0, 54, 149, 220, 213, 208, 11, 64, 30, 166, 255, 249, 255, 172, 38, 10, 216, 189, 16})
} else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH1 {
} else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH1 {
return 0, errors.New("h1 read error")
} else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH2LSB {
} else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH2LSB {
buf.Write([]byte{112, 1, 0, 19, 1, 0, 30})
}
copy(b, buf.Bytes())
@ -120,16 +95,16 @@ func TestBME280DriverInitH1Error(t *testing.T) {
gobottest.Assert(t, bme280.Start(), errors.New("h1 read error"))
}
func TestBME280DriverInitH2Error(t *testing.T) {
bme280, adaptor := initTestBME280DriverWithStubbedAdaptor()
func TestBME280InitH2Error(t *testing.T) {
bme280, adaptor := initTestBME280WithStubbedAdaptor()
adaptor.i2cReadImpl = func(b []byte) (int, error) {
buf := new(bytes.Buffer)
// Values produced by dumping data from actual sensor
if adaptor.written[len(adaptor.written)-1] == bmp280RegisterCalib00 {
if adaptor.written[len(adaptor.written)-1] == bmp280RegCalib00 {
buf.Write([]byte{126, 109, 214, 102, 50, 0, 54, 149, 220, 213, 208, 11, 64, 30, 166, 255, 249, 255, 172, 38, 10, 216, 189, 16})
} else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH1 {
} else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH1 {
buf.Write([]byte{75})
} else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH2LSB {
} else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH2LSB {
return 0, errors.New("h2 read error")
}
copy(b, buf.Bytes())
@ -139,8 +114,8 @@ func TestBME280DriverInitH2Error(t *testing.T) {
gobottest.Assert(t, bme280.Start(), errors.New("h2 read error"))
}
func TestBME280DriverHumidityWriteError(t *testing.T) {
bme280, adaptor := initTestBME280DriverWithStubbedAdaptor()
func TestBME280HumidityWriteError(t *testing.T) {
bme280, adaptor := initTestBME280WithStubbedAdaptor()
bme280.Start()
adaptor.i2cWriteImpl = func([]byte) (int, error) {
@ -151,8 +126,8 @@ func TestBME280DriverHumidityWriteError(t *testing.T) {
gobottest.Assert(t, hum, float32(0.0))
}
func TestBME280DriverHumidityReadError(t *testing.T) {
bme280, adaptor := initTestBME280DriverWithStubbedAdaptor()
func TestBME280HumidityReadError(t *testing.T) {
bme280, adaptor := initTestBME280WithStubbedAdaptor()
bme280.Start()
adaptor.i2cReadImpl = func([]byte) (int, error) {
@ -163,20 +138,20 @@ func TestBME280DriverHumidityReadError(t *testing.T) {
gobottest.Assert(t, hum, float32(0.0))
}
func TestBME280DriverHumidityNotEnabled(t *testing.T) {
bme280, adaptor := initTestBME280DriverWithStubbedAdaptor()
func TestBME280HumidityNotEnabled(t *testing.T) {
bme280, adaptor := initTestBME280WithStubbedAdaptor()
adaptor.i2cReadImpl = func(b []byte) (int, error) {
buf := new(bytes.Buffer)
// Values produced by dumping data from actual sensor
if adaptor.written[len(adaptor.written)-1] == bmp280RegisterCalib00 {
if adaptor.written[len(adaptor.written)-1] == bmp280RegCalib00 {
buf.Write([]byte{126, 109, 214, 102, 50, 0, 54, 149, 220, 213, 208, 11, 64, 30, 166, 255, 249, 255, 172, 38, 10, 216, 189, 16})
} else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH1 {
} else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH1 {
buf.Write([]byte{75})
} else if adaptor.written[len(adaptor.written)-1] == bmp280RegisterTempData {
} else if adaptor.written[len(adaptor.written)-1] == bmp280RegTempData {
buf.Write([]byte{129, 0, 0})
} else if adaptor.written[len(adaptor.written)-1] == bme280RegisterCalibDigH2LSB {
} else if adaptor.written[len(adaptor.written)-1] == bme280RegCalibDigH2LSB {
buf.Write([]byte{112, 1, 0, 19, 1, 0, 30})
} else if adaptor.written[len(adaptor.written)-1] == bme280RegisterHumidityMSB {
} else if adaptor.written[len(adaptor.written)-1] == bme280RegHumidityMSB {
buf.Write([]byte{0x80, 0x00})
}
copy(b, buf.Bytes())
@ -188,13 +163,28 @@ func TestBME280DriverHumidityNotEnabled(t *testing.T) {
gobottest.Assert(t, hum, float32(0.0))
}
func TestBME280DriverSetName(t *testing.T) {
b := initTestBME280Driver()
b.SetName("TESTME")
gobottest.Assert(t, b.Name(), "TESTME")
}
func TestBME280DriverOptions(t *testing.T) {
b := NewBME280Driver(newI2cTestAdaptor(), WithBus(2))
gobottest.Assert(t, b.GetBusOrDefault(1), 2)
func TestBME280_initializationBME280(t *testing.T) {
bme280, adaptor := initTestBME280WithStubbedAdaptor()
readCallCounter := 0
adaptor.i2cReadImpl = func(b []byte) (int, error) {
readCallCounter++
if readCallCounter == 1 {
// Simulate returning 24 bytes for the coefficients (register bmp280RegCalib00)
return 24, nil
}
if readCallCounter == 2 {
// Simulate returning a single byte for the hc.h1 value (register bme280RegCalibDigH1)
return 1, nil
}
if readCallCounter == 3 {
// Simulate returning 7 bytes for the coefficients (register bme280RegCalibDigH2LSB)
return 7, nil
}
if readCallCounter == 4 {
// Simulate returning 1 byte for the cmr (register bmp280RegControl)
return 1, nil
}
return 0, nil
}
gobottest.Assert(t, bme280.Start(), nil)
}

View File

@ -3,20 +3,23 @@ package i2c
import (
"bytes"
"encoding/binary"
"log"
"time"
"gobot.io/x/gobot"
)
const bmp180Address = 0x77
const bmp180Debug = false
const bmp180RegisterAC1MSB = 0xAA
// the default address is applicable for SDO to VDD, for SDO to GND it will be 0x76
const bmp180DefaultAddress = 0x77
const bmp180RegisterCtl = 0xF4
const bmp180CmdTemp = 0x2E
const bmp180RegisterTempMSB = 0xF6
const bmp180CmdPressure = 0x34
const bmp180RegisterPressureMSB = 0xF6
const (
bmp180RegisterAC1MSB = 0xAA // 11 x 16 bit calibration data (AC1..AC6, B1, B2, MB, MC, MD)
bmp180RegisterCtl = 0xF4 // control the value to read
bmp180RegisterDataMSB = 0xF6 // 16 bit data (temperature or pressure)
bmp180CtlTemp = 0x2E
bmp180CtlPressure = 0x34
)
const (
// BMP180UltraLowPower is the lowest oversampling mode of the pressure measurement.
@ -32,7 +35,7 @@ const (
// BMP180OversamplingMode is the oversampling ratio of the pressure measurement.
type BMP180OversamplingMode uint
type calibrationCoefficients struct {
type bmp180CalibrationCoefficients struct {
ac1 int16
ac2 int16
ac3 int16
@ -46,15 +49,12 @@ type calibrationCoefficients struct {
md int16
}
// BMP180Driver is the gobot driver for the Bosch pressure sensor BMP180.
// BMP180Driver is the gobot driver for the Bosch pressure and temperature sensor BMP180.
// Device datasheet: https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf
type BMP180Driver struct {
name string
Mode BMP180OversamplingMode
connector Connector
connection Connection
Config
calibrationCoefficients *calibrationCoefficients
*Driver
oversampling BMP180OversamplingMode
calCoeffs *bmp180CalibrationCoefficients
}
// NewBMP180Driver creates a new driver with the i2c interface for the BMP180 device.
@ -66,80 +66,38 @@ type BMP180Driver struct {
// i2c.WithAddress(int): address to use with this driver
//
func NewBMP180Driver(c Connector, options ...func(Config)) *BMP180Driver {
b := &BMP180Driver{
name: gobot.DefaultName("BMP180"),
connector: c,
Mode: BMP180UltraLowPower,
Config: NewConfig(),
calibrationCoefficients: &calibrationCoefficients{},
d := &BMP180Driver{
Driver: NewDriver(c, "BMP180", bmp180DefaultAddress),
oversampling: BMP180UltraLowPower,
calCoeffs: &bmp180CalibrationCoefficients{},
}
d.afterStart = d.initialization
for _, option := range options {
option(b)
option(d)
}
// TODO: expose commands to API
return b
return d
}
// Name returns the name of the device.
func (d *BMP180Driver) Name() string {
return d.name
}
// SetName sets the name of the device.
func (d *BMP180Driver) SetName(n string) {
d.name = n
}
// Connection returns the connection of the device.
func (d *BMP180Driver) Connection() gobot.Connection {
return d.connector.(gobot.Connection)
}
// Start initializes the BMP180 and loads the calibration coefficients.
func (d *BMP180Driver) Start() (err error) {
bus := d.GetBusOrDefault(d.connector.GetDefaultBus())
address := d.GetAddressOrDefault(bmp180Address)
if d.connection, err = d.connector.GetConnection(address, bus); err != nil {
return err
// WithBMP180oversampling option sets oversampling mode.
// Valid settings are of type "BMP180OversamplingMode"
func WithBMP180OversamplingMode(val BMP180OversamplingMode) func(Config) {
return func(c Config) {
if d, ok := c.(*BMP180Driver); ok {
d.oversampling = val
} else if bmp180Debug {
log.Printf("Trying to set oversampling mode for non-BMP180Driver %v", c)
}
}
if err := d.initialization(); err != nil {
return err
}
return nil
}
func (d *BMP180Driver) initialization() (err error) {
var coefficients []byte
// read the 11 calibration coefficients.
if coefficients, err = d.read(bmp180RegisterAC1MSB, 22); err != nil {
return err
}
buf := bytes.NewBuffer(coefficients)
binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.ac1)
binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.ac2)
binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.ac3)
binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.ac4)
binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.ac5)
binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.ac6)
binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.b1)
binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.b2)
binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.mb)
binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.mc)
binary.Read(buf, binary.BigEndian, &d.calibrationCoefficients.md)
return nil
}
// Halt halts the device.
func (d *BMP180Driver) Halt() (err error) {
return nil
}
// Temperature returns the current temperature, in celsius degrees.
func (d *BMP180Driver) Temperature() (temp float32, err error) {
d.mutex.Lock()
defer d.mutex.Unlock()
var rawTemp int16
if rawTemp, err = d.rawTemp(); err != nil {
return 0, err
@ -149,23 +107,49 @@ func (d *BMP180Driver) Temperature() (temp float32, err error) {
// Pressure returns the current pressure, in pascals.
func (d *BMP180Driver) Pressure() (pressure float32, err error) {
d.mutex.Lock()
defer d.mutex.Unlock()
var rawTemp int16
var rawPressure int32
if rawTemp, err = d.rawTemp(); err != nil {
return 0, err
}
if rawPressure, err = d.rawPressure(d.Mode); err != nil {
if rawPressure, err = d.rawPressure(d.oversampling); err != nil {
return 0, err
}
return d.calculatePressure(rawTemp, rawPressure, d.Mode), nil
return d.calculatePressure(rawTemp, rawPressure, d.oversampling), nil
}
func (d *BMP180Driver) initialization() (err error) {
// read the 11 calibration coefficients.
coefficients := make([]byte, 22)
if err = d.connection.ReadBlockData(bmp180RegisterAC1MSB, coefficients); err != nil {
return err
}
buf := bytes.NewBuffer(coefficients)
binary.Read(buf, binary.BigEndian, &d.calCoeffs.ac1)
binary.Read(buf, binary.BigEndian, &d.calCoeffs.ac2)
binary.Read(buf, binary.BigEndian, &d.calCoeffs.ac3)
binary.Read(buf, binary.BigEndian, &d.calCoeffs.ac4)
binary.Read(buf, binary.BigEndian, &d.calCoeffs.ac5)
binary.Read(buf, binary.BigEndian, &d.calCoeffs.ac6)
binary.Read(buf, binary.BigEndian, &d.calCoeffs.b1)
binary.Read(buf, binary.BigEndian, &d.calCoeffs.b2)
binary.Read(buf, binary.BigEndian, &d.calCoeffs.mb)
binary.Read(buf, binary.BigEndian, &d.calCoeffs.mc)
binary.Read(buf, binary.BigEndian, &d.calCoeffs.md)
return nil
}
func (d *BMP180Driver) rawTemp() (int16, error) {
if _, err := d.connection.Write([]byte{bmp180RegisterCtl, bmp180CmdTemp}); err != nil {
if _, err := d.connection.Write([]byte{bmp180RegisterCtl, bmp180CtlTemp}); err != nil {
return 0, err
}
time.Sleep(5 * time.Millisecond)
ret, err := d.read(bmp180RegisterTempMSB, 2)
ret := make([]byte, 2)
err := d.connection.ReadBlockData(bmp180RegisterDataMSB, ret)
if err != nil {
return 0, err
}
@ -175,18 +159,6 @@ func (d *BMP180Driver) rawTemp() (int16, error) {
return rawTemp, nil
}
func (d *BMP180Driver) read(address byte, n int) ([]byte, error) {
if _, err := d.connection.Write([]byte{address}); err != nil {
return nil, err
}
buf := make([]byte, n)
bytesRead, err := d.connection.Read(buf)
if bytesRead != n || err != nil {
return nil, err
}
return buf, nil
}
func (d *BMP180Driver) calculateTemp(rawTemp int16) float32 {
b5 := d.calculateB5(rawTemp)
t := (b5 + 8) >> 4
@ -194,36 +166,36 @@ func (d *BMP180Driver) calculateTemp(rawTemp int16) float32 {
}
func (d *BMP180Driver) calculateB5(rawTemp int16) int32 {
x1 := (int32(rawTemp) - int32(d.calibrationCoefficients.ac6)) * int32(d.calibrationCoefficients.ac5) >> 15
x2 := int32(d.calibrationCoefficients.mc) << 11 / (x1 + int32(d.calibrationCoefficients.md))
x1 := (int32(rawTemp) - int32(d.calCoeffs.ac6)) * int32(d.calCoeffs.ac5) >> 15
x2 := int32(d.calCoeffs.mc) << 11 / (x1 + int32(d.calCoeffs.md))
return x1 + x2
}
func (d *BMP180Driver) rawPressure(mode BMP180OversamplingMode) (rawPressure int32, err error) {
if _, err = d.connection.Write([]byte{bmp180RegisterCtl, bmp180CmdPressure + byte(mode<<6)}); err != nil {
func (d *BMP180Driver) rawPressure(oversampling BMP180OversamplingMode) (rawPressure int32, err error) {
if _, err = d.connection.Write([]byte{bmp180RegisterCtl, bmp180CtlPressure + byte(oversampling<<6)}); err != nil {
return 0, err
}
time.Sleep(pauseForReading(mode))
var ret []byte
if ret, err = d.read(bmp180RegisterPressureMSB, 3); err != nil {
time.Sleep(bmp180PauseForReading(oversampling))
ret := make([]byte, 3)
if err = d.connection.ReadBlockData(bmp180RegisterDataMSB, ret); err != nil {
return 0, err
}
rawPressure = (int32(ret[0])<<16 + int32(ret[1])<<8 + int32(ret[2])) >> (8 - uint(mode))
rawPressure = (int32(ret[0])<<16 + int32(ret[1])<<8 + int32(ret[2])) >> (8 - uint(oversampling))
return rawPressure, nil
}
func (d *BMP180Driver) calculatePressure(rawTemp int16, rawPressure int32, mode BMP180OversamplingMode) float32 {
func (d *BMP180Driver) calculatePressure(rawTemp int16, rawPressure int32, oversampling BMP180OversamplingMode) float32 {
b5 := d.calculateB5(rawTemp)
b6 := b5 - 4000
x1 := (int32(d.calibrationCoefficients.b2) * (b6 * b6 >> 12)) >> 11
x2 := (int32(d.calibrationCoefficients.ac2) * b6) >> 11
x1 := (int32(d.calCoeffs.b2) * (b6 * b6 >> 12)) >> 11
x2 := (int32(d.calCoeffs.ac2) * b6) >> 11
x3 := x1 + x2
b3 := (((int32(d.calibrationCoefficients.ac1)*4 + x3) << uint(mode)) + 2) >> 2
x1 = (int32(d.calibrationCoefficients.ac3) * b6) >> 13
x2 = (int32(d.calibrationCoefficients.b1) * ((b6 * b6) >> 12)) >> 16
b3 := (((int32(d.calCoeffs.ac1)*4 + x3) << uint(oversampling)) + 2) >> 2
x1 = (int32(d.calCoeffs.ac3) * b6) >> 13
x2 = (int32(d.calCoeffs.b1) * ((b6 * b6) >> 12)) >> 16
x3 = ((x1 + x2) + 2) >> 2
b4 := (uint32(d.calibrationCoefficients.ac4) * uint32(x3+32768)) >> 15
b7 := (uint32(rawPressure-b3) * (50000 >> uint(mode)))
b4 := (uint32(d.calCoeffs.ac4) * uint32(x3+32768)) >> 15
b7 := (uint32(rawPressure-b3) * (50000 >> uint(oversampling)))
var p int32
if b7 < 0x80000000 {
p = int32((b7 << 1) / b4)
@ -236,9 +208,9 @@ func (d *BMP180Driver) calculatePressure(rawTemp int16, rawPressure int32, mode
return float32(p + ((x1 + x2 + 3791) >> 4))
}
func pauseForReading(mode BMP180OversamplingMode) time.Duration {
func bmp180PauseForReading(oversampling BMP180OversamplingMode) time.Duration {
var d time.Duration
switch mode {
switch oversampling {
case BMP180UltraLowPower:
d = 5 * time.Millisecond
case BMP180Standard:

View File

@ -4,6 +4,7 @@ import (
"bytes"
"encoding/binary"
"errors"
"strings"
"testing"
"time"
@ -11,62 +12,39 @@ import (
"gobot.io/x/gobot/gobottest"
)
// this ensures that the implementation is based on i2c.Driver, which implements the gobot.Driver
// and tests all implementations, so no further tests needed here for gobot.Driver interface
var _ gobot.Driver = (*BMP180Driver)(nil)
// --------- HELPERS
func initTestBMP180Driver() (driver *BMP180Driver) {
driver, _ = initTestBMP180DriverWithStubbedAdaptor()
return
}
func initTestBMP180DriverWithStubbedAdaptor() (*BMP180Driver, *i2cTestAdaptor) {
func initTestBMP180WithStubbedAdaptor() (*BMP180Driver, *i2cTestAdaptor) {
adaptor := newI2cTestAdaptor()
return NewBMP180Driver(adaptor), adaptor
}
// --------- TESTS
func TestNewBMP180Driver(t *testing.T) {
// Does it return a pointer to an instance of BMP180Driver?
var bmp180 interface{} = NewBMP180Driver(newI2cTestAdaptor())
_, ok := bmp180.(*BMP180Driver)
var di interface{} = NewBMP180Driver(newI2cTestAdaptor())
d, ok := di.(*BMP180Driver)
if !ok {
t.Errorf("NewBMP180Driver() should have returned a *BMP180Driver")
}
gobottest.Refute(t, d.Driver, nil)
gobottest.Assert(t, strings.HasPrefix(d.Name(), "BMP180"), true)
gobottest.Assert(t, d.defaultAddress, 0x77)
gobottest.Assert(t, d.oversampling, BMP180OversamplingMode(0x00))
gobottest.Refute(t, d.calCoeffs, nil)
}
func TestBMP180Driver(t *testing.T) {
bmp180 := initTestBMP180Driver()
gobottest.Refute(t, bmp180.Connection(), nil)
func TestBMP180Options(t *testing.T) {
// This is a general test, that options are applied in constructor by using the common WithBus() option and
// least one of this driver. Further tests for options can also be done by call of "WithOption(val)(d)".
d := NewBMP180Driver(newI2cTestAdaptor(), WithBus(2), WithBMP180OversamplingMode(0x01))
gobottest.Assert(t, d.GetBusOrDefault(1), 2)
gobottest.Assert(t, d.oversampling, BMP180OversamplingMode(0x01))
}
func TestBMP180DriverStart(t *testing.T) {
bmp180, _ := initTestBMP180DriverWithStubbedAdaptor()
gobottest.Assert(t, bmp180.Start(), nil)
}
func TestBMP180StartConnectError(t *testing.T) {
d, adaptor := initTestBMP180DriverWithStubbedAdaptor()
adaptor.Testi2cConnectErr(true)
gobottest.Assert(t, d.Start(), errors.New("Invalid i2c connection"))
}
func TestBMP180DriverStartWriteError(t *testing.T) {
bmp180, adaptor := initTestBMP180DriverWithStubbedAdaptor()
adaptor.i2cWriteImpl = func([]byte) (int, error) {
return 0, errors.New("write error")
}
gobottest.Assert(t, bmp180.Start(), errors.New("write error"))
}
func TestBMP180DriverHalt(t *testing.T) {
bmp180 := initTestBMP180Driver()
gobottest.Assert(t, bmp180.Halt(), nil)
}
func TestBMP180DriverMeasurements(t *testing.T) {
bmp180, adaptor := initTestBMP180DriverWithStubbedAdaptor()
func TestBMP180Measurements(t *testing.T) {
bmp180, adaptor := initTestBMP180WithStubbedAdaptor()
adaptor.i2cReadImpl = func(b []byte) (int, error) {
buf := new(bytes.Buffer)
// Values from the datasheet example.
@ -82,9 +60,9 @@ func TestBMP180DriverMeasurements(t *testing.T) {
binary.Write(buf, binary.BigEndian, int16(-32768))
binary.Write(buf, binary.BigEndian, int16(-8711))
binary.Write(buf, binary.BigEndian, int16(2868))
} else if adaptor.written[len(adaptor.written)-2] == bmp180CmdTemp && adaptor.written[len(adaptor.written)-1] == bmp180RegisterTempMSB {
} else if adaptor.written[len(adaptor.written)-2] == bmp180CtlTemp && adaptor.written[len(adaptor.written)-1] == bmp180RegisterDataMSB {
binary.Write(buf, binary.BigEndian, int16(27898))
} else if adaptor.written[len(adaptor.written)-2] == bmp180CmdPressure && adaptor.written[len(adaptor.written)-1] == bmp180RegisterPressureMSB {
} else if adaptor.written[len(adaptor.written)-2] == bmp180CtlPressure && adaptor.written[len(adaptor.written)-1] == bmp180RegisterDataMSB {
binary.Write(buf, binary.BigEndian, int16(23843))
// XLSB, not used in this test.
buf.WriteByte(0)
@ -101,8 +79,8 @@ func TestBMP180DriverMeasurements(t *testing.T) {
gobottest.Assert(t, pressure, float32(69964))
}
func TestBMP180DriverTemperatureError(t *testing.T) {
bmp180, adaptor := initTestBMP180DriverWithStubbedAdaptor()
func TestBMP180TemperatureError(t *testing.T) {
bmp180, adaptor := initTestBMP180WithStubbedAdaptor()
adaptor.i2cReadImpl = func(b []byte) (int, error) {
buf := new(bytes.Buffer)
// Values from the datasheet example.
@ -118,9 +96,9 @@ func TestBMP180DriverTemperatureError(t *testing.T) {
binary.Write(buf, binary.BigEndian, int16(-32768))
binary.Write(buf, binary.BigEndian, int16(-8711))
binary.Write(buf, binary.BigEndian, int16(2868))
} else if adaptor.written[len(adaptor.written)-2] == bmp180CmdTemp && adaptor.written[len(adaptor.written)-1] == bmp180RegisterTempMSB {
} else if adaptor.written[len(adaptor.written)-2] == bmp180CtlTemp && adaptor.written[len(adaptor.written)-1] == bmp180RegisterDataMSB {
return 0, errors.New("temp error")
} else if adaptor.written[len(adaptor.written)-2] == bmp180CmdPressure && adaptor.written[len(adaptor.written)-1] == bmp180RegisterPressureMSB {
} else if adaptor.written[len(adaptor.written)-2] == bmp180CtlPressure && adaptor.written[len(adaptor.written)-1] == bmp180RegisterDataMSB {
binary.Write(buf, binary.BigEndian, int16(23843))
// XLSB, not used in this test.
buf.WriteByte(0)
@ -133,8 +111,8 @@ func TestBMP180DriverTemperatureError(t *testing.T) {
gobottest.Assert(t, err, errors.New("temp error"))
}
func TestBMP180DriverPressureError(t *testing.T) {
bmp180, adaptor := initTestBMP180DriverWithStubbedAdaptor()
func TestBMP180PressureError(t *testing.T) {
bmp180, adaptor := initTestBMP180WithStubbedAdaptor()
adaptor.i2cReadImpl = func(b []byte) (int, error) {
buf := new(bytes.Buffer)
// Values from the datasheet example.
@ -150,9 +128,9 @@ func TestBMP180DriverPressureError(t *testing.T) {
binary.Write(buf, binary.BigEndian, int16(-32768))
binary.Write(buf, binary.BigEndian, int16(-8711))
binary.Write(buf, binary.BigEndian, int16(2868))
} else if adaptor.written[len(adaptor.written)-2] == bmp180CmdTemp && adaptor.written[len(adaptor.written)-1] == bmp180RegisterTempMSB {
} else if adaptor.written[len(adaptor.written)-2] == bmp180CtlTemp && adaptor.written[len(adaptor.written)-1] == bmp180RegisterDataMSB {
binary.Write(buf, binary.BigEndian, int16(27898))
} else if adaptor.written[len(adaptor.written)-2] == bmp180CmdPressure && adaptor.written[len(adaptor.written)-1] == bmp180RegisterPressureMSB {
} else if adaptor.written[len(adaptor.written)-2] == bmp180CtlPressure && adaptor.written[len(adaptor.written)-1] == bmp180RegisterDataMSB {
return 0, errors.New("press error")
}
copy(b, buf.Bytes())
@ -163,8 +141,8 @@ func TestBMP180DriverPressureError(t *testing.T) {
gobottest.Assert(t, err, errors.New("press error"))
}
func TestBMP180DriverPressureWriteError(t *testing.T) {
bmp180, adaptor := initTestBMP180DriverWithStubbedAdaptor()
func TestBMP180PressureWriteError(t *testing.T) {
bmp180, adaptor := initTestBMP180WithStubbedAdaptor()
bmp180.Start()
adaptor.i2cWriteImpl = func([]byte) (int, error) {
@ -175,20 +153,56 @@ func TestBMP180DriverPressureWriteError(t *testing.T) {
gobottest.Assert(t, err, errors.New("write error"))
}
func TestBMP180DriverSetName(t *testing.T) {
b := initTestBMP180Driver()
b.SetName("TESTME")
gobottest.Assert(t, b.Name(), "TESTME")
func TestBMP180_initialization(t *testing.T) {
// sequence to read in initialization():
// * read 22 bytes (11 x 16 bit calibration data), starting from AC1 register (0xAA)
// * fill calibration struct with data (MSByte read first)
// arrange
d, a := initTestBMP180WithStubbedAdaptor()
a.written = []byte{} // reset writes of former test
// Values from the datasheet example.
ac1 := []uint8{0x01, 0x98}
ac2 := []uint8{0xFF, 0xB8}
ac3 := []uint8{0xC7, 0xD1}
ac4 := []uint8{0x7F, 0xE5}
ac5 := []uint8{0x7F, 0xF5}
ac6 := []uint8{0x5A, 0x71}
b1 := []uint8{0x18, 0x2E}
b2 := []uint8{0x00, 0x04}
mb := []uint8{0x80, 0x00}
mc := []uint8{0xDD, 0xF9}
md := []uint8{0x0B, 0x34}
returnRead := append(append(append(append(append(ac1, ac2...), ac3...), ac4...), ac5...), ac6...)
returnRead = append(append(append(append(append(returnRead, b1...), b2...), mb...), mc...), md...)
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
copy(b, returnRead)
return len(b), nil
}
// act, assert - initialization() must be called on Start()
err := d.Start()
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, numCallsRead, 1)
gobottest.Assert(t, len(a.written), 1)
gobottest.Assert(t, a.written[0], uint8(0xAA))
gobottest.Assert(t, d.calCoeffs.ac1, int16(408))
gobottest.Assert(t, d.calCoeffs.ac2, int16(-72))
gobottest.Assert(t, d.calCoeffs.ac3, int16(-14383))
gobottest.Assert(t, d.calCoeffs.ac4, uint16(32741))
gobottest.Assert(t, d.calCoeffs.ac5, uint16(32757))
gobottest.Assert(t, d.calCoeffs.ac6, uint16(23153))
gobottest.Assert(t, d.calCoeffs.b1, int16(6190))
gobottest.Assert(t, d.calCoeffs.b2, int16(4))
gobottest.Assert(t, d.calCoeffs.mb, int16(-32768))
gobottest.Assert(t, d.calCoeffs.mc, int16(-8711))
gobottest.Assert(t, d.calCoeffs.md, int16(2868))
}
func TestBMP180DriverOptions(t *testing.T) {
b := NewBMP180Driver(newI2cTestAdaptor(), WithBus(2))
gobottest.Assert(t, b.GetBusOrDefault(1), 2)
}
func TestBMP180PauseForReading(t *testing.T) {
gobottest.Assert(t, pauseForReading(BMP180UltraLowPower), time.Duration(5*time.Millisecond))
gobottest.Assert(t, pauseForReading(BMP180Standard), time.Duration(8*time.Millisecond))
gobottest.Assert(t, pauseForReading(BMP180HighResolution), time.Duration(14*time.Millisecond))
gobottest.Assert(t, pauseForReading(BMP180UltraHighResolution), time.Duration(26*time.Millisecond))
func TestBMP180_bmp180PauseForReading(t *testing.T) {
gobottest.Assert(t, bmp180PauseForReading(BMP180UltraLowPower), time.Duration(5*time.Millisecond))
gobottest.Assert(t, bmp180PauseForReading(BMP180Standard), time.Duration(8*time.Millisecond))
gobottest.Assert(t, bmp180PauseForReading(BMP180HighResolution), time.Duration(14*time.Millisecond))
gobottest.Assert(t, bmp180PauseForReading(BMP180UltraHighResolution), time.Duration(26*time.Millisecond))
}

View File

@ -3,18 +3,70 @@ package i2c
import (
"bytes"
"encoding/binary"
"log"
"math"
"gobot.io/x/gobot"
)
const bmp280Debug = true
// the default address is applicable for SDO to VDD, for SDO to GND it will be 0x76
// this is also true for bme280 (which using this address as well)
const bmp280DefaultAddress = 0x77
type BMP280PressureOversampling uint8
type BMP280TemperatureOversampling uint8
type BMP280IIRFilter uint8
const (
bmp280RegisterControl = 0xf4
bmp280RegisterConfig = 0xf5
bmp280RegisterPressureData = 0xf7
bmp280RegisterTempData = 0xfa
bmp280RegisterCalib00 = 0x88
bmp280SeaLevelPressure = 1013.25
bmp280RegCalib00 = 0x88 // 12 x 16 bit calibration data (T1..T3, P1..P9)
bmp280RegCtrl = 0xF4 // data acquisition options (oversampling of temperature and pressure, power mode)
bmp280RegConf = 0xF5 // rate, IIR-filter and interface options (SPI)
bmp280RegPressureData = 0xF7
bmp280RegTempData = 0xFA
// bits 0, 1 of control register
bmp280CtrlPwrSleepMode = 0x00
bmp280CtrlPwrForcedMode = 0x01
bmp280CtrlPwrForcedMode2 = 0x02 // same function as 0x01
bmp280CtrlPwrNormalMode = 0x03
// bits 2, 3, 4 of control register (will be shifted on write)
BMP280CtrlPressNoMeasurement BMP280PressureOversampling = 0x00 // no measurement (value will be 0x08 0x00 0x00)
BMP280CtrlPressOversampling1 BMP280PressureOversampling = 0x01 // resolution 16 bit
BMP280CtrlPressOversampling2 BMP280PressureOversampling = 0x02 // resolution 17 bit
BMP280CtrlPressOversampling4 BMP280PressureOversampling = 0x03 // resolution 18 bit
BMP280CtrlPressOversampling8 BMP280PressureOversampling = 0x04 // resolution 19 bit
BMP280CtrlPressOversampling16 BMP280PressureOversampling = 0x05 // resolution 20 bit (same as 0x06, 0x07)
// bits 5, 6, 7 of control register (will be shifted on write)
BMP280CtrlTempNoMeasurement BMP280TemperatureOversampling = 0x00 // no measurement (value will be 0x08 0x00 0x00)
BMP280CtrlTempOversampling1 BMP280TemperatureOversampling = 0x01 // resolution 16 bit
BMP280CtrlTempOversampling2 BMP280TemperatureOversampling = 0x02 // resolution 17 bit
BMP280CtrlTempOversampling4 BMP280TemperatureOversampling = 0x03 // resolution 18 bit
BMP280CtrlTempOversampling8 BMP280TemperatureOversampling = 0x04 // resolution 19 bit
BMP280CtrlTempOversampling16 BMP280TemperatureOversampling = 0x05 // resolution 20 bit
// bit 0 of config register
bmp280ConfSPIBit = 0x01 // if set, SPI is used
// bits 2, 3, 4 of config register (bit 1 is unused, will be shifted on write)
bmp280ConfStandBy0005 = 0x00 // 0.5 ms
bmp280ConfStandBy0625 = 0x01 // 62.5 ms
bmp280ConfStandBy0125 = 0x02 // 125 ms
bmp280ConfStandBy0250 = 0x03 // 250 ms
bmp280ConfStandBy0500 = 0x04 // 500 ms
bmp280ConfStandBy1000 = 0x05 // 1000 ms
bmp280ConfStandBy2000 = 0x06 // 2000 ms
bmp280ConfStandBy4000 = 0x07 // 4000 ms
// bits 5, 6, 7 of config register
BMP280ConfFilterOff BMP280IIRFilter = 0x00
BMP280ConfFilter2 BMP280IIRFilter = 0x01
BMP280ConfFilter4 BMP280IIRFilter = 0x02
BMP280ConfFilter8 BMP280IIRFilter = 0x03
BMP280ConfFilter16 BMP280IIRFilter = 0x04
bmp280SeaLevelPressure = 1013.25
)
type bmp280CalibrationCoefficients struct {
@ -34,76 +86,82 @@ type bmp280CalibrationCoefficients struct {
// BMP280Driver is a driver for the BMP280 temperature/pressure sensor
type BMP280Driver struct {
name string
connector Connector
connection Connection
Config
tpc *bmp280CalibrationCoefficients
*Driver
calCoeffs *bmp280CalibrationCoefficients
ctrlPwrMode uint8
ctrlPressOversamp BMP280PressureOversampling
ctrlTempOversamp BMP280TemperatureOversampling
confFilter BMP280IIRFilter
}
// NewBMP280Driver creates a new driver with specified i2c interface.
// Params:
// conn Connector - the Adaptor to use with this Driver
// c Connector - the Adaptor to use with this Driver
//
// Optional params:
// i2c.WithBus(int): bus to use with this driver
// i2c.WithAddress(int): address to use with this driver
//
func NewBMP280Driver(c Connector, options ...func(Config)) *BMP280Driver {
b := &BMP280Driver{
name: gobot.DefaultName("BMP280"),
connector: c,
Config: NewConfig(),
tpc: &bmp280CalibrationCoefficients{},
d := &BMP280Driver{
Driver: NewDriver(c, "BMP280", bmp280DefaultAddress),
calCoeffs: &bmp280CalibrationCoefficients{},
ctrlPwrMode: bmp280CtrlPwrNormalMode,
ctrlPressOversamp: BMP280CtrlPressOversampling16,
ctrlTempOversamp: BMP280CtrlTempOversampling1,
confFilter: BMP280ConfFilterOff,
}
d.afterStart = d.initialization
for _, option := range options {
option(b)
option(d)
}
// TODO: expose commands to API
return b
return d
}
// Name returns the name of the device.
func (d *BMP280Driver) Name() string {
return d.name
}
// SetName sets the name of the device.
func (d *BMP280Driver) SetName(n string) {
d.name = n
}
// Connection returns the connection of the device.
func (d *BMP280Driver) Connection() gobot.Connection {
return d.connector.(gobot.Connection)
}
// Start initializes the BMP280 and loads the calibration coefficients.
func (d *BMP280Driver) Start() (err error) {
bus := d.GetBusOrDefault(d.connector.GetDefaultBus())
address := d.GetAddressOrDefault(bmp180Address)
if d.connection, err = d.connector.GetConnection(address, bus); err != nil {
return err
// WithBMP280PressureOversampling option sets the oversampling for pressure.
// Valid settings are of type "BMP280PressureOversampling"
func WithBMP280PressureOversampling(val BMP280PressureOversampling) func(Config) {
return func(c Config) {
if d, ok := c.(*BMP280Driver); ok {
d.ctrlPressOversamp = val
} else if bmp280Debug {
log.Printf("Trying to set pressure oversampling for non-BMP280Driver %v", c)
}
}
if err := d.initialization(); err != nil {
return err
}
return nil
}
// Halt halts the device.
func (d *BMP280Driver) Halt() (err error) {
return nil
// WithBMP280TemperatureOversampling option sets oversampling for temperature.
// Valid settings are of type "BMP280TemperatureOversampling"
func WithBMP280TemperatureOversampling(val BMP280TemperatureOversampling) func(Config) {
return func(c Config) {
if d, ok := c.(*BMP280Driver); ok {
d.ctrlTempOversamp = val
} else if bmp280Debug {
log.Printf("Trying to set temperature oversampling for non-BMP280Driver %v", c)
}
}
}
// WithBMP280IIRFilter option sets the count of IIR filter coefficients.
// Valid settings are of type "BMP280IIRFilter"
func WithBMP280IIRFilter(val BMP280IIRFilter) func(Config) {
return func(c Config) {
if d, ok := c.(*BMP280Driver); ok {
d.confFilter = val
} else if bmp280Debug {
log.Printf("Trying to set IIR filter for non-BMP280Driver %v", c)
}
}
}
// Temperature returns the current temperature, in celsius degrees.
func (d *BMP280Driver) Temperature() (temp float32, err error) {
d.mutex.Lock()
defer d.mutex.Unlock()
var rawT int32
if rawT, err = d.rawTemp(); err != nil {
return 0.0, err
@ -114,6 +172,9 @@ func (d *BMP280Driver) Temperature() (temp float32, err error) {
// Pressure returns the current barometric pressure, in Pa
func (d *BMP280Driver) Pressure() (press float32, err error) {
d.mutex.Lock()
defer d.mutex.Unlock()
var rawT, rawP int32
if rawT, err = d.rawTemp(); err != nil {
return 0.0, err
@ -140,34 +201,38 @@ func (d *BMP280Driver) Altitude() (alt float32, err error) {
// initialization reads the calibration coefficients.
func (d *BMP280Driver) initialization() (err error) {
var coefficients []byte
if coefficients, err = d.read(bmp280RegisterCalib00, 24); err != nil {
coefficients := make([]byte, 24)
if err = d.connection.ReadBlockData(bmp280RegCalib00, coefficients); err != nil {
return err
}
buf := bytes.NewBuffer(coefficients)
binary.Read(buf, binary.LittleEndian, &d.tpc.t1)
binary.Read(buf, binary.LittleEndian, &d.tpc.t2)
binary.Read(buf, binary.LittleEndian, &d.tpc.t3)
binary.Read(buf, binary.LittleEndian, &d.tpc.p1)
binary.Read(buf, binary.LittleEndian, &d.tpc.p2)
binary.Read(buf, binary.LittleEndian, &d.tpc.p3)
binary.Read(buf, binary.LittleEndian, &d.tpc.p4)
binary.Read(buf, binary.LittleEndian, &d.tpc.p5)
binary.Read(buf, binary.LittleEndian, &d.tpc.p6)
binary.Read(buf, binary.LittleEndian, &d.tpc.p7)
binary.Read(buf, binary.LittleEndian, &d.tpc.p8)
binary.Read(buf, binary.LittleEndian, &d.tpc.p9)
binary.Read(buf, binary.LittleEndian, &d.calCoeffs.t1)
binary.Read(buf, binary.LittleEndian, &d.calCoeffs.t2)
binary.Read(buf, binary.LittleEndian, &d.calCoeffs.t3)
binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p1)
binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p2)
binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p3)
binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p4)
binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p5)
binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p6)
binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p7)
binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p8)
binary.Read(buf, binary.LittleEndian, &d.calCoeffs.p9)
d.connection.WriteByteData(bmp280RegisterControl, 0x3F)
ctrlReg := uint8(d.ctrlPwrMode) | uint8(d.ctrlPressOversamp)<<2 | uint8(d.ctrlTempOversamp)<<5
d.connection.WriteByteData(bmp280RegCtrl, ctrlReg)
confReg := uint8(bmp280ConfStandBy0005)<<2 | uint8(d.confFilter)<<5
d.connection.WriteByteData(bmp280RegConf, confReg & ^uint8(bmp280ConfSPIBit))
return nil
}
func (d *BMP280Driver) rawTemp() (temp int32, err error) {
var data []byte
var tp0, tp1, tp2 byte
if data, err = d.read(bmp280RegisterTempData, 3); err != nil {
data := make([]byte, 3)
if err = d.connection.ReadBlockData(bmp280RegTempData, data); err != nil {
return 0, err
}
buf := bytes.NewBuffer(data)
@ -181,10 +246,10 @@ func (d *BMP280Driver) rawTemp() (temp int32, err error) {
}
func (d *BMP280Driver) rawPressure() (press int32, err error) {
var data []byte
var tp0, tp1, tp2 byte
if data, err = d.read(bmp280RegisterPressureData, 3); err != nil {
data := make([]byte, 3)
if err = d.connection.ReadBlockData(bmp280RegPressureData, data); err != nil {
return 0, err
}
buf := bytes.NewBuffer(data)
@ -198,8 +263,8 @@ func (d *BMP280Driver) rawPressure() (press int32, err error) {
}
func (d *BMP280Driver) calculateTemp(rawTemp int32) (float32, int32) {
tcvar1 := ((float32(rawTemp) / 16384.0) - (float32(d.tpc.t1) / 1024.0)) * float32(d.tpc.t2)
tcvar2 := (((float32(rawTemp) / 131072.0) - (float32(d.tpc.t1) / 8192.0)) * ((float32(rawTemp) / 131072.0) - float32(d.tpc.t1)/8192.0)) * float32(d.tpc.t3)
tcvar1 := ((float32(rawTemp) / 16384.0) - (float32(d.calCoeffs.t1) / 1024.0)) * float32(d.calCoeffs.t2)
tcvar2 := (((float32(rawTemp) / 131072.0) - (float32(d.calCoeffs.t1) / 8192.0)) * ((float32(rawTemp) / 131072.0) - float32(d.calCoeffs.t1)/8192.0)) * float32(d.calCoeffs.t3)
temperatureComp := (tcvar1 + tcvar2) / 5120.0
tFine := int32(tcvar1 + tcvar2)
@ -210,33 +275,21 @@ func (d *BMP280Driver) calculatePress(rawPress int32, tFine int32) float32 {
var var1, var2, p int64
var1 = int64(tFine) - 128000
var2 = var1 * var1 * int64(d.tpc.p6)
var2 = var2 + ((var1 * int64(d.tpc.p5)) << 17)
var2 = var2 + (int64(d.tpc.p4) << 35)
var1 = (var1 * var1 * int64(d.tpc.p3) >> 8) +
((var1 * int64(d.tpc.p2)) << 12)
var1 = ((int64(1) << 47) + var1) * (int64(d.tpc.p1)) >> 33
var2 = var1 * var1 * int64(d.calCoeffs.p6)
var2 = var2 + ((var1 * int64(d.calCoeffs.p5)) << 17)
var2 = var2 + (int64(d.calCoeffs.p4) << 35)
var1 = (var1 * var1 * int64(d.calCoeffs.p3) >> 8) +
((var1 * int64(d.calCoeffs.p2)) << 12)
var1 = ((int64(1) << 47) + var1) * (int64(d.calCoeffs.p1)) >> 33
if var1 == 0 {
return 0 // avoid exception caused by division by zero
}
p = 1048576 - int64(rawPress)
p = (((p << 31) - var2) * 3125) / var1
var1 = (int64(d.tpc.p9) * (p >> 13) * (p >> 13)) >> 25
var2 = (int64(d.tpc.p8) * p) >> 19
var1 = (int64(d.calCoeffs.p9) * (p >> 13) * (p >> 13)) >> 25
var2 = (int64(d.calCoeffs.p8) * p) >> 19
p = ((p + var1 + var2) >> 8) + (int64(d.tpc.p7) << 4)
p = ((p + var1 + var2) >> 8) + (int64(d.calCoeffs.p7) << 4)
return float32(p) / 256
}
func (d *BMP280Driver) read(address byte, n int) ([]byte, error) {
if _, err := d.connection.Write([]byte{address}); err != nil {
return nil, err
}
buf := make([]byte, n)
bytesRead, err := d.connection.Read(buf)
if bytesRead != n || err != nil {
return nil, err
}
return buf, nil
}

View File

@ -3,156 +3,209 @@ package i2c
import (
"bytes"
"errors"
"strings"
"testing"
"gobot.io/x/gobot"
"gobot.io/x/gobot/gobottest"
)
// this ensures that the implementation is based on i2c.Driver, which implements the gobot.Driver
// and tests all implementations, so no further tests needed here for gobot.Driver interface
var _ gobot.Driver = (*BMP280Driver)(nil)
// --------- HELPERS
func initTestBMP280Driver() (driver *BMP280Driver) {
driver, _ = initTestBMP280DriverWithStubbedAdaptor()
return
}
func initTestBMP280DriverWithStubbedAdaptor() (*BMP280Driver, *i2cTestAdaptor) {
func initTestBMP280WithStubbedAdaptor() (*BMP280Driver, *i2cTestAdaptor) {
adaptor := newI2cTestAdaptor()
return NewBMP280Driver(adaptor), adaptor
}
// --------- TESTS
func TestNewBMP280Driver(t *testing.T) {
// Does it return a pointer to an instance of BME280Driver?
var bmp280 interface{} = NewBMP280Driver(newI2cTestAdaptor())
_, ok := bmp280.(*BMP280Driver)
var di interface{} = NewBMP280Driver(newI2cTestAdaptor())
d, ok := di.(*BMP280Driver)
if !ok {
t.Errorf("NewBMP280Driver() should have returned a *BMP280Driver")
}
gobottest.Refute(t, d.Driver, nil)
gobottest.Assert(t, strings.HasPrefix(d.Name(), "BMP280"), true)
gobottest.Assert(t, d.defaultAddress, 0x77)
gobottest.Assert(t, d.ctrlPwrMode, uint8(0x03))
gobottest.Assert(t, d.ctrlPressOversamp, BMP280PressureOversampling(0x05))
gobottest.Assert(t, d.ctrlTempOversamp, BMP280TemperatureOversampling(0x01))
gobottest.Assert(t, d.confFilter, BMP280IIRFilter(0x00))
gobottest.Refute(t, d.calCoeffs, nil)
}
func TestBMP280Driver(t *testing.T) {
bmp280 := initTestBMP280Driver()
gobottest.Refute(t, bmp280.Connection(), nil)
func TestBMP280Options(t *testing.T) {
// This is a general test, that options are applied in constructor by using the common WithBus() option and
// least one of this driver. Further tests for options can also be done by call of "WithOption(val)(d)".
d := NewBMP280Driver(newI2cTestAdaptor(), WithBus(2), WithBMP280PressureOversampling(0x04))
gobottest.Assert(t, d.GetBusOrDefault(1), 2)
gobottest.Assert(t, d.ctrlPressOversamp, BMP280PressureOversampling(0x04))
}
func TestBMP280DriverStart(t *testing.T) {
bmp280, _ := initTestBMP280DriverWithStubbedAdaptor()
gobottest.Assert(t, bmp280.Start(), nil)
func TestWithBMP280TemperatureOversampling(t *testing.T) {
// arrange
d, a := initTestBMP280WithStubbedAdaptor()
a.written = []byte{} // reset writes of former test
const (
setVal = BMP280TemperatureOversampling(0x04) // 8 x
)
// act
WithBMP280TemperatureOversampling(setVal)(d)
// assert
gobottest.Assert(t, d.ctrlTempOversamp, setVal)
gobottest.Assert(t, len(a.written), 0)
}
func TestBMP280StartConnectError(t *testing.T) {
d, adaptor := initTestBMP280DriverWithStubbedAdaptor()
adaptor.Testi2cConnectErr(true)
gobottest.Assert(t, d.Start(), errors.New("Invalid i2c connection"))
func TestWithBMP280IIRFilter(t *testing.T) {
// arrange
d, a := initTestBMP280WithStubbedAdaptor()
a.written = []byte{} // reset writes of former test
const (
setVal = BMP280IIRFilter(0x02) // 4 x
)
// act
WithBMP280IIRFilter(setVal)(d)
// assert
gobottest.Assert(t, d.confFilter, setVal)
gobottest.Assert(t, len(a.written), 0)
}
func TestBMP280DriverStartWriteError(t *testing.T) {
bmp280, adaptor := initTestBMP280DriverWithStubbedAdaptor()
adaptor.i2cWriteImpl = func([]byte) (int, error) {
return 0, errors.New("write error")
}
gobottest.Assert(t, bmp280.Start(), errors.New("write error"))
}
func TestBMP280DriverStartReadError(t *testing.T) {
bmp280, adaptor := initTestBMP280DriverWithStubbedAdaptor()
adaptor.i2cReadImpl = func(b []byte) (int, error) {
return 0, errors.New("read error")
}
gobottest.Assert(t, bmp280.Start(), errors.New("read error"))
}
func TestBMP280DriverHalt(t *testing.T) {
bmp280 := initTestBMP280Driver()
gobottest.Assert(t, bmp280.Halt(), nil)
}
func TestBMP280DriverMeasurements(t *testing.T) {
bmp280, adaptor := initTestBMP280DriverWithStubbedAdaptor()
func TestBMP280Measurements(t *testing.T) {
d, adaptor := initTestBMP280WithStubbedAdaptor()
adaptor.i2cReadImpl = func(b []byte) (int, error) {
buf := new(bytes.Buffer)
// Values produced by dumping data from actual sensor
if adaptor.written[len(adaptor.written)-1] == bmp280RegisterCalib00 {
if adaptor.written[len(adaptor.written)-1] == bmp280RegCalib00 {
buf.Write([]byte{126, 109, 214, 102, 50, 0, 54, 149, 220, 213, 208, 11, 64, 30, 166, 255, 249, 255, 172, 38, 10, 216, 189, 16})
} else if adaptor.written[len(adaptor.written)-1] == bmp280RegisterTempData {
} else if adaptor.written[len(adaptor.written)-1] == bmp280RegTempData {
buf.Write([]byte{128, 243, 0})
} else if adaptor.written[len(adaptor.written)-1] == bmp280RegisterPressureData {
} else if adaptor.written[len(adaptor.written)-1] == bmp280RegPressureData {
buf.Write([]byte{77, 23, 48})
}
copy(b, buf.Bytes())
return buf.Len(), nil
}
bmp280.Start()
temp, err := bmp280.Temperature()
d.Start()
temp, err := d.Temperature()
gobottest.Assert(t, err, nil)
gobottest.Assert(t, temp, float32(25.014637))
pressure, err := bmp280.Pressure()
pressure, err := d.Pressure()
gobottest.Assert(t, err, nil)
gobottest.Assert(t, pressure, float32(99545.414))
alt, err := bmp280.Altitude()
alt, err := d.Altitude()
gobottest.Assert(t, err, nil)
gobottest.Assert(t, alt, float32(149.22713))
}
func TestBMP280DriverTemperatureWriteError(t *testing.T) {
bmp280, adaptor := initTestBMP280DriverWithStubbedAdaptor()
bmp280.Start()
func TestBMP280TemperatureWriteError(t *testing.T) {
d, adaptor := initTestBMP280WithStubbedAdaptor()
d.Start()
adaptor.i2cWriteImpl = func([]byte) (int, error) {
return 0, errors.New("write error")
}
temp, err := bmp280.Temperature()
temp, err := d.Temperature()
gobottest.Assert(t, err, errors.New("write error"))
gobottest.Assert(t, temp, float32(0.0))
}
func TestBMP280DriverTemperatureReadError(t *testing.T) {
bmp280, adaptor := initTestBMP280DriverWithStubbedAdaptor()
bmp280.Start()
func TestBMP280TemperatureReadError(t *testing.T) {
d, adaptor := initTestBMP280WithStubbedAdaptor()
d.Start()
adaptor.i2cReadImpl = func([]byte) (int, error) {
return 0, errors.New("read error")
}
temp, err := bmp280.Temperature()
temp, err := d.Temperature()
gobottest.Assert(t, err, errors.New("read error"))
gobottest.Assert(t, temp, float32(0.0))
}
func TestBMP280DriverPressureWriteError(t *testing.T) {
bmp280, adaptor := initTestBMP280DriverWithStubbedAdaptor()
bmp280.Start()
func TestBMP280PressureWriteError(t *testing.T) {
d, adaptor := initTestBMP280WithStubbedAdaptor()
d.Start()
adaptor.i2cWriteImpl = func([]byte) (int, error) {
return 0, errors.New("write error")
}
press, err := bmp280.Pressure()
press, err := d.Pressure()
gobottest.Assert(t, err, errors.New("write error"))
gobottest.Assert(t, press, float32(0.0))
}
func TestBMP280DriverPressureReadError(t *testing.T) {
bmp280, adaptor := initTestBMP280DriverWithStubbedAdaptor()
bmp280.Start()
func TestBMP280PressureReadError(t *testing.T) {
d, adaptor := initTestBMP280WithStubbedAdaptor()
d.Start()
adaptor.i2cReadImpl = func([]byte) (int, error) {
return 0, errors.New("read error")
}
press, err := bmp280.Pressure()
press, err := d.Pressure()
gobottest.Assert(t, err, errors.New("read error"))
gobottest.Assert(t, press, float32(0.0))
}
func TestBMP280DriverSetName(t *testing.T) {
b := initTestBMP280Driver()
b.SetName("TESTME")
gobottest.Assert(t, b.Name(), "TESTME")
}
func TestBMP280DriverOptions(t *testing.T) {
b := NewBMP280Driver(newI2cTestAdaptor(), WithBus(2))
gobottest.Assert(t, b.GetBusOrDefault(1), 2)
func TestBMP280_initialization(t *testing.T) {
// sequence to read and write in initialization():
// * read 24 bytes (12 x 16 bit calibration data), starting from TC1 register (0x88)
// * fill calibration struct with data (LSByte read first)
// * prepare the content of control register
// * write the control register (0xF4)
// * prepare the content of config register
// * write the config register (0xF5)
// arrange
d, a := initTestBMP280WithStubbedAdaptor()
a.written = []byte{} // reset writes of former test
const (
wantCalibReg = uint8(0x88)
wantCtrlReg = uint8(0xF4)
wantCtrlRegVal = uint8(0x37) // normal power mode, 16 x pressure and 1 x temperature oversampling
wantConfReg = uint8(0xF5)
wantConfRegVal = uint8(0x00) // no SPI, no filter, smallest standby (unused, because normal power mode)
)
// Values from the datasheet example.
t1 := []uint8{0x70, 0x6B}
t2 := []uint8{0x43, 0x67}
t3 := []uint8{0x18, 0xFC}
p1 := []uint8{0x7D, 0x8E}
p2 := []uint8{0x43, 0xD6}
p3 := []uint8{0xD0, 0x0B}
p4 := []uint8{0x27, 0x0B}
p5 := []uint8{0x8C, 0x00}
p6 := []uint8{0xF9, 0xFF}
p7 := []uint8{0x8C, 0x3C}
p8 := []uint8{0xF8, 0xC6}
p9 := []uint8{0x70, 0x17}
returnRead := append(append(append(append(append(append(t1, t2...), t3...), p1...), p2...), p3...), p4...)
returnRead = append(append(append(append(append(returnRead, p5...), p6...), p7...), p8...), p9...)
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
copy(b, returnRead)
return len(b), nil
}
// act, assert - initialization() must be called on Start()
err := d.Start()
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, numCallsRead, 1)
gobottest.Assert(t, len(a.written), 5)
gobottest.Assert(t, a.written[0], wantCalibReg)
gobottest.Assert(t, a.written[1], wantCtrlReg)
gobottest.Assert(t, a.written[2], wantCtrlRegVal)
gobottest.Assert(t, a.written[3], wantConfReg)
gobottest.Assert(t, a.written[4], wantConfRegVal)
gobottest.Assert(t, d.calCoeffs.t1, uint16(27504))
gobottest.Assert(t, d.calCoeffs.t2, int16(26435))
gobottest.Assert(t, d.calCoeffs.t3, int16(-1000))
gobottest.Assert(t, d.calCoeffs.p1, uint16(36477))
gobottest.Assert(t, d.calCoeffs.p2, int16(-10685))
gobottest.Assert(t, d.calCoeffs.p3, int16(3024))
gobottest.Assert(t, d.calCoeffs.p4, int16(2855))
gobottest.Assert(t, d.calCoeffs.p5, int16(140))
gobottest.Assert(t, d.calCoeffs.p6, int16(-7))
gobottest.Assert(t, d.calCoeffs.p7, int16(15500))
gobottest.Assert(t, d.calCoeffs.p8, int16(-14600))
gobottest.Assert(t, d.calCoeffs.p9, int16(6000))
}

View File

@ -4,57 +4,66 @@ import (
"bytes"
"encoding/binary"
"fmt"
"log"
"math"
"gobot.io/x/gobot"
)
const bmp388Debug = false
// the default address is applicable for SDO to VDD, for SDO to GND it will be 0x76
const bmp388DefaultAddress = 0x77
// BMP388Accuracy accuracy type
type BMP388Accuracy uint8
type BMP388IIRFilter uint8
const (
bmp388ChipID = 0x50
bmp388RegisterChipID = 0x00
bmp388RegisterStatus = 0x03
bmp388RegisterConfig = 0x1F
bmp388RegisterPressureData = 0x04
bmp388RegisterTempData = 0x07
bmp388RegisterCalib00 = 0x31
bmp388RegisterCMD = 0x7E
// CMD : 0x00 nop (reserved. No command.)
// : 0x34 extmode_en_middle
// : 0xB0 fifo_flush (Clears all data in the FIFO, does not change FIFO_CONFIG registers)
// : 0xB6 softreset (Triggers a reset, all user configuration settings are overwritten with their default state)
bmp388RegisterODR = 0x1D // Output Data Rates
bmp388RegisterOSR = 0x1C // Oversampling Rates
bmp388RegChipID = 0x00
bmp388RegStatus = 0x03
bmp388RegPressureData = 0x04 // XLSB, 0x05 LSByte, 0x06 MSByte
bmp388RegTempData = 0x07 // XLSB, 0x08 LSByte, 0x09 MSByte
bmp388RegPWRCTRL = 0x1B // enable/disable pressure and temperature measurement, mode
bmp388RegOSR = 0x1C // Oversampling Rates
bmp388RegODR = 0x1D // Output Data Rates
bmp388RegConf = 0x1F // config filter for IIR coefficients
bmp388RegCalib00 = 0x31
bmp388RegCMD = 0x7E
bmp388RegisterPWRCTRL = 0x1B
bmp388PWRCTRLSleep = 0
bmp388PWRCTRLForced = 1
bmp388PWRCTRLNormal = 3
// bits 0, 1 of control register
bmp388PWRCTRLPressEnableBit = 0x01
bmp388PWRCTRLTempEnableBit = 0x02
bmp388SeaLevelPressure = 1013.25
// bits 4, 5 of control register (will be shifted on write)
bmp388PWRCTRLSleep = 0x00
bmp388PWRCTRLForced = 0x01 // same as 0x02
bmp388PWRCTRLNormal = 0x03
// IIR filter coefficients
bmp388IIRFIlterCoef0 = 0 // bypass-mode
bmp388IIRFIlterCoef1 = 1
bmp388IIRFIlterCoef3 = 2
bmp388IIRFIlterCoef7 = 3
bmp388IIRFIlterCoef15 = 4
bmp388IIRFIlterCoef31 = 5
bmp388IIRFIlterCoef63 = 6
bmp388IIRFIlterCoef127 = 7
)
// bits 1, 2 ,3 of config filter IIR filter coefficients (will be shifted on write)
bmp388ConfFilterCoef0 BMP388IIRFilter = 0 // bypass-mode
bmp388ConfFilterCoef1 BMP388IIRFilter = 1
bmp388ConfFilterCoef3 BMP388IIRFilter = 2
bmp388ConfFilterCoef7 BMP388IIRFilter = 3
bmp388ConfFilterCoef15 BMP388IIRFilter = 4
bmp388ConfFilterCoef31 BMP388IIRFilter = 5
bmp388ConfFilterCoef63 BMP388IIRFilter = 6
bmp388ConfFilterCoef127 BMP388IIRFilter = 7
// BMP388Accuracy accuracy type
type BMP388Accuracy uint8
// BMP388Accuracy accuracy modes
const (
// oversampling rate, a single value is used (could be different for pressure and temperature)
BMP388AccuracyUltraLow BMP388Accuracy = 0 // x1 sample
BMP388AccuracyLow BMP388Accuracy = 1 // x2 samples
BMP388AccuracyStandard BMP388Accuracy = 2 // x4 samples
BMP388AccuracyHigh BMP388Accuracy = 3 // x8 samples
BMP388AccuracyUltraHigh BMP388Accuracy = 4 // x16 samples
BMP388AccuracyHighest BMP388Accuracy = 5 // x32 samples
bmp388CMDReserved = 0x00 // reserved, no command
bmp388CMDExtModeEnMiddle = 0x34
bmp388CMDFifoFlush = 0xB0 // clears all data in the FIFO, does not change FIFO_CONFIG registers
bmp388CMDSoftReset = 0xB6 // triggers a reset, all user configuration settings are overwritten with their default state
bmp388SeaLevelPressure = 1013.25
)
type bmp388CalibrationCoefficients struct {
@ -76,96 +85,63 @@ type bmp388CalibrationCoefficients struct {
// BMP388Driver is a driver for the BMP388 temperature/pressure sensor
type BMP388Driver struct {
name string
connector Connector
connection Connection
Config
tpc *bmp388CalibrationCoefficients
*Driver
calCoeffs *bmp388CalibrationCoefficients
ctrlPwrMode uint8
confFilter BMP388IIRFilter
}
// NewBMP388Driver creates a new driver with specified i2c interface.
// Params:
// conn Connector - the Adaptor to use with this Driver
// c Connector - the Adaptor to use with this Driver
//
// Optional params:
// i2c.WithBus(int): bus to use with this driver
// i2c.WithAddress(int): address to use with this driver
//
func NewBMP388Driver(c Connector, options ...func(Config)) *BMP388Driver {
b := &BMP388Driver{
name: gobot.DefaultName("BMP388"),
connector: c,
Config: NewConfig(),
tpc: &bmp388CalibrationCoefficients{},
d := &BMP388Driver{
Driver: NewDriver(c, "BMP388", bmp388DefaultAddress),
calCoeffs: &bmp388CalibrationCoefficients{},
ctrlPwrMode: bmp388PWRCTRLForced,
confFilter: bmp388ConfFilterCoef0,
}
d.afterStart = d.initialization
for _, option := range options {
option(b)
option(d)
}
// TODO: expose commands to API
return b
return d
}
// Name returns the name of the device.
func (d *BMP388Driver) Name() string {
return d.name
}
// SetName sets the name of the device.
func (d *BMP388Driver) SetName(n string) {
d.name = n
}
// Connection returns the connection of the device.
func (d *BMP388Driver) Connection() gobot.Connection {
return d.connector.(gobot.Connection)
}
// Start initializes the BMP388 and loads the calibration coefficients.
func (d *BMP388Driver) Start() (err error) {
var chipID uint8
bus := d.GetBusOrDefault(d.connector.GetDefaultBus())
address := d.GetAddressOrDefault(bmp180Address)
if d.connection, err = d.connector.GetConnection(address, bus); err != nil {
return err
// WithBMP388IIRFilter option sets count of IIR filter coefficients.
// Valid settings are of type "BMP388IIRFilter"
func WithBMP388IIRFilter(val BMP388IIRFilter) func(Config) {
return func(c Config) {
if d, ok := c.(*BMP388Driver); ok {
d.confFilter = val
} else if bmp388Debug {
log.Printf("Trying to set IIR filter for non-BMP388Driver %v", c)
}
}
if chipID, err = d.connection.ReadByteData(bmp388RegisterChipID); err != nil {
return err
}
if bmp388ChipID != chipID {
return fmt.Errorf("Incorrect BMP388 chip ID '0%x' Expected 0x%x", chipID, bmp388ChipID)
}
if err := d.initialization(); err != nil {
return err
}
return nil
}
// Halt halts the device.
func (d *BMP388Driver) Halt() (err error) {
return nil
}
// Temperature returns the current temperature, in celsius degrees.
func (d *BMP388Driver) Temperature(accuracy BMP388Accuracy) (temp float32, err error) {
d.mutex.Lock()
defer d.mutex.Unlock()
var rawT int32
// Enable Pressure and Temperature measurement, set FORCED operating mode
var mode byte = (bmp388PWRCTRLForced << 4) | 3 // 1100|1|1 == mode|T|P
if err = d.connection.WriteByteData(bmp388RegisterPWRCTRL, mode); err != nil {
mode := uint8(d.ctrlPwrMode)<<4 | bmp388PWRCTRLPressEnableBit | bmp388PWRCTRLTempEnableBit
if err = d.connection.WriteByteData(bmp388RegPWRCTRL, mode); err != nil {
return 0, err
}
// Set Accuracy for temperature
if err = d.connection.WriteByteData(bmp388RegisterOSR, uint8(accuracy<<3)); err != nil {
if err = d.connection.WriteByteData(bmp388RegOSR, uint8(accuracy<<3)); err != nil {
return 0, err
}
@ -178,16 +154,18 @@ func (d *BMP388Driver) Temperature(accuracy BMP388Accuracy) (temp float32, err e
// Pressure returns the current barometric pressure, in Pa
func (d *BMP388Driver) Pressure(accuracy BMP388Accuracy) (press float32, err error) {
d.mutex.Lock()
defer d.mutex.Unlock()
var rawT, rawP int32
// Enable Pressure and Temperature measurement, set FORCED operating mode
var mode byte = (bmp388PWRCTRLForced << 4) | 3 // 1100|1|1 == mode|T|P
if err = d.connection.WriteByteData(bmp388RegisterPWRCTRL, mode); err != nil {
mode := uint8(d.ctrlPwrMode)<<4 | bmp388PWRCTRLPressEnableBit | bmp388PWRCTRLTempEnableBit
if err = d.connection.WriteByteData(bmp388RegPWRCTRL, mode); err != nil {
return 0, err
}
// Set Standard Accuracy for pressure
if err = d.connection.WriteByteData(bmp388RegisterOSR, uint8(accuracy)); err != nil {
if err = d.connection.WriteByteData(bmp388RegOSR, uint8(accuracy)); err != nil {
return 0, err
}
@ -215,25 +193,34 @@ func (d *BMP388Driver) Altitude(accuracy BMP388Accuracy) (alt float32, err error
// initialization reads the calibration coefficients.
func (d *BMP388Driver) initialization() (err error) {
var chipID uint8
if chipID, err = d.connection.ReadByteData(bmp388RegChipID); err != nil {
return err
}
if bmp388ChipID != chipID {
return fmt.Errorf("Incorrect BMP388 chip ID '0%x' Expected 0x%x", chipID, bmp388ChipID)
}
var (
coefficients []byte
t1 uint16
t2 uint16
t3 int8
p1 int16
p2 int16
p3 int8
p4 int8
p5 uint16
p6 uint16
p7 int8
p8 int8
p9 int16
p10 int8
p11 int8
t1 uint16
t2 uint16
t3 int8
p1 int16
p2 int16
p3 int8
p4 int8
p5 uint16
p6 uint16
p7 int8
p8 int8
p9 int16
p10 int8
p11 int8
)
if coefficients, err = d.read(bmp388RegisterCalib00, 24); err != nil {
coefficients := make([]byte, 24)
if err = d.connection.ReadBlockData(bmp388RegCalib00, coefficients); err != nil {
return err
}
buf := bytes.NewBuffer(coefficients)
@ -253,29 +240,26 @@ func (d *BMP388Driver) initialization() (err error) {
binary.Read(buf, binary.LittleEndian, &p10)
binary.Read(buf, binary.LittleEndian, &p11)
d.tpc.t1 = float32(float64(t1) / math.Pow(2, -8))
d.tpc.t2 = float32(float64(t2) / math.Pow(2, 30))
d.tpc.t3 = float32(float64(t3) / math.Pow(2, 48))
d.tpc.p1 = float32((float64(p1) - math.Pow(2, 14)) / math.Pow(2, 20))
d.tpc.p2 = float32((float64(p2) - math.Pow(2, 14)) / math.Pow(2, 29))
d.tpc.p3 = float32(float64(p3) / math.Pow(2, 32))
d.tpc.p4 = float32(float64(p4) / math.Pow(2, 37))
d.tpc.p5 = float32(float64(p5) / math.Pow(2, -3))
d.tpc.p6 = float32(float64(p6) / math.Pow(2, 6))
d.tpc.p7 = float32(float64(p7) / math.Pow(2, 8))
d.tpc.p8 = float32(float64(p8) / math.Pow(2, 15))
d.tpc.p9 = float32(float64(p9) / math.Pow(2, 48))
d.tpc.p10 = float32(float64(p10) / math.Pow(2, 48))
d.tpc.p11 = float32(float64(p11) / math.Pow(2, 65))
d.calCoeffs.t1 = float32(float64(t1) / math.Pow(2, -8))
d.calCoeffs.t2 = float32(float64(t2) / math.Pow(2, 30))
d.calCoeffs.t3 = float32(float64(t3) / math.Pow(2, 48))
d.calCoeffs.p1 = float32((float64(p1) - math.Pow(2, 14)) / math.Pow(2, 20))
d.calCoeffs.p2 = float32((float64(p2) - math.Pow(2, 14)) / math.Pow(2, 29))
d.calCoeffs.p3 = float32(float64(p3) / math.Pow(2, 32))
d.calCoeffs.p4 = float32(float64(p4) / math.Pow(2, 37))
d.calCoeffs.p5 = float32(float64(p5) / math.Pow(2, -3))
d.calCoeffs.p6 = float32(float64(p6) / math.Pow(2, 6))
d.calCoeffs.p7 = float32(float64(p7) / math.Pow(2, 8))
d.calCoeffs.p8 = float32(float64(p8) / math.Pow(2, 15))
d.calCoeffs.p9 = float32(float64(p9) / math.Pow(2, 48))
d.calCoeffs.p10 = float32(float64(p10) / math.Pow(2, 48))
d.calCoeffs.p11 = float32(float64(p11) / math.Pow(2, 65))
// Perform a power on reset. All user configuration settings are overwritten
// with their default state.
if err = d.connection.WriteByteData(bmp388RegisterCMD, 0xB6); err != nil {
if err = d.connection.WriteByteData(bmp388RegCMD, bmp388CMDSoftReset); err != nil {
return err
}
// set IIR filter to off
if err = d.connection.WriteByteData(bmp388RegisterConfig, bmp388IIRFIlterCoef0<<1); err != nil {
if err = d.connection.WriteByteData(bmp388RegConf, uint8(d.confFilter)<<1); err != nil {
return err
}
@ -283,10 +267,10 @@ func (d *BMP388Driver) initialization() (err error) {
}
func (d *BMP388Driver) rawTemp() (temp int32, err error) {
var data []byte
var tp0, tp1, tp2 byte
if data, err = d.read(bmp388RegisterTempData, 3); err != nil {
data := make([]byte, 3)
if err = d.connection.ReadBlockData(bmp388RegTempData, data); err != nil {
return 0, err
}
buf := bytes.NewBuffer(data)
@ -300,10 +284,10 @@ func (d *BMP388Driver) rawTemp() (temp int32, err error) {
}
func (d *BMP388Driver) rawPressure() (press int32, err error) {
var data []byte
var tp0, tp1, tp2 byte
if data, err = d.read(bmp388RegisterPressureData, 3); err != nil {
data := make([]byte, 3)
if err = d.connection.ReadBlockData(bmp388RegPressureData, data); err != nil {
return 0, err
}
buf := bytes.NewBuffer(data)
@ -319,43 +303,31 @@ func (d *BMP388Driver) rawPressure() (press int32, err error) {
func (d *BMP388Driver) calculateTemp(rawTemp int32) float32 {
// datasheet, sec 9.2 Temperature compensation
pd1 := float32(rawTemp) - d.tpc.t1
pd2 := pd1 * d.tpc.t2
pd1 := float32(rawTemp) - d.calCoeffs.t1
pd2 := pd1 * d.calCoeffs.t2
temperatureComp := pd2 + (pd1*pd1)*d.tpc.t3
temperatureComp := pd2 + (pd1*pd1)*d.calCoeffs.t3
return temperatureComp
}
func (d *BMP388Driver) calculatePress(rawPress int32, tLin float64) float32 {
pd1 := float64(d.tpc.p6) * tLin
pd2 := float64(d.tpc.p7) * math.Pow(tLin, 2)
pd3 := float64(d.tpc.p8) * math.Pow(tLin, 3)
po1 := float64(d.tpc.p5) + pd1 + pd2 + pd3
pd1 := float64(d.calCoeffs.p6) * tLin
pd2 := float64(d.calCoeffs.p7) * math.Pow(tLin, 2)
pd3 := float64(d.calCoeffs.p8) * math.Pow(tLin, 3)
po1 := float64(d.calCoeffs.p5) + pd1 + pd2 + pd3
pd1 = float64(d.tpc.p2) * tLin
pd2 = float64(d.tpc.p3) * math.Pow(tLin, 2)
pd3 = float64(d.tpc.p4) * math.Pow(tLin, 3)
po2 := float64(rawPress) * (float64(d.tpc.p1) + pd1 + pd2 + pd3)
pd1 = float64(d.calCoeffs.p2) * tLin
pd2 = float64(d.calCoeffs.p3) * math.Pow(tLin, 2)
pd3 = float64(d.calCoeffs.p4) * math.Pow(tLin, 3)
po2 := float64(rawPress) * (float64(d.calCoeffs.p1) + pd1 + pd2 + pd3)
pd1 = math.Pow(float64(rawPress), 2)
pd2 = float64(d.tpc.p9) + float64(d.tpc.p10)*tLin
pd2 = float64(d.calCoeffs.p9) + float64(d.calCoeffs.p10)*tLin
pd3 = pd1 * pd2
pd4 := pd3 + math.Pow(float64(rawPress), 3)*float64(d.tpc.p11)
pd4 := pd3 + math.Pow(float64(rawPress), 3)*float64(d.calCoeffs.p11)
pressure := po1 + po2 + pd4
return float32(pressure)
}
func (d *BMP388Driver) read(address byte, n int) ([]byte, error) {
if _, err := d.connection.Write([]byte{address}); err != nil {
return nil, err
}
buf := make([]byte, n)
bytesRead, err := d.connection.Read(buf)
if bytesRead != n || err != nil {
return nil, err
}
return buf, nil
}

View File

@ -4,171 +4,197 @@ import (
"bytes"
"encoding/binary"
"errors"
"strings"
"testing"
"gobot.io/x/gobot"
"gobot.io/x/gobot/gobottest"
)
// this ensures that the implementation is based on i2c.Driver, which implements the gobot.Driver
// and tests all implementations, so no further tests needed here for gobot.Driver interface
var _ gobot.Driver = (*BMP388Driver)(nil)
// --------- HELPERS
func initTestBMP388Driver() (driver *BMP388Driver) {
driver, _ = initTestBMP388DriverWithStubbedAdaptor()
return
}
func initTestBMP388WithStubbedAdaptor() (*BMP388Driver, *i2cTestAdaptor) {
a := newI2cTestAdaptor()
func initTestBMP388DriverWithStubbedAdaptor() (*BMP388Driver, *i2cTestAdaptor) {
adaptor := newI2cTestAdaptor()
adaptor.i2cReadImpl = func(b []byte) (int, error) {
buf := new(bytes.Buffer)
// Simulate returning of 0x50 for the
// ReadByteData(bmp388RegisterChipID) call in initialisation()
binary.Write(buf, binary.LittleEndian, uint8(0x50))
copy(b, buf.Bytes())
return buf.Len(), nil
readCallCounter := 0
a.i2cReadImpl = func(b []byte) (int, error) {
readCallCounter++
if readCallCounter == 1 {
buf := new(bytes.Buffer)
// Simulate returning of 0x50 for the
// ReadByteData(bmp388RegChipID) call in initialisation()
binary.Write(buf, binary.LittleEndian, uint8(0x50))
copy(b, buf.Bytes())
return buf.Len(), nil
}
if readCallCounter == 2 {
// Simulate returning 24 bytes for the coefficients (register bmp388RegCalib00)
return 24, nil
}
return 0, nil
}
return NewBMP388Driver(adaptor), adaptor
return NewBMP388Driver(a), a
}
// --------- TESTS
func TestNewBMP388Driver(t *testing.T) {
// Does it return a pointer to an instance of BMP388Driver?
var bmp388 interface{} = NewBMP388Driver(newI2cTestAdaptor())
_, ok := bmp388.(*BMP388Driver)
var di interface{} = NewBMP388Driver(newI2cTestAdaptor())
d, ok := di.(*BMP388Driver)
if !ok {
t.Errorf("NewBMP388Driver() should have returned a *BMP388Driver")
}
gobottest.Refute(t, d.Driver, nil)
gobottest.Assert(t, strings.HasPrefix(d.Name(), "BMP388"), true)
gobottest.Assert(t, d.defaultAddress, 0x77)
gobottest.Assert(t, d.ctrlPwrMode, uint8(0x01)) // forced mode
gobottest.Assert(t, d.confFilter, BMP388IIRFilter(0x00)) // filter off
gobottest.Refute(t, d.calCoeffs, nil)
}
func TestBMP388Driver(t *testing.T) {
bmp388 := initTestBMP388Driver()
gobottest.Refute(t, bmp388.Connection(), nil)
func TestBMP388Options(t *testing.T) {
// This is a general test, that options are applied in constructor by using the common WithBus() option and
// least one of this driver. Further tests for options can also be done by call of "WithOption(val)(d)".
d := NewBMP388Driver(newI2cTestAdaptor(), WithBus(2), WithBMP388IIRFilter(BMP388IIRFilter(0x03)))
gobottest.Assert(t, d.GetBusOrDefault(1), 2)
gobottest.Assert(t, d.confFilter, BMP388IIRFilter(0x03))
}
func TestBMP388DriverStart(t *testing.T) {
bmp388, _ := initTestBMP388DriverWithStubbedAdaptor()
gobottest.Assert(t, bmp388.Start(), nil)
}
func TestBMP388StartConnectError(t *testing.T) {
bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor()
adaptor.Testi2cConnectErr(true)
gobottest.Assert(t, bmp388.Start(), errors.New("Invalid i2c connection"))
}
func TestBMP388DriverStartWriteError(t *testing.T) {
bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor()
adaptor.i2cWriteImpl = func([]byte) (int, error) {
return 0, errors.New("write error")
}
gobottest.Assert(t, bmp388.Start(), errors.New("write error"))
}
func TestBMP388DriverStartReadError(t *testing.T) {
bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor()
adaptor.i2cReadImpl = func(b []byte) (int, error) {
return 0, errors.New("read error")
}
gobottest.Assert(t, bmp388.Start(), errors.New("read error"))
}
func TestBMP388DriverHalt(t *testing.T) {
bmp388 := initTestBMP388Driver()
gobottest.Assert(t, bmp388.Halt(), nil)
}
func TestBMP388DriverMeasurements(t *testing.T) {
bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor()
adaptor.i2cReadImpl = func(b []byte) (int, error) {
func TestBMP388Measurements(t *testing.T) {
d, a := initTestBMP388WithStubbedAdaptor()
a.i2cReadImpl = func(b []byte) (int, error) {
buf := new(bytes.Buffer)
lastWritten := adaptor.written[len(adaptor.written)-1]
lastWritten := a.written[len(a.written)-1]
switch lastWritten {
case bmp388RegisterChipID:
case bmp388RegChipID:
// Simulate returning of 0x50 for the
// ReadByteData(bmp388RegisterChipID) call in initialisation()
// ReadByteData(bmp388RegChipID) call in initialisation()
binary.Write(buf, binary.LittleEndian, uint8(0x50))
case bmp388RegisterCalib00:
case bmp388RegCalib00:
// Values produced by dumping data from actual sensor
buf.Write([]byte{36, 107, 156, 73, 246, 104, 255, 189, 245, 35, 0, 151, 101, 184, 122, 243, 246, 211, 64, 14, 196, 0, 0, 0})
case bmp388RegisterTempData:
case bmp388RegTempData:
buf.Write([]byte{0, 28, 127})
case bmp388RegisterPressureData:
case bmp388RegPressureData:
buf.Write([]byte{0, 66, 113})
}
copy(b, buf.Bytes())
return buf.Len(), nil
}
bmp388.Start()
temp, err := bmp388.Temperature(2)
d.Start()
temp, err := d.Temperature(2)
gobottest.Assert(t, err, nil)
gobottest.Assert(t, temp, float32(22.906143))
pressure, err := bmp388.Pressure(2)
pressure, err := d.Pressure(2)
gobottest.Assert(t, err, nil)
gobottest.Assert(t, pressure, float32(98874.85))
alt, err := bmp388.Altitude(2)
alt, err := d.Altitude(2)
gobottest.Assert(t, err, nil)
gobottest.Assert(t, alt, float32(205.89395))
}
func TestBMP388DriverTemperatureWriteError(t *testing.T) {
bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor()
bmp388.Start()
func TestBMP388TemperatureWriteError(t *testing.T) {
d, a := initTestBMP388WithStubbedAdaptor()
d.Start()
adaptor.i2cWriteImpl = func([]byte) (int, error) {
a.i2cWriteImpl = func([]byte) (int, error) {
return 0, errors.New("write error")
}
temp, err := bmp388.Temperature(2)
temp, err := d.Temperature(2)
gobottest.Assert(t, err, errors.New("write error"))
gobottest.Assert(t, temp, float32(0.0))
}
func TestBMP388DriverTemperatureReadError(t *testing.T) {
bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor()
bmp388.Start()
func TestBMP388TemperatureReadError(t *testing.T) {
d, a := initTestBMP388WithStubbedAdaptor()
d.Start()
adaptor.i2cReadImpl = func([]byte) (int, error) {
a.i2cReadImpl = func([]byte) (int, error) {
return 0, errors.New("read error")
}
temp, err := bmp388.Temperature(2)
temp, err := d.Temperature(2)
gobottest.Assert(t, err, errors.New("read error"))
gobottest.Assert(t, temp, float32(0.0))
}
func TestBMP388DriverPressureWriteError(t *testing.T) {
bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor()
bmp388.Start()
func TestBMP388PressureWriteError(t *testing.T) {
d, a := initTestBMP388WithStubbedAdaptor()
d.Start()
adaptor.i2cWriteImpl = func([]byte) (int, error) {
a.i2cWriteImpl = func([]byte) (int, error) {
return 0, errors.New("write error")
}
press, err := bmp388.Pressure(2)
press, err := d.Pressure(2)
gobottest.Assert(t, err, errors.New("write error"))
gobottest.Assert(t, press, float32(0.0))
}
func TestBMP388DriverPressureReadError(t *testing.T) {
bmp388, adaptor := initTestBMP388DriverWithStubbedAdaptor()
bmp388.Start()
func TestBMP388PressureReadError(t *testing.T) {
d, a := initTestBMP388WithStubbedAdaptor()
d.Start()
adaptor.i2cReadImpl = func([]byte) (int, error) {
a.i2cReadImpl = func([]byte) (int, error) {
return 0, errors.New("read error")
}
press, err := bmp388.Pressure(2)
press, err := d.Pressure(2)
gobottest.Assert(t, err, errors.New("read error"))
gobottest.Assert(t, press, float32(0.0))
}
func TestBMP388DriverSetName(t *testing.T) {
b := initTestBMP388Driver()
b.SetName("TESTME")
gobottest.Assert(t, b.Name(), "TESTME")
}
func TestBMP388DriverOptions(t *testing.T) {
b := NewBMP388Driver(newI2cTestAdaptor(), WithBus(2))
gobottest.Assert(t, b.GetBusOrDefault(1), 2)
func TestBMP388_initialization(t *testing.T) {
// sequence to read and write in initialization():
// * read chip ID register (0x00) and compare
// * read 24 bytes (12 x 16 bit calibration data), starting from TC1 register (0x31)
// * fill calibration struct with data (LSByte read first)
// * perform a soft reset by command register (0x7E)
// * prepare the content of config register
// * write the config register (0x1F)
// arrange
d, a := initTestBMP388WithStubbedAdaptor()
a.written = []byte{} // reset writes of former test
const (
wantChipIDReg = uint8(0x00)
wantCalibReg = uint8(0x31)
wantCommandReg = uint8(0x7E)
wantCommandRegVal = uint8(0xB6) // soft reset
wantConfReg = uint8(0x1F)
wantConfRegVal = uint8(0x00) // no filter
)
// Values produced by dumping data from actual sensor
returnRead := []byte{36, 107, 156, 73, 246, 104, 255, 189, 245, 35, 0, 151, 101, 184, 122, 243, 246, 211, 64, 14, 196, 0, 0, 0}
numCallsRead := 0
a.i2cReadImpl = func(b []byte) (int, error) {
numCallsRead++
if numCallsRead == 1 {
b[0] = 0x50
} else {
copy(b, returnRead)
}
return len(b), nil
}
// act, assert - initialization() must be called on Start()
err := d.Start()
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, numCallsRead, 2)
gobottest.Assert(t, len(a.written), 6)
gobottest.Assert(t, a.written[0], wantChipIDReg)
gobottest.Assert(t, a.written[1], wantCalibReg)
gobottest.Assert(t, a.written[2], wantCommandReg)
gobottest.Assert(t, a.written[3], wantCommandRegVal)
gobottest.Assert(t, a.written[4], wantConfReg)
gobottest.Assert(t, a.written[5], wantConfRegVal)
gobottest.Assert(t, d.calCoeffs.t1, float32(7.021568e+06))
gobottest.Assert(t, d.calCoeffs.t2, float32(1.7549843e-05))
gobottest.Assert(t, d.calCoeffs.t3, float32(-3.5527137e-14))
gobottest.Assert(t, d.calCoeffs.p1, float32(-0.015769958))
gobottest.Assert(t, d.calCoeffs.p2, float32(-3.5410747e-05))
gobottest.Assert(t, d.calCoeffs.p3, float32(8.1490725e-09))
gobottest.Assert(t, d.calCoeffs.p4, float32(0))
gobottest.Assert(t, d.calCoeffs.p5, float32(208056))
gobottest.Assert(t, d.calCoeffs.p6, float32(490.875))
gobottest.Assert(t, d.calCoeffs.p7, float32(-0.05078125))
gobottest.Assert(t, d.calCoeffs.p8, float32(-0.00030517578))
gobottest.Assert(t, d.calCoeffs.p9, float32(5.8957283e-11))
}

View File

@ -0,0 +1,67 @@
// +build example
//
// Do not build by default.
package main
import (
"fmt"
"time"
"gobot.io/x/gobot"
"gobot.io/x/gobot/drivers/i2c"
"gobot.io/x/gobot/platforms/tinkerboard"
)
// Wiring
// PWR Tinkerboard: 1 (+3.3V, VCC), 2(+5V), 6, 9, 14, 20 (GND)
// I2C1 Tinkerboard: 3 (SDA-ws), 5 (SCL-gn)
// BME280 plate: VIN (2.5..5V), GND, SDL, SDA, SDO to GND
func main() {
a := tinkerboard.NewAdaptor()
bme280 := i2c.NewBME280Driver(a, i2c.WithAddress(0x76),
i2c.WithBME280PressureOversampling(0x05),
i2c.WithBME280TemperatureOversampling(0x02),
i2c.WithBME280HumidityOversampling(0x01),
i2c.WithBME280IIRFilter(0x05))
work := func() {
gobot.Every(2*time.Second, func() {
t, e := bme280.Temperature()
fmt.Println("Temperature [°C]", t)
if e != nil {
fmt.Println(e)
}
p, e := bme280.Pressure()
fmt.Println("Pressure [Pa]", p) // 100hPa = 1Pa
if e != nil {
fmt.Println(e)
}
a, e := bme280.Altitude()
fmt.Println("Altitude [m]", a)
if e != nil {
fmt.Println(e)
}
h, e := bme280.Humidity()
fmt.Println("Humidity [%]", h)
if e != nil {
fmt.Println(e)
}
fmt.Println("-------------")
})
}
robot := gobot.NewRobot("bme280bot",
[]gobot.Connection{a},
[]gobot.Device{bme280},
work,
)
err := robot.Start()
if err != nil {
fmt.Println(err)
}
}