diff --git a/bmp180/bmp180.go b/bmp180/bmp180.go new file mode 100644 index 0000000..98e2dc6 --- /dev/null +++ b/bmp180/bmp180.go @@ -0,0 +1,180 @@ +// Package bmp180 provides a driver for the BMP180 digital pressure sensor +// by Bosch +// +// Datasheet: +// https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf +// +package bmp180 + +import ( + "time" + + "machine" +) + +// BMP180OversamplingMode is the oversampling ratio of the pressure measurement. +type OversamplingMode uint + +// calibrationCoefficients reads at startup and stores the calibration coefficients +type calibrationCoefficients struct { + ac1 int16 + ac2 int16 + ac3 int16 + ac4 uint16 + ac5 uint16 + ac6 uint16 + b1 int16 + b2 int16 + mb int16 + mc int16 + md int16 +} + +// Device wraps an I2C connection to a BMP180 device. +type Device struct { + bus machine.I2C + mode OversamplingMode + calibrationCoefficients calibrationCoefficients +} + +// New creates a new BMP180 connection. The I2C bus must already be +// configured. +// +// This function only creates the Device object, it does not touch the device. +func New(bus machine.I2C) Device { + return Device{ + bus: bus, + mode: ULTRAHIGHRESOLUTION, + } +} + +// Connected returns whether a BMP180 has been found. +// It does a "who am I" request and checks the response. +func (d *Device) Connected() bool { + data := []byte{0} + d.bus.ReadRegister(Address, WHO_AM_I, data) + return data[0] == CHIP_ID +} + +// Configure sets up the device for communication and +// read the calibration coefficientes. +func (d *Device) Configure() { + data := make([]byte, 22) + err := d.bus.ReadRegister(Address, AC1_MSB, data) + if err != nil { + return + } + d.calibrationCoefficients.ac1 = readInt(data[0], data[1]) + d.calibrationCoefficients.ac2 = readInt(data[2], data[3]) + d.calibrationCoefficients.ac3 = readInt(data[4], data[5]) + d.calibrationCoefficients.ac4 = readUint(data[6], data[7]) + d.calibrationCoefficients.ac5 = readUint(data[8], data[9]) + d.calibrationCoefficients.ac6 = readUint(data[10], data[11]) + d.calibrationCoefficients.b1 = readInt(data[12], data[13]) + d.calibrationCoefficients.b2 = readInt(data[14], data[15]) + d.calibrationCoefficients.mb = readInt(data[16], data[17]) + d.calibrationCoefficients.mc = readInt(data[18], data[19]) + d.calibrationCoefficients.md = readInt(data[20], data[21]) +} + +// Temperature returns the temperature in celsius milli degrees (ºC/1000) +func (d *Device) Temperature() (temperature int32, err error) { + rawTemp, err := d.rawTemp() + if err != nil { + return + } + b5 := d.calculateB5(rawTemp) + t := (b5 + 8) >> 4 + return 100*t, nil +} + +// Pressure returns the pressure in milli pascals (mPa) +func (d *Device) Pressure() (pressure int32, err error) { + rawTemp, err := d.rawTemp() + if err != nil { + return + } + rawPressure, err := d.rawPressure(d.mode) + if err != nil { + return + } + b5 := d.calculateB5(rawTemp) + b6 := b5 - 4000 + x1 := (int32(d.calibrationCoefficients.b2) * (b6 * b6 >> 12)) >> 11 + x2 := (int32(d.calibrationCoefficients.ac2) * b6) >> 11 + x3 := x1 + x2 + b3 := (((int32(d.calibrationCoefficients.ac1)*4 + x3) << uint(d.mode)) + 2) >> 2 + x1 = (int32(d.calibrationCoefficients.ac3) * b6) >> 13 + x2 = (int32(d.calibrationCoefficients.b1) * ((b6 * b6) >> 12)) >> 16 + x3 = ((x1 + x2) + 2) >> 2 + b4 := (uint32(d.calibrationCoefficients.ac4) * uint32(x3+32768)) >> 15 + b7 := uint32(rawPressure-b3) * (50000 >> uint(d.mode)) + var p int32 + if b7 < 0x80000000 { + p = int32((b7 << 1) / b4) + } else { + p = int32((b7 / b4) << 1) + } + x1 = (p >> 8) * (p >> 8) + x1 = (x1 * 3038) >> 16 + x2 = (-7357 * p) >> 16 + return 1000*(p + ((x1 + x2 + 3791) >> 4)), nil +} + +// rawTemp returns the sensor's raw values of the temperature +func (d *Device) rawTemp() (int16, error) { + d.bus.WriteRegister(Address, REG_CTRL, []byte{CMD_TEMP}) + time.Sleep(5 * time.Millisecond) + data := make([]byte, 2) + err := d.bus.ReadRegister(Address, REG_TEMP_MSB, data) + if err != nil { + return 0, err + } + return readInt(data[0], data[1]), nil +} + +// calculateB5 calculates intermediate value B5 as per page 15 of datasheet +func (d *Device) calculateB5(rawTemp int16) int32 { + x1 := (int32(rawTemp) - int32(d.calibrationCoefficients.ac6)) * int32(d.calibrationCoefficients.ac5) >> 15 + x2 := int32(d.calibrationCoefficients.mc) << 11 / (x1 + int32(d.calibrationCoefficients.md)) + return x1 + x2 +} + +// rawPressure returns the sensor's raw values of the pressure +func (d *Device) rawPressure(mode OversamplingMode) (int32, error) { + d.bus.WriteRegister(Address, REG_CTRL, []byte{CMD_PRESSURE + byte(mode<<6)}) + time.Sleep(pauseForReading(mode)) + data := make([]byte, 3) + err := d.bus.ReadRegister(Address, REG_PRESSURE_MSB, data) + if err != nil { + return 0, err + } + rawPressure := int32((uint32(data[0])<<16 + uint32(data[1])<<8 + uint32(data[2])) >> (8 - uint(mode))) + return rawPressure, nil +} + +// pauseForReading returns the pause duration depending on the sampling mode +func pauseForReading(mode OversamplingMode) time.Duration { + var d time.Duration + switch mode { + case ULTRALOWPOWER: + d = 5 * time.Millisecond + case STANDARD: + d = 8 * time.Millisecond + case HIGHRESOLUTION: + d = 14 * time.Millisecond + case ULTRAHIGHRESOLUTION: + d = 26 * time.Millisecond + } + return d +} + +// readInt converts two bytes to int16 +func readInt(msb byte, lsb byte) int16 { + return int16(uint16(msb)<<8 | uint16(lsb)) +} + +// readUint converts two bytes to uint16 +func readUint(msb byte, lsb byte) uint16 { + return (uint16(msb) << 8) | uint16(lsb) +} diff --git a/bmp180/registers.go b/bmp180/registers.go new file mode 100644 index 0000000..e108ca1 --- /dev/null +++ b/bmp180/registers.go @@ -0,0 +1,30 @@ +package bmp180 + +// Constants/addresses used for I2C. + +// The I2C address which this device listens to. +const Address = 0x77 + +// Registers. Names, addresses and comments copied from the datasheet. +const ( + AC1_MSB = 0xAA // Calibration coefficients start at 0xAA ends at 0xBF + CMD_TEMP = 0x2E + CMD_PRESSURE = 0x34 + REG_CTRL = 0xF4 + REG_TEMP_MSB = 0xF6 + REG_PRESSURE_MSB = 0xF6 + + WHO_AM_I = 0xD0 + CHIP_ID = 0x55 +) + +const ( + // ULTRALOWPOWER is the lowest oversampling mode of the pressure measurement. + ULTRALOWPOWER OversamplingMode = iota + // BSTANDARD is the standard oversampling mode of the pressure measurement. + STANDARD + // HIGHRESOLUTION is a high oversampling mode of the pressure measurement. + HIGHRESOLUTION + // ULTRAHIGHRESOLUTION is the highest oversampling mode of the pressure measurement. + ULTRAHIGHRESOLUTION +) diff --git a/examples/bmp180/main.go b/examples/bmp180/main.go new file mode 100644 index 0000000..61da0af --- /dev/null +++ b/examples/bmp180/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "time" + + "github.com/tinygo-org/drivers/bmp180" + "machine" +) + +func main() { + machine.I2C0.Configure(machine.I2CConfig{}) + sensor := bmp180.New(machine.I2C0) + sensor.Configure() + + connected := sensor.Connected() + if !connected { + println("BMP180 not detected") + return + } + println("BMP180 detected") + + for { + temp, _ := sensor.Temperature() + println("Temperature:", float32(temp)/1000, "ºC") + + pressure, _ := sensor.Pressure() + println("Pressure", float32(pressure)/100000, "hPa") + + time.Sleep(2 * time.Second) + } +}