bmxx80: Merge bmp180 and bme280 and add support for bmp280 (#156)

- cmd/bmxx80 now can read any of these 3 devices.
- Rewrite the driver to support all 3 devices. More devices of the family (like
  the bme680) could be easily added.
- Add -ia to bmx280smoketest

Fix #155
pull/1/head
M-A 9 years ago committed by GitHub
parent bbed3b44f7
commit 5e39729535

@ -1,644 +0,0 @@
// Copyright 2016 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 bme280 controls a Bosch BME280 device over I²C.
//
// Datasheet
//
// https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME280_DS001-11.pdf
package bme280
import (
"errors"
"fmt"
"log"
"sync"
"time"
"periph.io/x/periph/conn"
"periph.io/x/periph/conn/i2c"
"periph.io/x/periph/conn/spi"
"periph.io/x/periph/devices"
)
// Oversampling affects how much time is taken to measure each of temperature,
// pressure and humidity.
//
// Temperature must be measured for pressure and humidity to be measured. The
// duration is approximatively:
// duration_in_ms = 1 + 2*temp + 2*press+0.5 + 2*humidy+0.5
//
// Using high oversampling and low standby results in highest power
// consumption, but this is still below 1mA so we generally don't care.
type Oversampling uint8
// Possible oversampling values.
//
// The higher the more time and power it takes to take a measurement. Even at
// 16x for all 3 sensors, it is less than 100ms albeit increased power
// consumption may increase the temperature reading.
const (
Off Oversampling = 0
O1x Oversampling = 1
O2x Oversampling = 2
O4x Oversampling = 3
O8x Oversampling = 4
O16x Oversampling = 5
)
const oversamplingName = "Off1x2x4x8x16x"
var oversamplingIndex = [...]uint8{0, 3, 5, 7, 9, 11, 14}
func (o Oversampling) String() string {
if o >= Oversampling(len(oversamplingIndex)-1) {
return fmt.Sprintf("Oversampling(%d)", o)
}
return oversamplingName[oversamplingIndex[o]:oversamplingIndex[o+1]]
}
func (o Oversampling) asValue() int {
switch o {
case O1x:
return 1
case O2x:
return 2
case O4x:
return 4
case O8x:
return 8
case O16x:
return 16
default:
return 0
}
}
// Filter specifies the internal IIR filter to get steadier measurements.
//
// Oversampling will get better measurements than filtering but at a larger
// power consumption cost, which may slightly affect temperature measurement.
type Filter uint8
// Possible filtering values.
//
// The higher the filter, the slower the value converges but the more stable
// the measurement is.
const (
NoFilter Filter = 0
F2 Filter = 1
F4 Filter = 2
F8 Filter = 3
F16 Filter = 4
)
// Dev is a handle to an initialized bme280.
type Dev struct {
d conn.Conn
isSPI bool
opts Opts
measDelay time.Duration
c calibration
mu sync.Mutex
stop chan struct{}
wg sync.WaitGroup
}
func (d *Dev) String() string {
return fmt.Sprintf("BME280{%s}", d.d)
}
// Sense requests a one time measurement as °C, kPa and % of relative humidity.
//
// The very first measurements may be of poor quality.
func (d *Dev) Sense(env *devices.Environment) error {
d.mu.Lock()
defer d.mu.Unlock()
if d.stop != nil {
return wrap(errors.New("already sensing continuously"))
}
err := d.writeCommands([]byte{
// ctrl_meas
0xF4, byte(d.opts.Temperature)<<5 | byte(d.opts.Pressure)<<2 | byte(forced),
})
if err != nil {
return wrap(err)
}
time.Sleep(d.measDelay)
for idle := false; !idle; {
if idle, err = d.isIdle(); err != nil {
return wrap(err)
}
}
return d.sense(env)
}
// SenseContinuous returns measurements as °C, kPa and % of relative humidity
// on a continuous basis.
//
// The application must call Halt() to stop the sensing when done to stop the
// sensor and close the channel.
//
// It's the responsibility of the caller to retrieve the values from the
// channel as fast as possible, otherwise the interval may not be respected.
func (d *Dev) SenseContinuous(interval time.Duration) (<-chan devices.Environment, error) {
d.mu.Lock()
defer d.mu.Unlock()
if d.stop != nil {
// Don't send the stop command to the device.
close(d.stop)
d.stop = nil
d.wg.Wait()
}
s := chooseStandby(interval - d.measDelay)
err := d.writeCommands([]byte{
// config
0xF5, byte(s)<<5 | byte(d.opts.Filter)<<2,
// ctrl_meas
0xF4, byte(d.opts.Temperature)<<5 | byte(d.opts.Pressure)<<2 | byte(normal),
})
if err != nil {
return nil, wrap(err)
}
sensing := make(chan devices.Environment)
d.stop = make(chan struct{})
d.wg.Add(1)
go func() {
defer d.wg.Done()
defer close(sensing)
d.sensingContinuous(interval, sensing, d.stop)
}()
return sensing, nil
}
// Halt stops the bme280 from acquiring measurements as initiated by
// SenseContinuous().
//
// It is recommended to call this function before terminating the process to
// reduce idle power usage.
func (d *Dev) Halt() error {
d.mu.Lock()
defer d.mu.Unlock()
if d.stop == nil {
return nil
}
close(d.stop)
d.stop = nil
d.wg.Wait()
// Page 27 (for register) and 12~13 section 3.3.
return d.writeCommands([]byte{
// config
0xF5, byte(s1s)<<5 | byte(NoFilter)<<2,
// ctrl_meas
0xF4, byte(d.opts.Temperature)<<5 | byte(d.opts.Pressure)<<2 | byte(sleep),
})
}
// Opts is optional options to pass to the constructor.
//
// Recommended (and default) values are O4x for oversampling.
//
// Address can only used on creation of an I²C-device. Its default value is
// 0x76. It can be set to 0x77. Both values depend on HW configuration of the
// sensor's SDO pin.
//
// Filter is only used while using SenseContinuous().
//
// Recommended sensing settings as per the datasheet:
//
// → Weather monitoring: manual sampling once per minute, all sensors O1x.
// Power consumption: 0.16µA, filter NoFilter. RMS noise: 3.3Pa / 30cm, 0.07%RH.
//
// → Humidity sensing: manual sampling once per second, pressure Off, humidity
// and temperature O1X, filter NoFilter. Power consumption: 2.9µA, 0.07%RH.
//
// → Indoor navigation: continuous sampling at 40ms with filter F16, pressure
// O16x, temperature O2x, humidity O1x, filter F16. Power consumption 633µA.
// RMS noise: 0.2Pa / 1.7cm.
//
// → Gaming: continuous sampling at 40ms with filter F16, pressure O4x,
// temperature O1x, humidity Off, filter F16. Power consumption 581µA. RMS
// noise: 0.3Pa / 2.5cm.
//
// See the datasheet for more details about the trade offs.
type Opts struct {
Temperature Oversampling
Pressure Oversampling
Humidity Oversampling
Filter Filter
Address uint16
}
func (o *Opts) delayTypical() time.Duration {
// Page 51.
µs := 1000
if o.Temperature != Off {
µs += 2000 * o.Temperature.asValue()
}
if o.Pressure != Off {
µs += 2000*o.Pressure.asValue() + 500
}
if o.Humidity != Off {
µs += 2000*o.Humidity.asValue() + 500
}
return time.Microsecond * time.Duration(µs)
}
// NewI2C returns an object that communicates over I²C to BME280 environmental
// sensor.
//
// It is recommended to call Halt() when done with the device so it stops
// sampling.
func NewI2C(b i2c.Bus, opts *Opts) (*Dev, error) {
addr := uint16(0x76)
if opts != nil {
switch opts.Address {
case 0x76, 0x77:
addr = opts.Address
case 0x00:
// do not do anything
default:
return nil, wrap(errors.New("given address not supported by device"))
}
}
d := &Dev{d: &i2c.Dev{Bus: b, Addr: addr}, isSPI: false}
if err := d.makeDev(opts); err != nil {
return nil, err
}
return d, nil
}
// NewSPI returns an object that communicates over SPI to BME280 environmental
// sensor.
//
// It is recommended to call Halt() when done with the device so it stops
// sampling.
//
// When using SPI, the CS line must be used.
func NewSPI(p spi.Port, opts *Opts) (*Dev, error) {
if opts != nil && opts.Address != 0 {
return nil, wrap(errors.New("do not use Address in SPI"))
}
// It works both in Mode0 and Mode3.
c, err := p.Connect(10000000, spi.Mode3, 8)
if err != nil {
return nil, wrap(err)
}
d := &Dev{d: c, isSPI: true}
if err := d.makeDev(opts); err != nil {
return nil, err
}
return d, nil
}
//
func (d *Dev) makeDev(opts *Opts) error {
if opts == nil {
opts = &defaults
}
if opts.Temperature == Off {
return wrap(errors.New("temperature measurement is required, use at least O1x"))
}
d.opts = *opts
d.measDelay = d.opts.delayTypical()
// The device starts in 2ms as per datasheet. No need to wait for boot to be
// finished.
var chipID [1]byte
// Read register 0xD0 to read the chip id.
if err := d.readReg(0xD0, chipID[:]); err != nil {
return err
}
if chipID[0] != 0x60 {
return wrap(fmt.Errorf("unexpected chip id %x; is this a BME280?", chipID[0]))
}
// TODO(maruel): We may want to wait for isIdle().
// Read calibration data t1~3, p1~9, 8bits padding, h1.
var tph [0xA2 - 0x88]byte
if err := d.readReg(0x88, tph[:]); err != nil {
return err
}
// Read calibration data h2~6
var h [0xE8 - 0xE1]byte
if err := d.readReg(0xE1, h[:]); err != nil {
return err
}
d.c = newCalibration(tph[:], h[:])
config := []byte{
// ctrl_meas; put it to sleep otherwise the config update may be ignored.
// This is really just in case the device was somehow put into normal but
// was not Halt'ed.
0xF4, byte(d.opts.Temperature)<<5 | byte(d.opts.Pressure)<<2 | byte(sleep),
// ctrl_hum
0xF2, byte(opts.Humidity),
// config
0xF5, byte(s1s)<<5 | byte(NoFilter)<<2,
// As per page 25, ctrl_meas must be re-written last.
// ctrl_meas
0xF4, byte(d.opts.Temperature)<<5 | byte(d.opts.Pressure)<<2 | byte(sleep),
}
if err := d.writeCommands(config[:]); err != nil {
return err
}
return nil
}
// sense reads the device's registers.
//
// It must be called with d.mu lock held.
func (d *Dev) sense(env *devices.Environment) error {
// All registers must be read in a single pass, as noted at page 21, section
// 4.1.
// Pressure: 0xF7~0xF9
// Temperature: 0xFA~0xFC
// Humidity: 0xFD~0xFE
buf := [0xFF - 0xF7]byte{}
if err := d.readReg(0xF7, buf[:]); err != nil {
return err
}
// These values are 20 bits as per doc.
pRaw := int32(buf[0])<<12 | int32(buf[1])<<4 | int32(buf[2])>>4
tRaw := int32(buf[3])<<12 | int32(buf[4])<<4 | int32(buf[5])>>4
// This value is 16 bits as per doc.
hRaw := int32(buf[6])<<8 | int32(buf[7])
t, tFine := d.c.compensateTempInt(tRaw)
env.Temperature = devices.Celsius(t * 10)
p := d.c.compensatePressureInt64(pRaw, tFine)
env.Pressure = devices.KPascal((int32(p) + 127) / 256)
h := d.c.compensateHumidityInt(hRaw, tFine)
env.Humidity = devices.RelativeHumidity((int32(h)*100 + 511) / 1024)
return nil
}
func (d *Dev) sensingContinuous(interval time.Duration, sensing chan<- devices.Environment, stop <-chan struct{}) {
t := time.NewTicker(interval)
defer t.Stop()
for {
// Do one initial sensing right away.
var e devices.Environment
d.mu.Lock()
err := d.sense(&e)
d.mu.Unlock()
if err != nil {
log.Printf("bme280: failed to sense: %v", err)
return
}
select {
case sensing <- e:
case <-stop:
return
}
select {
case <-stop:
return
case <-t.C:
}
}
}
func (d *Dev) isIdle() (bool, error) {
// status
v := [1]byte{}
if err := d.readReg(0xF3, v[:]); err != nil {
return false, err
}
// Make sure bit 3 is cleared. Bit 0 is only important at device boot up.
return v[0]&8 == 0, nil
}
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 wrap(err)
}
copy(b, read[1:])
return nil
}
if err := d.d.Tx([]byte{reg}, b); err != nil {
return wrap(err)
}
return nil
}
// writeCommands writes a command to the bme280.
//
// Warning: b may be modified!
func (d *Dev) writeCommands(b []byte) error {
if d.isSPI {
// Page 33; set RW bit 7 to 0.
for i := 0; i < len(b); i += 2 {
b[i] &^= 0x80
}
}
if err := d.d.Tx(b, nil); err != nil {
return wrap(err)
}
return nil
}
//
// mode is the operating mode.
type mode byte
const (
sleep mode = 0 // no operation, all registers accessible, lowest power, selected after startup
forced mode = 1 // perform one measurement, store results and return to sleep mode
normal mode = 3 // perpetual cycling of measurements and inactive periods
)
type status byte
const (
measuring status = 8 // set when conversion is running
imUpdate status = 1 // set when NVM data are being copied to image registers
)
var defaults = Opts{
Temperature: O4x,
Pressure: O4x,
Humidity: O4x,
Address: 0x76,
}
// standby is the time the BME280 waits idle between measurements. This reduces
// power consumption when the host won't read the values as fast as the
// measurements are done.
type standby uint8
// Possible standby values, these determines the refresh rate.
const (
s500us standby = 0
s10ms standby = 6
s20ms standby = 7
s62ms standby = 1
s125ms standby = 2
s250ms standby = 3
s500ms standby = 4
s1s standby = 5
)
func chooseStandby(d time.Duration) standby {
switch {
case d < 10*time.Millisecond:
return s500us
case d < 20*time.Millisecond:
return s10ms
case d < 62500*time.Microsecond:
return s20ms
case d < 125*time.Millisecond:
return s62ms
case d < 250*time.Millisecond:
return s125ms
case d < 500*time.Millisecond:
return s250ms
case d < time.Second:
return s500ms
default:
return s1s
}
}
// Register table:
// 0x00..0x87 --
// 0x88..0xA1 Calibration data
// 0xA2..0xCF --
// 0xD0 Chip id; reads as 0x60
// 0xD1..0xDF --
// 0xE0 Reset by writing 0xB6 to it
// 0xE1..0xF0 Calibration data
// 0xF1 --
// 0xF2 ctrl_hum; ctrl_meas must be written to after for change to this register to take effect
// 0xF3 status
// 0xF4 ctrl_meas
// 0xF5 config
// 0xF6 --
// 0xF7 press_msb
// 0xF8 press_lsb
// 0xF9 press_xlsb
// 0xFA temp_msb
// 0xFB temp_lsb
// 0xFC temp_xlsb
// 0xFD hum_msb
// 0xFE hum_lsb
// https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf
// Page 23
// newCalibration parses calibration data from both buffers.
func newCalibration(tph, h []byte) (c calibration) {
c.t1 = uint16(tph[0]) | uint16(tph[1])<<8
c.t2 = int16(tph[2]) | int16(tph[3])<<8
c.t3 = int16(tph[4]) | int16(tph[5])<<8
c.p1 = uint16(tph[6]) | uint16(tph[7])<<8
c.p2 = int16(tph[8]) | int16(tph[9])<<8
c.p3 = int16(tph[10]) | int16(tph[11])<<8
c.p4 = int16(tph[12]) | int16(tph[13])<<8
c.p5 = int16(tph[14]) | int16(tph[15])<<8
c.p6 = int16(tph[16]) | int16(tph[17])<<8
c.p7 = int16(tph[18]) | int16(tph[19])<<8
c.p8 = int16(tph[20]) | int16(tph[21])<<8
c.p9 = int16(tph[22]) | int16(tph[23])<<8
c.h1 = uint8(tph[25])
c.h2 = int16(h[0]) | int16(h[1])<<8
c.h3 = uint8(h[2])
c.h4 = int16(h[3])<<4 | int16(h[4])&0xF
c.h5 = int16(h[4])>>4 | int16(h[5])<<4
c.h6 = int8(h[6])
return c
}
type calibration struct {
t1 uint16
t2, t3 int16
p1 uint16
p2, p3, p4, p5, p6, p7, p8, p9 int16
h2 int16 // Reordered for packing
h1, h3 uint8
h4, h5 int16
h6 int8
}
// Pages 23-24
// compensateTempInt returns temperature in °C, resolution is 0.01 °C.
// Output value of 5123 equals 51.23 C.
//
// raw has 20 bits of resolution.
func (c *calibration) compensateTempInt(raw int32) (int32, int32) {
x := ((raw>>3 - int32(c.t1)<<1) * int32(c.t2)) >> 11
y := ((((raw>>4 - int32(c.t1)) * (raw>>4 - int32(c.t1))) >> 12) * int32(c.t3)) >> 14
tFine := x + y
return (tFine*5 + 128) >> 8, tFine
}
// compensatePressureInt64 returns pressure in Pa in Q24.8 format (24 integer
// bits and 8 fractional bits). Output value of 24674867 represents
// 24674867/256 = 96386.2 Pa = 963.862 hPa.
//
// raw has 20 bits of resolution.
func (c *calibration) compensatePressureInt64(raw, tFine int32) uint32 {
x := int64(tFine) - 128000
y := x * x * int64(c.p6)
y += (x * int64(c.p5)) << 17
y += int64(c.p4) << 35
x = (x*x*int64(c.p3))>>8 + ((x * int64(c.p2)) << 12)
x = ((int64(1)<<47 + x) * int64(c.p1)) >> 33
if x == 0 {
return 0
}
p := ((((1048576 - int64(raw)) << 31) - y) * 3125) / x
x = (int64(c.p9) * (p >> 13) * (p >> 13)) >> 25
y = (int64(c.p8) * p) >> 19
return uint32(((p + x + y) >> 8) + (int64(c.p7) << 4))
}
// compensateHumidityInt returns humidity in %RH in Q22.10 format (22 integer
// and 10 fractional bits). Output value of 47445 represents 47445/1024 =
// 46.333%
//
// raw has 16 bits of resolution.
func (c *calibration) compensateHumidityInt(raw, tFine int32) uint32 {
x := tFine - 76800
x1 := raw<<14 - int32(c.h4)<<20 - int32(c.h5)*x
x2 := (x1 + 16384) >> 15
x3 := (x * int32(c.h6)) >> 10
x4 := (x * int32(c.h3)) >> 11
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
}
if x > 419430400 {
return 419430400 >> 12
}
return uint32(x >> 12)
}
func wrap(err error) error {
return fmt.Errorf("bme280: %v", err)
}
var _ devices.Environmental = &Dev{}
var _ devices.Device = &Dev{}
var _ fmt.Stringer = &Dev{}

@ -1,7 +0,0 @@
# '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.

@ -1,284 +0,0 @@
// 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 bmp180 controls a Bosch BMP180 device over I²C.
//
// Datasheet
//
// https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BMP180-DS000-121.pdf
//
// The font the official datasheet on page 15 is hard to read, a copy with
// readable text can be found here:
//
// https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf
//
// Notes on the datasheet
//
// The results of the calculations in the algorithm on page 15 are partly
// wrong. It looks like the original authors used non-integer calculations and
// some nubers were rounded. Take the results of the calculations with a grain
// of salt.
package bmp180
import (
"encoding/binary"
"errors"
"fmt"
"log"
"sync"
"time"
"periph.io/x/periph/conn/i2c"
"periph.io/x/periph/conn/mmr"
"periph.io/x/periph/devices"
)
// Oversampling affects how much time is taken to measure pressure.
type Oversampling uint8
// Possible oversampling values.
//
// The higher the more time and power it takes to take a measurement. Even at
// 8x for pressure sensor, it is less than 30ms albeit at small increased power
// consumption, which may increase the temperature reading.
const (
O1x Oversampling = 0
O2x Oversampling = 1
O4x Oversampling = 2
O8x Oversampling = 3
)
const oversamplingName = "1x2x4x8x"
var oversamplingIndex = [...]uint8{0, 2, 4, 6, 8}
func (i Oversampling) String() string {
if i >= Oversampling(len(oversamplingIndex)-1) {
return fmt.Sprintf("Oversampling(%d)", i)
}
return oversamplingName[oversamplingIndex[i]:oversamplingIndex[i+1]]
}
// Dev is a handle to a bmp180.
type Dev struct {
dev mmr.Dev8
os Oversampling
cal calibration
mu sync.Mutex
stop chan struct{}
wg sync.WaitGroup
}
func (d *Dev) String() string {
return fmt.Sprintf("BMP180{%s}", d.dev.Conn)
}
// Sense returns measurements as °C and kPa.
func (d *Dev) Sense(env *devices.Environment) error {
d.mu.Lock()
defer d.mu.Unlock()
if d.stop != nil {
return wrap(errors.New("already sensing continuously"))
}
return d.sense(env)
}
// SenseContinuous implements devices.Environmental.
func (d *Dev) SenseContinuous(interval time.Duration) (<-chan devices.Environment, error) {
d.mu.Lock()
defer d.mu.Unlock()
if d.stop != nil {
close(d.stop)
d.stop = nil
d.wg.Wait()
}
sensing := make(chan devices.Environment)
d.stop = make(chan struct{})
d.wg.Add(1)
go func() {
defer d.wg.Done()
defer close(sensing)
d.sensingContinuous(interval, sensing, d.stop)
}()
return sensing, nil
}
// Halt stops continuous reading.
func (d *Dev) Halt() error {
d.mu.Lock()
defer d.mu.Unlock()
if d.stop != nil {
close(d.stop)
d.stop = nil
d.wg.Wait()
}
return nil
}
// New returns an object that communicates over I²C to BMP180 environmental
// sensor.
//
// The I²C bus frequency can be up to 3.4MHz.
func New(b i2c.Bus, os Oversampling) (d *Dev, err error) {
bus := &i2c.Dev{Bus: b, Addr: 0x77}
d = &Dev{dev: mmr.Dev8{Conn: bus, Order: binary.BigEndian}, os: os}
// Confirm the chip ID.
id, err := d.dev.ReadUint8(0xD0)
if err != nil {
return nil, wrap(err)
}
if id != 0x55 {
return nil, wrap(fmt.Errorf("unexpected chip id 0x%x; is this a BMP180?", id))
}
// Read calibration data.
if err := d.dev.ReadStruct(0xAA, &d.cal); err != nil {
return nil, wrap(err)
}
if !d.cal.isValid() {
return nil, wrap(errors.New("calibration data is invalid"))
}
return d, nil
}
//
func (d *Dev) sense(env *devices.Environment) error {
// Request temperature convertion and read measurement.
if err := d.dev.WriteUint8(0xF4, 0x20|0x0E); err != nil {
return wrap(err)
}
time.Sleep(4500 * time.Microsecond)
ut, err := d.dev.ReadUint16(0xF6)
if err != nil {
return wrap(err)
}
temp := d.cal.compensateTemp(ut)
// Request pressure conversion and read measurement.
if err := d.dev.WriteUint8(0xF4, 0x20|0x14|(uint8(d.os)<<6)); err != nil {
return wrap(err)
}
time.Sleep(pressureConvTime[d.os])
var pressureBuf [3]byte
if err := d.dev.ReadStruct(0xF6, pressureBuf[:]); err != nil {
return wrap(err)
}
up := (int32(pressureBuf[0])<<16 + int32(pressureBuf[1])<<8 | int32(pressureBuf[2])) >> (8 - d.os)
pressure := d.cal.compensatePressure(up, int32(ut), d.os)
env.Temperature = devices.Celsius(temp * 100)
env.Pressure = devices.KPascal(pressure)
return nil
}
func (d *Dev) sensingContinuous(interval time.Duration, sensing chan<- devices.Environment, stop <-chan struct{}) {
t := time.NewTicker(interval)
defer t.Stop()
for {
// Do one initial sensing right away.
var e devices.Environment
d.mu.Lock()
err := d.sense(&e)
d.mu.Unlock()
if err != nil {
log.Printf("bmp180: failed to sense: %v", err)
return
}
select {
case sensing <- e:
case <-stop:
return
}
select {
case <-stop:
return
case <-t.C:
}
}
}
//
// Maximum conversion time for pressure.
var pressureConvTime = [...]time.Duration{
4500 * time.Microsecond,
7500 * time.Microsecond,
13500 * time.Microsecond,
25500 * time.Microsecond,
}
// calibration data read from the internal EEPROM (datasheet page 13).
type calibration struct {
AC1, AC2, AC3 int16
AC4, AC5, AC6 uint16
B1, B2 int16
MB, MC, MD int16
}
func isValid(i int16) bool {
return i != 0 && i != ^int16(0)
}
func isValidU(i uint16) bool {
return i != 0 && i != 0xFFFF
}
// valid checks whether the calibration data is valid.
func (c *calibration) isValid() bool {
return isValid(c.AC1) && isValid(c.AC2) && isValid(c.AC3) && isValidU(c.AC4) && isValidU(c.AC5) && isValidU(c.AC6) && isValid(c.B1) && isValid(c.B2) && isValid(c.MB) && isValid(c.MC) && isValid(c.MD)
}
// compensateTemp returns temperature in °C, resolution is 0.1 °C.
// Output value of 512 equals 51.2 C.
func (c *calibration) compensateTemp(raw uint16) int32 {
x1 := ((int64(raw) - int64(c.AC6)) * int64(c.AC5)) >> 15
x2 := (int64(c.MC) << 11) / (x1 + int64(c.MD))
b5 := x1 + x2
t := (b5 + 8) >> 4
return int32(t)
}
// compensatePressure returns pressure in Pa.
func (c *calibration) compensatePressure(up, ut int32, os Oversampling) uint32 {
x1 := ((int64(ut) - int64(c.AC6)) * int64(c.AC5)) >> 15
x2 := (int64(c.MC) * 2048) / (x1 + int64(c.MD))
b5 := x1 + x2
b6 := b5 - 4000
x1 = (int64(c.B2) * ((b6 * b6) >> 12)) >> 11
x2 = int64(c.AC2) * b6 >> 11
x3 := x1 + x2
b3 := (((int64(c.AC1)*4 + x3) << uint(os)) + 2) / 4
x1 = (int64(c.AC3) * b6) >> 13
x2 = (int64(c.B1) * ((b6 * b6) >> 12)) >> 16
x3 = ((x1 + x2) + 2) / 4
b4 := (int64(c.AC4) * (x3 + 32768)) >> 15
b7 := (int64(up) - b3) * (50000 >> uint(os))
var p int64
if b7 < 0x80000000 {
p = (b7 * 2) / b4
} else {
p = (b7 / b4) * 2
}
x1 = (p >> 8) * (p >> 8)
x1 = (x1 * 3038) >> 16
x2 = (-7357 * p) >> 16
p = p + (x1+x2+3791)>>4
return uint32(p)
}
func wrap(err error) error {
return fmt.Errorf("bmp180: %v", err)
}
var _ devices.Environmental = &Dev{}
var _ devices.Device = &Dev{}
var _ fmt.Stringer = &Dev{}

@ -0,0 +1,115 @@
// 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 bmxx80
import (
"encoding/binary"
"time"
"periph.io/x/periph/devices"
)
// sense180 reads the device's registers for bmp180.
//
// It must be called with d.mu lock held.
func (d *Dev) sense180(env *devices.Environment) error {
// Request temperature convertion and read measurement.
if err := d.writeCommands([]byte{0xF4, 0x20 | 0x0E}); err != nil {
return d.wrap(err)
}
time.Sleep(4500 * time.Microsecond)
var tempBuf [2]byte
if err := d.readReg(0xF6, tempBuf[:]); err != nil {
return d.wrap(err)
}
rawTemp := binary.BigEndian.Uint16(tempBuf[:])
temp := d.cal180.compensateTemp(rawTemp)
// Request pressure conversion and read measurement.
if err := d.writeCommands([]byte{0xF4, 0x20 | 0x14 | d.os<<6}); err != nil {
return d.wrap(err)
}
time.Sleep(pressureConvTime180[d.os])
var pressureBuf [3]byte
if err := d.readReg(0xF6, pressureBuf[:]); err != nil {
return d.wrap(err)
}
up := (int32(pressureBuf[0])<<16 + int32(pressureBuf[1])<<8 | int32(pressureBuf[2])) >> (8 - d.os)
pressure := d.cal180.compensatePressure(up, int32(rawTemp), uint(d.os))
env.Temperature = devices.Celsius(temp * 100)
env.Pressure = devices.KPascal(pressure)
return nil
}
// pressureConvTime180 is the maximum conversion time for pressure.
var pressureConvTime180 = [...]time.Duration{
4500 * time.Microsecond,
7500 * time.Microsecond,
13500 * time.Microsecond,
25500 * time.Microsecond,
}
// calibration180 data read from the internal EEPROM (datasheet page 13).
type calibration180 struct {
AC1, AC2, AC3 int16
AC4, AC5, AC6 uint16
B1, B2 int16
MB, MC, MD int16
}
func isValid(i int16) bool {
return i != 0 && i != ^int16(0)
}
func isValidU(i uint16) bool {
return i != 0 && i != 0xFFFF
}
// valid checks whether the calibration data is valid.
func (c *calibration180) isValid() bool {
return isValid(c.AC1) && isValid(c.AC2) && isValid(c.AC3) && isValidU(c.AC4) && isValidU(c.AC5) && isValidU(c.AC6) && isValid(c.B1) && isValid(c.B2) && isValid(c.MB) && isValid(c.MC) && isValid(c.MD)
}
// compensateTemp returns temperature in °C, resolution is 0.1 °C.
// Output value of 512 equals 51.2 C.
func (c *calibration180) compensateTemp(raw uint16) int32 {
x1 := ((int64(raw) - int64(c.AC6)) * int64(c.AC5)) >> 15
x2 := (int64(c.MC) << 11) / (x1 + int64(c.MD))
b5 := x1 + x2
t := (b5 + 8) >> 4
return int32(t)
}
// compensatePressure returns pressure in Pa.
func (c *calibration180) compensatePressure(up, ut int32, os uint) uint32 {
x1 := ((int64(ut) - int64(c.AC6)) * int64(c.AC5)) >> 15
x2 := (int64(c.MC) * 2048) / (x1 + int64(c.MD))
b5 := x1 + x2
b6 := b5 - 4000
x1 = (int64(c.B2) * ((b6 * b6) >> 12)) >> 11
x2 = int64(c.AC2) * b6 >> 11
x3 := x1 + x2
b3 := (((int64(c.AC1)*4 + x3) << os) + 2) / 4
x1 = (int64(c.AC3) * b6) >> 13
x2 = (int64(c.B1) * ((b6 * b6) >> 12)) >> 16
x3 = ((x1 + x2) + 2) / 4
b4 := (int64(c.AC4) * (x3 + 32768)) >> 15
b7 := (int64(up) - b3) * (50000 >> os)
var p int64
if b7 < 0x80000000 {
p = (b7 * 2) / b4
} else {
p = (b7 / b4) * 2
}
x1 = (p >> 8) * (p >> 8)
x1 = (x1 * 3038) >> 16
x2 = (-7357 * p) >> 16
p = p + (x1+x2+3791)>>4
return uint32(p)
}

@ -2,7 +2,7 @@
// Use of this source code is governed under the Apache License, Version 2.0 // Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file. // that can be found in the LICENSE file.
package bmp180 package bmxx80
import ( import (
"testing" "testing"
@ -12,31 +12,33 @@ import (
"periph.io/x/periph/devices" "periph.io/x/periph/devices"
) )
func TestNew_fail_read_chipid(t *testing.T) { var opts180 = &Opts{Temperature: O1x, Pressure: O1x}
func TestNew180_fail_read_chipid(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection read fail. // Chip ID detection read fail.
}, },
DontPanic: true, DontPanic: true,
} }
if _, err := New(&bus, O1x); err == nil { if _, err := NewI2C(&bus, 0x77, opts180); err == nil {
t.Fatal("can't read chip ID") t.Fatal("can't read chip ID")
} }
} }
func TestNew_bad_chipid(t *testing.T) { func TestNew180_bad_chipid(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Bad Chip ID detection. // Bad Chip ID detection.
{Addr: 0x77, W: []byte{0xd0}, R: []byte{0x60}}, {Addr: 0x77, W: []byte{0xd0}, R: []byte{0x61}},
}, },
} }
if _, err := New(&bus, O1x); err == nil { if _, err := NewI2C(&bus, 0x77, opts180); err == nil {
t.Fatal("bad chip ID") t.Fatal("bad chip ID")
} }
} }
func TestNew_fail_calib(t *testing.T) { func TestNew180_fail_calib(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -45,12 +47,12 @@ func TestNew_fail_calib(t *testing.T) {
}, },
DontPanic: true, DontPanic: true,
} }
if _, err := New(&bus, O1x); err == nil { if _, err := NewI2C(&bus, 0x77, opts180); err == nil {
t.Fatal("can't read calibration") t.Fatal("can't read calibration")
} }
} }
func TestNew_bad_calib(t *testing.T) { func TestNew180_bad_calib(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -64,12 +66,24 @@ func TestNew_bad_calib(t *testing.T) {
}, },
DontPanic: true, DontPanic: true,
} }
if _, err := New(&bus, O1x); err == nil { if _, err := NewI2C(&bus, 0x77, opts180); err == nil {
t.Fatal("bad calibration") t.Fatal("bad calibration")
} }
} }
func TestSense_success(t *testing.T) { func TestSense180_success(t *testing.T) {
values := []struct {
o Oversampling
c byte
p devices.KPascal
}{
{Oversampling(42), 0x34, 100567},
{O1x, 0x34, 100567},
{O2x, 0x74, 100567},
{O4x, 0xB4, 100568},
{O8x, 0xF4, 100568},
}
for _, line := range values {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -85,12 +99,12 @@ func TestSense_success(t *testing.T) {
// Read temperature. // Read temperature.
{Addr: 0x77, W: []byte{0xF6}, R: []byte{0x71, 0xBf}}, {Addr: 0x77, W: []byte{0xF6}, R: []byte{0x71, 0xBf}},
// Request pressure. // Request pressure.
{Addr: 0x77, W: []byte{0xF4, 0x34}}, {Addr: 0x77, W: []byte{0xF4, line.c}},
// Read pressure. // Read pressure.
{Addr: 0x77, W: []byte{0xF6}, R: []byte{0xAb, 0x96, 0}}, {Addr: 0x77, W: []byte{0xF6}, R: []byte{0xAb, 0x96, 0}},
}, },
} }
dev, err := New(&bus, O1x) dev, err := NewI2C(&bus, 0x77, &Opts{Pressure: line.o})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -104,7 +118,7 @@ func TestSense_success(t *testing.T) {
if env.Temperature != 25300 { if env.Temperature != 25300 {
t.Fatalf("temp %d", env.Temperature) t.Fatalf("temp %d", env.Temperature)
} }
if env.Pressure != 100567 { if env.Pressure != line.p {
t.Fatalf("pressure %d", env.Pressure) t.Fatalf("pressure %d", env.Pressure)
} }
if env.Humidity != 0 { if env.Humidity != 0 {
@ -117,8 +131,9 @@ func TestSense_success(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} }
}
func TestSense_fail_1(t *testing.T) { func TestSense180_fail_1(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -133,7 +148,7 @@ func TestSense_fail_1(t *testing.T) {
}, },
DontPanic: true, DontPanic: true,
} }
dev, err := New(&bus, O1x) dev, err := NewI2C(&bus, 0x77, opts180)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -146,7 +161,7 @@ func TestSense_fail_1(t *testing.T) {
} }
} }
func TestSense_fail_2(t *testing.T) { func TestSense180_fail_2(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -163,7 +178,7 @@ func TestSense_fail_2(t *testing.T) {
}, },
DontPanic: true, DontPanic: true,
} }
dev, err := New(&bus, O1x) dev, err := NewI2C(&bus, 0x77, opts180)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -176,7 +191,7 @@ func TestSense_fail_2(t *testing.T) {
} }
} }
func TestSense_fail_3(t *testing.T) { func TestSense180_fail_3(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -195,7 +210,7 @@ func TestSense_fail_3(t *testing.T) {
}, },
DontPanic: true, DontPanic: true,
} }
dev, err := New(&bus, O1x) dev, err := NewI2C(&bus, 0x77, opts180)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -208,7 +223,7 @@ func TestSense_fail_3(t *testing.T) {
} }
} }
func TestSense_fail_4(t *testing.T) { func TestSense180_fail_4(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -229,7 +244,7 @@ func TestSense_fail_4(t *testing.T) {
}, },
DontPanic: true, DontPanic: true,
} }
dev, err := New(&bus, O1x) dev, err := NewI2C(&bus, 0x77, opts180)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -242,7 +257,7 @@ func TestSense_fail_4(t *testing.T) {
} }
} }
func TestSenseContinuous_success(t *testing.T) { func TestSenseContinuous180_success(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -272,7 +287,7 @@ func TestSenseContinuous_success(t *testing.T) {
}, },
DontPanic: true, DontPanic: true,
} }
dev, err := New(&bus, O1x) dev, err := NewI2C(&bus, 0x77, opts180)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -318,6 +333,7 @@ func TestSenseContinuous_success(t *testing.T) {
} }
} }
/*
func TestOversampling(t *testing.T) { func TestOversampling(t *testing.T) {
data := []struct { data := []struct {
o Oversampling o Oversampling
@ -336,9 +352,10 @@ func TestOversampling(t *testing.T) {
} }
} }
} }
*/
func TestCompensate(t *testing.T) { func TestCompensate180(t *testing.T) {
c := calibration{ c := calibration180{
AC1: 408, AC1: 408,
AC2: -72, AC2: -72,
AC3: -14383, AC3: -14383,

@ -0,0 +1,209 @@
// Copyright 2016 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 bmxx80
import (
"time"
"periph.io/x/periph/devices"
)
// sense280 reads the device's registers for bme280/bmp280.
//
// It must be called with d.mu lock held.
func (d *Dev) sense280(env *devices.Environment) error {
// All registers must be read in a single pass, as noted at page 21, section
// 4.1.
// Pressure: 0xF7~0xF9
// Temperature: 0xFA~0xFC
// Humidity: 0xFD~0xFE
buf := [8]byte{}
b := buf[:]
if !d.isBME {
b = buf[:6]
}
if err := d.readReg(0xF7, b); err != nil {
return err
}
// These values are 20 bits as per doc.
pRaw := int32(buf[0])<<12 | int32(buf[1])<<4 | int32(buf[2])>>4
tRaw := int32(buf[3])<<12 | int32(buf[4])<<4 | int32(buf[5])>>4
t, tFine := d.cal280.compensateTempInt(tRaw)
env.Temperature = devices.Celsius(t * 10)
if d.opts.Pressure != Off {
p := d.cal280.compensatePressureInt64(pRaw, tFine)
env.Pressure = devices.KPascal((int32(p) + 127) / 256)
}
if d.opts.Humidity != Off {
// This value is 16 bits as per doc.
hRaw := int32(buf[6])<<8 | int32(buf[7])
h := d.cal280.compensateHumidityInt(hRaw, tFine)
env.Humidity = devices.RelativeHumidity((int32(h)*100 + 511) / 1024)
}
return nil
}
func (d *Dev) isIdle280() (bool, error) {
// status
v := [1]byte{}
if err := d.readReg(0xF3, v[:]); err != nil {
return false, err
}
// Make sure bit 3 is cleared. Bit 0 is only important at device boot up.
return v[0]&8 == 0, nil
}
// mode is the operating mode.
type mode byte
const (
sleep mode = 0 // no operation, all registers accessible, lowest power, selected after startup
forced mode = 1 // perform one measurement, store results and return to sleep mode
normal mode = 3 // perpetual cycling of measurements and inactive periods
)
// standby is the time the BMx280 waits idle between measurements. This reduces
// power consumption when the host won't read the values as fast as the
// measurements are done.
type standby uint8
// Possible standby values, these determines the refresh rate.
const (
s500us standby = 0
s10msBME standby = 6
s20msBME standby = 7
s62ms standby = 1
s125ms standby = 2
s250ms standby = 3
s500ms standby = 4
s1s standby = 5
s2sBMP standby = 6
s4sBMP standby = 7
)
func chooseStandby(isBME bool, d time.Duration) standby {
switch {
case d < 10*time.Millisecond:
return s500us
case isBME && d < 20*time.Millisecond:
return s10msBME
case isBME && d < 62500*time.Microsecond:
return s20msBME
case d < 125*time.Millisecond:
return s62ms
case d < 250*time.Millisecond:
return s125ms
case d < 500*time.Millisecond:
return s250ms
case d < time.Second:
return s500ms
case d < 2*time.Second:
return s1s
case !isBME && d < 4*time.Second:
return s2sBMP
default:
if isBME {
return s1s
}
return s4sBMP
}
}
// newCalibration parses calibration data from both buffers.
func newCalibration(tph, h []byte) (c calibration280) {
c.t1 = uint16(tph[0]) | uint16(tph[1])<<8
c.t2 = int16(tph[2]) | int16(tph[3])<<8
c.t3 = int16(tph[4]) | int16(tph[5])<<8
c.p1 = uint16(tph[6]) | uint16(tph[7])<<8
c.p2 = int16(tph[8]) | int16(tph[9])<<8
c.p3 = int16(tph[10]) | int16(tph[11])<<8
c.p4 = int16(tph[12]) | int16(tph[13])<<8
c.p5 = int16(tph[14]) | int16(tph[15])<<8
c.p6 = int16(tph[16]) | int16(tph[17])<<8
c.p7 = int16(tph[18]) | int16(tph[19])<<8
c.p8 = int16(tph[20]) | int16(tph[21])<<8
c.p9 = int16(tph[22]) | int16(tph[23])<<8
c.h1 = uint8(tph[25])
c.h2 = int16(h[0]) | int16(h[1])<<8
c.h3 = uint8(h[2])
c.h4 = int16(h[3])<<4 | int16(h[4])&0xF
c.h5 = int16(h[4])>>4 | int16(h[5])<<4
c.h6 = int8(h[6])
return c
}
type calibration280 struct {
t1 uint16
t2, t3 int16
p1 uint16
p2, p3, p4, p5, p6, p7, p8, p9 int16
h2 int16 // Reordered for packing
h1, h3 uint8
h4, h5 int16
h6 int8
}
// Pages 23-24
// compensateTempInt returns temperature in °C, resolution is 0.01 °C.
// Output value of 5123 equals 51.23 C.
//
// raw has 20 bits of resolution.
func (c *calibration280) compensateTempInt(raw int32) (int32, int32) {
x := ((raw>>3 - int32(c.t1)<<1) * int32(c.t2)) >> 11
y := ((((raw>>4 - int32(c.t1)) * (raw>>4 - int32(c.t1))) >> 12) * int32(c.t3)) >> 14
tFine := x + y
return (tFine*5 + 128) >> 8, tFine
}
// compensatePressureInt64 returns pressure in Pa in Q24.8 format (24 integer
// bits and 8 fractional bits). Output value of 24674867 represents
// 24674867/256 = 96386.2 Pa = 963.862 hPa.
//
// raw has 20 bits of resolution.
func (c *calibration280) compensatePressureInt64(raw, tFine int32) uint32 {
x := int64(tFine) - 128000
y := x * x * int64(c.p6)
y += (x * int64(c.p5)) << 17
y += int64(c.p4) << 35
x = (x*x*int64(c.p3))>>8 + ((x * int64(c.p2)) << 12)
x = ((int64(1)<<47 + x) * int64(c.p1)) >> 33
if x == 0 {
return 0
}
p := ((((1048576 - int64(raw)) << 31) - y) * 3125) / x
x = (int64(c.p9) * (p >> 13) * (p >> 13)) >> 25
y = (int64(c.p8) * p) >> 19
return uint32(((p + x + y) >> 8) + (int64(c.p7) << 4))
}
// compensateHumidityInt returns humidity in %RH in Q22.10 format (22 integer
// and 10 fractional bits). Output value of 47445 represents 47445/1024 =
// 46.333%
//
// raw has 16 bits of resolution.
func (c *calibration280) compensateHumidityInt(raw, tFine int32) uint32 {
x := tFine - 76800
x1 := raw<<14 - int32(c.h4)<<20 - int32(c.h5)*x
x2 := (x1 + 16384) >> 15
x3 := (x * int32(c.h6)) >> 10
x4 := (x * int32(c.h3)) >> 11
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
}
if x > 419430400 {
return 419430400 >> 12
}
return uint32(x >> 12)
}

@ -2,7 +2,7 @@
// Use of this source code is governed under the Apache License, Version 2.0 // Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file. // that can be found in the LICENSE file.
package bme280 package bmxx80
import ( import (
"errors" "errors"
@ -22,7 +22,7 @@ import (
) )
// Real data extracted from a device. // Real data extracted from a device.
var calib = calibration{ var calib280 = calibration280{
t1: 28176, t1: 28176,
t2: 26220, t2: 26220,
t3: 350, t3: 350,
@ -43,7 +43,7 @@ var calib = calibration{
h6: 30, h6: 30,
} }
func TestSPISense_success(t *testing.T) { func TestSPISenseBME280_success(t *testing.T) {
s := spitest.Playback{ s := spitest.Playback{
Playback: conntest.Playback{ Playback: conntest.Playback{
Ops: []conntest.IO{ Ops: []conntest.IO{
@ -57,7 +57,7 @@ func TestSPISense_success(t *testing.T) {
W: []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}, W: []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},
R: []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}, R: []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. // Calibration data humidity.
{ {
W: []byte{0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, W: []byte{0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
R: []byte{0x00, 0x5C, 0x01, 0x00, 0x15, 0x0F, 0x00, 0x1E}, R: []byte{0x00, 0x5C, 0x01, 0x00, 0x15, 0x0F, 0x00, 0x1E},
@ -109,16 +109,13 @@ func TestSPISense_success(t *testing.T) {
} }
} }
func TestNewSPI_fail(t *testing.T) { func TestNewSPIBME280_fail_Connect(t *testing.T) {
if d, err := NewSPI(&spiFail{}, nil); d != nil || err == nil { if dev, err := NewSPI(&spiFail{}, nil); dev != nil || err == nil {
t.Fatal("Connect() have failed") t.Fatal("read failed")
}
if d, err := NewSPI(&spiFail{}, &Opts{Address: 1}); d != nil || err == nil {
t.Fatal("Address can't be used with SPI")
} }
} }
func TestNewSPI_fail_len(t *testing.T) { func TestNewSPIBME280_fail_len(t *testing.T) {
s := spitest.Playback{ s := spitest.Playback{
Playback: conntest.Playback{ Playback: conntest.Playback{
Ops: []conntest.IO{ Ops: []conntest.IO{
@ -141,7 +138,7 @@ func TestNewSPI_fail_len(t *testing.T) {
} }
} }
func TestNewSPI_fail_chipid(t *testing.T) { func TestNewSPIBME280_fail_chipid(t *testing.T) {
s := spitest.Playback{ s := spitest.Playback{
Playback: conntest.Playback{ Playback: conntest.Playback{
Ops: []conntest.IO{ Ops: []conntest.IO{
@ -161,7 +158,7 @@ func TestNewSPI_fail_chipid(t *testing.T) {
} }
} }
func TestNewI2C_fail_io(t *testing.T) { func TestNewI2CBME280_fail_io(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -169,7 +166,7 @@ func TestNewI2C_fail_io(t *testing.T) {
}, },
DontPanic: true, DontPanic: true,
} }
if dev, err := NewI2C(&bus, nil); dev != nil || err == nil { if dev, err := NewI2C(&bus, 0x76, nil); dev != nil || err == nil {
t.Fatal("read failed") t.Fatal("read failed")
} }
// The I/O didn't occur. // The I/O didn't occur.
@ -179,7 +176,7 @@ func TestNewI2C_fail_io(t *testing.T) {
} }
} }
func TestNewI2C_fail_chipid(t *testing.T) { func TestNewI2CBME280_fail_read_calib1(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -187,7 +184,7 @@ func TestNewI2C_fail_chipid(t *testing.T) {
}, },
DontPanic: true, DontPanic: true,
} }
if dev, err := NewI2C(&bus, nil); dev != nil || err == nil { if dev, err := NewI2C(&bus, 0x76, nil); dev != nil || err == nil {
t.Fatal("invalid chip id") t.Fatal("invalid chip id")
} }
if err := bus.Close(); err != nil { if err := bus.Close(); err != nil {
@ -195,7 +192,7 @@ func TestNewI2C_fail_chipid(t *testing.T) {
} }
} }
func TestNewI2C_calib1(t *testing.T) { func TestNewI2CBME280_read_calib2(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -209,8 +206,8 @@ func TestNewI2C_calib1(t *testing.T) {
}, },
DontPanic: true, DontPanic: true,
} }
opts := Opts{Temperature: O1x, Address: 0} opts := Opts{Temperature: O1x}
if dev, err := NewI2C(&bus, &opts); dev != nil || err == nil { if dev, err := NewI2C(&bus, 0x76, &opts); dev != nil || err == nil {
t.Fatal("2nd calib read failed") t.Fatal("2nd calib read failed")
} }
if err := bus.Close(); err != nil { if err := bus.Close(); err != nil {
@ -218,7 +215,7 @@ func TestNewI2C_calib1(t *testing.T) {
} }
} }
func TestNewI2C_calib2(t *testing.T) { func TestNewI2C280_write_config(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -229,12 +226,12 @@ func TestNewI2C_calib2(t *testing.T) {
W: []byte{0x88}, W: []byte{0x88},
R: []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}, R: []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. // Calibration data humidity.
{Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}}, {Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}},
}, },
DontPanic: true, DontPanic: true,
} }
if dev, err := NewI2C(&bus, nil); dev != nil || err == nil { if dev, err := NewI2C(&bus, 0x76, nil); dev != nil || err == nil {
t.Fatal("3rd calib read failed") t.Fatal("3rd calib read failed")
} }
if err := bus.Close(); err != nil { if err := bus.Close(); err != nil {
@ -242,10 +239,14 @@ func TestNewI2C_calib2(t *testing.T) {
} }
} }
func TestI2COpts_bad_addr(t *testing.T) { func TestNewI2C280Opts_temperature(t *testing.T) {
bus := i2ctest.Playback{} bus := i2ctest.Playback{
opts := Opts{Address: 1} Ops: []i2ctest.IO{
if dev, err := NewI2C(&bus, &opts); dev != nil || err == nil { // Chip ID detection.
{Addr: 0x76, W: []byte{0xd0}, R: []byte{0x60}},
},
}
if dev, err := NewI2C(&bus, 0x76, &Opts{}); dev != nil || err == nil {
t.Fatal("bad addr") t.Fatal("bad addr")
} }
if err := bus.Close(); err != nil { if err := bus.Close(); err != nil {
@ -253,18 +254,17 @@ func TestI2COpts_bad_addr(t *testing.T) {
} }
} }
func TestI2COpts(t *testing.T) { func TestNewI2C280_bad_addr(t *testing.T) {
bus := i2ctest.Playback{DontPanic: true} bus := i2ctest.Playback{}
opts := Opts{Address: 0x76} if dev, err := NewI2C(&bus, 1, nil); dev != nil || err == nil {
if dev, err := NewI2C(&bus, &opts); dev != nil || err == nil { t.Fatal("bad addr")
t.Fatal("write fails")
} }
if err := bus.Close(); err != nil { if err := bus.Close(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestI2CSense_fail(t *testing.T) { func TestI2CSenseBME280_fail(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -275,7 +275,7 @@ func TestI2CSense_fail(t *testing.T) {
W: []byte{0x88}, W: []byte{0x88},
R: []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}, R: []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. // Calibration data humidity.
{Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}}, {Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}},
// Configuration. // Configuration.
{Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xa0, 0xf4, 0x6c}, R: nil}, {Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xa0, 0xf4, 0x6c}, R: nil},
@ -285,7 +285,7 @@ func TestI2CSense_fail(t *testing.T) {
}, },
DontPanic: true, DontPanic: true,
} }
dev, err := NewI2C(&bus, nil) dev, err := NewI2C(&bus, 0x76, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -299,18 +299,69 @@ func TestI2CSense_fail(t *testing.T) {
} }
} }
func TestI2CSense_success(t *testing.T) { func TestI2CSenseBMP280_success(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
{Addr: 0x76, W: []byte{0xd0}, R: []byte{0x60}}, {Addr: 0x76, W: []byte{0xd0}, R: []byte{0x58}},
// Calibration data. // Calibration data.
{ {
Addr: 0x76, Addr: 0x76,
W: []byte{0x88}, W: []byte{0x88},
R: []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}, R: []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},
}, },
// Configuration.
{Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf5, 0xa0, 0xf4, 0x6c}, R: nil},
// Forced mode.
{Addr: 0x76, W: []byte{0xF4, 0x6d}},
// Check if idle; not idle.
{Addr: 0x76, W: []byte{0xF3}, R: []byte{8}},
// Check if idle.
{Addr: 0x76, W: []byte{0xF3}, R: []byte{0}},
// Read.
{Addr: 0x76, W: []byte{0xf7}, R: []byte{0x4a, 0x52, 0xc0, 0x80, 0x96, 0xc0}},
},
}
dev, err := NewI2C(&bus, 0x76, nil)
if err != nil {
t.Fatal(err)
}
if s := dev.String(); s != "BMP280{playback(118)}" {
t.Fatal(s)
}
env := devices.Environment{}
if err := dev.Sense(&env); err != nil {
t.Fatal(err)
}
if env.Temperature != 23720 {
t.Fatalf("temp %d", env.Temperature)
}
if env.Pressure != 100943 {
t.Fatalf("pressure %d", env.Pressure)
}
if env.Humidity != 0 {
t.Fatalf("humidity %d", env.Humidity)
}
if err := dev.Halt(); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestI2CSenseBME280_success(t *testing.T) {
bus := i2ctest.Playback{
Ops: []i2ctest.IO{
// Chip ID detection.
{Addr: 0x76, W: []byte{0xd0}, R: []byte{0x60}},
// Calibration data. // Calibration data.
{
Addr: 0x76,
W: []byte{0x88},
R: []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 humidity.
{Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}}, {Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}},
// Configuration. // Configuration.
{Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xa0, 0xf4, 0x6c}, R: nil}, {Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xa0, 0xf4, 0x6c}, R: nil},
@ -324,7 +375,7 @@ func TestI2CSense_success(t *testing.T) {
{Addr: 0x76, W: []byte{0xf7}, R: []byte{0x4a, 0x52, 0xc0, 0x80, 0x96, 0xc0, 0x7a, 0x76}}, {Addr: 0x76, W: []byte{0xf7}, R: []byte{0x4a, 0x52, 0xc0, 0x80, 0x96, 0xc0, 0x7a, 0x76}},
}, },
} }
dev, err := NewI2C(&bus, nil) dev, err := NewI2C(&bus, 0x76, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -352,7 +403,7 @@ func TestI2CSense_success(t *testing.T) {
} }
} }
func TestI2CSense_idle_fail(t *testing.T) { func TestI2CSense280_idle_fail(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -363,7 +414,7 @@ func TestI2CSense_idle_fail(t *testing.T) {
W: []byte{0x88}, W: []byte{0x88},
R: []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}, R: []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. // Calibration data humidity.
{Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}}, {Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}},
// Configuration. // Configuration.
{Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xa0, 0xf4, 0x6c}, R: nil}, {Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xa0, 0xf4, 0x6c}, R: nil},
@ -373,7 +424,7 @@ func TestI2CSense_idle_fail(t *testing.T) {
}, },
DontPanic: true, DontPanic: true,
} }
dev, err := NewI2C(&bus, nil) dev, err := NewI2C(&bus, 0x76, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -386,7 +437,7 @@ func TestI2CSense_idle_fail(t *testing.T) {
} }
} }
func TestI2CSense_command_fail(t *testing.T) { func TestI2CSense280_command_fail(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -397,7 +448,7 @@ func TestI2CSense_command_fail(t *testing.T) {
W: []byte{0x88}, W: []byte{0x88},
R: []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}, R: []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. // Calibration data humidity.
{Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}}, {Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}},
// Configuration. // Configuration.
{Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xa0, 0xf4, 0x6c}, R: nil}, {Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xa0, 0xf4, 0x6c}, R: nil},
@ -409,7 +460,7 @@ func TestI2CSense_command_fail(t *testing.T) {
}, },
DontPanic: true, DontPanic: true,
} }
dev, err := NewI2C(&bus, nil) dev, err := NewI2C(&bus, 0x76, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -422,7 +473,7 @@ func TestI2CSense_command_fail(t *testing.T) {
} }
} }
func TestI2CSenseContinuous_success(t *testing.T) { func TestI2CSenseContinuous280_success(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -433,7 +484,7 @@ func TestI2CSenseContinuous_success(t *testing.T) {
W: []byte{0x88}, W: []byte{0x88},
R: []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}, R: []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. // Calibration data humidity.
{Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}}, {Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}},
// Configuration. // Configuration.
{Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xa0, 0xf4, 0x6c}, R: nil}, {Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xa0, 0xf4, 0x6c}, R: nil},
@ -453,7 +504,7 @@ func TestI2CSenseContinuous_success(t *testing.T) {
{Addr: 0x76, W: []byte{0xF5, 0xa0, 0xf4, 0x6c}}, {Addr: 0x76, W: []byte{0xF5, 0xa0, 0xf4, 0x6c}},
}, },
} }
dev, err := NewI2C(&bus, nil) dev, err := NewI2C(&bus, 0x76, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -532,7 +583,7 @@ func TestI2CSenseContinuous_success(t *testing.T) {
} }
} }
func TestI2CSenseContinuous_command_fail(t *testing.T) { func TestI2CSenseContinuous280_command_fail(t *testing.T) {
bus := i2ctest.Playback{ bus := i2ctest.Playback{
Ops: []i2ctest.IO{ Ops: []i2ctest.IO{
// Chip ID detection. // Chip ID detection.
@ -543,7 +594,7 @@ func TestI2CSenseContinuous_command_fail(t *testing.T) {
W: []byte{0x88}, W: []byte{0x88},
R: []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}, R: []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. // Calibration data humidity.
{Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}}, {Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}},
// Configuration. // Configuration.
{Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xa0, 0xf4, 0x6c}, R: nil}, {Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xa0, 0xf4, 0x6c}, R: nil},
@ -551,7 +602,7 @@ func TestI2CSenseContinuous_command_fail(t *testing.T) {
}, },
DontPanic: true, DontPanic: true,
} }
dev, err := NewI2C(&bus, nil) dev, err := NewI2C(&bus, 0x76, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -560,7 +611,7 @@ func TestI2CSenseContinuous_command_fail(t *testing.T) {
} }
} }
func TestI2CSenseContinuous_sense_fail(t *testing.T) { func TestI2CSenseContinuous280_sense_fail(t *testing.T) {
if !testing.Verbose() { if !testing.Verbose() {
log.SetOutput(ioutil.Discard) log.SetOutput(ioutil.Discard)
defer log.SetOutput(os.Stderr) defer log.SetOutput(os.Stderr)
@ -575,7 +626,7 @@ func TestI2CSenseContinuous_sense_fail(t *testing.T) {
W: []byte{0x88}, W: []byte{0x88},
R: []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}, R: []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. // Calibration data humidity.
{Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}}, {Addr: 0x76, W: []byte{0xe1}, R: []byte{0x6e, 0x1, 0x0, 0x13, 0x5, 0x0, 0x1e}},
// Configuration. // Configuration.
{Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xa0, 0xf4, 0x6c}, R: nil}, {Addr: 0x76, W: []byte{0xf4, 0x6c, 0xf2, 0x3, 0xf5, 0xa0, 0xf4, 0x6c}, R: nil},
@ -585,7 +636,7 @@ func TestI2CSenseContinuous_sense_fail(t *testing.T) {
}, },
DontPanic: true, DontPanic: true,
} }
dev, err := NewI2C(&bus, nil) dev, err := NewI2C(&bus, 0x76, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -603,16 +654,16 @@ func TestI2CSenseContinuous_sense_fail(t *testing.T) {
} }
} }
func TestCalibrationFloat(t *testing.T) { func TestCalibration280Float(t *testing.T) {
// Real data extracted from measurements from this device. // Real data extracted from measurements from this device.
tRaw := int32(524112) tRaw := int32(524112)
pRaw := int32(309104) pRaw := int32(309104)
hRaw := int32(30987) hRaw := int32(30987)
// Compare the values with the 3 algorithms. // Compare the values with the 3 algorithms.
temp, tFine := calib.compensateTempFloat(tRaw) temp, tFine := calib280.compensateTempFloat(tRaw)
pres := calib.compensatePressureFloat(pRaw, tFine) pres := calib280.compensatePressureFloat(pRaw, tFine)
humi := calib.compensateHumidityFloat(hRaw, tFine) humi := calib280.compensateHumidityFloat(hRaw, tFine)
if tFine != 117494 { if tFine != 117494 {
t.Fatalf("tFine %d", tFine) t.Fatalf("tFine %d", tFine)
} }
@ -630,16 +681,16 @@ func TestCalibrationFloat(t *testing.T) {
} }
} }
func TestCalibrationInt(t *testing.T) { func TestCalibration280Int(t *testing.T) {
// Real data extracted from measurements from this device. // Real data extracted from measurements from this device.
tRaw := int32(524112) tRaw := int32(524112)
pRaw := int32(309104) pRaw := int32(309104)
hRaw := int32(30987) hRaw := int32(30987)
temp, tFine := calib.compensateTempInt(tRaw) temp, tFine := calib280.compensateTempInt(tRaw)
pres64 := calib.compensatePressureInt64(pRaw, tFine) pres64 := calib280.compensatePressureInt64(pRaw, tFine)
pres32 := calib.compensatePressureInt32(pRaw, tFine) pres32 := calib280.compensatePressureInt32(pRaw, tFine)
humi := calib.compensateHumidityInt(hRaw, tFine) humi := calib280.compensateHumidityInt(hRaw, tFine)
if tFine != 117407 { if tFine != 117407 {
t.Fatalf("tFine %d", tFine) t.Fatalf("tFine %d", tFine)
} }
@ -665,14 +716,14 @@ func TestCalibrationInt(t *testing.T) {
} }
} }
func TestCalibration_limits_0(t *testing.T) { func TestCalibration280_limits_0(t *testing.T) {
c := calibration{h1: 0xFF, h2: 1, h3: 1, h6: 1} c := calibration280{h1: 0xFF, h2: 1, h3: 1, h6: 1}
if v := c.compensateHumidityInt(0x7FFFFFFF>>14, 0xFFFFFFF); v != 0 { if v := c.compensateHumidityInt(0x7FFFFFFF>>14, 0xFFFFFFF); v != 0 {
t.Fatal(v) t.Fatal(v)
} }
} }
func TestCalibration_limits_419430400(t *testing.T) { func TestCalibration280_limits_419430400(t *testing.T) {
// TODO(maruel): Reverse the equation to overflow 419430400 // TODO(maruel): Reverse the equation to overflow 419430400
} }
@ -684,7 +735,7 @@ func Example() {
log.Fatalf("failed to open I²C: %v", err) log.Fatalf("failed to open I²C: %v", err)
} }
defer bus.Close() defer bus.Close()
dev, err := NewI2C(bus, nil) dev, err := NewI2C(bus, 0x76, nil)
if err != nil { if err != nil {
log.Fatalf("failed to initialize bme280: %v", err) log.Fatalf("failed to initialize bme280: %v", err)
} }
@ -721,36 +772,51 @@ func TestOversampling(t *testing.T) {
func TestStandby(t *testing.T) { func TestStandby(t *testing.T) {
data := []struct { data := []struct {
isBME bool
d time.Duration d time.Duration
s standby s standby
}{ }{
{0, s500us}, {true, 0, s500us},
{time.Millisecond, s500us}, {true, time.Millisecond, s500us},
{10 * time.Millisecond, s10ms}, {true, 10 * time.Millisecond, s10msBME},
{20 * time.Millisecond, s20ms}, {true, 20 * time.Millisecond, s20msBME},
{62500 * time.Microsecond, s62ms}, {true, 62500 * time.Microsecond, s62ms},
{125 * time.Millisecond, s125ms}, {true, 125 * time.Millisecond, s125ms},
{250 * time.Millisecond, s250ms}, {true, 250 * time.Millisecond, s250ms},
{500 * time.Millisecond, s500ms}, {true, 500 * time.Millisecond, s500ms},
{time.Second, s1s}, {true, time.Second, s1s},
{time.Minute, s1s}, {true, 2 * time.Second, s1s},
{true, 4 * time.Second, s1s},
{true, time.Minute, s1s},
{false, 0, s500us},
{false, time.Millisecond, s500us},
{false, 10 * time.Millisecond, s62ms},
{false, 20 * time.Millisecond, s62ms},
{false, 62500 * time.Microsecond, s62ms},
{false, 125 * time.Millisecond, s125ms},
{false, 250 * time.Millisecond, s250ms},
{false, 500 * time.Millisecond, s500ms},
{false, time.Second, s1s},
{false, 2 * time.Second, s2sBMP},
{false, 4 * time.Second, s4sBMP},
{false, time.Minute, s4sBMP},
} }
for i, line := range data { for i, line := range data {
if s := chooseStandby(line.d); s != line.s { if s := chooseStandby(line.isBME, line.d); s != line.s {
t.Fatalf("#%d chooseStandby(%s) = %d != %d", i, line.d, s, line.s) t.Fatalf("#%d chooseStandby(%s) = %d != %d", i, line.d, s, line.s)
} }
} }
} }
func TestCalibration_compensatePressureInt64(t *testing.T) { func TestCalibration280_compensatePressureInt64(t *testing.T) {
c := calibration{} c := calibration280{}
if x := c.compensatePressureInt64(0, 0); x != 0 { if x := c.compensatePressureInt64(0, 0); x != 0 {
t.Fatal(x) t.Fatal(x)
} }
} }
func TestCalibration_compensateHumidityInt(t *testing.T) { func TestCalibration280_compensateHumidityInt(t *testing.T) {
c := calibration{ c := calibration280{
h1: 0xFF, h1: 0xFF,
} }
if x := c.compensateHumidityInt(0, 0); x != 0 { if x := c.compensateHumidityInt(0, 0); x != 0 {
@ -777,7 +843,7 @@ func floatEqual(a, b float32) bool {
// raw has 20 bits of resolution. // raw has 20 bits of resolution.
// //
// BUG(maruel): Output is incorrect. // BUG(maruel): Output is incorrect.
func (c *calibration) compensatePressureInt32(raw, tFine int32) uint32 { func (c *calibration280) compensatePressureInt32(raw, tFine int32) uint32 {
x := tFine>>1 - 64000 x := tFine>>1 - 64000
y := (((x >> 2) * (x >> 2)) >> 11) * int32(c.p6) y := (((x >> 2) * (x >> 2)) >> 11) * int32(c.p6)
y += (x * int32(c.p5)) << 1 y += (x * int32(c.p5)) << 1
@ -798,15 +864,13 @@ func (c *calibration) compensatePressureInt32(raw, tFine int32) uint32 {
return uint32(int32(p) + ((x + y + int32(c.p7)) >> 4)) return uint32(int32(p) + ((x + y + int32(c.p7)) >> 4))
} }
var _ devices.Environmental = &Dev{}
// Page 49 // Page 49
// compensateTempFloat returns temperature in °C. Output value of "51.23" // compensateTempFloat returns temperature in °C. Output value of "51.23"
// equals 51.23 °C. // equals 51.23 °C.
// //
// raw has 20 bits of resolution. // raw has 20 bits of resolution.
func (c *calibration) compensateTempFloat(raw int32) (float32, int32) { func (c *calibration280) compensateTempFloat(raw int32) (float32, int32) {
x := (float64(raw)/16384. - float64(c.t1)/1024.) * float64(c.t2) x := (float64(raw)/16384. - float64(c.t1)/1024.) * float64(c.t2)
y := (float64(raw)/131072. - float64(c.t1)/8192.) * float64(c.t3) y := (float64(raw)/131072. - float64(c.t1)/8192.) * float64(c.t3)
tFine := int32(x + y) tFine := int32(x + y)
@ -817,7 +881,7 @@ func (c *calibration) compensateTempFloat(raw int32) (float32, int32) {
// equals 96386.2 Pa = 963.862 hPa. // equals 96386.2 Pa = 963.862 hPa.
// //
// raw has 20 bits of resolution. // raw has 20 bits of resolution.
func (c *calibration) compensatePressureFloat(raw, tFine int32) float32 { func (c *calibration280) compensatePressureFloat(raw, tFine int32) float32 {
x := float64(tFine)*0.5 - 64000. x := float64(tFine)*0.5 - 64000.
y := x * x * float64(c.p6) / 32768. y := x * x * float64(c.p6) / 32768.
y += x * float64(c.p5) * 2. y += x * float64(c.p5) * 2.
@ -838,7 +902,7 @@ func (c *calibration) compensatePressureFloat(raw, tFine int32) float32 {
// represents 46.332 %rH. // represents 46.332 %rH.
// //
// raw has 16 bits of resolution. // raw has 16 bits of resolution.
func (c *calibration) compensateHumidityFloat(raw, tFine int32) float32 { func (c *calibration280) compensateHumidityFloat(raw, tFine int32) float32 {
h := float64(tFine - 76800) h := float64(tFine - 76800)
h = (float64(raw) - float64(c.h4)*64. + float64(c.h5)/16384.*h) * float64(c.h2) / 65536. * (1. + float64(c.h6)/67108864.*h*(1.+float64(c.h3)/67108864.*h)) h = (float64(raw) - float64(c.h4)*64. + float64(c.h5)/16384.*h) * float64(c.h2) / 65536. * (1. + float64(c.h6)/67108864.*h*(1.+float64(c.h3)/67108864.*h))
h *= 1. - float64(c.h1)*h/524288. h *= 1. - float64(c.h1)*h/524288.

@ -0,0 +1,7 @@
# 'bmx280' smoke test
Verifies that two BME280/BMP280, 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.

@ -2,10 +2,10 @@
// Use of this source code is governed under the Apache License, Version 2.0 // Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file. // that can be found in the LICENSE file.
// Package bme280smoketest is leveraged by periph-smoketest to verify that two // Package bmx280smoketest is leveraged by periph-smoketest to verify that two
// BME280, one over I²C, one over SPI, read roughly the same temperature, // BME280/BMP280, one over I²C, one over SPI, read roughly the same temperature,
// humidity and pressure. // humidity and pressure.
package bme280smoketest package bmx280smoketest
import ( import (
"flag" "flag"
@ -18,7 +18,7 @@ import (
"periph.io/x/periph/conn/spi/spireg" "periph.io/x/periph/conn/spi/spireg"
"periph.io/x/periph/conn/spi/spitest" "periph.io/x/periph/conn/spi/spitest"
"periph.io/x/periph/devices" "periph.io/x/periph/devices"
"periph.io/x/periph/devices/bme280" "periph.io/x/periph/devices/bmxx80"
) )
// SmokeTest is imported by periph-smoketest. // SmokeTest is imported by periph-smoketest.
@ -27,18 +27,19 @@ type SmokeTest struct {
// Name implements the SmokeTest interface. // Name implements the SmokeTest interface.
func (s *SmokeTest) Name() string { func (s *SmokeTest) Name() string {
return "bme280" return "bmx280"
} }
// Description implements the SmokeTest interface. // Description implements the SmokeTest interface.
func (s *SmokeTest) Description() string { func (s *SmokeTest) Description() string {
return "Tests BME280 over I²C and SPI" return "Tests BMx280 over I²C and SPI"
} }
// Run implements the SmokeTest interface. // Run implements the SmokeTest interface.
func (s *SmokeTest) Run(args []string) (err error) { func (s *SmokeTest) Run(args []string) (err error) {
f := flag.NewFlagSet("buses", flag.ExitOnError) f := flag.NewFlagSet("buses", flag.ExitOnError)
i2cID := f.String("i2c", "", "I²C bus to use") i2cID := f.String("i2c", "", "I²C bus to use")
i2cAddr := f.Uint("ia", 0x76, "I²C bus address to use; either 0x76 (BMx280, the default) or 0x77 (BMP180)")
spiID := f.String("spi", "", "SPI port to use") spiID := f.String("spi", "", "SPI port to use")
record := f.Bool("r", false, "record operation (for playback unit testing)") record := f.Bool("r", false, "record operation (for playback unit testing)")
f.Parse(args) f.Parse(args)
@ -63,12 +64,12 @@ func (s *SmokeTest) Run(args []string) (err error) {
} }
}() }()
if !*record { if !*record {
return run(i2cBus, spiPort) return run(i2cBus, uint16(*i2cAddr), spiPort)
} }
i2cRecorder := i2ctest.Record{Bus: i2cBus} i2cRecorder := i2ctest.Record{Bus: i2cBus}
spiRecorder := spitest.Record{Port: spiPort} spiRecorder := spitest.Record{Port: spiPort}
err = run(&i2cRecorder, &spiRecorder) err = run(&i2cRecorder, uint16(*i2cAddr), &spiRecorder)
if len(i2cRecorder.Ops) != 0 { if len(i2cRecorder.Ops) != 0 {
fmt.Printf("I²C recorder Addr: 0x%02X\n", i2cRecorder.Ops[0].Addr) fmt.Printf("I²C recorder Addr: 0x%02X\n", i2cRecorder.Ops[0].Addr)
} else { } else {
@ -119,15 +120,15 @@ func (s *SmokeTest) Run(args []string) (err error) {
return err return err
} }
func run(i2cBus i2c.Bus, spiPort spi.PortCloser) (err error) { func run(i2cBus i2c.Bus, i2cAddr uint16, spiPort spi.PortCloser) (err error) {
opts := &bme280.Opts{ opts := &bmxx80.Opts{
Temperature: bme280.O16x, Temperature: bmxx80.O16x,
Pressure: bme280.O16x, Pressure: bmxx80.O16x,
Humidity: bme280.O16x, Humidity: bmxx80.O16x,
Filter: bme280.NoFilter, Filter: bmxx80.NoFilter,
} }
i2cDev, err2 := bme280.NewI2C(i2cBus, opts) i2cDev, err2 := bmxx80.NewI2C(i2cBus, i2cAddr, opts)
if err2 != nil { if err2 != nil {
return err2 return err2
} }
@ -137,7 +138,7 @@ func run(i2cBus i2c.Bus, spiPort spi.PortCloser) (err error) {
} }
}() }()
spiDev, err2 := bme280.NewSPI(spiPort, opts) spiDev, err2 := bmxx80.NewSPI(spiPort, opts)
if err2 != nil { if err2 != nil {
return err2 return err2
} }

@ -0,0 +1,519 @@
// Copyright 2016 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 bmxx80 controls a Bosch BMP180/BME280/BMP280 device over I²C, or SPI
// for the BMx280.
//
// BMx280
//
// https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME280_DS001-11.pdf
//
// https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BMP280-DS001-18.pdf
//
// https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BMP180-DS000-121.pdf
//
// The font the official datasheet on page 15 is hard to read, a copy with
// readable text can be found here:
//
// https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf
//
// Notes on the BMP180 datasheet
//
// The results of the calculations in the algorithm on page 15 are partly
// wrong. It looks like the original authors used non-integer calculations and
// some nubers were rounded. Take the results of the calculations with a grain
// of salt.
package bmxx80
import (
"encoding/binary"
"errors"
"fmt"
"log"
"strings"
"sync"
"time"
"periph.io/x/periph/conn"
"periph.io/x/periph/conn/i2c"
"periph.io/x/periph/conn/mmr"
"periph.io/x/periph/conn/spi"
"periph.io/x/periph/devices"
)
// Oversampling affects how much time is taken to measure each of temperature,
// pressure and humidity.
//
// Using high oversampling and low standby results in highest power
// consumption, but this is still below 1mA so we generally don't care.
type Oversampling uint8
// Possible oversampling values.
//
// The higher the more time and power it takes to take a measurement. Even at
// 16x for all 3 sensors, it is less than 100ms albeit increased power
// consumption may increase the temperature reading.
const (
Off Oversampling = 0
O1x Oversampling = 1
O2x Oversampling = 2
O4x Oversampling = 3
O8x Oversampling = 4
O16x Oversampling = 5
)
const oversamplingName = "Off1x2x4x8x16x"
var oversamplingIndex = [...]uint8{0, 3, 5, 7, 9, 11, 14}
func (o Oversampling) String() string {
if o >= Oversampling(len(oversamplingIndex)-1) {
return fmt.Sprintf("Oversampling(%d)", o)
}
return oversamplingName[oversamplingIndex[o]:oversamplingIndex[o+1]]
}
func (o Oversampling) asValue() int {
switch o {
case O1x:
return 1
case O2x:
return 2
case O4x:
return 4
case O8x:
return 8
case O16x:
return 16
default:
return 0
}
}
func (o Oversampling) to180() uint8 {
switch o {
default:
fallthrough
case Off, O1x:
return 0
case O2x:
return 1
case O4x:
return 2
case O8x, O16x:
return 3
}
}
// Filter specifies the internal IIR filter to get steadier measurements.
//
// Oversampling will get better measurements than filtering but at a larger
// power consumption cost, which may slightly affect temperature measurement.
type Filter uint8
// Possible filtering values.
//
// The higher the filter, the slower the value converges but the more stable
// the measurement is.
const (
NoFilter Filter = 0
F2 Filter = 1
F4 Filter = 2
F8 Filter = 3
F16 Filter = 4
)
// Dev is a handle to an initialized BMxx80 device.
//
// The actual device type was auto detected.
type Dev struct {
d conn.Conn
isSPI bool
is280 bool
isBME bool
opts Opts
measDelay time.Duration
name string
os uint8
cal180 calibration180
cal280 calibration280
mu sync.Mutex
stop chan struct{}
wg sync.WaitGroup
}
func (d *Dev) String() string {
// d.dev.Conn
return fmt.Sprintf("%s{%s}", d.name, d.d)
}
// Sense requests a one time measurement as °C, kPa and % of relative humidity.
//
// The very first measurements may be of poor quality.
func (d *Dev) Sense(env *devices.Environment) error {
d.mu.Lock()
defer d.mu.Unlock()
if d.stop != nil {
return d.wrap(errors.New("already sensing continuously"))
}
if d.is280 {
err := d.writeCommands([]byte{
// ctrl_meas
0xF4, byte(d.opts.Temperature)<<5 | byte(d.opts.Pressure)<<2 | byte(forced),
})
if err != nil {
return d.wrap(err)
}
time.Sleep(d.measDelay)
for idle := false; !idle; {
if idle, err = d.isIdle280(); err != nil {
return d.wrap(err)
}
}
return d.sense280(env)
}
return d.sense180(env)
}
// SenseContinuous returns measurements as °C, kPa and % of relative humidity
// on a continuous basis.
//
// The application must call Halt() to stop the sensing when done to stop the
// sensor and close the channel.
//
// It's the responsibility of the caller to retrieve the values from the
// channel as fast as possible, otherwise the interval may not be respected.
func (d *Dev) SenseContinuous(interval time.Duration) (<-chan devices.Environment, error) {
d.mu.Lock()
defer d.mu.Unlock()
if d.stop != nil {
// Don't send the stop command to the device.
close(d.stop)
d.stop = nil
d.wg.Wait()
}
if d.is280 {
s := chooseStandby(d.isBME, interval-d.measDelay)
err := d.writeCommands([]byte{
// config
0xF5, byte(s)<<5 | byte(d.opts.Filter)<<2,
// ctrl_meas
0xF4, byte(d.opts.Temperature)<<5 | byte(d.opts.Pressure)<<2 | byte(normal),
})
if err != nil {
return nil, d.wrap(err)
}
}
sensing := make(chan devices.Environment)
d.stop = make(chan struct{})
d.wg.Add(1)
go func() {
defer d.wg.Done()
defer close(sensing)
d.sensingContinuous(interval, sensing, d.stop)
}()
return sensing, nil
}
// Halt stops the BMxx80 from acquiring measurements as initiated by
// SenseContinuous().
//
// It is recommended to call this function before terminating the process to
// reduce idle power usage and a goroutine leak.
func (d *Dev) Halt() error {
d.mu.Lock()
defer d.mu.Unlock()
if d.stop == nil {
return nil
}
close(d.stop)
d.stop = nil
d.wg.Wait()
if d.is280 {
// Page 27 (for register) and 12~13 section 3.3.
return d.writeCommands([]byte{
// config
0xF5, byte(s1s)<<5 | byte(NoFilter)<<2,
// ctrl_meas
0xF4, byte(d.opts.Temperature)<<5 | byte(d.opts.Pressure)<<2 | byte(sleep),
})
}
return nil
}
// Opts is optional options to pass to the constructor.
//
// Recommended (and default) values are O4x for oversampling.
//
// Recommended sensing settings as per the datasheet:
//
// → Weather monitoring: manual sampling once per minute, all sensors O1x.
// Power consumption: 0.16µA, filter NoFilter. RMS noise: 3.3Pa / 30cm, 0.07%RH.
//
// → Humidity sensing: manual sampling once per second, pressure Off, humidity
// and temperature O1X, filter NoFilter. Power consumption: 2.9µA, 0.07%RH.
//
// → Indoor navigation: continuous sampling at 40ms with filter F16, pressure
// O16x, temperature O2x, humidity O1x, filter F16. Power consumption 633µA.
// RMS noise: 0.2Pa / 1.7cm.
//
// → Gaming: continuous sampling at 40ms with filter F16, pressure O4x,
// temperature O1x, humidity Off, filter F16. Power consumption 581µA. RMS
// noise: 0.3Pa / 2.5cm.
//
// See the datasheet for more details about the trade offs.
type Opts struct {
// Temperature can only be oversampled on BME280/BMP280.
//
// Temperature must be measured for pressure and humidity to be measured.
Temperature Oversampling
// Pressure can be oversampled up to 8x on BMP180 and 16x on BME280/BMP280.
Pressure Oversampling
// Humidity sensing is only supported on BME280. The value is ignored on other
// devices.
Humidity Oversampling
// Filter is only used while using SenseContinuous() and is only supported on
// BMx280.
Filter Filter
}
func (o *Opts) delayTypical280() time.Duration {
// Page 51.
µs := 1000
if o.Temperature != Off {
µs += 2000 * o.Temperature.asValue()
}
if o.Pressure != Off {
µs += 2000*o.Pressure.asValue() + 500
}
if o.Humidity != Off {
µs += 2000*o.Humidity.asValue() + 500
}
return time.Microsecond * time.Duration(µs)
}
// NewI2C returns an object that communicates over I²C to BMP180/BME280/BMP280
// environmental sensor.
//
// The address must be 0x76 or 0x77. BMP180 uses 0x77. BME280/BMP280 default to
// 0x76 and can optionally use 0x77. The value used depends on HW
// configuration of the sensor's SDO pin.
//
// It is recommended to call Halt() when done with the device so it stops
// sampling.
func NewI2C(b i2c.Bus, addr uint16, opts *Opts) (*Dev, error) {
switch addr {
case 0x76, 0x77:
default:
return nil, errors.New("bmxx80: given address not supported by device")
}
d := &Dev{d: &i2c.Dev{Bus: b, Addr: addr}, isSPI: false}
if err := d.makeDev(opts); err != nil {
return nil, err
}
return d, nil
}
// NewSPI returns an object that communicates over SPI to BME280 environmental
// sensor.
//
// It is recommended to call Halt() when done with the device so it stops
// sampling.
//
// When using SPI, the CS line must be used.
func NewSPI(p spi.Port, opts *Opts) (*Dev, error) {
// It works both in Mode0 and Mode3.
c, err := p.Connect(10000000, spi.Mode3, 8)
if err != nil {
return nil, fmt.Errorf("bmxx80: %v", err)
}
d := &Dev{d: c, isSPI: true}
if err := d.makeDev(opts); err != nil {
return nil, err
}
return d, nil
}
//
func (d *Dev) makeDev(opts *Opts) error {
if opts == nil {
opts = &defaults
}
d.opts = *opts
d.measDelay = d.opts.delayTypical280()
// The device starts in 2ms as per datasheet. No need to wait for boot to be
// finished.
var chipID [1]byte
// Read register 0xD0 to read the chip id.
if err := d.readReg(0xD0, chipID[:]); err != nil {
return err
}
switch chipID[0] {
case 0x55:
d.name = "BMP180"
d.os = opts.Pressure.to180()
case 0x58:
d.name = "BMP280"
d.is280 = true
d.opts.Humidity = Off
case 0x60:
d.name = "BME280"
d.is280 = true
d.isBME = true
default:
return fmt.Errorf("bmxx80: unexpected chip id %x", chipID[0])
}
if d.is280 && opts.Temperature == Off {
// Ignore the value for BMP180, since it's not controllable.
return d.wrap(errors.New("temperature measurement is required, use at least O1x"))
}
if d.is280 {
// TODO(maruel): We may want to wait for isIdle280().
// Read calibration data t1~3, p1~9, 8bits padding, h1.
var tph [0xA2 - 0x88]byte
if err := d.readReg(0x88, tph[:]); err != nil {
return err
}
// Read calibration data h2~6
var h [0xE8 - 0xE1]byte
if d.isBME {
if err := d.readReg(0xE1, h[:]); err != nil {
return err
}
}
d.cal280 = newCalibration(tph[:], h[:])
var b []byte
if d.isBME {
b = []byte{
// ctrl_meas; put it to sleep otherwise the config update may be
// ignored. This is really just in case the device was somehow put
// into normal but was not Halt'ed.
0xF4, byte(d.opts.Temperature)<<5 | byte(d.opts.Pressure)<<2 | byte(sleep),
// ctrl_hum
0xF2, byte(d.opts.Humidity),
// config
0xF5, byte(s1s)<<5 | byte(NoFilter)<<2,
// As per page 25, ctrl_meas must be re-written last.
0xF4, byte(d.opts.Temperature)<<5 | byte(d.opts.Pressure)<<2 | byte(sleep),
}
} else {
// BMP280 doesn't have humidity to control.
b = []byte{
// ctrl_meas; put it to sleep otherwise the config update may be
// ignored. This is really just in case the device was somehow put
// into normal but was not Halt'ed.
0xF4, byte(d.opts.Temperature)<<5 | byte(d.opts.Pressure)<<2 | byte(sleep),
// config
0xF5, byte(s1s)<<5 | byte(NoFilter)<<2,
// As per page 25, ctrl_meas must be re-written last.
0xF4, byte(d.opts.Temperature)<<5 | byte(d.opts.Pressure)<<2 | byte(sleep),
}
}
if err := d.writeCommands(b); err != nil {
return err
}
return nil
}
// Read calibration data.
dev := mmr.Dev8{Conn: d.d, Order: binary.BigEndian}
if err := dev.ReadStruct(0xAA, &d.cal180); err != nil {
return d.wrap(err)
}
if !d.cal180.isValid() {
return d.wrap(errors.New("calibration data is invalid"))
}
return nil
}
func (d *Dev) sensingContinuous(interval time.Duration, sensing chan<- devices.Environment, stop <-chan struct{}) {
t := time.NewTicker(interval)
defer t.Stop()
var err error
for {
// Do one initial sensing right away.
var e devices.Environment
d.mu.Lock()
if d.is280 {
err = d.sense280(&e)
} else {
err = d.sense180(&e)
}
d.mu.Unlock()
if err != nil {
log.Printf("%s: failed to sense: %v", d, err)
return
}
select {
case sensing <- e:
case <-stop:
return
}
select {
case <-stop:
return
case <-t.C:
}
}
}
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 d.wrap(err)
}
copy(b, read[1:])
return nil
}
if err := d.d.Tx([]byte{reg}, b); err != nil {
return d.wrap(err)
}
return nil
}
// writeCommands writes a command to the device.
//
// Warning: b may be modified!
func (d *Dev) writeCommands(b []byte) error {
if d.isSPI {
// Page 33; set RW bit 7 to 0.
for i := 0; i < len(b); i += 2 {
b[i] &^= 0x80
}
}
if err := d.d.Tx(b, nil); err != nil {
return d.wrap(err)
}
return nil
}
func (d *Dev) wrap(err error) error {
return fmt.Errorf("%s: %v", strings.ToLower(d.name), err)
}
var defaults = Opts{
Temperature: O4x,
Pressure: O4x,
Humidity: O4x,
}
var _ devices.Environmental = &Dev{}
var _ devices.Device = &Dev{}
var _ fmt.Stringer = &Dev{}
Loading…
Cancel
Save