mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
424 lines
13 KiB
C
424 lines
13 KiB
C
/*
|
|
* Copyright (C) 2016 Kees Bakker, SODAQ
|
|
* 2017 Inria
|
|
* 2018 Freie Universität Berlin
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup drivers_bmx280
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Device driver implementation for BME280 and BMP280 sensors
|
|
*
|
|
* @author Kees Bakker <kees@sodaq.com>
|
|
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include "log.h"
|
|
#include "assert.h"
|
|
#include "bmx280.h"
|
|
#include "bmx280_internals.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
#ifdef BMX280_USE_SPI
|
|
#define BUS (dev->params.spi)
|
|
#define CS (dev->params.cs)
|
|
#define CLK (dev->params.clk)
|
|
#define MODE SPI_MODE_0
|
|
#define WRITE_MASK (0x7F)
|
|
#else
|
|
#define BUS (dev->params.i2c_dev)
|
|
#define ADDR (dev->params.i2c_addr)
|
|
#endif
|
|
|
|
/* shortcut for accessing byte x of the latest sensor reading */
|
|
#define RAW_DATA (dev->last_reading)
|
|
|
|
/* implementation for the driver's configured bus interface (I2C vs SPI) */
|
|
#ifdef BMX280_USE_SPI /* using SPI mode */
|
|
static inline int _acquire(const bmx280_t *dev)
|
|
{
|
|
spi_acquire(BUS, CS, MODE, CLK);
|
|
return BMX280_OK;
|
|
}
|
|
|
|
static inline void _release(const bmx280_t *dev)
|
|
{
|
|
spi_release(BUS);
|
|
}
|
|
|
|
static int _read_reg(const bmx280_t *dev, uint8_t reg, uint8_t *data)
|
|
{
|
|
*data = spi_transfer_reg(BUS, CS, reg, 0);
|
|
return BMX280_OK;
|
|
}
|
|
|
|
static int _write_reg(const bmx280_t *dev, uint8_t reg, uint8_t data)
|
|
{
|
|
(void)spi_transfer_reg(BUS, CS, (reg & WRITE_MASK), data);
|
|
return BMX280_OK;
|
|
}
|
|
|
|
static int _read_burst(const bmx280_t *dev, uint8_t reg, void *buf, size_t len)
|
|
{
|
|
spi_transfer_regs(BUS, CS, reg, NULL, buf, len);
|
|
return BMX280_OK;
|
|
}
|
|
|
|
#else /* using I2C mode */
|
|
|
|
static inline int _acquire(const bmx280_t *dev)
|
|
{
|
|
i2c_acquire(BUS);
|
|
return BMX280_OK;
|
|
}
|
|
|
|
static inline void _release(const bmx280_t *dev)
|
|
{
|
|
i2c_release(BUS);
|
|
}
|
|
|
|
static int _read_reg(const bmx280_t *dev, uint8_t reg, uint8_t *data)
|
|
{
|
|
if (i2c_read_reg(BUS, ADDR, reg, data, 0) != 0) {
|
|
return BMX280_ERR_BUS;
|
|
}
|
|
return BMX280_OK;
|
|
}
|
|
|
|
static int _write_reg(const bmx280_t *dev, uint8_t reg, uint8_t data)
|
|
{
|
|
if (i2c_write_reg(BUS, ADDR, reg, data, 0) != 0) {
|
|
return BMX280_ERR_BUS;
|
|
}
|
|
return BMX280_OK;
|
|
}
|
|
|
|
static int _read_burst(const bmx280_t *dev, uint8_t reg, void *buf, size_t len)
|
|
{
|
|
if (i2c_read_regs(BUS, ADDR, reg, buf, len, 0) != 0) {
|
|
return BMX280_ERR_BUS;
|
|
}
|
|
return BMX280_OK;
|
|
}
|
|
|
|
#endif /* bus mode selection */
|
|
|
|
static uint16_t _to_u16_le(const uint8_t *buffer, size_t offset)
|
|
{
|
|
return (((uint16_t)buffer[offset + 1]) << 8) | buffer[offset];
|
|
}
|
|
|
|
static int16_t _to_i16_le(const uint8_t *buffer, size_t offset)
|
|
{
|
|
return (((int16_t)buffer[offset + 1]) << 8) | buffer[offset];
|
|
}
|
|
|
|
/**
|
|
* @brief Read the calibration data from sensor ROM, it is in registers
|
|
* 0x88..0x9F, 0xA1, and 0xE1..0xE7
|
|
*/
|
|
static int _read_calibration_data(bmx280_t *dev)
|
|
{
|
|
/* no need to acquire a bus here, as this is done in the init function */
|
|
|
|
/* allocate some memory to store the largest block of calibration data */
|
|
uint8_t buf[CALIB_T_P_LEN];
|
|
|
|
/* read humidity and temperature calibration data */
|
|
if (_read_burst(dev, CALIB_T_P_BASE, buf, CALIB_T_P_LEN) != BMX280_OK) {
|
|
return BMX280_ERR_BUS;
|
|
}
|
|
|
|
/* convert calibration values to little endian format and save them */
|
|
dev->calibration.dig_T1 = _to_u16_le(buf, OFFSET_T_P(BMX280_DIG_T1_LSB_REG));
|
|
dev->calibration.dig_T2 = _to_i16_le(buf, OFFSET_T_P(BMX280_DIG_T2_LSB_REG));
|
|
dev->calibration.dig_T3 = _to_i16_le(buf, OFFSET_T_P(BMX280_DIG_T3_LSB_REG));
|
|
|
|
dev->calibration.dig_P1 = _to_u16_le(buf, OFFSET_T_P(BMX280_DIG_P1_LSB_REG));
|
|
dev->calibration.dig_P2 = _to_i16_le(buf, OFFSET_T_P(BMX280_DIG_P2_LSB_REG));
|
|
dev->calibration.dig_P3 = _to_i16_le(buf, OFFSET_T_P(BMX280_DIG_P3_LSB_REG));
|
|
dev->calibration.dig_P4 = _to_i16_le(buf, OFFSET_T_P(BMX280_DIG_P4_LSB_REG));
|
|
dev->calibration.dig_P5 = _to_i16_le(buf, OFFSET_T_P(BMX280_DIG_P5_LSB_REG));
|
|
dev->calibration.dig_P6 = _to_i16_le(buf, OFFSET_T_P(BMX280_DIG_P6_LSB_REG));
|
|
dev->calibration.dig_P7 = _to_i16_le(buf, OFFSET_T_P(BMX280_DIG_P7_LSB_REG));
|
|
dev->calibration.dig_P8 = _to_i16_le(buf, OFFSET_T_P(BMX280_DIG_P8_LSB_REG));
|
|
dev->calibration.dig_P9 = _to_i16_le(buf, OFFSET_T_P(BMX280_DIG_P9_LSB_REG));
|
|
|
|
#if defined(MODULE_BME280_SPI) || defined(MODULE_BME280_I2C)
|
|
/* read dig_H1 in a single read, as this value is not in the block with the
|
|
* rest of the humidity calibration values */
|
|
if (_read_reg(dev, BME280_DIG_H1_REG, &dev->calibration.dig_H1) != BMX280_OK) {
|
|
return BMX280_ERR_BUS;
|
|
}
|
|
|
|
/* read the block with the rest of the values */
|
|
if (_read_burst(dev, CALIB_H_BASE, buf, CALIB_H_LEN) != BMX280_OK) {
|
|
return BMX280_ERR_BUS;
|
|
}
|
|
|
|
/* parse the humidity compensation and store in device descriptor */
|
|
dev->calibration.dig_H2 = _to_i16_le(buf, OFFSET_H(BME280_DIG_H2_LSB_REG));
|
|
dev->calibration.dig_H3 = buf[OFFSET_H(BME280_DIG_H3_REG)];
|
|
dev->calibration.dig_H4 = ((((int16_t)buf[OFFSET_H(BME280_DIG_H4_MSB_REG)]) << 4) +
|
|
(buf[OFFSET_H(BME280_DIG_H4_H5_REG)] & 0x0F));
|
|
dev->calibration.dig_H5 = ((((int16_t)buf[OFFSET_H(BME280_DIG_H5_MSB_REG)]) << 4) +
|
|
((buf[OFFSET_H(BME280_DIG_H4_H5_REG)] & 0xF0) >> 4));
|
|
dev->calibration.dig_H6 = buf[OFFSET_H(BME280_DIG_H6_REG)];
|
|
#endif
|
|
|
|
return BMX280_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Trigger a new measurement (if applicable) and read raw data
|
|
*/
|
|
static int _do_measurement(bmx280_t *dev)
|
|
{
|
|
uint8_t reg;
|
|
|
|
/* get access to the bus */
|
|
if (_acquire(dev) != BMX280_OK) {
|
|
goto err;
|
|
}
|
|
|
|
/* if in FORCED mode, we need to manually trigger a measurement */
|
|
if (dev->params.run_mode != BMX280_MODE_NORMAL) {
|
|
reg = ((dev->params.temp_oversample << MEAS_OSRS_T_POS) |
|
|
(dev->params.press_oversample << MEAS_OSRS_P_POS) |
|
|
BMX280_MODE_FORCED);
|
|
if (_write_reg(dev, BMX280_CTRL_MEAS_REG, reg) != BMX280_OK) {
|
|
goto err;
|
|
}
|
|
do {
|
|
if (_read_reg(dev, BMX280_STAT_REG, ®) != BMX280_OK) {
|
|
goto err;
|
|
}
|
|
} while (reg & STAT_MEASURING);
|
|
/* results are ready now */
|
|
DEBUG("[bmx280] _do_measurement: measurement data ready\n");
|
|
}
|
|
|
|
/* read all raw data registers into data buffer */
|
|
if (_read_burst(dev, DATA_BASE, RAW_DATA, BMX280_RAW_LEN) != BMX280_OK) {
|
|
goto err;
|
|
}
|
|
|
|
/* we are done reading from the device, so release the bus again */
|
|
_release(dev);
|
|
return BMX280_OK;
|
|
|
|
err:
|
|
_release(dev);
|
|
return BMX280_ERR_BUS;
|
|
}
|
|
|
|
int bmx280_init(bmx280_t *dev, const bmx280_params_t *params)
|
|
{
|
|
assert(dev && params);
|
|
|
|
dev->params = *params;
|
|
uint8_t reg;
|
|
|
|
#ifdef BMX280_USE_SPI
|
|
/* configure the chip-select pin */
|
|
if (spi_init_cs(BUS, CS) != SPI_OK) {
|
|
DEBUG("[bmx280] error: unable to configure chip the select pin\n");
|
|
return BMX280_ERR_BUS;
|
|
}
|
|
#endif
|
|
|
|
/* acquire bus bus, this also tests the bus parameters in SPI mode */
|
|
if (_acquire(dev) != BMX280_OK) {
|
|
DEBUG("[bmx280] error: unable to acquire bus\n");
|
|
return BMX280_ERR_BUS;
|
|
}
|
|
|
|
/* test the connection to the device by reading and verifying its chip ID */
|
|
if (_read_reg(dev, BMX280_CHIP_ID_REG, ®) != BMX280_OK) {
|
|
DEBUG("[bmx280] error: unable to read chip ID from device\n");
|
|
_release(dev);
|
|
return BMX280_ERR_NODEV;
|
|
}
|
|
if (reg != BMX280_CHIP_ID_VAL) {
|
|
DEBUG("[bmx280] error: invalid chip ID (0x%02x)\n", (int)reg);
|
|
_release(dev);
|
|
return BMX280_ERR_NODEV;
|
|
}
|
|
|
|
/* trigger a power-on reset sequence to reset all registers */
|
|
if (_write_reg(dev, BMEX80_RST_REG, RESET_WORD) != BMX280_OK) {
|
|
goto err;
|
|
}
|
|
/* wait for reset sequence to finish */
|
|
do {
|
|
if (_read_reg(dev, BMX280_STAT_REG, ®) != BMX280_OK) {
|
|
goto err;
|
|
}
|
|
} while (reg != 0);
|
|
|
|
/* read the compensation data from the sensor's ROM */
|
|
if (_read_calibration_data(dev) != BMX280_OK) {
|
|
DEBUG("[bmx280] error: could not read calibration data\n");
|
|
goto err;
|
|
}
|
|
|
|
/* write basic device configuration: t_sb and filter values */
|
|
reg = (dev->params.t_sb | dev->params.filter);
|
|
if (_write_reg(dev, BMX280_CONFIG_REG, reg) != BMX280_OK) {
|
|
goto err;
|
|
}
|
|
|
|
#if defined(MODULE_BME280_SPI) || defined(MODULE_BME280_I2C)
|
|
/* ctrl_hum must be written before ctrl_meas for changes to become
|
|
* effective */
|
|
reg = dev->params.humid_oversample;
|
|
if (_write_reg(dev, BME280_CTRL_HUM_REG, reg) != BMX280_OK) {
|
|
goto err;
|
|
}
|
|
#endif
|
|
|
|
/* finally apply the temperature and pressure oversampling configuration and
|
|
* configure the run mode */
|
|
reg = ((dev->params.temp_oversample << MEAS_OSRS_T_POS) |
|
|
(dev->params.press_oversample << MEAS_OSRS_P_POS) |
|
|
(dev->params.run_mode));
|
|
if (_write_reg(dev, BMX280_CTRL_MEAS_REG, reg) != BMX280_OK) {
|
|
goto err;
|
|
}
|
|
|
|
_release(dev);
|
|
return BMX280_OK;
|
|
|
|
err:
|
|
_release(dev);
|
|
DEBUG("[bmx280] init: bus error while initializing device\n");
|
|
return BMX280_ERR_BUS;
|
|
}
|
|
|
|
int16_t bmx280_read_temperature(bmx280_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
if (_do_measurement(dev) < 0) {
|
|
return INT16_MIN;
|
|
}
|
|
|
|
const bmx280_calibration_t *cal = &dev->calibration; /* helper variable */
|
|
|
|
/* Read the uncompensated temperature */
|
|
int32_t adc_T = (((uint32_t)RAW_DATA[3 + 0]) << 12) |
|
|
(((uint32_t)RAW_DATA[3 + 1]) << 4) |
|
|
((((uint32_t)RAW_DATA[3 + 2]) >> 4) & 0x0F);
|
|
|
|
/*
|
|
* Compensate the temperature value.
|
|
* The following is code from Bosch's BME280_driver
|
|
* bme280_compensate_temperature_int32(). The variable names and the many
|
|
* defines have been modified to make the code more readable.
|
|
*/
|
|
int32_t var1;
|
|
int32_t var2;
|
|
|
|
var1 = ((((adc_T >> 3) - ((int32_t)cal->dig_T1 << 1))) * ((int32_t)cal->dig_T2)) >> 11;
|
|
var2 = (((((adc_T >> 4) - ((int32_t)cal->dig_T1)) * ((adc_T >> 4) - ((int32_t)cal->dig_T1))) >> 12) *
|
|
((int32_t)cal->dig_T3)) >> 14;
|
|
|
|
/* calculate t_fine (used for pressure and humidity too) */
|
|
dev->t_fine = var1 + var2;
|
|
|
|
return (dev->t_fine * 5 + 128) >> 8;
|
|
}
|
|
|
|
uint32_t bmx280_read_pressure(bmx280_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
bmx280_read_temperature(dev);
|
|
const bmx280_calibration_t *cal = &dev->calibration; /* helper variable */
|
|
|
|
/* Read the uncompensated pressure */
|
|
int32_t adc_P = (((uint32_t)RAW_DATA[0 + 0]) << 12) |
|
|
(((uint32_t)RAW_DATA[0 + 1]) << 4) |
|
|
((((uint32_t)RAW_DATA[0 + 2]) >> 4) & 0x0F);
|
|
|
|
int64_t var1;
|
|
int64_t var2;
|
|
int64_t p_acc;
|
|
|
|
/*
|
|
* Compensate the pressure value.
|
|
* The following is code from Bosch's BME280_driver
|
|
* bme280_compensate_pressure_int64(). The variable names and the many
|
|
* defines have been modified to make the code more readable.
|
|
*/
|
|
var1 = ((int64_t)dev->t_fine) - 128000;
|
|
var2 = var1 * var1 * (int64_t)cal->dig_P6;
|
|
var2 = var2 + ((var1 * (int64_t)cal->dig_P5) << 17);
|
|
var2 = var2 + (((int64_t)cal->dig_P4) << 35);
|
|
var1 = ((var1 * var1 * (int64_t)cal->dig_P3) >> 8) + ((var1 * (int64_t)cal->dig_P2) << 12);
|
|
var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)cal->dig_P1) >> 33;
|
|
/* Avoid division by zero */
|
|
if (var1 == 0) {
|
|
return UINT32_MAX;
|
|
}
|
|
|
|
p_acc = 1048576 - adc_P;
|
|
p_acc = (((p_acc << 31) - var2) * 3125) / var1;
|
|
var1 = (((int64_t)cal->dig_P9) * (p_acc >> 13) * (p_acc >> 13)) >> 25;
|
|
var2 = (((int64_t)cal->dig_P8) * p_acc) >> 19;
|
|
p_acc = ((p_acc + var1 + var2) >> 8) + (((int64_t)cal->dig_P7) << 4);
|
|
|
|
return p_acc >> 8;
|
|
}
|
|
|
|
#if defined(MODULE_BME280_SPI) || defined(MODULE_BME280_I2C)
|
|
uint16_t bme280_read_humidity(bmx280_t *dev)
|
|
{
|
|
assert(dev);
|
|
|
|
bmx280_read_temperature(dev);
|
|
const bmx280_calibration_t *cal = &dev->calibration; /* helper variable */
|
|
|
|
/* Read the uncompensated pressure */
|
|
int32_t adc_H = (((uint32_t)RAW_DATA[6 + 0]) << 8) |
|
|
(((uint32_t)RAW_DATA[6 + 1]));
|
|
|
|
/*
|
|
* Compensate the humidity value.
|
|
* The following is code from Bosch's BME280_driver
|
|
* bme280_compensate_humidity_int32(). The variable names and the many
|
|
* defines have been modified to make the code more readable.
|
|
* The value is first computed as a value in %rH as unsigned 32bit integer
|
|
* in Q22.10 format(22 integer 10 fractional bits).
|
|
*/
|
|
int32_t var1;
|
|
|
|
/* calculate x1*/
|
|
var1 = (dev->t_fine - ((int32_t)76800));
|
|
/* calculate x1*/
|
|
var1 = (((((adc_H << 14) - (((int32_t)cal->dig_H4) << 20) - (((int32_t)cal->dig_H5) * var1)) +
|
|
((int32_t)16384)) >> 15) *
|
|
(((((((var1 * ((int32_t)cal->dig_H6)) >> 10) *
|
|
(((var1 * ((int32_t)cal->dig_H3)) >> 11) + ((int32_t)32768))) >> 10) +
|
|
((int32_t)2097152)) * ((int32_t)cal->dig_H2) + 8192) >> 14));
|
|
var1 = (var1 - (((((var1 >> 15) * (var1 >> 15)) >> 7) * ((int32_t)cal->dig_H1)) >> 4));
|
|
var1 = (var1 < 0) ? 0 : var1;
|
|
var1 = (var1 > 419430400) ? 419430400 : var1;
|
|
/* First multiply to avoid losing the accuracy after the shift by ten */
|
|
return (100 * ((uint32_t)var1 >> 12)) >> 10;
|
|
}
|
|
#endif
|