mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
474 lines
14 KiB
C
474 lines
14 KiB
C
/*
|
|
* Copyright (C) 2016,2017,2018 Kees Bakker, SODAQ
|
|
* 2017 George Psimenos
|
|
* 2018 Steffen Robertz
|
|
* 2022 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_sht2x
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Device driver implementation for the SHT2x humidity and
|
|
* temperature humidity sensor.
|
|
*
|
|
* @author Kees Bakker <kees@sodaq.com>
|
|
* @author George Psimenos <gp7g14@soton.ac.uk>
|
|
* @author Steffen Robertz <steffen.robertz@rwth-aachen.de>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include <math.h>
|
|
|
|
#include "checksum/crc8.h"
|
|
#include "log.h"
|
|
#include "periph/i2c.h"
|
|
#include "ztimer.h"
|
|
|
|
#include "sht2x.h"
|
|
#include "sht2x_params.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
/**
|
|
* @brief The number of retries when doing a polled measurement
|
|
*/
|
|
#define MAX_RETRIES 20
|
|
|
|
/**
|
|
* @brief A few helper macros
|
|
*/
|
|
#define _BUS (dev->params.i2c_dev)
|
|
#define _ADDR (dev->params.i2c_addr)
|
|
|
|
typedef enum {
|
|
temp_hold_cmd = 0xE3, /**< trigger temp measurement, hold master */
|
|
hum_hold_cmd = 0xE5, /**< trigger humidity measurement, hold master */
|
|
write_user_cmd = 0xE6, /**< write user register */
|
|
read_user_cmd = 0xE7, /**< read user register */
|
|
temp_no_hold_cmd = 0xF3, /**< trigger temp measurement, no hold master (poll) */
|
|
hum_no_hold_cmd = 0xF5, /**< trigger humidity measurement, no hold master (poll) */
|
|
soft_reset_cmd = 0xFE, /**< soft reset */
|
|
} cmd_t;
|
|
|
|
typedef enum {
|
|
SHT2X_MEASURE_TEMP,
|
|
SHT2X_MEASURE_RH,
|
|
} measure_type_t;
|
|
|
|
/**
|
|
* @brief Register addresses to read SHT2x Identification Code.
|
|
*/
|
|
static const uint16_t first_mem_addr = 0x0FFA;
|
|
static const uint16_t second_mem_addr = 0xC9FC;
|
|
|
|
static int read_sensor(const sht2x_t* dev, cmd_t command, uint16_t *val);
|
|
static int read_sensor_poll(const sht2x_t* dev, cmd_t command, uint16_t *val);
|
|
static uint8_t sht2x_checkcrc(uint8_t data[], uint8_t nbrOfBytes, uint8_t checksum);
|
|
static void sleep_during_temp_measurement(sht2x_res_t res);
|
|
static void sleep_during_hum_measurement(sht2x_res_t resolution);
|
|
|
|
/*---------------------------------------------------------------------------*
|
|
* SHT2x Core API *
|
|
*---------------------------------------------------------------------------*/
|
|
|
|
int sht2x_init(sht2x_t* dev, const sht2x_params_t* params)
|
|
{
|
|
int i2c_result;
|
|
|
|
dev->params = *params;
|
|
if (dev->params.resolution != SHT2X_RES_12_14BIT &&
|
|
dev->params.resolution != SHT2X_RES_8_12BIT &&
|
|
dev->params.resolution != SHT2X_RES_10_13BIT &&
|
|
dev->params.resolution != SHT2X_RES_11_11BIT) {
|
|
return SHT2X_ERR_RES;
|
|
}
|
|
|
|
i2c_result = sht2x_reset(dev);
|
|
if (i2c_result != SHT2X_OK) {
|
|
return SHT2X_ERR_I2C;
|
|
}
|
|
/* wait 15 ms for device to reset */
|
|
ztimer_sleep(ZTIMER_MSEC, 15);
|
|
|
|
uint8_t userreg;
|
|
uint8_t userreg2;
|
|
i2c_result = sht2x_read_userreg(dev, &userreg);
|
|
if (i2c_result != SHT2X_OK) {
|
|
return i2c_result;
|
|
}
|
|
DEBUG("[SHT2x] User Register=%02x\n", userreg);
|
|
|
|
if ((userreg & SHT2X_USER_RESOLUTION_MASK) != (uint8_t)dev->params.resolution) {
|
|
userreg &= ~SHT2X_USER_RESOLUTION_MASK;
|
|
userreg |= (uint8_t)dev->params.resolution;
|
|
i2c_result = sht2x_write_userreg(dev, userreg);
|
|
if (i2c_result != SHT2X_OK) {
|
|
return i2c_result;
|
|
}
|
|
i2c_result = sht2x_read_userreg(dev, &userreg2);
|
|
if (i2c_result != SHT2X_OK) {
|
|
return i2c_result;
|
|
}
|
|
DEBUG("[SHT2x] New User Register=%02x\n", userreg2);
|
|
if (userreg != userreg2) {
|
|
return SHT2X_ERR_USERREG;
|
|
}
|
|
}
|
|
|
|
return SHT2X_OK;
|
|
}
|
|
|
|
int sht2x_reset(sht2x_t* dev)
|
|
{
|
|
int i2c_result;
|
|
cmd_t command = soft_reset_cmd;
|
|
|
|
/* Acquire exclusive access */
|
|
i2c_acquire(_BUS);
|
|
|
|
DEBUG("[SHT2x] write command: addr=%02x cmd=%02x\n", _ADDR, (uint8_t)command);
|
|
i2c_result = i2c_write_byte(_BUS, _ADDR, (uint8_t)command, 0);
|
|
|
|
i2c_release(_BUS);
|
|
|
|
if (i2c_result != 0) {
|
|
return SHT2X_ERR_I2C;
|
|
}
|
|
|
|
return SHT2X_OK;
|
|
}
|
|
|
|
/*
|
|
* Returns temperature in centi DegC.
|
|
*/
|
|
int16_t sht2x_read_temperature(const sht2x_t* dev)
|
|
{
|
|
uint16_t raw_value;
|
|
int i2c_result;
|
|
if (dev->params.measure_mode == SHT2X_MEASURE_MODE_NO_HOLD) {
|
|
i2c_result = read_sensor_poll(dev, temp_no_hold_cmd, &raw_value);
|
|
} else {
|
|
i2c_result = read_sensor(dev, temp_hold_cmd, &raw_value);
|
|
}
|
|
if (i2c_result != SHT2X_OK) {
|
|
return INT16_MIN;
|
|
}
|
|
return ((17572 * raw_value) / 65536) - 4685;
|
|
}
|
|
|
|
/*
|
|
* Returns humidity in centi %RH (i.e. the percentage times 100).
|
|
*/
|
|
uint16_t sht2x_read_humidity(const sht2x_t *dev)
|
|
{
|
|
uint16_t raw_value;
|
|
int i2c_result;
|
|
if (dev->params.measure_mode == SHT2X_MEASURE_MODE_NO_HOLD) {
|
|
i2c_result = read_sensor_poll(dev, hum_no_hold_cmd, &raw_value);
|
|
} else {
|
|
i2c_result = read_sensor(dev, hum_hold_cmd, &raw_value);
|
|
}
|
|
if (i2c_result != SHT2X_OK) {
|
|
return 0;
|
|
}
|
|
return ((12500 * raw_value) / 65536) - 600;
|
|
}
|
|
|
|
static size_t _sht2x_add_ident_byte(uint8_t * buffer, size_t buflen, uint8_t b, size_t ix)
|
|
{
|
|
if (ix < buflen) {
|
|
buffer[ix++] = b;
|
|
}
|
|
return ix;
|
|
}
|
|
|
|
int sht2x_read_ident(const sht2x_t *dev, uint8_t * buffer, size_t buflen)
|
|
{
|
|
uint8_t data1[8]; /* SNB_3, CRC, SNB_2, CRC, SNB_1, CRC, SNB_0, CRC */
|
|
uint8_t data2[6]; /* SNC_1, SNC_0, CRC, SNA_1, SNA_0, CRC */
|
|
size_t ix;
|
|
int res;
|
|
|
|
i2c_acquire(_BUS);
|
|
res = i2c_read_regs(_BUS, _ADDR,
|
|
first_mem_addr, data1, sizeof(data1), I2C_REG16);
|
|
i2c_release(_BUS);
|
|
if (res < 0) {
|
|
return res;
|
|
}
|
|
|
|
DEBUG("[SHT2x] ident (1): %02x %02x %02x %02x\n", data1[0], data1[1], data1[2], data1[3]);
|
|
DEBUG("[SHT2x] ident (1): %02x %02x %02x %02x\n", data1[4], data1[5], data1[6], data1[7]);
|
|
for (size_t ix = 0; ix < sizeof(data1); ix += 2) {
|
|
if (sht2x_checkcrc(&data1[ix], 1, data1[ix + 1]) != 0) {
|
|
DEBUG("[SHT2x] checksum error first (ix=%d)\n", ix);
|
|
return SHT2X_ERR_CRC;
|
|
}
|
|
}
|
|
|
|
i2c_acquire(_BUS);
|
|
res = i2c_read_regs(_BUS, _ADDR,
|
|
second_mem_addr, data2, sizeof(data2), I2C_REG16);
|
|
i2c_release(_BUS);
|
|
if (res < 0) {
|
|
return res;
|
|
}
|
|
DEBUG("[SHT2x] ident (2): %02x %02x %02x\n", data2[0], data2[1], data2[2]);
|
|
DEBUG("[SHT2x] ident (2): %02x %02x %02x\n", data2[3], data2[4], data2[5]);
|
|
for (size_t ix = 0; ix < sizeof(data2); ix += 3) {
|
|
if (sht2x_checkcrc(&data2[ix], 2, data2[ix + 2]) != 0) {
|
|
DEBUG("[SHT2x] checksum error, second (ix=%d)\n", ix);
|
|
return SHT2X_ERR_CRC;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* See Sensirion document Electronic_Identification_Code_SHT2x_V1-1_C2
|
|
*
|
|
* first memory address:
|
|
* SNB_3, CRC, SNB_2, CRC, SNB_1, CRC, SNB_0, CRC,
|
|
* Second memory address:
|
|
* SNC_1, SNC_0, CRC, SNA_1, SNA_0, CRC
|
|
*
|
|
* To assemble the Identification code:
|
|
* SNA_1, SNA_0, SNB_3, SNB_2, SNB_1, SNB_0, SNC_1, SNC_0
|
|
*/
|
|
if (buffer == NULL) {
|
|
return 0;
|
|
}
|
|
ix = 0;
|
|
ix = _sht2x_add_ident_byte(buffer, buflen, data2[3], ix);
|
|
ix = _sht2x_add_ident_byte(buffer, buflen, data2[4], ix);
|
|
ix = _sht2x_add_ident_byte(buffer, buflen, data1[0], ix);
|
|
ix = _sht2x_add_ident_byte(buffer, buflen, data1[2], ix);
|
|
ix = _sht2x_add_ident_byte(buffer, buflen, data1[4], ix);
|
|
ix = _sht2x_add_ident_byte(buffer, buflen, data1[6], ix);
|
|
ix = _sht2x_add_ident_byte(buffer, buflen, data2[0], ix);
|
|
ix = _sht2x_add_ident_byte(buffer, buflen, data2[1], ix);
|
|
return ix;
|
|
}
|
|
|
|
int sht2x_read_userreg(const sht2x_t *dev, uint8_t * userreg)
|
|
{
|
|
cmd_t command = read_user_cmd;
|
|
|
|
if (userreg) {
|
|
int i2c_result;
|
|
DEBUG("[SHT2x] read command: addr=%02x cmd=%02x\n", _ADDR, (uint8_t)command);
|
|
i2c_acquire(_BUS);
|
|
i2c_result = i2c_read_reg(_BUS, _ADDR, (uint8_t)command, userreg, 0);
|
|
i2c_release(_BUS);
|
|
if (i2c_result != 0) {
|
|
return SHT2X_ERR_I2C_READ;
|
|
}
|
|
} else {
|
|
return SHT2X_ERR_OTHER;
|
|
}
|
|
|
|
return SHT2X_OK;
|
|
}
|
|
|
|
int sht2x_write_userreg(const sht2x_t *dev, uint8_t userreg)
|
|
{
|
|
cmd_t command = write_user_cmd;
|
|
int i2c_result;
|
|
DEBUG("[SHT2x] write command: addr=%02x cmd=%02x\n", _ADDR, (uint8_t)command);
|
|
|
|
i2c_acquire(_BUS);
|
|
i2c_result = i2c_write_reg(_BUS, _ADDR, (uint8_t)command, userreg, 0);
|
|
i2c_release(_BUS);
|
|
if (i2c_result != 0) {
|
|
return SHT2X_ERR_I2C;
|
|
}
|
|
|
|
return SHT2X_OK;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Local Functions
|
|
******************************************************************************/
|
|
|
|
/**
|
|
* @brief Read a sensor value from the given SHT2X device
|
|
*
|
|
* @param[in] dev Device descriptor of SHT2X device to read from
|
|
* @param[in] cmd The SHT2x command (hold mode only)
|
|
* @param[out] val The raw sensor value (only valid if no error)
|
|
*
|
|
* @return SHT2X_OK value is returned in @p val
|
|
* @return SHT2X_NODEV if sensor communication failed
|
|
* @return SHT2X_ERR_OTHER if parameters are invalid
|
|
* @return SHT2X_ERR_TIMEDOUT if sensor times out
|
|
* @return SHT2X_ERR_CRC if the checksum is wrong
|
|
*/
|
|
static int read_sensor(const sht2x_t* dev, cmd_t command, uint16_t *val)
|
|
{
|
|
uint8_t buffer[3];
|
|
int i2c_result;
|
|
|
|
/* Acquire exclusive access */
|
|
i2c_acquire(_BUS);
|
|
|
|
DEBUG("[SHT2x] write command: addr=%02x cmd=%02x\n", _ADDR, (uint8_t)command);
|
|
(void)i2c_write_byte(_BUS, _ADDR, (uint8_t)command, 0);
|
|
i2c_result = i2c_read_bytes(_BUS, _ADDR, buffer, sizeof(buffer), 0);
|
|
i2c_release(_BUS);
|
|
if (i2c_result != 0) {
|
|
DEBUG("[Error] Cannot read SHT2x sensor data.\n");
|
|
return SHT2X_ERR_I2C_READ;
|
|
}
|
|
|
|
DEBUG("[SHT2x] read: %02x %02x %02x\n", buffer[0], buffer[1], buffer[2]);
|
|
if (val) {
|
|
*val = (buffer[0] << 8) | buffer[1];
|
|
*val &= ~0x0003; /* clear two low bits (status bits) */
|
|
}
|
|
|
|
if (dev->params.is_crc_enabled) {
|
|
/* byte #3 is the checksum */
|
|
if (sht2x_checkcrc(buffer, 2, buffer[2]) != 0) {
|
|
return SHT2X_ERR_CRC;
|
|
}
|
|
}
|
|
|
|
return SHT2X_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Read a sensor value from the given SHT2X device, polling mode
|
|
*
|
|
* @param[in] dev Device descriptor of SHT2X device to read from
|
|
* @param[in] cmd The SHT2x command (hold mode only)
|
|
* @param[out] val The raw sensor value (only valid if no error)
|
|
*
|
|
* @return SHT2X_OK value is returned in @p val
|
|
* @return SHT2X_NODEV if sensor communication failed
|
|
* @return SHT2X_ERR_OTHER if parameters are invalid
|
|
* @return SHT2X_ERR_TIMEDOUT if sensor times out
|
|
* @return SHT2X_ERR_CRC if the checksum is wrong
|
|
*/
|
|
static int read_sensor_poll(const sht2x_t* dev, cmd_t command, uint16_t *val)
|
|
{
|
|
uint8_t buffer[3];
|
|
int i2c_result;
|
|
|
|
/* acquire the bus for exclusive access */
|
|
i2c_acquire(_BUS);
|
|
|
|
DEBUG("[SHT2x] write command: addr=%02x cmd=%02x\n", _ADDR, (uint8_t)command);
|
|
(void)i2c_write_byte(_BUS, _ADDR, (uint8_t)command, 0);
|
|
/* release the bus for measurement duration */
|
|
i2c_release(_BUS);
|
|
|
|
/* sleep for measurement duration */
|
|
if (command == temp_no_hold_cmd) {
|
|
sleep_during_temp_measurement(dev->params.resolution);
|
|
} else {
|
|
sleep_during_hum_measurement(dev->params.resolution);
|
|
}
|
|
|
|
/* reacquire the bus for exclusive access */
|
|
i2c_acquire(_BUS);
|
|
|
|
uint8_t ix = 0;
|
|
for (; ix < MAX_RETRIES; ix++) {
|
|
i2c_result = i2c_read_bytes(_BUS, _ADDR, buffer, sizeof(buffer), 0);
|
|
if (i2c_result == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
i2c_release(_BUS);
|
|
if (i2c_result != 0) {
|
|
DEBUG("[Error] Cannot read SHT2x sensor data.\n");
|
|
return SHT2X_ERR_I2C_READ;
|
|
}
|
|
|
|
DEBUG("[SHT2x] read: %02x %02x %02x\n", buffer[0], buffer[1], buffer[2]);
|
|
if (val) {
|
|
*val = (buffer[0] << 8) | buffer[1];
|
|
*val &= ~0x0003; /* clear two low bits (status bits) */
|
|
}
|
|
|
|
/* byte #3 is the checksum */
|
|
if (dev->params.is_crc_enabled) {
|
|
if (sht2x_checkcrc(buffer, 2, buffer[2]) != 0) {
|
|
return SHT2X_ERR_CRC;
|
|
}
|
|
}
|
|
|
|
return SHT2X_OK;
|
|
}
|
|
|
|
static const uint8_t POLYNOMIAL = 0x31; /* P(x)=x^8+x^5+x^4+1 = 100110001 */
|
|
/**
|
|
* @brief Calculate 8-Bit checksum with given polynomial
|
|
*/
|
|
static uint8_t sht2x_checkcrc(uint8_t data[], uint8_t nbrOfBytes, uint8_t checksum)
|
|
{
|
|
return crc8(data, nbrOfBytes, POLYNOMIAL, 0) != checksum;
|
|
}
|
|
|
|
/**
|
|
* @brief Sleep during measurement
|
|
*
|
|
* @param[in] res The resolution bits in the User Register
|
|
*
|
|
* @details Sleep for the maximum time it takes to complete the measurement
|
|
* this depends on the resolution and is taken from the datasheet.
|
|
* Measurement time differs for temperature and humidity.
|
|
*
|
|
* @note According to the data sheet, typical times are recommended for
|
|
* calculating energy consumption, while maximum values should be
|
|
* used for calculating waiting times in communication.
|
|
*/
|
|
static void sleep_during_temp_measurement(sht2x_res_t res)
|
|
{
|
|
uint32_t amount_ms = 0;
|
|
|
|
switch (res) {
|
|
case SHT2X_RES_12_14BIT:
|
|
amount_ms = 85;
|
|
break;
|
|
case SHT2X_RES_8_12BIT:
|
|
amount_ms = 22;
|
|
break;
|
|
case SHT2X_RES_10_13BIT:
|
|
amount_ms = 43;
|
|
break;
|
|
case SHT2X_RES_11_11BIT:
|
|
amount_ms = 11;
|
|
break;
|
|
}
|
|
ztimer_sleep(ZTIMER_MSEC, amount_ms);
|
|
}
|
|
|
|
static void sleep_during_hum_measurement(sht2x_res_t resolution)
|
|
{
|
|
uint32_t amount_ms = 0;
|
|
|
|
switch (resolution) {
|
|
case SHT2X_RES_12_14BIT:
|
|
amount_ms = 29;
|
|
break;
|
|
case SHT2X_RES_8_12BIT:
|
|
amount_ms = 4;
|
|
break;
|
|
case SHT2X_RES_10_13BIT:
|
|
amount_ms = 9;
|
|
break;
|
|
case SHT2X_RES_11_11BIT:
|
|
amount_ms = 15;
|
|
break;
|
|
}
|
|
ztimer_sleep(ZTIMER_MSEC, amount_ms);
|
|
}
|