esp-open-rtos/extras/sht3x/sht3x.c
Gunar Schorcht 803931f2e2 Driver for Sensirion SHT3x sensor added
This is a driver for Sensirion SHT3x temperature and humidity sensors
connected via I2C.

This commit is a rebasing and contains some interface changes based on
the review from @ourairquality.
2017-10-12 15:42:38 +02:00

442 lines
13 KiB
C

/*
* Driver for Sensirion SHT3x digital temperature and humidity sensor
* connected to I2C
*
* Part of esp-open-rtos
*
* ----------------------------------------------------------------
*
* The BSD License (3-clause license)
*
* Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Driver for Sensirion SHT3x digital temperature and humity sensor
* connected to I2C
*
* Part of esp-open-rtos
*/
#include <string.h>
#include "sht3x.h"
#include "FreeRTOS.h"
#include "espressif/esp_common.h"
#include "espressif/sdk_private.h"
#define SHT3x_STATUS_CMD 0xF32D
#define SHT3x_CLEAR_STATUS_CMD 0x3041
#define SHT3x_RESET_CMD 0x30A2
#define SHT3x_FETCH_DATA_CMD 0xE000
#define SHT3x_HEATER_OFF_CMD 0x3066
uint16_t SHT3x_MEASURE_CMD[6][3] = { {0x2c06,0x2c0d,0x2c10}, // [SINGLE_SHOT][H,M,L]
{0x2032,0x2024,0x202f}, // [PERIODIC_05][H,M,L]
{0x2130,0x2126,0x212d}, // [PERIODIC_05][H,M,L]
{0x2236,0x2220,0x222b}, // [PERIODIC_05][H,M,L]
{0x2234,0x2322,0x2329}, // [PERIODIC_05][H,M,L]
{0x2737,0x2721,0x272a} }; // [PERIODIC_05][H,M,L]
int32_t SHT3x_MEASURE_DURATION[3] = {30,20,20}; // [High, Medium, Low]
#ifdef SHT3x_DEBUG
#define debug(s, f, ...) printf("%s %s: " s "\n", "SHT3x", f, ## __VA_ARGS__)
#define debug_dev(s, f, d, ...) printf("%s %s: bus %d, addr %02x - " s "\n", "SHT3x", f, d->bus, d->addr, ## __VA_ARGS__)
#else
#define debug(s, f, ...)
#define debug_dev(s, f, d, ...)
#endif
#define error(s, f, ...) printf("%s %s: " s "\n", "SHT3x", f, ## __VA_ARGS__)
#define error_dev(s, f, d, ...) printf("%s %s: bus %d, addr %02x - " s "\n", "SHT3x", f, d->bus, d->addr, ## __VA_ARGS__)
/** Forward declaration of function for internal use */
static bool sht3x_send_command (sht3x_sensor_t*, uint16_t);
static bool sht3x_read_data (sht3x_sensor_t*, uint8_t*, uint32_t);
static bool sht3x_get_status (sht3x_sensor_t*, uint16_t*);
static uint8_t crc8 (uint8_t data[], int len);
/** ------------------------------------------------ */
bool sht3x_init_driver()
{
return true;
}
sht3x_sensor_t* sht3x_init_sensor(uint8_t bus, uint8_t addr)
{
sht3x_sensor_t* dev;
if ((dev = malloc (sizeof(sht3x_sensor_t))) == NULL)
return NULL;
// init sensor data structure
dev->bus = bus;
dev->addr = addr;
dev->mode = single_shot;
dev->repeatability = high;
dev->meas_started = false;
dev->meas_start_time = 0;
dev->active = true;
uint16_t status;
// soft-reset including status check leads to i2c problems.
// if (!sht3x_reset (dev))
if (!sht3x_get_status (dev, & status))
{
error_dev ("could not reset or check the status of the sensor", __FUNCTION__, dev);
free(dev);
return NULL;
}
// clear sensor status register
if (!sht3x_send_command(dev, SHT3x_CLEAR_STATUS_CMD))
{
error_dev ("could not clear sensor status", __FUNCTION__, dev);
free(dev);
return NULL;
}
// check the again the status after clear status command
if (!sht3x_get_status(dev, &status))
{
error_dev ("could not get sensor status", __FUNCTION__, dev);
free(dev);
return NULL;
}
debug_dev ("sensor initialized", __FUNCTION__, dev);
return dev;
}
int32_t sht3x_start_measurement (sht3x_sensor_t* dev, sht3x_mode_t mode)
{
if (!dev || !dev->active) return -1;
dev->error_code = SHT3x_OK;
// return remaining time when measurement is already running
if (dev->meas_started)
{
debug_dev ("measurement already started", __FUNCTION__, dev);
dev->error_code = SHT3x_MEAS_ALREADY_RUNNING;
return sht3x_is_measuring (dev);
}
dev->mode = mode;
// start measurement according to selected mode and return an duration estimate
if (sht3x_send_command(dev, SHT3x_MEASURE_CMD[dev->mode][dev->repeatability]))
{
dev->meas_start_time = sdk_system_get_time ();
dev->meas_started = true;
dev->meas_first = true;
return SHT3x_MEASURE_DURATION[dev->repeatability]; // in ms
}
dev->error_code |= SHT3x_SEND_MEAS_CMD_FAILED;
return -1; // on error
}
// returns 0 when finished, -1 on error, remaining measurement time otherwise
int32_t sht3x_is_measuring (sht3x_sensor_t* dev)
{
if (!dev || !dev->active) return -1;
dev->error_code = SHT3x_OK;
if (!dev->meas_started)
{
error_dev ("measurement not started", __FUNCTION__, dev);
dev->error_code = SHT3x_MEAS_NOT_STARTED;
return -1;
}
// computation is necessary in periodic mode only for first measurement
// in single shot mode every measurment is the first one
if (!dev->meas_first)
return 0;
uint32_t start_time = dev->meas_start_time ; // in us
uint32_t system_time = sdk_system_get_time(); // in us
uint32_t elapsed_time;
if (system_time < start_time)
// in case of timer overflow
elapsed_time = (UINT32_MAX - start_time + system_time) / 1000; // in ms
else
// normal case
elapsed_time = (system_time - start_time) / 1000; // in ms
if (elapsed_time >= SHT3x_MEASURE_DURATION[dev->repeatability])
return 0;
else
return SHT3x_MEASURE_DURATION[dev->repeatability] - elapsed_time;
}
bool sht3x_get_raw_data(sht3x_sensor_t* dev, sht3x_raw_data_t raw_data)
{
if (!dev || !dev->active || !raw_data) return false;
dev->error_code = SHT3x_OK;
if (sht3x_is_measuring (dev) == -1)
{
error_dev ("measurement is still running", __FUNCTION__, dev);
return false;
}
if (dev->mode == single_shot &&
sht3x_read_data(dev, raw_data, sizeof(sht3x_raw_data_t)))
{
debug_dev ("single shot data available", __FUNCTION__, dev);
dev->meas_started = false;
}
else if (dev->mode != single_shot &&
sht3x_send_command(dev, SHT3x_FETCH_DATA_CMD) &&
sht3x_read_data(dev, raw_data, sizeof(sht3x_raw_data_t)))
{
debug_dev ("periodic data available", __FUNCTION__, dev);
dev->meas_first = false;
}
else
{
error_dev ("read raw data failed", __FUNCTION__, dev);
dev->error_code |= SHT3x_READ_RAW_DATA_FAILED;
return false;
}
if (crc8(raw_data,2) != raw_data[2])
{
error_dev ("CRC check for temperature data failed", __FUNCTION__, dev);
dev->error_code |= SHT3x_WRONG_CRC_TEMPERATURE;
return false;
}
if (crc8(raw_data+3,2) != raw_data[5])
{
error_dev ("CRC check for humidity data failed", __FUNCTION__, dev);
dev->error_code |= SHT3x_WRONG_CRC_HUMIDITY;
return false;
}
return true;
}
bool sht3x_compute_values (sht3x_raw_data_t raw_data, sht3x_values_t* values)
{
if (!raw_data || !values) return false;
values->temperature = ((((raw_data[0] * 256.0) + raw_data[1]) * 175) / 65535.0) - 45;
values->humidity = ((((raw_data[3] * 256.0) + raw_data[4]) * 100) / 65535.0);
return true;
}
bool sht3x_get_results (sht3x_sensor_t* dev, sht3x_values_t* values)
{
if (!dev || !dev->active || !values) return false;
sht3x_raw_data_t raw_data;
if (!sht3x_get_raw_data (dev, raw_data))
return false;
return sht3x_compute_values (raw_data, values);
}
/* Functions for internal use only */
static bool sht3x_send_command(sht3x_sensor_t* dev, uint16_t cmd)
{
if (!dev || !dev->active) return false;
uint8_t data[2] = { cmd >> 8, cmd & 0xff };
debug_dev ("send command MSB=%02x LSB=%02x", __FUNCTION__, dev, data[0], data[1]);
int err;
int count = 10;
// in case i2c is busy, try to write up to ten times and 100 ms
// tested with a task that is disturbing by using i2c bus almost all the time
while ((err=i2c_slave_write(dev->bus, dev->addr, 0, data, 2)) == -EBUSY && count--)
vTaskDelay (10 / portTICK_PERIOD_MS);
if (err)
{
dev->error_code |= (err == -EBUSY) ? SHT3x_I2C_BUSY : SHT3x_I2C_SEND_CMD_FAILED;
error_dev ("i2c error %d on write command %02x", __FUNCTION__, dev, err, cmd);
return false;
}
return true;
}
static bool sht3x_read_data(sht3x_sensor_t* dev, uint8_t *data, uint32_t len)
{
if (!dev || !dev->active) return false;
int err;
int count = 10;
// in case i2c is busy, try to read up to ten times and 100 ms
while ((err=i2c_slave_read(dev->bus, dev->addr, 0, data, len)) == -EBUSY && count--)
vTaskDelay (10 / portTICK_PERIOD_MS);
if (err)
{
dev->error_code |= (err == -EBUSY) ? SHT3x_I2C_BUSY : SHT3x_I2C_READ_FAILED;
error_dev ("error %d on read %d byte", __FUNCTION__, dev, err, len);
return false;
}
# ifdef SHT3x_DEBUG
printf("SHT3x %s: bus %d, addr %02x - read following bytes: ",
__FUNCTION__, dev->bus, dev->addr);
for (int i=0; i < len; i++)
printf("%02x ", data[i]);
printf("\n");
# endif
return true;
}
/*
static bool sht3x_reset (sht3x_sensor_t* dev)
{
if (!dev || !dev->active) return false;
debug_dev ("soft-reset triggered", __FUNCTION__, dev);
dev->error_code = SHT3x_OK;
// send reset command
if (!sht3x_send_command(dev, SHT3x_RESET_CMD))
{
dev->error_code |= SHT3x_SEND_RESET_CMD_FAILED;
return false;
}
// wait for small amount of time needed (according to datasheet 0.5ms)
vTaskDelay (20 / portTICK_PERIOD_MS);
uint16_t status;
// check the status after reset
if (!sht3x_get_status(dev, &status))
return false;
return true;
}
*/
static bool sht3x_get_status (sht3x_sensor_t* dev, uint16_t* status)
{
if (!dev || !dev->active || !status) return false;
dev->error_code = SHT3x_OK;
uint8_t data[3];
if (!sht3x_send_command(dev, SHT3x_STATUS_CMD) || !sht3x_read_data(dev, data, 3))
{
dev->error_code |= SHT3x_STATUS_CMD_FAILED;
return false;
}
*status = data[0] << 8 | data[1];
debug_dev ("status=%02x", __FUNCTION__, dev, *status);
return true;
}
static uint8_t* crc8_table; // lookup table with precomputed crc values
static bool crc8_first_time = true; // indicator whether table has still to be created
static void generate_crc8_table()
{
const uint8_t g_polynom = 0x31;
for (int i=0; i < 256; i++)
{
uint8_t value = (uint8_t)i;
for (uint8_t bit = 0; bit < 8; bit++)
{
bool xor = value & 0x80;
value = value << 1;
value = xor ? value ^ g_polynom : value;
}
crc8_table[i] = value;
}
}
static uint8_t crc8 (uint8_t data[], int len)
{
// generate crc lookup table first time it is called
if (crc8_first_time)
{
crc8_first_time = false;
crc8_table = malloc(256);
generate_crc8_table ();
}
// initialization value
uint8_t crc = 0xff;
// iterate over all bytes
for (int i=0; i < len; i++)
{
uint8_t b = data[i];
uint8_t data = (uint8_t)(b ^ crc);
crc = (uint8_t)(crc8_table[data]);
}
return crc;
}