Added SGP40 readout.
This commit is contained in:
parent
d86713277f
commit
2a491090e6
2
.gitignore
vendored
2
.gitignore
vendored
@ -39,3 +39,5 @@ fw/Debug
|
||||
fw/.settings
|
||||
|
||||
*.zip
|
||||
|
||||
tests/__pycache__/
|
||||
|
11
.project
Normal file
11
.project
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>IAQ_Wired_Sensor</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -53,10 +53,11 @@ extern "C" {
|
||||
#include "scd4x.h"
|
||||
#include "sht4x.h"
|
||||
#include "sps30.h"
|
||||
#include "sgp4x.h"
|
||||
#include <sgp40.h>
|
||||
#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
|
||||
|
||||
|
280
fw/Core/Inc/sensirion_gas_index_algorithm.h
Normal file
280
fw/Core/Inc/sensirion_gas_index_algorithm.h
Normal file
@ -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 <stdint.h>
|
||||
#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 <stdbool.h>
|
||||
#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_ */
|
50
fw/Core/Inc/sgp40.h
Normal file
50
fw/Core/Inc/sgp40.h
Normal file
@ -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_ */
|
@ -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_ */
|
@ -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;
|
||||
}
|
||||
|
855
fw/Core/Src/sensirion_gas_index_algorithm.c
Normal file
855
fw/Core/Src/sensirion_gas_index_algorithm.c
Normal file
@ -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;
|
||||
}
|
169
fw/Core/Src/sgp40.c
Normal file
169
fw/Core/Src/sgp40.c
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* SGP40.c
|
||||
*
|
||||
* Created on: Jan 9, 2022
|
||||
* Author: david
|
||||
*/
|
||||
|
||||
#include <sgp40.h>
|
||||
|
||||
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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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, \
|
||||
|
Loading…
Reference in New Issue
Block a user