mirror of https://github.com/periph/devices
Add driver for Bosch BMP180 temp/pressure sensor (#132)
parent
b5aa4c03f3
commit
61c1b2c139
@ -0,0 +1,247 @@
|
||||
// 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
|
||||
//
|
||||
// The official data sheet can be found here:
|
||||
//
|
||||
// https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BMP180-DS000-121.pdf
|
||||
//
|
||||
// The font the official datasheet on page 15 is unreadable, 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"
|
||||
"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
|
||||
|
||||
const (
|
||||
// Possible oversampling values
|
||||
No Oversampling = 0
|
||||
O2x Oversampling = 1
|
||||
O4x Oversampling = 2
|
||||
O8x Oversampling = 3
|
||||
|
||||
// bit offsets in regCtrlMeas
|
||||
ctrlMeasurementControlShift = 0
|
||||
ctrlStartConversionShift = 5
|
||||
ctrlOversamplingShift = 6
|
||||
|
||||
// commands
|
||||
cmdStartTempConv uint8 = (1 << ctrlStartConversionShift) | (0x0E << ctrlMeasurementControlShift)
|
||||
cmdStartPressureConv uint8 = (1 << ctrlStartConversionShift) | (0x14 << ctrlMeasurementControlShift)
|
||||
|
||||
chipAddress = 0x77 // the address of a BMP180 is fixed at 0x77
|
||||
chipID = 0x55 // contents of the ID register, always 0x55
|
||||
|
||||
// registers
|
||||
regChipID = 0xD0 // register contains the chip id
|
||||
regCalibrationStart = 0xAA // first calibration register address
|
||||
regSoftReset = 0xE0 // soft reset register
|
||||
regCtrlMeas = 0xF4 // control measurement
|
||||
regOut = 0xF6 // 3 bytes register with measurement data
|
||||
|
||||
softResetValue = 0xB6
|
||||
|
||||
tempConvTime = 4500 * time.Microsecond // maximum conversion time for temperature
|
||||
)
|
||||
|
||||
// maximum conversion time for pressure
|
||||
var pressureConvTime = [...]time.Duration{
|
||||
4500 * time.Microsecond,
|
||||
7500 * time.Microsecond,
|
||||
13500 * time.Microsecond,
|
||||
25500 * time.Microsecond,
|
||||
}
|
||||
|
||||
// Dev is a handle to a bmp180.
|
||||
type Dev struct {
|
||||
dev mmr.Dev8
|
||||
cal calibration
|
||||
os Oversampling
|
||||
}
|
||||
|
||||
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 {
|
||||
// start conversion for temperature
|
||||
if err := d.dev.WriteUint8(regCtrlMeas, cmdStartTempConv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(tempConvTime)
|
||||
|
||||
// read value
|
||||
ut, err := d.dev.ReadUint16(regOut)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
temp := d.cal.compensateTemp(ut)
|
||||
|
||||
// start conversion for pressure
|
||||
cmd := cmdStartPressureConv
|
||||
cmd |= uint8(d.os) << ctrlOversamplingShift
|
||||
|
||||
if err := d.dev.WriteUint8(regCtrlMeas, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(pressureConvTime[d.os])
|
||||
|
||||
// read value
|
||||
var pressureBuf [3]byte
|
||||
if err := d.dev.ReadStruct(regOut, pressureBuf[:]); err != nil {
|
||||
return 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
|
||||
}
|
||||
|
||||
// Halt is a noop for the BMP180.
|
||||
func (d *Dev) Halt() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset issues a soft reset to the device.
|
||||
func (d *Dev) Reset() error {
|
||||
// issue soft reset to initialize device
|
||||
if err := d.dev.WriteUint8(regSoftReset, softResetValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// wait for restart
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// New returns an object that communicates over I²C to BMP180 environmental
|
||||
// sensor. The frequency for the bus can be up to 3.4MHz.
|
||||
func New(b i2c.Bus, os Oversampling) (d *Dev, err error) {
|
||||
bus := &i2c.Dev{Bus: b, Addr: chipAddress}
|
||||
d = &Dev{
|
||||
os: os,
|
||||
dev: mmr.Dev8{
|
||||
Conn: bus,
|
||||
Order: binary.BigEndian,
|
||||
},
|
||||
}
|
||||
|
||||
id, err := d.dev.ReadUint8(regChipID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if id != chipID {
|
||||
return nil, fmt.Errorf("bmp180: unexpected chip id 0x%x; is this a BMP180?", id)
|
||||
}
|
||||
|
||||
// read calibration data from internal EEPROM, 11 registers with two bytes each
|
||||
if err := d.dev.ReadStruct(regCalibrationStart, &d.cal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !d.cal.isValid() {
|
||||
return nil, errors.New("calibration data is invalid")
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
var _ devices.Environmental = &Dev{}
|
||||
var _ devices.Device = &Dev{}
|
||||
@ -0,0 +1,31 @@
|
||||
// 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
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCompensate(t *testing.T) {
|
||||
c := calibration{
|
||||
AC1: 408,
|
||||
AC2: -72,
|
||||
AC3: -14383,
|
||||
AC4: 32741,
|
||||
AC5: 32757,
|
||||
AC6: 23153,
|
||||
B1: 6190,
|
||||
B2: 4,
|
||||
MB: -32768,
|
||||
MC: -8711,
|
||||
MD: 2868,
|
||||
}
|
||||
|
||||
if temp := c.compensateTemp(27898); temp != 150 {
|
||||
t.Errorf("temperature is wrong, want %v, got %v", 150, temp)
|
||||
}
|
||||
|
||||
if pressure := c.compensatePressure(23843, 27898, 0); pressure != 69964 {
|
||||
t.Errorf("pressure is wrong, want %v, got %v", 69964, pressure)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue