From d0df1ec1e07666e2ac72f8be002539a4e468df31 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 14 Mar 2022 13:12:12 +0100 Subject: [PATCH] initial commit --- CMakeLists.txt | 2 + include/scd4x_i2c.c | 452 ++++++++++++++++++++++++++++++++++++ include/scd4x_i2c.h | 378 ++++++++++++++++++++++++++++++ include/sensirion_common.h | 181 +++++++++++++++ include/sensirion_config.h | 88 +++++++ include/sensirion_i2c.h | 314 +++++++++++++++++++++++++ include/sensirion_i2c_hal.h | 112 +++++++++ scd4x_i2c.c | 452 ++++++++++++++++++++++++++++++++++++ sensirion_common.c | 101 ++++++++ sensirion_i2c.c | 294 +++++++++++++++++++++++ sensirion_i2c_hal.c | 148 ++++++++++++ 11 files changed, 2522 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 include/scd4x_i2c.c create mode 100644 include/scd4x_i2c.h create mode 100644 include/sensirion_common.h create mode 100644 include/sensirion_config.h create mode 100644 include/sensirion_i2c.h create mode 100644 include/sensirion_i2c_hal.h create mode 100644 scd4x_i2c.c create mode 100644 sensirion_common.c create mode 100644 sensirion_i2c.c create mode 100644 sensirion_i2c_hal.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6cb99c6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "sensirion_common.c" "sensirion_i2c.c" "sensirion_i2c_hal.c" "scd4x_i2c.c" + INCLUDE_DIRS "include") diff --git a/include/scd4x_i2c.c b/include/scd4x_i2c.c new file mode 100644 index 0000000..0e175f5 --- /dev/null +++ b/include/scd4x_i2c.c @@ -0,0 +1,452 @@ +/* + * THIS FILE IS AUTOMATICALLY GENERATED AND MUST NOT BE EDITED MANUALLY! + * + * I2C-Generator: 0.2.0 + * Yaml Version: 0.1.0 + * Template Version: 0.2.1 + */ +/* + * 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 "scd4x_i2c.h" +#include "sensirion_common.h" +#include "sensirion_i2c.h" +#include "sensirion_i2c_hal.h" + +#define SCD4X_I2C_ADDRESS 98 + +int16_t scd4x_start_periodic_measurement() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x21B1); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(1000); + return NO_ERROR; +} + +int16_t scd4x_read_measurement_ticks(uint16_t* co2, uint16_t* temperature, + uint16_t* humidity) { + int16_t error; + uint8_t buffer[9]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0xEC05); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(1000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 6); + if (error) { + return error; + } + *co2 = sensirion_common_bytes_to_uint16_t(&buffer[0]); + *temperature = sensirion_common_bytes_to_uint16_t(&buffer[2]); + *humidity = sensirion_common_bytes_to_uint16_t(&buffer[4]); + return NO_ERROR; +} + +int16_t scd4x_read_measurement(uint16_t* co2, int32_t* temperature_m_deg_c, + int32_t* humidity_m_percent_rh) { + int16_t error; + uint16_t temperature; + uint16_t humidity; + + error = scd4x_read_measurement_ticks(co2, &temperature, &humidity); + if (error) { + return error; + } + *temperature_m_deg_c = ((21875 * (int32_t)temperature) >> 13) - 45000; + *humidity_m_percent_rh = ((12500 * (int32_t)humidity) >> 13); + return NO_ERROR; +} + +int16_t scd4x_stop_periodic_measurement() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x3F86); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(500000); + return NO_ERROR; +} + +int16_t scd4x_get_temperature_offset_ticks(uint16_t* t_offset) { + int16_t error; + uint8_t buffer[3]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x2318); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(1000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 2); + if (error) { + return error; + } + *t_offset = sensirion_common_bytes_to_uint16_t(&buffer[0]); + return NO_ERROR; +} + +int16_t scd4x_get_temperature_offset(int32_t* t_offset_m_deg_c) { + int16_t error; + uint16_t t_offset; + + error = scd4x_get_temperature_offset_ticks(&t_offset); + if (error) { + return error; + } + *t_offset_m_deg_c = ((21875 * (int32_t)t_offset) >> 13); + return NO_ERROR; +} + +int16_t scd4x_set_temperature_offset_ticks(uint16_t t_offset) { + int16_t error; + uint8_t buffer[5]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x241D); + + offset = sensirion_i2c_add_uint16_t_to_buffer(&buffer[0], offset, t_offset); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(1000); + return NO_ERROR; +} + +int16_t scd4x_set_temperature_offset(int32_t t_offset_m_deg_c) { + uint16_t t_offset = (uint16_t)((t_offset_m_deg_c * 12271) >> 15); + return scd4x_set_temperature_offset_ticks(t_offset); +} + +int16_t scd4x_get_sensor_altitude(uint16_t* sensor_altitude) { + int16_t error; + uint8_t buffer[3]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x2322); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(1000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 2); + if (error) { + return error; + } + *sensor_altitude = sensirion_common_bytes_to_uint16_t(&buffer[0]); + return NO_ERROR; +} + +int16_t scd4x_set_sensor_altitude(uint16_t sensor_altitude) { + int16_t error; + uint8_t buffer[5]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x2427); + + offset = sensirion_i2c_add_uint16_t_to_buffer(&buffer[0], offset, + sensor_altitude); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(1000); + return NO_ERROR; +} + +int16_t scd4x_set_ambient_pressure(uint16_t ambient_pressure) { + int16_t error; + uint8_t buffer[5]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0xE000); + + offset = sensirion_i2c_add_uint16_t_to_buffer(&buffer[0], offset, + ambient_pressure); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(1000); + return NO_ERROR; +} + +int16_t scd4x_perform_forced_recalibration(uint16_t target_co2_concentration, + uint16_t* frc_correction) { + int16_t error; + uint8_t buffer[5]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x362F); + + offset = sensirion_i2c_add_uint16_t_to_buffer(&buffer[0], offset, + target_co2_concentration); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(400000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 2); + if (error) { + return error; + } + *frc_correction = sensirion_common_bytes_to_uint16_t(&buffer[0]); + return NO_ERROR; +} + +int16_t scd4x_get_automatic_self_calibration(uint16_t* asc_enabled) { + int16_t error; + uint8_t buffer[3]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x2313); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(1000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 2); + if (error) { + return error; + } + *asc_enabled = sensirion_common_bytes_to_uint16_t(&buffer[0]); + return NO_ERROR; +} + +int16_t scd4x_set_automatic_self_calibration(uint16_t asc_enabled) { + int16_t error; + uint8_t buffer[5]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x2416); + + offset = + sensirion_i2c_add_uint16_t_to_buffer(&buffer[0], offset, asc_enabled); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(1000); + return NO_ERROR; +} + +int16_t scd4x_start_low_power_periodic_measurement() { + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x21AC); + + return sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); +} + +int16_t scd4x_get_data_ready_status(uint16_t* data_ready) { + int16_t error; + uint8_t buffer[3]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0xE4B8); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(1000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 2); + if (error) { + return error; + } + *data_ready = sensirion_common_bytes_to_uint16_t(&buffer[0]); + return NO_ERROR; +} + +int16_t scd4x_persist_settings() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x3615); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(800000); + return NO_ERROR; +} + +int16_t scd4x_get_serial_number(uint16_t* serial_0, uint16_t* serial_1, + uint16_t* serial_2) { + int16_t error; + uint8_t buffer[9]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x3682); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(1000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 6); + if (error) { + return error; + } + *serial_0 = sensirion_common_bytes_to_uint16_t(&buffer[0]); + *serial_1 = sensirion_common_bytes_to_uint16_t(&buffer[2]); + *serial_2 = sensirion_common_bytes_to_uint16_t(&buffer[4]); + return NO_ERROR; +} + +int16_t scd4x_perform_self_test(uint16_t* sensor_status) { + int16_t error; + uint8_t buffer[3]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x3639); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(10000000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 2); + if (error) { + return error; + } + *sensor_status = sensirion_common_bytes_to_uint16_t(&buffer[0]); + return NO_ERROR; +} + +int16_t scd4x_perform_factory_reset() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x3632); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(800000); + return NO_ERROR; +} + +int16_t scd4x_reinit() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x3646); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(20000); + return NO_ERROR; +} + +int16_t scd4x_measure_single_shot() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x219D); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(5000000); + return NO_ERROR; +} + +int16_t scd4x_measure_single_shot_rht_only() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x2196); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(50000); + return NO_ERROR; +} + +int16_t scd4x_power_down() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x36E0); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(1000); + return NO_ERROR; +} + +int16_t scd4x_wake_up() { + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x36F6); + + // Sensor does not acknowledge the wake-up call, error is ignored + (void)sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + sensirion_i2c_hal_sleep_usec(20000); + return NO_ERROR; +} diff --git a/include/scd4x_i2c.h b/include/scd4x_i2c.h new file mode 100644 index 0000000..05166af --- /dev/null +++ b/include/scd4x_i2c.h @@ -0,0 +1,378 @@ +/* + * THIS FILE IS AUTOMATICALLY GENERATED AND MUST NOT BE EDITED MANUALLY! + * + * I2C-Generator: 0.2.0 + * Yaml Version: 0.1.0 + * Template Version: 0.2.1 + */ +/* + * 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 SCD4X_I2C_H +#define SCD4X_I2C_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "sensirion_config.h" + +/** + * scd4x_start_periodic_measurement() - start periodic measurement, signal + * update interval is 5 seconds. + * + * @note This command is only available in idle mode. + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_start_periodic_measurement(void); + +/** + * scd4x_read_measurement_ticks() - read sensor output. The measurement data can + * only be read out once per signal update interval as the buffer is emptied + * upon read-out. If no data is available in the buffer, the sensor returns a + * NACK. To avoid a NACK response the get_data_ready_status can be issued to + * check data status. The I2C master can abort the read transfer with a NACK + * followed by a STOP condition after any data byte if the user is not + * interested in subsequent data. + * + * @note This command is only available in measurement mode. The firmware + * updates the measurement values depending on the measurement mode. + * + * @param co2 CO₂ concentration in ppm + * + * @param temperature Convert value to °C by: -45 °C + 175 °C * value/2^16 + * + * @param humidity Convert value to %RH by: 100%RH * value/2^16 + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_read_measurement_ticks(uint16_t* co2, uint16_t* temperature, + uint16_t* humidity); + +/** + * scd4x_read_measurement() - read sensor output and convert. + * See @ref scd4x_read_measurement_ticks() for more details. + * + * @note This command is only available in measurement mode. The firmware + * updates the measurement values depending on the measurement mode. + * + * @param co2 CO₂ concentration in ppm + * + * @param temperature_m_deg_c Temperature in milli degrees celsius (°C * 1000) + * + * @param humidity_m_percent_rh Relative humidity in milli percent RH + * (%RH * 1000) + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_read_measurement(uint16_t* co2, int32_t* temperature_m_deg_c, + int32_t* humidity_m_percent_rh); + +/** + * scd4x_stop_periodic_measurement() - Stop periodic measurement and return to + * idle mode for sensor configuration or to safe energy. + * + * @note This command is only available in measurement mode. + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_stop_periodic_measurement(void); + +/** + * scd4x_get_temperature_offset_ticks() - The temperature offset represents the + * difference between the measured temperature by the SCD4x and the actual + * ambient temperature. Per default, the temperature offset is set to 4°C. + * + * @note Only available in idle mode. + * + * @param t_offset Temperature offset. Convert value to °C by: 175 * value / + * 2^16 + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_get_temperature_offset_ticks(uint16_t* t_offset); + +/** + * scd4x_get_temperature_offset() - The temperature offset represents the + * difference between the measured temperature by the SCD4x and the actual + * ambient temperature. Per default, the temperature offset is set to 4°C. + * + * @note Only available in idle mode. + * + * @param t_offset_m_deg_c Temperature offset in milli degrees Celsius. + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_get_temperature_offset(int32_t* t_offset_m_deg_c); + +/** + * scd4x_set_temperature_offset_ticks() - Setting the temperature offset of the + * SCD4x inside the customer device correctly allows the user to leverage the RH + * and T output signal. Note that the temperature offset can depend on various + * factors such as the SCD4x measurement mode, self-heating of close components, + * the ambient temperature and air flow. Thus, the SCD4x temperature offset + * should be determined inside the customer device under its typical operation + * and in thermal equilibrium. + * + * @note Only available in idle mode. + * + * @param t_offset Temperature offset. Convert °C to value by: T * 2^16 / 175. + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_set_temperature_offset_ticks(uint16_t t_offset); + +/** + * scd4x_set_temperature_offset() - Setting the temperature offset of the SCD4x + * inside the customer device correctly allows the user to leverage the RH and T + * output signal. Note that the temperature offset can depend on various factors + * such as the SCD4x measurement mode, self-heating of close components, the + * ambient temperature and air flow. Thus, the SCD4x temperature offset should + * be determined inside the customer device under its typical operation and in + * thermal equilibrium. + * + * @note Only available in idle mode. + * + * @param t_offset_m_deg_c Temperature offset in milli degrees Celsius. + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_set_temperature_offset(int32_t t_offset_m_deg_c); + +/** + * scd4x_get_sensor_altitude() - Get configured sensor altitude in meters above + * sea level. Per default, the sensor altitude is set to 0 meter above + * sea-level. + * + * @note Only available in idle mode. + * + * @param sensor_altitude Sensor altitude in meters. + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_get_sensor_altitude(uint16_t* sensor_altitude); + +/** + * scd4x_set_sensor_altitude() - Set sensor altitude in meters above sea level. + * Note that setting a sensor altitude to the sensor overrides any pressure + * compensation based on a previously set ambient pressure. + * + * @note Only available in idle mode. + * + * @param sensor_altitude Sensor altitude in meters. + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_set_sensor_altitude(uint16_t sensor_altitude); + +/** + * scd4x_set_ambient_pressure() - The set_ambient_pressure command can be sent + * during periodic measurements to enable continuous pressure compensation. Note + * that setting an ambient pressure to the sensor overrides any pressure + * compensation based on a previously set sensor altitude. + * + * @note Available during measurements. + * + * @param ambient_pressure Ambient pressure in hPa. Convert value to Pa by: + * value * 100. + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_set_ambient_pressure(uint16_t ambient_pressure); + +/** + * scd4x_perform_forced_recalibration() - To successfully conduct an accurate +forced recalibration, the following steps need to be carried out: +1. Operate the SCD4x in a periodic measurement mode for > 3 minutes in an +environment with homogenous and constant CO₂ concentration. +2. Stop periodic measurement. Wait 500 ms. +3. Subsequently issue the perform_forced_recalibration command and optionally +read out the baseline correction. A return value of 0xffff indicates that the +forced recalibration failed. + * + * @param target_co2_concentration Target CO₂ concentration in ppm. + * + * @param frc_correction FRC correction value in CO₂ ppm or 0xFFFF if the +command failed. Convert value to CO₂ ppm with: value - 0x8000 + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_perform_forced_recalibration(uint16_t target_co2_concentration, + uint16_t* frc_correction); + +/** + * scd4x_get_automatic_self_calibration() - By default, the ASC is enabled. + * + * @param asc_enabled 1 if ASC is enabled, 0 if ASC is disabled + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_get_automatic_self_calibration(uint16_t* asc_enabled); + +/** + * scd4x_set_automatic_self_calibration() - By default, the ASC is enabled. + * + * @param asc_enabled 1 to enable ASC, 0 to disable ASC + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_set_automatic_self_calibration(uint16_t asc_enabled); + +/** + * scd4x_start_low_power_periodic_measurement() - Start low power periodic + * measurement, signal update interval is 30 seconds. + * + * @note This command is only available in idle mode. + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_start_low_power_periodic_measurement(void); + +/** + * scd4x_get_data_ready_status() - Check whether new measurement data is + * available for read-out. + * + * @param data_ready If last 11 bits are 0 data not ready, else data ready + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_get_data_ready_status(uint16_t* data_ready); + +/** + * scd4x_persist_settings() - Configuration settings such as the temperature + * offset, sensor altitude and the ASC enabled/disabled parameter are by default + * stored in the volatile memory (RAM) only and will be lost after a + * power-cycle. The persist_settings command stores the current configuration in + * the EEPROM of the SCD4x, making them resistant to power-cycling. To avoid + * unnecessary wear of the EEPROM, the persist_settings command should only be + * sent when persistence is required and if actual changes to the configuration + * have been made. Note that field calibration history (i.e. FRC and ASC) is + * stored in the EEPROM automatically. + * + * @note + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_persist_settings(void); + +/** + * scd4x_get_serial_number() - Reading out the serial number can be used to + * identify the chip and to verify the presence of the sensor. The get serial + * number command returns 3 words. Together, the 3 words constitute a unique + * serial number with a length of 48 bits (big endian format). + * + * @param serial_0 First word of the 48 bit serial number + * + * @param serial_1 Second word of the 48 bit serial number + * + * @param serial_2 Third word of the 48 bit serial number + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_get_serial_number(uint16_t* serial_0, uint16_t* serial_1, + uint16_t* serial_2); + +/** + * scd4x_perform_self_test() - The perform_self_test feature can be used as an + * end-of-line test to confirm sensor functionality. + * + * @param sensor_status 0 means no malfunction detected + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_perform_self_test(uint16_t* sensor_status); + +/** + * scd4x_perform_factory_reset() - Initiates the reset of all configurations + * stored in the EEPROM and erases the FRC and ASC algorithm history. + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_perform_factory_reset(void); + +/** + * scd4x_reinit() - The reinit command reinitializes the sensor by reloading + * user settings from EEPROM. Before sending the reinit command, the stop + * measurement command must be issued. If reinit command does not trigger the + * desired re-initialization, a power-cycle should be applied to the SCD4x. + * + * @note Only available in idle mode. + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_reinit(void); + +/** + * scd4x_measure_single_shot() - On-demand measurement of CO₂ concentration, + * relative humidity and temperature. The sensor output is read with the + * read_measurement command. + * + * @note Only available in idle mode. + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_measure_single_shot(void); + +/** + * scd4x_measure_single_shot_rht_only() - On-demand measurement of relative + * humidity and temperature only. + * + * @note Only available in idle mode. + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_measure_single_shot_rht_only(void); + +/** + * scd4x_power_down() - Put the sensor from idle to sleep mode to reduce current + * consumption. + * + * @note Only available in idle mode. + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_power_down(void); + +/** + * scd4x_wake_up() - Wake up sensor from sleep mode to idle mode. + * + * @note Only available in sleep mode. + * + * @return 0 on success, an error code otherwise + */ +int16_t scd4x_wake_up(void); + +#ifdef __cplusplus +} +#endif + +#endif /* SCD4X_I2C_H */ diff --git a/include/sensirion_common.h b/include/sensirion_common.h new file mode 100644 index 0000000..cfcd19f --- /dev/null +++ b/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/include/sensirion_config.h b/include/sensirion_config.h new file mode 100644 index 0000000..1e88ceb --- /dev/null +++ b/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/include/sensirion_i2c.h b/include/sensirion_i2c.h new file mode 100644 index 0000000..db99c20 --- /dev/null +++ b/include/sensirion_i2c.h @@ -0,0 +1,314 @@ +/* + * 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, + 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/include/sensirion_i2c_hal.h b/include/sensirion_i2c_hal.h new file mode 100644 index 0000000..98e3611 --- /dev/null +++ b/include/sensirion_i2c_hal.h @@ -0,0 +1,112 @@ +/* + * 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" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Select the current i2c bus by index. + * All following i2c operations will be directed at that bus. + * + * THE IMPLEMENTATION IS OPTIONAL ON SINGLE-BUS SETUPS (all sensors on the same + * bus) + * + * @param bus_idx Bus index to select + * @returns 0 on success, an error code otherwise + */ +int16_t sensirion_i2c_hal_select_bus(uint8_t bus_idx); + +/** + * Initialize all hard- and software components that are needed for the I2C + * communication. + */ +void sensirion_i2c_hal_init(int SDA, int SCL); + +/** + * 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/scd4x_i2c.c b/scd4x_i2c.c new file mode 100644 index 0000000..0e175f5 --- /dev/null +++ b/scd4x_i2c.c @@ -0,0 +1,452 @@ +/* + * THIS FILE IS AUTOMATICALLY GENERATED AND MUST NOT BE EDITED MANUALLY! + * + * I2C-Generator: 0.2.0 + * Yaml Version: 0.1.0 + * Template Version: 0.2.1 + */ +/* + * 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 "scd4x_i2c.h" +#include "sensirion_common.h" +#include "sensirion_i2c.h" +#include "sensirion_i2c_hal.h" + +#define SCD4X_I2C_ADDRESS 98 + +int16_t scd4x_start_periodic_measurement() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x21B1); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(1000); + return NO_ERROR; +} + +int16_t scd4x_read_measurement_ticks(uint16_t* co2, uint16_t* temperature, + uint16_t* humidity) { + int16_t error; + uint8_t buffer[9]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0xEC05); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(1000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 6); + if (error) { + return error; + } + *co2 = sensirion_common_bytes_to_uint16_t(&buffer[0]); + *temperature = sensirion_common_bytes_to_uint16_t(&buffer[2]); + *humidity = sensirion_common_bytes_to_uint16_t(&buffer[4]); + return NO_ERROR; +} + +int16_t scd4x_read_measurement(uint16_t* co2, int32_t* temperature_m_deg_c, + int32_t* humidity_m_percent_rh) { + int16_t error; + uint16_t temperature; + uint16_t humidity; + + error = scd4x_read_measurement_ticks(co2, &temperature, &humidity); + if (error) { + return error; + } + *temperature_m_deg_c = ((21875 * (int32_t)temperature) >> 13) - 45000; + *humidity_m_percent_rh = ((12500 * (int32_t)humidity) >> 13); + return NO_ERROR; +} + +int16_t scd4x_stop_periodic_measurement() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x3F86); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(500000); + return NO_ERROR; +} + +int16_t scd4x_get_temperature_offset_ticks(uint16_t* t_offset) { + int16_t error; + uint8_t buffer[3]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x2318); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(1000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 2); + if (error) { + return error; + } + *t_offset = sensirion_common_bytes_to_uint16_t(&buffer[0]); + return NO_ERROR; +} + +int16_t scd4x_get_temperature_offset(int32_t* t_offset_m_deg_c) { + int16_t error; + uint16_t t_offset; + + error = scd4x_get_temperature_offset_ticks(&t_offset); + if (error) { + return error; + } + *t_offset_m_deg_c = ((21875 * (int32_t)t_offset) >> 13); + return NO_ERROR; +} + +int16_t scd4x_set_temperature_offset_ticks(uint16_t t_offset) { + int16_t error; + uint8_t buffer[5]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x241D); + + offset = sensirion_i2c_add_uint16_t_to_buffer(&buffer[0], offset, t_offset); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(1000); + return NO_ERROR; +} + +int16_t scd4x_set_temperature_offset(int32_t t_offset_m_deg_c) { + uint16_t t_offset = (uint16_t)((t_offset_m_deg_c * 12271) >> 15); + return scd4x_set_temperature_offset_ticks(t_offset); +} + +int16_t scd4x_get_sensor_altitude(uint16_t* sensor_altitude) { + int16_t error; + uint8_t buffer[3]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x2322); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(1000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 2); + if (error) { + return error; + } + *sensor_altitude = sensirion_common_bytes_to_uint16_t(&buffer[0]); + return NO_ERROR; +} + +int16_t scd4x_set_sensor_altitude(uint16_t sensor_altitude) { + int16_t error; + uint8_t buffer[5]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x2427); + + offset = sensirion_i2c_add_uint16_t_to_buffer(&buffer[0], offset, + sensor_altitude); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(1000); + return NO_ERROR; +} + +int16_t scd4x_set_ambient_pressure(uint16_t ambient_pressure) { + int16_t error; + uint8_t buffer[5]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0xE000); + + offset = sensirion_i2c_add_uint16_t_to_buffer(&buffer[0], offset, + ambient_pressure); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(1000); + return NO_ERROR; +} + +int16_t scd4x_perform_forced_recalibration(uint16_t target_co2_concentration, + uint16_t* frc_correction) { + int16_t error; + uint8_t buffer[5]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x362F); + + offset = sensirion_i2c_add_uint16_t_to_buffer(&buffer[0], offset, + target_co2_concentration); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(400000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 2); + if (error) { + return error; + } + *frc_correction = sensirion_common_bytes_to_uint16_t(&buffer[0]); + return NO_ERROR; +} + +int16_t scd4x_get_automatic_self_calibration(uint16_t* asc_enabled) { + int16_t error; + uint8_t buffer[3]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x2313); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(1000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 2); + if (error) { + return error; + } + *asc_enabled = sensirion_common_bytes_to_uint16_t(&buffer[0]); + return NO_ERROR; +} + +int16_t scd4x_set_automatic_self_calibration(uint16_t asc_enabled) { + int16_t error; + uint8_t buffer[5]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x2416); + + offset = + sensirion_i2c_add_uint16_t_to_buffer(&buffer[0], offset, asc_enabled); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(1000); + return NO_ERROR; +} + +int16_t scd4x_start_low_power_periodic_measurement() { + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x21AC); + + return sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); +} + +int16_t scd4x_get_data_ready_status(uint16_t* data_ready) { + int16_t error; + uint8_t buffer[3]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0xE4B8); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(1000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 2); + if (error) { + return error; + } + *data_ready = sensirion_common_bytes_to_uint16_t(&buffer[0]); + return NO_ERROR; +} + +int16_t scd4x_persist_settings() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x3615); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(800000); + return NO_ERROR; +} + +int16_t scd4x_get_serial_number(uint16_t* serial_0, uint16_t* serial_1, + uint16_t* serial_2) { + int16_t error; + uint8_t buffer[9]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x3682); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(1000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 6); + if (error) { + return error; + } + *serial_0 = sensirion_common_bytes_to_uint16_t(&buffer[0]); + *serial_1 = sensirion_common_bytes_to_uint16_t(&buffer[2]); + *serial_2 = sensirion_common_bytes_to_uint16_t(&buffer[4]); + return NO_ERROR; +} + +int16_t scd4x_perform_self_test(uint16_t* sensor_status) { + int16_t error; + uint8_t buffer[3]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x3639); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + + sensirion_i2c_hal_sleep_usec(10000000); + + error = sensirion_i2c_read_data_inplace(SCD4X_I2C_ADDRESS, &buffer[0], 2); + if (error) { + return error; + } + *sensor_status = sensirion_common_bytes_to_uint16_t(&buffer[0]); + return NO_ERROR; +} + +int16_t scd4x_perform_factory_reset() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x3632); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(800000); + return NO_ERROR; +} + +int16_t scd4x_reinit() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x3646); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(20000); + return NO_ERROR; +} + +int16_t scd4x_measure_single_shot() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x219D); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(5000000); + return NO_ERROR; +} + +int16_t scd4x_measure_single_shot_rht_only() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x2196); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(50000); + return NO_ERROR; +} + +int16_t scd4x_power_down() { + int16_t error; + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x36E0); + + error = sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + if (error) { + return error; + } + sensirion_i2c_hal_sleep_usec(1000); + return NO_ERROR; +} + +int16_t scd4x_wake_up() { + uint8_t buffer[2]; + uint16_t offset = 0; + offset = sensirion_i2c_add_command_to_buffer(&buffer[0], offset, 0x36F6); + + // Sensor does not acknowledge the wake-up call, error is ignored + (void)sensirion_i2c_write_data(SCD4X_I2C_ADDRESS, &buffer[0], offset); + sensirion_i2c_hal_sleep_usec(20000); + return NO_ERROR; +} diff --git a/sensirion_common.c b/sensirion_common.c new file mode 100644 index 0000000..4ee7a96 --- /dev/null +++ b/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/sensirion_i2c.c b/sensirion_i2c.c new file mode 100644 index 0000000..784c402 --- /dev/null +++ b/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, + 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/sensirion_i2c_hal.c b/sensirion_i2c_hal.c new file mode 100644 index 0000000..5c8fb48 --- /dev/null +++ b/sensirion_i2c_hal.c @@ -0,0 +1,148 @@ +/* + * 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" + +#include "driver/i2c.h" + +#include +/* + * INSTRUCTIONS + * ============ + * + * Implement all functions where they are marked as IMPLEMENT. + * Follow the function specification in the comments. + */ + +/** + * Select the current i2c bus by index. + * All following i2c operations will be directed at that bus. + * + * THE IMPLEMENTATION IS OPTIONAL ON SINGLE-BUS SETUPS (all sensors on the same + * bus) + * + * @param bus_idx Bus index to select + * @returns 0 on success, an error code otherwise + */ +int16_t sensirion_i2c_hal_select_bus(uint8_t bus_idx) { + /* TODO:IMPLEMENT or leave empty if all sensors are located on one single + * bus + */ + return NOT_IMPLEMENTED_ERROR; +} + +/** + * Initialize all hard- and software components that are needed for the I2C + * communication. + */ +void sensirion_i2c_hal_init(int SDA, int SCL) { + int i2c_master_port = I2C_NUM_0; + i2c_config_t conf = { + .mode = I2C_MODE_MASTER, + .sda_io_num = SDA, // select GPIO specific to your project + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = SCL, // select GPIO specific to your project + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = 10000, // select frequency specific to your project + // .clk_flags = 0, /*!< Optional, you can use I2C_SCLK_SRC_FLAG_* flags to choose i2c source clock here. */ + }; + i2c_param_config(i2c_master_port, &conf); + + i2c_driver_install(i2c_master_port, I2C_MODE_MASTER, 0, 0, 0); +} + +/** + * Release all resources initialized by sensirion_i2c_hal_init(). + */ +void sensirion_i2c_hal_free(void) { + i2c_driver_delete(I2C_NUM_0); /* TODO:non const port */ +} + +/** + * 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) { + i2c_cmd_handle_t cmdLnk = i2c_cmd_link_create(); + esp_err_t err = ESP_OK; + err = i2c_master_start(cmdLnk); + err = i2c_master_write_byte(cmdLnk, address << 1 | I2C_MASTER_READ, true); + err = i2c_master_read(cmdLnk, data, count-1, I2C_MASTER_ACK); + err = i2c_master_read(cmdLnk, data + count - 1, 1, I2C_MASTER_NACK); + err = i2c_master_stop(cmdLnk); + i2c_master_cmd_begin(I2C_NUM_0,cmdLnk,1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmdLnk); + return err; +} + +/** + * 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) { + i2c_cmd_handle_t cmdLnk = i2c_cmd_link_create(); + esp_err_t err = ESP_OK; + err = i2c_master_start(cmdLnk); + err = i2c_master_write_byte(cmdLnk, address << 1 | I2C_MASTER_WRITE, true); + err = i2c_master_write(cmdLnk, data, count, true); + err = i2c_master_stop(cmdLnk); + i2c_master_cmd_begin(I2C_NUM_0,cmdLnk,1000 / portTICK_RATE_MS); /* TODO:non const port */ + i2c_cmd_link_delete(cmdLnk); + return err; +} + +/** + * 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) { + vTaskDelay(useconds / portTICK_PERIOD_MS / 1000); +}