mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
411 lines
12 KiB
C
411 lines
12 KiB
C
/*
|
|
* Copyright (C) 2018 Gunar Schorcht
|
|
*
|
|
* 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_sht3x
|
|
* @brief Device Driver for Sensirion SHT30/SHT31/SHT35 Humidity and Temperature Sensors
|
|
* @author Gunar Schorcht <gunar@schorcht.net>
|
|
* @file
|
|
*/
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
#include "log.h"
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
#include "checksum/crc8.h"
|
|
#include "sht3x.h"
|
|
#include "ztimer.h"
|
|
|
|
#define ASSERT_PARAM(cond) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
DEBUG("[sht3x] %s: %s\n", \
|
|
__func__, "parameter condition (" #cond ") not fulfilled"); \
|
|
assert(cond); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define DEBUG_DEV(f, d, ...) \
|
|
DEBUG("[sht3x] %s dev=%d addr=%02x: " f "\n", \
|
|
__func__, d->i2c_dev, d->i2c_addr, ## __VA_ARGS__)
|
|
|
|
#define ERROR_DEV(f, d, ...) \
|
|
LOG_ERROR("[sht3x] dev=%d addr=%x: " f "\n", \
|
|
d->i2c_dev, d->i2c_addr, ## __VA_ARGS__)
|
|
|
|
/* SHT3x common commands */
|
|
#define SHT3X_CMD_CLEAR_STATUS 0x3041 /* clear status command */
|
|
#define SHT3X_CMD_HEATER_OFF 0x3066 /* Heater disable command */
|
|
#define SHT3X_CMD_BREAK 0x3093 /* Break command */
|
|
#define SHT3X_CMD_RESET 0x30A2 /* reset command */
|
|
#define SHT3X_CMD_FETCH_DATA 0xE000 /* fetch data command */
|
|
#define SHT3X_CMD_STATUS 0xF32D /* get status command */
|
|
|
|
/* SHT3x status register flags */
|
|
#define SHT3X_STATUS_REG_MASK (0xbc13) /* valid status register bit mask */
|
|
#define SHT3X_STATUS_REG_CRC (1 << 0) /* write data checksum status */
|
|
#define SHT3X_STATUS_REG_CMD (1 << 1) /* last command execution status */
|
|
|
|
/* Raw date size */
|
|
#define SHT3X_RAW_DATA_SIZE 6
|
|
|
|
/* SHT3x measurement period times in us */
|
|
static const uint32_t SHT3X_MEASURE_PERIOD[] = {
|
|
0, /* [SINGLE_SHOT ] */
|
|
2000, /* [PERIODIC_0_5] */
|
|
1000, /* [PERIODIC_1 ] */
|
|
500, /* [PERIODIC_2 ] */
|
|
250, /* [PERIODIC_4 ] */
|
|
100 /* [PERIODIC_10 ] */
|
|
};
|
|
|
|
/* SHT3x measurement command sequences */
|
|
static const uint16_t SHT3X_CMD_MEASURE[6][3] = {
|
|
{0x2400, 0x240b, 0x2416}, /* [SINGLE_SHOT ][H, M, L] without clock stretching */
|
|
{0x2032, 0x2024, 0x202f}, /* [PERIODIC_0_5][H, M, L] */
|
|
{0x2130, 0x2126, 0x212d}, /* [PERIODIC_1 ][H, M, L] */
|
|
{0x2236, 0x2220, 0x222b}, /* [PERIODIC_2 ][H, M, L] */
|
|
{0x2234, 0x2322, 0x2329}, /* [PERIODIC_4 ][H, M, L] */
|
|
{0x2737, 0x2721, 0x272a} /* [PERIODIC_10 ][H, M, L] */
|
|
};
|
|
|
|
/* maximum measurement durations dependent on repatability in ms */
|
|
#define SHT3X_MEAS_DURATION_REP_HIGH 16
|
|
#define SHT3X_MEAS_DURATION_REP_MEDIUM 7
|
|
#define SHT3X_MEAS_DURATION_REP_LOW 5
|
|
|
|
/* measurement durations in us */
|
|
const uint16_t SHT3X_MEAS_DURATION_MS[3] = { SHT3X_MEAS_DURATION_REP_HIGH,
|
|
SHT3X_MEAS_DURATION_REP_MEDIUM,
|
|
SHT3X_MEAS_DURATION_REP_LOW };
|
|
|
|
/* functions for internal use */
|
|
static int _get_raw_data(sht3x_dev_t* dev, uint8_t* raw_data);
|
|
static int _compute_values (uint8_t* raw_data, int16_t* temp, int16_t* hum);
|
|
|
|
/* sensor commands */
|
|
static int _start_measurement (sht3x_dev_t* dev);
|
|
static int _reset (sht3x_dev_t* dev);
|
|
static int _status (sht3x_dev_t* dev, uint16_t* status);
|
|
|
|
/* sensor access functions */
|
|
static int _send_command(sht3x_dev_t* dev, uint16_t cmd);
|
|
static int _read_data(sht3x_dev_t* dev, uint8_t *data, uint8_t len);
|
|
|
|
static inline uint8_t _crc8(const void* buf, size_t len)
|
|
{
|
|
return crc8(buf, len, 0x31, 0xff);
|
|
}
|
|
|
|
/* ------------------------------------------------ */
|
|
|
|
int sht3x_init (sht3x_dev_t *dev, const sht3x_params_t *params)
|
|
{
|
|
ASSERT_PARAM(dev != NULL);
|
|
ASSERT_PARAM(params != NULL);
|
|
|
|
int res = SHT3X_OK;
|
|
|
|
/* initialize sensor data structure */
|
|
dev->i2c_dev = params->i2c_dev;
|
|
dev->i2c_addr = params->i2c_addr;
|
|
dev->mode = params->mode;
|
|
dev->repeat = params->repeat;
|
|
|
|
dev->meas_start_time = 0;
|
|
dev->meas_duration = 0;
|
|
dev->meas_started = false;
|
|
|
|
/* try to reset the sensor */
|
|
if ((res = _reset(dev)) != SHT3X_OK) {
|
|
return res;
|
|
}
|
|
|
|
DEBUG_DEV("sensor initialized", dev);
|
|
|
|
/* start periodic measurements */
|
|
if ((dev->mode != SHT3X_SINGLE_SHOT) &&
|
|
(res = _start_measurement (dev)) != SHT3X_OK){
|
|
return res;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
int sht3x_read (sht3x_dev_t* dev, int16_t* temp, int16_t* hum)
|
|
{
|
|
ASSERT_PARAM(dev != NULL);
|
|
ASSERT_PARAM(temp != NULL || hum != NULL);
|
|
|
|
int res = SHT3X_OK;
|
|
|
|
uint8_t raw_data[SHT3X_RAW_DATA_SIZE];
|
|
|
|
/*
|
|
* fetches raw sensor data, implicitly starts the measurement if necessary,
|
|
* e.g., in single-shot measurement mode, and waits until sensor data are
|
|
* available
|
|
*/
|
|
if ((res = _get_raw_data (dev, raw_data)) != SHT3X_OK) {
|
|
return res;
|
|
}
|
|
|
|
return _compute_values (raw_data, temp, hum);
|
|
}
|
|
|
|
/* Functions for internal use only */
|
|
|
|
static int _start_measurement (sht3x_dev_t* dev)
|
|
{
|
|
/* start measurement according to selected mode */
|
|
if (_send_command(dev, SHT3X_CMD_MEASURE[dev->mode][dev->repeat]) != SHT3X_OK)
|
|
{
|
|
DEBUG_DEV("start measurement failed, "
|
|
"could not send SHT3X_CMD_MEASURE to sensor", dev);
|
|
return -SHT3X_ERROR_I2C;
|
|
}
|
|
|
|
/*
|
|
* in periodic modes, we check whether the command were processed, in
|
|
* single shot mode, reading results has to follow directly.
|
|
*/
|
|
if (dev->mode != SHT3X_SINGLE_SHOT) {
|
|
/* sensor needs up to 250 us to process the measurement command */
|
|
ztimer_sleep(ZTIMER_MSEC, 1);
|
|
|
|
uint16_t status;
|
|
int res;
|
|
|
|
/* read the status after start measurement command */
|
|
if ((res = _status(dev, &status)) != SHT3X_OK) {
|
|
return res;
|
|
}
|
|
|
|
if (status & SHT3X_STATUS_REG_CMD) {
|
|
DEBUG_DEV("start measurement failed, "
|
|
"SHT3X_CMD_MEASURE were not executed", dev);
|
|
return -SHT3X_ERROR_MEASURE_CMD_INV;
|
|
}
|
|
}
|
|
|
|
dev->meas_start_time = ztimer_now(ZTIMER_MSEC);
|
|
dev->meas_duration = SHT3X_MEAS_DURATION_MS[dev->repeat];
|
|
dev->meas_started = true;
|
|
|
|
return SHT3X_OK;
|
|
}
|
|
|
|
static int _get_raw_data(sht3x_dev_t* dev, uint8_t* raw_data)
|
|
{
|
|
int res = SHT3X_OK;
|
|
|
|
/* start the measurement if it has not started yet */
|
|
if (!dev->meas_started &&
|
|
(res = _start_measurement (dev)) != SHT3X_OK) {
|
|
return res;
|
|
}
|
|
|
|
/* determine the time elapsed since the start of current measurement cycle */
|
|
uint32_t elapsed = ztimer_now(ZTIMER_MSEC) - dev->meas_start_time;
|
|
if (elapsed < dev->meas_duration) {
|
|
/* if necessary, wait until the measurement results become available */
|
|
ztimer_sleep(ZTIMER_MSEC, dev->meas_duration - elapsed);
|
|
}
|
|
|
|
/* send fetch command in any periodic mode (mode > 0) before read raw data */
|
|
if (dev->mode != SHT3X_SINGLE_SHOT &&
|
|
_send_command(dev, SHT3X_CMD_FETCH_DATA) != SHT3X_OK) {
|
|
DEBUG_DEV("could not send SHT3X_CMD_FETCH_DATA to sensor", dev);
|
|
return -SHT3X_ERROR_I2C;
|
|
}
|
|
|
|
/* read raw data */
|
|
if (_read_data(dev, raw_data, SHT3X_RAW_DATA_SIZE) != SHT3X_OK) {
|
|
DEBUG_DEV("could not read raw sensor data", dev);
|
|
return -SHT3X_ERROR_I2C;
|
|
}
|
|
|
|
/* in single shot mode update dmeasurement started flag of the driver */
|
|
if (dev->mode == SHT3X_SINGLE_SHOT) {
|
|
dev->meas_started = false;
|
|
}
|
|
/* start next measurement cycle in periodic modes */
|
|
else {
|
|
dev->meas_start_time = ztimer_now(ZTIMER_MSEC);
|
|
dev->meas_duration = SHT3X_MEASURE_PERIOD[dev->mode];
|
|
}
|
|
|
|
/* check temperature crc */
|
|
if (_crc8(raw_data,2) != raw_data[2]) {
|
|
DEBUG_DEV("CRC check for temperature data failed", dev);
|
|
return -SHT3X_ERROR_CRC;
|
|
}
|
|
|
|
/* check humidity crc */
|
|
if (_crc8(raw_data+3,2) != raw_data[5]) {
|
|
DEBUG_DEV("CRC check for humidity data failed", dev);
|
|
return -SHT3X_ERROR_CRC;
|
|
}
|
|
|
|
return SHT3X_OK;
|
|
}
|
|
|
|
static int _compute_values (uint8_t* raw_data, int16_t* temp, int16_t* hum)
|
|
{
|
|
if (temp) {
|
|
uint32_t _tmp = ((uint32_t)raw_data[0] << 8) + raw_data[1]; /* S_T */
|
|
_tmp *= 17500; /* S_T x 175 scaled to hundredths of degree */
|
|
_tmp >>= 16; /* S_T x 175 / 65535 (shift inaccuracy < resolution) */
|
|
_tmp -= 4500; /* S_T x 175 / 65535 - 45 */
|
|
*temp = _tmp;
|
|
}
|
|
|
|
if (hum) {
|
|
uint32_t _tmp = ((uint32_t)raw_data[3] << 8) + raw_data[4]; /* S_RH */
|
|
_tmp *= 10000; /* S_RH x 100 scaled to hundredths of degree */
|
|
_tmp >>= 16; /* S_RH x 100 / 65535 (shift inaccuracy < resolution) */
|
|
*hum = _tmp;
|
|
}
|
|
|
|
return SHT3X_OK;
|
|
}
|
|
|
|
static int _send_command(sht3x_dev_t* dev, uint16_t cmd)
|
|
{
|
|
ASSERT_PARAM (dev != NULL);
|
|
|
|
int res;
|
|
|
|
uint8_t data[2] = { cmd >> 8, cmd & 0xff };
|
|
DEBUG_DEV("send command 0x%02x%02x", dev, data[0], data[1]);
|
|
|
|
i2c_acquire(dev->i2c_dev);
|
|
res = i2c_write_bytes(dev->i2c_dev, dev->i2c_addr, (const void*)data, 2, 0);
|
|
i2c_release(dev->i2c_dev);
|
|
|
|
if (res != 0) {
|
|
DEBUG_DEV("could not send command 0x%02x%02x to sensor, reason=%d",
|
|
dev, data[0], data[1], res);
|
|
return -SHT3X_ERROR_I2C;
|
|
}
|
|
|
|
return SHT3X_OK;
|
|
}
|
|
|
|
static int _read_data(sht3x_dev_t* dev, uint8_t *data, uint8_t len)
|
|
{
|
|
int res;
|
|
|
|
i2c_acquire(dev->i2c_dev);
|
|
res = i2c_read_bytes(dev->i2c_dev, dev->i2c_addr, (void*)data, len, 0);
|
|
i2c_release(dev->i2c_dev);
|
|
|
|
if (res == 0) {
|
|
if (IS_ACTIVE(ENABLE_DEBUG)) {
|
|
printf("[sht3x] %s bus=%d addr=%02x: read following bytes: ",
|
|
__func__, dev->i2c_dev, dev->i2c_addr);
|
|
for (int i=0; i < len; i++) {
|
|
printf("%02x ", data[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
else {
|
|
DEBUG_DEV("could not read %d bytes from sensor, reason %d",
|
|
dev, len, res);
|
|
return -SHT3X_ERROR_I2C;
|
|
}
|
|
|
|
return SHT3X_OK;
|
|
}
|
|
|
|
static int _reset (sht3x_dev_t* dev)
|
|
{
|
|
ASSERT_PARAM (dev != NULL);
|
|
|
|
DEBUG_DEV("", dev);
|
|
|
|
/*
|
|
* Sensor can only be soft reset in idle mode. Therefore, we
|
|
* send a break and wait 1 ms. After that the sensor should be
|
|
* in idle mode. We don't check I2C errors at this moment.
|
|
*/
|
|
_send_command(dev, SHT3X_CMD_BREAK);
|
|
ztimer_sleep(ZTIMER_MSEC, 1);
|
|
|
|
/* send the soft-reset command */
|
|
if (_send_command(dev, SHT3X_CMD_RESET) != SHT3X_OK) {
|
|
DEBUG_DEV("reset failed, could not send SHT3X_CMD_RESET", dev);
|
|
return -SHT3X_ERROR_I2C;
|
|
}
|
|
|
|
/* wait for 2 ms, the time needed to restart (according to datasheet 0.5 ms) */
|
|
ztimer_sleep(ZTIMER_MSEC, 2);
|
|
|
|
/* send reset command */
|
|
if (_send_command(dev, SHT3X_CMD_CLEAR_STATUS) != SHT3X_OK) {
|
|
DEBUG_DEV("reset failed, could not send SHT3X_CMD_CLEAR_STATUS", dev);
|
|
return -SHT3X_ERROR_I2C;
|
|
}
|
|
|
|
/* sensor needs some time to process the command */
|
|
ztimer_sleep(ZTIMER_MSEC, 1);
|
|
|
|
uint16_t status;
|
|
int res = SHT3X_OK;
|
|
|
|
/* read the status after reset */
|
|
if ((res = _status(dev, &status)) != SHT3X_OK) {
|
|
return res;
|
|
}
|
|
|
|
/* status register should be 0x0000 after a reset */
|
|
if (status != 0) {
|
|
DEBUG_DEV("reset failed, sensor status is status=%04x", dev, status);
|
|
return -SHT3X_ERROR_STATUS;
|
|
}
|
|
|
|
DEBUG_DEV("sensor status=%04x", dev, status);
|
|
|
|
return res;
|
|
}
|
|
|
|
static int _status (sht3x_dev_t* dev, uint16_t* status)
|
|
{
|
|
ASSERT_PARAM (dev != NULL);
|
|
ASSERT_PARAM (status != NULL);
|
|
|
|
DEBUG_DEV("", dev);
|
|
|
|
uint8_t data[3];
|
|
|
|
/* read sensor status */
|
|
if (_send_command(dev, SHT3X_CMD_STATUS) != SHT3X_OK) {
|
|
DEBUG_DEV("could not send SHT3X_CMD_STATUS to sensor", dev);
|
|
return -SHT3X_ERROR_I2C;
|
|
}
|
|
if (_read_data(dev, data, 3) != SHT3X_OK) {
|
|
DEBUG_DEV("could not read status data from sensor", dev);
|
|
return -SHT3X_ERROR_I2C;
|
|
}
|
|
|
|
/* check status crc */
|
|
if (_crc8(data,2) != data[2]) {
|
|
DEBUG_DEV("CRC check for status failed", dev);
|
|
return -SHT3X_ERROR_CRC;
|
|
}
|
|
|
|
*status = (data[0] << 8 | data[1]) & SHT3X_STATUS_REG_MASK;
|
|
DEBUG_DEV("status=%02x", dev, *status);
|
|
return SHT3X_OK;
|
|
}
|