From 1d09194bbc9e2f3f6cd269683f78ac4a55d0064a Mon Sep 17 00:00:00 2001 From: Nerdmeister <66128951+nerdmeister@users.noreply.github.com> Date: Sun, 21 Jun 2020 16:16:29 +0200 Subject: [PATCH] Added support for the Bosch BMP280 temperature and pressure sensor. (#158) * bmp280: Added support for the Bosch BMP280 temperature and pressure sensor. --- Makefile | 2 + README.md | 3 +- bmp280/bmp280.go | 243 ++++++++++++++++++++++++++++++++++++++++ bmp280/registers.go | 56 +++++++++ examples/bmp280/main.go | 44 ++++++++ 5 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 bmp280/bmp280.go create mode 100644 bmp280/registers.go create mode 100644 examples/bmp280/main.go diff --git a/Makefile b/Makefile index f9bae8b..bc48bd5 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,8 @@ smoke-test: @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp180/main.go @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp280/main.go + @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/sram/main.go @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/time/main.go diff --git a/README.md b/README.md index e5864e7..87262a6 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ func main() { ## Currently supported devices -The following 49 devices are supported. +The following 50 devices are supported. | Device Name | Interface Type | |----------|-------------| @@ -66,6 +66,7 @@ The following 49 devices are supported. | [BlinkM RGB LED](http://thingm.com/fileadmin/thingm/downloads/BlinkM_datasheet.pdf) | I2C | | [BME280 humidity/pressure sensor](https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf) | I2C | | [BMP180 barometer](https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf) | I2C | +| [BMP280 temperature/barometer](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf) | I2C | | [Buzzer](https://en.wikipedia.org/wiki/Buzzer#Piezoelectric) | GPIO | | [DS1307 real time clock](https://datasheets.maximintegrated.com/en/ds/DS1307.pdf) | I2C | | [DS3231 real time clock](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf) | I2C | diff --git a/bmp280/bmp280.go b/bmp280/bmp280.go new file mode 100644 index 0000000..73243ad --- /dev/null +++ b/bmp280/bmp280.go @@ -0,0 +1,243 @@ +package bmp280 + +import ( + "machine" + "time" +) + +// OversamplingMode is the oversampling ratio of the temperature or pressure measurement. +type Oversampling uint + +// Mode is the Power Mode. +type Mode uint + +// Standby is the inactive period between the reads when the sensor is in normal power mode. +type Standby uint + +// Filter unwanted changes in measurement caused by external (environmental) or internal changes (IC). +type Filter uint + +// Device wraps an I2C connection to a BMP280 device. +type Device struct { + bus machine.I2C + Address uint16 + cali calibrationCoefficients + Temperature Oversampling + Pressure Oversampling + Mode Mode + Standby Standby + Filter Filter +} + +type calibrationCoefficients struct { + // Temperature compensation + t1 uint16 + t2 int16 + t3 int16 + + // Pressure compensation + p1 uint16 + p2 int16 + p3 int16 + p4 int16 + p5 int16 + p6 int16 + p7 int16 + p8 int16 + p9 int16 +} + +// New creates a new BMP280 connection. The I2C bus must already be +// configured. +// +// This function only creates the Device object, it does not initialize the device. +// You must call Configure() first in order to use the device itself. +func New(bus machine.I2C) Device { + return Device{ + bus: bus, + Address: Address, + } +} + +// Connected returns whether a BMP280 has been found. +// It does a "who am I" request and checks the response. +func (d *Device) Connected() bool { + data := make([]byte, 1) + d.bus.ReadRegister(uint8(d.Address), REG_ID, data) + return data[0] == CHIP_ID +} + +// Reset preforms complete power-on-reset procedure. +// It is required to call Configure afterwards. +func (d *Device) Reset() { + d.bus.WriteRegister(uint8(d.Address), REG_RESET, []byte{CMD_RESET}) +} + +// Configure sets up the device for communication and +// read the calibration coefficients. +func (d *Device) Configure(standby Standby, filter Filter, temp Oversampling, pres Oversampling, mode Mode) { + d.Standby = standby + d.Filter = filter + d.Temperature = temp + d.Pressure = pres + d.Mode = mode + + // Write the configuration (standby, filter, spi 3 wire) + config := uint(d.Standby<<5) | uint(d.Filter<<2) | 0x00 + d.bus.WriteRegister(uint8(d.Address), REG_CONFIG, []byte{byte(config)}) + + // Write the control (temperature oversampling, pressure oversampling, + config = uint(d.Temperature<<5) | uint(d.Pressure<<2) | uint(d.Mode) + d.bus.WriteRegister(uint8(d.Address), REG_CTRL_MEAS, []byte{byte(config)}) + + // Read Calibration data + data := make([]byte, 24) + err := d.bus.ReadRegister(uint8(d.Address), REG_CALI, data) + if err != nil { + return + } + + // Datasheet: 3.11.2 Trimming parameter readout + d.cali.t1 = readUintLE(data[0], data[1]) + d.cali.t2 = readIntLE(data[2], data[3]) + d.cali.t3 = readIntLE(data[4], data[5]) + + d.cali.p1 = readUintLE(data[6], data[7]) + d.cali.p2 = readIntLE(data[8], data[9]) + d.cali.p3 = readIntLE(data[10], data[11]) + d.cali.p4 = readIntLE(data[12], data[13]) + d.cali.p5 = readIntLE(data[14], data[15]) + d.cali.p6 = readIntLE(data[16], data[17]) + d.cali.p7 = readIntLE(data[18], data[19]) + d.cali.p8 = readIntLE(data[20], data[21]) + d.cali.p9 = readIntLE(data[22], data[23]) +} + +// PrintCali prints the Calibration information. +func (d *Device) PrintCali() { + println("T1:", d.cali.t1) + println("T2:", d.cali.t2) + println("T3:", d.cali.t3) + + println("P1:", d.cali.p1) + println("P2:", d.cali.p2) + println("P3:", d.cali.p3) + println("P4:", d.cali.p4) + println("P5:", d.cali.p5) + println("P6:", d.cali.p6) + println("P7:", d.cali.p7) + println("P8:", d.cali.p8) + println("P9:", d.cali.p9, "\n") +} + +// ReadTemperature returns the temperature in celsius milli degrees (°C/1000). +func (d *Device) ReadTemperature() (temperature int32, err error) { + data, err := d.readData(REG_TEMP, 3) + if err != nil { + return + } + + rawTemp := convert3Bytes(data[0], data[1], data[2]) + + // Datasheet: 8.2 Compensation formula in 32 bit fixed point + // Temperature compensation + var1 := ((rawTemp >> 3) - int32(d.cali.t1<<1)) * int32(d.cali.t2) >> 11 + var2 := (((rawTemp >> 4) - int32(d.cali.t1)) * ((rawTemp >> 4) - int32(d.cali.t1)) >> 12) * + int32(d.cali.t3) >> 14 + + tFine := var1 + var2 + + // Convert from degrees to milli degrees by multiplying by 10. + // Will output 30250 milli degrees celsius for 30.25 degrees celsius + temperature = 10 * ((tFine*5 + 128) >> 8) + return +} + +// ReadPressure returns the pressure in milli pascals (mPa). +func (d *Device) ReadPressure() (pressure int32, err error) { + // First 3 bytes are Pressure, last 3 bytes are Temperature + data, err := d.readData(REG_PRES, 6) + if err != nil { + return + } + + rawTemp := convert3Bytes(data[3], data[4], data[5]) + + // Datasheet: 8.2 Compensation formula in 32 bit fixed point + // Calculate tFine (temperature), used for the Pressure compensation + var1 := ((rawTemp >> 3) - int32(d.cali.t1<<1)) * int32(d.cali.t2) >> 11 + var2 := (((rawTemp >> 4) - int32(d.cali.t1)) * ((rawTemp >> 4) - int32(d.cali.t1)) >> 12) * + int32(d.cali.t3) >> 14 + + tFine := var1 + var2 + + rawPres := convert3Bytes(data[0], data[1], data[2]) + + // Datasheet: 8.2 Compensation formula in 32 bit fixed point + // Pressure compensation + var1 = (tFine >> 1) - 64000 + var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * int32(d.cali.p6) + var2 = var2 + ((var1 * int32(d.cali.p5)) << 1) + var2 = (var2 >> 2) + (int32(d.cali.p4) << 16) + var1 = (((int32(d.cali.p3) * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) + + ((int32(d.cali.p2) * var1) >> 1)) >> 18 + var1 = ((32768 + var1) * int32(d.cali.p1)) >> 15 + + if var1 == 0 { + return 0, nil + } + + p := uint32(((1048576 - rawPres) - (var2 >> 12)) * 3125) + if p < 0x80000000 { + p = (p << 1) / uint32(var1) + } else { + p = (p / uint32(var1)) * 2 + } + + var1 = (int32(d.cali.p9) * int32(((p>>3)*(p>>3))>>13)) >> 12 + var2 = (int32(p>>2) * int32(d.cali.p8)) >> 13 + + return 1000 * (int32(p) + ((var1 + var2 + int32(d.cali.p7)) >> 4)), nil +} + +// readData reads n number of bytes of the specified register +func (d *Device) readData(register int, n int) ([]byte, error) { + // If not in normal mode, set the mode to FORCED mode, to prevent incorrect measurements + // After the measurement in FORCED mode, the sensor will return to SLEEP mode + if d.Mode != MODE_NORMAL { + config := uint(d.Temperature<<5) | uint(d.Pressure<<2) | uint(MODE_FORCED) + d.bus.WriteRegister(uint8(d.Address), REG_CTRL_MEAS, []byte{byte(config)}) + } + + // Check STATUS register, wait if data is not available yet + status := make([]byte, 1) + for d.bus.ReadRegister(uint8(d.Address), uint8(REG_STATUS), status[0:]); status[0] != 4 && status[0] != 0; d.bus.ReadRegister(uint8(d.Address), uint8(REG_STATUS), status[0:]) { + time.Sleep(time.Millisecond) + } + + // Read the requested register + data := make([]byte, n) + err := d.bus.ReadRegister(uint8(d.Address), uint8(register), data[:]) + return data, err +} + +// convert3Bytes converts three bytes to int32 +func convert3Bytes(msb byte, b1 byte, lsb byte) int32 { + return int32(((((uint32(msb) << 8) | uint32(b1)) << 8) | uint32(lsb)) >> 4) +} + +// readUint converts two bytes to uint16 +func readUint(msb byte, lsb byte) uint16 { + return (uint16(msb) << 8) | uint16(lsb) +} + +// readUintLE converts two little endian bytes to uint16 +func readUintLE(msb byte, lsb byte) uint16 { + temp := readUint(msb, lsb) + return (temp >> 8) | (temp << 8) +} + +// readIntLE converts two little endian bytes to int16 +func readIntLE(msb byte, lsb byte) int16 { + return int16(readUintLE(msb, lsb)) +} diff --git a/bmp280/registers.go b/bmp280/registers.go new file mode 100644 index 0000000..de523ab --- /dev/null +++ b/bmp280/registers.go @@ -0,0 +1,56 @@ +// Package bmp280 provides a driver for the BMP280 digital temperature & pressure sensor by Bosch. +// +// Datasheet: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf +package bmp280 + +// The I2C address which this device listens to. +const Address = 0x77 + +// Registers +const ( + REG_ID = 0xD0 // WHO_AM_I + REG_RESET = 0xE0 + REG_STATUS = 0xF3 + REG_CTRL_MEAS = 0xF4 + REG_CONFIG = 0xF5 + REG_TEMP = 0xFA + REG_PRES = 0xF7 + REG_CALI = 0x88 + + CHIP_ID = 0x58 + CMD_RESET = 0xB6 +) + +const ( + SAMPLING_SKIPPED Oversampling = iota + SAMPLING_1X + SAMPLING_2X + SAMPLING_4X + SAMPLING_8X + SAMPLING_16X +) + +const ( + MODE_SLEEP Mode = 0x00 + MODE_FORCED Mode = 0x01 + MODE_NORMAL Mode = 0x03 +) + +const ( + STANDBY_1MS Standby = iota + STANDBY_63MS + STANDBY_125MS + STANDBY_250MS + STANDBY_500MS + STANDBY_1000MS + STANDBY_2000MS + STANDBY_4000MS +) + +const ( + FILTER_OFF Filter = iota + FILTER_2X + FILTER_4X + FILTER_8X + FILTER_16X +) diff --git a/examples/bmp280/main.go b/examples/bmp280/main.go new file mode 100644 index 0000000..b88d2ba --- /dev/null +++ b/examples/bmp280/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + "machine" + "time" + "tinygo.org/x/drivers/bmp280" +) + +func main() { + time.Sleep(5 * time.Second) + + machine.I2C0.Configure(machine.I2CConfig{}) + sensor := bmp280.New(machine.I2C0) + sensor.Configure(bmp280.STANDBY_125MS, bmp280.FILTER_4X, bmp280.SAMPLING_16X, bmp280.SAMPLING_16X, bmp280.MODE_FORCED) + + connected := sensor.Connected() + if !connected { + println("\nBMP280 Sensor not detected\n") + return + } + println("\nBMP280 Sensor detected\n") + + println("Calibration:") + sensor.PrintCali() + + for { + t, err := sensor.ReadTemperature() + if err != nil { + println("Error reading temperature") + } + // Temperature in degrees Celsius + fmt.Printf("Temperature: %.2f °C\n", float32(t)/1000) + + p, err := sensor.ReadPressure() + if err != nil { + println("Error reading pressure") + } + // Pressure in hectoPascal + fmt.Printf("Pressure: %.2f hPa\n", float32(p)/100000) + + time.Sleep(5 * time.Second) + } +}