/* * 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 * @file */ #define ENABLE_DEBUG 0 #include "debug.h" #include "log.h" #include #include #include #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; }