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, \