229 lines
4.9 KiB
C
229 lines
4.9 KiB
C
/*
|
|
* Copyright (c) 2021 Leonard Pollak
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT sensirion_sht4x
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/drivers/sensor.h>
|
|
#include <zephyr/sys/__assert.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <zephyr/sys/crc.h>
|
|
|
|
#include <zephyr/drivers/sensor/sht4x.h>
|
|
#include "sht4x.h"
|
|
|
|
LOG_MODULE_REGISTER(SHT4X, CONFIG_SENSOR_LOG_LEVEL);
|
|
|
|
static uint8_t sht4x_compute_crc(uint16_t value)
|
|
{
|
|
uint8_t buf[2];
|
|
|
|
sys_put_be16(value, buf);
|
|
|
|
return crc8(buf, 2, SHT4X_CRC_POLY, SHT4X_CRC_INIT, false);
|
|
}
|
|
|
|
static int sht4x_write_command(const struct device *dev, uint8_t cmd)
|
|
{
|
|
const struct sht4x_config *cfg = dev->config;
|
|
uint8_t tx_buf[1] = { cmd };
|
|
|
|
return i2c_write_dt(&cfg->bus, tx_buf, sizeof(tx_buf));
|
|
}
|
|
|
|
static int sht4x_read_sample(const struct device *dev,
|
|
uint16_t *t_sample,
|
|
uint16_t *rh_sample)
|
|
{
|
|
const struct sht4x_config *cfg = dev->config;
|
|
uint8_t rx_buf[6];
|
|
int rc;
|
|
|
|
rc = i2c_read_dt(&cfg->bus, rx_buf, sizeof(rx_buf));
|
|
if (rc < 0) {
|
|
LOG_ERR("Failed to read data from device.");
|
|
return rc;
|
|
}
|
|
|
|
*t_sample = sys_get_be16(rx_buf);
|
|
if (sht4x_compute_crc(*t_sample) != rx_buf[2]) {
|
|
LOG_ERR("Invalid CRC for T.");
|
|
return -EIO;
|
|
}
|
|
|
|
*rh_sample = sys_get_be16(&rx_buf[3]);
|
|
if (sht4x_compute_crc(*rh_sample) != rx_buf[5]) {
|
|
LOG_ERR("Invalid CRC for RH.");
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* public API for handling the heater */
|
|
int sht4x_fetch_with_heater(const struct device *dev)
|
|
{
|
|
struct sht4x_data *data = dev->data;
|
|
int rc;
|
|
|
|
rc = sht4x_write_command(dev,
|
|
heater_cmd[data->heater_power][data->heater_duration]);
|
|
if (rc < 0) {
|
|
LOG_ERR("Failed to start measurement.");
|
|
return rc;
|
|
}
|
|
|
|
k_sleep(K_MSEC(heater_wait_ms[data->heater_duration]));
|
|
|
|
rc = sht4x_read_sample(dev, &data->t_sample, &data->rh_sample);
|
|
if (rc < 0) {
|
|
LOG_ERR("Failed to fetch data.");
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sht4x_sample_fetch(const struct device *dev,
|
|
enum sensor_channel chan)
|
|
{
|
|
const struct sht4x_config *cfg = dev->config;
|
|
struct sht4x_data *data = dev->data;
|
|
int rc;
|
|
|
|
if (chan != SENSOR_CHAN_ALL &&
|
|
chan != SENSOR_CHAN_AMBIENT_TEMP &&
|
|
chan != SENSOR_CHAN_HUMIDITY) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
rc = sht4x_write_command(dev, measure_cmd[cfg->repeatability]);
|
|
if (rc < 0) {
|
|
LOG_ERR("Failed to start measurement.");
|
|
return rc;
|
|
}
|
|
|
|
k_sleep(K_USEC(measure_wait_us[cfg->repeatability]));
|
|
|
|
rc = sht4x_read_sample(dev, &data->t_sample, &data->rh_sample);
|
|
if (rc < 0) {
|
|
LOG_ERR("Failed to fetch data.");
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sht4x_channel_get(const struct device *dev,
|
|
enum sensor_channel chan,
|
|
struct sensor_value *val)
|
|
{
|
|
const struct sht4x_data *data = dev->data;
|
|
|
|
/*
|
|
* See datasheet "Conversion of Signal Output" section
|
|
* for more details on processing sample data.
|
|
*/
|
|
if (chan == SENSOR_CHAN_AMBIENT_TEMP) {
|
|
int64_t tmp;
|
|
|
|
tmp = data->t_sample * 175;
|
|
val->val1 = (int32_t)(tmp / 0xFFFF) - 45;
|
|
val->val2 = ((tmp % 0xFFFF) * 1000000) / 0xFFFF;
|
|
} else if (chan == SENSOR_CHAN_HUMIDITY) {
|
|
uint64_t tmp;
|
|
|
|
tmp = data->rh_sample * 125U;
|
|
val->val1 = (uint32_t)(tmp / 0xFFFF) - 6U;
|
|
val->val2 = (tmp % 0xFFFF) * 15625U / 1024U;
|
|
} else {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sht4x_attr_set(const struct device *dev,
|
|
enum sensor_channel chan,
|
|
enum sensor_attribute attr,
|
|
const struct sensor_value *val)
|
|
{
|
|
struct sht4x_data *data = dev->data;
|
|
|
|
if (val->val1 < 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch ((enum sensor_attribute_sht4x)attr) {
|
|
case SENSOR_ATTR_SHT4X_HEATER_POWER:
|
|
if (val->val1 > SHT4X_HEATER_POWER_IDX_MAX) {
|
|
return -EINVAL;
|
|
}
|
|
data->heater_power = val->val1;
|
|
break;
|
|
case SENSOR_ATTR_SHT4X_HEATER_DURATION:
|
|
if (val->val1 > SHT4X_HEATER_DURATION_IDX_MAX) {
|
|
return -EINVAL;
|
|
}
|
|
data->heater_duration = val->val1;
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sht4x_init(const struct device *dev)
|
|
{
|
|
const struct sht4x_config *cfg = dev->config;
|
|
int rc = 0;
|
|
|
|
if (!device_is_ready(cfg->bus.bus)) {
|
|
LOG_ERR("Device not ready.");
|
|
return -ENODEV;
|
|
}
|
|
|
|
rc = sht4x_write_command(dev, SHT4X_CMD_RESET);
|
|
if (rc < 0) {
|
|
LOG_ERR("Failed to reset the device.");
|
|
return rc;
|
|
}
|
|
|
|
k_sleep(K_MSEC(SHT4X_RESET_WAIT_MS));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const struct sensor_driver_api sht4x_api = {
|
|
.sample_fetch = sht4x_sample_fetch,
|
|
.channel_get = sht4x_channel_get,
|
|
.attr_set = sht4x_attr_set,
|
|
};
|
|
|
|
#define SHT4X_INIT(n) \
|
|
static struct sht4x_data sht4x_data_##n; \
|
|
\
|
|
static const struct sht4x_config sht4x_config_##n = { \
|
|
.bus = I2C_DT_SPEC_INST_GET(n), \
|
|
.repeatability = DT_INST_PROP(n, repeatability) \
|
|
}; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(n, \
|
|
sht4x_init, \
|
|
NULL, \
|
|
&sht4x_data_##n, \
|
|
&sht4x_config_##n, \
|
|
POST_KERNEL, \
|
|
CONFIG_SENSOR_INIT_PRIORITY, \
|
|
&sht4x_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(SHT4X_INIT)
|