A driver for the Sensirion SHT40/SHT41/SHT45 temperature and humidity

sensor.  An example of this chip is:

https://www.adafruit.com/product/4885

This is a lower cost chip that provides higher then usual precision
according to the data sheet.  This driver supports all of the published
functions that the chip has.
This commit is contained in:
brad 2021-10-03 17:27:02 +00:00
parent 0eda81aab2
commit ee2dd9d79d
11 changed files with 1130 additions and 6 deletions

View File

@ -1,4 +1,4 @@
# $NetBSD: mi,v 1.1725 2021/08/01 21:56:26 andvar Exp $
# $NetBSD: mi,v 1.1726 2021/10/03 17:27:02 brad Exp $
#
# Note: don't delete entries from here - mark them as "obsolete" instead.
#
@ -1723,6 +1723,7 @@
./usr/share/man/cat4/shb.0 man-sys-catman .cat
./usr/share/man/cat4/shmif.0 man-sys-catman .cat
./usr/share/man/cat4/shpcic.0 man-sys-catman .cat
./usr/share/man/cat4/sht4xtemp.0 man-sys-catman .cat
./usr/share/man/cat4/si.0 man-sys-catman .cat
./usr/share/man/cat4/si70xxtemp.0 man-sys-catman .cat
./usr/share/man/cat4/siisata.0 man-sys-catman .cat
@ -4896,6 +4897,7 @@
./usr/share/man/html4/shb.html man-sys-htmlman html
./usr/share/man/html4/shmif.html man-sys-htmlman html
./usr/share/man/html4/shpcic.html man-sys-htmlman html
./usr/share/man/html4/sht4xtemp.html man-sys-htmlman html
./usr/share/man/html4/si.html man-sys-htmlman html
./usr/share/man/html4/si70xxtemp.html man-sys-htmlman html
./usr/share/man/html4/siisata.html man-sys-htmlman html
@ -7975,6 +7977,7 @@
./usr/share/man/man4/shb.4 man-sys-man .man
./usr/share/man/man4/shmif.4 man-sys-man .man
./usr/share/man/man4/shpcic.4 man-sys-man .man
./usr/share/man/man4/sht4xtemp.4 man-sys-man .man
./usr/share/man/man4/si.4 man-sys-man .man
./usr/share/man/man4/si70xxtemp.4 man-sys-man .man
./usr/share/man/man4/siisata.4 man-sys-man .man

View File

@ -1,4 +1,4 @@
# $NetBSD: mi,v 1.146 2021/09/11 16:10:37 pgoyette Exp $
# $NetBSD: mi,v 1.147 2021/10/03 17:27:02 brad Exp $
#
# Note: don't delete entries from here - mark them as "obsolete" instead.
#
@ -397,6 +397,8 @@
./@MODULEDIR@/securelevel/securelevel.kmod modules-base-kernel kmod
./@MODULEDIR@/sequencer modules-base-kernel kmod
./@MODULEDIR@/sequencer/sequencer.kmod modules-base-kernel kmod
./@MODULEDIR@/sht4xtemp modules-base-kernel kmod
./@MODULEDIR@/sht4xtemp/sht4xtemp.kmod modules-base-kernel kmod
./@MODULEDIR@/si70xxtemp modules-base-kernel kmod
./@MODULEDIR@/si70xxtemp/si70xxtemp.kmod modules-base-kernel kmod
./@MODULEDIR@/skipjack modules-base-kernel kmod

View File

@ -1,4 +1,4 @@
# $NetBSD: Makefile,v 1.715 2021/08/01 21:56:27 andvar Exp $
# $NetBSD: Makefile,v 1.716 2021/10/03 17:27:02 brad Exp $
# @(#)Makefile 8.1 (Berkeley) 6/18/93
MAN= aac.4 ac97.4 acardide.4 aceride.4 acphy.4 \
@ -56,7 +56,7 @@ MAN= aac.4 ac97.4 acardide.4 aceride.4 acphy.4 \
rnd.4 route.4 rs5c372rtc.4 rtk.4 rtsx.4 rtw.4 rtwn.4 rum.4 run.4 \
s390rtc.4 satalink.4 sbus.4 schide.4 \
scsi.4 sctp.4 sd.4 se.4 seeprom.4 sem.4 \
ses.4 sf.4 sfb.4 sgsmix.4 shb.4 shmif.4 shpcic.4 si70xxtemp.4 \
ses.4 sf.4 sfb.4 sgsmix.4 shb.4 shmif.4 shpcic.4 sht4xtemp.4 si70xxtemp.4 \
siisata.4 siop.4 sip.4 siside.4 sk.4 sl.4 slide.4 \
sm.4 smscphy.4 smsh.4 sn.4 sony.4 spc.4 speaker.4 spif.4 sqphy.4 \
srt.4 ss.4 \

View File

@ -0,0 +1,93 @@
.\" $NetBSD: sht4xtemp.4,v 1.1 2021/10/03 17:27:02 brad Exp $
.\"
.\" Copyright (c) 2021 Brad Spencer <brad@anduin.eldar.org>
.\"
.\" Permission to use, copy, modify, and distribute this software for any
.\" purpose with or without fee is hereby granted, provided that the above
.\" copyright notice and this permission notice appear in all copies.
.\"
.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd September 28th, 2021
.Dt SHT4XTEMP 4
.Os
.Sh NAME
.Nm sht4xtemp
.Nd Driver for Sensirion SHT40/SHT41/SHT45 sensor chip via I2C bus
.Sh SYNOPSIS
.Cd "sht4xtemp* at iic? addr 0x44"
.Sh DESCRIPTION
The
.Nm
driver provides measurements from the SHT40/SHT41/SHT45 humidity/temperature
sensors via the
.Xr envsys 4
framework.
The
.Nm
.Ar addr
argument selects the address at the
.Xr iic 4
bus.
The resolution, heater controls and crc validity can be changed through
.Xr sysctl 8
nodes.
.Sh SYSCTL VARIABLES
The following
.Xr sysctl 3
variables are provided:
.Bl -tag -width indent
.It hw.sht4xtemp0.resolutions
Lists the resolutions supported by the driver and chip.
.It hw.sht4xtemp0.resolution
Set the resolution, or number of bits, used for %RH and temperature.
Use one of the strings listed in hw.sht4xtemp.resolutions.
.It hw.sht4xtemp0.ignorecrc
If set, the crc calculation for %RH and temperature will be ignored.
.It hw.sht4xtemp0.heateron
Turn the heater on and off. Please note that the heater is turned on right
before the measurement and runs for a pulse width of time. Then the measurement
is taken and the heater is turned off. There is no way to keep the heater running
with this chip.
.It hw.sht4xtemp0.heaterstrength
From 1 to 3, the amount of energy put into the heater.
The higher the number, the more power used.
.It hw.sht4xtemp0.heaterpulses
Lists the valid heater pulses supported by the driver and chip.
.It hw.sht4xtemp0.heaterpulse
Set the heater pulse length. Use one of the strings listed in
hw.sht4xtemp.heaterpulses
.It hw.sht4xtemp0.debug
If the driver is compiled with
.Dv SI70XX_DEBUG ,
this node will appear and can be used to set the debugging level.
.It hw.sht4xtemp0.readattempts
To read %RH or temperature the chip requires that the command be sent,
then a delay must be observed before a read can be done to get the values
back. The delays are documented in the datasheet for the chip.
The driver will attempt to read back the values readattempts number of
times.
The default is 10 which should be more than enough for most purposes.
.El
.Sh SEE ALSO
.Xr envsys 4 ,
.Xr iic 4 ,
.Xr envstat 8 ,
.Xr sysctl 8
.Sh HISTORY
The
.Nm
driver first appeared in
.Nx 10.0 .
.Sh AUTHORS
.An -nosplit
The
.Nm
driver was written by
.An Brad Spencer Aq Mt brad@anduin.eldar.org .

View File

@ -1,4 +1,4 @@
# $NetBSD: files.i2c,v 1.116 2021/07/27 20:23:41 macallan Exp $
# $NetBSD: files.i2c,v 1.117 2021/10/03 17:27:02 brad Exp $
obsolete defflag opt_i2cbus.h I2C_SCAN
define i2cbus { }
@ -395,6 +395,11 @@ device cwfg: sysmon_envsys
attach cwfg at iic
file dev/i2c/cwfg.c cwfg
# Sensirion SHT40/SHT41/SHT45 Temperature and Humidity sensor
device sht4xtemp
attach sht4xtemp at iic
file dev/i2c/sht4x.c sht4xtemp
# Philips PCA955x GPIO
device pcagpio: leds
attach pcagpio at iic

886
sys/dev/i2c/sht4x.c Normal file
View File

@ -0,0 +1,886 @@
/* $NetBSD: sht4x.c,v 1.1 2021/10/03 17:27:02 brad Exp $ */
/*
* Copyright (c) 2021 Brad Spencer <brad@anduin.eldar.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sht4x.c,v 1.1 2021/10/03 17:27:02 brad Exp $");
/*
Driver for the Sensirion SHT40/SHT41/SHT45
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/mutex.h>
#include <dev/sysmon/sysmonvar.h>
#include <dev/i2c/i2cvar.h>
#include <dev/i2c/sht4xreg.h>
#include <dev/i2c/sht4xvar.h>
static uint8_t sht4x_crc(uint8_t *, size_t);
static int sht4x_poke(i2c_tag_t, i2c_addr_t, bool);
static int sht4x_match(device_t, cfdata_t, void *);
static void sht4x_attach(device_t, device_t, void *);
static int sht4x_detach(device_t, int);
static void sht4x_refresh(struct sysmon_envsys *, envsys_data_t *);
static int sht4x_verify_sysctl(SYSCTLFN_ARGS);
static int sht4x_verify_sysctl_resolution(SYSCTLFN_ARGS);
static int sht4x_verify_sysctl_heateron(SYSCTLFN_ARGS);
static int sht4x_verify_sysctl_heatervalue(SYSCTLFN_ARGS);
static int sht4x_verify_sysctl_heaterpulse(SYSCTLFN_ARGS);
#define SHT4X_DEBUG
#ifdef SHT4X_DEBUG
#define DPRINTF(s, l, x) \
do { \
if (l <= s->sc_sht4xdebug) \
printf x; \
} while (/*CONSTCOND*/0)
#else
#define DPRINTF(s, l, x)
#endif
CFATTACH_DECL_NEW(sht4xtemp, sizeof(struct sht4x_sc),
sht4x_match, sht4x_attach, sht4x_detach, NULL);
static struct sht4x_sensor sht4x_sensors[] = {
{
.desc = "humidity",
.type = ENVSYS_SRELHUMIDITY,
},
{
.desc = "temperature",
.type = ENVSYS_STEMP,
}
};
/* The typical delays are documented in the datasheet for the chip.
There is no need to be very accurate with these, just rough estimates
will work fine.
*/
static struct sht4x_timing sht4x_timings[] = {
{
.cmd = SHT4X_READ_SERIAL,
.typicaldelay = 5000,
},
{
.cmd = SHT4X_SOFT_RESET,
.typicaldelay = 1000,
},
{
.cmd = SHT4X_MEASURE_HIGH_PRECISION,
.typicaldelay = 8000,
},
{
.cmd = SHT4X_MEASURE_MEDIUM_PRECISION,
.typicaldelay = 4000,
},
{
.cmd = SHT4X_MEASURE_LOW_PRECISION,
.typicaldelay = 2000,
},
{
.cmd = SHT4X_MEASURE_HIGH_PRECISION_HIGH_HEAT_1_S,
.typicaldelay = 1000000,
},
{
.cmd = SHT4X_MEASURE_HIGH_PRECISION_MEDIUM_HEAT_1_S,
.typicaldelay = 1000000,
},
{
.cmd = SHT4X_MEASURE_HIGH_PRECISION_LOW_HEAT_1_S,
.typicaldelay = 1000000,
},
{
.cmd = SHT4X_MEASURE_HIGH_PRECISION_HIGH_HEAT_TENTH_S,
.typicaldelay = 100000,
},
{
.cmd = SHT4X_MEASURE_HIGH_PRECISION_MEDIUM_HEAT_TENTH_S,
.typicaldelay = 100000,
},
{
.cmd = SHT4X_MEASURE_HIGH_PRECISION_LOW_HEAT_TENTH_S,
.typicaldelay = 100000,
}
};
/* Used when the heater is not on to find the command to use for the
* measurement.
*/
static struct sht4x_resolution sht4x_resolutions[] = {
{
.text = "high",
.cmd = SHT4X_MEASURE_HIGH_PRECISION,
},
{
.text = "medium",
.cmd = SHT4X_MEASURE_MEDIUM_PRECISION,
},
{
.text = "low",
.cmd = SHT4X_MEASURE_LOW_PRECISION,
}
};
static const char sht4x_resolution_names[] =
"high, medium, low";
static struct sht4x_heaterpulse sht4x_heaterpulses[] = {
{
.length = "short",
},
{
.length = "long",
}
};
/* This is consulted when the heater is on for which command is to be
used for the measurement.
*/
static struct sht4x_heateron_command sht4x_heateron_commands[] = {
{
.heatervalue = 1,
.pulselength = "short",
.cmd = SHT4X_MEASURE_HIGH_PRECISION_LOW_HEAT_TENTH_S,
},
{
.heatervalue = 2,
.pulselength = "short",
.cmd = SHT4X_MEASURE_HIGH_PRECISION_MEDIUM_HEAT_TENTH_S,
},
{
.heatervalue = 3,
.pulselength = "short",
.cmd = SHT4X_MEASURE_HIGH_PRECISION_HIGH_HEAT_TENTH_S,
},
{
.heatervalue = 1,
.pulselength = "long",
.cmd = SHT4X_MEASURE_HIGH_PRECISION_LOW_HEAT_1_S,
},
{
.heatervalue = 2,
.pulselength = "long",
.cmd = SHT4X_MEASURE_HIGH_PRECISION_MEDIUM_HEAT_1_S,
},
{
.heatervalue = 3,
.pulselength = "long",
.cmd = SHT4X_MEASURE_HIGH_PRECISION_HIGH_HEAT_1_S,
}
};
static const char sht4x_heaterpulse_names[] =
"short, long";
int
sht4x_verify_sysctl(SYSCTLFN_ARGS)
{
int error, t;
struct sysctlnode node;
node = *rnode;
t = *(int *)rnode->sysctl_data;
node.sysctl_data = &t;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
if (t < 0)
return EINVAL;
*(int *)rnode->sysctl_data = t;
return 0;
}
/* None of the heater and resolutions sysctls change anything on the chip in
real time. The values set are used to send different commands depending on
how they are set up.
What this implies is that the chip could be reset and the driver would not care.
*/
int
sht4x_verify_sysctl_resolution(SYSCTLFN_ARGS)
{
char buf[SHT4X_RES_NAME];
struct sht4x_sc *sc;
struct sysctlnode node;
int error = 0;
size_t i;
node = *rnode;
sc = node.sysctl_data;
(void) memcpy(buf, sc->sc_resolution, SHT4X_RES_NAME);
node.sysctl_data = buf;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
for (i = 0; i < __arraycount(sht4x_resolutions); i++) {
if (strncmp(node.sysctl_data, sht4x_resolutions[i].text,
SHT4X_RES_NAME) == 0) {
break;
}
}
if (i == __arraycount(sht4x_resolutions))
return EINVAL;
(void) memcpy(sc->sc_resolution, node.sysctl_data, SHT4X_RES_NAME);
return error;
}
int
sht4x_verify_sysctl_heateron(SYSCTLFN_ARGS)
{
int error;
bool t;
struct sht4x_sc *sc;
struct sysctlnode node;
node = *rnode;
sc = node.sysctl_data;
t = sc->sc_heateron;
node.sysctl_data = &t;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
sc->sc_heateron = t;
return error;
}
int
sht4x_verify_sysctl_heatervalue(SYSCTLFN_ARGS)
{
int error = 0, t;
struct sht4x_sc *sc;
struct sysctlnode node;
node = *rnode;
sc = node.sysctl_data;
t = sc->sc_heaterval;
node.sysctl_data = &t;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return (error);
if (t < 1 || t > 3)
return (EINVAL);
sc->sc_heaterval = t;
return error;
}
int
sht4x_verify_sysctl_heaterpulse(SYSCTLFN_ARGS)
{
char buf[SHT4X_PULSE_NAME];
struct sht4x_sc *sc;
struct sysctlnode node;
int error = 0;
size_t i;
node = *rnode;
sc = node.sysctl_data;
(void) memcpy(buf, sc->sc_heaterpulse, SHT4X_PULSE_NAME);
node.sysctl_data = buf;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL)
return error;
for (i = 0; i < __arraycount(sht4x_heaterpulses); i++) {
if (strncmp(node.sysctl_data, sht4x_heaterpulses[i].length,
SHT4X_RES_NAME) == 0) {
break;
}
}
if (i == __arraycount(sht4x_heaterpulses))
return EINVAL;
(void) memcpy(sc->sc_heaterpulse, node.sysctl_data, SHT4X_PULSE_NAME);
return error;
}
static int
sht4x_cmddelay(uint8_t cmd)
{
int r = -1;
for(int i = 0;i < __arraycount(sht4x_timings);i++) {
if (cmd == sht4x_timings[i].cmd) {
r = sht4x_timings[i].typicaldelay;
break;
}
}
if (r == -1) {
panic("Bad command look up in cmd delay: cmd: %d\n",cmd);
}
return r;
}
static int
sht4x_cmd(i2c_tag_t tag, i2c_addr_t addr, uint8_t *cmd,
uint8_t clen, uint8_t *buf, size_t blen, int readattempts)
{
int error;
int cmddelay;
error = iic_exec(tag,I2C_OP_WRITE_WITH_STOP,addr,cmd,clen,NULL,0,0);
/* Every command returns something except for the soft reset
which returns nothing. This chip is also nice in that pretty
much every command that returns something does it in the same way.
*/
if (error == 0 && cmd[0] != SHT4X_SOFT_RESET) {
cmddelay = sht4x_cmddelay(cmd[0]);
delay(cmddelay);
for (int aint = 0; aint < readattempts; aint++) {
error = iic_exec(tag,I2C_OP_READ_WITH_STOP,addr,NULL,0,buf,blen,0);
if (error == 0)
break;
delay(1000);
}
}
return error;
}
static int
sht4x_cmdr(struct sht4x_sc *sc, uint8_t cmd, uint8_t *buf, size_t blen)
{
return sht4x_cmd(sc->sc_tag, sc->sc_addr, &cmd, 1, buf, blen, sc->sc_readattempts);
}
static uint8_t
sht4x_crc(uint8_t * data, size_t size)
{
uint8_t crc = 0xFF;
for (size_t i = 0; i < size; i++) {
crc ^= data[i];
for (size_t j = 8; j > 0; j--) {
if (crc & 0x80)
crc = (crc << 1) ^ 0x131;
else
crc <<= 1;
}
}
return crc;
}
static int
sht4x_poke(i2c_tag_t tag, i2c_addr_t addr, bool matchdebug)
{
uint8_t reg = SHT4X_READ_SERIAL;
uint8_t buf[6];
int error;
error = sht4x_cmd(tag, addr, &reg, 1, buf, 6, 10);
if (matchdebug) {
printf("poke X 1: %d\n", error);
}
return error;
}
static int
sht4x_sysctl_init(struct sht4x_sc *sc)
{
int error;
const struct sysctlnode *cnode;
int sysctlroot_num;
if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode,
0, CTLTYPE_NODE, device_xname(sc->sc_dev),
SYSCTL_DESCR("sht4x controls"), NULL, 0, NULL, 0, CTL_HW,
CTL_CREATE, CTL_EOL)) != 0)
return error;
sysctlroot_num = cnode->sysctl_num;
#ifdef SHT4X_DEBUG
if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_INT, "debug",
SYSCTL_DESCR("Debug level"), sht4x_verify_sysctl, 0,
&sc->sc_sht4xdebug, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
CTL_EOL)) != 0)
return error;
#endif
if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_INT, "readattempts",
SYSCTL_DESCR("The number of times to attempt to read the values"),
sht4x_verify_sysctl, 0, &sc->sc_readattempts, 0, CTL_HW,
sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
return error;
if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode,
CTLFLAG_READONLY, CTLTYPE_STRING, "resolutions",
SYSCTL_DESCR("Valid resolutions"), 0, 0,
__UNCONST(sht4x_resolution_names),
sizeof(sht4x_resolution_names) + 1,
CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
return error;
if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_STRING, "resolution",
SYSCTL_DESCR("Resolution of RH and Temp"),
sht4x_verify_sysctl_resolution, 0, (void *) sc,
SHT4X_RES_NAME, CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
return error;
if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_BOOL, "ignorecrc",
SYSCTL_DESCR("Ignore the CRC byte"), NULL, 0, &sc->sc_ignorecrc,
0, CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
return error;
if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_BOOL, "heateron",
SYSCTL_DESCR("Heater on"), sht4x_verify_sysctl_heateron, 0,
(void *)sc, 0, CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
return error;
if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_INT, "heaterstrength",
SYSCTL_DESCR("Heater strength 1 to 3"),
sht4x_verify_sysctl_heatervalue, 0, (void *)sc, 0, CTL_HW,
sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
return error;
if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode,
CTLFLAG_READONLY, CTLTYPE_STRING, "heaterpulses",
SYSCTL_DESCR("Valid heater pulse lengths"), 0, 0,
__UNCONST(sht4x_heaterpulse_names),
sizeof(sht4x_heaterpulse_names) + 1,
CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
return error;
if ((error = sysctl_createv(&sc->sc_sht4xlog, 0, NULL, &cnode,
CTLFLAG_READWRITE, CTLTYPE_STRING, "heaterpulse",
SYSCTL_DESCR("Heater pulse length"),
sht4x_verify_sysctl_heaterpulse, 0, (void *) sc,
SHT4X_RES_NAME, CTL_HW, sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
return error;
return 0;
}
static int
sht4x_match(device_t parent, cfdata_t match, void *aux)
{
struct i2c_attach_args *ia = aux;
int error, match_result;
const bool matchdebug = false;
if (iic_use_direct_match(ia, match, NULL, &match_result))
return match_result;
/* indirect config - check for configured address */
if (ia->ia_addr != SHT4X_TYPICAL_ADDR)
return 0;
/*
* Check to see if something is really at this i2c address. This will
* keep phantom devices from appearing
*/
if (iic_acquire_bus(ia->ia_tag, 0) != 0) {
if (matchdebug)
printf("in match acquire bus failed\n");
return 0;
}
error = sht4x_poke(ia->ia_tag, ia->ia_addr, matchdebug);
iic_release_bus(ia->ia_tag, 0);
return error == 0 ? I2C_MATCH_ADDRESS_AND_PROBE : 0;
}
static void
sht4x_attach(device_t parent, device_t self, void *aux)
{
struct sht4x_sc *sc;
struct i2c_attach_args *ia;
int error, i;
int ecount = 0;
uint8_t buf[6];
uint8_t sncrcpt1, sncrcpt2;
ia = aux;
sc = device_private(self);
sc->sc_dev = self;
sc->sc_tag = ia->ia_tag;
sc->sc_addr = ia->ia_addr;
sc->sc_sht4xdebug = 0;
strlcpy(sc->sc_resolution,"high",SHT4X_RES_NAME);
sc->sc_readattempts = 10;
sc->sc_ignorecrc = false;
sc->sc_heateron = false;
sc->sc_heaterval = 1;
strlcpy(sc->sc_heaterpulse,"short",SHT4X_PULSE_NAME);
sc->sc_sme = NULL;
aprint_normal("\n");
mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_NONE);
sc->sc_numsensors = __arraycount(sht4x_sensors);
if ((sc->sc_sme = sysmon_envsys_create()) == NULL) {
aprint_error_dev(self,
"Unable to create sysmon structure\n");
sc->sc_sme = NULL;
return;
}
if ((error = sht4x_sysctl_init(sc)) != 0) {
aprint_error_dev(self, "Can't setup sysctl tree (%d)\n", error);
goto out;
}
error = iic_acquire_bus(sc->sc_tag, 0);
if (error) {
aprint_error_dev(self, "Could not acquire iic bus: %d\n",
error);
goto out;
}
error = sht4x_cmdr(sc, SHT4X_SOFT_RESET, NULL, 0);
if (error != 0)
aprint_error_dev(self, "Reset failed: %d\n", error);
delay(1000); /* 1 ms max */
error = sht4x_cmdr(sc, SHT4X_READ_SERIAL, buf, 6);
if (error) {
aprint_error_dev(self, "Failed to read serial number: %d\n",
error);
ecount++;
}
sncrcpt1 = sht4x_crc(&buf[0],2);
sncrcpt2 = sht4x_crc(&buf[3],2);
DPRINTF(sc, 2, ("%s: read serial number values: %02x%02x - %02x, %02x%02x - %02x ; %02x %02x\n",
device_xname(sc->sc_dev), buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], sncrcpt1, sncrcpt2));
iic_release_bus(sc->sc_tag, 0);
if (error != 0) {
aprint_error_dev(self, "Unable to setup device\n");
goto out;
}
for (i = 0; i < sc->sc_numsensors; i++) {
strlcpy(sc->sc_sensors[i].desc, sht4x_sensors[i].desc,
sizeof(sc->sc_sensors[i].desc));
sc->sc_sensors[i].units = sht4x_sensors[i].type;
sc->sc_sensors[i].state = ENVSYS_SINVALID;
DPRINTF(sc, 2, ("%s: registering sensor %d (%s)\n", __func__, i,
sc->sc_sensors[i].desc));
error = sysmon_envsys_sensor_attach(sc->sc_sme,
&sc->sc_sensors[i]);
if (error) {
aprint_error_dev(self,
"Unable to attach sensor %d: %d\n", i, error);
goto out;
}
}
sc->sc_sme->sme_name = device_xname(sc->sc_dev);
sc->sc_sme->sme_cookie = sc;
sc->sc_sme->sme_refresh = sht4x_refresh;
DPRINTF(sc, 2, ("sht4x_attach: registering with envsys\n"));
if (sysmon_envsys_register(sc->sc_sme)) {
aprint_error_dev(self,
"unable to register with sysmon\n");
sysmon_envsys_destroy(sc->sc_sme);
sc->sc_sme = NULL;
return;
}
/* There is no documented way to ask the chip what version it is. This
is likely fine as the only apparent difference is in how precise the
measurements will be. The actual conversation with the chip is
identical no matter which one you are talking to.
*/
aprint_normal_dev(self, "Sensirion SHT40/SHT41/SHT45, "
"Serial number: %02x%02x%02x%02x%s",
buf[0], buf[1], buf[3], buf[4],
(sncrcpt1 == buf[2] && sncrcpt2 == buf[5]) ? "\n" : " (bad crc)\n");
return;
out:
sysmon_envsys_destroy(sc->sc_sme);
sc->sc_sme = NULL;
}
/* If you use the heater on this chip, there is no documented choice but to use
the highest precision. If the heater is not in use one may select different
precisions or repeatability for the measurement.
Further, if the heater is used, it will only be active during the measurement.
The use of the heater will add delay to the measurement as chip will not
return anything until the heater pulse time is over.
*/
static uint8_t
sht4x_compute_measure_command(char *resolution, bool heateron,
int heatervalue, char *heaterpulse)
{
int i;
uint8_t r;
if (heateron == false) {
for (i = 0; i < __arraycount(sht4x_resolutions); i++) {
if (strncmp(resolution, sht4x_resolutions[i].text,
SHT4X_RES_NAME) == 0) {
r = sht4x_resolutions[i].cmd;
break;
}
}
if (i == __arraycount(sht4x_resolutions))
panic("Heater off could not find command for resolution: %s\n",resolution);
} else {
for (i = 0; i < __arraycount(sht4x_heateron_commands); i++) {
if (heatervalue == sht4x_heateron_commands[i].heatervalue &&
strncmp(heaterpulse, sht4x_heateron_commands[i].pulselength,
SHT4X_PULSE_NAME) == 0) {
r = sht4x_heateron_commands[i].cmd;
break;
}
}
if (i == __arraycount(sht4x_heateron_commands))
panic("Heater on could not find command for heatervalue, heaterpulse: %d %s\n",
heatervalue,heaterpulse);
}
return r;
}
static void
sht4x_refresh(struct sysmon_envsys * sme, envsys_data_t * edata)
{
struct sht4x_sc *sc;
sc = sme->sme_cookie;
int error;
uint8_t rawdata[6];
uint8_t measurement_command;
edata->state = ENVSYS_SINVALID;
mutex_enter(&sc->sc_mutex);
error = iic_acquire_bus(sc->sc_tag, 0);
if (error) {
DPRINTF(sc, 2, ("%s: Could not acquire i2c bus: %x\n",
device_xname(sc->sc_dev), error));
goto out;
}
/*
The documented conversion calculations for the raw values are as follows:
%RH = (-6 + 125 * rawvalue / 65535)
T in Celsius = (-45 + 175 * rawvalue / 65535)
It follows then:
T in Kelvin = (229.15 + 175 * rawvalue / 65535)
given the relationship between Celsius and Kelvin.
What follows reorders the calculation a bit and scales it up to avoid
the use of any floating point. All that would really have to happen
is a scale up to 10^6 for the sysenv framework, which wants
temperature in micro-kelvin and percent relative humidity scaled up
10^6, but since this conversion uses 64 bits due to intermediate
values that are bigger than 32 bits the conversion first scales up to
10^9 and the scales back down by 10^3 at the end. This preserves some
precision in the conversion that would otherwise be lost.
*/
measurement_command = sht4x_compute_measure_command(sc->sc_resolution,
sc->sc_heateron, sc->sc_heaterval, sc->sc_heaterpulse);
DPRINTF(sc, 2, ("%s: Measurement command: %02x\n",
device_xname(sc->sc_dev), measurement_command));
/* This chip is pretty nice in that all commands are the same length and
return the same result. What is not so nice is that you can not ask
for temperature and humidity independently.
The result will be 16 bits of raw temperature and a CRC byte followed
by 16 bits of humidity followed by a CRC byte.
*/
error = sht4x_cmdr(sc,measurement_command,rawdata,6);
if (error == 0) {
DPRINTF(sc, 2, ("%s: Raw data: %02x%02x %02x - %02x%02x %02x\n",
device_xname(sc->sc_dev), rawdata[0], rawdata[1], rawdata[2],
rawdata[3], rawdata[4], rawdata[5]));
uint8_t *svalptr;
uint64_t svalue;
int64_t v1;
uint64_t v2;
uint64_t d1 = 65535;
uint64_t mul1;
uint64_t mul2;
uint64_t div1 = 10000;
uint64_t q;
switch (edata->sensor) {
case SHT4X_TEMP_SENSOR:
svalptr = &rawdata[0];
v1 = 22915; /* this is scaled up already from 229.15 */
v2 = 175;
mul1 = 10000000000;
mul2 = 100000000;
break;
case SHT4X_HUMIDITY_SENSOR:
svalptr = &rawdata[3];
v1 = -6;
v2 = 125;
mul1 = 10000000000;
mul2 = 10000000000;
break;
default:
error = EINVAL;
break;
}
if (error == 0) {
uint8_t testcrc;
/* Fake out the CRC check if being asked to ignore CRC */
if (sc->sc_ignorecrc) {
testcrc = *(svalptr + 2);
} else {
testcrc = sht4x_crc(svalptr,2);
}
if (*(svalptr + 2) == testcrc) {
svalue = *svalptr << 8 | *(svalptr + 1);
DPRINTF(sc, 2, ("%s: Raw sensor 16 bit: %#jx\n",
device_xname(sc->sc_dev), (uintmax_t)svalue));
/* Scale up */
svalue = svalue * mul1;
v1 = v1 * mul2;
/* Perform the conversion */
q = ((v2 * (svalue / d1)) + v1) / div1;
DPRINTF(sc, 2, ("%s: Computed sensor: %#jx\n",
device_xname(sc->sc_dev), (uintmax_t)q));
/* The results will fit in 32 bits, so nothing will be lost */
edata->value_cur = (uint32_t) q;
edata->state = ENVSYS_SVALID;
} else {
error = EINVAL;
}
}
}
if (error) {
DPRINTF(sc, 2, ("%s: Failed to get new status in refresh %d\n",
device_xname(sc->sc_dev), error));
}
iic_release_bus(sc->sc_tag, 0);
out:
mutex_exit(&sc->sc_mutex);
}
static int
sht4x_detach(device_t self, int flags)
{
struct sht4x_sc *sc;
sc = device_private(self);
mutex_enter(&sc->sc_mutex);
/* Remove the sensors */
if (sc->sc_sme != NULL) {
sysmon_envsys_unregister(sc->sc_sme);
sc->sc_sme = NULL;
}
mutex_exit(&sc->sc_mutex);
/* Remove the sysctl tree */
sysctl_teardown(&sc->sc_sht4xlog);
/* Remove the mutex */
mutex_destroy(&sc->sc_mutex);
return 0;
}
MODULE(MODULE_CLASS_DRIVER, sht4xtemp, "i2cexec,sysmon_envsys");
#ifdef _MODULE
#include "ioconf.c"
#endif
static int
sht4xtemp_modcmd(modcmd_t cmd, void *opaque)
{
switch (cmd) {
case MODULE_CMD_INIT:
#ifdef _MODULE
return config_init_component(cfdriver_ioconf_sht4xtemp,
cfattach_ioconf_sht4xtemp, cfdata_ioconf_sht4xtemp);
#else
return 0;
#endif
case MODULE_CMD_FINI:
#ifdef _MODULE
return config_fini_component(cfdriver_ioconf_sht4xtemp,
cfattach_ioconf_sht4xtemp, cfdata_ioconf_sht4xtemp);
#else
return 0;
#endif
default:
return ENOTTY;
}
}

44
sys/dev/i2c/sht4xreg.h Normal file
View File

@ -0,0 +1,44 @@
/* $NetBSD: sht4xreg.h,v 1.1 2021/10/03 17:27:02 brad Exp $ */
/*
* Copyright (c) 2021 Brad Spencer <brad@anduin.eldar.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _DEV_I2C_SHT4XREG_H_
#define _DEV_I2C_SHT4XREG_H_
#define SHT4X_TYPICAL_ADDR 0x44
#define SHT4X_READ_SERIAL 0x89
#define SHT4X_SOFT_RESET 0x94
/* If you do not use the heater, you can take measurements at a couple
of different percisions */
#define SHT4X_MEASURE_HIGH_PRECISION 0xFD
#define SHT4X_MEASURE_MEDIUM_PRECISION 0xF6
#define SHT4X_MEASURE_LOW_PRECISION 0xE0
/* The SHT4X chip only support the heater when reading with the
highest percision and then only when the measurement is happening.
You can have the heater on for 1 second or 1 tenth of a second.
After the measurement the heater will switch itself off */
#define SHT4X_MEASURE_HIGH_PRECISION_HIGH_HEAT_1_S 0x39
#define SHT4X_MEASURE_HIGH_PRECISION_HIGH_HEAT_TENTH_S 0x32
#define SHT4X_MEASURE_HIGH_PRECISION_MEDIUM_HEAT_1_S 0x2F
#define SHT4X_MEASURE_HIGH_PRECISION_MEDIUM_HEAT_TENTH_S 0x24
#define SHT4X_MEASURE_HIGH_PRECISION_LOW_HEAT_1_S 0x1E
#define SHT4X_MEASURE_HIGH_PRECISION_LOW_HEAT_TENTH_S 0x15
#endif

72
sys/dev/i2c/sht4xvar.h Normal file
View File

@ -0,0 +1,72 @@
/* $NetBSD: sht4xvar.h,v 1.1 2021/10/03 17:27:02 brad Exp $ */
/*
* Copyright (c) 2021 Brad Spencer <brad@anduin.eldar.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _DEV_I2C_SHT4XVAR_H_
#define _DEV_I2C_SHT4XVAR_H_
#define SHT4X_NUM_SENSORS 2
#define SHT4X_HUMIDITY_SENSOR 0
#define SHT4X_TEMP_SENSOR 1
#define SHT4X_RES_NAME 7
#define SHT4X_PULSE_NAME 6
struct sht4x_sc {
int sc_sht4xdebug;
device_t sc_dev;
i2c_tag_t sc_tag;
i2c_addr_t sc_addr;
kmutex_t sc_mutex;
int sc_numsensors;
struct sysmon_envsys *sc_sme;
struct sysctllog *sc_sht4xlog;
envsys_data_t sc_sensors[SHT4X_NUM_SENSORS];
bool sc_ignorecrc;
char sc_resolution[SHT4X_RES_NAME];
int sc_readattempts;
bool sc_heateron;
int sc_heaterval;
char sc_heaterpulse[SHT4X_PULSE_NAME];
};
struct sht4x_sensor {
const char *desc;
enum envsys_units type;
};
struct sht4x_timing {
uint8_t cmd;
int typicaldelay;
};
struct sht4x_resolution {
const char *text;
uint8_t cmd;
};
struct sht4x_heaterpulse {
const char *length;
};
struct sht4x_heateron_command {
int heatervalue;
const char *pulselength;
uint8_t cmd;
};
#endif

View File

@ -1,4 +1,4 @@
# $NetBSD: Makefile,v 1.256 2021/09/25 17:55:37 maya Exp $
# $NetBSD: Makefile,v 1.257 2021/10/03 17:27:02 brad Exp $
.include <bsd.own.mk>
@ -69,6 +69,7 @@ SUBDIR+= hfs
SUBDIR+= hythygtemp
SUBDIR+= si70xxtemp
SUBDIR+= am2315temp
SUBDIR+= sht4xtemp
SUBDIR+= i2cexec
SUBDIR+= i2c_bitbang
SUBDIR+= if_agr

View File

@ -0,0 +1,11 @@
.include "../Makefile.inc"
.PATH: ${S}/dev/i2c
KMOD= sht4xtemp
IOCONF= sht4xtemp.ioconf
SRCS= sht4x.c
WARNS= 3
.include <bsd.kmodule.mk>

View File

@ -0,0 +1,7 @@
ioconf sht4xtemp
include "conf/files"
pseudo-root iic*
sht4xtemp* at iic? addr 0x44