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.
This commit is contained in:
parent
bb8e9cddaa
commit
ea034c40cb
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 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 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 \
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
.\" $NetBSD: sgp40mox.4,v 1.1 2021/10/14 13:54:45 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 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 brad@anduin.eldar.org .
|
||||
.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.
|
|
@ -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
|
||||
|
|
|
@ -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 <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, 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 */
|
|
@ -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;
|
||||
}
|
|
@ -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_ */
|
|
@ -0,0 +1,802 @@
|
|||
/* $NetBSD: sgp40.c,v 1.1 2021/10/14 13:54:46 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, 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/* $NetBSD: sgp40reg.h,v 1.1 2021/10/14 13:54:46 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_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
|
|
@ -0,0 +1,61 @@
|
|||
/* $NetBSD: sgp40var.h,v 1.1 2021/10/14 13:54:46 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_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
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -0,0 +1,8 @@
|
|||
ioconf sgp40mox
|
||||
|
||||
include "conf/files"
|
||||
|
||||
pseudo-root iic*
|
||||
|
||||
sgp40mox* at iic? addr 0x59
|
||||
|
Loading…
Reference in New Issue