diff --git a/.gitignore b/.gitignore index 0cfd49b..a309775 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ fw/Debug fw/.settings *.zip + +tests/__pycache__/ diff --git a/.project b/.project new file mode 100644 index 0000000..5153776 --- /dev/null +++ b/.project @@ -0,0 +1,11 @@ + + + IAQ_Wired_Sensor + + + + + + + + diff --git a/fw/Core/Inc/main.h b/fw/Core/Inc/main.h index 69582ee..5e2126d 100644 --- a/fw/Core/Inc/main.h +++ b/fw/Core/Inc/main.h @@ -53,10 +53,11 @@ extern "C" { #include "scd4x.h" #include "sht4x.h" #include "sps30.h" -#include "sgp4x.h" +#include #include "modbus.h" #include "config.h" #include "rgbled.h" +#include "sensirion_gas_index_algorithm.h" /* USER CODE END Includes */ /* Exported types ------------------------------------------------------------*/ @@ -97,7 +98,8 @@ int8_t uart_enable_interrupts(void); 0 bit for subpriority */ #endif /* USER CODE BEGIN Private defines */ -#define MEASUREMENT_PERIOD_MS 600000 +#define SYSTEM_CLOCK_HZ 12000000 +#define MEASUREMENT_PERIOD_S 6 #define RESET_MAGIC_NUMBER 0xABCD #define MODBUS_ASSERT(x) if (x == 0) return MODBUS_ERROR_FUNCTION_NOT_IMPLEMENTED diff --git a/fw/Core/Inc/sensirion_gas_index_algorithm.h b/fw/Core/Inc/sensirion_gas_index_algorithm.h new file mode 100644 index 0000000..b7feae6 --- /dev/null +++ b/fw/Core/Inc/sensirion_gas_index_algorithm.h @@ -0,0 +1,280 @@ +/* + * 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 GASINDEXALGORITHM_H_ +#define GASINDEXALGORITHM_H_ + +#include +#include "main.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))) + +#ifndef __cplusplus + +#if __STDC_VERSION__ >= 199901L +#include +#else + +#ifndef bool +#define bool int +#define true 1 +#define false 0 +#endif // bool + +#endif // __STDC_VERSION__ + +#endif // __cplusplus + +// Should be set by the building toolchain +#ifndef LIBRARY_VERSION_NAME +#define LIBRARY_VERSION_NAME "3.1.0" +#endif + +#define GasIndexAlgorithm_ALGORITHM_TYPE_VOC (0) +#define GasIndexAlgorithm_ALGORITHM_TYPE_NOX (1) +#define GasIndexAlgorithm_SAMPLING_INTERVAL (MEASUREMENT_PERIOD_S) +#define GasIndexAlgorithm_INITIAL_BLACKOUT (60.) // changed +#define GasIndexAlgorithm_INDEX_GAIN (230.) +#define GasIndexAlgorithm_SRAW_STD_INITIAL (50.) +#define GasIndexAlgorithm_SRAW_STD_BONUS_VOC (220.) +#define GasIndexAlgorithm_SRAW_STD_NOX (2000.) +#define GasIndexAlgorithm_TAU_MEAN_HOURS (12.) +#define GasIndexAlgorithm_TAU_VARIANCE_HOURS (12.) +#define GasIndexAlgorithm_TAU_INITIAL_MEAN_VOC (20.) +#define GasIndexAlgorithm_TAU_INITIAL_MEAN_NOX (1200.) +#define GasIndexAlgorithm_INIT_DURATION_MEAN_VOC ((3600. * 0.75)) +#define GasIndexAlgorithm_INIT_DURATION_MEAN_NOX ((3600. * 4.75)) +#define GasIndexAlgorithm_INIT_TRANSITION_MEAN (0.01) +#define GasIndexAlgorithm_TAU_INITIAL_VARIANCE (2500.) +#define GasIndexAlgorithm_INIT_DURATION_VARIANCE_VOC ((3600. * 1.45)) +#define GasIndexAlgorithm_INIT_DURATION_VARIANCE_NOX ((3600. * 5.70)) +#define GasIndexAlgorithm_INIT_TRANSITION_VARIANCE (0.01) +#define GasIndexAlgorithm_GATING_THRESHOLD_VOC (340.) +#define GasIndexAlgorithm_GATING_THRESHOLD_NOX (30.) +#define GasIndexAlgorithm_GATING_THRESHOLD_INITIAL (510.) +#define GasIndexAlgorithm_GATING_THRESHOLD_TRANSITION (0.09) +#define GasIndexAlgorithm_GATING_VOC_MAX_DURATION_MINUTES ((60. * 3.)) +#define GasIndexAlgorithm_GATING_NOX_MAX_DURATION_MINUTES ((60. * 12.)) +#define GasIndexAlgorithm_GATING_MAX_RATIO (0.3) +#define GasIndexAlgorithm_SIGMOID_L (500.) +#define GasIndexAlgorithm_SIGMOID_K_VOC (-0.0065) +#define GasIndexAlgorithm_SIGMOID_X0_VOC (213.) +#define GasIndexAlgorithm_SIGMOID_K_NOX (-0.0101) +#define GasIndexAlgorithm_SIGMOID_X0_NOX (614.) +#define GasIndexAlgorithm_VOC_INDEX_OFFSET_DEFAULT (100.) +#define GasIndexAlgorithm_NOX_INDEX_OFFSET_DEFAULT (1.) +#define GasIndexAlgorithm_LP_TAU_FAST (20.0) +#define GasIndexAlgorithm_LP_TAU_SLOW (500.0) +#define GasIndexAlgorithm_LP_ALPHA (-0.2) +#define GasIndexAlgorithm_VOC_SRAW_MINIMUM (20000) +#define GasIndexAlgorithm_NOX_SRAW_MINIMUM (10000) +#define GasIndexAlgorithm_PERSISTENCE_UPTIME_GAMMA ((3. * 3600.)) +#define GasIndexAlgorithm_TUNING_INDEX_OFFSET_MIN (1) +#define GasIndexAlgorithm_TUNING_INDEX_OFFSET_MAX (250) +#define GasIndexAlgorithm_TUNING_LEARNING_TIME_OFFSET_HOURS_MIN (1) +#define GasIndexAlgorithm_TUNING_LEARNING_TIME_OFFSET_HOURS_MAX (1000) +#define GasIndexAlgorithm_TUNING_LEARNING_TIME_GAIN_HOURS_MIN (1) +#define GasIndexAlgorithm_TUNING_LEARNING_TIME_GAIN_HOURS_MAX (1000) +#define GasIndexAlgorithm_TUNING_GATING_MAX_DURATION_MINUTES_MIN (0) +#define GasIndexAlgorithm_TUNING_GATING_MAX_DURATION_MINUTES_MAX (3000) +#define GasIndexAlgorithm_TUNING_STD_INITIAL_MIN (10) +#define GasIndexAlgorithm_TUNING_STD_INITIAL_MAX (5000) +#define GasIndexAlgorithm_TUNING_GAIN_FACTOR_MIN (1) +#define GasIndexAlgorithm_TUNING_GAIN_FACTOR_MAX (1000) +#define GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING (64.) +#define GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__ADDITIONAL_GAMMA_MEAN_SCALING \ + (8.) +#define GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX (32767.) + +/** + * Struct to hold all parameters and states of the gas algorithm. + */ +typedef struct { + int32_t mAlgorithm_Type; + fix16_t mIndex_Offset; + int32_t mSraw_Minimum; + fix16_t mGating_Max_Duration_Minutes; + fix16_t mInit_Duration_Mean; + fix16_t mInit_Duration_Variance; + fix16_t mGating_Threshold; + fix16_t mIndex_Gain; + fix16_t mTau_Mean_Hours; + fix16_t mTau_Variance_Hours; + fix16_t mSraw_Std_Initial; + fix16_t mUptime; + fix16_t mSraw; + fix16_t mGas_Index; + 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_Mean; + fix16_t m_Mean_Variance_Estimator___Gamma_Variance; + 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__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__K; + fix16_t m_Sigmoid_Scaled__X0; + fix16_t m_Sigmoid_Scaled__Offset_Default; + 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; +} GasIndexAlgorithmParams; + +/** + * Initialize the gas index algorithm parameters for the specified algorithm + * type and reset its internal states. Call this once at the beginning. + * @param params Pointer to the GasIndexAlgorithmParams struct + * @param algorithm_type 0 (GasIndexAlgorithm_ALGORITHM_TYPE_VOC) for VOC or + * 1 (GasIndexAlgorithm_ALGORITHM_TYPE_NOX) for NOx + */ +void GasIndexAlgorithm_init(GasIndexAlgorithmParams* params, + int32_t algorithm_type); + +/** + * Reset the internal states of the gas index algorithm. Previously set tuning + * parameters are preserved. Call this when resuming operation after a + * measurement interruption. + * @param params Pointer to the GasIndexAlgorithmParams struct + */ +void GasIndexAlgorithm_reset(GasIndexAlgorithmParams* params); + +/** + * Get current algorithm states. Retrieved values can be used in + * GasIndexAlgorithm_set_states() to resume operation after a short + * interruption, skipping initial learning phase. + * NOTE: This feature can only be used for VOC algorithm type and after at least + * 3 hours of continuous operation. + * @param params Pointer to the GasIndexAlgorithmParams struct + * @param state0 State0 to be stored + * @param state1 State1 to be stored + */ +void GasIndexAlgorithm_get_states(const GasIndexAlgorithmParams* 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 interruptions of more than 10 minutes. Call this once after + * GasIndexAlgorithm_init() or GasIndexAlgorithm_reset() and the optional + * GasIndexAlgorithm_set_tuning_parameters(), if desired. Otherwise, the + * algorithm will start with initial learning phase. + * NOTE: This feature can only be used for VOC algorithm type. + * @param params Pointer to the GasIndexAlgorithmParams struct + * @param state0 State0 to be restored + * @param state1 State1 to be restored + */ +void GasIndexAlgorithm_set_states(GasIndexAlgorithmParams* params, + int32_t state0, int32_t state1); + +/** + * Set parameters to customize the gas index algorithm. Call this once after + * GasIndexAlgorithm_init() and before optional GasIndexAlgorithm_set_states(), + * if desired. Otherwise, the default values will be used. + * + * @param params Pointer to the GasIndexAlgorithmParams + * struct + * @param index_offset Gas index representing typical (average) + * conditions. Range 1..250, + * default 100 for VOC and 1 for NOx + * @param learning_time_offset_hours Time constant of long-term estimator for + * offset. Past events will be forgotten + * after about twice the learning time. + * Range 1..1000 [hours], default 12 [hours] + * @param learning_time_gain_hours Time constant of long-term estimator for + * gain. Past events will be forgotten + * after about twice the learning time. + * Range 1..1000 [hours], default 12 [hours] + * NOTE: This value is not relevant for NOx + * algorithm type + * @param gating_max_duration_minutes Maximum duration of gating (freeze of + * estimator during high gas index signal). + * 0 (no gating) or range 1..3000 [minutes], + * default 180 [minutes] for VOC and + * 720 [minutes] for NOx + * @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..5000, default 50 + * NOTE: This value is not relevant for NOx + * algorithm type + * @param gain_factor Factor used to scale applied gain value + * when calculating gas index. Range 1..1000, + * default 230 + */ +void GasIndexAlgorithm_set_tuning_parameters( + GasIndexAlgorithmParams* params, int32_t index_offset, + int32_t learning_time_offset_hours, int32_t learning_time_gain_hours, + int32_t gating_max_duration_minutes, int32_t std_initial, + int32_t gain_factor); + +/** + * Get current parameters to customize the gas index algorithm. + * Refer to GasIndexAlgorithm_set_tuning_parameters() for description of the + * parameters. + */ +void GasIndexAlgorithm_get_tuning_parameters( + const GasIndexAlgorithmParams* params, int32_t* index_offset, + int32_t* learning_time_offset_hours, int32_t* learning_time_gain_hours, + int32_t* gating_max_duration_minutes, int32_t* std_initial, + int32_t* gain_factor); + +/** + * Calculate the gas index value from the raw sensor value. + * + * @param params Pointer to the GasIndexAlgorithmParams struct + * @param sraw Raw value from the SGP4x sensor + * @param gas_index Calculated gas index value from the raw sensor value. Zero + * during initial blackout period and 1..500 afterwards + */ +void GasIndexAlgorithm_process(GasIndexAlgorithmParams* params, int32_t sraw, + int32_t* gas_index); + +#endif /* GASINDEXALGORITHM_H_ */ diff --git a/fw/Core/Inc/sgp40.h b/fw/Core/Inc/sgp40.h new file mode 100644 index 0000000..e4faf01 --- /dev/null +++ b/fw/Core/Inc/sgp40.h @@ -0,0 +1,50 @@ +/* + * sgp4x.h + * + * Created on: Jan 9, 2022 + * Author: david + */ + +#ifndef INC_SGP40_H_ +#define INC_SGP40_H_ + + +#include "stdint.h" +#include "stm32l0xx_ll_i2c.h" +#include "stm32l0xx_ll_utils.h" +#include "i2c.h" +#include "crc8.h" + +/* + * Defines & macros + */ + +#define SGP40_I2C_ADDRESS 0x59 +#define SGP40_MAX_MEAS_DURATION_MS 50 + +/* + * Return values + */ + +#define SGP40_OK 0 +#define SGP40_ERROR -1 // generic error +#define SGP40_CRC8_ERROR -2 // checksum failed + +typedef enum { + SGP40_MEASURE_RAW_SIGNAL = 0x260F, + SGP40_EXECUTE_SELF_TEST = 0x280E, + SGP40_TURN_HEATER_OFF = 0x3615, + SGP40_GET_SERIAL_NUMBER = 0x3682, + SGP40_SOFT_RESET = 0x0006 +} sgp40_cmd_t; + +int8_t sgp40_send_cmd(sgp40_cmd_t cmd); + +int8_t sgp40_measure_raw_signal (uint16_t * voc_ticks); +int8_t sgp40_measure_raw_signal_compensated (uint16_t * voc_ticks, uint16_t relative_humidity, int16_t temperature); +int8_t sgp40_execute_self_test ( uint8_t * test_result); +int8_t sgp40_get_serial_number ( uint8_t serial[6]); +int8_t sgp40_turn_heater_off ( void ); +int8_t sgp40_soft_reset ( void ); + +#endif /* INC_SGP40_H_ */ diff --git a/fw/Core/Inc/sgp4x.h b/fw/Core/Inc/sgp4x.h deleted file mode 100644 index acbaac7..0000000 --- a/fw/Core/Inc/sgp4x.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * sgp4x.h - * - * Created on: Jan 9, 2022 - * Author: david - */ - -#ifndef INC_SGP4X_H_ -#define INC_SGP4X_H_ - - -#include "stdint.h" -#include "stm32l0xx_ll_i2c.h" -#include "stm32l0xx_ll_utils.h" -#include "i2c.h" -#include "crc8.h" - -/* - * Defines & macros - */ - -#define SGP4X_I2C_ADDRESS 0x59 -#define SGP4X_MAX_MEAS_DURATION_MS 50 - -/* - * Return values - */ - -#define SGP4X_OK 0 -#define SGP4X_ERROR -1 // generic error -#define SGP4X_CRC8_ERROR -2 // checksum failed - -typedef enum { - SGP4X_MEASURE_RAW_SIGNAL = 0x260F, - SGP4X_EXECUTE_SELF_TEST = 0x280E, - SGP4X_TURN_HEATER_OFF = 0x3615, - SGP4X_GET_SERIAL_NUMBER = 0x3682, - SGP4X_SOFT_RESET = 0x0006 -} sgp4x_cmd_t; - -int8_t sgp4x_send_cmd(sgp4x_cmd_t cmd); - -int8_t sgp4x_measure_raw_signal (uint16_t * voc_ticks); -int8_t sgp4x_measure_raw_signal_compensated (uint16_t * voc_ticks, uint16_t relative_humidity, int16_t temperature); -int8_t sgp4x_execute_self_test ( uint8_t * test_result); -int8_t sgp4x_get_serial_number ( uint8_t serial[6]); -int8_t sgp4x_turn_heater_off ( void ); -int8_t sgp4x_soft_reset ( void ); - -#endif /* INC_SGP4X_H_ */ diff --git a/fw/Core/Src/main.c b/fw/Core/Src/main.c index 4631e38..bf1d424 100644 --- a/fw/Core/Src/main.c +++ b/fw/Core/Src/main.c @@ -48,7 +48,8 @@ */ const uint16_t tim21_prescaler = 60000-1; // 100Hz //const uint16_t tim21_period = 12000-1; // 60s -const uint16_t tim21_period = 1200-1; // 6s +//const uint16_t tim21_period = 1200-1; // 6s +const uint16_t tim21_period = MEASUREMENT_PERIOD_S * (SYSTEM_CLOCK_HZ / tim21_prescaler) - 1; //const uint16_t tim21_period = 200-1; // 1s /* Input register memory map @@ -87,7 +88,10 @@ enum REGISTER_NUM_PMC_NUMBER_2_5 = 30023, /* 1 / m^3 */ REGISTER_NUM_PMC_NUMBER_4_0 = 30024, /* 1 / m^3 */ REGISTER_NUM_PMC_NUMBER_10_0 = 30025, /* 1 / m^3 */ - REGISTER_NUM_TYPICAL_PARTICLE_SIZE = 30026 /* nm */ + REGISTER_NUM_TYPICAL_PARTICLE_SIZE = 30026, /* nm */ + REGISTER_NUM_VOC_RAW = 30027, /* raw VOC ticks */ + REGISTER_NUM_VOC_INDEX = 30028 /* VOC index as calculated by Sensirion library (1 to 500, average 100) */ + /* VOC Index has initial blackout beriod, when the data is not ready. VOC index is 0 during this period */ } data_registers_numbers; enum @@ -116,6 +120,10 @@ enum int16_t T_SCD4x, T_SHT4x; uint16_t CO2, RH_SCD4x, RH_SHT4x; sps30_data_t PM_SPS30; +/* VOC related varibles */ +uint16_t voc_ticks_compensated; +uint16_t voc_ticks; +uint16_t voc_index; /* Struct to store the sensor config */ config_t sensor_config; @@ -128,6 +136,9 @@ uint8_t co2_valid = 0; /* dynamic sensor configuration */ uint8_t scd4x_is_connected = 0; uint8_t sps30_is_connected = 0; +uint8_t sgp40_is_connected = 0; +/* Sensirion library for VOC */ +GasIndexAlgorithmParams voc_params; /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ @@ -271,6 +282,12 @@ int main(void) sps30_is_connected = 1; } + if (sgp40_measure_raw_signal(voc_ticks) == SGP40_OK) + { + sgp40_is_connected = 1; + GasIndexAlgorithm_init(&voc_params, GasIndexAlgorithm_ALGORITHM_TYPE_VOC); + } + /* Wait 1000ms for sensors initialization */ /* SHT4x Init Time: max 1 ms (datasheet pg. 8) */ /* SCD4x Init Time: max 1000 ms (datasheet pg. 6) */ @@ -357,6 +374,17 @@ int main(void) /* TODO: Process data and light a desired color of LED */ /* TODO: Add hystheresis */ + /* Read SGP40 (if connected) */ + if (sgp40_is_connected == 1) + { + //sgp40_measure_raw_signal(&voc_ticks); + sgp40_measure_raw_signal_compensated(&voc_ticks_compensated, RH_SHT4x, T_SHT4x); +// sgp40_measure_raw_signal(&voc_ticks); + /* Sensirion VOC library */ + GasIndexAlgorithm_process(&voc_params, voc_ticks_compensated, &voc_index); + + } + /* Reset the TIM21 Elapsed Period Flag */ tim21_elapsed_period = 0; } @@ -442,9 +470,8 @@ void SystemClock_Config(void) } - LL_Init1msTick(12000000); - - LL_SetSystemCoreClock(12000000); + LL_Init1msTick(SYSTEM_CLOCK_HZ); + LL_SetSystemCoreClock(SYSTEM_CLOCK_HZ); LL_RCC_SetUSARTClockSource(LL_RCC_USART2_CLKSOURCE_PCLK1); LL_RCC_SetI2CClockSource(LL_RCC_I2C1_CLKSOURCE_PCLK1); } @@ -902,6 +929,14 @@ int8_t modbus_slave_callback(modbus_transaction_t *transaction) MODBUS_ASSERT(sps30_is_connected); transaction->input_registers[i] = (uint16_t)PM_SPS30.typical_particle_size; break; + case REGISTER_NUM_VOC_RAW: + MODBUS_ASSERT(sgp40_is_connected); + transaction->input_registers[i] = (uint16_t)voc_ticks_compensated; + break; + case REGISTER_NUM_VOC_INDEX: + MODBUS_ASSERT(sgp40_is_connected); + transaction->input_registers[i] = (uint16_t)voc_index; + break; default: return MODBUS_ERROR_FUNCTION_NOT_IMPLEMENTED; } diff --git a/fw/Core/Src/sensirion_gas_index_algorithm.c b/fw/Core/Src/sensirion_gas_index_algorithm.c new file mode 100644 index 0000000..79ed421 --- /dev/null +++ b/fw/Core/Src/sensirion_gas_index_algorithm.c @@ -0,0 +1,855 @@ +/* + * 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_gas_index_algorithm.h" + +/*!< 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_t)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 GasIndexAlgorithm__init_instances(GasIndexAlgorithmParams* params); +static void GasIndexAlgorithm__mean_variance_estimator__set_parameters( + GasIndexAlgorithmParams* params); +static void GasIndexAlgorithm__mean_variance_estimator__set_states( + GasIndexAlgorithmParams* params, fix16_t mean, fix16_t std, + fix16_t uptime_gamma); +static fix16_t GasIndexAlgorithm__mean_variance_estimator__get_std( + const GasIndexAlgorithmParams* params); +static fix16_t GasIndexAlgorithm__mean_variance_estimator__get_mean( + const GasIndexAlgorithmParams* params); +static bool GasIndexAlgorithm__mean_variance_estimator__is_initialized( + GasIndexAlgorithmParams* params); +static void GasIndexAlgorithm__mean_variance_estimator___calculate_gamma( + GasIndexAlgorithmParams* params); +static void GasIndexAlgorithm__mean_variance_estimator__process( + GasIndexAlgorithmParams* params, fix16_t sraw); +static void +GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters( + GasIndexAlgorithmParams* params, fix16_t X0, fix16_t K); +static fix16_t GasIndexAlgorithm__mean_variance_estimator___sigmoid__process( + GasIndexAlgorithmParams* params, fix16_t sample); +static void GasIndexAlgorithm__mox_model__set_parameters( + GasIndexAlgorithmParams* params, fix16_t SRAW_STD, fix16_t SRAW_MEAN); +static fix16_t +GasIndexAlgorithm__mox_model__process(GasIndexAlgorithmParams* params, + fix16_t sraw); +static void GasIndexAlgorithm__sigmoid_scaled__set_parameters( + GasIndexAlgorithmParams* params, fix16_t X0, fix16_t K, + fix16_t offset_default); +static fix16_t +GasIndexAlgorithm__sigmoid_scaled__process(GasIndexAlgorithmParams* params, + fix16_t sample); +static void GasIndexAlgorithm__adaptive_lowpass__set_parameters( + GasIndexAlgorithmParams* params); +static fix16_t +GasIndexAlgorithm__adaptive_lowpass__process(GasIndexAlgorithmParams* params, + fix16_t sample); + +void GasIndexAlgorithm_init(GasIndexAlgorithmParams* params, + int32_t algorithm_type) { + + params->mAlgorithm_Type = algorithm_type; + if ((algorithm_type == GasIndexAlgorithm_ALGORITHM_TYPE_NOX)) { + params->mIndex_Offset = F16(GasIndexAlgorithm_NOX_INDEX_OFFSET_DEFAULT); + params->mSraw_Minimum = GasIndexAlgorithm_NOX_SRAW_MINIMUM; + params->mGating_Max_Duration_Minutes = + F16(GasIndexAlgorithm_GATING_NOX_MAX_DURATION_MINUTES); + params->mInit_Duration_Mean = + F16(GasIndexAlgorithm_INIT_DURATION_MEAN_NOX); + params->mInit_Duration_Variance = + F16(GasIndexAlgorithm_INIT_DURATION_VARIANCE_NOX); + params->mGating_Threshold = F16(GasIndexAlgorithm_GATING_THRESHOLD_NOX); + } else { + params->mIndex_Offset = F16(GasIndexAlgorithm_VOC_INDEX_OFFSET_DEFAULT); + params->mSraw_Minimum = GasIndexAlgorithm_VOC_SRAW_MINIMUM; + params->mGating_Max_Duration_Minutes = + F16(GasIndexAlgorithm_GATING_VOC_MAX_DURATION_MINUTES); + params->mInit_Duration_Mean = + F16(GasIndexAlgorithm_INIT_DURATION_MEAN_VOC); + params->mInit_Duration_Variance = + F16(GasIndexAlgorithm_INIT_DURATION_VARIANCE_VOC); + params->mGating_Threshold = F16(GasIndexAlgorithm_GATING_THRESHOLD_VOC); + } + params->mIndex_Gain = F16(GasIndexAlgorithm_INDEX_GAIN); + params->mTau_Mean_Hours = F16(GasIndexAlgorithm_TAU_MEAN_HOURS); + params->mTau_Variance_Hours = F16(GasIndexAlgorithm_TAU_VARIANCE_HOURS); + params->mSraw_Std_Initial = F16(GasIndexAlgorithm_SRAW_STD_INITIAL); + GasIndexAlgorithm_reset(params); +} + +void GasIndexAlgorithm_reset(GasIndexAlgorithmParams* params) { + params->mUptime = F16(0.); + params->mSraw = F16(0.); + params->mGas_Index = 0; + GasIndexAlgorithm__init_instances(params); +} + +static void GasIndexAlgorithm__init_instances(GasIndexAlgorithmParams* params) { + + GasIndexAlgorithm__mean_variance_estimator__set_parameters(params); + GasIndexAlgorithm__mox_model__set_parameters( + params, GasIndexAlgorithm__mean_variance_estimator__get_std(params), + GasIndexAlgorithm__mean_variance_estimator__get_mean(params)); + if ((params->mAlgorithm_Type == GasIndexAlgorithm_ALGORITHM_TYPE_NOX)) { + GasIndexAlgorithm__sigmoid_scaled__set_parameters( + params, F16(GasIndexAlgorithm_SIGMOID_X0_NOX), + F16(GasIndexAlgorithm_SIGMOID_K_NOX), + F16(GasIndexAlgorithm_NOX_INDEX_OFFSET_DEFAULT)); + } else { + GasIndexAlgorithm__sigmoid_scaled__set_parameters( + params, F16(GasIndexAlgorithm_SIGMOID_X0_VOC), + F16(GasIndexAlgorithm_SIGMOID_K_VOC), + F16(GasIndexAlgorithm_VOC_INDEX_OFFSET_DEFAULT)); + } + GasIndexAlgorithm__adaptive_lowpass__set_parameters(params); +} + +void GasIndexAlgorithm_get_states(const GasIndexAlgorithmParams* params, + int32_t* state0, int32_t* state1) { + + *state0 = GasIndexAlgorithm__mean_variance_estimator__get_mean(params); + *state1 = GasIndexAlgorithm__mean_variance_estimator__get_std(params); + return; +} + +void GasIndexAlgorithm_set_states(GasIndexAlgorithmParams* params, + int32_t state0, int32_t state1) { + + GasIndexAlgorithm__mean_variance_estimator__set_states( + params, state0, state1, + F16(GasIndexAlgorithm_PERSISTENCE_UPTIME_GAMMA)); + GasIndexAlgorithm__mox_model__set_parameters( + params, GasIndexAlgorithm__mean_variance_estimator__get_std(params), + GasIndexAlgorithm__mean_variance_estimator__get_mean(params)); + params->mSraw = state0; +} + +void GasIndexAlgorithm_set_tuning_parameters( + GasIndexAlgorithmParams* params, int32_t index_offset, + int32_t learning_time_offset_hours, int32_t learning_time_gain_hours, + int32_t gating_max_duration_minutes, int32_t std_initial, + int32_t gain_factor) { + + params->mIndex_Offset = (fix16_from_int(index_offset)); + params->mTau_Mean_Hours = (fix16_from_int(learning_time_offset_hours)); + params->mTau_Variance_Hours = (fix16_from_int(learning_time_gain_hours)); + params->mGating_Max_Duration_Minutes = + (fix16_from_int(gating_max_duration_minutes)); + params->mSraw_Std_Initial = (fix16_from_int(std_initial)); + params->mIndex_Gain = (fix16_from_int(gain_factor)); + GasIndexAlgorithm__init_instances(params); +} + +void GasIndexAlgorithm_get_tuning_parameters( + const GasIndexAlgorithmParams* params, int32_t* index_offset, + int32_t* learning_time_offset_hours, int32_t* learning_time_gain_hours, + int32_t* gating_max_duration_minutes, int32_t* std_initial, + int32_t* gain_factor) { + + *index_offset = (fix16_cast_to_int(params->mIndex_Offset)); + *learning_time_offset_hours = (fix16_cast_to_int(params->mTau_Mean_Hours)); + *learning_time_gain_hours = + (fix16_cast_to_int(params->mTau_Variance_Hours)); + *gating_max_duration_minutes = + (fix16_cast_to_int(params->mGating_Max_Duration_Minutes)); + *std_initial = (fix16_cast_to_int(params->mSraw_Std_Initial)); + *gain_factor = (fix16_cast_to_int(params->mIndex_Gain)); + return; +} + +void GasIndexAlgorithm_process(GasIndexAlgorithmParams* params, int32_t sraw, + int32_t* gas_index) { + + if ((params->mUptime <= F16(GasIndexAlgorithm_INITIAL_BLACKOUT))) { + params->mUptime = + (params->mUptime + F16(GasIndexAlgorithm_SAMPLING_INTERVAL)); + } else { + if (((sraw > 0) && (sraw < 65000))) { + if ((sraw < (params->mSraw_Minimum + 1))) { + sraw = (params->mSraw_Minimum + 1); + } else if ((sraw > (params->mSraw_Minimum + 32767))) { + sraw = (params->mSraw_Minimum + 32767); + } + params->mSraw = (fix16_from_int((sraw - params->mSraw_Minimum))); + } + if (((params->mAlgorithm_Type == + GasIndexAlgorithm_ALGORITHM_TYPE_VOC) || + GasIndexAlgorithm__mean_variance_estimator__is_initialized( + params))) { + params->mGas_Index = + GasIndexAlgorithm__mox_model__process(params, params->mSraw); + params->mGas_Index = GasIndexAlgorithm__sigmoid_scaled__process( + params, params->mGas_Index); + } else { + params->mGas_Index = params->mIndex_Offset; + } + params->mGas_Index = GasIndexAlgorithm__adaptive_lowpass__process( + params, params->mGas_Index); + if ((params->mGas_Index < F16(0.5))) { + params->mGas_Index = F16(0.5); + } + if ((params->mSraw > F16(0.))) { + GasIndexAlgorithm__mean_variance_estimator__process(params, + params->mSraw); + GasIndexAlgorithm__mox_model__set_parameters( + params, + GasIndexAlgorithm__mean_variance_estimator__get_std(params), + GasIndexAlgorithm__mean_variance_estimator__get_mean(params)); + } + } + *gas_index = (fix16_cast_to_int((params->mGas_Index + F16(0.5)))); + return; +} + +static void GasIndexAlgorithm__mean_variance_estimator__set_parameters( + GasIndexAlgorithmParams* params) { + + 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 = params->mSraw_Std_Initial; + params->m_Mean_Variance_Estimator___Gamma_Mean = (fix16_div( + F16(( + (GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__ADDITIONAL_GAMMA_MEAN_SCALING * + GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING) * + (GasIndexAlgorithm_SAMPLING_INTERVAL / 3600.))), + (params->mTau_Mean_Hours + + F16((GasIndexAlgorithm_SAMPLING_INTERVAL / 3600.))))); + params->m_Mean_Variance_Estimator___Gamma_Variance = (fix16_div( + F16((GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING * + (GasIndexAlgorithm_SAMPLING_INTERVAL / 3600.))), + (params->mTau_Variance_Hours + + F16((GasIndexAlgorithm_SAMPLING_INTERVAL / 3600.))))); + if ((params->mAlgorithm_Type == GasIndexAlgorithm_ALGORITHM_TYPE_NOX)) { + params->m_Mean_Variance_Estimator___Gamma_Initial_Mean = F16(( + ((GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__ADDITIONAL_GAMMA_MEAN_SCALING * + GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING) * + GasIndexAlgorithm_SAMPLING_INTERVAL) / + (GasIndexAlgorithm_TAU_INITIAL_MEAN_NOX + + GasIndexAlgorithm_SAMPLING_INTERVAL))); + } else { + params->m_Mean_Variance_Estimator___Gamma_Initial_Mean = F16(( + ((GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__ADDITIONAL_GAMMA_MEAN_SCALING * + GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING) * + GasIndexAlgorithm_SAMPLING_INTERVAL) / + (GasIndexAlgorithm_TAU_INITIAL_MEAN_VOC + + GasIndexAlgorithm_SAMPLING_INTERVAL))); + } + params->m_Mean_Variance_Estimator___Gamma_Initial_Variance = + F16(((GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING * + GasIndexAlgorithm_SAMPLING_INTERVAL) / + (GasIndexAlgorithm_TAU_INITIAL_VARIANCE + + GasIndexAlgorithm_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 GasIndexAlgorithm__mean_variance_estimator__set_states( + GasIndexAlgorithmParams* 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 GasIndexAlgorithm__mean_variance_estimator__get_std( + const GasIndexAlgorithmParams* params) { + + return params->m_Mean_Variance_Estimator___Std; +} + +static fix16_t GasIndexAlgorithm__mean_variance_estimator__get_mean( + const GasIndexAlgorithmParams* params) { + + return (params->m_Mean_Variance_Estimator___Mean + + params->m_Mean_Variance_Estimator___Sraw_Offset); +} + +static bool GasIndexAlgorithm__mean_variance_estimator__is_initialized( + GasIndexAlgorithmParams* params) { + + return params->m_Mean_Variance_Estimator___Initialized; +} + +static void GasIndexAlgorithm__mean_variance_estimator___calculate_gamma( + GasIndexAlgorithmParams* params) { + + 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((GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX - + GasIndexAlgorithm_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(GasIndexAlgorithm_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(GasIndexAlgorithm_SAMPLING_INTERVAL)); + } + GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters( + params, params->mInit_Duration_Mean, + F16(GasIndexAlgorithm_INIT_TRANSITION_MEAN)); + sigmoid_gamma_mean = + GasIndexAlgorithm__mean_variance_estimator___sigmoid__process( + params, params->m_Mean_Variance_Estimator___Uptime_Gamma); + gamma_mean = + (params->m_Mean_Variance_Estimator___Gamma_Mean + + (fix16_mul((params->m_Mean_Variance_Estimator___Gamma_Initial_Mean - + params->m_Mean_Variance_Estimator___Gamma_Mean), + sigmoid_gamma_mean))); + gating_threshold_mean = + (params->mGating_Threshold + + (fix16_mul( + (F16(GasIndexAlgorithm_GATING_THRESHOLD_INITIAL) - + params->mGating_Threshold), + GasIndexAlgorithm__mean_variance_estimator___sigmoid__process( + params, params->m_Mean_Variance_Estimator___Uptime_Gating)))); + GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters( + params, gating_threshold_mean, + F16(GasIndexAlgorithm_GATING_THRESHOLD_TRANSITION)); + sigmoid_gating_mean = + GasIndexAlgorithm__mean_variance_estimator___sigmoid__process( + params, params->mGas_Index); + params->m_Mean_Variance_Estimator__Gamma_Mean = + (fix16_mul(sigmoid_gating_mean, gamma_mean)); + GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters( + params, params->mInit_Duration_Variance, + F16(GasIndexAlgorithm_INIT_TRANSITION_VARIANCE)); + sigmoid_gamma_variance = + GasIndexAlgorithm__mean_variance_estimator___sigmoid__process( + params, params->m_Mean_Variance_Estimator___Uptime_Gamma); + gamma_variance = + (params->m_Mean_Variance_Estimator___Gamma_Variance + + (fix16_mul( + (params->m_Mean_Variance_Estimator___Gamma_Initial_Variance - + params->m_Mean_Variance_Estimator___Gamma_Variance), + (sigmoid_gamma_variance - sigmoid_gamma_mean)))); + gating_threshold_variance = + (params->mGating_Threshold + + (fix16_mul( + (F16(GasIndexAlgorithm_GATING_THRESHOLD_INITIAL) - + params->mGating_Threshold), + GasIndexAlgorithm__mean_variance_estimator___sigmoid__process( + params, params->m_Mean_Variance_Estimator___Uptime_Gating)))); + GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters( + params, gating_threshold_variance, + F16(GasIndexAlgorithm_GATING_THRESHOLD_TRANSITION)); + sigmoid_gating_variance = + GasIndexAlgorithm__mean_variance_estimator___sigmoid__process( + params, params->mGas_Index); + 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((GasIndexAlgorithm_SAMPLING_INTERVAL / 60.)), + ((fix16_mul((F16(1.) - sigmoid_gating_mean), + F16((1. + GasIndexAlgorithm_GATING_MAX_RATIO)))) - + F16(GasIndexAlgorithm_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->mGating_Max_Duration_Minutes)) { + params->m_Mean_Variance_Estimator___Uptime_Gating = F16(0.); + } +} + +static void GasIndexAlgorithm__mean_variance_estimator__process( + GasIndexAlgorithmParams* params, fix16_t sraw) { + + 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); + GasIndexAlgorithm__mean_variance_estimator___calculate_gamma(params); + delta_sgp = (fix16_div( + (sraw - params->m_Mean_Variance_Estimator___Mean), + F16(GasIndexAlgorithm_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 = (fix16_mul((fix16_div(c, F16(1440.))), + (fix16_div(c, F16(1440.))))); + } + params->m_Mean_Variance_Estimator___Std = (fix16_mul( + fix16_sqrt((fix16_mul( + additional_scaling, + (F16(GasIndexAlgorithm_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(GasIndexAlgorithm_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_div( + (fix16_mul(params->m_Mean_Variance_Estimator__Gamma_Mean, + delta_sgp)), + F16(GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__ADDITIONAL_GAMMA_MEAN_SCALING)))); + } +} + +static void +GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters( + GasIndexAlgorithmParams* params, fix16_t X0, fix16_t K) { + + params->m_Mean_Variance_Estimator___Sigmoid__K = K; + params->m_Mean_Variance_Estimator___Sigmoid__X0 = X0; +} + +static fix16_t GasIndexAlgorithm__mean_variance_estimator___sigmoid__process( + GasIndexAlgorithmParams* 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 F16(1.); + } else if ((x > F16(50.))) { + return F16(0.); + } else { + return (fix16_div(F16(1.), (F16(1.) + fix16_exp(x)))); + } +} + +static void GasIndexAlgorithm__mox_model__set_parameters( + GasIndexAlgorithmParams* 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 +GasIndexAlgorithm__mox_model__process(GasIndexAlgorithmParams* params, + fix16_t sraw) { + + if ((params->mAlgorithm_Type == GasIndexAlgorithm_ALGORITHM_TYPE_NOX)) { + return (fix16_mul((fix16_div((sraw - params->m_Mox_Model__Sraw_Mean), + F16(GasIndexAlgorithm_SRAW_STD_NOX))), + params->mIndex_Gain)); + } else { + return (fix16_mul( + (fix16_div((sraw - params->m_Mox_Model__Sraw_Mean), + (-(params->m_Mox_Model__Sraw_Std + + F16(GasIndexAlgorithm_SRAW_STD_BONUS_VOC))))), + params->mIndex_Gain)); + } +} + +static void GasIndexAlgorithm__sigmoid_scaled__set_parameters( + GasIndexAlgorithmParams* params, fix16_t X0, fix16_t K, + fix16_t offset_default) { + + params->m_Sigmoid_Scaled__K = K; + params->m_Sigmoid_Scaled__X0 = X0; + params->m_Sigmoid_Scaled__Offset_Default = offset_default; +} + +static fix16_t +GasIndexAlgorithm__sigmoid_scaled__process(GasIndexAlgorithmParams* params, + fix16_t sample) { + + fix16_t x; + fix16_t shift; + + x = (fix16_mul(params->m_Sigmoid_Scaled__K, + (sample - params->m_Sigmoid_Scaled__X0))); + if ((x < F16(-50.))) { + return F16(GasIndexAlgorithm_SIGMOID_L); + } else if ((x > F16(50.))) { + return F16(0.); + } else { + if ((sample >= F16(0.))) { + if ((params->m_Sigmoid_Scaled__Offset_Default == F16(1.))) { + shift = (fix16_mul(F16((500. / 499.)), + (F16(1.) - params->mIndex_Offset))); + } else { + shift = + (fix16_div((F16(GasIndexAlgorithm_SIGMOID_L) - + (fix16_mul(F16(5.), params->mIndex_Offset))), + F16(4.))); + } + return ((fix16_div((F16(GasIndexAlgorithm_SIGMOID_L) + shift), + (F16(1.) + fix16_exp(x)))) - + shift); + } else { + return ( + fix16_mul((fix16_div(params->mIndex_Offset, + params->m_Sigmoid_Scaled__Offset_Default)), + (fix16_div(F16(GasIndexAlgorithm_SIGMOID_L), + (F16(1.) + fix16_exp(x)))))); + } + } +} + +static void GasIndexAlgorithm__adaptive_lowpass__set_parameters( + GasIndexAlgorithmParams* params) { + + params->m_Adaptive_Lowpass__A1 = F16(( + GasIndexAlgorithm_SAMPLING_INTERVAL / + (GasIndexAlgorithm_LP_TAU_FAST + GasIndexAlgorithm_SAMPLING_INTERVAL))); + params->m_Adaptive_Lowpass__A2 = F16(( + GasIndexAlgorithm_SAMPLING_INTERVAL / + (GasIndexAlgorithm_LP_TAU_SLOW + GasIndexAlgorithm_SAMPLING_INTERVAL))); + params->m_Adaptive_Lowpass___Initialized = false; +} + +static fix16_t +GasIndexAlgorithm__adaptive_lowpass__process(GasIndexAlgorithmParams* 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(GasIndexAlgorithm_LP_ALPHA), abs_delta))); + tau_a = ((fix16_mul(F16((GasIndexAlgorithm_LP_TAU_SLOW - + GasIndexAlgorithm_LP_TAU_FAST)), + F1)) + + F16(GasIndexAlgorithm_LP_TAU_FAST)); + a3 = (fix16_div(F16(GasIndexAlgorithm_SAMPLING_INTERVAL), + (F16(GasIndexAlgorithm_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; +} diff --git a/fw/Core/Src/sgp40.c b/fw/Core/Src/sgp40.c new file mode 100644 index 0000000..0edee8b --- /dev/null +++ b/fw/Core/Src/sgp40.c @@ -0,0 +1,169 @@ +/* + * SGP40.c + * + * Created on: Jan 9, 2022 + * Author: david + */ + +#include + +int8_t sgp40_send_cmd(sgp40_cmd_t cmd) +{ + uint8_t buffer[32]; + int result; + + // start measurement + buffer[0] = cmd >> 8; + buffer[1] = cmd & 0x00ff; + result = i2c_transmit(SGP40_I2C_ADDRESS<<1, buffer, 2); + if (result == I2C_ERROR_TX_INCOMPLETE) { + return SGP40_ERROR; + } + + /* Sensirion sensors return NACK after last byte (so NACK at the end is ok) */ + return SGP40_OK; +} + +int8_t sgp40_measure_raw_signal(uint16_t * voc_ticks) +{ + uint8_t buffer[32]; + int result; + + /* Start measurement */ + buffer[0] = 0x26; + buffer[1] = 0x0F; + buffer[2] = 0x80; + buffer[3] = 0x00; + buffer[4] = 0xA2; + buffer[5] = 0x66; + buffer[6] = 0x66; + buffer[7] = 0x93; + + /* Returns NACK if CRC is wrong */ + result = i2c_transmit(SGP40_I2C_ADDRESS<<1, buffer, 8); + if (result != I2C_OK) { + return SGP40_ERROR; + } + + LL_mDelay(SGP40_MAX_MEAS_DURATION_MS); // 30ms + + result = i2c_receive(SGP40_I2C_ADDRESS<<1, buffer, 3); + if (result != I2C_OK) + { + return SGP40_ERROR; + } + + *voc_ticks = (buffer[0] << 8) + buffer[1]; + uint8_t voc_ticks_crc = buffer[2]; + uint8_t crc_correct = crc8_calculate(buffer, 2) == voc_ticks_crc; + if (!crc_correct) { + return SGP40_CRC8_ERROR; + } + return SGP40_OK; + +} + +int8_t sgp40_measure_raw_signal_compensated(uint16_t * voc_ticks, uint16_t relative_humidity, int16_t temperature) +{ + uint8_t buffer[32]; + int result; + + uint16_t rh_ticks = (uint16_t)((uint32_t)relative_humidity * 65535 / 100); + uint16_t t_ticks = (uint16_t)(((uint32_t)temperature/10 + 45) * 65535 / 175); + + buffer[0] = SGP40_MEASURE_RAW_SIGNAL >> 8; + buffer[1] = SGP40_MEASURE_RAW_SIGNAL & 0x00ff; + buffer[2] = (uint8_t)(rh_ticks >> 8); + buffer[3] = (uint8_t)rh_ticks; + buffer[4] = crc8_calculate(buffer+2, 2); + buffer[5] = (uint8_t)(t_ticks >> 8); + buffer[6] = (uint8_t)t_ticks; + buffer[7] = crc8_calculate(buffer+5, 2); + + /* Returns NACK if CRC is wrong */ + result = i2c_transmit(SGP40_I2C_ADDRESS<<1, buffer, 8); + if (result != I2C_OK) { + return SGP40_ERROR; + } + + LL_mDelay(SGP40_MAX_MEAS_DURATION_MS); // 30ms + + result = i2c_receive(SGP40_I2C_ADDRESS<<1, buffer, 3); + if (result != I2C_OK) + { + return SGP40_ERROR; + } + + *voc_ticks = (buffer[0] << 8) + buffer[1]; + uint8_t voc_ticks_crc = buffer[2]; + uint8_t crc_correct = crc8_calculate(buffer, 2) == voc_ticks_crc; + if (!crc_correct) { + return SGP40_CRC8_ERROR; + } + return SGP40_OK; +} + +int8_t SGP40_execute_self_test ( uint8_t * test_result) +{ + uint8_t buffer[16]; + int8_t result; + + result = sgp40_send_cmd(SGP40_EXECUTE_SELF_TEST); + if (result != I2C_OK) { + return SGP40_ERROR; + } + + LL_mDelay(350); + + result = i2c_receive(SGP40_I2C_ADDRESS << 1, buffer, 3); + if (result != I2C_OK) { + return SGP40_ERROR; + } + + test_result = buffer[0]; + uint8_t test_result_crc = buffer[2]; + + uint8_t crc_correct = crc8_calculate(buffer, 2) == test_result_crc; + if (!crc_correct) { + return SGP40_CRC8_ERROR; + } + return SGP40_OK; +} + +int8_t SGP40_get_serial_number(uint8_t serial[6]) +{ + uint8_t buffer[16]; + + sgp40_send_cmd(SGP40_GET_SERIAL_NUMBER); + + LL_mDelay(5); + + i2c_receive(SGP40_I2C_ADDRESS << 1, buffer, 9); + + serial[0] = buffer[0]; + serial[1] = buffer[1]; + uint8_t crc_ser01 = buffer[3]; + serial[2] = buffer[4]; + serial[3] = buffer[5]; + uint8_t crc_ser23 = buffer[6]; + serial[4] = buffer[7]; + serial[5] = buffer[8]; + uint8_t crc_ser45 = buffer[9]; + + uint8_t crc_correct = crc8_calculate(buffer, 2) == crc_ser01; + crc_correct &= crc8_calculate(buffer + 3, 2) == crc_ser23; + crc_correct &= crc8_calculate(buffer + 6, 2) == crc_ser45; + if (!crc_correct) { + return SGP40_CRC8_ERROR; + } + return SGP40_OK; +} + +int8_t SGP40_turn_heater_off(void) +{ + return sgp40_send_cmd(SGP40_TURN_HEATER_OFF); +} +int8_t SGP40_soft_reset(void) +{ + return sgp40_send_cmd(SGP40_SOFT_RESET); +} diff --git a/fw/Core/Src/sgp4x.c b/fw/Core/Src/sgp4x.c deleted file mode 100644 index 3b9c588..0000000 --- a/fw/Core/Src/sgp4x.c +++ /dev/null @@ -1,170 +0,0 @@ -/* - * sgp4x.c - * - * Created on: Jan 9, 2022 - * Author: david - */ - -#include "sgp4x.h" - -int8_t sgp4x_send_cmd(sgp4x_cmd_t cmd) -{ - uint8_t buffer[32]; - int result; - - // start measurement - buffer[0] = cmd >> 8; - buffer[1] = cmd & 0x00ff; - result = i2c_transmit(SGP4X_I2C_ADDRESS<<1, buffer, 2); - if (result == I2C_ERROR_TX_INCOMPLETE) { - return SGP4X_ERROR; - } - - /* Sensirion sensors return NACK after last byte (so NACK at the end is ok) */ - return SGP4X_OK; -} - -int8_t sgp4x_measure_raw_signal (uint16_t * voc_ticks) -{ - uint8_t buffer[32]; - int result; - - // start measurement - //buffer[0] = SGP4X_MEASURE_RAW_SIGNAL >> 8; - //buffer[1] = SGP4X_MEASURE_RAW_SIGNAL & 0x00ff; - buffer[0] = 0x26; - buffer[1] = 0x0F; - buffer[2] = 0x80; - buffer[3] = 0x00; - buffer[4] = 0xA2; - buffer[5] = 0x66; - buffer[6] = 0x66; - buffer[7] = 0x93; - - result = i2c_transmit(SGP4X_I2C_ADDRESS<<1, buffer, 8); - if (result != I2C_OK) { - return SGP4X_ERROR; - } - - LL_mDelay(SGP4X_MAX_MEAS_DURATION_MS); // 30ms - - result = i2c_receive(SGP4X_I2C_ADDRESS<<1, buffer, 3); - if (result != I2C_OK) - { - return SGP4X_ERROR; - } - - *voc_ticks = (buffer[0] << 8) + buffer[1]; - uint8_t voc_ticks_crc = buffer[2]; - uint8_t crc_correct = crc8_calculate(buffer, 2) == voc_ticks_crc; - if (!crc_correct) { - return SGP4X_CRC8_ERROR; - } - return SGP4X_OK; - -} - -int8_t sgp4x_measure_raw_signal_compensated (uint16_t * voc_ticks, uint16_t relative_humidity, int16_t temperature) -{ - uint8_t buffer[32]; - int result; - - uint16_t rh_ticks = (uint16_t) ((uint32_t)relative_humidity * (uint32_t)(65535/100)); - uint16_t t_ticks = (uint16_t) ((uint32_t)(temperature + 45) * (uint32_t)(65535/175)); - - buffer[0] = SGP4X_MEASURE_RAW_SIGNAL >> 8; - buffer[1] = SGP4X_MEASURE_RAW_SIGNAL & 0x00ff; - buffer[2] = 0x80; - buffer[3] = 0x00; - buffer[4] = crc8_calculate(buffer+2, 2); - buffer[5] = 0x66; - buffer[6] = 0x66; - buffer[7] = crc8_calculate(buffer+5, 2); - - - result = i2c_transmit(SGP4X_I2C_ADDRESS<<1, buffer, 8); - if (result != I2C_OK) { - return SGP4X_ERROR; - } - - LL_mDelay(SGP4X_MAX_MEAS_DURATION_MS); // 30ms - - result = i2c_receive(SGP4X_I2C_ADDRESS<<1, buffer, 3); - if (result != I2C_OK) - { - return SGP4X_ERROR; - } - - *voc_ticks = (buffer[0] << 8) + buffer[1]; - uint8_t voc_ticks_crc = buffer[2]; - uint8_t crc_correct = crc8_calculate(buffer, 2) == voc_ticks_crc; - if (!crc_correct) { - return SGP4X_CRC8_ERROR; - } - return SGP4X_OK; -} - -int8_t sgp4x_execute_self_test ( uint8_t * test_result) -{ - uint8_t buffer[16]; - int8_t result; - - result = sgp4x_send_cmd(SGP4X_EXECUTE_SELF_TEST); - if (result != I2C_OK) { - return SGP4X_ERROR; - } - - LL_mDelay(350); - - result = i2c_receive(SGP4X_I2C_ADDRESS << 1, buffer, 3); - if (result != I2C_OK) { - return SGP4X_ERROR; - } - - test_result = buffer[0]; - uint8_t test_result_crc = buffer[2]; - - uint8_t crc_correct = crc8_calculate(buffer, 2) == test_result_crc; - if (!crc_correct) { - return SGP4X_CRC8_ERROR; - } - return SGP4X_OK; -} - -int8_t sgp4x_get_serial_number ( uint8_t serial[6]) -{ - uint8_t buffer[16]; - - sgp4x_send_cmd(SGP4X_GET_SERIAL_NUMBER); - - LL_mDelay(5); - - i2c_receive(SGP4X_I2C_ADDRESS << 1, buffer, 9); - - serial[0] = buffer[0]; - serial[1] = buffer[1]; - uint8_t crc_ser01 = buffer[3]; - serial[2] = buffer[4]; - serial[3] = buffer[5]; - uint8_t crc_ser23 = buffer[6]; - serial[4] = buffer[7]; - serial[5] = buffer[8]; - uint8_t crc_ser45 = buffer[9]; - - uint8_t crc_correct = crc8_calculate(buffer, 2) == crc_ser01; - crc_correct &= crc8_calculate(buffer + 3, 2) == crc_ser23; - crc_correct &= crc8_calculate(buffer + 6, 2) == crc_ser45; - if (!crc_correct) { - return SGP4X_CRC8_ERROR; - } - return SGP4X_OK; -} - -int8_t sgp4x_turn_heater_off ( void ) -{ - return sgp4x_send_cmd(SGP4X_TURN_HEATER_OFF); -} -int8_t sgp4x_soft_reset ( void ) -{ - return sgp4x_send_cmd(SGP4X_SOFT_RESET); -} diff --git a/tests/sensor.py b/tests/sensor.py index c001137..6d0206e 100644 --- a/tests/sensor.py +++ b/tests/sensor.py @@ -27,7 +27,9 @@ class Sensor(): 'PM_number_concentration_2.5': 30023, \ 'PM_number_concentration_4.0': 30024, \ 'PM_number_concentration_10.0': 30025, \ - 'PM_typical_particle_size': 30026 } + 'PM_typical_particle_size': 30026, \ + 'VOC_ticks': 30027, \ + 'VOC_index': 30028} holding_registers = { \ 'LED_on': 40001, \ 'LED_brightness': 40002, \