Module Name: src
Committed By: brad
Date: Thu Oct 14 13:54:46 UTC 2021
Modified Files:
src/distrib/sets/lists/debug: module.mi
src/distrib/sets/lists/man: mi
src/distrib/sets/lists/modules: mi
src/share/man/man4: Makefile
src/sys/dev/i2c: files.i2c
src/sys/modules: Makefile
Added Files:
src/share/man/man4: sgp40mox.4
src/sys/dev/i2c: sensirion_arch_config.h sensirion_voc_algorithm.c
sensirion_voc_algorithm.h sgp40.c sgp40reg.h sgp40var.h
src/sys/modules/sgp40mox: Makefile sgp40mox.ioconf
Log Message:
A driver for the Sensirion SGP40 MOx gas sensor. An example of this
chip from Adafruit is:
https://www.adafruit.com/product/4829
This is a moderately priced gas sensor that can detect volatile
organic compounds in the air. The driver uses the 3-clause BSD
licensed VOC algorithm provided by Sensirion to turn the raw sensor
metric into a VOC index which can indicate the quality of the air in a
particular indoor environment. All published functions of the chip
are supported and one unpublished feature.
To generate a diff of this commit:
cvs rdiff -u -r1.13 -r1.14 src/distrib/sets/lists/debug/module.mi
cvs rdiff -u -r1.1727 -r1.1728 src/distrib/sets/lists/man/mi
cvs rdiff -u -r1.147 -r1.148 src/distrib/sets/lists/modules/mi
cvs rdiff -u -r1.717 -r1.718 src/share/man/man4/Makefile
cvs rdiff -u -r0 -r1.1 src/share/man/man4/sgp40mox.4
cvs rdiff -u -r1.117 -r1.118 src/sys/dev/i2c/files.i2c
cvs rdiff -u -r0 -r1.1 src/sys/dev/i2c/sensirion_arch_config.h \
src/sys/dev/i2c/sensirion_voc_algorithm.c \
src/sys/dev/i2c/sensirion_voc_algorithm.h src/sys/dev/i2c/sgp40.c \
src/sys/dev/i2c/sgp40reg.h src/sys/dev/i2c/sgp40var.h
cvs rdiff -u -r1.258 -r1.259 src/sys/modules/Makefile
cvs rdiff -u -r0 -r1.1 src/sys/modules/sgp40mox/Makefile \
src/sys/modules/sgp40mox/sgp40mox.ioconf
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/distrib/sets/lists/debug/module.mi
diff -u src/distrib/sets/lists/debug/module.mi:1.13 src/distrib/sets/lists/debug/module.mi:1.14
--- src/distrib/sets/lists/debug/module.mi:1.13 Mon Oct 4 07:04:39 2021
+++ src/distrib/sets/lists/debug/module.mi Thu Oct 14 13:54:46 2021
@@ -1,4 +1,4 @@
-# $NetBSD: module.mi,v 1.13 2021/10/04 07:04:39 brad Exp $
+# $NetBSD: module.mi,v 1.14 2021/10/14 13:54:46 brad Exp $
./usr/libdata/debug/@MODULEDIR@ modules-base-kernel kmod,debug
./usr/libdata/debug/@MODULEDIR@/accf_dataready modules-base-kernel kmod,debug
./usr/libdata/debug/@MODULEDIR@/accf_dataready/accf_dataready.kmod.debug modules-base-kernel kmod,debug
@@ -332,6 +332,8 @@
./usr/libdata/debug/@MODULEDIR@/securelevel/securelevel.kmod.debug modules-base-kernel kmod,debug
./usr/libdata/debug/@MODULEDIR@/sequencer modules-base-kernel kmod,debug
./usr/libdata/debug/@MODULEDIR@/sequencer/sequencer.kmod.debug modules-base-kernel kmod,debug
+./usr/libdata/debug/@MODULEDIR@/sgp40mox modules-base-kernel kmod,debug
+./usr/libdata/debug/@MODULEDIR@/sgp40mox/sgp40mox.kmod.debug modules-base-kernel kmod,debug
./usr/libdata/debug/@MODULEDIR@/sht4xtemp modules-base-kernel kmod,debug
./usr/libdata/debug/@MODULEDIR@/sht4xtemp/sht4xtemp.kmod.debug modules-base-kernel kmod,debug
./usr/libdata/debug/@MODULEDIR@/si70xxtemp modules-base-kernel kmod,debug
Index: src/distrib/sets/lists/man/mi
diff -u src/distrib/sets/lists/man/mi:1.1727 src/distrib/sets/lists/man/mi:1.1728
--- src/distrib/sets/lists/man/mi:1.1727 Tue Oct 12 04:55:19 2021
+++ src/distrib/sets/lists/man/mi Thu Oct 14 13:54:46 2021
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.1727 2021/10/12 04:55:19 msaitoh Exp $
+# $NetBSD: mi,v 1.1728 2021/10/14 13:54:46 brad Exp $
#
# Note: don't delete entries from here - mark them as "obsolete" instead.
#
@@ -1720,6 +1720,7 @@
./usr/share/man/cat4/sgimips/pic.0 man-sys-catman .cat
./usr/share/man/cat4/sgimips/sq.0 man-sys-catman .cat
./usr/share/man/cat4/sgimips/wdsc.0 man-sys-catman .cat
+./usr/share/man/cat4/sgp40mox.0 man-sys-catman .cat
./usr/share/man/cat4/sgsmix.0 man-sys-catman .cat
./usr/share/man/cat4/shb.0 man-sys-catman .cat
./usr/share/man/cat4/shmif.0 man-sys-catman .cat
@@ -4895,6 +4896,7 @@
./usr/share/man/html4/sgimips/pic.html man-sys-htmlman html
./usr/share/man/html4/sgimips/sq.html man-sys-htmlman html
./usr/share/man/html4/sgimips/wdsc.html man-sys-htmlman html
+./usr/share/man/html4/sgp40mox.html man-sys-htmlman html
./usr/share/man/html4/sgsmix.html man-sys-htmlman html
./usr/share/man/html4/shb.html man-sys-htmlman html
./usr/share/man/html4/shmif.html man-sys-htmlman html
@@ -7976,6 +7978,7 @@
./usr/share/man/man4/sgimips/pic.4 man-sys-man .man
./usr/share/man/man4/sgimips/sq.4 man-sys-man .man
./usr/share/man/man4/sgimips/wdsc.4 man-sys-man .man
+./usr/share/man/man4/sgp40mox.4 man-sys-man .man
./usr/share/man/man4/sgsmix.4 man-sys-man .man
./usr/share/man/man4/shb.4 man-sys-man .man
./usr/share/man/man4/shmif.4 man-sys-man .man
Index: src/distrib/sets/lists/modules/mi
diff -u src/distrib/sets/lists/modules/mi:1.147 src/distrib/sets/lists/modules/mi:1.148
--- src/distrib/sets/lists/modules/mi:1.147 Sun Oct 3 17:27:02 2021
+++ src/distrib/sets/lists/modules/mi Thu Oct 14 13:54:46 2021
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.147 2021/10/03 17:27:02 brad Exp $
+# $NetBSD: mi,v 1.148 2021/10/14 13:54:46 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@/sgp40mox modules-base-kernel kmod
+./@MODULEDIR@/sgp40mox/sgp40mox.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
Index: src/share/man/man4/Makefile
diff -u src/share/man/man4/Makefile:1.717 src/share/man/man4/Makefile:1.718
--- src/share/man/man4/Makefile:1.717 Tue Oct 12 04:55:19 2021
+++ src/share/man/man4/Makefile Thu Oct 14 13:54:45 2021
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.717 2021/10/12 04:55:19 msaitoh Exp $
+# $NetBSD: Makefile,v 1.718 2021/10/14 13:54:45 brad Exp $
# @(#)Makefile 8.1 (Berkeley) 6/18/93
MAN= aac.4 ac97.4 acardide.4 aceride.4 acphy.4 \
@@ -56,8 +56,8 @@ MAN= aac.4 ac97.4 acardide.4 aceride.4 a
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 sht4xtemp.4 si70xxtemp.4 \
- siisata.4 siop.4 sip.4 siside.4 sk.4 sl.4 slide.4 \
+ ses.4 sf.4 sfb.4 sgp40mox.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 \
ssdfb.4 st.4 ste.4 stge.4 sti.4 stpcide.4 sv.4 \
Index: src/sys/dev/i2c/files.i2c
diff -u src/sys/dev/i2c/files.i2c:1.117 src/sys/dev/i2c/files.i2c:1.118
--- src/sys/dev/i2c/files.i2c:1.117 Sun Oct 3 17:27:02 2021
+++ src/sys/dev/i2c/files.i2c Thu Oct 14 13:54:46 2021
@@ -1,4 +1,4 @@
-# $NetBSD: files.i2c,v 1.117 2021/10/03 17:27:02 brad Exp $
+# $NetBSD: files.i2c,v 1.118 2021/10/14 13:54:46 brad Exp $
obsolete defflag opt_i2cbus.h I2C_SCAN
define i2cbus { }
@@ -400,6 +400,12 @@ device sht4xtemp
attach sht4xtemp at iic
file dev/i2c/sht4x.c sht4xtemp
+# Sensirion SGP40 MOx gas sensor
+device sgp40mox
+attach sgp40mox at iic
+file dev/i2c/sgp40.c sgp40mox
+file dev/i2c/sensirion_voc_algorithm.c sgp40mox
+
# Philips PCA955x GPIO
device pcagpio: leds
attach pcagpio at iic
Index: src/sys/modules/Makefile
diff -u src/sys/modules/Makefile:1.258 src/sys/modules/Makefile:1.259
--- src/sys/modules/Makefile:1.258 Sat Oct 9 07:01:34 2021
+++ src/sys/modules/Makefile Thu Oct 14 13:54:45 2021
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.258 2021/10/09 07:01:34 ryo Exp $
+# $NetBSD: Makefile,v 1.259 2021/10/14 13:54:45 brad Exp $
.include <bsd.own.mk>
@@ -70,6 +70,7 @@ SUBDIR+= hythygtemp
SUBDIR+= si70xxtemp
SUBDIR+= am2315temp
SUBDIR+= sht4xtemp
+SUBDIR+= sgp40mox
SUBDIR+= i2cexec
SUBDIR+= i2c_bitbang
SUBDIR+= if_agr
Added files:
Index: src/share/man/man4/sgp40mox.4
diff -u /dev/null src/share/man/man4/sgp40mox.4:1.1
--- /dev/null Thu Oct 14 13:54:46 2021
+++ src/share/man/man4/sgp40mox.4 Thu Oct 14 13:54:45 2021
@@ -0,0 +1,107 @@
+.\" $NetBSD: sgp40mox.4,v 1.1 2021/10/14 13:54:45 brad Exp $
+.\"
+.\" Copyright (c) 2021 Brad Spencer <[email protected]>
+.\"
+.\" 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 October 7, 2021
+.Dt SGP40MOX 4
+.Os
+.Sh NAME
+.Nm sgp40mox
+.Nd Driver for Sensirion SGP40 MOx gas sensor
+.Sh SYNOPSIS
+.Cd "sgp40mox* at iic? addr 0x59"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides an air quality measurement from the SGP40
+sensor via the
+.Xr envsys 4
+framework.
+The
+.Nm
+.Ar addr
+argument selects the address at the
+.Xr iic 4
+bus.
+The crc validity and temperature and %RH compensation can be changed through
+.Xr sysctl 8
+nodes.
+.Pp
+In order to calculate the VOC index, the volatile organic compounds index, which
+is the measure of air quality the sensor is polled once a second and the raw sensor
+value is fed into the Sensirion VOC algorithm. This VOC algorithm used in this driver
+is licensed under a 3 clause BSD license and was pulled from the Sensirion Github
+repository at
+.Rs
+.%U https://github.com/Sensirion/embedded-sgp
+.Re
+.Sh SYSCTL VARIABLES
+The following
+.Xr sysctl 3
+variables are provided:
+.Bl -tag -width indent
+.It Li hw.sgp40mox0.compensation.temperature
+This should be set to the temperature in Celsius of the environment that the sensor
+is in. The valid values are from -45 to 130 degrees Celsius.
+.It Li hw.sgp40mox0.compensation.humidity
+This should be set to the %RH of the environment that the sensor is in. The valid
+values are from 0 to 100.
+.Pp
+For the best performance of the VOC algorithm it is important that the temperature
+and %RH compensation values be current and set using the
+.Xr sysctl 3
+variables mentioned above.
+This data will need to be pulled from another source, such as a another sensor in
+the environment that the SGP40 is in.
+.It Li hw.sgp40mox0.ignorecrc
+If set, the crc calculation will be ignored on the calls to the chip for the purposes
+of measurement.
+.It Li hw.sgp40mox0.debug
+If the driver is compiled with
+.Dv SGP40_DEBUG ,
+this node will appear and can be used to set the debugging level.
+.It Li hw.sgp40mox0.readattempts
+To read the air quality metric from 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 data sheet 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 [email protected] .
+.Sh BUGS
+The driver does not make complete use of the VOC algorithm. In particular, there is no
+need to restart the algorithm from scratch if there is a stoppage of polling for less than
+10 minutes. The driver does not have the ability to determine that, and therefore
+assumes that the sensor is completely cold each time the driver attaches to the chip.
+.Pp
+The temperature and humidity compensation could be allowed to contain fractional degrees Celsius
+and %RH. The driver only supports setting whole numbers for either of those.
Index: src/sys/dev/i2c/sensirion_arch_config.h
diff -u /dev/null src/sys/dev/i2c/sensirion_arch_config.h:1.1
--- /dev/null Thu Oct 14 13:54:46 2021
+++ src/sys/dev/i2c/sensirion_arch_config.h Thu Oct 14 13:54:46 2021
@@ -0,0 +1,33 @@
+/* $NetBSD: sensirion_arch_config.h,v 1.1 2021/10/14 13:54:46 brad Exp $ */
+
+/*
+ * Copyright (c) 2021 Brad Spencer <[email protected]>
+ *
+ * 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, NEGL`IGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+/* The Sensirion code wants a file called sensirion_arch_config.h. This
+ is the shim for NetBSD
+*/
+
+#ifndef SENSIRION_ARCH_CONFIG_H
+#define SENSIRION_ARCH_CONFIG_H
+
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+
+
+#endif /* SENSIRION_ARCH_CONFIG_H */
Index: src/sys/dev/i2c/sensirion_voc_algorithm.c
diff -u /dev/null src/sys/dev/i2c/sensirion_voc_algorithm.c:1.1
--- /dev/null Thu Oct 14 13:54:46 2021
+++ src/sys/dev/i2c/sensirion_voc_algorithm.c Thu Oct 14 13:54:46 2021
@@ -0,0 +1,809 @@
+/*
+ * $NetBSD: sensirion_voc_algorithm.c,v 1.1 2021/10/14 13:54:46 brad Exp $
+ */
+
+/*
+ * Copyright (c) 2021, Sensirion AG
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * 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.
+ *
+ * * Neither the name of Sensirion AG 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.
+ */
+
+#include "sensirion_voc_algorithm.h"
+
+/* The fixed point arithmetic parts of this code were originally created by
+ * https://github.com/PetteriAimonen/libfixmath
+ */
+
+/*!< the maximum value of fix16_t */
+#define FIX16_MAXIMUM 0x7FFFFFFF
+/*!< the minimum value of fix16_t */
+#define FIX16_MINIMUM 0x80000000
+/*!< the value used to indicate overflows when FIXMATH_NO_OVERFLOW is not
+ * specified */
+#define FIX16_OVERFLOW 0x80000000
+/*!< fix16_t value of 1 */
+#define FIX16_ONE 0x00010000
+
+static inline fix16_t fix16_from_int(int32_t a) {
+ return a * FIX16_ONE;
+}
+
+static inline int32_t fix16_cast_to_int(fix16_t a) {
+ return (a >= 0) ? (a >> 16) : -((-a) >> 16);
+}
+
+/*! Multiplies the two given fix16_t's and returns the result. */
+static fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1);
+
+/*! Divides the first given fix16_t by the second and returns the result. */
+static fix16_t fix16_div(fix16_t inArg0, fix16_t inArg1);
+
+/*! Returns the square root of the given fix16_t. */
+static fix16_t fix16_sqrt(fix16_t inValue);
+
+/*! Returns the exponent (e^) of the given fix16_t. */
+static fix16_t fix16_exp(fix16_t inValue);
+
+static fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1) {
+ // Each argument is divided to 16-bit parts.
+ // AB
+ // * CD
+ // -----------
+ // BD 16 * 16 -> 32 bit products
+ // CB
+ // AD
+ // AC
+ // |----| 64 bit product
+ uint32_t absArg0 = (uint32_t)((inArg0 >= 0) ? inArg0 : (-inArg0));
+ uint32_t absArg1 = (uint32_t)((inArg1 >= 0) ? inArg1 : (-inArg1));
+ uint32_t A = (absArg0 >> 16), C = (absArg1 >> 16);
+ uint32_t B = (absArg0 & 0xFFFF), D = (absArg1 & 0xFFFF);
+
+ uint32_t AC = A * C;
+ uint32_t AD_CB = A * D + C * B;
+ uint32_t BD = B * D;
+
+ uint32_t product_hi = AC + (AD_CB >> 16);
+
+ // Handle carry from lower 32 bits to upper part of result.
+ uint32_t ad_cb_temp = AD_CB << 16;
+ uint32_t product_lo = BD + ad_cb_temp;
+ if (product_lo < BD)
+ product_hi++;
+
+#ifndef FIXMATH_NO_OVERFLOW
+ // The upper 17 bits should all be zero.
+ if (product_hi >> 15)
+ return (fix16_t)FIX16_OVERFLOW;
+#endif
+
+#ifdef FIXMATH_NO_ROUNDING
+ fix16_t result = (fix16_t)((product_hi << 16) | (product_lo >> 16));
+ if ((inArg0 < 0) != (inArg1 < 0))
+ result = -result;
+ return result;
+#else
+ // Adding 0x8000 (= 0.5) and then using right shift
+ // achieves proper rounding to result.
+ // Handle carry from lower to upper part.
+ uint32_t product_lo_tmp = product_lo;
+ product_lo += 0x8000;
+ if (product_lo < product_lo_tmp)
+ product_hi++;
+
+ // Discard the lowest 16 bits and convert back to signed result.
+ fix16_t result = (fix16_t)((product_hi << 16) | (product_lo >> 16));
+ if ((inArg0 < 0) != (inArg1 < 0))
+ result = -result;
+ return result;
+#endif
+}
+
+static fix16_t fix16_div(fix16_t a, fix16_t b) {
+ // This uses the basic binary restoring division algorithm.
+ // It appears to be faster to do the whole division manually than
+ // trying to compose a 64-bit divide out of 32-bit divisions on
+ // platforms without hardware divide.
+
+ if (b == 0)
+ return (fix16_t)FIX16_MINIMUM;
+
+ uint32_t remainder = (uint32_t)((a >= 0) ? a : (-a));
+ uint32_t divider = (uint32_t)((b >= 0) ? b : (-b));
+
+ uint32_t quotient = 0;
+ uint32_t bit = 0x10000;
+
+ /* The algorithm requires D >= R */
+ while (divider < remainder) {
+ divider <<= 1;
+ bit <<= 1;
+ }
+
+#ifndef FIXMATH_NO_OVERFLOW
+ if (!bit)
+ return (fix16_t)FIX16_OVERFLOW;
+#endif
+
+ if (divider & 0x80000000) {
+ // Perform one step manually to avoid overflows later.
+ // We know that divider's bottom bit is 0 here.
+ if (remainder >= divider) {
+ quotient |= bit;
+ remainder -= divider;
+ }
+ divider >>= 1;
+ bit >>= 1;
+ }
+
+ /* Main division loop */
+ while (bit && remainder) {
+ if (remainder >= divider) {
+ quotient |= bit;
+ remainder -= divider;
+ }
+
+ remainder <<= 1;
+ bit >>= 1;
+ }
+
+#ifndef FIXMATH_NO_ROUNDING
+ if (remainder >= divider) {
+ quotient++;
+ }
+#endif
+
+ fix16_t result = (fix16_t)quotient;
+
+ /* Figure out the sign of result */
+ if ((a < 0) != (b < 0)) {
+#ifndef FIXMATH_NO_OVERFLOW
+ if (result == FIX16_MINIMUM)
+ return (fix16_t)FIX16_OVERFLOW;
+#endif
+
+ result = -result;
+ }
+
+ return result;
+}
+
+static fix16_t fix16_sqrt(fix16_t x) {
+ // It is assumed that x is not negative
+
+ uint32_t num = (uint32_t)x;
+ uint32_t result = 0;
+ uint32_t bit;
+ uint8_t n;
+
+ bit = (uint32_t)1 << 30;
+ while (bit > num)
+ bit >>= 2;
+
+ // The main part is executed twice, in order to avoid
+ // using 64 bit values in computations.
+ for (n = 0; n < 2; n++) {
+ // First we get the top 24 bits of the answer.
+ while (bit) {
+ if (num >= result + bit) {
+ num -= result + bit;
+ result = (result >> 1) + bit;
+ } else {
+ result = (result >> 1);
+ }
+ bit >>= 2;
+ }
+
+ if (n == 0) {
+ // Then process it again to get the lowest 8 bits.
+ if (num > 65535) {
+ // The remainder 'num' is too large to be shifted left
+ // by 16, so we have to add 1 to result manually and
+ // adjust 'num' accordingly.
+ // num = a - (result + 0.5)^2
+ // = num + result^2 - (result + 0.5)^2
+ // = num - result - 0.5
+ num -= result;
+ num = (num << 16) - 0x8000;
+ result = (result << 16) + 0x8000;
+ } else {
+ num <<= 16;
+ result <<= 16;
+ }
+
+ bit = 1 << 14;
+ }
+ }
+
+#ifndef FIXMATH_NO_ROUNDING
+ // Finally, if next bit would have been 1, round the result upwards.
+ if (num > result) {
+ result++;
+ }
+#endif
+
+ return (fix16_t)result;
+}
+
+static fix16_t fix16_exp(fix16_t x) {
+ // Function to approximate exp(); optimized more for code size than speed
+
+ // exp(x) for x = +/- {1, 1/8, 1/64, 1/512}
+#define NUM_EXP_VALUES 4
+ static const fix16_t exp_pos_values[NUM_EXP_VALUES] = {
+ F16(2.7182818), F16(1.1331485), F16(1.0157477), F16(1.0019550)};
+ static const fix16_t exp_neg_values[NUM_EXP_VALUES] = {
+ F16(0.3678794), F16(0.8824969), F16(0.9844964), F16(0.9980488)};
+ const fix16_t* exp_values;
+
+ fix16_t res, arg;
+ uint16_t i;
+
+ if (x >= F16(10.3972))
+ return FIX16_MAXIMUM;
+ if (x <= F16(-11.7835))
+ return 0;
+
+ if (x < 0) {
+ x = -x;
+ exp_values = exp_neg_values;
+ } else {
+ exp_values = exp_pos_values;
+ }
+
+ res = FIX16_ONE;
+ arg = FIX16_ONE;
+ for (i = 0; i < NUM_EXP_VALUES; i++) {
+ while (x >= arg) {
+ res = fix16_mul(res, exp_values[i]);
+ x -= arg;
+ }
+ arg >>= 3;
+ }
+ return res;
+}
+
+static void VocAlgorithm__init_instances(VocAlgorithmParams* params);
+static void
+VocAlgorithm__mean_variance_estimator__init(VocAlgorithmParams* params);
+static void VocAlgorithm__mean_variance_estimator___init_instances(
+ VocAlgorithmParams* params);
+static void VocAlgorithm__mean_variance_estimator__set_parameters(
+ VocAlgorithmParams* params, fix16_t std_initial,
+ fix16_t tau_mean_variance_hours, fix16_t gating_max_duration_minutes);
+static void
+VocAlgorithm__mean_variance_estimator__set_states(VocAlgorithmParams* params,
+ fix16_t mean, fix16_t std,
+ fix16_t uptime_gamma);
+static fix16_t
+VocAlgorithm__mean_variance_estimator__get_std(VocAlgorithmParams* params);
+static fix16_t
+VocAlgorithm__mean_variance_estimator__get_mean(VocAlgorithmParams* params);
+static void VocAlgorithm__mean_variance_estimator___calculate_gamma(
+ VocAlgorithmParams* params, fix16_t voc_index_from_prior);
+static void VocAlgorithm__mean_variance_estimator__process(
+ VocAlgorithmParams* params, fix16_t sraw, fix16_t voc_index_from_prior);
+static void VocAlgorithm__mean_variance_estimator___sigmoid__init(
+ VocAlgorithmParams* params);
+static void VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+ VocAlgorithmParams* params, fix16_t L, fix16_t X0, fix16_t K);
+static fix16_t VocAlgorithm__mean_variance_estimator___sigmoid__process(
+ VocAlgorithmParams* params, fix16_t sample);
+static void VocAlgorithm__mox_model__init(VocAlgorithmParams* params);
+static void VocAlgorithm__mox_model__set_parameters(VocAlgorithmParams* params,
+ fix16_t SRAW_STD,
+ fix16_t SRAW_MEAN);
+static fix16_t VocAlgorithm__mox_model__process(VocAlgorithmParams* params,
+ fix16_t sraw);
+static void VocAlgorithm__sigmoid_scaled__init(VocAlgorithmParams* params);
+static void
+VocAlgorithm__sigmoid_scaled__set_parameters(VocAlgorithmParams* params,
+ fix16_t offset);
+static fix16_t VocAlgorithm__sigmoid_scaled__process(VocAlgorithmParams* params,
+ fix16_t sample);
+static void VocAlgorithm__adaptive_lowpass__init(VocAlgorithmParams* params);
+static void
+VocAlgorithm__adaptive_lowpass__set_parameters(VocAlgorithmParams* params);
+static fix16_t
+VocAlgorithm__adaptive_lowpass__process(VocAlgorithmParams* params,
+ fix16_t sample);
+
+void VocAlgorithm_init(VocAlgorithmParams* params) {
+
+ params->mVoc_Index_Offset = F16(VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT);
+ params->mTau_Mean_Variance_Hours =
+ F16(VocAlgorithm_TAU_MEAN_VARIANCE_HOURS);
+ params->mGating_Max_Duration_Minutes =
+ F16(VocAlgorithm_GATING_MAX_DURATION_MINUTES);
+ params->mSraw_Std_Initial = F16(VocAlgorithm_SRAW_STD_INITIAL);
+ params->mUptime = F16(0.);
+ params->mSraw = F16(0.);
+ params->mVoc_Index = 0;
+ VocAlgorithm__init_instances(params);
+}
+
+static void VocAlgorithm__init_instances(VocAlgorithmParams* params) {
+
+ VocAlgorithm__mean_variance_estimator__init(params);
+ VocAlgorithm__mean_variance_estimator__set_parameters(
+ params, params->mSraw_Std_Initial, params->mTau_Mean_Variance_Hours,
+ params->mGating_Max_Duration_Minutes);
+ VocAlgorithm__mox_model__init(params);
+ VocAlgorithm__mox_model__set_parameters(
+ params, VocAlgorithm__mean_variance_estimator__get_std(params),
+ VocAlgorithm__mean_variance_estimator__get_mean(params));
+ VocAlgorithm__sigmoid_scaled__init(params);
+ VocAlgorithm__sigmoid_scaled__set_parameters(params,
+ params->mVoc_Index_Offset);
+ VocAlgorithm__adaptive_lowpass__init(params);
+ VocAlgorithm__adaptive_lowpass__set_parameters(params);
+}
+
+void VocAlgorithm_get_states(VocAlgorithmParams* params, int32_t* state0,
+ int32_t* state1) {
+
+ *state0 = VocAlgorithm__mean_variance_estimator__get_mean(params);
+ *state1 = VocAlgorithm__mean_variance_estimator__get_std(params);
+ return;
+}
+
+void VocAlgorithm_set_states(VocAlgorithmParams* params, int32_t state0,
+ int32_t state1) {
+
+ VocAlgorithm__mean_variance_estimator__set_states(
+ params, state0, state1, F16(VocAlgorithm_PERSISTENCE_UPTIME_GAMMA));
+ params->mSraw = state0;
+}
+
+void VocAlgorithm_set_tuning_parameters(VocAlgorithmParams* params,
+ int32_t voc_index_offset,
+ int32_t learning_time_hours,
+ int32_t gating_max_duration_minutes,
+ int32_t std_initial) {
+
+ params->mVoc_Index_Offset = (fix16_from_int(voc_index_offset));
+ params->mTau_Mean_Variance_Hours = (fix16_from_int(learning_time_hours));
+ params->mGating_Max_Duration_Minutes =
+ (fix16_from_int(gating_max_duration_minutes));
+ params->mSraw_Std_Initial = (fix16_from_int(std_initial));
+ VocAlgorithm__init_instances(params);
+}
+
+void VocAlgorithm_process(VocAlgorithmParams* params, int32_t sraw,
+ int32_t* voc_index) {
+
+ if ((params->mUptime <= F16(VocAlgorithm_INITIAL_BLACKOUT))) {
+ params->mUptime =
+ (params->mUptime + F16(VocAlgorithm_SAMPLING_INTERVAL));
+ } else {
+ if (((sraw > 0) && (sraw < 65000))) {
+ if ((sraw < 20001)) {
+ sraw = 20001;
+ } else if ((sraw > 52767)) {
+ sraw = 52767;
+ }
+ params->mSraw = (fix16_from_int((sraw - 20000)));
+ }
+ params->mVoc_Index =
+ VocAlgorithm__mox_model__process(params, params->mSraw);
+ params->mVoc_Index =
+ VocAlgorithm__sigmoid_scaled__process(params, params->mVoc_Index);
+ params->mVoc_Index =
+ VocAlgorithm__adaptive_lowpass__process(params, params->mVoc_Index);
+ if ((params->mVoc_Index < F16(0.5))) {
+ params->mVoc_Index = F16(0.5);
+ }
+ if ((params->mSraw > F16(0.))) {
+ VocAlgorithm__mean_variance_estimator__process(
+ params, params->mSraw, params->mVoc_Index);
+ VocAlgorithm__mox_model__set_parameters(
+ params, VocAlgorithm__mean_variance_estimator__get_std(params),
+ VocAlgorithm__mean_variance_estimator__get_mean(params));
+ }
+ }
+ *voc_index = (fix16_cast_to_int((params->mVoc_Index + F16(0.5))));
+ return;
+}
+
+static void
+VocAlgorithm__mean_variance_estimator__init(VocAlgorithmParams* params) {
+
+ VocAlgorithm__mean_variance_estimator__set_parameters(params, F16(0.),
+ F16(0.), F16(0.));
+ VocAlgorithm__mean_variance_estimator___init_instances(params);
+}
+
+static void VocAlgorithm__mean_variance_estimator___init_instances(
+ VocAlgorithmParams* params) {
+
+ VocAlgorithm__mean_variance_estimator___sigmoid__init(params);
+}
+
+static void VocAlgorithm__mean_variance_estimator__set_parameters(
+ VocAlgorithmParams* params, fix16_t std_initial,
+ fix16_t tau_mean_variance_hours, fix16_t gating_max_duration_minutes) {
+
+ params->m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes =
+ gating_max_duration_minutes;
+ params->m_Mean_Variance_Estimator___Initialized = false;
+ params->m_Mean_Variance_Estimator___Mean = F16(0.);
+ params->m_Mean_Variance_Estimator___Sraw_Offset = F16(0.);
+ params->m_Mean_Variance_Estimator___Std = std_initial;
+ params->m_Mean_Variance_Estimator___Gamma =
+ (fix16_div(F16((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
+ (VocAlgorithm_SAMPLING_INTERVAL / 3600.))),
+ (tau_mean_variance_hours +
+ F16((VocAlgorithm_SAMPLING_INTERVAL / 3600.)))));
+ params->m_Mean_Variance_Estimator___Gamma_Initial_Mean =
+ F16(((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
+ VocAlgorithm_SAMPLING_INTERVAL) /
+ (VocAlgorithm_TAU_INITIAL_MEAN + VocAlgorithm_SAMPLING_INTERVAL)));
+ params->m_Mean_Variance_Estimator___Gamma_Initial_Variance = F16(
+ ((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
+ VocAlgorithm_SAMPLING_INTERVAL) /
+ (VocAlgorithm_TAU_INITIAL_VARIANCE + VocAlgorithm_SAMPLING_INTERVAL)));
+ params->m_Mean_Variance_Estimator__Gamma_Mean = F16(0.);
+ params->m_Mean_Variance_Estimator__Gamma_Variance = F16(0.);
+ params->m_Mean_Variance_Estimator___Uptime_Gamma = F16(0.);
+ params->m_Mean_Variance_Estimator___Uptime_Gating = F16(0.);
+ params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = F16(0.);
+}
+
+static void
+VocAlgorithm__mean_variance_estimator__set_states(VocAlgorithmParams* params,
+ fix16_t mean, fix16_t std,
+ fix16_t uptime_gamma) {
+
+ params->m_Mean_Variance_Estimator___Mean = mean;
+ params->m_Mean_Variance_Estimator___Std = std;
+ params->m_Mean_Variance_Estimator___Uptime_Gamma = uptime_gamma;
+ params->m_Mean_Variance_Estimator___Initialized = true;
+}
+
+static fix16_t
+VocAlgorithm__mean_variance_estimator__get_std(VocAlgorithmParams* params) {
+
+ return params->m_Mean_Variance_Estimator___Std;
+}
+
+static fix16_t
+VocAlgorithm__mean_variance_estimator__get_mean(VocAlgorithmParams* params) {
+
+ return (params->m_Mean_Variance_Estimator___Mean +
+ params->m_Mean_Variance_Estimator___Sraw_Offset);
+}
+
+static void VocAlgorithm__mean_variance_estimator___calculate_gamma(
+ VocAlgorithmParams* params, fix16_t voc_index_from_prior) {
+
+ fix16_t uptime_limit;
+ fix16_t sigmoid_gamma_mean;
+ fix16_t gamma_mean;
+ fix16_t gating_threshold_mean;
+ fix16_t sigmoid_gating_mean;
+ fix16_t sigmoid_gamma_variance;
+ fix16_t gamma_variance;
+ fix16_t gating_threshold_variance;
+ fix16_t sigmoid_gating_variance;
+
+ uptime_limit = F16((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX -
+ VocAlgorithm_SAMPLING_INTERVAL));
+ if ((params->m_Mean_Variance_Estimator___Uptime_Gamma < uptime_limit)) {
+ params->m_Mean_Variance_Estimator___Uptime_Gamma =
+ (params->m_Mean_Variance_Estimator___Uptime_Gamma +
+ F16(VocAlgorithm_SAMPLING_INTERVAL));
+ }
+ if ((params->m_Mean_Variance_Estimator___Uptime_Gating < uptime_limit)) {
+ params->m_Mean_Variance_Estimator___Uptime_Gating =
+ (params->m_Mean_Variance_Estimator___Uptime_Gating +
+ F16(VocAlgorithm_SAMPLING_INTERVAL));
+ }
+ VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+ params, F16(1.), F16(VocAlgorithm_INIT_DURATION_MEAN),
+ F16(VocAlgorithm_INIT_TRANSITION_MEAN));
+ sigmoid_gamma_mean =
+ VocAlgorithm__mean_variance_estimator___sigmoid__process(
+ params, params->m_Mean_Variance_Estimator___Uptime_Gamma);
+ gamma_mean =
+ (params->m_Mean_Variance_Estimator___Gamma +
+ (fix16_mul((params->m_Mean_Variance_Estimator___Gamma_Initial_Mean -
+ params->m_Mean_Variance_Estimator___Gamma),
+ sigmoid_gamma_mean)));
+ gating_threshold_mean =
+ (F16(VocAlgorithm_GATING_THRESHOLD) +
+ (fix16_mul(
+ F16((VocAlgorithm_GATING_THRESHOLD_INITIAL -
+ VocAlgorithm_GATING_THRESHOLD)),
+ VocAlgorithm__mean_variance_estimator___sigmoid__process(
+ params, params->m_Mean_Variance_Estimator___Uptime_Gating))));
+ VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+ params, F16(1.), gating_threshold_mean,
+ F16(VocAlgorithm_GATING_THRESHOLD_TRANSITION));
+ sigmoid_gating_mean =
+ VocAlgorithm__mean_variance_estimator___sigmoid__process(
+ params, voc_index_from_prior);
+ params->m_Mean_Variance_Estimator__Gamma_Mean =
+ (fix16_mul(sigmoid_gating_mean, gamma_mean));
+ VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+ params, F16(1.), F16(VocAlgorithm_INIT_DURATION_VARIANCE),
+ F16(VocAlgorithm_INIT_TRANSITION_VARIANCE));
+ sigmoid_gamma_variance =
+ VocAlgorithm__mean_variance_estimator___sigmoid__process(
+ params, params->m_Mean_Variance_Estimator___Uptime_Gamma);
+ gamma_variance =
+ (params->m_Mean_Variance_Estimator___Gamma +
+ (fix16_mul(
+ (params->m_Mean_Variance_Estimator___Gamma_Initial_Variance -
+ params->m_Mean_Variance_Estimator___Gamma),
+ (sigmoid_gamma_variance - sigmoid_gamma_mean))));
+ gating_threshold_variance =
+ (F16(VocAlgorithm_GATING_THRESHOLD) +
+ (fix16_mul(
+ F16((VocAlgorithm_GATING_THRESHOLD_INITIAL -
+ VocAlgorithm_GATING_THRESHOLD)),
+ VocAlgorithm__mean_variance_estimator___sigmoid__process(
+ params, params->m_Mean_Variance_Estimator___Uptime_Gating))));
+ VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+ params, F16(1.), gating_threshold_variance,
+ F16(VocAlgorithm_GATING_THRESHOLD_TRANSITION));
+ sigmoid_gating_variance =
+ VocAlgorithm__mean_variance_estimator___sigmoid__process(
+ params, voc_index_from_prior);
+ params->m_Mean_Variance_Estimator__Gamma_Variance =
+ (fix16_mul(sigmoid_gating_variance, gamma_variance));
+ params->m_Mean_Variance_Estimator___Gating_Duration_Minutes =
+ (params->m_Mean_Variance_Estimator___Gating_Duration_Minutes +
+ (fix16_mul(F16((VocAlgorithm_SAMPLING_INTERVAL / 60.)),
+ ((fix16_mul((F16(1.) - sigmoid_gating_mean),
+ F16((1. + VocAlgorithm_GATING_MAX_RATIO)))) -
+ F16(VocAlgorithm_GATING_MAX_RATIO)))));
+ if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes <
+ F16(0.))) {
+ params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = F16(0.);
+ }
+ if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes >
+ params->m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes)) {
+ params->m_Mean_Variance_Estimator___Uptime_Gating = F16(0.);
+ }
+}
+
+static void VocAlgorithm__mean_variance_estimator__process(
+ VocAlgorithmParams* params, fix16_t sraw, fix16_t voc_index_from_prior) {
+
+ fix16_t delta_sgp;
+ fix16_t c;
+ fix16_t additional_scaling;
+
+ if ((params->m_Mean_Variance_Estimator___Initialized == false)) {
+ params->m_Mean_Variance_Estimator___Initialized = true;
+ params->m_Mean_Variance_Estimator___Sraw_Offset = sraw;
+ params->m_Mean_Variance_Estimator___Mean = F16(0.);
+ } else {
+ if (((params->m_Mean_Variance_Estimator___Mean >= F16(100.)) ||
+ (params->m_Mean_Variance_Estimator___Mean <= F16(-100.)))) {
+ params->m_Mean_Variance_Estimator___Sraw_Offset =
+ (params->m_Mean_Variance_Estimator___Sraw_Offset +
+ params->m_Mean_Variance_Estimator___Mean);
+ params->m_Mean_Variance_Estimator___Mean = F16(0.);
+ }
+ sraw = (sraw - params->m_Mean_Variance_Estimator___Sraw_Offset);
+ VocAlgorithm__mean_variance_estimator___calculate_gamma(
+ params, voc_index_from_prior);
+ delta_sgp = (fix16_div(
+ (sraw - params->m_Mean_Variance_Estimator___Mean),
+ F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING)));
+ if ((delta_sgp < F16(0.))) {
+ c = (params->m_Mean_Variance_Estimator___Std - delta_sgp);
+ } else {
+ c = (params->m_Mean_Variance_Estimator___Std + delta_sgp);
+ }
+ additional_scaling = F16(1.);
+ if ((c > F16(1440.))) {
+ additional_scaling = F16(4.);
+ }
+ params->m_Mean_Variance_Estimator___Std = (fix16_mul(
+ fix16_sqrt((fix16_mul(
+ additional_scaling,
+ (F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING) -
+ params->m_Mean_Variance_Estimator__Gamma_Variance)))),
+ fix16_sqrt((
+ (fix16_mul(
+ params->m_Mean_Variance_Estimator___Std,
+ (fix16_div(
+ params->m_Mean_Variance_Estimator___Std,
+ (fix16_mul(
+ F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING),
+ additional_scaling)))))) +
+ (fix16_mul(
+ (fix16_div(
+ (fix16_mul(
+ params->m_Mean_Variance_Estimator__Gamma_Variance,
+ delta_sgp)),
+ additional_scaling)),
+ delta_sgp))))));
+ params->m_Mean_Variance_Estimator___Mean =
+ (params->m_Mean_Variance_Estimator___Mean +
+ (fix16_mul(params->m_Mean_Variance_Estimator__Gamma_Mean,
+ delta_sgp)));
+ }
+}
+
+static void VocAlgorithm__mean_variance_estimator___sigmoid__init(
+ VocAlgorithmParams* params) {
+
+ VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+ params, F16(0.), F16(0.), F16(0.));
+}
+
+static void VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
+ VocAlgorithmParams* params, fix16_t L, fix16_t X0, fix16_t K) {
+
+ params->m_Mean_Variance_Estimator___Sigmoid__L = L;
+ params->m_Mean_Variance_Estimator___Sigmoid__K = K;
+ params->m_Mean_Variance_Estimator___Sigmoid__X0 = X0;
+}
+
+static fix16_t VocAlgorithm__mean_variance_estimator___sigmoid__process(
+ VocAlgorithmParams* params, fix16_t sample) {
+
+ fix16_t x;
+
+ x = (fix16_mul(params->m_Mean_Variance_Estimator___Sigmoid__K,
+ (sample - params->m_Mean_Variance_Estimator___Sigmoid__X0)));
+ if ((x < F16(-50.))) {
+ return params->m_Mean_Variance_Estimator___Sigmoid__L;
+ } else if ((x > F16(50.))) {
+ return F16(0.);
+ } else {
+ return (fix16_div(params->m_Mean_Variance_Estimator___Sigmoid__L,
+ (F16(1.) + fix16_exp(x))));
+ }
+}
+
+static void VocAlgorithm__mox_model__init(VocAlgorithmParams* params) {
+
+ VocAlgorithm__mox_model__set_parameters(params, F16(1.), F16(0.));
+}
+
+static void VocAlgorithm__mox_model__set_parameters(VocAlgorithmParams* params,
+ fix16_t SRAW_STD,
+ fix16_t SRAW_MEAN) {
+
+ params->m_Mox_Model__Sraw_Std = SRAW_STD;
+ params->m_Mox_Model__Sraw_Mean = SRAW_MEAN;
+}
+
+static fix16_t VocAlgorithm__mox_model__process(VocAlgorithmParams* params,
+ fix16_t sraw) {
+
+ return (fix16_mul((fix16_div((sraw - params->m_Mox_Model__Sraw_Mean),
+ (-(params->m_Mox_Model__Sraw_Std +
+ F16(VocAlgorithm_SRAW_STD_BONUS))))),
+ F16(VocAlgorithm_VOC_INDEX_GAIN)));
+}
+
+static void VocAlgorithm__sigmoid_scaled__init(VocAlgorithmParams* params) {
+
+ VocAlgorithm__sigmoid_scaled__set_parameters(params, F16(0.));
+}
+
+static void
+VocAlgorithm__sigmoid_scaled__set_parameters(VocAlgorithmParams* params,
+ fix16_t offset) {
+
+ params->m_Sigmoid_Scaled__Offset = offset;
+}
+
+static fix16_t VocAlgorithm__sigmoid_scaled__process(VocAlgorithmParams* params,
+ fix16_t sample) {
+
+ fix16_t x;
+ fix16_t shift;
+
+ x = (fix16_mul(F16(VocAlgorithm_SIGMOID_K),
+ (sample - F16(VocAlgorithm_SIGMOID_X0))));
+ if ((x < F16(-50.))) {
+ return F16(VocAlgorithm_SIGMOID_L);
+ } else if ((x > F16(50.))) {
+ return F16(0.);
+ } else {
+ if ((sample >= F16(0.))) {
+ shift = (fix16_div(
+ (F16(VocAlgorithm_SIGMOID_L) -
+ (fix16_mul(F16(5.), params->m_Sigmoid_Scaled__Offset))),
+ F16(4.)));
+ return ((fix16_div((F16(VocAlgorithm_SIGMOID_L) + shift),
+ (F16(1.) + fix16_exp(x)))) -
+ shift);
+ } else {
+ return (fix16_mul(
+ (fix16_div(params->m_Sigmoid_Scaled__Offset,
+ F16(VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT))),
+ (fix16_div(F16(VocAlgorithm_SIGMOID_L),
+ (F16(1.) + fix16_exp(x))))));
+ }
+ }
+}
+
+static void VocAlgorithm__adaptive_lowpass__init(VocAlgorithmParams* params) {
+
+ VocAlgorithm__adaptive_lowpass__set_parameters(params);
+}
+
+static void
+VocAlgorithm__adaptive_lowpass__set_parameters(VocAlgorithmParams* params) {
+
+ params->m_Adaptive_Lowpass__A1 =
+ F16((VocAlgorithm_SAMPLING_INTERVAL /
+ (VocAlgorithm_LP_TAU_FAST + VocAlgorithm_SAMPLING_INTERVAL)));
+ params->m_Adaptive_Lowpass__A2 =
+ F16((VocAlgorithm_SAMPLING_INTERVAL /
+ (VocAlgorithm_LP_TAU_SLOW + VocAlgorithm_SAMPLING_INTERVAL)));
+ params->m_Adaptive_Lowpass___Initialized = false;
+}
+
+static fix16_t
+VocAlgorithm__adaptive_lowpass__process(VocAlgorithmParams* params,
+ fix16_t sample) {
+
+ fix16_t abs_delta;
+ fix16_t F1;
+ fix16_t tau_a;
+ fix16_t a3;
+
+ if ((params->m_Adaptive_Lowpass___Initialized == false)) {
+ params->m_Adaptive_Lowpass___X1 = sample;
+ params->m_Adaptive_Lowpass___X2 = sample;
+ params->m_Adaptive_Lowpass___X3 = sample;
+ params->m_Adaptive_Lowpass___Initialized = true;
+ }
+ params->m_Adaptive_Lowpass___X1 =
+ ((fix16_mul((F16(1.) - params->m_Adaptive_Lowpass__A1),
+ params->m_Adaptive_Lowpass___X1)) +
+ (fix16_mul(params->m_Adaptive_Lowpass__A1, sample)));
+ params->m_Adaptive_Lowpass___X2 =
+ ((fix16_mul((F16(1.) - params->m_Adaptive_Lowpass__A2),
+ params->m_Adaptive_Lowpass___X2)) +
+ (fix16_mul(params->m_Adaptive_Lowpass__A2, sample)));
+ abs_delta =
+ (params->m_Adaptive_Lowpass___X1 - params->m_Adaptive_Lowpass___X2);
+ if ((abs_delta < F16(0.))) {
+ abs_delta = (-abs_delta);
+ }
+ F1 = fix16_exp((fix16_mul(F16(VocAlgorithm_LP_ALPHA), abs_delta)));
+ tau_a =
+ ((fix16_mul(F16((VocAlgorithm_LP_TAU_SLOW - VocAlgorithm_LP_TAU_FAST)),
+ F1)) +
+ F16(VocAlgorithm_LP_TAU_FAST));
+ a3 = (fix16_div(F16(VocAlgorithm_SAMPLING_INTERVAL),
+ (F16(VocAlgorithm_SAMPLING_INTERVAL) + tau_a)));
+ params->m_Adaptive_Lowpass___X3 =
+ ((fix16_mul((F16(1.) - a3), params->m_Adaptive_Lowpass___X3)) +
+ (fix16_mul(a3, sample)));
+ return params->m_Adaptive_Lowpass___X3;
+}
Index: src/sys/dev/i2c/sensirion_voc_algorithm.h
diff -u /dev/null src/sys/dev/i2c/sensirion_voc_algorithm.h:1.1
--- /dev/null Thu Oct 14 13:54:46 2021
+++ src/sys/dev/i2c/sensirion_voc_algorithm.h Thu Oct 14 13:54:46 2021
@@ -0,0 +1,191 @@
+/*
+ * $NetBSD: sensirion_voc_algorithm.h,v 1.1 2021/10/14 13:54:46 brad Exp $
+ */
+
+/*
+ * Copyright (c) 2021, Sensirion AG
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * 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.
+ *
+ * * Neither the name of Sensirion AG 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.
+ */
+
+#ifndef VOCALGORITHM_H_
+#define VOCALGORITHM_H_
+
+#include "sensirion_arch_config.h"
+
+/* The fixed point arithmetic parts of this code were originally created by
+ * https://github.com/PetteriAimonen/libfixmath
+ */
+
+typedef int32_t fix16_t;
+
+#define F16(x) \
+ ((fix16_t)(((x) >= 0) ? ((x)*65536.0 + 0.5) : ((x)*65536.0 - 0.5)))
+
+// Should be set by the building toolchain
+#ifndef LIBRARY_VERSION_NAME
+#define LIBRARY_VERSION_NAME "custom build"
+#endif
+
+#define VocAlgorithm_SAMPLING_INTERVAL (1.)
+#define VocAlgorithm_INITIAL_BLACKOUT (45.)
+#define VocAlgorithm_VOC_INDEX_GAIN (230.)
+#define VocAlgorithm_SRAW_STD_INITIAL (50.)
+#define VocAlgorithm_SRAW_STD_BONUS (220.)
+#define VocAlgorithm_TAU_MEAN_VARIANCE_HOURS (12.)
+#define VocAlgorithm_TAU_INITIAL_MEAN (20.)
+#define VocAlgorithm_INIT_DURATION_MEAN ((3600. * 0.75))
+#define VocAlgorithm_INIT_TRANSITION_MEAN (0.01)
+#define VocAlgorithm_TAU_INITIAL_VARIANCE (2500.)
+#define VocAlgorithm_INIT_DURATION_VARIANCE ((3600. * 1.45))
+#define VocAlgorithm_INIT_TRANSITION_VARIANCE (0.01)
+#define VocAlgorithm_GATING_THRESHOLD (340.)
+#define VocAlgorithm_GATING_THRESHOLD_INITIAL (510.)
+#define VocAlgorithm_GATING_THRESHOLD_TRANSITION (0.09)
+#define VocAlgorithm_GATING_MAX_DURATION_MINUTES ((60. * 3.))
+#define VocAlgorithm_GATING_MAX_RATIO (0.3)
+#define VocAlgorithm_SIGMOID_L (500.)
+#define VocAlgorithm_SIGMOID_K (-0.0065)
+#define VocAlgorithm_SIGMOID_X0 (213.)
+#define VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT (100.)
+#define VocAlgorithm_LP_TAU_FAST (20.0)
+#define VocAlgorithm_LP_TAU_SLOW (500.0)
+#define VocAlgorithm_LP_ALPHA (-0.2)
+#define VocAlgorithm_PERSISTENCE_UPTIME_GAMMA ((3. * 3600.))
+#define VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING (64.)
+#define VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX (32767.)
+
+/**
+ * Struct to hold all the states of the VOC algorithm.
+ */
+typedef struct {
+ fix16_t mVoc_Index_Offset;
+ fix16_t mTau_Mean_Variance_Hours;
+ fix16_t mGating_Max_Duration_Minutes;
+ fix16_t mSraw_Std_Initial;
+ fix16_t mUptime;
+ fix16_t mSraw;
+ fix16_t mVoc_Index;
+ fix16_t m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes;
+ bool m_Mean_Variance_Estimator___Initialized;
+ fix16_t m_Mean_Variance_Estimator___Mean;
+ fix16_t m_Mean_Variance_Estimator___Sraw_Offset;
+ fix16_t m_Mean_Variance_Estimator___Std;
+ fix16_t m_Mean_Variance_Estimator___Gamma;
+ fix16_t m_Mean_Variance_Estimator___Gamma_Initial_Mean;
+ fix16_t m_Mean_Variance_Estimator___Gamma_Initial_Variance;
+ fix16_t m_Mean_Variance_Estimator__Gamma_Mean;
+ fix16_t m_Mean_Variance_Estimator__Gamma_Variance;
+ fix16_t m_Mean_Variance_Estimator___Uptime_Gamma;
+ fix16_t m_Mean_Variance_Estimator___Uptime_Gating;
+ fix16_t m_Mean_Variance_Estimator___Gating_Duration_Minutes;
+ fix16_t m_Mean_Variance_Estimator___Sigmoid__L;
+ fix16_t m_Mean_Variance_Estimator___Sigmoid__K;
+ fix16_t m_Mean_Variance_Estimator___Sigmoid__X0;
+ fix16_t m_Mox_Model__Sraw_Std;
+ fix16_t m_Mox_Model__Sraw_Mean;
+ fix16_t m_Sigmoid_Scaled__Offset;
+ fix16_t m_Adaptive_Lowpass__A1;
+ fix16_t m_Adaptive_Lowpass__A2;
+ bool m_Adaptive_Lowpass___Initialized;
+ fix16_t m_Adaptive_Lowpass___X1;
+ fix16_t m_Adaptive_Lowpass___X2;
+ fix16_t m_Adaptive_Lowpass___X3;
+} VocAlgorithmParams;
+
+/**
+ * Initialize the VOC algorithm parameters. Call this once at the beginning or
+ * whenever the sensor stopped measurements.
+ * @param params Pointer to the VocAlgorithmParams struct
+ */
+void VocAlgorithm_init(VocAlgorithmParams* params);
+
+/**
+ * Get current algorithm states. Retrieved values can be used in
+ * VocAlgorithm_set_states() to resume operation after a short interruption,
+ * skipping initial learning phase. This feature can only be used after at least
+ * 3 hours of continuous operation.
+ * @param params Pointer to the VocAlgorithmParams struct
+ * @param state0 State0 to be stored
+ * @param state1 State1 to be stored
+ */
+void VocAlgorithm_get_states(VocAlgorithmParams* params, int32_t* state0,
+ int32_t* state1);
+
+/**
+ * Set previously retrieved algorithm states to resume operation after a short
+ * interruption, skipping initial learning phase. This feature should not be
+ * used after inerruptions of more than 10 minutes. Call this once after
+ * VocAlgorithm_init() and the optional VocAlgorithm_set_tuning_parameters(), if
+ * desired. Otherwise, the algorithm will start with initial learning phase.
+ * @param params Pointer to the VocAlgorithmParams struct
+ * @param state0 State0 to be restored
+ * @param state1 State1 to be restored
+ */
+void VocAlgorithm_set_states(VocAlgorithmParams* params, int32_t state0,
+ int32_t state1);
+
+/**
+ * Set parameters to customize the VOC algorithm. Call this once after
+ * VocAlgorithm_init(), if desired. Otherwise, the default values will be used.
+ *
+ * @param params Pointer to the VocAlgorithmParams struct
+ * @param voc_index_offset VOC index representing typical (average)
+ * conditions. Range 1..250, default 100
+ * @param learning_time_hours Time constant of long-term estimator.
+ * Past events will be forgotten after about
+ * twice the learning time.
+ * Range 1..72 [hours], default 12 [hours]
+ * @param gating_max_duration_minutes Maximum duration of gating (freeze of
+ * estimator during high VOC index signal).
+ * 0 (no gating) or range 1..720 [minutes],
+ * default 180 [minutes]
+ * @param std_initial Initial estimate for standard deviation.
+ * Lower value boosts events during initial
+ * learning period, but may result in larger
+ * device-to-device variations.
+ * Range 10..500, default 50
+ */
+void VocAlgorithm_set_tuning_parameters(VocAlgorithmParams* params,
+ int32_t voc_index_offset,
+ int32_t learning_time_hours,
+ int32_t gating_max_duration_minutes,
+ int32_t std_initial);
+
+/**
+ * Calculate the VOC index value from the raw sensor value.
+ *
+ * @param params Pointer to the VocAlgorithmParams struct
+ * @param sraw Raw value from the SGP40 sensor
+ * @param voc_index Calculated VOC index value from the raw sensor value. Zero
+ * during initial blackout period and 1..500 afterwards
+ */
+void VocAlgorithm_process(VocAlgorithmParams* params, int32_t sraw,
+ int32_t* voc_index);
+
+#endif /* VOCALGORITHM_H_ */
Index: src/sys/dev/i2c/sgp40.c
diff -u /dev/null src/sys/dev/i2c/sgp40.c:1.1
--- /dev/null Thu Oct 14 13:54:46 2021
+++ src/sys/dev/i2c/sgp40.c Thu Oct 14 13:54:46 2021
@@ -0,0 +1,802 @@
+/* $NetBSD: sgp40.c,v 1.1 2021/10/14 13:54:46 brad Exp $ */
+
+/*
+ * Copyright (c) 2021 Brad Spencer <[email protected]>
+ *
+ * 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, NEGL`IGENCE 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: sgp40.c,v 1.1 2021/10/14 13:54:46 brad Exp $");
+
+/*
+ Driver for the Sensirion SGP40 MOx gas sensor for air quality
+*/
+
+#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 <sys/condvar.h>
+#include <sys/kthread.h>
+
+#include <dev/sysmon/sysmonvar.h>
+#include <dev/i2c/i2cvar.h>
+#include <dev/i2c/sgp40reg.h>
+#include <dev/i2c/sgp40var.h>
+
+#include <dev/i2c/sensirion_arch_config.h>
+#include <dev/i2c/sensirion_voc_algorithm.h>
+
+static uint8_t sgp40_crc(uint8_t *, size_t);
+static int sgp40_cmdr(struct sgp40_sc *, uint16_t, uint8_t *, uint8_t, uint8_t *, size_t);
+static int sgp40_poke(i2c_tag_t, i2c_addr_t, bool);
+static int sgp40_match(device_t, cfdata_t, void *);
+static void sgp40_attach(device_t, device_t, void *);
+static int sgp40_detach(device_t, int);
+static void sgp40_refresh(struct sysmon_envsys *, envsys_data_t *);
+static int sgp40_verify_sysctl(SYSCTLFN_ARGS);
+static int sgp40_verify_temp_sysctl(SYSCTLFN_ARGS);
+static int sgp40_verify_rh_sysctl(SYSCTLFN_ARGS);
+static void sgp40_thread(void *);
+static void sgp40_stop_thread(void *);
+static void sgp40_take_measurement(void *, VocAlgorithmParams *);
+
+#define SGP40_DEBUG
+#ifdef SGP40_DEBUG
+#define DPRINTF(s, l, x) \
+ do { \
+ if (l <= s->sc_sgp40debug) \
+ printf x; \
+ } while (/*CONSTCOND*/0)
+#else
+#define DPRINTF(s, l, x)
+#endif
+
+CFATTACH_DECL_NEW(sgp40mox, sizeof(struct sgp40_sc),
+ sgp40_match, sgp40_attach, sgp40_detach, NULL);
+
+static struct sgp40_sensor sgp40_sensors[] = {
+ {
+ .desc = "VOC index",
+ .type = ENVSYS_INTEGER,
+ }
+};
+
+static struct sgp40_timing sgp40_timings[] = {
+ {
+ .cmd = SGP40_MEASURE_RAW,
+ .typicaldelay = 25000,
+ },
+ {
+ .cmd = SGP40_MEASURE_TEST,
+ .typicaldelay = 240000,
+ },
+ {
+ .cmd = SGP40_HEATER_OFF,
+ .typicaldelay = 100,
+ },
+ {
+ .cmd = SGP40_GET_SERIAL_NUMBER,
+ .typicaldelay = 100,
+ },
+ {
+ .cmd = SGP40_GET_FEATURESET,
+ .typicaldelay = 1000,
+ }
+};
+
+void
+sgp40_thread(void *aux)
+{
+ struct sgp40_sc *sc = aux;
+ int rv;
+ VocAlgorithmParams voc_algorithm_params;
+
+ mutex_enter(&sc->sc_threadmutex);
+
+ VocAlgorithm_init(&voc_algorithm_params);
+
+ while (sc->sc_stopping == false) {
+ rv = cv_timedwait(&sc->sc_condvar, &sc->sc_threadmutex, mstohz(1000));
+ if (rv == EWOULDBLOCK && sc->sc_stopping == false) {
+ sgp40_take_measurement(sc,&voc_algorithm_params);
+ }
+ }
+ mutex_exit(&sc->sc_threadmutex);
+ kthread_exit(0);
+}
+
+static void
+sgp40_stop_thread(void *aux)
+{
+ struct sgp40_sc *sc;
+ sc = aux;
+ int error;
+
+ mutex_enter(&sc->sc_threadmutex);
+ sc->sc_stopping = true;
+ cv_signal(&sc->sc_condvar);
+ mutex_exit(&sc->sc_threadmutex);
+
+ /* wait for the thread to exit */
+ kthread_join(sc->sc_thread);
+
+ mutex_enter(&sc->sc_mutex);
+ error = iic_acquire_bus(sc->sc_tag, 0);
+ if (error) {
+ DPRINTF(sc, 2, ("%s: Could not acquire iic bus for heater off in stop thread: %d\n",
+ device_xname(sc->sc_dev), error));
+ } else {
+ error = sgp40_cmdr(sc, SGP40_HEATER_OFF,NULL,0,NULL,0);
+ if (error) {
+ DPRINTF(sc, 2, ("%s: Error turning heater off: %d\n",
+ device_xname(sc->sc_dev), error));
+ }
+ iic_release_bus(sc->sc_tag, 0);
+ }
+ mutex_exit(&sc->sc_mutex);
+}
+
+static int
+sgp40_compute_temp_comp(int unconverted)
+{
+ /* The published algorithm for this conversion is:
+ (temp_in_celcius + 45) * 65535 / 175
+
+ However, this did not exactly yield the results that
+ the example in the data sheet, so something a little
+ different was done.
+
+ (temp_in_celcius + 45) * 65536 / 175
+
+ This was also scaled up by 10^2 and then scaled back to
+ preserve some percision. 37449 is simply (65536 * 100) / 175
+ and rounded.
+ */
+
+ return (((unconverted + 45) * 100) * 37449) / 10000;
+}
+
+static int
+sgp40_compute_rh_comp(int unconverted)
+{
+ int q;
+
+ /* The published algorithm for this conversion is:
+ %rh * 65535 / 100
+
+ However, this did not exactly yield the results that
+ the example in the data sheet, so something a little
+ different was done.
+
+ %rh * 65536 / 100
+
+ This was also scaled up by 10^2 and then scaled back to
+ preserve some percision. The value is also latched to 65535
+ as an upper limit.
+ */
+
+ q = ((unconverted * 100) * 65536) / 10000;
+ if (q > 65535)
+ q = 65535;
+ return q;
+}
+
+static void
+sgp40_take_measurement(void *aux, VocAlgorithmParams* params)
+{
+ struct sgp40_sc *sc;
+ sc = aux;
+ uint8_t args[6];
+ uint8_t buf[3];
+ uint16_t rawmeasurement;
+ int error;
+ uint8_t crc;
+ uint16_t convertedrh, convertedtemp;
+ int32_t voc_index;
+
+ mutex_enter(&sc->sc_mutex);
+ convertedrh = (uint16_t)sgp40_compute_rh_comp(sc->sc_rhcomp);
+ convertedtemp = (uint16_t)sgp40_compute_temp_comp(sc->sc_tempcomp);
+
+ DPRINTF(sc, 2, ("%s: Converted RH and Temp: %04x %04x\n",
+ device_xname(sc->sc_dev), convertedrh, convertedtemp));
+
+ args[0] = convertedrh >> 8;
+ args[1] = convertedrh & 0x00ff;
+ args[2] = sgp40_crc(&args[0],2);
+ args[3] = convertedtemp >> 8;
+ args[4] = convertedtemp & 0x00ff;
+ args[5] = sgp40_crc(&args[3],2);
+
+ /* The VOC algoritm has a black out time when it first starts to run
+ and does not return any indicator that is going on, so voc_index
+ in that case would be 0.. however, that is also a valid response
+ otherwise, although an unlikely one.
+ */
+ error = iic_acquire_bus(sc->sc_tag, 0);
+ if (error) {
+ DPRINTF(sc, 2, ("%s: Could not acquire iic bus for take measurement: %d\n",
+ device_xname(sc->sc_dev), error));
+ sc->sc_voc = 0;
+ sc->sc_vocvalid = false;
+ } else {
+ error = sgp40_cmdr(sc, SGP40_MEASURE_RAW, args, 6, buf, 3);
+ iic_release_bus(sc->sc_tag, 0);
+ if (error == 0) {
+ crc = sgp40_crc(&buf[0],2);
+ DPRINTF(sc, 2, ("%s: Raw ticks and crc: %02x%02x %02x %02x\n",
+ device_xname(sc->sc_dev), buf[0], buf[1], buf[2],crc));
+ if (buf[2] == crc) {
+ rawmeasurement = buf[0] << 8;
+ rawmeasurement |= buf[1];
+ VocAlgorithm_process(params, rawmeasurement, &voc_index);
+ DPRINTF(sc, 2, ("%s: VOC index: %d\n",
+ device_xname(sc->sc_dev), voc_index));
+ sc->sc_voc = voc_index;
+ sc->sc_vocvalid = true;
+ } else {
+ sc->sc_voc = 0;
+ sc->sc_vocvalid = false;
+ }
+ } else {
+ DPRINTF(sc, 2, ("%s: Failed to get measurement %d\n",
+ device_xname(sc->sc_dev), error));
+ sc->sc_voc = 0;
+ sc->sc_vocvalid = false;
+ }
+ }
+
+ mutex_exit(&sc->sc_mutex);
+}
+
+int
+sgp40_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;
+}
+
+int
+sgp40_verify_temp_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 < -45 || t > 130)
+ return EINVAL;
+
+ *(int *)rnode->sysctl_data = t;
+
+ return 0;
+}
+
+int
+sgp40_verify_rh_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 || t > 100)
+ return EINVAL;
+
+ *(int *)rnode->sysctl_data = t;
+
+ return 0;
+}
+
+static int
+sgp40_cmddelay(uint16_t cmd)
+{
+ int r = -1;
+
+ for(int i = 0;i < __arraycount(sgp40_timings);i++) {
+ if (cmd == sgp40_timings[i].cmd) {
+ r = sgp40_timings[i].typicaldelay;
+ break;
+ }
+ }
+
+ if (r == -1) {
+ panic("Bad command look up in cmd delay: cmd: %d\n",cmd);
+ }
+
+ return r;
+}
+
+static int
+sgp40_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;
+ uint16_t cmd16;
+
+ cmd16 = cmd[0] << 8;
+ cmd16 = cmd16 | cmd[1];
+
+ error = iic_exec(tag,I2C_OP_WRITE_WITH_STOP,addr,cmd,clen,NULL,0,0);
+
+ /* Every command returns something except for turning the heater off
+ and the general soft reset which returns nothing. */
+ if (error == 0 && cmd16 != SGP40_HEATER_OFF) {
+ /* Every command has a particular delay for how long
+ it typically takes and the max time it will take. */
+ cmddelay = sgp40_cmddelay(cmd16);
+ 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
+sgp40_cmdr(struct sgp40_sc *sc, uint16_t cmd, uint8_t *extraargs, uint8_t argslen, uint8_t *buf, size_t blen)
+{
+ uint8_t fullcmd[8];
+ uint8_t cmdlen;
+ int n;
+
+ /* The biggest documented command + arguments is 8 uint8_t bytes long. */
+ /* Catch anything that ties to have an arglen more than 6 */
+
+ KASSERT(argslen <= 6);
+
+ memset(fullcmd, 0, 8);
+
+ fullcmd[0] = cmd >> 8;
+ fullcmd[1] = cmd & 0x00ff;
+ cmdlen = 2;
+
+ n = 0;
+ while (extraargs != NULL && n < argslen && cmdlen <= 7) {
+ fullcmd[cmdlen] = extraargs[n];
+ cmdlen++;
+ n++;
+ }
+ DPRINTF(sc, 2, ("%s: Full command and arguments: %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ device_xname(sc->sc_dev), fullcmd[0], fullcmd[1],
+ fullcmd[2], fullcmd[3], fullcmd[4], fullcmd[5],
+ fullcmd[6], fullcmd[7]));
+ return sgp40_cmd(sc->sc_tag, sc->sc_addr, fullcmd, cmdlen, buf, blen, sc->sc_readattempts);
+}
+
+static uint8_t
+sgp40_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) ^ 0x31;
+ else
+ crc <<= 1;
+ }
+ }
+ return crc;
+}
+
+static int
+sgp40_poke(i2c_tag_t tag, i2c_addr_t addr, bool matchdebug)
+{
+ uint8_t reg[2];
+ uint8_t buf[9];
+ int error;
+
+ /* Possible bug... this command may not work if the chip is not idle,
+ however, it appears to be used by a lot of other code as a probe.
+ */
+ reg[0] = SGP40_GET_SERIAL_NUMBER >> 8;
+ reg[1] = SGP40_GET_SERIAL_NUMBER & 0x00ff;
+
+ error = sgp40_cmd(tag, addr, reg, 2, buf, 9, 10);
+ if (matchdebug) {
+ printf("poke X 1: %d\n", error);
+ }
+ return error;
+}
+
+static int
+sgp40_sysctl_init(struct sgp40_sc *sc)
+{
+ int error;
+ const struct sysctlnode *cnode;
+ int sysctlroot_num;
+
+ if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
+ 0, CTLTYPE_NODE, device_xname(sc->sc_dev),
+ SYSCTL_DESCR("SGP40 controls"), NULL, 0, NULL, 0, CTL_HW,
+ CTL_CREATE, CTL_EOL)) != 0)
+ return error;
+
+ sysctlroot_num = cnode->sysctl_num;
+
+#ifdef SGP40_DEBUG
+ if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
+ CTLFLAG_READWRITE, CTLTYPE_INT, "debug",
+ SYSCTL_DESCR("Debug level"), sgp40_verify_sysctl, 0,
+ &sc->sc_sgp40debug, 0, CTL_HW, sysctlroot_num, CTL_CREATE,
+ CTL_EOL)) != 0)
+ return error;
+
+#endif
+
+ if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
+ CTLFLAG_READWRITE, CTLTYPE_INT, "readattempts",
+ SYSCTL_DESCR("The number of times to attempt to read the values"),
+ sgp40_verify_sysctl, 0, &sc->sc_readattempts, 0, CTL_HW,
+ sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
+ return error;
+
+ if ((error = sysctl_createv(&sc->sc_sgp40log, 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_sgp40log, 0, NULL, &cnode,
+ 0, CTLTYPE_NODE, "compensation",
+ SYSCTL_DESCR("SGP40 measurement compensations"), NULL, 0, NULL, 0, CTL_HW,
+ sysctlroot_num, CTL_CREATE, CTL_EOL)) != 0)
+ return error;
+ int compensation_num = cnode->sysctl_num;
+
+ if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
+ CTLFLAG_READWRITE, CTLTYPE_INT, "temperature",
+ SYSCTL_DESCR("Temperature compensation in celsius"),
+ sgp40_verify_temp_sysctl, 0, &sc->sc_tempcomp, 0, CTL_HW,
+ sysctlroot_num, compensation_num, CTL_CREATE, CTL_EOL)) != 0)
+ return error;
+
+ if ((error = sysctl_createv(&sc->sc_sgp40log, 0, NULL, &cnode,
+ CTLFLAG_READWRITE, CTLTYPE_INT, "humidity",
+ SYSCTL_DESCR("Humidity compensation in %RH"),
+ sgp40_verify_rh_sysctl, 0, &sc->sc_rhcomp, 0, CTL_HW,
+ sysctlroot_num, compensation_num, CTL_CREATE, CTL_EOL)) != 0)
+ return error;
+
+ return 0;
+}
+
+static int
+sgp40_match(device_t parent, cfdata_t match, void *aux)
+{
+ struct i2c_attach_args *ia = aux;
+ int error, match_result;
+ const bool matchdebug = false;
+
+ if (matchdebug)
+ printf("in match\n");
+
+ if (iic_use_direct_match(ia, match, NULL, &match_result))
+ return match_result;
+
+ /* indirect config - check for configured address */
+ if (ia->ia_addr != SGP40_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 = sgp40_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
+sgp40_attach(device_t parent, device_t self, void *aux)
+{
+ struct sgp40_sc *sc;
+ struct i2c_attach_args *ia;
+ int error, i;
+ int ecount = 0;
+ uint8_t buf[9];
+ uint8_t tstcrc;
+ uint16_t chiptestvalue;
+ uint64_t serial_number = 0;
+ uint8_t sn_crc1, sn_crc2, sn_crc3, sn_crcv1, sn_crcv2, sn_crcv3;
+ uint8_t fs_crc, fs_crcv;
+ uint16_t featureset;
+
+ ia = aux;
+ sc = device_private(self);
+
+ sc->sc_dev = self;
+ sc->sc_tag = ia->ia_tag;
+ sc->sc_addr = ia->ia_addr;
+ sc->sc_sgp40debug = 0;
+ sc->sc_readattempts = 10;
+ sc->sc_ignorecrc = false;
+ sc->sc_stopping = false;
+ sc->sc_voc = 0;
+ sc->sc_vocvalid = false;
+ sc->sc_tempcomp = SGP40_DEFAULT_TEMP_COMP;
+ sc->sc_rhcomp = SGP40_DEFAULT_RH_COMP;
+ sc->sc_sme = NULL;
+
+ aprint_normal("\n");
+
+ mutex_init(&sc->sc_threadmutex, MUTEX_DEFAULT, IPL_NONE);
+ mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_NONE);
+ cv_init(&sc->sc_condvar, "sgp40cv");
+ sc->sc_numsensors = __arraycount(sgp40_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 = sgp40_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;
+ }
+
+ /* Usually one would reset the chip here, but that is not possible
+ without resetting the entire bus, so we won't do that.
+
+ What we will do is make sure that the chip is idle by running the
+ turn-the-heater command.
+ */
+
+ error = sgp40_cmdr(sc, SGP40_HEATER_OFF, NULL, 0, NULL, 0);
+ if (error) {
+ aprint_error_dev(self, "Failed to turn off the heater: %d\n",
+ error);
+ ecount++;
+ }
+
+ error = sgp40_cmdr(sc, SGP40_GET_SERIAL_NUMBER, NULL, 0, buf, 9);
+ if (error) {
+ aprint_error_dev(self, "Failed to get serial number: %d\n",
+ error);
+ ecount++;
+ }
+
+ sn_crc1 = sgp40_crc(&buf[0],2);
+ sn_crc2 = sgp40_crc(&buf[3],2);
+ sn_crc3 = sgp40_crc(&buf[6],2);
+ sn_crcv1 = buf[2];
+ sn_crcv2 = buf[5];
+ sn_crcv3 = buf[8];
+ serial_number = buf[0];
+ serial_number = (serial_number << 8) | buf[1];
+ serial_number = (serial_number << 8) | buf[3];
+ serial_number = (serial_number << 8) | buf[4];
+ serial_number = (serial_number << 8) | buf[6];
+ serial_number = (serial_number << 8) | buf[7];
+
+ DPRINTF(sc, 2, ("%s: raw serial number: %02x %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], buf[6], buf[7], buf[8]));
+
+ error = sgp40_cmdr(sc, SGP40_GET_FEATURESET, NULL, 0, buf, 3);
+ if (error) {
+ aprint_error_dev(self, "Failed to get featureset: %d\n",
+ error);
+ ecount++;
+ }
+
+ fs_crc = sgp40_crc(&buf[0],2);
+ fs_crcv = buf[2];
+ featureset = buf[0];
+ featureset = (featureset << 8) | buf[1];
+
+ DPRINTF(sc, 2, ("%s: raw feature set: %02x %02x %02x\n",
+ device_xname(sc->sc_dev), buf[0], buf[1], buf[2]));
+
+ error = sgp40_cmdr(sc, SGP40_MEASURE_TEST, NULL, 0, buf, 3);
+ if (error) {
+ aprint_error_dev(self, "Failed to perform a chip test: %d\n",
+ error);
+ ecount++;
+ }
+
+ tstcrc = sgp40_crc(&buf[0],2);
+
+ DPRINTF(sc, 2, ("%s: chip test values: %02x%02x - %02x ; %02x\n",
+ device_xname(sc->sc_dev), buf[0], buf[1], buf[2], tstcrc));
+
+ iic_release_bus(sc->sc_tag, 0);
+ if (error != 0) {
+ aprint_error_dev(self, "Unable to setup device\n");
+ goto out;
+ }
+
+ chiptestvalue = buf[0] << 8;
+ chiptestvalue |= buf[1];
+
+ for (i = 0; i < sc->sc_numsensors; i++) {
+ strlcpy(sc->sc_sensors[i].desc, sgp40_sensors[i].desc,
+ sizeof(sc->sc_sensors[i].desc));
+
+ sc->sc_sensors[i].units = sgp40_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 = sgp40_refresh;
+
+ DPRINTF(sc, 2, ("sgp40_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;
+ }
+
+ error = kthread_create(PRI_NONE, KTHREAD_MUSTJOIN, NULL,
+ sgp40_thread, sc, &sc->sc_thread,
+ device_xname(sc->sc_dev));
+ if (error) {
+ aprint_error_dev(self,"Unable to create measurement thread\n");
+ goto out;
+ }
+
+ aprint_normal_dev(self, "Sensirion SGP40, Serial number: %jx%sFeature set word: 0x%jx%s%s%s",
+ serial_number,
+ (sn_crc1 == sn_crcv1 && sn_crc2 == sn_crcv2 && sn_crc3 == sn_crcv3) ? ", " : " (bad crc), ",
+ (uintmax_t)featureset,
+ (fs_crc == fs_crcv) ? ", " : " (bad crc), ",
+ (chiptestvalue == SGP40_TEST_RESULTS_ALL_PASSED) ? "All chip tests passed" :
+ (chiptestvalue == SGP40_TEST_RESULTS_SOME_FAILED) ? "Some chip tests failed" :
+ "Unknown test results",
+ (tstcrc == buf[2]) ? "\n" : " (bad crc)\n");
+ return;
+out:
+ sysmon_envsys_destroy(sc->sc_sme);
+ sc->sc_sme = NULL;
+}
+
+static void
+sgp40_refresh(struct sysmon_envsys * sme, envsys_data_t * edata)
+{
+ struct sgp40_sc *sc;
+ sc = sme->sme_cookie;
+
+ mutex_enter(&sc->sc_mutex);
+ if (sc->sc_vocvalid == true) {
+ edata->value_cur = (uint32_t)sc->sc_voc;
+ edata->state = ENVSYS_SVALID;
+ } else {
+ edata->state = ENVSYS_SINVALID;
+ }
+ mutex_exit(&sc->sc_mutex);
+}
+
+static int
+sgp40_detach(device_t self, int flags)
+{
+ struct sgp40_sc *sc;
+
+ sc = device_private(self);
+
+ /* stop the measurement thread */
+ sgp40_stop_thread(sc);
+
+ /* Remove the sensors */
+ mutex_enter(&sc->sc_mutex);
+ 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_sgp40log);
+
+ /* Remove the mutex */
+ mutex_destroy(&sc->sc_mutex);
+ mutex_destroy(&sc->sc_threadmutex);
+
+ return 0;
+}
+
+MODULE(MODULE_CLASS_DRIVER, sgp40mox, "i2cexec,sysmon_envsys");
+
+#ifdef _MODULE
+#include "ioconf.c"
+#endif
+
+static int
+sgp40mox_modcmd(modcmd_t cmd, void *opaque)
+{
+
+ switch (cmd) {
+ case MODULE_CMD_INIT:
+#ifdef _MODULE
+ return config_init_component(cfdriver_ioconf_sgp40mox,
+ cfattach_ioconf_sgp40mox, cfdata_ioconf_sgp40mox);
+#else
+ return 0;
+#endif
+ case MODULE_CMD_FINI:
+#ifdef _MODULE
+ return config_fini_component(cfdriver_ioconf_sgp40mox,
+ cfattach_ioconf_sgp40mox, cfdata_ioconf_sgp40mox);
+#else
+ return 0;
+#endif
+ default:
+ return ENOTTY;
+ }
+}
Index: src/sys/dev/i2c/sgp40reg.h
diff -u /dev/null src/sys/dev/i2c/sgp40reg.h:1.1
--- /dev/null Thu Oct 14 13:54:46 2021
+++ src/sys/dev/i2c/sgp40reg.h Thu Oct 14 13:54:46 2021
@@ -0,0 +1,50 @@
+/* $NetBSD: sgp40reg.h,v 1.1 2021/10/14 13:54:46 brad Exp $ */
+
+/*
+ * Copyright (c) 2021 Brad Spencer <[email protected]>
+ *
+ * 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_SGP40REG_H_
+#define _DEV_I2C_SGP40REG_H_
+
+#define SGP40_TYPICAL_ADDR 0x59
+
+/* There are three documented commands for this chip, plus one that is called a
+ soft reset. But really the soft reset is a general reset of all devices on
+ the bus, hence it is not as usable as one would hope
+ */
+
+#define SGP40_MEASURE_RAW 0x260f
+#define SGP40_MEASURE_TEST 0x280e
+#define SGP40_HEATER_OFF 0x3615
+/* The get serial number command is documented in version 1.1 of the
+ datasheet.
+*/
+#define SGP40_GET_SERIAL_NUMBER 0x3682
+/* The get featureset command is not documented in any datasheet that could
+ be found. However, it is present and used in a lot of example code.
+ It has no additional arguments aside from the command itself, and returns
+ a uint16_t for the data and a uint8_t for the crc.
+*/
+#define SGP40_GET_FEATURESET 0x202f
+
+/* The results of a self test are documented as being either everything is ok, or
+ something failed.
+ */
+
+#define SGP40_TEST_RESULTS_ALL_PASSED 0xd400
+#define SGP40_TEST_RESULTS_SOME_FAILED 0x4b00
+
+#endif
Index: src/sys/dev/i2c/sgp40var.h
diff -u /dev/null src/sys/dev/i2c/sgp40var.h:1.1
--- /dev/null Thu Oct 14 13:54:46 2021
+++ src/sys/dev/i2c/sgp40var.h Thu Oct 14 13:54:46 2021
@@ -0,0 +1,61 @@
+/* $NetBSD: sgp40var.h,v 1.1 2021/10/14 13:54:46 brad Exp $ */
+
+/*
+ * Copyright (c) 2021 Brad Spencer <[email protected]>
+ *
+ * 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_SGP40VAR_H_
+#define _DEV_I2C_SGP40VAR_H_
+
+#define SGP40_NUM_SENSORS 1
+#define SGP40_VOC_SENSOR 0
+
+#define SGP40_DEFAULT_TEMP_COMP 25
+#define SGP40_DEFAULT_RH_COMP 50
+
+
+struct sgp40_sc {
+ int sc_sgp40debug;
+ device_t sc_dev;
+ i2c_tag_t sc_tag;
+ i2c_addr_t sc_addr;
+ kmutex_t sc_threadmutex; /* for the measurement kthread */
+ kmutex_t sc_mutex; /* for reading the i2c bus */
+ kcondvar_t sc_condvar;
+ struct lwp *sc_thread;
+ int sc_numsensors;
+ struct sysmon_envsys *sc_sme;
+ struct sysctllog *sc_sgp40log;
+ envsys_data_t sc_sensors[SGP40_NUM_SENSORS];
+ uint16_t sc_voc;
+ bool sc_vocvalid;
+ bool sc_ignorecrc;
+ int sc_readattempts;
+ bool sc_stopping;
+ int sc_tempcomp;
+ int sc_rhcomp;
+};
+
+struct sgp40_sensor {
+ const char *desc;
+ enum envsys_units type;
+};
+
+struct sgp40_timing {
+ uint16_t cmd;
+ int typicaldelay;
+};
+
+#endif
Index: src/sys/modules/sgp40mox/Makefile
diff -u /dev/null src/sys/modules/sgp40mox/Makefile:1.1
--- /dev/null Thu Oct 14 13:54:46 2021
+++ src/sys/modules/sgp40mox/Makefile Thu Oct 14 13:54:46 2021
@@ -0,0 +1,12 @@
+.include "../Makefile.inc"
+
+.PATH: ${S}/dev/i2c
+
+KMOD= sgp40mox
+IOCONF= sgp40mox.ioconf
+SRCS= sgp40.c
+SRCS+= sensirion_voc_algorithm.c
+
+WARNS= 3
+
+.include <bsd.kmodule.mk>
Index: src/sys/modules/sgp40mox/sgp40mox.ioconf
diff -u /dev/null src/sys/modules/sgp40mox/sgp40mox.ioconf:1.1
--- /dev/null Thu Oct 14 13:54:46 2021
+++ src/sys/modules/sgp40mox/sgp40mox.ioconf Thu Oct 14 13:54:46 2021
@@ -0,0 +1,8 @@
+ioconf sgp40mox
+
+include "conf/files"
+
+pseudo-root iic*
+
+sgp40mox* at iic? addr 0x59
+