1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/drivers/bmx280/bmx280.c
2018-07-25 12:01:38 +02:00

384 lines
13 KiB
C

/*
* Copyright (C) 2016 Kees Bakker, SODAQ
* 2017 Inria
*
* 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 sensors BMX280 (BME280 and BMP280).
*
* @author Kees Bakker <kees@sodaq.com>
*
* @}
*/
#include <string.h>
#include <math.h>
#include "log.h"
#include "bmx280.h"
#include "bmx280_internals.h"
#include "bmx280_params.h"
#include "periph/i2c.h"
#include "xtimer.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
static int read_calibration_data(bmx280_t* dev);
static int do_measurement(const bmx280_t* dev);
static uint8_t get_ctrl_meas(const bmx280_t* dev);
static uint8_t get_status(const bmx280_t* dev);
static uint8_t read_u8_reg(const bmx280_t* dev, uint8_t reg);
static void write_u8_reg(const bmx280_t* dev, uint8_t reg, uint8_t b);
static uint16_t get_uint16_le(const uint8_t *buffer, size_t offset);
static int16_t get_int16_le(const uint8_t *buffer, size_t offset);
#if ENABLE_DEBUG
static void dump_buffer(const char *txt, uint8_t *buffer, size_t size);
#define DUMP_BUFFER(txt, buffer, size) dump_buffer(txt, buffer, size)
#else
#define DUMP_BUFFER(txt, buffer, size)
#endif
/**
* @brief Fine resolution temperature value, also needed for pressure and humidity.
*/
static int32_t t_fine;
/**
* @brief The measurement registers, including temperature, pressure and humidity
*
* A temporary buffer for the memory map 0xF7..0xFE
* These are read all at once and then used to compute the three sensor values.
*/
static uint8_t measurement_regs[8];
/*---------------------------------------------------------------------------*
* BMX280 Core API *
*---------------------------------------------------------------------------*/
int bmx280_init(bmx280_t* dev, const bmx280_params_t* params)
{
uint8_t chip_id;
dev->params = *params;
/* Read chip ID */
chip_id = read_u8_reg(dev, BMX280_CHIP_ID_REG);
if ((chip_id != BME280_CHIP_ID) && (chip_id != BMP280_CHIP_ID)) {
DEBUG("[Error] Did not detect a BMX280 at address %02x (%02x != %02x or %02x)\n",
dev->params.i2c_addr, chip_id, BME280_CHIP_ID, BMP280_CHIP_ID);
return BMX280_ERR_NODEV;
}
/* Read compensation data, 0x88..0x9F, 0xA1, 0xE1..0xE7 */
if (read_calibration_data(dev)) {
DEBUG("[Error] Could not read calibration data\n");
return BMX280_ERR_NOCAL;
}
return BMX280_OK;
}
/*
* Returns temperature in DegC, resolution is 0.01 DegC.
* t_fine carries fine temperature as global value
*/
int16_t bmx280_read_temperature(const bmx280_t* 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)measurement_regs[3 + 0]) << 12) |
(((uint32_t)measurement_regs[3 + 1]) << 4) |
((((uint32_t)measurement_regs[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) */
t_fine = var1 + var2;
return (t_fine * 5 + 128) >> 8;
}
/*
* Returns pressure in Pa
*/
uint32_t bmx280_read_pressure(const bmx280_t *dev)
{
const bmx280_calibration_t *cal = &dev->calibration; /* helper variable */
/* Read the uncompensated pressure */
int32_t adc_P = (((uint32_t)measurement_regs[0 + 0]) << 12) |
(((uint32_t)measurement_regs[0 + 1]) << 4) |
((((uint32_t)measurement_regs[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)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)
uint16_t bme280_read_humidity(const bmx280_t *dev)
{
const bmx280_calibration_t *cal = &dev->calibration; /* helper variable */
/* Read the uncompensated pressure */
int32_t adc_H = (((uint32_t)measurement_regs[6 + 0]) << 8) |
(((uint32_t)measurement_regs[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 = (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
/**
* Read compensation data, 0x88..0x9F, 0xA1, 0xE1..0xE7
*
* This function reads all calibration bytes at once. These are
* the registers DIG_T1_LSB (0x88) upto and including DIG_H6 (0xE7).
*/
static int read_calibration_data(bmx280_t* dev)
{
uint8_t buffer[128]; /* 128 should be enough to read all calibration bytes */
int result;
#ifdef MODULE_BME280
int nr_bytes_to_read = (BME280_DIG_H6_REG - BMX280_DIG_T1_LSB_REG) + 1;
#else
int nr_bytes_to_read = (BMX280_DIG_P9_MSB_REG - BMX280_DIG_T1_LSB_REG) + 1;
#endif
uint8_t offset = 0x88;
memset(buffer, 0, sizeof(buffer));
i2c_acquire(dev->params.i2c_dev);
result = i2c_read_regs(dev->params.i2c_dev, dev->params.i2c_addr, offset,
buffer, nr_bytes_to_read, 0);
i2c_release(dev->params.i2c_dev);
if (result != 0) {
LOG_ERROR("Unable to read calibration data\n");
return -1;
}
DUMP_BUFFER("Raw Calibration Data", buffer, nr_bytes_to_read);
/* All little endian */
dev->calibration.dig_T1 = get_uint16_le(buffer, BMX280_DIG_T1_LSB_REG - offset);
dev->calibration.dig_T2 = get_int16_le(buffer, BMX280_DIG_T2_LSB_REG - offset);
dev->calibration.dig_T3 = get_int16_le(buffer, BMX280_DIG_T3_LSB_REG - offset);
dev->calibration.dig_P1 = get_uint16_le(buffer, BMX280_DIG_P1_LSB_REG - offset);
dev->calibration.dig_P2 = get_int16_le(buffer, BMX280_DIG_P2_LSB_REG - offset);
dev->calibration.dig_P3 = get_int16_le(buffer, BMX280_DIG_P3_LSB_REG - offset);
dev->calibration.dig_P4 = get_int16_le(buffer, BMX280_DIG_P4_LSB_REG - offset);
dev->calibration.dig_P5 = get_int16_le(buffer, BMX280_DIG_P5_LSB_REG - offset);
dev->calibration.dig_P6 = get_int16_le(buffer, BMX280_DIG_P6_LSB_REG - offset);
dev->calibration.dig_P7 = get_int16_le(buffer, BMX280_DIG_P7_LSB_REG - offset);
dev->calibration.dig_P8 = get_int16_le(buffer, BMX280_DIG_P8_LSB_REG - offset);
dev->calibration.dig_P9 = get_int16_le(buffer, BMX280_DIG_P9_LSB_REG - offset);
#if defined(MODULE_BME280)
dev->calibration.dig_H1 = buffer[BME280_DIG_H1_REG - offset];
dev->calibration.dig_H2 = get_int16_le(buffer, BME280_DIG_H2_LSB_REG - offset);
dev->calibration.dig_H3 = buffer[BME280_DIG_H3_REG - offset];
dev->calibration.dig_H4 = (((int16_t)buffer[BME280_DIG_H4_MSB_REG - offset]) << 4) +
(buffer[BME280_DIG_H4_H5_REG - offset] & 0x0F);
dev->calibration.dig_H5 = (((int16_t)buffer[BME280_DIG_H5_MSB_REG - offset]) << 4) +
((buffer[BME280_DIG_H4_H5_REG - offset] & 0xF0) >> 4);
dev->calibration.dig_H6 = buffer[BME280_DIG_H6_REG - offset];
#endif
DEBUG("[INFO] Chip ID = 0x%02X\n", buffer[BMX280_CHIP_ID_REG - offset]);
/* Config is only writable in sleep mode */
i2c_acquire(dev->params.i2c_dev);
(void)i2c_write_reg(dev->params.i2c_dev, dev->params.i2c_addr,
BMX280_CTRL_MEAS_REG, 0, 0);
i2c_release(dev->params.i2c_dev);
uint8_t b;
/* Config Register */
/* spi3w_en unused */
b = ((dev->params.t_sb & 7) << 5) | ((dev->params.filter & 7) << 2);
write_u8_reg(dev, BMX280_CONFIG_REG, b);
#if defined(MODULE_BME280)
/*
* Note from the datasheet about ctrl_hum: "Changes to this register only become effective
* after a write operation to "ctrl_meas".
* So, set ctrl_hum first.
*/
b = dev->params.humid_oversample & 7;
write_u8_reg(dev, BME280_CTRL_HUMIDITY_REG, b);
#endif
b = ((dev->params.temp_oversample & 7) << 5) |
((dev->params.press_oversample & 7) << 2) |
(dev->params.run_mode & 3);
write_u8_reg(dev, BMX280_CTRL_MEAS_REG, b);
return 0;
}
/**
* @brief Start a measurement and read the registers
*/
static int do_measurement(const bmx280_t* dev)
{
/*
* If settings has FORCED mode, then the device go to sleep after
* it finished the measurement. To read again we have to set the
* run_mode back to FORCED.
*/
uint8_t ctrl_meas = get_ctrl_meas(dev);
uint8_t run_mode = ctrl_meas & 3;
if (run_mode != dev->params.run_mode) {
/* Set the run_mode back to what we want. */
ctrl_meas &= ~3;
ctrl_meas |= dev->params.run_mode;
write_u8_reg(dev, BMX280_CTRL_MEAS_REG, ctrl_meas);
/* Wait for measurement ready? */
size_t count = 0;
while (count < 10 && (get_status(dev) & 0x08) != 0) {
++count;
}
/* What to do when measuring is still on? */
}
int result;
int nr_bytes_to_read = sizeof(measurement_regs);
uint8_t offset = BMX280_PRESSURE_MSB_REG;
i2c_acquire(dev->params.i2c_dev);
result = i2c_read_regs(dev->params.i2c_dev, dev->params.i2c_addr,
offset, measurement_regs, nr_bytes_to_read, 0);
i2c_release(dev->params.i2c_dev);
if (result != 0) {
LOG_ERROR("Unable to read temperature data\n");
return -1;
}
DUMP_BUFFER("Raw Sensor Data", measurement_regs, nr_bytes_to_read);
return 0;
}
static uint8_t get_ctrl_meas(const bmx280_t* dev)
{
return read_u8_reg(dev, BMX280_CTRL_MEAS_REG);
}
static uint8_t get_status(const bmx280_t* dev)
{
return read_u8_reg(dev, BMX280_STAT_REG);
}
static uint8_t read_u8_reg(const bmx280_t* dev, uint8_t reg)
{
uint8_t b;
/* Assuming device is correct, it should return 1 (nr bytes) */
i2c_acquire(dev->params.i2c_dev);
(void)i2c_read_reg(dev->params.i2c_dev, dev->params.i2c_addr, reg, &b, 0);
i2c_release(dev->params.i2c_dev);
return b;
}
static void write_u8_reg(const bmx280_t* dev, uint8_t reg, uint8_t b)
{
/* Assuming device is correct, it should return 1 (nr bytes) */
i2c_acquire(dev->params.i2c_dev);
(void)i2c_write_reg(dev->params.i2c_dev, dev->params.i2c_addr, reg, b, 0);
i2c_release(dev->params.i2c_dev);
}
static uint16_t get_uint16_le(const uint8_t *buffer, size_t offset)
{
return (((uint16_t)buffer[offset + 1]) << 8) + buffer[offset];
}
static int16_t get_int16_le(const uint8_t *buffer, size_t offset)
{
return (((int16_t)buffer[offset + 1]) << 8) + buffer[offset];
}
#if ENABLE_DEBUG
static void dump_buffer(const char *txt, uint8_t *buffer, size_t size)
{
size_t ix;
DEBUG("%s\n", txt);
for (ix = 0; ix < size; ix++) {
DEBUG("%02X", buffer[ix]);
if ((ix + 1) == size || (((ix + 1) % 16) == 0)) {
DEBUG("\n");
}
}
}
#endif