diff --git a/drivers/Kconfig b/drivers/Kconfig index a2f3ee7867..a37c455784 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -46,6 +46,7 @@ rsource "matrix_keypad/Kconfig" rsource "mma8x5x/Kconfig" rsource "opt3001/Kconfig" rsource "seesaw_soil/Kconfig" +rsource "sen5x/Kconfig" rsource "sht2x/Kconfig" rsource "sm_pwm_01c/Kconfig" rsource "sps30/Kconfig" diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index 97362cb6fe..5daf9e5859 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -188,6 +188,10 @@ ifneq (,$(filter sdp3x_%,$(USEMODULE))) USEMODULE += sdp3x endif +ifneq (,$(filter sen5%,$(USEMODULE))) + USEMODULE += sen5x +endif + ifneq (,$(filter servo_%,$(USEMODULE))) USEMODULE += servo endif diff --git a/drivers/include/sen5x.h b/drivers/include/sen5x.h new file mode 100644 index 0000000000..0675e9d594 --- /dev/null +++ b/drivers/include/sen5x.h @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2023 TUÚ Braunschweig Institut für Betriebssysteme und Rechnerverbund + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_sen5x Sensirion Embedded I2C SEN5x Driver + * @ingroup drivers_sensors + * @ingroup drivers_saul + * @brief Driver for I2C communication to SEN5x devices. + * + * @{ + * + * @file + * + * @author Daniel Prigoshij + */ + +#ifndef SEN5X_H +#define SEN5X_H + +/* Add header includes here */ + +#include "periph/i2c.h" +#include +#include "saul.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Declare the API of the driver */ + +/** + * @brief Wrapper for measured values + */ +typedef struct { + uint16_t mass_concentration_pm1p0; /**< raw value is scaled with factor 10: PM1.0 [µg/m³] = value / 10 */ + uint16_t mass_concentration_pm2p5; /**< raw value is scaled with factor 10: PM2.5 [µg/m³] = value / 10 */ + uint16_t mass_concentration_pm4p0; /**< raw value is scaled with factor 10: PM4.0 [µg/m³] = value / 10 */ + uint16_t mass_concentration_pm10p0; /**< raw value is scaled with factor 10: PM10.0 [µg/m³] = value / 10 */ + uint16_t number_concentration_pm0p5; /**< raw value is scaled with factor 10: PM0.5 [#/cm³] = value / 10 */ + uint16_t number_concentration_pm1p0; /**< raw value is scaled with factor 10: PM1.0 [#/cm³] = value / 10 */ + uint16_t number_concentration_pm2p5; /**< raw value is scaled with factor 10: PM2.5 [#/cm³] = value / 10 */ + uint16_t number_concentration_pm4p0; /**< raw value is scaled with factor 10: PM4.0 [#/cm³] = value / 10 */ + uint16_t number_concentration_pm10p0; /**< raw value is scaled with factor 10: PM10.0 [#/cm³] = value / 10 */ + uint16_t typical_particle_size; /**< raw value is scaled with factor 1000: Size [µm] = value / 1000*/ + int16_t ambient_humidity; /**< raw value is scaled with factor 100: RH [%] = value / 100 */ + int16_t ambient_temperature; /**< raw value is scaled with factor 200: T [°C] = value / 200 */ + int16_t voc_index; /**< raw value is scaled with factor 10: VOC Index = value / 10 */ + int16_t nox_index; /**< raw value is scaled with factor 10: NOx Index = value / 10 */ +} sen5x_measurement_t; + +/** + * @brief Device initialization parameters + */ +typedef struct { + i2c_t i2c_dev; /**< I2C device which is used */ + uint8_t i2c_addr; /**< I2C address */ +} sen5x_params_t; + +/** + * @brief Device descriptor for the driver + */ +typedef struct { + sen5x_params_t params; /**< Device initialization parameters */ +} sen5x_t; + +/** + * @brief Initialize the given device + * + * @param[inout] dev Device descriptor of the driver + * @param[in] params Initialization parameters + */ +void sen5x_init(sen5x_t *dev, const sen5x_params_t *params); + +/** + * @brief Execute a reset on the given device + * + * @param[inout] dev Device descriptor of the driver + */ +void sen5x_reset(const sen5x_t *dev); + +/** + * @brief Starts a continuous measurement + * + * @param[inout] dev Device descriptor of the driver + */ +void sen5x_wake(const sen5x_t *dev); + +/** + * @brief Starts a continuous measurement without PM. Only humidity, temperature, VOC and NOx are measured. + * + * @param[inout] dev Device descriptor of the driver + */ +void sen5x_wake_no_pm(const sen5x_t *dev); + +/** + * @brief Stops the measurement and returns to idle mode + * + * @param[inout] dev Device descriptor of the driver + */ +void sen5x_sleep(const sen5x_t *dev); + +/** + * @brief Sets the fan to maximum speed, to clean it within 10 seconds + * + * @param[inout] dev Device descriptor of the driver + */ +void sen5x_clean_fan(const sen5x_t *dev); + +/** + * @brief Sets the fan to maximum speed, to clean it within 10 seconds + * + * @param[inout] dev Device descriptor of the driver + * + * @return 0 if no new measurements are available + * @return 1 if new measuremtns are ready to be read + */ +bool sen5x_data_ready_flag(const sen5x_t *dev); + +/** + * @brief Read measured mass concentration, humidity and temperature values + * + * @param[inout] dev Device descriptor of the driver + * @param[out] values Pointer to wrapper containing all measured values + */ +void sen5x_read_values(const sen5x_t *dev, sen5x_measurement_t *values); + +/** + * @brief Read measured particle matter values + * + * @param[inout] dev Device descriptor of the driver + * @param[out] values Pointer to wrapper containing all measured values + */ +void sen5x_read_pm_values(const sen5x_t *dev, sen5x_measurement_t *values); + +/** + * @brief Set a custom temperature offset to the ambient temperature + * + * @param[inout] dev Device descriptor of the driver + * @param[in] temp_offset Temperature offset in °C + * @param[in] slope Normalized temperature offset slope + * @param[in] time_constant Time constant in seconds + */ +void sen5x_set_temperature_offset(const sen5x_t *dev, int16_t temp_offset, int16_t slope, uint16_t time_constant); + +/** + * @brief Set a custom temperature offset to the ambient temperature + * + * @param[inout] dev Device descriptor of the driver + * @param[out] temp_offset Temperature offset in °C + * @param[out] slope Normalized temperature offset slope + * @param[out] time_constant Time constant in seconds + */ +void sen5x_get_temperature_offset(const sen5x_t *dev, int16_t *temp_offset, int16_t *slope, uint16_t *time_constant); + +/** + * @brief Set the parameter for a warm start on the device, to improve initial accuracy of the ambient temperature output + * + * @param[inout] dev Device descriptor of the driver + * @param[in] warm_start Warm start behavior as a value in the range from + * 0 (cold start, default) to 65535 (warm start). + */ +void sen5x_set_warm_start(const sen5x_t *dev, uint16_t warm_start); + +/** + * @brief Get the warm start paramater + * + * @param[inout] dev Device descriptor of the driver + * @param[out] warm_start Warm start behavior as a value in the range from + * 0 (cold start, default) to 65535 (warm start). + */ +void sen5x_get_warm_start(const sen5x_t *dev, uint16_t *warm_start); + +/** + * @brief Set the parameters for the VOC Algorithm tuning + * + * @param[inout] dev Device descriptor of the driver + * @param[in] index_offset VOC index representing typical (average) conditions + * @param[in] learning_time_offset_hours Time constant to estimate the VOC algorithm offset from the + * history in hours + * @param[in] learning_time_gain_hours Time constant to estimate the VOC algorithm gain from the history + * in hours + * @param[in] gating_max_duration_minutes Maximum duration of gating in minutes + * @param[in] std_initial Initial estimate for standard deviation + * @param[in] gain_factor Gain factor to amplify or to attenuate the VOC index output + */ +void sen5x_set_voc_algorithm_tuning( + const sen5x_t *dev, int16_t index_offset, int16_t learning_time_offset_hours, + int16_t learning_time_gain_hours, int16_t gating_max_duration_minutes, + int16_t std_initial, int16_t gain_factor); + +/** + * @brief Get the VOC Algortihm tuning parameters + * + * @param[inout] dev Device descriptor of the driver + * @param[out] index_offset VOC index representing typical (average) conditions + * @param[out] learning_time_offset_hours Time constant to estimate the VOC algorithm offset from the + * history in hours + * @param[out] learning_time_gain_hours Time constant to estimate the VOC algorithm gain from the history + * in hours + * @param[out] gating_max_duration_minutes Maximum duration of gating in minutes + * @param[out] std_initial Initial estimate for standard deviation + * @param[out] gain_factor Gain factor to amplify or to attenuate the VOC index output + */ +void sen5x_get_voc_algorithm_tuning( + const sen5x_t *dev, int16_t *index_offset, int16_t *learning_time_offset_hours, + int16_t *learning_time_gain_hours, int16_t *gating_max_duration_minutes, + int16_t *std_initial, int16_t *gain_factor); + +/** + * @brief Set the parameters for the NOx Algorithm tuning + * + * @param[inout] dev Device descriptor of the driver + * @param[in] index_offset NOx index representing typical (average) conditions + * @param[in] learning_time_offset_hours Time constant to estimate the NOx algorithm offset from the + * history in hours + * @param[in] learning_time_gain_hours The time constant to estimate the NOx algorithm gain from the + * history has no impact for NOx. This parameter is still in place for + * consistency reasons with the VOC tuning parameters command. + * This parameter must always be set to 12 hours + * @param[in] gating_max_duration_minutes Maximum duration of gating in minutes + * @param[in] std_initial Initial estimate for standard deviation + * @param[in] gain_factor Gain factor to amplify or to attenuate the NOx index output + */ +void sen5x_set_nox_algorithm_tuning( + const sen5x_t *dev, int16_t index_offset, int16_t learning_time_offset_hours, + int16_t learning_time_gain_hours, int16_t gating_max_duration_minutes, + int16_t std_initial, int16_t gain_factor); + +/** + * @brief Get the NOx Algortihm tuning parameters + * + * @param[inout] dev Device descriptor of the driver + * @param[out] index_offset NOx index representing typical (average) conditions + * @param[out] learning_time_offset_hours Time constant to estimate the NOx algorithm offset from the + * history in hours + * @param[out] learning_time_gain_hours The time constant to estimate the NOx algorithm gain from the + * history has no impact for NOx. This parameter is still in place for + * consistency reasons with the VOC tuning parameters command. + * This parameter must always be set to 12 hours + * @param[out] gating_max_duration_minutes Maximum duration of gating in minutes + * @param[out] std_initial Initial estimate for standard deviation + * @param[out] gain_factor Gain factor to amplify or to attenuate the NOx index output + */ +void sen5x_get_nox_algorithm_tuning( + const sen5x_t *dev, int16_t *index_offset, int16_t *learning_time_offset_hours, + int16_t *learning_time_gain_hours, int16_t *gating_max_duration_minutes, + int16_t *std_initial, int16_t *gain_factor); + +/** + * @brief Set the mode for the RH/T acceleration algorithm + * + * @param[inout] dev Device descriptor of the driver + * @param[in] mode RH/T accelaration mode: + * = 0: Low Acceleration + * = 1: High Acceleration + * = 2: Medium Acceleration + */ +void sen5x_set_rht_acceleration(const sen5x_t *dev, uint16_t mode); + +/** + * @brief Get the mode for the RH/T acceleration algorithm + * + * @param[inout] dev Device descriptor of the driver + * @param[out] mode RH/T accelaration mode: + * = 0: Low Acceleration + * = 1: High Acceleration + * = 2: Medium Acceleration + */ +void sen5x_get_rht_acceleration(const sen5x_t *dev, uint16_t *mode); + +/** + * @brief Get the VOC Algorithm state + * + * @param[inout] dev Device descriptor of the driver + * @param[in] state VOC Algorithm state + * @param[in] state_size Size of the VOC Algorithm state + */ +void sen5x_set_voc_state(const sen5x_t *dev, const uint8_t *state, uint8_t state_size); + +/** + * @brief Set the VOC Algorithm state + * + * @param[inout] dev Device descriptor of the driver + * @param[out] state VOC Algorithm state + * @param[in] state_size Size of the VOC Algorithm state + */ +void sen5x_get_voc_state(const sen5x_t *dev, uint8_t *state, uint8_t state_size); + +#ifdef __cplusplus +} +#endif + +#endif /* SEN5X_H */ +/** @} */ diff --git a/drivers/saul/init_devs/auto_init_sen5x.c b/drivers/saul/init_devs/auto_init_sen5x.c new file mode 100644 index 0000000000..201a371459 --- /dev/null +++ b/drivers/saul/init_devs/auto_init_sen5x.c @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2023 TU Braunschweig Institut für Betriebssysteme und Rechnerverbund + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_auto_init_saul + * @{ + * + * @file + * @brief Auto initialization of sen5x device driver. + * + * @author Daniel Prigoshij + * + * @} + */ + +#include "assert.h" +#include "log.h" +#include "saul_reg.h" +#include "sen5x.h" +#include "sen5x_params.h" + +/** + * @brief Allocation of memory for device descriptors + */ +static sen5x_t sen5x_devs[SEN5X_NUM]; + +/** + * @brief Memory for the SAUL registry entries + */ +static saul_reg_t saul_entries[SEN5X_NUM * 12]; + +/** + * @brief Define the number of saul info + */ +#define SEN5X_INFO_NUM ARRAY_SIZE(sen5x_saul_info) + +/** + * @name Reference the driver structs. + * @{ + */ +extern const saul_driver_t sen5x_mass_concentration_pm1p0_driver; +extern const saul_driver_t sen5x_mass_concentration_pm2p5_driver; +extern const saul_driver_t sen5x_mass_concentration_pm4p0_driver; +extern const saul_driver_t sen5x_mass_concentration_pm10p0_driver; +extern const saul_driver_t sen5x_number_concentration_pm0p5_driver; +extern const saul_driver_t sen5x_number_concentration_pm1p0_driver; +extern const saul_driver_t sen5x_number_concentration_pm2p5_driver; +extern const saul_driver_t sen5x_number_concentration_pm4p0_driver; +extern const saul_driver_t sen5x_number_concentration_pm10p0_driver; +extern const saul_driver_t sen5x_typical_particle_size_driver; +extern const saul_driver_t sen5x_ambient_humidity_driver; +extern const saul_driver_t sen5x_ambient_temperature_driver; +/** @} */ + +void auto_init_sen5x(void) +{ + assert(SEN5X_INFO_NUM == SEN5X_NUM); + + for (unsigned i = 0; i < SEN5X_NUM; i++) { + LOG_DEBUG("[auto_init_saul] initializing SEN5X #%u\n", i); + + if (sen5x_init(&sen5x_devs[i], &sen5x_params[i]) != 0) { + LOG_ERROR("[auto_init_saul] error initializing SEN5X #%u\n", i); + continue; + } + + sen5x_wake(&sen5x_devs[i]); + + /* Mass Concentration pm1p0 */ + saul_entries[(i * 12)].dev = &(sen5x_devs[i]); + saul_entries[(i * 12)].name = sen5x_saul_info[i].name; + saul_entries[(i * 12)].driver = &sen5x_mass_concentration_pm1p0_driver; + + /* Mass Concentration pm2p5 */ + saul_entries[(i * 12) + 1].dev = &(sen5x_devs[i]); + saul_entries[(i * 12) + 1].name = sen5x_saul_info[i].name; + saul_entries[(i * 12) + 1].driver = &sen5x_mass_concentration_pm2p5_driver; + + /* Mass Concentration pm4p0 */ + saul_entries[(i * 12) + 2].dev = &(sen5x_devs[i]); + saul_entries[(i * 12) + 2].name = sen5x_saul_info[i].name; + saul_entries[(i * 12) + 2].driver = &sen5x_mass_concentration_pm4p0_driver; + + /* Mass Concentration pm10p0 */ + saul_entries[(i * 12) + 3].dev = &(sen5x_devs[i]); + saul_entries[(i * 12) + 3].name = sen5x_saul_info[i].name; + saul_entries[(i * 12) + 3].driver = &sen5x_mass_concentration_pm10p0_driver; + + /* Number Concentration pm0p5 */ + saul_entries[(i * 12) + 4].dev = &(sen5x_devs[i]); + saul_entries[(i * 12) + 4].name = sen5x_saul_info[i].name; + saul_entries[(i * 12) + 4].driver = &sen5x_number_concentration_pm0p5_driver; + + /* Number Concentration pm1p0 */ + saul_entries[(i * 12) + 5].dev = &(sen5x_devs[i]); + saul_entries[(i * 12) + 5].name = sen5x_saul_info[i].name; + saul_entries[(i * 12) + 5].driver = &sen5x_number_concentration_pm1p0_driver; + + /* Number Concentration pm2p5 */ + saul_entries[(i * 12) + 6].dev = &(sen5x_devs[i]); + saul_entries[(i * 12) + 6].name = sen5x_saul_info[i].name; + saul_entries[(i * 12) + 6].driver = &sen5x_number_concentration_pm2p5_driver; + + /* Number Concentration pm4p0 */ + saul_entries[(i * 12) + 7].dev = &(sen5x_devs[i]); + saul_entries[(i * 12) + 7].name = sen5x_saul_info[i].name; + saul_entries[(i * 12) + 7].driver = &sen5x_number_concentration_pm4p0_driver; + + /* Number Concentration pm10p0 */ + saul_entries[(i * 12) + 8].dev = &(sen5x_devs[i]); + saul_entries[(i * 12) + 8].name = sen5x_saul_info[i].name; + saul_entries[(i * 12) + 8].driver = &sen5x_number_concentration_pm10p0_driver; + + /* Typical particle size */ + saul_entries[(i * 12) + 9].dev = &(sen5x_devs[i]); + saul_entries[(i * 12) + 9].name = sen5x_saul_info[i].name; + saul_entries[(i * 12) + 9].driver = &sen5x_typical_particle_size_driver; + + /* Ambient humidity */ + saul_entries[(i * 12) + 10].dev = &(sen5x_devs[i]); + saul_entries[(i * 12) + 10].name = sen5x_saul_info[i].name; + saul_entries[(i * 12) + 10].driver = &sen5x_ambient_humidity_driver; + + /* Ambient temperature */ + saul_entries[(i * 12) + 11].dev = &(sen5x_devs[i]); + saul_entries[(i * 12) + 11].name = sen5x_saul_info[i].name; + saul_entries[(i * 12) + 11].driver = &sen5x_ambient_temperature_driver; + + /* Add register entries to saul */ + for (unsigned int j = 0; j < 12; j++) { + saul_reg_add(&(saul_entries[(i * 4) + j])); + } + } +} \ No newline at end of file diff --git a/drivers/sen5x/Kconfig b/drivers/sen5x/Kconfig new file mode 100644 index 0000000000..bddb7ab786 --- /dev/null +++ b/drivers/sen5x/Kconfig @@ -0,0 +1,11 @@ +# Copyright (c) 2023 TU Braunschweig Institut für Betriebssysteme und Rechnerverbund +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +config MODULE_SEN5X + bool "Sensirion Embedded I2C SEN5x Driver" + depends on TEST_KCONFIG + select MODULE_PERIPH_I2C + select MODULE_ZTIMER diff --git a/drivers/sen5x/Makefile b/drivers/sen5x/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/sen5x/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/sen5x/Makefile.dep b/drivers/sen5x/Makefile.dep new file mode 100644 index 0000000000..0d6929e83c --- /dev/null +++ b/drivers/sen5x/Makefile.dep @@ -0,0 +1,2 @@ +FEATURES_REQUIRED += periph_i2c +USEMODULE += xtimer \ No newline at end of file diff --git a/drivers/sen5x/Makefile.include b/drivers/sen5x/Makefile.include new file mode 100644 index 0000000000..3dce3caf53 --- /dev/null +++ b/drivers/sen5x/Makefile.include @@ -0,0 +1,2 @@ +USEMODULE_INCLUDES_sen5x := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_sen5x) diff --git a/drivers/sen5x/include/sen5x_constants.h b/drivers/sen5x/include/sen5x_constants.h new file mode 100644 index 0000000000..594019a057 --- /dev/null +++ b/drivers/sen5x/include/sen5x_constants.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 TU Braunschweig Institut für Betriebssysteme und Rechnerverbund + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_sen5x + * @{ + * + * @file + * @brief Internal addresses, registers and constants + * + * @author Daniel Prigoshij + */ + +#ifndef SEN5X_CONSTANTS_H +#define SEN5X_CONSTANTS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* define here the addresses, register and constants of the driver */ + +#ifndef SEN5X_I2C_ADDRESS +#define SEN5X_I2C_ADDRESS (0x69) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* SEN5X_CONSTANTS_H */ +/** @} */ diff --git a/drivers/sen5x/include/sen5x_i2c.h b/drivers/sen5x/include/sen5x_i2c.h new file mode 100644 index 0000000000..60fcce9e26 --- /dev/null +++ b/drivers/sen5x/include/sen5x_i2c.h @@ -0,0 +1,715 @@ +/* + * THIS FILE IS AUTOMATICALLY GENERATED + * + * I2C-Generator: 0.3.0 + * Yaml Version: 2.1.3 + * Template Version: 0.7.0-109-gb259776 + */ +/* + * 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 SEN5X_I2C_H +#define SEN5X_I2C_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "sensirion_config.h" + +/** + * sen5x_start_measurement() - Starts a continuous measurement. + * + * After starting the measurement, it takes some time (~1s) until the first + * measurement results are available. You could poll with the command + * 0x0202 \"Read Data Ready\" to check when the results are ready to read. + * + * This command is only available in idle mode. If the device is already + * in any measure mode, this command has no effect. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_start_measurement(void); + +/** + * sen5x_start_measurement_without_pm() - Starts a continuous measurement + * without PM. Only humidity, temperature, VOC and NOx are available in this + * mode. Laser and fan are switched off to keep power consumption low. + * + * After starting the measurement, it takes some time (~1s) until the first + * measurement results are available. You could poll with the command + * 0x0202 \"Read Data Ready\" to check when the results are ready to read. + * + * This command is only available in idle mode. If the device is already + * in any measure mode, this command has no effect. + * + * Supported sensors: SEN54, SEN55 + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_start_measurement_without_pm(void); + +/** + * sen5x_stop_measurement() - Stops the measurement and returns to idle mode. + * + * If the device is already in idle mode, this command has no effect. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_stop_measurement(void); + +/** + * sen5x_read_data_ready() - This command can be used to check if new + * measurement results are ready to read. The data ready flag is automatically + * reset after reading the measurement values with the 0x03.. \"Read Measured + * Values\" commands. + * + * @note During fan (auto-)cleaning, no measurement data is available for + * several seconds and thus this flag will not be set until cleaning has + * finished. So please expect gaps of several seconds at any time if fan + * auto-cleaning is enabled. + * + * @param padding Padding byte, always 0x00. + * + * @param data_ready True (0x01) if data is ready, False (0x00) if not. When no + * measurement is running, False will be returned. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_read_data_ready(bool* data_ready); + +/** + * sen5x_read_measured_values() - Returns the measured values. + * + * The command 0x0202 \"Read Data Ready\" can be used to check if new + * data is available since the last read operation. If no new data is + * available, the previous values will be returned again. If no data is + * available at all (e.g. measurement not running for at least one + * second), all values will be at their upper limit (0xFFFF for `uint16`, + * 0x7FFF for `int16`). + * + * @param mass_concentration_pm1p0 Value is scaled with factor 10: + * PM1.0 [µg/m³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param mass_concentration_pm2p5 Value is scaled with factor 10: + * PM2.5 [µg/m³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param mass_concentration_pm4p0 Value is scaled with factor 10: + * PM4.0 [µg/m³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param mass_concentration_pm10p0 Value is scaled with factor 10: + * PM10.0 [µg/m³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param ambient_humidity Value is scaled with factor 100: RH [%] = value / 100 + * @note If this value is unknown, 0x7FFF is returned.* + * + * @param ambient_temperature Value is scaled with factor 200: + * T [°C] = value / 200 + * @note If this value is unknown, 0x7FFF is returned.* + * + * @param voc_index Value is scaled with factor 10: VOC Index = value / 10 + * @note If this value is unknown, 0x7FFF is returned.* + * + * @param nox_index Value is scaled with factor 10: NOx Index = value / 10 + * @note If this value is unknown, 0x7FFF is returned. During + * the first 10..11 seconds after power-on or device reset, this + * value will be 0x7FFF as well.* + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_read_measured_values(uint16_t* mass_concentration_pm1p0, + uint16_t* mass_concentration_pm2p5, + uint16_t* mass_concentration_pm4p0, + uint16_t* mass_concentration_pm10p0, + int16_t* ambient_humidity, + int16_t* ambient_temperature, + int16_t* voc_index, int16_t* nox_index); + +/** + * sen5x_read_measured_raw_values() - Returns the measured raw values. + +The command 0x0202 \"Read Data Ready\" can be used to check if new +data is available since the last read operation. If no new data is +available, the previous values will be returned again. If no data +is available at all (e.g. measurement not running for at least one +second), all values will be at their upper limit (0xFFFF for `uint16`, +0x7FFF for `int16`). + * + * @param raw_humidity Value is scaled with factor 100: RH [%] = value / 100 + * @note If this value is unknown, 0x7FFF is returned. + * + * @param raw_temperature Value is scaled with factor 200: T [°C] = value / 200 + * @note If this value is unknown, 0x7FFF is returned. + * + * @param raw_voc Raw measured VOC ticks without scale factor. + * @note If this value is unknown, 0xFFFF is returned. + * + * @param raw_nox Raw measured NOx ticks without scale factor. + * @note If this value is unknown, 0xFFFF is returned. During + * the first 10..11 seconds after power-on or device reset, this + * value will be 0xFFFF as well.* + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_read_measured_raw_values(int16_t* raw_humidity, + int16_t* raw_temperature, + uint16_t* raw_voc, uint16_t* raw_nox); + +/** + * sen5x_read_measured_values_sen50() - Returns the measured values for SEN50. + * + * The command 0x0202 \"Read Data Ready\" can be used to check if new + * data is available since the last read operation. If no new data is + * available, the previous values will be returned again. If no data is + * available at all (e.g. measurement not running for at least one + * second), all values will be at their upper limit (0xFFFF). + * + * @param mass_concentration_pm1p0 Value is scaled with factor 10: + * PM1.0 [µg/m³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param mass_concentration_pm2p5 Value is scaled with factor 10: + * PM2.5 [µg/m³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param mass_concentration_pm4p0 Value is scaled with factor 10: + * PM4.0 [µg/m³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param mass_concentration_pm10p0 Value is scaled with factor 10: + * PM10.0 [µg/m³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_read_measured_values_sen50(uint16_t* mass_concentration_pm1p0, + uint16_t* mass_concentration_pm2p5, + uint16_t* mass_concentration_pm4p0, + uint16_t* mass_concentration_pm10p0); + +/** + * sen5x_read_measured_pm_values() - Returns the measured particulate matter + * values. + * + * The command 0x0202 \"Read Data Ready\" can be used to check if new + * data is available since the last read operation. If no new data is + * available, the previous values will be returned again. If no data + * is available at all (e.g. measurement not running for at least one + * second), all values will be 0xFFFF. + * + * @param mass_concentration_pm1p0 Value is scaled with factor 10: + * PM1.0 [µg/m³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param mass_concentration_pm2p5 Value is scaled with factor 10: + * PM2.5 [µg/m³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param mass_concentration_pm4p0 Value is scaled with factor 10: + * PM4.0 [µg/m³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param mass_concentration_pm10p0 Value is scaled with factor 10: + * PM10.0 [µg/m³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param number_concentration_pm0p5 Value is scaled with factor 10: + * PM0.5 [#/cm³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param number_concentration_pm1p0 Value is scaled with factor 10: + * PM1.0 [#/cm³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param number_concentration_pm2p5 Value is scaled with factor 10: + * PM2.5 [#/cm³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param number_concentration_pm4p0 Value is scaled with factor 10: + * PM4.0 [#/cm³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param number_concentration_pm10p0 Value is scaled with factor 10: + * PM10.0 [#/cm³] = value / 10 + * @note If this value is unknown, 0xFFFF is returned. + * + * @param typical_particle_size Value is scaled with factor 1000: + * Size [µm] = value / 1000 + * @note If this value is unknown, 0xFFFF is returned. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_read_measured_pm_values( + uint16_t* mass_concentration_pm1p0, uint16_t* mass_concentration_pm2p5, + uint16_t* mass_concentration_pm4p0, uint16_t* mass_concentration_pm10p0, + uint16_t* number_concentration_pm0p5, uint16_t* number_concentration_pm1p0, + uint16_t* number_concentration_pm2p5, uint16_t* number_concentration_pm4p0, + uint16_t* number_concentration_pm10p0, uint16_t* typical_particle_size); + +/** + * sen5x_start_fan_cleaning() - Starts the fan cleaning manually. The \"data + * ready\"-flag will be cleared immediately and during the next few seconds, no + * new measurement results will be available (old values will be returned). Once + * the cleaning is finished, the \"data ready\"-flag will be set and new + * measurement results will be available. + * + * When executing this command while cleaning is already active, the + * command does nothing. + * + * If you stop the measurement while fan cleaning is active, the cleaning + * will be aborted immediately. + * + * @note This command is only available in measure mode with PM measurement + * enabled, i.e. only if the fan is already running. In any other state, this + * command does nothing. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_start_fan_cleaning(void); + +/** + * sen5x_set_temperature_offset_parameters() - Sets the temperature offset + * parameters for the device. + * + * Supported sensors: SEN54, SEN55 + * + * @param temp_offset Constant temperature offset scaled with factor 200 (T [°C] + * = value / 200). The default value is 0. + * + * @param slope Normalized temperature offset slope scaled with factor 10000 + * (applied factor = value / 10000). The default value is 0. + * + * @param time_constant Time constant [s] how fast the new slope and offset will + * be applied. After the specified value in seconds, 63% of the new slope and + * offset are applied. A time constant of zero means the new values will be + * applied immediately (within the next measure interval of 1 second). + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_set_temperature_offset_parameters(int16_t temp_offset, + int16_t slope, + uint16_t time_constant); + +/** + * sen5x_get_temperature_offset_parameters() - Gets the temperature offset + * parameters from the device. + * + * Supported sensors: SEN54, SEN55 + * + * @param temp_offset Constant temperature offset scaled with factor 200 (T [°C] + * = value / 200). + * + * @param slope Normalized temperature offset slope scaled with factor 10000 + * (applied factor = value / 10000). + * + * @param time_constant Time constant [s] how fast the slope and offset are + * applied. After the specified value in seconds, 63% of the new slope and + * offset are applied. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_get_temperature_offset_parameters(int16_t* temp_offset, + int16_t* slope, + uint16_t* time_constant); + +/** + * sen5x_set_warm_start_parameter() - Sets the warm start parameter for the + * device. + * + * Supported sensors: SEN54, SEN55 + * + * @note This parameter can be changed in any state of the device (and the + * getter immediately returns the new value), but it is applied only the next + * time starting a measurement, i.e. when sending a \"Start Measurement\" + * command! So the parameter needs to be set *before* a warm-start measurement + * is started. + * + * @param warm_start Warm start behavior as a value in the range from 0 (cold + * start) to 65535 (warm start). The default value is 0. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_set_warm_start_parameter(uint16_t warm_start); + +/** + * sen5x_get_warm_start_parameter() - Gets the warm start parameter from the + * device. + * + * Supported sensors: SEN54, SEN55 + * + * @param warm_start Warm start behavior as a value in the range from 0 (cold + * start) to 65535 (warm start). + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_get_warm_start_parameter(uint16_t* warm_start); + +/** + * sen5x_set_voc_algorithm_tuning_parameters() - Sets the tuning parameters of + * the VOC algorithm. + * + * Supported sensors: SEN54, SEN55 + * + * @note This command is available only in idle mode. In measure mode, this + * command has no effect. In addition, it has no effect if at least one + * parameter is outside the specified range. + * + * @param index_offset VOC index representing typical (average) conditions. + * Allowed values are in range 1..250. The default value is 100. + * + * @param learning_time_offset_hours Time constant to estimate the VOC algorithm + * offset from the history in hours. Past events will be forgotten after about + * twice the learning time. Allowed values are in range 1..1000. The default + * value is 12 hours. + * + * @param learning_time_gain_hours Time constant to estimate the VOC algorithm + * gain from the history in hours. Past events will be forgotten after about + * twice the learning time. Allowed values are in range 1..1000. The default + * value is 12 hours. + * + * @param gating_max_duration_minutes Maximum duration of gating in minutes + * (freeze of estimator during high VOC index signal). Set to zero to disable + * the gating. Allowed values are in range 0..3000. The default value is 180 + * minutes. + * + * @param std_initial Initial estimate for standard deviation. Lower value + * boosts events during initial learning period, but may result in larger + * device-to-device variations. Allowed values are in range 10..5000. The + * default value is 50. + * + * @param gain_factor Gain factor to amplify or to attenuate the VOC index + * output. Allowed values are in range 1..1000. The default value is 230. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_set_voc_algorithm_tuning_parameters( + int16_t index_offset, int16_t learning_time_offset_hours, + int16_t learning_time_gain_hours, int16_t gating_max_duration_minutes, + int16_t std_initial, int16_t gain_factor); + +/** + * sen5x_get_voc_algorithm_tuning_parameters() - Gets the currently set tuning + * parameters of the VOC algorithm. + * + * Supported sensors: SEN54, SEN55 + * + * @param index_offset VOC index representing typical (average) conditions. + * + * @param learning_time_offset_hours Time constant to estimate the VOC algorithm + * offset from the history in hours. Past events will be forgotten after about + * twice the learning time. + * + * @param learning_time_gain_hours Time constant to estimate the VOC algorithm + * gain from the history in hours. Past events will be forgotten after about + * twice the learning time. + * + * @param gating_max_duration_minutes Maximum duration of gating in minutes + * (freeze of estimator during high VOC index signal). Zero disables the gating. + * + * @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. + * + * @param gain_factor Gain factor to amplify or to attenuate the VOC index + * output. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_get_voc_algorithm_tuning_parameters( + int16_t* index_offset, int16_t* learning_time_offset_hours, + int16_t* learning_time_gain_hours, int16_t* gating_max_duration_minutes, + int16_t* std_initial, int16_t* gain_factor); + +/** + * sen5x_set_nox_algorithm_tuning_parameters() - Sets the tuning parameters of + * the NOx algorithm. + * + * Supported sensors: SEN55 + * + * @note This command is available only in idle mode. In measure mode, this + * command has no effect. In addition, it has no effect if at least one + * parameter is outside the specified range. + * + * @param index_offset NOx index representing typical (average) conditions. + * Allowed values are in range 1..250. The default value is 1. + * + * @param learning_time_offset_hours Time constant to estimate the NOx algorithm + * offset from the history in hours. Past events will be forgotten after about + * twice the learning time. Allowed values are in range 1..1000. The default + * value is 12 hours. + * + * @param learning_time_gain_hours The time constant to estimate the NOx + * algorithm gain from the history has no impact for NOx. This parameter is + * still in place for consistency reasons with the VOC tuning parameters + * command. This parameter must always be set to 12 hours. + * + * @param gating_max_duration_minutes Maximum duration of gating in minutes + * (freeze of estimator during high NOx index signal). Set to zero to disable + * the gating. Allowed values are in range 0..3000. The default value is 720 + * minutes. + * + * @param std_initial The initial estimate for standard deviation parameter has + * no impact for NOx. This parameter is still in place for consistency reasons + * with the VOC tuning parameters command. This parameter must always be set + * to 50. + * + * @param gain_factor Gain factor to amplify or to attenuate the NOx index + * output. Allowed values are in range 1..1000. The default value is 230. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_set_nox_algorithm_tuning_parameters( + int16_t index_offset, int16_t learning_time_offset_hours, + int16_t learning_time_gain_hours, int16_t gating_max_duration_minutes, + int16_t std_initial, int16_t gain_factor); + +/** + * sen5x_get_nox_algorithm_tuning_parameters() - Gets the currently set tuning + * parameters of the NOx algorithm. + * + * Supported sensors: SEN55 + * + * @param index_offset NOx index representing typical (average) conditions. + * + * @param learning_time_offset_hours Time constant to estimate the NOx algorithm + * offset from the history in hours. Past events will be forgotten after about + * twice the learning time. + * + * @param learning_time_gain_hours The time constant to estimate the NOx + * algorithm gain from the history has no impact for NOx. This parameter is + * still in place for consistency reasons with the VOC tuning parameters + * command. + * + * @param gating_max_duration_minutes Maximum duration of gating in minutes + * (freeze of estimator during high NOx index signal). Zero disables the gating. + * + * @param std_initial The initial estimate for standard deviation has no impact + * for NOx. This parameter is still in place for consistency reasons with the + * VOC tuning parameters command. + * + * @param gain_factor Gain factor to amplify or to attenuate the NOx index + * output. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_get_nox_algorithm_tuning_parameters( + int16_t* index_offset, int16_t* learning_time_offset_hours, + int16_t* learning_time_gain_hours, int16_t* gating_max_duration_minutes, + int16_t* std_initial, int16_t* gain_factor); + +/** + * sen5x_set_rht_acceleration_mode() - Sets the RH/T acceleration mode. + * + * Supported sensors: SEN54, SEN55 + * + * @note This parameter can be changed in any state of the device (and the + * getter immediately returns the new value), but it is applied only the next + * time starting a measurement, i.e. when sending a \"Start Measurement\" + * command. So the parameter needs to be set *before* a new measurement is + * started. + * + * @param mode The new RH/T acceleration mode. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_set_rht_acceleration_mode(uint16_t mode); + +/** + * sen5x_get_rht_acceleration_mode() - Gets the RH/T acceleration mode. + * + * Supported sensors: SEN54, SEN55 + * + * @param mode The current RH/T acceleration mode. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_get_rht_acceleration_mode(uint16_t* mode); + +/** + * sen5x_set_voc_algorithm_state() - Sets the VOC algorithm state previously + * received with the \"Get VOC Algorithm State\" command. + * + * Supported sensors: SEN54, SEN55 + * + * @note This command is only available in idle mode and the state will be + * applied only once when starting the next measurement. Any further + * measurements (i.e. when stopping and restarting the measure mode) will reset + * the state to initial values. In measure mode, this command has no effect. + * + * @param state VOC algorithm state to restore. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_set_voc_algorithm_state(const uint8_t* state, uint8_t state_size); + +/** + * sen5x_get_voc_algorithm_state() - Gets the current VOC algorithm state. This + * data can be used to restore the state with the \"Set VOC Algorithm State\" + * command after a short power cycle or device reset. + * + * This command can be used either in measure mode or in idle mode + * (which will then return the state at the time when the measurement + * was stopped). In measure mode, the state can be read each measure + * interval to always have the latest state available, even in case of + * a sudden power loss. + * + * Supported sensors: SEN54, SEN55 + * + * @param state Current VOC algorithm state. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_get_voc_algorithm_state(uint8_t* state, uint8_t state_size); + +/** + * sen5x_set_fan_auto_cleaning_interval() - Sets the fan auto cleaning interval + * for the device. + * + * @param interval Fan auto cleaning interval [s]. Set to zero to disable auto + * cleaning. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_set_fan_auto_cleaning_interval(uint32_t interval); + +/** + * sen5x_get_fan_auto_cleaning_interval() - Gets the fan auto cleaning interval + * from the device. + * + * @param interval Fan auto cleaning interval [s]. Zero means auto cleaning is + * disabled. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_get_fan_auto_cleaning_interval(uint32_t* interval); + +/** + * sen5x_get_product_name() - Gets the product name from the device. + * + * @param product_name Null-terminated ASCII string containing the product name. + * Up to 32 characters can be read from the device. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_get_product_name(unsigned char* product_name, + uint8_t product_name_size); + +/** + * sen5x_get_serial_number() - Gets the serial number from the device. + * + * @param serial_number Null-terminated ASCII string containing the serial + * number. Up to 32 characters can be read from the device. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_get_serial_number(unsigned char* serial_number, + uint8_t serial_number_size); + +/** + * sen5x_get_version() - Gets the version information for the hardware, firmware + * and communication protocol. + * + * @param firmware_major Firmware major version number. + * + * @param firmware_minor Firmware minor version number. + * + * @param firmware_debug Firmware debug state. If the debug state is set, the + * firmware is in development. + * + * @param hardware_major Hardware major version number. + * + * @param hardware_minor Hardware minor version number. + * + * @param protocol_major Protocol major version number. + * + * @param protocol_minor Protocol minor version number. + * + * @param padding Padding byte, ignore this. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_get_version(uint8_t* firmware_major, uint8_t* firmware_minor, + bool* firmware_debug, uint8_t* hardware_major, + uint8_t* hardware_minor, uint8_t* protocol_major, + uint8_t* protocol_minor); + +/** + * sen5x_read_device_status() - Reads the current device status. + + * Use this command to get detailed information about the device status. + * The device status is encoded in flags. Each device status flag + * represents a single bit in a 32-bit integer value. If more than one + * error is present, the device status register value is the sum of the + * corresponding flag values. For details about the available flags, + * refer to the device status flags documentation. + * + * @note The status flags of type \"Error\" are sticky, i.e. they are not + * cleared automatically even if the error condition no longer exists. So they + * can only be cleared manually with the command 0xD210 \"Read And Clear Device + * Status\" or with a device reset. All other flags are not sticky, i.e. they + * are cleared automatically if the trigger condition disappears. + * + * @param device_status Device status (32 flags as an integer value). For + * details, please refer to the device status flags documentation. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_read_device_status(uint32_t* device_status); + +/** + * sen5x_read_and_clear_device_status() - Reads the current device status (like + * command 0xD206 \"Read Device Status\") and afterwards clears all flags. + * + * @param device_status Device status (32 flags as an integer value) **before** + * clearing it. For details, please refer to the device status flags + * documentation. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_read_and_clear_device_status(uint32_t* device_status); + +/** + * sen5x_device_reset() - Executes a reset on the device. This has the same + * effect as a power cycle. + * + * @return 0 on success, an error code otherwise + */ +int16_t sen5x_device_reset(void); + +#ifdef __cplusplus +} +#endif + +#endif /* SEN5X_I2C_H */ diff --git a/drivers/sen5x/include/sen5x_params.h b/drivers/sen5x/include/sen5x_params.h new file mode 100644 index 0000000000..3cc2c220df --- /dev/null +++ b/drivers/sen5x/include/sen5x_params.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 TU Braunschweig Institut für Betriebssysteme und Rechnerverbund + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_sen5x + * + * @{ + * @file + * @brief Default configuration + * + * @author Daniel Prigoshij + */ + +#ifndef SEN5X_PARAMS_H +#define SEN5X_PARAMS_H + +#include "board.h" +#include "sen5x.h" +#include "sen5x_constants.h" +#include "periph/i2c.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Set default configuration parameters for SEN5x sensors + * @{ + */ +#ifndef SEN5X_PARAM_I2C_DEV +#define SEN5X_PARAM_I2C_DEV I2C_DEV(0) +#endif +#ifndef SEN5X_PARAM_ADDR +#define SEN5X_PARAM_ADDR SEN5X_I2C_ADDRESS +#endif + + +#ifndef SEN5X_PARAMS +#define SEN5X_PARAMS { .i2c_dev = SEN5X_PARAM_I2C_DEV, \ + .i2c_addr = SEN5X_PARAM_ADDR } +#endif +/**@}*/ + +/** + * @brief Configure SEN55/54 + */ +static const sen5x_params_t sen5x_params[] = +{ + SEN5X_PARAMS +}; + +/** + * @brief Configure SAUL registry entries + */ +static const saul_reg_info_t sen5x_saul_info[] = +{ + SEN5X_SAUL_INFO +}; + +/** + * @brief Get the number of configured SCD30 devices + */ + +#define SEN5X_NUM ARRAY_SIZE(sen5x_params) + +#ifdef __cplusplus +} +#endif + +#endif /* SEN5X_PARAMS_H */ +/** @} */ diff --git a/drivers/sen5x/include/sensirion_common.h b/drivers/sen5x/include/sensirion_common.h new file mode 100644 index 0000000000..cfcd19f8be --- /dev/null +++ b/drivers/sen5x/include/sensirion_common.h @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2018, 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 SENSIRION_COMMON_H +#define SENSIRION_COMMON_H + +#include "sensirion_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define NO_ERROR 0 +#define NOT_IMPLEMENTED_ERROR 31 + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) +#endif + +#define SENSIRION_COMMAND_SIZE 2 +#define SENSIRION_WORD_SIZE 2 +#define SENSIRION_NUM_WORDS(x) (sizeof(x) / SENSIRION_WORD_SIZE) +#define SENSIRION_MAX_BUFFER_WORDS 32 + +/** + * sensirion_common_bytes_to_int16_t() - Convert an array of bytes to an int16_t + * + * Convert an array of bytes received from the sensor in big-endian/MSB-first + * format to an int16_t value in the correct system-endianness. + * + * @param bytes An array of at least two bytes (MSB first) + * @return The byte array represented as int16_t + */ +int16_t sensirion_common_bytes_to_int16_t(const uint8_t* bytes); + +/** + * sensirion_common_bytes_to_int32_t() - Convert an array of bytes to an int32_t + * + * Convert an array of bytes received from the sensor in big-endian/MSB-first + * format to an int32_t value in the correct system-endianness. + * + * @param bytes An array of at least four bytes (MSB first) + * @return The byte array represented as int32_t + */ +int32_t sensirion_common_bytes_to_int32_t(const uint8_t* bytes); + +/** + * sensirion_common_bytes_to_uint16_t() - Convert an array of bytes to an + * uint16_t + * + * Convert an array of bytes received from the sensor in big-endian/MSB-first + * format to an uint16_t value in the correct system-endianness. + * + * @param bytes An array of at least two bytes (MSB first) + * @return The byte array represented as uint16_t + */ +uint16_t sensirion_common_bytes_to_uint16_t(const uint8_t* bytes); + +/** + * sensirion_common_bytes_to_uint32_t() - Convert an array of bytes to an + * uint32_t + * + * Convert an array of bytes received from the sensor in big-endian/MSB-first + * format to an uint32_t value in the correct system-endianness. + * + * @param bytes An array of at least four bytes (MSB first) + * @return The byte array represented as uint32_t + */ +uint32_t sensirion_common_bytes_to_uint32_t(const uint8_t* bytes); + +/** + * sensirion_common_bytes_to_float() - Convert an array of bytes to a float + * + * Convert an array of bytes received from the sensor in big-endian/MSB-first + * format to an float value in the correct system-endianness. + * + * @param bytes An array of at least four bytes (MSB first) + * @return The byte array represented as float + */ +float sensirion_common_bytes_to_float(const uint8_t* bytes); + +/** + * sensirion_common_uint32_t_to_bytes() - Convert an uint32_t to an array of + * bytes + * + * Convert an uint32_t value in system-endianness to big-endian/MBS-first + * format to send to the sensor. + * + * @param value Value to convert + * @param bytes An array of at least four bytes + */ +void sensirion_common_uint32_t_to_bytes(const uint32_t value, uint8_t* bytes); + +/** + * sensirion_common_uint16_t_to_bytes() - Convert an uint16_t to an array of + * bytes + * + * Convert an uint16_t value in system-endianness to big-endian/MBS-first + * format to send to the sensor. + * + * @param value Value to convert + * @param bytes An array of at least two bytes + */ +void sensirion_common_uint16_t_to_bytes(const uint16_t value, uint8_t* bytes); + +/** + * sensirion_common_int32_t_to_bytes() - Convert an int32_t to an array of bytes + * + * Convert an int32_t value in system-endianness to big-endian/MBS-first + * format to send to the sensor. + * + * @param value Value to convert + * @param bytes An array of at least four bytes + */ +void sensirion_common_int32_t_to_bytes(const int32_t value, uint8_t* bytes); + +/** + * sensirion_common_int16_t_to_bytes() - Convert an int16_t to an array of bytes + * + * Convert an int16_t value in system-endianness to big-endian/MBS-first + * format to send to the sensor. + * + * @param value Value to convert + * @param bytes An array of at least two bytes + */ +void sensirion_common_int16_t_to_bytes(const int16_t value, uint8_t* bytes); + +/** + * sensirion_common_float_to_bytes() - Convert an float to an array of bytes + * + * Convert an float value in system-endianness to big-endian/MBS-first + * format to send to the sensor. + * + * @param value Value to convert + * @param bytes An array of at least four bytes + */ +void sensirion_common_float_to_bytes(const float value, uint8_t* bytes); + +/** + * sensirion_common_copy_bytes() - Copy bytes from one array to the other. + * + * @param source Array of bytes to be copied. + * @param destination Array of bytes to be copied to. + * @param data_length Number of bytes to copy. + */ +void sensirion_common_copy_bytes(const uint8_t* source, uint8_t* destination, + uint16_t data_length); + +#ifdef __cplusplus +} +#endif + +#endif /* SENSIRION_COMMON_H */ diff --git a/drivers/sen5x/include/sensirion_config.h b/drivers/sen5x/include/sensirion_config.h new file mode 100644 index 0000000000..1e88ceb949 --- /dev/null +++ b/drivers/sen5x/include/sensirion_config.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019, 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 SENSIRION_CONFIG_H +#define SENSIRION_CONFIG_H + +/** + * If your platform does not provide the library stdlib.h you have to remove the + * include and define NULL yourself (see below). + */ +#include + +/** + * #ifndef NULL + * #define NULL ((void *)0) + * #endif + */ + +/** + * If your platform does not provide the library stdint.h you have to + * define the integral types yourself (see below). + */ +#include + +/** + * Typedef section for types commonly defined in + * If your system does not provide stdint headers, please define them + * accordingly. Please make sure to define int64_t and uint64_t. + */ +/* typedef unsigned long long int uint64_t; + * typedef long long int int64_t; + * typedef long int32_t; + * typedef unsigned long uint32_t; + * typedef short int16_t; + * typedef unsigned short uint16_t; + * typedef char int8_t; + * typedef unsigned char uint8_t; + */ + +#ifndef __cplusplus + +/** + * If your platform doesn't define the bool type we define it as int. Depending + * on your system update the definition below. + */ +#if __STDC_VERSION__ >= 199901L +#include +#else + +#ifndef bool +#define bool int +#define true 1 +#define false 0 +#endif /* bool */ + +#endif /* __STDC_VERSION__ */ + +#endif /* __cplusplus */ + +#endif /* SENSIRION_CONFIG_H */ diff --git a/drivers/sen5x/include/sensirion_i2c.h b/drivers/sen5x/include/sensirion_i2c.h new file mode 100644 index 0000000000..ac1e49dd8e --- /dev/null +++ b/drivers/sen5x/include/sensirion_i2c.h @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2018, 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 SENSIRION_I2C_H +#define SENSIRION_I2C_H + +#include "sensirion_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CRC_ERROR 1 +#define I2C_BUS_ERROR 2 +#define I2C_NACK_ERROR 3 +#define BYTE_NUM_ERROR 4 + +#define CRC8_POLYNOMIAL 0x31 +#define CRC8_INIT 0xFF +#define CRC8_LEN 1 + +#define SENSIRION_COMMAND_SIZE 2 +#define SENSIRION_WORD_SIZE 2 +#define SENSIRION_NUM_WORDS(x) (sizeof(x) / SENSIRION_WORD_SIZE) +#define SENSIRION_MAX_BUFFER_WORDS 32 + +uint8_t sensirion_i2c_generate_crc(const uint8_t* data, uint16_t count); + +int8_t sensirion_i2c_check_crc(const uint8_t* data, uint16_t count, + uint8_t checksum); + +/** + * sensirion_i2c_general_call_reset() - Send a general call reset. + * + * @warning This will reset all attached I2C devices on the bus which support + * general call reset. + * + * @return NO_ERROR on success, an error code otherwise + */ +int16_t sensirion_i2c_general_call_reset(void); + +/** + * sensirion_i2c_fill_cmd_send_buf() - create the i2c send buffer for a command + * and a set of argument words. The output buffer interleaves argument words + * with their checksums. + * @buf: The generated buffer to send over i2c. Then buffer length must + * be at least SENSIRION_COMMAND_LEN + num_args * + * (SENSIRION_WORD_SIZE + CRC8_LEN). + * @cmd: The i2c command to send. It already includes a checksum. + * @args: The arguments to the command. Can be NULL if none. + * @num_args: The number of word arguments in args. + * + * @return The number of bytes written to buf + */ +uint16_t sensirion_i2c_fill_cmd_send_buf(uint8_t* buf, uint16_t cmd, + const uint16_t* args, + uint8_t num_args); + +/** + * sensirion_i2c_read_words() - read data words from sensor + * + * @address: Sensor i2c address + * @data_words: Allocated buffer to store the read words. + * The buffer may also have been modified in case of an error. + * @num_words: Number of data words to read (without CRC bytes) + * + * @return NO_ERROR on success, an error code otherwise + */ +int16_t sensirion_i2c_read_words(uint8_t address, uint16_t* data_words, + uint16_t num_words); + +/** + * sensirion_i2c_read_words_as_bytes() - read data words as byte-stream from + * sensor + * + * Read bytes without adjusting values to the uP's word-order. + * + * @address: Sensor i2c address + * @data: Allocated buffer to store the read bytes. + * The buffer may also have been modified in case of an error. + * @num_words: Number of data words(!) to read (without CRC bytes) + * Since only word-chunks can be read from the sensor the size + * is still specified in sensor-words (num_words = num_bytes * + * SENSIRION_WORD_SIZE) + * + * @return NO_ERROR on success, an error code otherwise + */ +int16_t sensirion_i2c_read_words_as_bytes(uint8_t address, uint8_t* data, + uint16_t num_words); + +/** + * sensirion_i2c_write_cmd() - writes a command to the sensor + * @address: Sensor i2c address + * @command: Sensor command + * + * @return NO_ERROR on success, an error code otherwise + */ +int16_t sensirion_i2c_write_cmd(uint8_t address, uint16_t command); + +/** + * sensirion_i2c_write_cmd_with_args() - writes a command with arguments to the + * sensor + * @address: Sensor i2c address + * @command: Sensor command + * @data: Argument buffer with words to send + * @num_words: Number of data words to send (without CRC bytes) + * + * @return NO_ERROR on success, an error code otherwise + */ +int16_t sensirion_i2c_write_cmd_with_args(uint8_t address, uint16_t command, + const uint16_t* data_words, + uint16_t num_words); + +/** + * sensirion_i2c_delayed_read_cmd() - send a command, wait for the sensor to + * process and read data back + * @address: Sensor i2c address + * @cmd: Command + * @delay: Time in microseconds to delay sending the read request + * @data_words: Allocated buffer to store the read data + * @num_words: Data words to read (without CRC bytes) + * + * @return NO_ERROR on success, an error code otherwise + */ +int16_t sensirion_i2c_delayed_read_cmd(uint8_t address, uint16_t cmd, + uint32_t delay_us, uint16_t* data_words, + uint16_t num_words); +/** + * sensirion_i2c_read_cmd() - reads data words from the sensor after a command + * is issued + * @address: Sensor i2c address + * @cmd: Command + * @data_words: Allocated buffer to store the read data + * @num_words: Data words to read (without CRC bytes) + * + * @return NO_ERROR on success, an error code otherwise + */ +int16_t sensirion_i2c_read_cmd(uint8_t address, uint16_t cmd, + uint16_t* data_words, uint16_t num_words); + +/** + * sensirion_i2c_add_command_to_buffer() - Add a command to the buffer at + * offset. Adds 2 bytes to the buffer. + * + * @param buffer Pointer to buffer in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param command Command to be written into the buffer. + * + * @return Offset of next free byte in the buffer after writing the data. + */ +uint16_t sensirion_i2c_add_command_to_buffer(uint8_t* buffer, uint16_t offset, + uint16_t command); + +/** + * sensirion_i2c_add_uint32_t_to_buffer() - Add a uint32_t to the buffer at + * offset. Adds 6 bytes to the buffer. + * + * @param buffer Pointer to buffer in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param data uint32_t to be written into the buffer. + * + * @return Offset of next free byte in the buffer after writing the data. + */ +uint16_t sensirion_i2c_add_uint32_t_to_buffer(uint8_t* buffer, uint16_t offset, + uint32_t data); + +/** + * sensirion_i2c_add_int32_t_to_buffer() - Add a int32_t to the buffer at + * offset. Adds 6 bytes to the buffer. + * + * @param buffer Pointer to buffer in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param data int32_t to be written into the buffer. + * + * @return Offset of next free byte in the buffer after writing the data. + */ +uint16_t sensirion_i2c_add_int32_t_to_buffer(uint8_t* buffer, uint16_t offset, + int32_t data); + +/** + * sensirion_i2c_add_uint16_t_to_buffer() - Add a uint16_t to the buffer at + * offset. Adds 3 bytes to the buffer. + * + * @param buffer Pointer to buffer in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param data uint16_t to be written into the buffer. + * + * @return Offset of next free byte in the buffer after writing the data. + */ +uint16_t sensirion_i2c_add_uint16_t_to_buffer(uint8_t* buffer, uint16_t offset, + uint16_t data); + +/** + * sensirion_i2c_add_int16_t_to_buffer() - Add a int16_t to the buffer at + * offset. Adds 3 bytes to the buffer. + * + * @param buffer Pointer to buffer in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param data int16_t to be written into the buffer. + * + * @return Offset of next free byte in the buffer after writing the data. + */ +uint16_t sensirion_i2c_add_int16_t_to_buffer(uint8_t* buffer, uint16_t offset, + int16_t data); + +/** + * sensirion_i2c_add_float_to_buffer() - Add a float to the buffer at offset. + * Adds 6 bytes to the buffer. + * + * @param buffer Pointer to buffer in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param data float to be written into the buffer. + * + * @return Offset of next free byte in the buffer after writing the data. + */ +uint16_t sensirion_i2c_add_float_to_buffer(uint8_t* buffer, uint16_t offset, + float data); + +/** + * sensirion_i2c_add_bytes_to_buffer() - Add a byte array to the buffer at + * offset. + * + * @param buffer Pointer to buffer in which the write frame will be + * prepared. Caller needs to make sure that there is + * enough space after offset left to write the data + * into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param data Pointer to data to be written into the buffer. + * @param data_length Number of bytes to be written into the buffer. Needs to + * be a multiple of SENSIRION_WORD_SIZE otherwise the + * function returns BYTE_NUM_ERROR. + * + * @return Offset of next free byte in the buffer after writing the + * data. + */ +uint16_t sensirion_i2c_add_bytes_to_buffer(uint8_t* buffer, uint16_t offset, + const uint8_t* data, + uint16_t data_length); + +/** + * sensirion_i2c_write_data() - Writes data to the Sensor. + * + * @note This is just a wrapper for sensirion_i2c_hal_write() to + * not need to include the HAL in the drivers. + * + * @param address I2C address to write to. + * @param data Pointer to the buffer containing the data to write. + * @param data_length Number of bytes to send to the Sensor. + * + * @return NO_ERROR on success, error code otherwise + */ +int16_t sensirion_i2c_write_data(uint8_t address, const uint8_t* data, + uint16_t data_length); + +/** + * sensirion_i2c_read_data_inplace() - Reads data from the Sensor. + * + * @param address Sensor I2C address + * @param buffer Allocated buffer to store data as bytes. Needs + * to be big enough to store the data including + * CRC. Twice the size of data should always + * suffice. + * @param expected_data_length Number of bytes to read (without CRC). Needs + * to be a multiple of SENSIRION_WORD_SIZE, + * otherwise the function returns BYTE_NUM_ERROR. + * + * @return NO_ERROR on success, an error code otherwise + */ +int16_t sensirion_i2c_read_data_inplace(uint8_t address, uint8_t* buffer, + uint16_t expected_data_length); +#ifdef __cplusplus +} +#endif + +#endif /* SENSIRION_I2C_H */ diff --git a/drivers/sen5x/include/sensirion_i2c_hal.h b/drivers/sen5x/include/sensirion_i2c_hal.h new file mode 100644 index 0000000000..b41cc1101d --- /dev/null +++ b/drivers/sen5x/include/sensirion_i2c_hal.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018, 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 SENSIRION_I2C_HAL_H +#define SENSIRION_I2C_HAL_H + +#include "sensirion_config.h" +#include "sen5x_params.h" +#include "xtimer.h" +#include "periph/i2c.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Initialize all hard- and software components that are needed for the I2C + * communication. + */ +void sensirion_i2c_hal_init(void); + +/** + * Release all resources initialized by sensirion_i2c_hal_init(). + */ +void sensirion_i2c_hal_free(void); + +/** + * Execute one read transaction on the I2C bus, reading a given number of bytes. + * If the device does not acknowledge the read command, an error shall be + * returned. + * + * @param address 7-bit I2C address to read from + * @param data pointer to the buffer where the data is to be stored + * @param count number of bytes to read from I2C and store in the buffer + * @returns 0 on success, error code otherwise + */ +int8_t sensirion_i2c_hal_read(uint8_t address, uint8_t* data, uint16_t count); + +/** + * Execute one write transaction on the I2C bus, sending a given number of + * bytes. The bytes in the supplied buffer must be sent to the given address. If + * the slave device does not acknowledge any of the bytes, an error shall be + * returned. + * + * @param address 7-bit I2C address to write to + * @param data pointer to the buffer containing the data to write + * @param count number of bytes to read from the buffer and send over I2C + * @returns 0 on success, error code otherwise + */ +int8_t sensirion_i2c_hal_write(uint8_t address, const uint8_t* data, + uint16_t count); + +/** + * Sleep for a given number of microseconds. The function should delay the + * execution approximately, but no less than, the given time. + * + * When using hardware i2c: + * Despite the unit, a <10 millisecond precision is sufficient. + * + * When using software i2c: + * The precision needed depends on the desired i2c frequency, i.e. should be + * exact to about half a clock cycle (defined in + * `SENSIRION_I2C_CLOCK_PERIOD_USEC` in `sensirion_sw_i2c_gpio.h`). + * + * Example with 400kHz requires a precision of 1 / (2 * 400kHz) == 1.25usec. + * + * @param useconds the sleep time in microseconds + */ +void sensirion_i2c_hal_sleep_usec(uint32_t useconds); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SENSIRION_I2C_HAL_H */ diff --git a/drivers/sen5x/sen5x.c b/drivers/sen5x/sen5x.c new file mode 100644 index 0000000000..ca32a872f3 --- /dev/null +++ b/drivers/sen5x/sen5x.c @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2023 TU Braunschweig Institut für Betriebssysteme und Rechnerverbund + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_sen5x + * @{ + * + * @file + * @brief Device driver implementation for the Sensirion Embedded I2C SEN5x Driver + * + * @author Daniel Prigoshij + * + * @} + */ + +#include "sen5x.h" +#include "sen5x_constants.h" +#include "sen5x_params.h" +#include "sensirion_i2c_hal.h" +#include "sen5x_i2c.h" + +void sen5x_init(sen5x_t *dev, const sen5x_params_t *params) +{ + /* check parameters */ + assert(dev && params); + + dev->params = *params; + + i2c_init(dev->params.i2c_dev); + sen5x_reset(dev); +} + +void sen5x_reset(const sen5x_t *dev) +{ + assert(dev); + i2c_acquire(dev->params.i2c_dev); + + sen5x_device_reset(); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_wake(const sen5x_t *dev) +{ + assert(dev); + i2c_acquire(dev->params.i2c_dev); + + sen5x_start_measurement(); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_sleep(const sen5x_t *dev) +{ + assert(dev); + i2c_acquire(dev->params.i2c_dev); + + sen5x_stop_measurement(); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_clean_fan(const sen5x_t *dev) +{ + assert(dev); + i2c_acquire(dev->params.i2c_dev); + + sen5x_start_fan_cleaning(); + + i2c_release(dev->params.i2c_dev); +} + +bool sen5x_data_ready_flag(const sen5x_t *dev) +{ + assert(dev && status); + i2c_acquire(dev->params.i2c_dev); + + bool* status; + sen5x_read_data_ready(&status); + + i2c_release(dev->params.i2c_dev); + return status; +} + +void sen5x_read_values(sen5x_t *dev ,sen5x_measurement_t *values) +{ + assert(dev && values); + i2c_acquire(dev->params.i2c_dev); + + sen5x_read_measured_values( + &values->mass_concentration_pm1p0, &values->mass_concentration_pm2p5, + &values->mass_concentration_pm4p0, &values->mass_concentration_pm10p0, + &values->ambient_humidity, &values->ambient_temperature, &values->voc_index, + &values->nox_index); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_read_pm_values(const sen5x_t *dev, sen5x_measurement_t *values) +{ + assert(dev && values); + i2c_acquire(dev->params.i2c_dev); + + sen5x_read_measured_pm_values( + &values->mass_concentration_pm1p0, &values->mass_concentration_pm2p5, + &values->mass_concentration_pm4p0, &values->mass_concentration_pm10p0, + &values->number_concentration_pm0p5, &values->number_concentration_pm1p0, + &values->number_concentration_pm2p5, &values->number_concentration_pm4p0, + &values->number_concentration_pm10p0, &values->typical_particle_size); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_set_temperature_offset(const sen5x_t *dev, int16_t temp_offset, int16_t slope, uint16_t time_constant) +{ + assert(dev); + i2c_acquire(dev->params.i2c_dev); + + sen5x_set_temperature_offset_parameters(temp_offset, slope, time_constant); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_get_temperature_offset(const sen5x_t *dev, int16_t *temp_offset, int16_t *slope, uint16_t *time_constant) +{ + assert(dev && temp_offset && slope && time_constant); + i2c_acquire(dev->params.i2c_dev); + + sen5x_get_temperature_offset_parameters(temp_offset, slope, time_constant); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_set_warm_start(const sen5x_t *dev, uint16_t warm_start) +{ + assert(dev); + i2c_acquire(dev->params.i2c_dev); + + sen5x_set_warm_start_parameter(warm_start); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_get_warm_start(const sen5x_t *dev, uint16_t *warm_start) +{ + assert(dev && warm_start); + i2c_acquire(dev->params.i2c_dev); + + sen5x_get_warm_start_parameter(warm_start); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_set_voc_algorithm_tuning( + const sen5x_t *dev, int16_t index_offset, int16_t learning_time_offset_hours, + int16_t learning_time_gain_hours, int16_t gating_max_duration_minutes, + int16_t std_initial, int16_t gain_factor) +{ + assert(dev); + i2c_acquire(dev->params.i2c_dev); + + sen5x_set_voc_algorithm_tuning_parameters( + index_offset, learning_time_offset_hours, + learning_time_gain_hours, gating_max_duration_minutes, + std_initial, gain_factor); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_get_voc_algorithm_tuning( + const sen5x_t *dev, int16_t *index_offset, int16_t *learning_time_offset_hours, + int16_t *learning_time_gain_hours, int16_t *gating_max_duration_minutes, + int16_t *std_initial, int16_t *gain_factor) +{ + assert(dev && index_offset && learning_time_offset_hours && learning_time_gain_hours + && gating_max_duration_minutes && std_initial && gain_factor); + i2c_acquire(dev->params.i2c_dev); + + sen5x_get_voc_algorithm_tuning_parameters( + index_offset, learning_time_offset_hours, + learning_time_gain_hours, gating_max_duration_minutes, + std_initial, gain_factor); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_set_nox_algorithm_tuning( + const sen5x_t *dev, int16_t index_offset, int16_t learning_time_offset_hours, + int16_t learning_time_gain_hours, int16_t gating_max_duration_minutes, + int16_t std_initial, int16_t gain_factor) +{ + assert(dev); + i2c_acquire(dev->params.i2c_dev); + + sen5x_set_nox_algorithm_tuning_parameters( + index_offset, learning_time_offset_hours, + learning_time_gain_hours, gating_max_duration_minutes, + std_initial, gain_factor); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_get_nox_algorithm_tuning( + const sen5x_t *dev, int16_t *index_offset, int16_t *learning_time_offset_hours, + int16_t *learning_time_gain_hours, int16_t *gating_max_duration_minutes, + int16_t *std_initial, int16_t *gain_factor) +{ + assert(dev && index_offset && learning_time_offset_hours && learning_time_gain_hours + && gating_max_duration_minutes && std_initial && gain_factor); + i2c_acquire(dev->params.i2c_dev); + + sen5x_get_nox_algorithm_tuning_parameters( + index_offset, learning_time_offset_hours, + learning_time_gain_hours, gating_max_duration_minutes, + std_initial, gain_factor); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_set_rht_acceleration(const sen5x_t *dev, uint16_t mode) +{ + assert(dev); + i2c_acquire(dev->params.i2c_dev); + + sen5x_set_rht_acceleration_mode(mode); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_get_rht_acceleration(const sen5x_t *dev, uint16_t *mode) +{ + assert(dev && mode); + i2c_acquire(dev->params.i2c_dev); + + sen5x_get_rht_acceleration_mode(mode); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_set_voc_state(const sen5x_t *dev, const uint8_t *state, uint8_t state_size) +{ + assert(dev && state); + i2c_acquire(dev->params.i2c_dev); + + sen5x_set_voc_algorithm_state(state, state_size); + + i2c_release(dev->params.i2c_dev); +} + +void sen5x_get_voc_state(const sen5x_t *dev, uint8_t *state, uint8_t state_size) +{ + assert(dev && state); + i2c_acquire(dev->params.i2c_dev); + + sen5x_get_voc_algorithm_state(state, state_size); + + i2c_release(dev->params.i2c_dev); +} diff --git a/drivers/sen5x/sen5x_i2c.c b/drivers/sen5x/sen5x_i2c.c new file mode 100644 index 0000000000..ebf4bad30d --- /dev/null +++ b/drivers/sen5x/sen5x_i2c.c @@ -0,0 +1,690 @@ +/* + * THIS FILE IS AUTOMATICALLY GENERATED + * + * I2C-Generator: 0.3.0 + * Yaml Version: 2.1.3 + * Template Version: 0.7.0-109-gb259776 + */ +/* + * 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 "sen5x_i2c.h" +#include "sensirion_common.h" +#include "sensirion_i2c.h" +#include "sensirion_i2c_hal.h" + +#define SEN5X_I2C_ADDRESS 0x69 + +int16_t sen5x_start_measurement(void) { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x21); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(50000); + return NO_ERROR; +} + +int16_t sen5x_start_measurement_without_pm(void) { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x37); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(50000); + return NO_ERROR; +} + +int16_t sen5x_stop_measurement(void) { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x104); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(200000); + return NO_ERROR; +} + +int16_t sen5x_read_data_ready(bool* data_ready) { + int16_t error; + uint8_t buffer[3]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x202); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(20000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 2); + if (error) { + return error; + } + *data_ready = buffer[1]; + return NO_ERROR; +} + +int16_t sen5x_read_measured_values(uint16_t* mass_concentration_pm1p0, + uint16_t* mass_concentration_pm2p5, + uint16_t* mass_concentration_pm4p0, + uint16_t* mass_concentration_pm10p0, + int16_t* ambient_humidity, + int16_t* ambient_temperature, + int16_t* voc_index, int16_t* nox_index) { + int16_t error; + uint8_t buffer[24]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x3C4); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(20000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 16); + if (error) { + return error; + } + *mass_concentration_pm1p0 = sensirion_common_bytes_to_uint16_t(&buffer[0]); + *mass_concentration_pm2p5 = sensirion_common_bytes_to_uint16_t(&buffer[2]); + *mass_concentration_pm4p0 = sensirion_common_bytes_to_uint16_t(&buffer[4]); + *mass_concentration_pm10p0 = sensirion_common_bytes_to_uint16_t(&buffer[6]); + *ambient_humidity = sensirion_common_bytes_to_int16_t(&buffer[8]); + *ambient_temperature = sensirion_common_bytes_to_int16_t(&buffer[10]); + *voc_index = sensirion_common_bytes_to_int16_t(&buffer[12]); + *nox_index = sensirion_common_bytes_to_int16_t(&buffer[14]); + return NO_ERROR; +} + +int16_t sen5x_read_measured_raw_values(int16_t* raw_humidity, + int16_t* raw_temperature, + uint16_t* raw_voc, uint16_t* raw_nox) { + int16_t error; + uint8_t buffer[12]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x3D2); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(20000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 8); + if (error) { + return error; + } + *raw_humidity = sensirion_common_bytes_to_int16_t(&buffer[0]); + *raw_temperature = sensirion_common_bytes_to_int16_t(&buffer[2]); + *raw_voc = sensirion_common_bytes_to_uint16_t(&buffer[4]); + *raw_nox = sensirion_common_bytes_to_uint16_t(&buffer[6]); + return NO_ERROR; +} + +int16_t sen5x_read_measured_values_sen50(uint16_t* mass_concentration_pm1p0, + uint16_t* mass_concentration_pm2p5, + uint16_t* mass_concentration_pm4p0, + uint16_t* mass_concentration_pm10p0) { + int16_t error; + + int16_t ambient_humidity_dummy; + int16_t ambient_temperature_dummy; + int16_t voc_index_dummy; + int16_t nox_index_dummy; + + error = sen5x_read_measured_values( + mass_concentration_pm1p0, mass_concentration_pm2p5, + mass_concentration_pm4p0, mass_concentration_pm10p0, + &ambient_humidity_dummy, &ambient_temperature_dummy, &voc_index_dummy, + &nox_index_dummy); + + return error; +} + +int16_t sen5x_read_measured_pm_values( + uint16_t* mass_concentration_pm1p0, uint16_t* mass_concentration_pm2p5, + uint16_t* mass_concentration_pm4p0, uint16_t* mass_concentration_pm10p0, + uint16_t* number_concentration_pm0p5, uint16_t* number_concentration_pm1p0, + uint16_t* number_concentration_pm2p5, uint16_t* number_concentration_pm4p0, + uint16_t* number_concentration_pm10p0, uint16_t* typical_particle_size) { + int16_t error; + uint8_t buffer[30]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x413); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(20000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 20); + if (error) { + return error; + } + *mass_concentration_pm1p0 = sensirion_common_bytes_to_uint16_t(&buffer[0]); + *mass_concentration_pm2p5 = sensirion_common_bytes_to_uint16_t(&buffer[2]); + *mass_concentration_pm4p0 = sensirion_common_bytes_to_uint16_t(&buffer[4]); + *mass_concentration_pm10p0 = sensirion_common_bytes_to_uint16_t(&buffer[6]); + *number_concentration_pm0p5 = + sensirion_common_bytes_to_uint16_t(&buffer[8]); + *number_concentration_pm1p0 = + sensirion_common_bytes_to_uint16_t(&buffer[10]); + *number_concentration_pm2p5 = + sensirion_common_bytes_to_uint16_t(&buffer[12]); + *number_concentration_pm4p0 = + sensirion_common_bytes_to_uint16_t(&buffer[14]); + *number_concentration_pm10p0 = + sensirion_common_bytes_to_uint16_t(&buffer[16]); + *typical_particle_size = sensirion_common_bytes_to_uint16_t(&buffer[18]); + return NO_ERROR; +} + +int16_t sen5x_start_fan_cleaning(void) { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x5607); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(20000); + return NO_ERROR; +} + +int16_t sen5x_set_temperature_offset_parameters(int16_t temp_offset, + int16_t slope, + uint16_t time_constant) { + int16_t error; + uint8_t buffer[11]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x60B2); + + offset = + sensirion_i2c_add_int16_t_to_buffer(&buffer[0], offset, temp_offset); + offset = sensirion_i2c_add_int16_t_to_buffer(&buffer[0], offset, slope); + offset = + sensirion_i2c_add_uint16_t_to_buffer(&buffer[0], offset, time_constant); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(20000); + return NO_ERROR; +} + +int16_t sen5x_get_temperature_offset_parameters(int16_t* temp_offset, + int16_t* slope, + uint16_t* time_constant) { + int16_t error; + uint8_t buffer[9]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x60B2); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(20000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 6); + if (error) { + return error; + } + *temp_offset = sensirion_common_bytes_to_int16_t(&buffer[0]); + *slope = sensirion_common_bytes_to_int16_t(&buffer[2]); + *time_constant = sensirion_common_bytes_to_uint16_t(&buffer[4]); + return NO_ERROR; +} + +int16_t sen5x_set_warm_start_parameter(uint16_t warm_start) { + int16_t error; + uint8_t buffer[5]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x60C6); + + offset = + sensirion_i2c_add_uint16_t_to_buffer(&buffer[0], offset, warm_start); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(20000); + return NO_ERROR; +} + +int16_t sen5x_get_warm_start_parameter(uint16_t* warm_start) { + int16_t error; + uint8_t buffer[3]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x60C6); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(20000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 2); + if (error) { + return error; + } + *warm_start = sensirion_common_bytes_to_uint16_t(&buffer[0]); + return NO_ERROR; +} + +int16_t sen5x_set_voc_algorithm_tuning_parameters( + int16_t index_offset, int16_t learning_time_offset_hours, + int16_t learning_time_gain_hours, int16_t gating_max_duration_minutes, + int16_t std_initial, int16_t gain_factor) { + int16_t error; + uint8_t buffer[20]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x60D0); + + offset = + sensirion_i2c_add_int16_t_to_buffer(&buffer[0], offset, index_offset); + offset = sensirion_i2c_add_int16_t_to_buffer(&buffer[0], offset, + learning_time_offset_hours); + offset = sensirion_i2c_add_int16_t_to_buffer(&buffer[0], offset, + learning_time_gain_hours); + offset = sensirion_i2c_add_int16_t_to_buffer(&buffer[0], offset, + gating_max_duration_minutes); + offset = + sensirion_i2c_add_int16_t_to_buffer(&buffer[0], offset, std_initial); + offset = + sensirion_i2c_add_int16_t_to_buffer(&buffer[0], offset, gain_factor); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(20000); + return NO_ERROR; +} + +int16_t sen5x_get_voc_algorithm_tuning_parameters( + int16_t* index_offset, int16_t* learning_time_offset_hours, + int16_t* learning_time_gain_hours, int16_t* gating_max_duration_minutes, + int16_t* std_initial, int16_t* gain_factor) { + int16_t error; + uint8_t buffer[18]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x60D0); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(20000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 12); + if (error) { + return error; + } + *index_offset = sensirion_common_bytes_to_int16_t(&buffer[0]); + *learning_time_offset_hours = sensirion_common_bytes_to_int16_t(&buffer[2]); + *learning_time_gain_hours = sensirion_common_bytes_to_int16_t(&buffer[4]); + *gating_max_duration_minutes = + sensirion_common_bytes_to_int16_t(&buffer[6]); + *std_initial = sensirion_common_bytes_to_int16_t(&buffer[8]); + *gain_factor = sensirion_common_bytes_to_int16_t(&buffer[10]); + return NO_ERROR; +} + +int16_t sen5x_set_nox_algorithm_tuning_parameters( + int16_t index_offset, int16_t learning_time_offset_hours, + int16_t learning_time_gain_hours, int16_t gating_max_duration_minutes, + int16_t std_initial, int16_t gain_factor) { + int16_t error; + uint8_t buffer[20]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x60E1); + + offset = + sensirion_i2c_add_int16_t_to_buffer(&buffer[0], offset, index_offset); + offset = sensirion_i2c_add_int16_t_to_buffer(&buffer[0], offset, + learning_time_offset_hours); + offset = sensirion_i2c_add_int16_t_to_buffer(&buffer[0], offset, + learning_time_gain_hours); + offset = sensirion_i2c_add_int16_t_to_buffer(&buffer[0], offset, + gating_max_duration_minutes); + offset = + sensirion_i2c_add_int16_t_to_buffer(&buffer[0], offset, std_initial); + offset = + sensirion_i2c_add_int16_t_to_buffer(&buffer[0], offset, gain_factor); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(20000); + return NO_ERROR; +} + +int16_t sen5x_get_nox_algorithm_tuning_parameters( + int16_t* index_offset, int16_t* learning_time_offset_hours, + int16_t* learning_time_gain_hours, int16_t* gating_max_duration_minutes, + int16_t* std_initial, int16_t* gain_factor) { + int16_t error; + uint8_t buffer[18]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x60E1); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(20000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 12); + if (error) { + return error; + } + *index_offset = sensirion_common_bytes_to_int16_t(&buffer[0]); + *learning_time_offset_hours = sensirion_common_bytes_to_int16_t(&buffer[2]); + *learning_time_gain_hours = sensirion_common_bytes_to_int16_t(&buffer[4]); + *gating_max_duration_minutes = + sensirion_common_bytes_to_int16_t(&buffer[6]); + *std_initial = sensirion_common_bytes_to_int16_t(&buffer[8]); + *gain_factor = sensirion_common_bytes_to_int16_t(&buffer[10]); + return NO_ERROR; +} + +int16_t sen5x_set_rht_acceleration_mode(uint16_t mode) { + int16_t error; + uint8_t buffer[5]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x60F7); + + offset = sensirion_i2c_add_uint16_t_to_buffer(&buffer[0], offset, mode); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(20000); + return NO_ERROR; +} + +int16_t sen5x_get_rht_acceleration_mode(uint16_t* mode) { + int16_t error; + uint8_t buffer[3]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x60F7); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(20000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 2); + if (error) { + return error; + } + *mode = sensirion_common_bytes_to_uint16_t(&buffer[0]); + return NO_ERROR; +} + +int16_t sen5x_set_voc_algorithm_state(const uint8_t* state, + uint8_t state_size) { + int16_t error; + uint8_t buffer[14]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x6181); + + offset = sensirion_i2c_add_bytes_to_buffer(&buffer[0], offset, state, + state_size); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(20000); + return NO_ERROR; +} + +int16_t sen5x_get_voc_algorithm_state(uint8_t* state, uint8_t state_size) { + int16_t error; + uint8_t buffer[12]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x6181); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(20000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 8); + if (error) { + return error; + } + sensirion_common_copy_bytes(&buffer[0], state, state_size); + return NO_ERROR; +} + +int16_t sen5x_set_fan_auto_cleaning_interval(uint32_t interval) { + int16_t error; + uint8_t buffer[8]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x8004); + + offset = sensirion_i2c_add_uint32_t_to_buffer(&buffer[0], offset, interval); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(20000); + return NO_ERROR; +} + +int16_t sen5x_get_fan_auto_cleaning_interval(uint32_t* interval) { + int16_t error; + uint8_t buffer[6]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x8004); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(20000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 4); + if (error) { + return error; + } + *interval = sensirion_common_bytes_to_uint32_t(&buffer[0]); + return NO_ERROR; +} + +int16_t sen5x_get_product_name(unsigned char* product_name, + uint8_t product_name_size) { + int16_t error; + uint8_t buffer[48]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0xD014); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(50000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 32); + if (error) { + return error; + } + sensirion_common_copy_bytes(&buffer[0], product_name, product_name_size); + return NO_ERROR; +} + +int16_t sen5x_get_serial_number(unsigned char* serial_number, + uint8_t serial_number_size) { + int16_t error; + uint8_t buffer[48]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0xD033); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(50000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 32); + if (error) { + return error; + } + sensirion_common_copy_bytes(&buffer[0], serial_number, serial_number_size); + return NO_ERROR; +} + +int16_t sen5x_get_version(uint8_t* firmware_major, uint8_t* firmware_minor, + bool* firmware_debug, uint8_t* hardware_major, + uint8_t* hardware_minor, uint8_t* protocol_major, + uint8_t* protocol_minor) { + int16_t error; + uint8_t buffer[12]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0xD100); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(20000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 8); + if (error) { + return error; + } + *firmware_major = buffer[0]; + *firmware_minor = buffer[1]; + *firmware_debug = buffer[2]; + *hardware_major = buffer[3]; + *hardware_minor = buffer[4]; + *protocol_major = buffer[5]; + *protocol_minor = buffer[6]; + return NO_ERROR; +} + +int16_t sen5x_read_device_status(uint32_t* device_status) { + int16_t error; + uint8_t buffer[6]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0xD206); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(20000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 4); + if (error) { + return error; + } + *device_status = sensirion_common_bytes_to_uint32_t(&buffer[0]); + return NO_ERROR; +} + +int16_t sen5x_read_and_clear_device_status(uint32_t* device_status) { + int16_t error; + uint8_t buffer[6]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0xD210); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(20000); + + error = sensirion_i2c_read_data_inplace(SEN5X_I2C_ADDRESS, &buffer[0], 4); + if (error) { + return error; + } + *device_status = sensirion_common_bytes_to_uint32_t(&buffer[0]); + return NO_ERROR; +} + +int16_t sen5x_device_reset(void) { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0xD304); + + error = sensirion_i2c_write_data(SEN5X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(200000); + return NO_ERROR; +} diff --git a/drivers/sen5x/sen5x_saul.c b/drivers/sen5x/sen5x_saul.c new file mode 100644 index 0000000000..44fbb36baf --- /dev/null +++ b/drivers/sen5x/sen5x_saul.c @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2023 TU Braunschweig Institut für Betriebssysteme und Rechnerverbund + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +/** + * @ingroup drivers_sen5x + * @{ + * + * @file + * @brief SAUL adaptation for SEN50/54/55 devices. + * + * @author Daniel Prigoshij + * + * @} + */ + +#include "saul.h" +#include "sen5x.h" +#include "sen5x_params.h" + +static int read_mass_concentration_pm1p0(const void *dev, phydat_t *res) { + sen5x_t *d = (sen5x_t *)dev; + + sen5x_read_pm_values(d, &d->values); + + res->val[0] = (int16_t)d->values.mass_concentration_pm1p0; + res->unit = UNIT_GPM3; + res->scale = -5; + + return 1; +} + +static int read_mass_concentration_pm2p5(const void *dev, phydat_t *res) { + sen5x_t *d = (sen5x_t *)dev; + + sen5x_read_pm_values(d, &d->values); + + res->val[0] = (int16_t)d->values.mass_concentration_pm2p5; + res->unit = UNIT_GPM3; + res->scale = -5; + + return 1; +} + +static int read_mass_concentration_pm4p0(const void *dev, phydat_t *res) { + sen5x_t *d = (sen5x_t *)dev; + + sen5x_read_pm_values(d, &d->values); + + res->val[0] = (int16_t)d->values.mass_concentration_pm4p0; + res->unit = UNIT_GPM3; + res->scale = -5; + + return 1; +} + +static int read_mass_concentration_pm10p0(const void *dev, phydat_t *res) { + sen5x_t *d = (sen5x_t *)dev; + + sen5x_read_pm_values(d, &d->values); + + res->val[0] = (int16_t)d->values.mass_concentration_pm10p0; + res->unit = UNIT_GPM3; + res->scale = -5; + + return 1; +} + +static int read_number_concentration_pm0p5(const void *dev, phydat_t *res) { + sen5x_t *d = (sen5x_t *)dev; + + sen5x_read_pm_values(d, &d->values); + + res->val[0] = (int16_t)d->values.number_concentration_pm0p5; + res->unit = UNIT_CPM3; + res->scale = -5; + + return 1; +} + +static int read_number_concentration_pm1p0(const void *dev, phydat_t *res) { + sen5x_t *d = (sen5x_t *)dev; + + sen5x_read_pm_values(d, &d->values); + + res->val[0] = (int16_t)d->values.number_concentration_pm1p0; + res->unit = UNIT_CPM3; + res->scale = -5; + return 1; +} + +static int read_number_concentration_pm2p5(const void *dev, phydat_t *res) { + sen5x_t *d = (sen5x_t *)dev; + + sen5x_read_pm_values(d, &d->values); + + res->val[0] = (int16_t)d->values.number_concentration_pm2p5; + res->unit = UNIT_CPM3; + res->scale = -5; + return 1; +} + +static int read_number_concentration_pm4p0(const void *dev, phydat_t *res) { + sen5x_t *d = (sen5x_t *)dev; + + sen5x_read_pm_values(d, &d->values); + + res->val[0] = (int16_t)d->values.number_concentration_pm4p0; + res->unit = UNIT_CPM3; + res->scale = -5; + + return 1; +} + +static int read_number_concentration_pm10p0(const void *dev, phydat_t *res) { + sen5x_t *d = (sen5x_t *)dev; + + sen5x_read_pm_values(d, &d->values); + + res->val[0] = (int16_t)d->values.number_concentration_pm10p0; + res->unit = UNIT_CPM3; + res->scale = -5; + + return 1; +} + +static int read_typical_particle_size(const void *dev, phydat_t *res) { + sen5x_t *d = (sen5x_t *)dev; + + sen5x_read_pm_values(d, &d->values); + + res->val[0] = (int16_t)d->values.typical_particle_size; + res->unit = UNIT_M; + res->scale = -3; + + return 1; +} + +static int read_ambient_humidity(const void *dev, phydat_t *res) { + sen5x_t *d = (sen5x_t *)dev; + + sen5x_read_pm_values(d, &d->values); + + res->val[0] = (int16_t)d->values.ambient_humidity; + res->unit = UNIT_PERCENT; + res->scale = 2; + + return 1; +} + +static int read_ambient_temperature(const void *dev, phydat_t *res) { + sen5x_t *d = (sen5x_t *)dev; + + sen5x_read_pm_values(d, &d->values); + + res->val[0] = ((int16_t)d->values.ambient_temperature) / 2; + res->unit = UNIT_TEMP_C; + res->scale = 2; + + return 1; +} + + +const saul_driver_t sen5x_mass_concentration_pm1p0_driver = { + .read = read_mass_concentration_pm1p0, + .write = saul_write_notsup, + .type = SAUL_SENSE_PM +}; + +const saul_driver_t sen5x_mass_concentration_pm2p5_driver = { + .read = read_mass_concentration_pm2p5, + .write = saul_write_notsup, + .type = SAUL_SENSE_PM +}; + +const saul_driver_t sen5x_mass_concentration_pm4p0_driver = { + .read = read_mass_concentration_pm4p0, + .write = saul_write_notsup, + .type = SAUL_SENSE_PM +}; + +const saul_driver_t sen5x_mass_concentration_pm10p0_driver = { + .read = read_mass_concentration_pm10p0, + .write = saul_write_notsup, + .type = SAUL_SENSE_PM +}; + +const saul_driver_t sen5x_number_concentration_pm0p5_driver = { + .read = read_number_concentration_pm0p5, + .write = saul_write_notsup, + .type = SAUL_SENSE_COUNT +}; + +const saul_driver_t sen5x_number_concentration_pm1p0_driver = { + .read = read_number_concentration_pm1p0, + .write = saul_write_notsup, + .type = SAUL_SENSE_COUNT +}; + +const saul_driver_t sen5x_number_concentration_pm2p5_driver = { + .read = read_number_concentration_pm2p5, + .write = saul_write_notsup, + .type = SAUL_SENSE_COUNT +}; + +const saul_driver_t sen5x_number_concentration_pm4p0_driver = { + .read = read_number_concentration_pm4p0, + .write = saul_write_notsup, + .type = SAUL_SENSE_COUNT +}; + +const saul_driver_t sen5x_number_concentration_pm10p0_driver = { + .read = read_number_concentration_pm10p0, + .write = saul_write_notsup, + .type = SAUL_SENSE_COUNT +}; + +const saul_driver_t sen5x_typical_particle_size_driver = { + .read = read_typical_particle_size, + .write = saul_write_notsup, + .type = SAUL_SENSE_SIZE +}; + +const saul_driver_t sen5x_ambient_humidity_driver = { + .read = read_ambient_humidity, + .write = saul_write_notsup, + .type = SAUL_SENSE_HUM +}; + +const saul_driver_t sen5x_ambient_temperature_driver = { + .read = read_ambient_temperature, + .write = saul_write_notsup, + .type = SAUL_SENSE_TEMP +}; \ No newline at end of file diff --git a/drivers/sen5x/sensirion_common.c b/drivers/sen5x/sensirion_common.c new file mode 100644 index 0000000000..4ee7a96b64 --- /dev/null +++ b/drivers/sen5x/sensirion_common.c @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2018, 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_common.h" +#include "sensirion_config.h" + +uint16_t sensirion_common_bytes_to_uint16_t(const uint8_t* bytes) { + return (uint16_t)bytes[0] << 8 | (uint16_t)bytes[1]; +} + +uint32_t sensirion_common_bytes_to_uint32_t(const uint8_t* bytes) { + return (uint32_t)bytes[0] << 24 | (uint32_t)bytes[1] << 16 | + (uint32_t)bytes[2] << 8 | (uint32_t)bytes[3]; +} + +int16_t sensirion_common_bytes_to_int16_t(const uint8_t* bytes) { + return (int16_t)sensirion_common_bytes_to_uint16_t(bytes); +} + +int32_t sensirion_common_bytes_to_int32_t(const uint8_t* bytes) { + return (int32_t)sensirion_common_bytes_to_uint32_t(bytes); +} + +float sensirion_common_bytes_to_float(const uint8_t* bytes) { + union { + uint32_t u32_value; + float float32; + } tmp; + + tmp.u32_value = sensirion_common_bytes_to_uint32_t(bytes); + return tmp.float32; +} + +void sensirion_common_uint32_t_to_bytes(const uint32_t value, uint8_t* bytes) { + bytes[0] = value >> 24; + bytes[1] = value >> 16; + bytes[2] = value >> 8; + bytes[3] = value; +} + +void sensirion_common_uint16_t_to_bytes(const uint16_t value, uint8_t* bytes) { + bytes[0] = value >> 8; + bytes[1] = value; +} + +void sensirion_common_int32_t_to_bytes(const int32_t value, uint8_t* bytes) { + bytes[0] = value >> 24; + bytes[1] = value >> 16; + bytes[2] = value >> 8; + bytes[3] = value; +} + +void sensirion_common_int16_t_to_bytes(const int16_t value, uint8_t* bytes) { + bytes[0] = value >> 8; + bytes[1] = value; +} + +void sensirion_common_float_to_bytes(const float value, uint8_t* bytes) { + union { + uint32_t u32_value; + float float32; + } tmp; + tmp.float32 = value; + sensirion_common_uint32_t_to_bytes(tmp.u32_value, bytes); +} + +void sensirion_common_copy_bytes(const uint8_t* source, uint8_t* destination, + uint16_t data_length) { + uint16_t i; + for (i = 0; i < data_length; i++) { + destination[i] = source[i]; + } +} diff --git a/drivers/sen5x/sensirion_i2c.c b/drivers/sen5x/sensirion_i2c.c new file mode 100644 index 0000000000..cb10c470ff --- /dev/null +++ b/drivers/sen5x/sensirion_i2c.c @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2018, 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_i2c.h" +#include "sensirion_common.h" +#include "sensirion_config.h" +#include "sensirion_i2c_hal.h" + +uint8_t sensirion_i2c_generate_crc(const uint8_t* data, uint16_t count) { + uint16_t current_byte; + uint8_t crc = CRC8_INIT; + uint8_t crc_bit; + + /* calculates 8-Bit checksum with given polynomial */ + for (current_byte = 0; current_byte < count; ++current_byte) { + crc ^= (data[current_byte]); + for (crc_bit = 8; crc_bit > 0; --crc_bit) { + if (crc & 0x80) + crc = (crc << 1) ^ CRC8_POLYNOMIAL; + else + crc = (crc << 1); + } + } + return crc; +} + +int8_t sensirion_i2c_check_crc(const uint8_t* data, uint16_t count, + uint8_t checksum) { + if (sensirion_i2c_generate_crc(data, count) != checksum) + return CRC_ERROR; + return NO_ERROR; +} + +int16_t sensirion_i2c_general_call_reset(void) { + const uint8_t data = 0x06; + return sensirion_i2c_hal_write(0, &data, (uint16_t)sizeof(data)); +} + +uint16_t sensirion_i2c_fill_cmd_send_buf(uint8_t* buf, uint16_t cmd, + const uint16_t* args, + uint8_t num_args) { + uint8_t i; + uint16_t idx = 0; + + buf[idx++] = (uint8_t)((cmd & 0xFF00) >> 8); + buf[idx++] = (uint8_t)((cmd & 0x00FF) >> 0); + + for (i = 0; i < num_args; ++i) { + buf[idx++] = (uint8_t)((args[i] & 0xFF00) >> 8); + buf[idx++] = (uint8_t)((args[i] & 0x00FF) >> 0); + + uint8_t crc = sensirion_i2c_generate_crc((uint8_t*)&buf[idx - 2], + SENSIRION_WORD_SIZE); + buf[idx++] = crc; + } + return idx; +} + +int16_t sensirion_i2c_read_words_as_bytes(uint8_t address, uint8_t* data, + uint16_t num_words) { + int16_t ret; + uint16_t i, j; + uint16_t size = num_words * (SENSIRION_WORD_SIZE + CRC8_LEN); + uint16_t word_buf[SENSIRION_MAX_BUFFER_WORDS]; + uint8_t* const buf8 = (uint8_t*)word_buf; + + ret = sensirion_i2c_hal_read(address, buf8, size); + if (ret != NO_ERROR) + return ret; + + /* check the CRC for each word */ + for (i = 0, j = 0; i < size; i += SENSIRION_WORD_SIZE + CRC8_LEN) { + + ret = sensirion_i2c_check_crc(&buf8[i], SENSIRION_WORD_SIZE, + buf8[i + SENSIRION_WORD_SIZE]); + if (ret != NO_ERROR) + return ret; + + data[j++] = buf8[i]; + data[j++] = buf8[i + 1]; + } + + return NO_ERROR; +} + +int16_t sensirion_i2c_read_words(uint8_t address, uint16_t* data_words, + uint16_t num_words) { + int16_t ret; + uint8_t i; + + ret = sensirion_i2c_read_words_as_bytes(address, (uint8_t*)data_words, + num_words); + if (ret != NO_ERROR) + return ret; + + for (i = 0; i < num_words; ++i) { + const uint8_t* word_bytes = (uint8_t*)&data_words[i]; + data_words[i] = ((uint16_t)word_bytes[0] << 8) | word_bytes[1]; + } + + return NO_ERROR; +} + +int16_t sensirion_i2c_write_cmd(uint8_t address, uint16_t command) { + uint8_t buf[SENSIRION_COMMAND_SIZE]; + + sensirion_i2c_fill_cmd_send_buf(buf, command, NULL, 0); + return sensirion_i2c_hal_write(address, buf, SENSIRION_COMMAND_SIZE); +} + +int16_t sensirion_i2c_write_cmd_with_args(uint8_t address, uint16_t command, + const uint16_t* data_words, + uint16_t num_words) { + uint8_t buf[SENSIRION_MAX_BUFFER_WORDS]; + uint16_t buf_size; + + buf_size = + sensirion_i2c_fill_cmd_send_buf(buf, command, data_words, num_words); + return sensirion_i2c_hal_write(address, buf, buf_size); +} + +int16_t sensirion_i2c_delayed_read_cmd(uint8_t address, uint16_t cmd, + uint32_t delay_us, uint16_t* data_words, + uint16_t num_words) { + int16_t ret; + uint8_t buf[SENSIRION_COMMAND_SIZE]; + + sensirion_i2c_fill_cmd_send_buf(buf, cmd, NULL, 0); + ret = sensirion_i2c_hal_write(address, buf, SENSIRION_COMMAND_SIZE); + if (ret != NO_ERROR) + return ret; + + if (delay_us) + sensirion_i2c_hal_sleep_usec(delay_us); + + return sensirion_i2c_read_words(address, data_words, num_words); +} + +int16_t sensirion_i2c_read_cmd(uint8_t address, uint16_t cmd, + uint16_t* data_words, uint16_t num_words) { + return sensirion_i2c_delayed_read_cmd(address, cmd, 0, data_words, + num_words); +} + +uint16_t sensirion_i2c_add_command_to_buffer(uint8_t* buffer, uint16_t offset, + uint16_t command) { + buffer[offset++] = (uint8_t)((command & 0xFF00) >> 8); + buffer[offset++] = (uint8_t)((command & 0x00FF) >> 0); + return offset; +} + +uint16_t sensirion_i2c_add_uint32_t_to_buffer(uint8_t* buffer, uint16_t offset, + uint32_t data) { + buffer[offset++] = (uint8_t)((data & 0xFF000000) >> 24); + buffer[offset++] = (uint8_t)((data & 0x00FF0000) >> 16); + buffer[offset] = sensirion_i2c_generate_crc( + &buffer[offset - SENSIRION_WORD_SIZE], SENSIRION_WORD_SIZE); + offset++; + buffer[offset++] = (uint8_t)((data & 0x0000FF00) >> 8); + buffer[offset++] = (uint8_t)((data & 0x000000FF) >> 0); + buffer[offset] = sensirion_i2c_generate_crc( + &buffer[offset - SENSIRION_WORD_SIZE], SENSIRION_WORD_SIZE); + offset++; + + return offset; +} + +uint16_t sensirion_i2c_add_int32_t_to_buffer(uint8_t* buffer, uint16_t offset, + int32_t data) { + return sensirion_i2c_add_uint32_t_to_buffer(buffer, offset, (uint32_t)data); +} + +uint16_t sensirion_i2c_add_uint16_t_to_buffer(uint8_t* buffer, uint16_t offset, + uint16_t data) { + buffer[offset++] = (uint8_t)((data & 0xFF00) >> 8); + buffer[offset++] = (uint8_t)((data & 0x00FF) >> 0); + buffer[offset] = sensirion_i2c_generate_crc( + &buffer[offset - SENSIRION_WORD_SIZE], SENSIRION_WORD_SIZE); + offset++; + + return offset; +} + +uint16_t sensirion_i2c_add_int16_t_to_buffer(uint8_t* buffer, uint16_t offset, + int16_t data) { + return sensirion_i2c_add_uint16_t_to_buffer(buffer, offset, (uint16_t)data); +} + +uint16_t sensirion_i2c_add_float_to_buffer(uint8_t* buffer, uint16_t offset, + float data) { + union { + uint32_t uint32_data; + float float_data; + } convert; + + convert.float_data = data; + + buffer[offset++] = (uint8_t)((convert.uint32_data & 0xFF000000) >> 24); + buffer[offset++] = (uint8_t)((convert.uint32_data & 0x00FF0000) >> 16); + buffer[offset] = sensirion_i2c_generate_crc( + &buffer[offset - SENSIRION_WORD_SIZE], SENSIRION_WORD_SIZE); + offset++; + buffer[offset++] = (uint8_t)((convert.uint32_data & 0x0000FF00) >> 8); + buffer[offset++] = (uint8_t)((convert.uint32_data & 0x000000FF) >> 0); + buffer[offset] = sensirion_i2c_generate_crc( + &buffer[offset - SENSIRION_WORD_SIZE], SENSIRION_WORD_SIZE); + offset++; + + return offset; +} + +uint16_t sensirion_i2c_add_bytes_to_buffer(uint8_t* buffer, uint16_t offset, + const uint8_t* data, + uint16_t data_length) { + uint16_t i; + + if (data_length % SENSIRION_WORD_SIZE != 0) { + return BYTE_NUM_ERROR; + } + + for (i = 0; i < data_length; i += 2) { + buffer[offset++] = data[i]; + buffer[offset++] = data[i + 1]; + + buffer[offset] = sensirion_i2c_generate_crc( + &buffer[offset - SENSIRION_WORD_SIZE], SENSIRION_WORD_SIZE); + offset++; + } + + return offset; +} + +int16_t sensirion_i2c_write_data(uint8_t address, const uint8_t* data, + uint16_t data_length) { + return sensirion_i2c_hal_write(address, data, data_length); +} + +int16_t sensirion_i2c_read_data_inplace(uint8_t address, uint8_t* buffer, + uint16_t expected_data_length) { + int16_t error; + uint16_t i, j; + uint16_t size = (expected_data_length / SENSIRION_WORD_SIZE) * + (SENSIRION_WORD_SIZE + CRC8_LEN); + + if (expected_data_length % SENSIRION_WORD_SIZE != 0) { + return BYTE_NUM_ERROR; + } + + error = sensirion_i2c_hal_read(address, buffer, size); + if (error) { + return error; + } + + for (i = 0, j = 0; i < size; i += SENSIRION_WORD_SIZE + CRC8_LEN) { + + error = sensirion_i2c_check_crc(&buffer[i], SENSIRION_WORD_SIZE, + buffer[i + SENSIRION_WORD_SIZE]); + if (error) { + return error; + } + buffer[j++] = buffer[i]; + buffer[j++] = buffer[i + 1]; + } + + return NO_ERROR; +} diff --git a/drivers/sen5x/sensirion_i2c_hal.c b/drivers/sen5x/sensirion_i2c_hal.c new file mode 100644 index 0000000000..d4ee11e521 --- /dev/null +++ b/drivers/sen5x/sensirion_i2c_hal.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018, 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_i2c_hal.h" +#include "sensirion_common.h" +#include "sensirion_config.h" + +/** + * Initialize all hard- and software components that are needed for the I2C + * communication. + */ +void sensirion_i2c_hal_init(void) { + + // initialize the bus + i2c_init(I2C_DEVICE); + + // first, acquire the shared bus again + i2c_acquire(I2C_DEVICE); +} + +/** + * Release all resources initialized by sensirion_i2c_hal_init(). + */ +void sensirion_i2c_hal_free(void) { + i2c_release(I2C_DEVICE); +} + +/** + * Execute one read transaction on the I2C bus, reading a given number of bytes. + * If the device does not acknowledge the read command, an error shall be + * returned. + * + * @param address 7-bit I2C address to read from + * @param data pointer to the buffer where the data is to be stored + * @param count number of bytes to read from I2C and store in the buffer + * @returns 0 on success, error code otherwise + */ +int8_t sensirion_i2c_hal_read(uint8_t address, uint8_t* data, uint16_t count) { + return i2c_read_bytes(I2C_DEVICE, address, data, count, 0); +} + +/** + * Execute one write transaction on the I2C bus, sending a given number of + * bytes. The bytes in the supplied buffer must be sent to the given address. If + * the slave device does not acknowledge any of the bytes, an error shall be + * returned. + * + * @param address 7-bit I2C address to write to + * @param data pointer to the buffer containing the data to write + * @param count number of bytes to read from the buffer and send over I2C + * @returns 0 on success, error code otherwise + */ +int8_t sensirion_i2c_hal_write(uint8_t address, const uint8_t* data, uint16_t count) { + return i2c_write_bytes(I2C_DEVICE, address, data, count, 0); +} + +/** + * Sleep for a given number of microseconds. The function should delay the + * execution for at least the given time, but may also sleep longer. + * + * Despite the unit, a <10 millisecond precision is sufficient. + * + * @param useconds the sleep time in microseconds + */ +void sensirion_i2c_hal_sleep_usec(uint32_t useconds) { + xtimer_usleep(useconds); +}