/* * SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD * * SPDX-License-Identifier: MIT */ /*! @file unit_SCD40.hpp @brief SCD40 Unit for M5UnitUnified */ #ifndef M5_UNIT_ENV_UNIT_SCD40_HPP #define M5_UNIT_ENV_UNIT_SCD40_HPP #include #include #include // NaN namespace m5 { namespace unit { /*! @namespace scd4x @brief For SCD40/41 */ namespace scd4x { /*! @enum Mode @brief Mode of periodic measurement */ enum class Mode : uint8_t { Normal, //!< Normal (Receive data every 5 seconds) LowPower, //!< Low power (Receive data every 30 seconds) }; /*! @struct Data @brief Measurement data group */ struct Data { std::array raw{}; //!< @brief RAW data uint16_t co2() const; //!< @brief CO2 concentration (ppm) //! @brief temperature (Celsius) inline float temperature() const { return celsius(); } float celsius() const; //!< @brief temperature (Celsius) float fahrenheit() const; //!< @brief temperature (Fahrenheit) float humidity() const; //!< @brief humidity (RH) }; ///@cond // Max command duration(ms) // For SCD40/41 constexpr uint16_t READ_MEASUREMENT_DURATION{1}; constexpr uint16_t STOP_PERIODIC_MEASUREMENT_DURATION{500}; constexpr uint16_t SET_TEMPERATURE_OFFSET_DURATION{1}; constexpr uint16_t GET_TEMPERATURE_OFFSET_DURATION{1}; constexpr uint16_t SET_SENSOR_ALTITUDE_DURATION{1}; constexpr uint16_t GET_SENSOR_ALTITUDE_DURATION{1}; constexpr uint16_t SET_AMBIENT_PRESSURE_DURATION{1}; constexpr uint16_t GET_AMBIENT_PRESSURE_DURATION{1}; constexpr uint16_t PERFORM_FORCED_CALIBRATION_DURATION{400}; constexpr uint16_t SET_AUTOMATIC_SELF_CALIBRATION_ENABLED_DURATION{1}; constexpr uint16_t GET_AUTOMATIC_SELF_CALIBRATION_ENABLED_DURATION{1}; constexpr uint16_t SET_AUTOMATIC_SELF_CALIBRATION_TARGET_DURATION{1}; constexpr uint16_t GET_AUTOMATIC_SELF_CALIBRATION_TARGET_DURATION{1}; constexpr uint16_t GET_DATA_READY_STATUS_DURATION{1}; constexpr uint16_t PERSIST_SETTINGS_DURATION{800}; constexpr uint16_t GET_SERIAL_NUMBER_DURATION{1}; constexpr uint16_t PERFORM_SELF_TEST_DURATION{10000}; constexpr uint16_t PERFORM_FACTORY_RESET_DURATION{1200}; constexpr uint16_t REINIT_DURATION{20}; /// @endcond } // namespace scd4x /*! @class m5::unit::UnitSCD40 @brief SCD40 unit component */ class UnitSCD40 : public Component, public PeriodicMeasurementAdapter { M5_UNIT_COMPONENT_HPP_BUILDER(UnitSCD40, 0x62); public: /*! @struct config_t @brief Settings for begin */ struct config_t { //! Start periodic measurement on begin? bool start_periodic{true}; //! Mode of periodic measurement if start on begin? scd4x::Mode mode{scd4x::Mode::Normal}; //! Enable ASC on begin? bool calibration{true}; }; explicit UnitSCD40(const uint8_t addr = DEFAULT_ADDRESS) : Component(addr), _data{new m5::container::CircularBuffer(1)} { auto ccfg = component_config(); ccfg.clock = 400 * 1000U; component_config(ccfg); } virtual ~UnitSCD40() { } virtual bool begin() override; virtual void update(const bool force = false) override; ///@name Settings for begin ///@{ /*! @brief Gets the configration */ inline config_t config() const { return _cfg; } //! @brief Set the configration inline void config(const config_t &cfg) { _cfg = cfg; } ///@} ///@name Measurement data by periodic ///@{ //! @brief Oldest measured CO2 concentration (ppm) inline uint16_t co2() const { return !empty() ? oldest().co2() : 0; } //! @brief Oldest measured temperature (Celsius) inline float temperature() const { return !empty() ? oldest().temperature() : std::numeric_limits::quiet_NaN(); } //! @brief Oldest measured temperature (Celsius) inline float celsius() const { return !empty() ? oldest().celsius() : std::numeric_limits::quiet_NaN(); } //! @brief Oldest measured temperature (Fahrenheit) inline float fahrenheit() const { return !empty() ? oldest().fahrenheit() : std::numeric_limits::quiet_NaN(); } //! @brief Oldest measured humidity (RH) inline float humidity() const { return !empty() ? oldest().humidity() : std::numeric_limits::quiet_NaN(); } ///@} ///@name Periodic measurement ///@{ /*! @brief Start periodic measurement @param mode Measurement mode @return True if successful */ inline bool startPeriodicMeasurement(const scd4x::Mode mode = scd4x::Mode::Normal) { return PeriodicMeasurementAdapter::startPeriodicMeasurement(mode); } /*! @brief Start low power periodic measurement @return True if successful */ inline bool startLowPowerPeriodicMeasurement() { return startPeriodicMeasurement(scd4x::Mode::LowPower); } /*! @brief Stop periodic measurement @param duration Max command duration(ms) @return True if successful */ inline bool stopPeriodicMeasurement(const uint32_t duration = scd4x::STOP_PERIODIC_MEASUREMENT_DURATION) { return PeriodicMeasurementAdapter::stopPeriodicMeasurement(duration); } ///@} ///@name On-Chip Output Signal Compensation ///@{ /*! @brief Write the temperature offset @details Define how warm the sensor is compared to ambient, so RH and T are temperature compensated. Has no effect on the CO2 reading Default offsetis 4C @param offset (0 <= offset < 175) @param duration Max command duration(ms) @return True if successful @note Recommended temperature offset values are between 0 and 20 @warning During periodic detection runs, an error is returned */ bool writeTemperatureOffset(const float offset, const uint32_t duration = scd4x::SET_TEMPERATURE_OFFSET_DURATION); /*! @brief Read the temperature offset @param[out] offset Offset value @return True if successful @warning During periodic detection runs, an error is returned */ bool readTemperatureOffset(float &offset); /*! @brief Write the sensor altitude @details Define the sensor altitude in metres above sea level, so RH and CO2 arecompensated for atmospheric pressure Default altitude is 0m @param altitude Sensor altitude [m] @param duration Max command duration(ms) @return True if successful @warning During periodic detection runs, an error is returned */ bool writeSensorAltitude(const uint16_t altitude, const uint32_t duration = scd4x::SET_SENSOR_ALTITUDE_DURATION); /*! @brief Read the sensor altitude @param[out] altitude Sensor altitude [m] @return True if successful @warning During periodic detection runs, an error is returned */ bool readSensorAltitude(uint16_t &altitude); /*! @brief Write the ambient pressure @details Define the ambient pressure in Pascals, so RH and CO2 are compensated for atmospheric pressure setAmbientPressure overrides setSensorAltitude @param pressure Ambient pressure [hPa] @param duration Max command duration(ms) @return True if successful @warning Valid Valid input values are between 700 – 1200 hPa */ bool writeAmbientPressure(const uint16_t pressure, const uint32_t duration = scd4x::SET_AMBIENT_PRESSURE_DURATION); /*! @brief Read the ambient pressure @param[out] presure Ambient pressure [hPa] @return True if successful */ bool readAmbientPressure(uint16_t &pressure); ///@} ///@name Field Calibration ///@{ /*! @brief Perform forced recalibration @param concentration Unit:ppm @param[out] correction The FRC correction value @return True if successful @warning During periodic detection runs, an error is returned @warning Blocked until the process is completed (about 400ms) */ bool performForcedRecalibration(const uint16_t concentration, int16_t &correction); /*! @brief Enable/disable automatic self calibration @param enabled Enable automatic self calibration if true @param duration Max command duration(ms) @return True if successful @warning During periodic detection runs, an error is returned */ bool writeAutomaticSelfCalibrationEnabled( const bool enabled = true, const uint32_t duration = scd4x::SET_AUTOMATIC_SELF_CALIBRATION_ENABLED_DURATION); /*! @brief Check if automatic self calibration is enabled @param[out] enabled True if automatic self calibration is enabled @return True if successful @warning During periodic detection runs, an error is returned */ bool readAutomaticSelfCalibrationEnabled(bool &enabled); /*! @brief Write the value of the ASC baseline target @param ppm ASC target ppm @param duration Max command duration(ms) @return True if successful @warning During periodic detection runs, an error is returned */ bool writeAutomaticSelfCalibrationTarget( const uint16_t ppm, const uint32_t duration = scd4x::SET_AUTOMATIC_SELF_CALIBRATION_TARGET_DURATION); /*! @brief Read the value of the ASC baseline target @param[out] ppm ASC target ppm @return True if successful @warning During periodic detection runs, an error is returned */ bool readAutomaticSelfCalibrationTarget(uint16_t &ppm); ///@} ///@name Advanced Features ///@{ /*! @brief Write sensor settings from RAM to EEPROM @return True if successful @warning During periodic detection runs, an error is returned */ bool writePersistSettings(const uint32_t duration = scd4x::PERSIST_SETTINGS_DURATION); /*! @brief Read the serial number string @param[out] serialNumber Output buffer @return True if successful @warning Size must be at least 13 bytes @warning During periodic detection runs, an error is returned */ bool readSerialNumber(char *serialNumber); /*! @brief Read the serial number value @param[out] serialNumber serial number value @return True if successful @note The serial number is 48 bit @warning During periodic detection runs, an error is returned */ bool readSerialNumber(uint64_t &serialNumber); /*! @brief Perform self test @param[out] True if malfunction detected @return True if successful @note Takes 10 seconds to complete @warning During periodic detection runs, an error is returned */ bool performSelfTest(bool &malfunction); /*! @brief Peform factory reset @details Reset all settings to the factory values @return True if successful @warning During periodic detection runs, an error is returned @warning Measurement duration max 1200 ms */ bool performFactoryReset(const uint32_t duration = scd4x::PERFORM_FACTORY_RESET_DURATION); /*! @brief Re-initialize the sensor, load settings from EEPROM @return True if successful @warning During periodic detection runs, an error is returned @warning Measurement duration max 20 ms */ bool reInit(const uint32_t duration = scd4x::REINIT_DURATION); ///@} protected: bool read_register(const uint16_t reg, uint8_t *rbuf, const uint32_t rlen, const uint32_t duration = 1); bool write_register(const uint16_t reg, uint8_t *wbuf, const uint32_t wlen); bool start_periodic_measurement(const scd4x::Mode mode = scd4x::Mode::Normal); bool stop_periodic_measurement(const uint32_t duration = scd4x::STOP_PERIODIC_MEASUREMENT_DURATION); bool read_data_ready_status(); bool read_measurement(scd4x::Data &d, const bool all = true); virtual bool is_valid_chip(); bool delay_true(const uint32_t duration); M5_UNIT_COMPONENT_PERIODIC_MEASUREMENT_ADAPTER_HPP_BUILDER(UnitSCD40, scd4x::Data); protected: std::unique_ptr> _data{}; config_t _cfg{}; }; ///@cond namespace scd4x { namespace command { // Basic Commands constexpr uint16_t START_PERIODIC_MEASUREMENT{0x21b1}; constexpr uint16_t READ_MEASUREMENT{0xec05}; constexpr uint16_t STOP_PERIODIC_MEASUREMENT{0x3f86}; // On-chip output signal compensation constexpr uint16_t SET_TEMPERATURE_OFFSET{0x241d}; constexpr uint16_t GET_TEMPERATURE_OFFSET{0x2318}; constexpr uint16_t SET_SENSOR_ALTITUDE{0x2427}; constexpr uint16_t GET_SENSOR_ALTITUDE{0x2322}; constexpr uint16_t AMBIENT_PRESSURE{0xe000}; // Field calibration constexpr uint16_t PERFORM_FORCED_CALIBRATION{0x362f}; constexpr uint16_t SET_AUTOMATIC_SELF_CALIBRATION_ENABLED{0x2416}; constexpr uint16_t GET_AUTOMATIC_SELF_CALIBRATION_ENABLED{0x2313}; constexpr uint16_t SET_AUTOMATIC_SELF_CALIBRATION_TARGET{0x243a}; constexpr uint16_t GET_AUTOMATIC_SELF_CALIBRATION_TARGET{0x233f}; // Low power constexpr uint16_t START_LOW_POWER_PERIODIC_MEASUREMENT{0x21ac}; constexpr uint16_t GET_DATA_READY_STATUS{0xe4b8}; // Advanced features constexpr uint16_t PERSIST_SETTINGS{0x3615}; constexpr uint16_t GET_SERIAL_NUMBER{0x3682}; constexpr uint16_t PERFORM_SELF_TEST{0x3639}; constexpr uint16_t PERFORM_FACTORY_RESET{0x3632}; constexpr uint16_t REINIT{0x3646}; // constexpr uint16_t GET_SENSOR_VARIANT{0x202f}; } // namespace command } // namespace scd4x ///@endcond } // namespace unit } // namespace m5 #endif