bme280: Fix SPI, add smoke test.

Increase coverage; only one line would need some math to figure out values that
would allow exceeding the threshold.
pull/1/head
Marc-Antoine Ruel 9 years ago
parent 7faec2c2d3
commit 94686e7f5d

@ -11,6 +11,7 @@ package bme280
import (
"errors"
"fmt"
"periph.io/x/periph/conn"
"periph.io/x/periph/conn/i2c"
@ -232,7 +233,7 @@ func (d *Dev) makeDev(opts *Opts) error {
return err
}
if chipID[0] != 0x60 {
return errors.New("bme280: unexpected chip id; is this a BME280?")
return fmt.Errorf("bme280: unexpected chip id %x; is this a BME280?", chipID[0])
}
// Read calibration data t1~3, p1~9, 8bits padding, h1.
var tph [0xA2 - 0x88]byte
@ -273,13 +274,16 @@ func (d *Dev) makeDev(opts *Opts) error {
func (d *Dev) readReg(reg uint8, b []byte) error {
// Page 32-33
if d.isSPI {
// MSB is 0 for write and 1 for read.
read := make([]byte, len(b)+1)
write := make([]byte, len(read))
// Rest of the write buffer is ignored.
write[0] = reg
if err := d.d.Tx(write, read); err != nil {
return err
}
copy(b, read[:1])
copy(b, read[1:])
return nil
}
return d.d.Tx([]byte{reg}, b)
}
@ -375,19 +379,6 @@ func (c *calibration) compensatePressureInt64(raw, tFine int32) uint32 {
// raw has 16 bits of resolution.
func (c *calibration) compensateHumidityInt(raw, tFine int32) uint32 {
x := tFine - 76800
/*
Yes, someone wrote the following in the datasheet unironically:
v_x1_u32r = (((((adc_H << 14) (((BME280_S32_t)dig_H4) << 20)
(((BME280_S32_t)dig_H5) * v_x1_u32r)) + ((BME280_S32_t)16384)) >> 15) *
(((((((v_x1_u32r * ((BME280_S32_t)dig_H6)) >> 10) * (((v_x1_u32r *
((BME280_S32_t)dig_H3)) >> 11) + ((BME280_S32_t)32768))) >> 10) +
((BME280_S32_t)2097152)) * ((BME280_S32_t)dig_H2) + 8192) >> 14));
v_x1_u32r = (v_x1_u32r (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((BME280_S32_t)dig_H1)) >> 4));
v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
*/
// Here's a more "readable" version:
x1 := raw<<14 - int32(c.h4)<<20 - int32(c.h5)*x
x2 := (x1 + 16384) >> 15
x3 := (x * int32(c.h6)) >> 10
@ -395,7 +386,6 @@ func (c *calibration) compensateHumidityInt(raw, tFine int32) uint32 {
x5 := (x3 * (x4 + 32768)) >> 10
x6 := ((x5+2097152)*int32(c.h2) + 8192) >> 14
x = x2 * x6
x = x - ((((x>>15)*(x>>15))>>7)*int32(c.h1))>>4
if x < 0 {
return 0

@ -5,12 +5,16 @@
package bme280
import (
"errors"
"fmt"
"log"
"testing"
"periph.io/x/periph/conn/conntest"
"periph.io/x/periph/conn/i2c/i2creg"
"periph.io/x/periph/conn/i2c/i2ctest"
"periph.io/x/periph/conn/spi"
"periph.io/x/periph/conn/spi/spitest"
"periph.io/x/periph/devices"
)
@ -36,7 +40,213 @@ var calib = calibration{
h6: 30,
}
func TestRead(t *testing.T) {
func TestSPISense_success(t *testing.T) {
bus := spitest.Playback{
Playback: conntest.Playback{
Ops: []conntest.IO{
// Chipd ID detection.
{
Write: []byte{0xD0, 0x00},
Read: []byte{0x00, 0x60},
},
// Calibration data.
{
Write: []byte{0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
Read: []byte{0x00, 0xC9, 0x6C, 0x63, 0x65, 0x32, 0x00, 0x77, 0x93, 0x98, 0xD5, 0xD0, 0x0B, 0x67, 0x23, 0xBA, 0x00, 0xF9, 0xFF, 0xAC, 0x26, 0x0A, 0xD8, 0xBD, 0x10, 0x00, 0x4B},
},
// Calibration data.
{
Write: []byte{0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
Read: []byte{0x00, 0x5C, 0x01, 0x00, 0x15, 0x0F, 0x00, 0x1E},
},
{Write: []byte{0x74, 0xB4, 0x72, 0x05, 0x75, 0xA0, 0x74, 0xB7}},
// Read.
{
Write: []byte{0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
Read: []byte{0x00, 0x51, 0x9F, 0xC0, 0x9E, 0x3A, 0x50, 0x5E, 0x5B},
},
},
},
}
opts := Opts{
Temperature: O16x,
Pressure: O16x,
Humidity: O16x,
Standby: S1s,
Filter: FOff,
}
dev, err := NewSPI(&bus, &opts)
if err != nil {
t.Fatal(err)
}
dev.Stop()
env := devices.Environment{}
if err := dev.Sense(&env); err != nil {
t.Fatal(err)
}
// TODO(maruel): The values do not make sense but I think I burned my SPI
// BME280 by misconnecting it in reverse for a few minutes. It still "work"
// but fail to read data. It could also be a bug in the driver. :(
if env.Temperature != 62680 {
t.Fatalf("temp %d", env.Temperature)
}
if env.Pressure != 99576 {
t.Fatalf("pressure %d", env.Pressure)
}
if env.Humidity != 995 {
t.Fatalf("humidity %d", env.Humidity)
}
}
func TestNewSPI_fail(t *testing.T) {
if d, err := NewSPI(&spiFail{}, nil); d != nil || err == nil {
t.Fatal("DevParams() have failed")
}
}
func TestNewSPI_fail_len(t *testing.T) {
bus := spitest.Playback{
Playback: conntest.Playback{
Ops: []conntest.IO{
{
// Chipd ID detection.
Write: []byte{0xD0, 0x00},
Read: []byte{0x00},
},
},
},
}
if dev, err := NewSPI(&bus, nil); dev != nil || err == nil {
t.Fatal("read failed")
}
}
func TestNewSPI_fail_chipid(t *testing.T) {
bus := spitest.Playback{
Playback: conntest.Playback{
Ops: []conntest.IO{
{
// Chipd ID detection.
Write: []byte{0xD0, 0x00},
Read: []byte{0x00, 0xFF},
},
},
},
}
if dev, err := NewSPI(&bus, nil); dev != nil || err == nil {
t.Fatal("read failed")
}
}
func TestNewI2C_fail(t *testing.T) {
bus := i2ctest.Playback{
Ops: []i2ctest.IO{
// Chipd ID detection.
{Addr: 0x76, Write: []byte{0xd0}},
},
}
if dev, err := NewI2C(&bus, nil); dev != nil || err == nil {
t.Fatal("read failed")
}
}
func TestNewI2C_chipid(t *testing.T) {
bus := i2ctest.Playback{
Ops: []i2ctest.IO{
// Chipd ID detection.
{Addr: 0x76, Write: []byte{0xd0}, Read: []byte{0x60}},
},
}
if dev, err := NewI2C(&bus, nil); dev != nil || err == nil {
t.Fatal("invalid chip id")
}
}
func TestNewI2C_calib1(t *testing.T) {
bus := i2ctest.Playback{
Ops: []i2ctest.IO{
// Chipd ID detection.
{Addr: 0x76, Write: []byte{0xd0}, Read: []byte{0x60}},
// Calibration data.
{
Addr: 0x76,
Write: []byte{0x88},
Read: []byte{0x10, 0x6e, 0x6c, 0x66, 0x32, 0x0, 0x5d, 0x95, 0xb8, 0xd5, 0xd0, 0xb, 0x77, 0x1e, 0x9d, 0xff, 0xf9, 0xff, 0xac, 0x26, 0xa, 0xd8, 0xbd, 0x10, 0x0, 0x4b},
},
},
}
opts := Opts{Address: 0}
if dev, err := NewI2C(&bus, &opts); dev != nil || err == nil {
t.Fatal("2nd calib read failed")
}
}
func TestNewI2C_calib2(t *testing.T) {
bus := i2ctest.Playback{
Ops: []i2ctest.IO{
// Chipd ID detection.
{Addr: 0x76, Write: []byte{0xd0}, Read: []byte{0x60}},
// Calibration data.
{
Addr: 0x76,
Write: []byte{0x88},
Read: []byte{0x10, 0x6e, 0x6c, 0x66, 0x32, 0x0, 0x5d, 0x95, 0xb8, 0xd5, 0xd0, 0xb, 0x77, 0x1e, 0x9d, 0xff, 0xf9, 0xff, 0xac, 0x26, 0xa, 0xd8, 0xbd, 0x10, 0x0, 0x4b},
},
// Calibration data.
{Addr: 0x76, Write: []byte{0xe1}, Read: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}},
},
}
if dev, err := NewI2C(&bus, nil); dev != nil || err == nil {
t.Fatal("3rd calib read failed")
}
}
func TestI2COpts_bad_addr(t *testing.T) {
bus := i2ctest.Playback{}
opts := Opts{Address: 1}
if dev, err := NewI2C(&bus, &opts); dev != nil || err == nil {
t.Fatal("bad addr")
}
}
func TestI2COpts(t *testing.T) {
bus := i2ctest.Playback{}
opts := Opts{Address: 0x76}
if dev, err := NewI2C(&bus, &opts); dev != nil || err == nil {
t.Fatal("write fails")
}
}
func TestI2CSense_fail(t *testing.T) {
// This data was generated with "bme280 -r"
bus := i2ctest.Playback{
Ops: []i2ctest.IO{
// Chipd ID detection.
{Addr: 0x76, Write: []byte{0xd0}, Read: []byte{0x60}},
// Calibration data.
{
Addr: 0x76,
Write: []byte{0x88},
Read: []byte{0x10, 0x6e, 0x6c, 0x66, 0x32, 0x0, 0x5d, 0x95, 0xb8, 0xd5, 0xd0, 0xb, 0x77, 0x1e, 0x9d, 0xff, 0xf9, 0xff, 0xac, 0x26, 0xa, 0xd8, 0xbd, 0x10, 0x0, 0x4b},
},
// Calibration data.
{Addr: 0x76, Write: []byte{0xe1}, Read: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}},
// Configuration.
{Addr: 0x76, Write: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xe0, 0xf4, 0x6f}, Read: nil},
// Read.
{Addr: 0x76, Write: []byte{0xf7}},
},
}
dev, err := NewI2C(&bus, nil)
if err != nil {
t.Fatal(err)
}
if dev.Sense(&devices.Environment{}) == nil {
t.Fatal("sense fail read")
}
}
func TestI2CSense_success(t *testing.T) {
// This data was generated with "bme280 -r"
bus := i2ctest.Playback{
Ops: []i2ctest.IO{
@ -54,6 +264,8 @@ func TestRead(t *testing.T) {
{Addr: 0x76, Write: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xe0, 0xf4, 0x6f}, Read: nil},
// Read.
{Addr: 0x76, Write: []byte{0xf7}, Read: []byte{0x4a, 0x52, 0xc0, 0x80, 0x96, 0xc0, 0x7a, 0x76}},
// Stop.
{Addr: 0x76, Write: []byte{0xf4, 0x0}},
},
}
dev, err := NewI2C(&bus, nil)
@ -62,7 +274,7 @@ func TestRead(t *testing.T) {
}
env := devices.Environment{}
if err := dev.Sense(&env); err != nil {
t.Fatalf("Sense(): %v", err)
t.Fatal(err)
}
if env.Temperature != 23720 {
t.Fatalf("temp %d", env.Temperature)
@ -73,6 +285,12 @@ func TestRead(t *testing.T) {
if env.Humidity != 6531 {
t.Fatalf("humidity %d", env.Humidity)
}
if err := dev.Stop(); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestCalibrationFloat(t *testing.T) {
@ -137,6 +355,17 @@ func TestCalibrationInt(t *testing.T) {
}
}
func TestCalibration_limits_0(t *testing.T) {
c := calibration{h1: 0xFF, h2: 1, h3: 1, h6: 1}
if v := c.compensateHumidityInt(0x7FFFFFFF>>14, 0xFFFFFFF); v != 0 {
t.Fatal(v)
}
}
func TestCalibration_limits_419430400(t *testing.T) {
// TODO(maruel): Reverse the equation to overflow 419430400
}
//
func Example() {
@ -156,6 +385,22 @@ func Example() {
fmt.Printf("%8s %10s %9s\n", env.Temperature, env.Pressure, env.Humidity)
}
func TestCalibration_compensatePressureInt64(t *testing.T) {
c := calibration{}
if x := c.compensatePressureInt64(0, 0); x != 0 {
t.Fatal(x)
}
}
func TestCalibration_compensateHumidityInt(t *testing.T) {
c := calibration{
h1: 0xFF,
}
if x := c.compensateHumidityInt(0, 0); x != 0 {
t.Fatal(x)
}
}
//
var epsilon float32 = 0.00000001
@ -248,3 +493,11 @@ func (c *calibration) compensateHumidityFloat(raw, tFine int32) float32 {
}
return float32(h)
}
type spiFail struct {
spitest.Playback
}
func (s *spiFail) DevParams(maxHz int64, mode spi.Mode, bits int) error {
return errors.New("failing")
}

@ -0,0 +1,7 @@
# 'bme280' smoke test
Verifies that two BME280, one over I²C, one over SPI, can read roughly the same
temperature, humidity and pressure.
It can also be leveraged to record the I/O to write playback unit tests. It is a
good example to reuse to write other device driver unit test.

@ -0,0 +1,172 @@
// Copyright 2017 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
// Package bme280smoketest is leveraged by periph-smoketest to verify that two
// BME280, one over I²C, one over SPI, read roughly the same temperature,
// humidity and pressure.
package bme280smoketest
import (
"flag"
"fmt"
"periph.io/x/periph/conn/i2c"
"periph.io/x/periph/conn/i2c/i2creg"
"periph.io/x/periph/conn/i2c/i2ctest"
"periph.io/x/periph/conn/spi"
"periph.io/x/periph/conn/spi/spireg"
"periph.io/x/periph/conn/spi/spitest"
"periph.io/x/periph/devices"
"periph.io/x/periph/devices/bme280"
)
// SmokeTest is imported by periph-smoketest.
type SmokeTest struct {
}
// Name implements the SmokeTest interface.
func (s *SmokeTest) Name() string {
return "bme280"
}
// Description implements the SmokeTest interface.
func (s *SmokeTest) Description() string {
return "Tests BME280 over I²C and SPI"
}
// Run implements the SmokeTest interface.
func (s *SmokeTest) Run(args []string) (err error) {
f := flag.NewFlagSet("buses", flag.ExitOnError)
i2cName := f.String("i2c", "", "I²C bus to use")
spiName := f.String("spi", "", "SPI bus to use")
record := f.Bool("r", false, "record operation (for playback unit testing)")
f.Parse(args)
i2cBus, err2 := i2creg.Open(*i2cName)
if err2 != nil {
return err2
}
defer func() {
if err2 := i2cBus.Close(); err == nil {
err = err2
}
}()
spiBus, err2 := spireg.Open(*spiName)
if err2 != nil {
return err2
}
defer func() {
if err2 := spiBus.Close(); err == nil {
err = err2
}
}()
if !*record {
return run(i2cBus, spiBus)
}
i2cRecorder := i2ctest.Record{Bus: i2cBus}
spiRecorder := spitest.Record{Conn: spiBus}
err = run(&i2cRecorder, &spiRecorder)
if len(i2cRecorder.Ops) != 0 {
fmt.Printf("I²C recorder Addr: 0x%02X\n", i2cRecorder.Ops[0].Addr)
} else {
fmt.Print("I²C recorder\n")
}
for _, op := range i2cRecorder.Ops {
fmt.Print(" Write: ")
for i, b := range op.Write {
if i != 0 {
fmt.Print(", ")
}
fmt.Printf("0x%02X", b)
}
fmt.Print("\n Read: ")
for i, b := range op.Read {
if i != 0 {
fmt.Print(", ")
}
fmt.Printf("0x%02X", b)
}
fmt.Print("\n")
}
fmt.Print("\nSPI recorder\n")
for _, op := range spiRecorder.Ops {
fmt.Print(" Write: ")
if len(op.Read) != 0 {
// Read data.
fmt.Printf("0x%02X\n Read: ", op.Write[0])
// first byte is dummy.
for i, b := range op.Read[1:] {
if i != 0 {
fmt.Print(", ")
}
fmt.Printf("0x%02X", b)
}
} else {
// Write-only command.
for i, b := range op.Write {
if i != 0 {
fmt.Print(", ")
}
fmt.Printf("0x%02X", b)
}
fmt.Print("\n Read: ")
}
fmt.Print("\n")
}
return err
}
func run(i2cBus i2c.Bus, spiBus spi.ConnCloser) (err error) {
opts := &bme280.Opts{
Temperature: bme280.O16x,
Pressure: bme280.O16x,
Humidity: bme280.O16x,
Standby: bme280.S1s,
Filter: bme280.FOff,
}
i2cDev, err2 := bme280.NewI2C(i2cBus, opts)
if err2 != nil {
return err2
}
defer func() {
if err2 := i2cDev.Stop(); err == nil {
err = err2
}
}()
spiDev, err2 := bme280.NewSPI(spiBus, opts)
if err2 != nil {
return err2
}
defer func() {
if err2 := spiDev.Stop(); err == nil {
err = err2
}
}()
// TODO(maruel): Generally the first measurement is way off.
i2cEnv := devices.Environment{}
spiEnv := devices.Environment{}
if err2 := i2cDev.Sense(&i2cEnv); err2 != nil {
return err2
}
if err2 = spiDev.Sense(&spiEnv); err2 != nil {
return err2
}
// TODO(maruel): Determine acceptable threshold.
if d := i2cEnv.Temperature - spiEnv.Temperature; d > 1000 || d < -1000 {
return fmt.Errorf("Temperature delta higher than expected (%s): I²C got %s; SPI got %s", d, i2cEnv.Temperature, spiEnv.Temperature)
}
if d := i2cEnv.Pressure - spiEnv.Pressure; d > 100 || d < -100 {
return fmt.Errorf("Pressure delta higher than expected (%s): I²C got %s; SPI got %s", d, i2cEnv.Pressure, spiEnv.Pressure)
}
if d := i2cEnv.Humidity - spiEnv.Humidity; d > 100 || d < -100 {
return fmt.Errorf("Humidity delta higher than expected (%s): I²C got %s; SPI got %s", d, i2cEnv.Humidity, spiEnv.Humidity)
}
return nil
}
Loading…
Cancel
Save