first commit
This commit is contained in:
commit
5893b00dd2
1669 changed files with 1982740 additions and 0 deletions
237
libraries/M5Unit-ENV/src/BMP280.cpp
Normal file
237
libraries/M5Unit-ENV/src/BMP280.cpp
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
#include "BMP280.h"
|
||||
|
||||
bool BMP280::begin(TwoWire* wire, uint8_t addr, uint8_t sda, uint8_t scl,
|
||||
long freq) {
|
||||
_i2c.begin(wire, sda, scl, freq);
|
||||
_addr = addr;
|
||||
if (!_i2c.exist(_addr)) {
|
||||
return false;
|
||||
}
|
||||
readCoefficients();
|
||||
setSampling();
|
||||
|
||||
return true;
|
||||
}
|
||||
bool BMP280::update() {
|
||||
readTemperature();
|
||||
readPressure();
|
||||
readAltitude();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
float BMP280::readTemperature() {
|
||||
int32_t var1, var2;
|
||||
|
||||
int32_t adc_T = read24(BMP280_REGISTER_TEMPDATA);
|
||||
adc_T >>= 4;
|
||||
|
||||
var1 = ((((adc_T >> 3) - ((int32_t)_bmp280_calib.dig_T1 << 1))) *
|
||||
((int32_t)_bmp280_calib.dig_T2)) >>
|
||||
11;
|
||||
|
||||
var2 = (((((adc_T >> 4) - ((int32_t)_bmp280_calib.dig_T1)) *
|
||||
((adc_T >> 4) - ((int32_t)_bmp280_calib.dig_T1))) >>
|
||||
12) *
|
||||
((int32_t)_bmp280_calib.dig_T3)) >>
|
||||
14;
|
||||
|
||||
t_fine = var1 + var2;
|
||||
|
||||
float T = (t_fine * 5 + 128) >> 8;
|
||||
cTemp = T / 100;
|
||||
return cTemp;
|
||||
}
|
||||
|
||||
float BMP280::readPressure() {
|
||||
int64_t var1, var2, p;
|
||||
|
||||
// Must be done first to get the t_fine variable set up
|
||||
|
||||
int32_t adc_P = read24(BMP280_REGISTER_PRESSUREDATA);
|
||||
adc_P >>= 4;
|
||||
|
||||
var1 = ((int64_t)t_fine) - 128000;
|
||||
var2 = var1 * var1 * (int64_t)_bmp280_calib.dig_P6;
|
||||
var2 = var2 + ((var1 * (int64_t)_bmp280_calib.dig_P5) << 17);
|
||||
var2 = var2 + (((int64_t)_bmp280_calib.dig_P4) << 35);
|
||||
var1 = ((var1 * var1 * (int64_t)_bmp280_calib.dig_P3) >> 8) +
|
||||
((var1 * (int64_t)_bmp280_calib.dig_P2) << 12);
|
||||
var1 =
|
||||
(((((int64_t)1) << 47) + var1)) * ((int64_t)_bmp280_calib.dig_P1) >> 33;
|
||||
|
||||
if (var1 == 0) {
|
||||
return 0; // avoid exception caused by division by zero
|
||||
}
|
||||
p = 1048576 - adc_P;
|
||||
p = (((p << 31) - var2) * 3125) / var1;
|
||||
var1 = (((int64_t)_bmp280_calib.dig_P9) * (p >> 13) * (p >> 13)) >> 25;
|
||||
var2 = (((int64_t)_bmp280_calib.dig_P8) * p) >> 19;
|
||||
|
||||
p = ((p + var1 + var2) >> 8) + (((int64_t)_bmp280_calib.dig_P7) << 4);
|
||||
pressure = p / 256;
|
||||
return pressure;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Calculates the approximate altitude using barometric pressure and the
|
||||
* supplied sea level hPa as a reference.
|
||||
* @param seaLevelhPa
|
||||
* The current hPa at sea level.
|
||||
* @return The approximate altitude above sea level in meters.
|
||||
*/
|
||||
float BMP280::readAltitude(float seaLevelhPa) {
|
||||
float pressure = readPressure(); // in Si units for Pascal
|
||||
pressure /= 100;
|
||||
altitude = 44330 * (1.0 - pow(pressure / seaLevelhPa, 0.1903));
|
||||
return altitude;
|
||||
}
|
||||
|
||||
void BMP280::setSampling(sensor_mode mode, sensor_sampling tempSampling,
|
||||
sensor_sampling pressSampling, sensor_filter filter,
|
||||
standby_duration duration) {
|
||||
_measReg.mode = mode;
|
||||
_measReg.osrs_t = tempSampling;
|
||||
_measReg.osrs_p = pressSampling;
|
||||
|
||||
_configReg.filter = filter;
|
||||
_configReg.t_sb = duration;
|
||||
|
||||
write8(BMP280_REGISTER_CONFIG, _configReg.get());
|
||||
write8(BMP280_REGISTER_CONTROL, _measReg.get());
|
||||
}
|
||||
|
||||
void BMP280::readCoefficients() {
|
||||
_bmp280_calib.dig_T1 = read16_LE(BMP280_REGISTER_DIG_T1);
|
||||
_bmp280_calib.dig_T2 = readS16_LE(BMP280_REGISTER_DIG_T2);
|
||||
_bmp280_calib.dig_T3 = readS16_LE(BMP280_REGISTER_DIG_T3);
|
||||
|
||||
_bmp280_calib.dig_P1 = read16_LE(BMP280_REGISTER_DIG_P1);
|
||||
_bmp280_calib.dig_P2 = readS16_LE(BMP280_REGISTER_DIG_P2);
|
||||
_bmp280_calib.dig_P3 = readS16_LE(BMP280_REGISTER_DIG_P3);
|
||||
_bmp280_calib.dig_P4 = readS16_LE(BMP280_REGISTER_DIG_P4);
|
||||
_bmp280_calib.dig_P5 = readS16_LE(BMP280_REGISTER_DIG_P5);
|
||||
_bmp280_calib.dig_P6 = readS16_LE(BMP280_REGISTER_DIG_P6);
|
||||
_bmp280_calib.dig_P7 = readS16_LE(BMP280_REGISTER_DIG_P7);
|
||||
_bmp280_calib.dig_P8 = readS16_LE(BMP280_REGISTER_DIG_P8);
|
||||
_bmp280_calib.dig_P9 = readS16_LE(BMP280_REGISTER_DIG_P9);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Calculates the pressure at sea level (QNH) from the specified altitude,
|
||||
* and atmospheric pressure (QFE).
|
||||
* @param altitude Altitude in m
|
||||
* @param atmospheric Atmospheric pressure in hPa
|
||||
* @return The approximate pressure in hPa
|
||||
*/
|
||||
float BMP280::seaLevelForAltitude(float altitude, float atmospheric) {
|
||||
// Equation taken from BMP180 datasheet (page 17):
|
||||
// http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf
|
||||
|
||||
// Note that using the equation from wikipedia can give bad results
|
||||
// at high altitude. See this thread for more information:
|
||||
// http://forums.adafruit.com/viewtopic.php?f=22&t=58064
|
||||
return atmospheric / pow(1.0 - (altitude / 44330.0), 5.255);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief calculates the boiling point of water by a given pressure
|
||||
@param pressure pressure in hPa
|
||||
@return temperature in °C
|
||||
*/
|
||||
|
||||
float BMP280::waterBoilingPoint(float pressure) {
|
||||
// Magnusformular for calculation of the boiling point of water at a given
|
||||
// pressure
|
||||
return (234.175 * log(pressure / 6.1078)) /
|
||||
(17.08085 - log(pressure / 6.1078));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Take a new measurement (only possible in forced mode)
|
||||
@return true if successful, otherwise false
|
||||
*/
|
||||
bool BMP280::takeForcedMeasurement() {
|
||||
// If we are in forced mode, the BME sensor goes back to sleep after each
|
||||
// measurement and we need to set it to forced mode once at this point, so
|
||||
// it will take the next measurement and then return to sleep again.
|
||||
// In normal mode simply does new measurements periodically.
|
||||
if (_measReg.mode == MODE_FORCED) {
|
||||
// set to forced mode, i.e. "take next measurement"
|
||||
write8(BMP280_REGISTER_CONTROL, _measReg.get());
|
||||
// wait until measurement has been completed, otherwise we would read
|
||||
// the values from the last measurement
|
||||
while (read8(BMP280_REGISTER_STATUS) & 0x08) delay(1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Resets the chip via soft reset
|
||||
*/
|
||||
void BMP280::reset(void) {
|
||||
write8(BMP280_REGISTER_SOFTRESET, MODE_SOFT_RESET_CODE);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Gets the most recent sensor event from the hardware status register.
|
||||
@return Sensor status as a byte.
|
||||
*/
|
||||
uint8_t BMP280::getStatus(void) {
|
||||
return read8(BMP280_REGISTER_STATUS);
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Writes an 8 bit value over I2C/SPI
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void BMP280::write8(byte reg, byte value) {
|
||||
_i2c.writeByte(_addr, reg, value);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Reads an 8 bit value over I2C/SPI
|
||||
* @param reg
|
||||
* selected register
|
||||
* @return value from selected register
|
||||
*/
|
||||
uint8_t BMP280::read8(byte reg) {
|
||||
return _i2c.readByte(_addr, reg);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Reads a 16 bit value over I2C/SPI
|
||||
*/
|
||||
uint16_t BMP280::read16(byte reg) {
|
||||
uint8_t buffer[2];
|
||||
_i2c.readBytes(_addr, reg, buffer, 2);
|
||||
return uint16_t(buffer[0]) << 8 | uint16_t(buffer[1]);
|
||||
}
|
||||
|
||||
uint16_t BMP280::read16_LE(byte reg) {
|
||||
uint16_t temp = read16(reg);
|
||||
return (temp >> 8) | (temp << 8);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Reads a signed 16 bit value over I2C/SPI
|
||||
*/
|
||||
int16_t BMP280::readS16(byte reg) {
|
||||
return (int16_t)read16(reg);
|
||||
}
|
||||
|
||||
int16_t BMP280::readS16_LE(byte reg) {
|
||||
return (int16_t)read16_LE(reg);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Reads a 24 bit value over I2C/SPI
|
||||
*/
|
||||
uint32_t BMP280::read24(byte reg) {
|
||||
uint8_t buffer[3];
|
||||
_i2c.readBytes(_addr, reg, buffer, 3);
|
||||
return uint32_t(buffer[0]) << 16 | uint32_t(buffer[1]) << 8 |
|
||||
uint32_t(buffer[2]);
|
||||
}
|
||||
199
libraries/M5Unit-ENV/src/BMP280.h
Normal file
199
libraries/M5Unit-ENV/src/BMP280.h
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
|
||||
#ifndef _BMP280_H
|
||||
#define _BMP280_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Wire.h"
|
||||
#include "I2C_Class.h"
|
||||
|
||||
#define BMP280_I2C_ADDR 0x76
|
||||
|
||||
enum {
|
||||
BMP280_REGISTER_DIG_T1 = 0x88,
|
||||
BMP280_REGISTER_DIG_T2 = 0x8A,
|
||||
BMP280_REGISTER_DIG_T3 = 0x8C,
|
||||
BMP280_REGISTER_DIG_P1 = 0x8E,
|
||||
BMP280_REGISTER_DIG_P2 = 0x90,
|
||||
BMP280_REGISTER_DIG_P3 = 0x92,
|
||||
BMP280_REGISTER_DIG_P4 = 0x94,
|
||||
BMP280_REGISTER_DIG_P5 = 0x96,
|
||||
BMP280_REGISTER_DIG_P6 = 0x98,
|
||||
BMP280_REGISTER_DIG_P7 = 0x9A,
|
||||
BMP280_REGISTER_DIG_P8 = 0x9C,
|
||||
BMP280_REGISTER_DIG_P9 = 0x9E,
|
||||
BMP280_REGISTER_CHIPID = 0xD0,
|
||||
BMP280_REGISTER_VERSION = 0xD1,
|
||||
BMP280_REGISTER_SOFTRESET = 0xE0,
|
||||
BMP280_REGISTER_CAL26 = 0xE1, /**< R calibration = 0xE1-0xF0 */
|
||||
BMP280_REGISTER_STATUS = 0xF3,
|
||||
BMP280_REGISTER_CONTROL = 0xF4,
|
||||
BMP280_REGISTER_CONFIG = 0xF5,
|
||||
BMP280_REGISTER_PRESSUREDATA = 0xF7,
|
||||
BMP280_REGISTER_TEMPDATA = 0xFA,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint16_t dig_T1; /**< dig_T1 cal register. */
|
||||
int16_t dig_T2; /**< dig_T2 cal register. */
|
||||
int16_t dig_T3; /**< dig_T3 cal register. */
|
||||
|
||||
uint16_t dig_P1; /**< dig_P1 cal register. */
|
||||
int16_t dig_P2; /**< dig_P2 cal register. */
|
||||
int16_t dig_P3; /**< dig_P3 cal register. */
|
||||
int16_t dig_P4; /**< dig_P4 cal register. */
|
||||
int16_t dig_P5; /**< dig_P5 cal register. */
|
||||
int16_t dig_P6; /**< dig_P6 cal register. */
|
||||
int16_t dig_P7; /**< dig_P7 cal register. */
|
||||
int16_t dig_P8; /**< dig_P8 cal register. */
|
||||
int16_t dig_P9; /**< dig_P9 cal register. */
|
||||
} bmp280_calib_data;
|
||||
|
||||
class BMP280 {
|
||||
public:
|
||||
/** Oversampling rate for the sensor. */
|
||||
enum sensor_sampling {
|
||||
/** No over-sampling. */
|
||||
SAMPLING_NONE = 0x00,
|
||||
/** 1x over-sampling. */
|
||||
SAMPLING_X1 = 0x01,
|
||||
/** 2x over-sampling. */
|
||||
SAMPLING_X2 = 0x02,
|
||||
/** 4x over-sampling. */
|
||||
SAMPLING_X4 = 0x03,
|
||||
/** 8x over-sampling. */
|
||||
SAMPLING_X8 = 0x04,
|
||||
/** 16x over-sampling. */
|
||||
SAMPLING_X16 = 0x05
|
||||
};
|
||||
|
||||
/** Operating mode for the sensor. */
|
||||
enum sensor_mode {
|
||||
/** Sleep mode. */
|
||||
MODE_SLEEP = 0x00,
|
||||
/** Forced mode. */
|
||||
MODE_FORCED = 0x01,
|
||||
/** Normal mode. */
|
||||
MODE_NORMAL = 0x03,
|
||||
/** Software reset. */
|
||||
MODE_SOFT_RESET_CODE = 0xB6
|
||||
};
|
||||
|
||||
/** Filtering level for sensor data. */
|
||||
enum sensor_filter {
|
||||
/** No filtering. */
|
||||
FILTER_OFF = 0x00,
|
||||
/** 2x filtering. */
|
||||
FILTER_X2 = 0x01,
|
||||
/** 4x filtering. */
|
||||
FILTER_X4 = 0x02,
|
||||
/** 8x filtering. */
|
||||
FILTER_X8 = 0x03,
|
||||
/** 16x filtering. */
|
||||
FILTER_X16 = 0x04
|
||||
};
|
||||
|
||||
/** Standby duration in ms */
|
||||
enum standby_duration {
|
||||
/** 1 ms standby. */
|
||||
STANDBY_MS_1 = 0x00,
|
||||
/** 62.5 ms standby. */
|
||||
STANDBY_MS_63 = 0x01,
|
||||
/** 125 ms standby. */
|
||||
STANDBY_MS_125 = 0x02,
|
||||
/** 250 ms standby. */
|
||||
STANDBY_MS_250 = 0x03,
|
||||
/** 500 ms standby. */
|
||||
STANDBY_MS_500 = 0x04,
|
||||
/** 1000 ms standby. */
|
||||
STANDBY_MS_1000 = 0x05,
|
||||
/** 2000 ms standby. */
|
||||
STANDBY_MS_2000 = 0x06,
|
||||
/** 4000 ms standby. */
|
||||
STANDBY_MS_4000 = 0x07
|
||||
};
|
||||
|
||||
public:
|
||||
bool begin(TwoWire *wire = &Wire, uint8_t addr = BMP280_I2C_ADDR,
|
||||
uint8_t sda = 21, uint8_t scl = 22, long freq = 400000U);
|
||||
bool update();
|
||||
|
||||
float pressure = 0;
|
||||
float cTemp = 0;
|
||||
float altitude = 0;
|
||||
|
||||
void reset(void);
|
||||
uint8_t getStatus(void);
|
||||
|
||||
float readTemperature();
|
||||
float readPressure(void);
|
||||
float readAltitude(float seaLevelhPa = 1013.25);
|
||||
float seaLevelForAltitude(float altitude, float atmospheric);
|
||||
float waterBoilingPoint(float pressure);
|
||||
bool takeForcedMeasurement();
|
||||
|
||||
void setSampling(sensor_mode mode = MODE_NORMAL,
|
||||
sensor_sampling tempSampling = SAMPLING_X16,
|
||||
sensor_sampling pressSampling = SAMPLING_X16,
|
||||
sensor_filter filter = FILTER_OFF,
|
||||
standby_duration duration = STANDBY_MS_1);
|
||||
|
||||
private:
|
||||
/** Encapsulates the config register */
|
||||
struct config {
|
||||
/** Initialize to power-on-reset state */
|
||||
config()
|
||||
: t_sb(STANDBY_MS_1), filter(FILTER_OFF), none(0), spi3w_en(0) {
|
||||
}
|
||||
/** Inactive duration (standby time) in normal mode */
|
||||
unsigned int t_sb : 3;
|
||||
/** Filter settings */
|
||||
unsigned int filter : 3;
|
||||
/** Unused - don't set */
|
||||
unsigned int none : 1;
|
||||
/** Enables 3-wire SPI */
|
||||
unsigned int spi3w_en : 1;
|
||||
/** Used to retrieve the assembled config register's byte value. */
|
||||
unsigned int get() {
|
||||
return (t_sb << 5) | (filter << 2) | spi3w_en;
|
||||
}
|
||||
};
|
||||
|
||||
/** Encapsulates trhe ctrl_meas register */
|
||||
struct ctrl_meas {
|
||||
/** Initialize to power-on-reset state */
|
||||
ctrl_meas()
|
||||
: osrs_t(SAMPLING_NONE), osrs_p(SAMPLING_NONE), mode(MODE_SLEEP) {
|
||||
}
|
||||
/** Temperature oversampling. */
|
||||
unsigned int osrs_t : 3;
|
||||
/** Pressure oversampling. */
|
||||
unsigned int osrs_p : 3;
|
||||
/** Device mode */
|
||||
unsigned int mode : 2;
|
||||
/** Used to retrieve the assembled ctrl_meas register's byte value. */
|
||||
unsigned int get() {
|
||||
return (osrs_t << 5) | (osrs_p << 2) | mode;
|
||||
}
|
||||
};
|
||||
|
||||
void readCoefficients(void);
|
||||
uint8_t spixfer(uint8_t x);
|
||||
void write8(byte reg, byte value);
|
||||
uint8_t read8(byte reg);
|
||||
uint16_t read16(byte reg);
|
||||
uint32_t read24(byte reg);
|
||||
int16_t readS16(byte reg);
|
||||
uint16_t read16_LE(byte reg);
|
||||
int16_t readS16_LE(byte reg);
|
||||
|
||||
int32_t t_fine;
|
||||
bmp280_calib_data _bmp280_calib;
|
||||
config _configReg;
|
||||
ctrl_meas _measReg;
|
||||
|
||||
private:
|
||||
uint8_t _addr;
|
||||
I2C_Class _i2c;
|
||||
};
|
||||
|
||||
#endif
|
||||
23
libraries/M5Unit-ENV/src/DHT12.cpp
Normal file
23
libraries/M5Unit-ENV/src/DHT12.cpp
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#include "DHT12.h"
|
||||
|
||||
bool DHT12::begin(TwoWire* wire, uint8_t addr, uint8_t sda, uint8_t scl,
|
||||
long freq) {
|
||||
_i2c.begin(wire, sda, scl, freq);
|
||||
_addr = addr;
|
||||
return _i2c.exist(_addr);
|
||||
}
|
||||
|
||||
bool DHT12::update() {
|
||||
uint8_t datos[5] = {0};
|
||||
if (_i2c.readBytes(_addr, 0, datos, 5)) {
|
||||
if (datos[4] != (datos[0] + datos[1] + datos[2] + datos[3])) {
|
||||
return false;
|
||||
}
|
||||
cTemp = (datos[2] + (float)datos[3] / 10);
|
||||
fTemp = ((datos[2] + (float)datos[3] / 10) * 1.8 + 32);
|
||||
kTemp = (datos[2] + (float)datos[3] / 10) + 273.15;
|
||||
humidity = (datos[0] + (float)datos[1] / 10);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
26
libraries/M5Unit-ENV/src/DHT12.h
Normal file
26
libraries/M5Unit-ENV/src/DHT12.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
#ifndef _DHT12_H
|
||||
#define _DHT12_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Wire.h"
|
||||
#include "I2C_Class.h"
|
||||
|
||||
#define DHT12_I2C_ADDR 0x5c
|
||||
|
||||
class DHT12 {
|
||||
public:
|
||||
bool begin(TwoWire* wire = &Wire, uint8_t addr = DHT12_I2C_ADDR,
|
||||
uint8_t sda = 21, uint8_t scl = 22, long freq = 400000U);
|
||||
bool update();
|
||||
float cTemp = 0;
|
||||
float fTemp = 0;
|
||||
float kTemp = 0;
|
||||
float humidity = 0;
|
||||
|
||||
private:
|
||||
uint8_t _addr;
|
||||
I2C_Class _i2c;
|
||||
};
|
||||
|
||||
#endif
|
||||
93
libraries/M5Unit-ENV/src/I2C_Class.cpp
Normal file
93
libraries/M5Unit-ENV/src/I2C_Class.cpp
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#include "I2C_Class.h"
|
||||
|
||||
void I2C_Class::begin(TwoWire *wire, uint8_t sda, uint8_t scl, long freq) {
|
||||
_wire = wire;
|
||||
_sda = sda;
|
||||
_scl = scl;
|
||||
_freq = freq;
|
||||
_wire->end();
|
||||
_wire->begin(static_cast<int>(_sda), _scl, freq);
|
||||
}
|
||||
|
||||
bool I2C_Class::exist(uint8_t addr) {
|
||||
int error;
|
||||
_wire->beginTransmission(addr);
|
||||
error = _wire->endTransmission();
|
||||
if (error == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool I2C_Class::writeBytes(uint8_t addr, uint8_t reg, uint8_t *buffer,
|
||||
uint8_t length) {
|
||||
_wire->beginTransmission(addr);
|
||||
_wire->write(reg);
|
||||
_wire->write(buffer, length);
|
||||
if (_wire->endTransmission() == 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool I2C_Class::readBytes(uint8_t addr, uint8_t reg, uint8_t *buffer,
|
||||
uint8_t length) {
|
||||
uint8_t index = 0;
|
||||
_wire->beginTransmission(addr);
|
||||
_wire->write(reg);
|
||||
_wire->endTransmission();
|
||||
if (_wire->requestFrom(addr, length)) {
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
buffer[index++] = _wire->read();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool I2C_Class::readU16(uint8_t addr, uint8_t reg_addr, uint16_t *value) {
|
||||
uint8_t read_buf[2] = {0x00, 0x00};
|
||||
bool result = readBytes(addr, reg_addr, read_buf, 2);
|
||||
*value = (read_buf[0] << 8) | read_buf[1];
|
||||
return result;
|
||||
}
|
||||
|
||||
bool I2C_Class::writeU16(uint8_t addr, uint8_t reg_addr, uint16_t value) {
|
||||
uint8_t write_buf[2];
|
||||
write_buf[0] = value >> 8;
|
||||
write_buf[1] = value & 0xff;
|
||||
return writeBytes(addr, reg_addr, write_buf, 2);
|
||||
}
|
||||
|
||||
bool I2C_Class::writeByte(uint8_t addr, uint8_t reg, uint8_t data) {
|
||||
_wire->beginTransmission(addr);
|
||||
_wire->write(reg);
|
||||
_wire->write(data);
|
||||
if (_wire->endTransmission() == 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t I2C_Class::readByte(uint8_t addr, uint8_t reg) {
|
||||
_wire->beginTransmission(addr);
|
||||
_wire->write(reg);
|
||||
_wire->endTransmission();
|
||||
|
||||
if (_wire->requestFrom(addr, 1)) {
|
||||
return _wire->read();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool I2C_Class::writeBitOn(uint8_t addr, uint8_t reg, uint8_t data) {
|
||||
uint8_t temp;
|
||||
uint8_t write_back;
|
||||
temp = readByte(addr, reg);
|
||||
write_back = (temp | data);
|
||||
return (writeByte(addr, reg, write_back));
|
||||
}
|
||||
|
||||
bool I2C_Class::writeBitOff(uint8_t addr, uint8_t reg, uint8_t data) {
|
||||
uint8_t temp;
|
||||
uint8_t write_back;
|
||||
temp = readByte(addr, reg);
|
||||
write_back = (temp & (~data));
|
||||
return (writeByte(addr, reg, write_back));
|
||||
}
|
||||
30
libraries/M5Unit-ENV/src/I2C_Class.h
Normal file
30
libraries/M5Unit-ENV/src/I2C_Class.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef _I2C_DEVICE_BUS_
|
||||
#define _I2C_DEVICE_BUS_
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Wire.h"
|
||||
|
||||
class I2C_Class {
|
||||
private:
|
||||
TwoWire* _wire;
|
||||
uint8_t _scl;
|
||||
uint8_t _sda;
|
||||
long _freq;
|
||||
|
||||
public:
|
||||
void begin(TwoWire* wire, uint8_t sda, uint8_t scl, long freq = 100000);
|
||||
bool exist(uint8_t addr);
|
||||
|
||||
bool writeBytes(uint8_t addr, uint8_t reg, uint8_t* buffer, uint8_t length);
|
||||
bool readBytes(uint8_t addr, uint8_t reg, uint8_t* buffer, uint8_t length);
|
||||
|
||||
bool readU16(uint8_t addr, uint8_t reg_addr, uint16_t* value);
|
||||
bool writeU16(uint8_t addr, uint8_t reg_addr, uint16_t value);
|
||||
|
||||
bool writeByte(uint8_t addr, uint8_t reg, uint8_t data);
|
||||
uint8_t readByte(uint8_t addr, uint8_t reg);
|
||||
bool writeBitOn(uint8_t addr, uint8_t reg, uint8_t data);
|
||||
bool writeBitOff(uint8_t addr, uint8_t reg, uint8_t data);
|
||||
};
|
||||
|
||||
#endif
|
||||
16
libraries/M5Unit-ENV/src/M5UnitENV.h
Normal file
16
libraries/M5Unit-ENV/src/M5UnitENV.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#if defined(M5_UNIT_UNIFIED_ENV_HPP)
|
||||
#error "DO NOT USE it at the same time as M5UnitUnified libraries"
|
||||
#endif
|
||||
|
||||
#ifndef _M5_UNIT_ENV_H_
|
||||
#define _M5_UNIT_ENV_H_
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "DHT12.h"
|
||||
#include "QMP6988.h"
|
||||
#include "SHT3X.h"
|
||||
#include "SHT4X.h"
|
||||
#include "BMP280.h"
|
||||
#include "SCD4X.h"
|
||||
|
||||
#endif
|
||||
17
libraries/M5Unit-ENV/src/M5UnitUnifiedENV.h
Normal file
17
libraries/M5Unit-ENV/src/M5UnitUnifiedENV.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file M5UnitUnifiedENV.h
|
||||
@brief Main header of M5UnitENV using M5UnitUnfied
|
||||
*/
|
||||
#ifndef M5_UNIT_UNIFIED_ENV_H
|
||||
#define M5_UNIT_UNIFIED_ENV_H
|
||||
#ifdef __cplusplus
|
||||
#include "M5UnitUnifiedENV.hpp"
|
||||
#else
|
||||
#error M5UnitUnifiedENV requires a C++ compiler, please change file extension to .cc or .cpp
|
||||
#endif
|
||||
#endif
|
||||
55
libraries/M5Unit-ENV/src/M5UnitUnifiedENV.hpp
Normal file
55
libraries/M5Unit-ENV/src/M5UnitUnifiedENV.hpp
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file M5UnitUnifiedENV.hpp
|
||||
@brief Main header of M5UnitENV using M5UnitUnified
|
||||
|
||||
@mainpage M5Unit-ENV
|
||||
Library for UnitENV using M5UnitUnified.
|
||||
*/
|
||||
#if defined(_M5_UNIT_ENV_H_)
|
||||
#error "DO NOT USE it at the same time as conventional libraries"
|
||||
#endif
|
||||
|
||||
#ifndef M5_UNIT_UNIFIED_ENV_HPP
|
||||
#define M5_UNIT_UNIFIED_ENV_HPP
|
||||
|
||||
// CO2
|
||||
#include "unit/unit_SCD40.hpp"
|
||||
#include "unit/unit_SCD41.hpp"
|
||||
// ENVIII
|
||||
#include "unit/unit_SHT30.hpp"
|
||||
#include "unit/unit_QMP6988.hpp"
|
||||
#include "unit/unit_ENV3.hpp"
|
||||
// ENVPro
|
||||
#include "unit/unit_BME688.hpp"
|
||||
// TVOC
|
||||
#include "unit/unit_SGP30.hpp"
|
||||
// ENVIV
|
||||
#include "unit/unit_SHT40.hpp"
|
||||
#include "unit/unit_BMP280.hpp"
|
||||
#include "unit/unit_ENV4.hpp"
|
||||
|
||||
/*!
|
||||
@namespace m5
|
||||
@brief Top level namespace of M5stack
|
||||
*/
|
||||
namespace m5 {
|
||||
|
||||
/*!
|
||||
@namespace unit
|
||||
@brief Unit-related namespace
|
||||
*/
|
||||
namespace unit {
|
||||
|
||||
using UnitCO2 = m5::unit::UnitSCD40;
|
||||
using UnitCO2L = m5::unit::UnitSCD41;
|
||||
using UnitENVPro = m5::unit::UnitBME688;
|
||||
using UnitTVOC = m5::unit::UnitSGP30;
|
||||
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
#endif
|
||||
353
libraries/M5Unit-ENV/src/QMP6988.cpp
Normal file
353
libraries/M5Unit-ENV/src/QMP6988.cpp
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
#include <math.h>
|
||||
#include "stdint.h"
|
||||
#include "stdio.h"
|
||||
#include "QMP6988.h"
|
||||
|
||||
// DISABLE LOG
|
||||
#define QMP6988_LOG(format...)
|
||||
#define QMP6988_ERR(format...)
|
||||
|
||||
// ENABLE LOG
|
||||
// #define QMP6988_LOG Serial.printf
|
||||
// #define QMP6988_ERR Serial.printf
|
||||
|
||||
int QMP6988::getCalibrationData() {
|
||||
int status = 0;
|
||||
// BITFIELDS temp_COE;
|
||||
uint8_t a_data_uint8_tr[QMP6988_CALIBRATION_DATA_LENGTH] = {0};
|
||||
int len;
|
||||
|
||||
for (len = 0; len < QMP6988_CALIBRATION_DATA_LENGTH; len += 1) {
|
||||
status = _i2c.readBytes(_addr, QMP6988_CALIBRATION_DATA_START + len,
|
||||
&a_data_uint8_tr[len], 1);
|
||||
if (status == 0) {
|
||||
QMP6988_LOG("qmp6988 read 0xA0 error!");
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
qmp6988.qmp6988_cali.COE_a0 =
|
||||
(QMP6988_S32_t)(((a_data_uint8_tr[18] << SHIFT_LEFT_12_POSITION) |
|
||||
(a_data_uint8_tr[19] << SHIFT_LEFT_4_POSITION) |
|
||||
(a_data_uint8_tr[24] & 0x0f))
|
||||
<< 12);
|
||||
qmp6988.qmp6988_cali.COE_a0 = qmp6988.qmp6988_cali.COE_a0 >> 12;
|
||||
|
||||
qmp6988.qmp6988_cali.COE_a1 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[20]) << SHIFT_LEFT_8_POSITION) |
|
||||
a_data_uint8_tr[21]);
|
||||
qmp6988.qmp6988_cali.COE_a2 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[22]) << SHIFT_LEFT_8_POSITION) |
|
||||
a_data_uint8_tr[23]);
|
||||
|
||||
qmp6988.qmp6988_cali.COE_b00 =
|
||||
(QMP6988_S32_t)(((a_data_uint8_tr[0] << SHIFT_LEFT_12_POSITION) |
|
||||
(a_data_uint8_tr[1] << SHIFT_LEFT_4_POSITION) |
|
||||
((a_data_uint8_tr[24] & 0xf0) >>
|
||||
SHIFT_RIGHT_4_POSITION))
|
||||
<< 12);
|
||||
qmp6988.qmp6988_cali.COE_b00 = qmp6988.qmp6988_cali.COE_b00 >> 12;
|
||||
|
||||
qmp6988.qmp6988_cali.COE_bt1 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[2]) << SHIFT_LEFT_8_POSITION) |
|
||||
a_data_uint8_tr[3]);
|
||||
qmp6988.qmp6988_cali.COE_bt2 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[4]) << SHIFT_LEFT_8_POSITION) |
|
||||
a_data_uint8_tr[5]);
|
||||
qmp6988.qmp6988_cali.COE_bp1 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[6]) << SHIFT_LEFT_8_POSITION) |
|
||||
a_data_uint8_tr[7]);
|
||||
qmp6988.qmp6988_cali.COE_b11 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[8]) << SHIFT_LEFT_8_POSITION) |
|
||||
a_data_uint8_tr[9]);
|
||||
qmp6988.qmp6988_cali.COE_bp2 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[10]) << SHIFT_LEFT_8_POSITION) |
|
||||
a_data_uint8_tr[11]);
|
||||
qmp6988.qmp6988_cali.COE_b12 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[12]) << SHIFT_LEFT_8_POSITION) |
|
||||
a_data_uint8_tr[13]);
|
||||
qmp6988.qmp6988_cali.COE_b21 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[14]) << SHIFT_LEFT_8_POSITION) |
|
||||
a_data_uint8_tr[15]);
|
||||
qmp6988.qmp6988_cali.COE_bp3 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[16]) << SHIFT_LEFT_8_POSITION) |
|
||||
a_data_uint8_tr[17]);
|
||||
|
||||
QMP6988_LOG("<-----------calibration data-------------->\r\n");
|
||||
QMP6988_LOG("COE_a0[%d] COE_a1[%d] COE_a2[%d] COE_b00[%d]\r\n",
|
||||
qmp6988.qmp6988_cali.COE_a0, qmp6988.qmp6988_cali.COE_a1,
|
||||
qmp6988.qmp6988_cali.COE_a2, qmp6988.qmp6988_cali.COE_b00);
|
||||
QMP6988_LOG("COE_bt1[%d] COE_bt2[%d] COE_bp1[%d] COE_b11[%d]\r\n",
|
||||
qmp6988.qmp6988_cali.COE_bt1, qmp6988.qmp6988_cali.COE_bt2,
|
||||
qmp6988.qmp6988_cali.COE_bp1, qmp6988.qmp6988_cali.COE_b11);
|
||||
QMP6988_LOG("COE_bp2[%d] COE_b12[%d] COE_b21[%d] COE_bp3[%d]\r\n",
|
||||
qmp6988.qmp6988_cali.COE_bp2, qmp6988.qmp6988_cali.COE_b12,
|
||||
qmp6988.qmp6988_cali.COE_b21, qmp6988.qmp6988_cali.COE_bp3);
|
||||
QMP6988_LOG("<-----------calibration data-------------->\r\n");
|
||||
|
||||
qmp6988.ik.a0 = qmp6988.qmp6988_cali.COE_a0; // 20Q4
|
||||
qmp6988.ik.b00 = qmp6988.qmp6988_cali.COE_b00; // 20Q4
|
||||
|
||||
qmp6988.ik.a1 = 3608L * (QMP6988_S32_t)qmp6988.qmp6988_cali.COE_a1 -
|
||||
1731677965L; // 31Q23
|
||||
qmp6988.ik.a2 = 16889L * (QMP6988_S32_t)qmp6988.qmp6988_cali.COE_a2 -
|
||||
87619360L; // 30Q47
|
||||
|
||||
qmp6988.ik.bt1 = 2982L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_bt1 +
|
||||
107370906L; // 28Q15
|
||||
qmp6988.ik.bt2 = 329854L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_bt2 +
|
||||
108083093L; // 34Q38
|
||||
qmp6988.ik.bp1 = 19923L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_bp1 +
|
||||
1133836764L; // 31Q20
|
||||
qmp6988.ik.b11 = 2406L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_b11 +
|
||||
118215883L; // 28Q34
|
||||
qmp6988.ik.bp2 = 3079L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_bp2 -
|
||||
181579595L; // 29Q43
|
||||
qmp6988.ik.b12 = 6846L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_b12 +
|
||||
85590281L; // 29Q53
|
||||
qmp6988.ik.b21 = 13836L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_b21 +
|
||||
79333336L; // 29Q60
|
||||
qmp6988.ik.bp3 = 2915L * (QMP6988_S64_t)qmp6988.qmp6988_cali.COE_bp3 +
|
||||
157155561L; // 28Q65
|
||||
QMP6988_LOG("<----------- int calibration data -------------->\r\n");
|
||||
QMP6988_LOG("a0[%d] a1[%d] a2[%d] b00[%d]\r\n", qmp6988.ik.a0,
|
||||
qmp6988.ik.a1, qmp6988.ik.a2, qmp6988.ik.b00);
|
||||
QMP6988_LOG("bt1[%lld] bt2[%lld] bp1[%lld] b11[%lld]\r\n",
|
||||
qmp6988.ik.bt1, qmp6988.ik.bt2, qmp6988.ik.bp1, qmp6988.ik.b11);
|
||||
QMP6988_LOG("bp2[%lld] b12[%lld] b21[%lld] bp3[%lld]\r\n",
|
||||
qmp6988.ik.bp2, qmp6988.ik.b12, qmp6988.ik.b21, qmp6988.ik.bp3);
|
||||
QMP6988_LOG("<----------- int calibration data -------------->\r\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
QMP6988_S16_t QMP6988::convTx02e(qmp6988_ik_data_t* ik, QMP6988_S32_t dt) {
|
||||
QMP6988_S16_t ret;
|
||||
QMP6988_S64_t wk1, wk2;
|
||||
|
||||
// wk1: 60Q4 // bit size
|
||||
wk1 = ((QMP6988_S64_t)ik->a1 * (QMP6988_S64_t)dt); // 31Q23+24-1=54 (54Q23)
|
||||
wk2 = ((QMP6988_S64_t)ik->a2 * (QMP6988_S64_t)dt) >>
|
||||
14; // 30Q47+24-1=53 (39Q33)
|
||||
wk2 = (wk2 * (QMP6988_S64_t)dt) >> 10; // 39Q33+24-1=62 (52Q23)
|
||||
wk2 = ((wk1 + wk2) / 32767) >> 19; // 54,52->55Q23 (20Q04)
|
||||
ret = (QMP6988_S16_t)((ik->a0 + wk2) >> 4); // 21Q4 -> 17Q0
|
||||
return ret;
|
||||
}
|
||||
|
||||
QMP6988_S32_t QMP6988::getPressure02e(qmp6988_ik_data_t* ik, QMP6988_S32_t dp,
|
||||
QMP6988_S16_t tx) {
|
||||
QMP6988_S32_t ret;
|
||||
QMP6988_S64_t wk1, wk2, wk3;
|
||||
|
||||
// wk1 = 48Q16 // bit size
|
||||
wk1 =
|
||||
((QMP6988_S64_t)ik->bt1 * (QMP6988_S64_t)tx); // 28Q15+16-1=43 (43Q15)
|
||||
wk2 = ((QMP6988_S64_t)ik->bp1 * (QMP6988_S64_t)dp) >>
|
||||
5; // 31Q20+24-1=54 (49Q15)
|
||||
wk1 += wk2; // 43,49->50Q15
|
||||
wk2 = ((QMP6988_S64_t)ik->bt2 * (QMP6988_S64_t)tx) >>
|
||||
1; // 34Q38+16-1=49 (48Q37)
|
||||
wk2 = (wk2 * (QMP6988_S64_t)tx) >> 8; // 48Q37+16-1=63 (55Q29)
|
||||
wk3 = wk2; // 55Q29
|
||||
wk2 = ((QMP6988_S64_t)ik->b11 * (QMP6988_S64_t)tx) >>
|
||||
4; // 28Q34+16-1=43 (39Q30)
|
||||
wk2 = (wk2 * (QMP6988_S64_t)dp) >> 1; // 39Q30+24-1=62 (61Q29)
|
||||
wk3 += wk2; // 55,61->62Q29
|
||||
wk2 = ((QMP6988_S64_t)ik->bp2 * (QMP6988_S64_t)dp) >>
|
||||
13; // 29Q43+24-1=52 (39Q30)
|
||||
wk2 = (wk2 * (QMP6988_S64_t)dp) >> 1; // 39Q30+24-1=62 (61Q29)
|
||||
wk3 += wk2; // 62,61->63Q29
|
||||
wk1 += wk3 >> 14; // Q29 >> 14 -> Q15
|
||||
wk2 =
|
||||
((QMP6988_S64_t)ik->b12 * (QMP6988_S64_t)tx); // 29Q53+16-1=45 (45Q53)
|
||||
wk2 = (wk2 * (QMP6988_S64_t)tx) >> 22; // 45Q53+16-1=61 (39Q31)
|
||||
wk2 = (wk2 * (QMP6988_S64_t)dp) >> 1; // 39Q31+24-1=62 (61Q30)
|
||||
wk3 = wk2; // 61Q30
|
||||
wk2 = ((QMP6988_S64_t)ik->b21 * (QMP6988_S64_t)tx) >>
|
||||
6; // 29Q60+16-1=45 (39Q54)
|
||||
wk2 = (wk2 * (QMP6988_S64_t)dp) >> 23; // 39Q54+24-1=62 (39Q31)
|
||||
wk2 = (wk2 * (QMP6988_S64_t)dp) >> 1; // 39Q31+24-1=62 (61Q20)
|
||||
wk3 += wk2; // 61,61->62Q30
|
||||
wk2 = ((QMP6988_S64_t)ik->bp3 * (QMP6988_S64_t)dp) >>
|
||||
12; // 28Q65+24-1=51 (39Q53)
|
||||
wk2 = (wk2 * (QMP6988_S64_t)dp) >> 23; // 39Q53+24-1=62 (39Q30)
|
||||
wk2 = (wk2 * (QMP6988_S64_t)dp); // 39Q30+24-1=62 (62Q30)
|
||||
wk3 += wk2; // 62,62->63Q30
|
||||
wk1 += wk3 >> 15; // Q30 >> 15 = Q15
|
||||
wk1 /= 32767L;
|
||||
wk1 >>= 11; // Q15 >> 7 = Q4
|
||||
wk1 += ik->b00; // Q4 + 20Q4
|
||||
// wk1 >>= 4; // 28Q4 -> 24Q0
|
||||
ret = (QMP6988_S32_t)wk1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void QMP6988::reset() {
|
||||
uint8_t ret = 0;
|
||||
|
||||
ret = _i2c.writeByte(_addr, QMP6988_RESET_REG, 0xe6);
|
||||
if (ret == 0) {
|
||||
QMP6988_LOG("reset fail!!! \r\n");
|
||||
}
|
||||
delay(20);
|
||||
ret = _i2c.writeByte(_addr, QMP6988_RESET_REG, 0x00);
|
||||
}
|
||||
|
||||
void QMP6988::setpPowermode(int power_mode) {
|
||||
uint8_t data;
|
||||
|
||||
QMP6988_LOG("qmp_set_powermode %d \r\n", power_mode);
|
||||
|
||||
qmp6988.power_mode = power_mode;
|
||||
_i2c.readBytes(_addr, QMP6988_CTRLMEAS_REG, &data, 1);
|
||||
data = data & 0xfc;
|
||||
if (power_mode == QMP6988_SLEEP_MODE) {
|
||||
data |= 0x00;
|
||||
} else if (power_mode == QMP6988_FORCED_MODE) {
|
||||
data |= 0x01;
|
||||
} else if (power_mode == QMP6988_NORMAL_MODE) {
|
||||
data |= 0x03;
|
||||
}
|
||||
_i2c.writeByte(_addr, QMP6988_CTRLMEAS_REG, data);
|
||||
|
||||
QMP6988_LOG("qmp_set_powermode 0xf4=0x%x \r\n", data);
|
||||
|
||||
delay(20);
|
||||
}
|
||||
|
||||
void QMP6988::setFilter(unsigned char filter) {
|
||||
uint8_t data;
|
||||
|
||||
data = (filter & 0x03);
|
||||
_i2c.writeByte(_addr, QMP6988_CONFIG_REG, data);
|
||||
|
||||
delay(20);
|
||||
}
|
||||
|
||||
void QMP6988::setOversamplingP(unsigned char oversampling_p) {
|
||||
uint8_t data;
|
||||
|
||||
_i2c.readBytes(_addr, QMP6988_CTRLMEAS_REG, &data, 1);
|
||||
data &= 0xe3;
|
||||
data |= (oversampling_p << 2);
|
||||
_i2c.writeByte(_addr, QMP6988_CTRLMEAS_REG, data);
|
||||
delay(20);
|
||||
}
|
||||
|
||||
void QMP6988::setOversamplingT(unsigned char oversampling_t) {
|
||||
uint8_t data;
|
||||
|
||||
_i2c.readBytes(_addr, QMP6988_CTRLMEAS_REG, &data, 1);
|
||||
data &= 0x1f;
|
||||
data |= (oversampling_t << 5);
|
||||
_i2c.writeByte(_addr, QMP6988_CTRLMEAS_REG, data);
|
||||
delay(20);
|
||||
}
|
||||
|
||||
float QMP6988::calcAltitude(float pressure, float temp) {
|
||||
float altitude;
|
||||
|
||||
altitude =
|
||||
(pow((101325 / pressure), 1 / 5.257) - 1) * (temp + 273.15) / 0.0065;
|
||||
QMP6988_LOG("altitude = %f\r\n", altitude);
|
||||
return altitude;
|
||||
}
|
||||
|
||||
float QMP6988::calcPressure() {
|
||||
uint8_t err = 0;
|
||||
QMP6988_U32_t P_read, T_read;
|
||||
QMP6988_S32_t P_raw, T_raw;
|
||||
uint8_t a_data_uint8_tr[6] = {0};
|
||||
QMP6988_S32_t T_int, P_int;
|
||||
|
||||
// press
|
||||
err = _i2c.readBytes(_addr, QMP6988_PRESSURE_MSB_REG, a_data_uint8_tr, 6);
|
||||
if (err == 0) {
|
||||
QMP6988_LOG("qmp6988 read press raw error! \r\n");
|
||||
return 0.0f;
|
||||
}
|
||||
P_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[0]))
|
||||
<< SHIFT_LEFT_16_POSITION) |
|
||||
(((QMP6988_U16_t)(a_data_uint8_tr[1]))
|
||||
<< SHIFT_LEFT_8_POSITION) |
|
||||
(a_data_uint8_tr[2]));
|
||||
P_raw = (QMP6988_S32_t)(P_read - SUBTRACTOR);
|
||||
|
||||
T_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[3]))
|
||||
<< SHIFT_LEFT_16_POSITION) |
|
||||
(((QMP6988_U16_t)(a_data_uint8_tr[4]))
|
||||
<< SHIFT_LEFT_8_POSITION) |
|
||||
(a_data_uint8_tr[5]));
|
||||
T_raw = (QMP6988_S32_t)(T_read - SUBTRACTOR);
|
||||
|
||||
T_int = convTx02e(&(qmp6988.ik), T_raw);
|
||||
P_int = getPressure02e(&(qmp6988.ik), P_raw, T_int);
|
||||
qmp6988.temperature = (float)T_int / 256.0f;
|
||||
qmp6988.pressure = (float)P_int / 16.0f;
|
||||
|
||||
return qmp6988.pressure;
|
||||
}
|
||||
|
||||
float QMP6988::calcTemperature() {
|
||||
uint8_t err = 0;
|
||||
QMP6988_U32_t P_read, T_read;
|
||||
QMP6988_S32_t P_raw, T_raw;
|
||||
uint8_t a_data_uint8_tr[6] = {0};
|
||||
QMP6988_S32_t T_int, P_int;
|
||||
|
||||
// press
|
||||
err = _i2c.readBytes(_addr, QMP6988_PRESSURE_MSB_REG, a_data_uint8_tr, 6);
|
||||
if (err == 0) {
|
||||
QMP6988_LOG("qmp6988 read press raw error! \r\n");
|
||||
return 0.0f;
|
||||
}
|
||||
P_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[0]))
|
||||
<< SHIFT_LEFT_16_POSITION) |
|
||||
(((QMP6988_U16_t)(a_data_uint8_tr[1]))
|
||||
<< SHIFT_LEFT_8_POSITION) |
|
||||
(a_data_uint8_tr[2]));
|
||||
P_raw = (QMP6988_S32_t)(P_read - SUBTRACTOR);
|
||||
|
||||
// temp
|
||||
err =
|
||||
_i2c.readBytes(_addr, QMP6988_TEMPERATURE_MSB_REG, a_data_uint8_tr, 3);
|
||||
if (err == 0) {
|
||||
QMP6988_LOG("qmp6988 read temp raw error! \n");
|
||||
}
|
||||
T_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[3]))
|
||||
<< SHIFT_LEFT_16_POSITION) |
|
||||
(((QMP6988_U16_t)(a_data_uint8_tr[4]))
|
||||
<< SHIFT_LEFT_8_POSITION) |
|
||||
(a_data_uint8_tr[5]));
|
||||
T_raw = (QMP6988_S32_t)(T_read - SUBTRACTOR);
|
||||
|
||||
T_int = convTx02e(&(qmp6988.ik), T_raw);
|
||||
P_int = getPressure02e(&(qmp6988.ik), P_raw, T_int);
|
||||
qmp6988.temperature = (float)T_int / 256.0f;
|
||||
qmp6988.pressure = (float)P_int / 16.0f;
|
||||
|
||||
return qmp6988.temperature;
|
||||
}
|
||||
|
||||
bool QMP6988::begin(TwoWire* wire, uint8_t addr, uint8_t sda, uint8_t scl,
|
||||
long freq) {
|
||||
_i2c.begin(wire, sda, scl, freq);
|
||||
_addr = addr;
|
||||
if (!_i2c.exist(_addr)) {
|
||||
return false;
|
||||
}
|
||||
reset();
|
||||
getCalibrationData();
|
||||
setpPowermode(QMP6988_NORMAL_MODE);
|
||||
setFilter(QMP6988_FILTERCOEFF_4);
|
||||
setOversamplingP(QMP6988_OVERSAMPLING_8X);
|
||||
setOversamplingT(QMP6988_OVERSAMPLING_1X);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QMP6988::update() {
|
||||
pressure = calcPressure();
|
||||
cTemp = calcTemperature();
|
||||
altitude = calcAltitude(pressure, cTemp);
|
||||
return true;
|
||||
}
|
||||
153
libraries/M5Unit-ENV/src/QMP6988.h
Normal file
153
libraries/M5Unit-ENV/src/QMP6988.h
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
#ifndef __QMP6988_H
|
||||
#define __QMP6988_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "Wire.h"
|
||||
#include "I2C_Class.h"
|
||||
|
||||
#define QMP6988_SLAVE_ADDRESS_L (0x70)
|
||||
#define QMP6988_SLAVE_ADDRESS_H (0x56)
|
||||
|
||||
#define QMP6988_U16_t unsigned short
|
||||
#define QMP6988_S16_t short
|
||||
#define QMP6988_U32_t unsigned int
|
||||
#define QMP6988_S32_t int
|
||||
#define QMP6988_U64_t unsigned long long
|
||||
#define QMP6988_S64_t long long
|
||||
|
||||
#define QMP6988_CHIP_ID 0x5C
|
||||
|
||||
#define QMP6988_CHIP_ID_REG 0xD1
|
||||
#define QMP6988_RESET_REG 0xE0 /* Device reset register */
|
||||
#define QMP6988_DEVICE_STAT_REG 0xF3 /* Device state register */
|
||||
#define QMP6988_CTRLMEAS_REG 0xF4 /* Measurement Condition Control Register */
|
||||
/* data */
|
||||
#define QMP6988_PRESSURE_MSB_REG 0xF7 /* Pressure MSB Register */
|
||||
#define QMP6988_TEMPERATURE_MSB_REG 0xFA /* Temperature MSB Reg */
|
||||
|
||||
/* compensation calculation */
|
||||
#define QMP6988_CALIBRATION_DATA_START \
|
||||
0xA0 /* QMP6988 compensation coefficients */
|
||||
#define QMP6988_CALIBRATION_DATA_LENGTH 25
|
||||
|
||||
#define SHIFT_RIGHT_4_POSITION 4
|
||||
#define SHIFT_LEFT_2_POSITION 2
|
||||
#define SHIFT_LEFT_4_POSITION 4
|
||||
#define SHIFT_LEFT_5_POSITION 5
|
||||
#define SHIFT_LEFT_8_POSITION 8
|
||||
#define SHIFT_LEFT_12_POSITION 12
|
||||
#define SHIFT_LEFT_16_POSITION 16
|
||||
|
||||
/* power mode */
|
||||
#define QMP6988_SLEEP_MODE 0x00
|
||||
#define QMP6988_FORCED_MODE 0x01
|
||||
#define QMP6988_NORMAL_MODE 0x03
|
||||
|
||||
#define QMP6988_CTRLMEAS_REG_MODE__POS 0
|
||||
#define QMP6988_CTRLMEAS_REG_MODE__MSK 0x03
|
||||
#define QMP6988_CTRLMEAS_REG_MODE__LEN 2
|
||||
|
||||
/* oversampling */
|
||||
#define QMP6988_OVERSAMPLING_SKIPPED 0x00
|
||||
#define QMP6988_OVERSAMPLING_1X 0x01
|
||||
#define QMP6988_OVERSAMPLING_2X 0x02
|
||||
#define QMP6988_OVERSAMPLING_4X 0x03
|
||||
#define QMP6988_OVERSAMPLING_8X 0x04
|
||||
#define QMP6988_OVERSAMPLING_16X 0x05
|
||||
#define QMP6988_OVERSAMPLING_32X 0x06
|
||||
#define QMP6988_OVERSAMPLING_64X 0x07
|
||||
|
||||
#define QMP6988_CTRLMEAS_REG_OSRST__POS 5
|
||||
#define QMP6988_CTRLMEAS_REG_OSRST__MSK 0xE0
|
||||
#define QMP6988_CTRLMEAS_REG_OSRST__LEN 3
|
||||
|
||||
#define QMP6988_CTRLMEAS_REG_OSRSP__POS 2
|
||||
#define QMP6988_CTRLMEAS_REG_OSRSP__MSK 0x1C
|
||||
#define QMP6988_CTRLMEAS_REG_OSRSP__LEN 3
|
||||
|
||||
/* filter */
|
||||
#define QMP6988_FILTERCOEFF_OFF 0x00
|
||||
#define QMP6988_FILTERCOEFF_2 0x01
|
||||
#define QMP6988_FILTERCOEFF_4 0x02
|
||||
#define QMP6988_FILTERCOEFF_8 0x03
|
||||
#define QMP6988_FILTERCOEFF_16 0x04
|
||||
#define QMP6988_FILTERCOEFF_32 0x05
|
||||
|
||||
#define QMP6988_CONFIG_REG 0xF1 /*IIR filter co-efficient setting Register*/
|
||||
#define QMP6988_CONFIG_REG_FILTER__POS 0
|
||||
#define QMP6988_CONFIG_REG_FILTER__MSK 0x07
|
||||
#define QMP6988_CONFIG_REG_FILTER__LEN 3
|
||||
|
||||
#define SUBTRACTOR 8388608
|
||||
|
||||
typedef struct _qmp6988_cali_data {
|
||||
QMP6988_S32_t COE_a0;
|
||||
QMP6988_S16_t COE_a1;
|
||||
QMP6988_S16_t COE_a2;
|
||||
QMP6988_S32_t COE_b00;
|
||||
QMP6988_S16_t COE_bt1;
|
||||
QMP6988_S16_t COE_bt2;
|
||||
QMP6988_S16_t COE_bp1;
|
||||
QMP6988_S16_t COE_b11;
|
||||
QMP6988_S16_t COE_bp2;
|
||||
QMP6988_S16_t COE_b12;
|
||||
QMP6988_S16_t COE_b21;
|
||||
QMP6988_S16_t COE_bp3;
|
||||
} qmp6988_cali_data_t;
|
||||
|
||||
typedef struct _qmp6988_fk_data {
|
||||
float a0, b00;
|
||||
float a1, a2, bt1, bt2, bp1, b11, bp2, b12, b21, bp3;
|
||||
} qmp6988_fk_data_t;
|
||||
|
||||
typedef struct _qmp6988_ik_data {
|
||||
QMP6988_S32_t a0, b00;
|
||||
QMP6988_S32_t a1, a2;
|
||||
QMP6988_S64_t bt1, bt2, bp1, b11, bp2, b12, b21, bp3;
|
||||
} qmp6988_ik_data_t;
|
||||
|
||||
typedef struct _qmp6988_data {
|
||||
uint8_t slave;
|
||||
uint8_t chip_id;
|
||||
uint8_t power_mode;
|
||||
float temperature;
|
||||
float pressure;
|
||||
float altitude;
|
||||
qmp6988_cali_data_t qmp6988_cali;
|
||||
qmp6988_ik_data_t ik;
|
||||
} qmp6988_data_t;
|
||||
|
||||
class QMP6988 {
|
||||
private:
|
||||
qmp6988_data_t qmp6988;
|
||||
uint8_t _addr;
|
||||
I2C_Class _i2c;
|
||||
|
||||
// read calibration data from otp
|
||||
int getCalibrationData();
|
||||
QMP6988_S32_t getPressure02e(qmp6988_ik_data_t* ik, QMP6988_S32_t dp,
|
||||
QMP6988_S16_t tx);
|
||||
QMP6988_S16_t convTx02e(qmp6988_ik_data_t* ik, QMP6988_S32_t dt);
|
||||
|
||||
void reset();
|
||||
|
||||
public:
|
||||
bool begin(TwoWire* wire = &Wire, uint8_t addr = QMP6988_SLAVE_ADDRESS_H,
|
||||
uint8_t sda = 21, uint8_t scl = 22, long freq = 400000U);
|
||||
bool update();
|
||||
|
||||
float pressure = 0;
|
||||
float cTemp = 0;
|
||||
float altitude = 0;
|
||||
|
||||
float calcAltitude(float pressure, float temp);
|
||||
float calcPressure();
|
||||
float calcTemperature();
|
||||
|
||||
void setpPowermode(int power_mode);
|
||||
void setFilter(unsigned char filter);
|
||||
void setOversamplingP(unsigned char oversampling_p);
|
||||
void setOversamplingT(unsigned char oversampling_t);
|
||||
};
|
||||
|
||||
#endif
|
||||
793
libraries/M5Unit-ENV/src/SCD4X.cpp
Normal file
793
libraries/M5Unit-ENV/src/SCD4X.cpp
Normal file
|
|
@ -0,0 +1,793 @@
|
|||
/*
|
||||
This is a library written for the SCD4X family of CO2 sensors
|
||||
SparkFun sells these at its website: www.sparkfun.com
|
||||
Do you like this library? Help support SparkFun. Buy a board!
|
||||
https://www.sparkfun.com/products/18365
|
||||
|
||||
Written by Paul Clark @ SparkFun Electronics, June 2nd, 2021
|
||||
|
||||
The SCD41 measures CO2 from 400ppm to 5000ppm with an accuracy of +/- 40ppm +
|
||||
5% of reading
|
||||
|
||||
This library handles the initialization of the SCD4X and outputs
|
||||
CO2 levels, relative humidty, and temperature.
|
||||
|
||||
https://github.com/sparkfun/SparkFun_SCD4x_Arduino_Library
|
||||
|
||||
Development environment specifics:
|
||||
Arduino IDE 1.8.13
|
||||
|
||||
SparkFun code, firmware, and software is released under the MIT License.
|
||||
Please see LICENSE.md for more details.
|
||||
*/
|
||||
|
||||
#include "SCD4X.h"
|
||||
|
||||
SCD4X::SCD4X(scd4x_sensor_type_e sensorType) {
|
||||
// Constructor
|
||||
_sensorType = sensorType;
|
||||
}
|
||||
|
||||
bool SCD4X::begin(TwoWire *wire, uint8_t addr, uint8_t sda, uint8_t scl,
|
||||
long freq, bool measBegin, bool autoCalibrate,
|
||||
bool skipStopPeriodicMeasurements,
|
||||
bool pollAndSetDeviceType) {
|
||||
_i2c.begin(wire, sda, scl, freq);
|
||||
_addr = addr;
|
||||
_wire = wire;
|
||||
|
||||
if (!_i2c.exist(_addr)) {
|
||||
return false;
|
||||
}
|
||||
bool success = true;
|
||||
|
||||
// If periodic measurements are already running, getSerialNumber will
|
||||
// fail... To be safe, let's stop period measurements before we do anything
|
||||
// else The user can override this by setting skipStopPeriodicMeasurements
|
||||
// to true
|
||||
if (skipStopPeriodicMeasurements == false)
|
||||
success &= stopPeriodicMeasurement(); // Delays for 500ms...
|
||||
|
||||
char serialNumber[13]; // Serial number is 12 digits plus trailing NULL
|
||||
success &= getSerialNumber(serialNumber); // Read the serial number. Return
|
||||
// false if the CRC check fails.
|
||||
if (pollAndSetDeviceType == true) {
|
||||
scd4x_sensor_type_e sensorType;
|
||||
success &= getFeatureSetVersion(&sensorType);
|
||||
|
||||
setSensorType(sensorType);
|
||||
}
|
||||
|
||||
if (autoCalibrate ==
|
||||
true) // Must be done before periodic measurements are started
|
||||
{
|
||||
success &= setAutomaticSelfCalibrationEnabled(true);
|
||||
success &= (getAutomaticSelfCalibrationEnabled() == true);
|
||||
} else {
|
||||
success &= setAutomaticSelfCalibrationEnabled(false);
|
||||
success &= (getAutomaticSelfCalibrationEnabled() == false);
|
||||
}
|
||||
|
||||
if (measBegin == true) {
|
||||
success &= startPeriodicMeasurement();
|
||||
}
|
||||
|
||||
return (success);
|
||||
}
|
||||
|
||||
bool SCD4X::update() {
|
||||
return readMeasurement();
|
||||
}
|
||||
// Start periodic measurements. See 3.5.1
|
||||
// signal update interval is 5 seconds.
|
||||
bool SCD4X::startPeriodicMeasurement(void) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (true); // Maybe this should be false?
|
||||
}
|
||||
|
||||
bool success = sendCommand(SCD4x_COMMAND_START_PERIODIC_MEASUREMENT);
|
||||
if (success) periodicMeasurementsAreRunning = true;
|
||||
return (success);
|
||||
}
|
||||
|
||||
// Stop periodic measurements. See 3.5.3
|
||||
// Stop periodic measurement to change the sensor configuration or to save
|
||||
// power. Note that the sensor will only respond to other commands after waiting
|
||||
// 500 ms after issuing the stop_periodic_measurement command.
|
||||
|
||||
bool SCD4X::stopPeriodicMeasurement(uint16_t delayMillis, TwoWire &wirePort)
|
||||
|
||||
{
|
||||
uint8_t i2cResult;
|
||||
if (_wire != NULL) // If the sensor has been begun (_wire is not
|
||||
// NULL) then _wire is used
|
||||
{
|
||||
_wire->beginTransmission(_addr);
|
||||
_wire->write(SCD4x_COMMAND_STOP_PERIODIC_MEASUREMENT >> 8); // MSB
|
||||
_wire->write(SCD4x_COMMAND_STOP_PERIODIC_MEASUREMENT & 0xFF); // LSB
|
||||
i2cResult = _wire->endTransmission();
|
||||
} else {
|
||||
// If the sensor has not been begun (_wire is NULL) then wirePort
|
||||
// is used (which will default to Wire)
|
||||
wirePort.beginTransmission(_addr);
|
||||
wirePort.write(SCD4x_COMMAND_STOP_PERIODIC_MEASUREMENT >> 8); // MSB
|
||||
wirePort.write(SCD4x_COMMAND_STOP_PERIODIC_MEASUREMENT & 0xFF); // LSB
|
||||
i2cResult = wirePort.endTransmission();
|
||||
}
|
||||
|
||||
if (i2cResult == 0) {
|
||||
periodicMeasurementsAreRunning = false;
|
||||
if (delayMillis > 0) delay(delayMillis);
|
||||
return (true);
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
|
||||
// Get 9 bytes from SCD4X. See 3.5.2
|
||||
// Updates global variables with floats
|
||||
// Returns true if data is read successfully
|
||||
// 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
|
||||
// (see chapter 3.8.2 for further details).
|
||||
bool SCD4X::readMeasurement(void) {
|
||||
// Verify we have data from the sensor
|
||||
if (getDataReadyStatus() == false) return (false);
|
||||
|
||||
scd4x_unsigned16Bytes_t tempCO2;
|
||||
tempCO2.unsigned16 = 0;
|
||||
scd4x_unsigned16Bytes_t tempHumidity;
|
||||
tempHumidity.unsigned16 = 0;
|
||||
scd4x_unsigned16Bytes_t tempTemperature;
|
||||
tempTemperature.unsigned16 = 0;
|
||||
|
||||
_wire->beginTransmission(_addr);
|
||||
_wire->write(SCD4x_COMMAND_READ_MEASUREMENT >> 8); // MSB
|
||||
_wire->write(SCD4x_COMMAND_READ_MEASUREMENT & 0xFF); // LSB
|
||||
if (_wire->endTransmission() != 0) return (false); // Sensor did not ACK
|
||||
|
||||
delay(1); // Datasheet specifies this
|
||||
|
||||
_wire->requestFrom((uint8_t)_addr, (uint8_t)9);
|
||||
bool error = false;
|
||||
if (_wire->available()) {
|
||||
byte bytesToCrc[2];
|
||||
for (byte x = 0; x < 9; x++) {
|
||||
byte incoming = _wire->read();
|
||||
|
||||
switch (x) {
|
||||
case 0:
|
||||
case 1:
|
||||
tempCO2.bytes[x == 0 ? 1 : 0] =
|
||||
incoming; // Store the two CO2 bytes in
|
||||
// little-endian format
|
||||
bytesToCrc[x] =
|
||||
incoming; // Calculate the CRC on the two CO2 bytes
|
||||
// in the order they arrive
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
tempTemperature.bytes[x == 3 ? 1 : 0] =
|
||||
incoming; // Store the two T bytes in little-endian
|
||||
// format
|
||||
bytesToCrc[x % 3] =
|
||||
incoming; // Calculate the CRC on the two T bytes
|
||||
// in the order they arrive
|
||||
break;
|
||||
case 6:
|
||||
case 7:
|
||||
tempHumidity.bytes[x == 6 ? 1 : 0] =
|
||||
incoming; // Store the two RH bytes in
|
||||
// little-endian format
|
||||
bytesToCrc[x % 3] =
|
||||
incoming; // Calculate the CRC on the two RH bytes
|
||||
// in the order they arrive
|
||||
break;
|
||||
default: // x == 2, 5, 8
|
||||
// Validate CRC
|
||||
uint8_t foundCrc = computeCRC8(
|
||||
bytesToCrc, 2); // Calculate what the CRC should be
|
||||
// for these two bytes
|
||||
if (foundCrc != incoming) // Does this match the CRC
|
||||
// byte from the sensor?
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return (false);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (false);
|
||||
}
|
||||
// Now copy the int16s into their associated floats
|
||||
co2 = (float)tempCO2.unsigned16;
|
||||
temperature = -45 + (((float)tempTemperature.unsigned16) * 175 / 65536);
|
||||
humidity = ((float)tempHumidity.unsigned16) * 100 / 65536;
|
||||
|
||||
// Mark our global variables as fresh
|
||||
co2HasBeenReported = false;
|
||||
humidityHasBeenReported = false;
|
||||
temperatureHasBeenReported = false;
|
||||
|
||||
return (true); // Success! New data available in globals.
|
||||
}
|
||||
|
||||
// Returns the latest available CO2 level
|
||||
// If the current level has already been reported, trigger a new read
|
||||
uint16_t SCD4X::getCO2(void) {
|
||||
if (co2HasBeenReported == true) // Trigger a new read
|
||||
readMeasurement(); // Pull in new co2, humidity, and temp into
|
||||
// global vars
|
||||
|
||||
co2HasBeenReported = true;
|
||||
|
||||
return (uint16_t)co2; // Cut off decimal as co2 is 0 to 10,000
|
||||
}
|
||||
|
||||
// Returns the latest available humidity
|
||||
// If the current level has already been reported, trigger a new read
|
||||
float SCD4X::getHumidity(void) {
|
||||
if (humidityHasBeenReported == true) // Trigger a new read
|
||||
readMeasurement(); // Pull in new co2, humidity, and temp into
|
||||
// global vars
|
||||
|
||||
humidityHasBeenReported = true;
|
||||
|
||||
return humidity;
|
||||
}
|
||||
|
||||
// Returns the latest available temperature
|
||||
// If the current level has already been reported, trigger a new read
|
||||
float SCD4X::getTemperature(void) {
|
||||
if (temperatureHasBeenReported == true) // Trigger a new read
|
||||
readMeasurement(); // Pull in new co2, humidity, and temp into
|
||||
// global vars
|
||||
|
||||
temperatureHasBeenReported = true;
|
||||
|
||||
return temperature;
|
||||
}
|
||||
|
||||
// Set the temperature offset (C). See 3.6.1
|
||||
// Max command duration: 1ms
|
||||
// The user can set delayMillis to zero f they want the function to return
|
||||
// immediately. The temperature offset has no influence on the SCD4X CO2
|
||||
// accuracy. Setting the temperature offset of the SCD4X inside the customer
|
||||
// device correctly allows the user to leverage the RH and T output signal.
|
||||
bool SCD4X::setTemperatureOffset(float offset, uint16_t delayMillis) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
if (offset < 0) {
|
||||
return (false);
|
||||
}
|
||||
if (offset >= 175) {
|
||||
return (false);
|
||||
}
|
||||
uint16_t offsetWord =
|
||||
(uint16_t)(offset * 65536 / 175); // Toffset [°C] * 2^16 / 175
|
||||
bool success =
|
||||
sendCommand(SCD4x_COMMAND_SET_TEMPERATURE_OFFSET, offsetWord);
|
||||
if (delayMillis > 0) delay(delayMillis);
|
||||
return (success);
|
||||
}
|
||||
|
||||
// Get the temperature offset. See 3.6.2
|
||||
float SCD4X::getTemperatureOffset(void) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (0.0);
|
||||
}
|
||||
|
||||
float offset;
|
||||
|
||||
getTemperatureOffset(&offset);
|
||||
|
||||
return (offset);
|
||||
}
|
||||
|
||||
// Get the temperature offset. See 3.6.2
|
||||
bool SCD4X::getTemperatureOffset(float *offset) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
uint16_t offsetWord = 0; // offset will be zero if readRegister fails
|
||||
bool success =
|
||||
readRegister(SCD4x_COMMAND_GET_TEMPERATURE_OFFSET, &offsetWord, 1);
|
||||
*offset = ((float)offsetWord) * 175.0 / 65536.0;
|
||||
return (success);
|
||||
}
|
||||
|
||||
// Set the sensor altitude (metres above sea level). See 3.6.3
|
||||
// Max command duration: 1ms
|
||||
// The user can set delayMillis to zero if they want the function to return
|
||||
// immediately. Reading and writing of the sensor altitude must be done
|
||||
// while the SCD4X is in idle mode. Typically, the sensor altitude is set
|
||||
// once after device installation. To save the setting to the EEPROM, the
|
||||
// persist setting (see chapter 3.9.1) command must be issued. Per default,
|
||||
// the sensor altitude is set to 0 meter above sea-level.
|
||||
bool SCD4X::setSensorAltitude(uint16_t altitude, uint16_t delayMillis) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
bool success = sendCommand(SCD4x_COMMAND_SET_SENSOR_ALTITUDE, altitude);
|
||||
if (delayMillis > 0) delay(delayMillis);
|
||||
return (success);
|
||||
}
|
||||
|
||||
// Get the sensor altitude. See 3.6.4
|
||||
uint16_t SCD4X::getSensorAltitude(void) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (0);
|
||||
}
|
||||
|
||||
uint16_t altitude = 0;
|
||||
|
||||
getSensorAltitude(&altitude);
|
||||
|
||||
return (altitude);
|
||||
}
|
||||
|
||||
// Get the sensor altitude. See 3.6.4
|
||||
bool SCD4X::getSensorAltitude(uint16_t *altitude) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (readRegister(SCD4x_COMMAND_GET_SENSOR_ALTITUDE, altitude, 1));
|
||||
}
|
||||
|
||||
// Set the ambient pressure (Pa). See 3.6.5
|
||||
// Max command duration: 1ms
|
||||
// The user can set delayMillis to zero if they want the function to return
|
||||
// immediately. The set_ambient_pressure command can be sent during periodic
|
||||
// measurements to enable continuous pressure compensation.
|
||||
// setAmbientPressure overrides setSensorAltitude
|
||||
bool SCD4X::setAmbientPressure(float pressure, uint16_t delayMillis) {
|
||||
if (pressure < 0) {
|
||||
return (false);
|
||||
}
|
||||
if (pressure > 6553500) {
|
||||
return (false);
|
||||
}
|
||||
uint16_t pressureWord = (uint16_t)(pressure / 100);
|
||||
bool success =
|
||||
sendCommand(SCD4x_COMMAND_SET_AMBIENT_PRESSURE, pressureWord);
|
||||
if (delayMillis > 0) delay(delayMillis);
|
||||
return (success);
|
||||
}
|
||||
|
||||
// Perform forced recalibration. See 3.7.1
|
||||
float SCD4X::performForcedRecalibration(uint16_t concentration) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (0.0);
|
||||
}
|
||||
|
||||
float correction = 0.0;
|
||||
|
||||
performForcedRecalibration(concentration, &correction);
|
||||
|
||||
return (correction);
|
||||
}
|
||||
|
||||
// Perform forced recalibration. See 3.7.1
|
||||
// To successfully conduct an accurate forced recalibration, the following
|
||||
// steps need to be carried out:
|
||||
// 1. Operate the SCD4X in the operation mode later used in normal sensor
|
||||
// operation (periodic measurement,
|
||||
// low power periodic measurement or single shot) for > 3 minutes in an
|
||||
// environment with homogenous and constant CO2 concentration.
|
||||
// 2. Issue stop_periodic_measurement. Wait 500 ms for the stop command to
|
||||
// complete.
|
||||
// 3. Subsequently issue the perform_forced_recalibration command and
|
||||
// optionally read out the FRC correction
|
||||
// (i.e. the magnitude of the correction) after waiting for 400 ms for
|
||||
// the command to complete.
|
||||
// A return value of 0xffff indicates that the forced recalibration has
|
||||
// failed.
|
||||
bool SCD4X::performForcedRecalibration(uint16_t concentration,
|
||||
float *correction) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
uint16_t correctionWord;
|
||||
|
||||
bool success =
|
||||
sendCommand(SCD4x_COMMAND_PERFORM_FORCED_CALIBRATION, concentration);
|
||||
|
||||
if (success == false) return (false);
|
||||
|
||||
delay(400); // Datasheet specifies this
|
||||
|
||||
_wire->requestFrom((uint8_t)_addr, (uint8_t)3);
|
||||
bool error = false;
|
||||
if (_wire->available()) {
|
||||
byte bytesToCrc[2];
|
||||
bytesToCrc[0] = _wire->read();
|
||||
correctionWord = ((uint16_t)bytesToCrc[0]) << 8;
|
||||
bytesToCrc[1] = _wire->read();
|
||||
correctionWord |= (uint16_t)bytesToCrc[1];
|
||||
byte incomingCrc = _wire->read();
|
||||
uint8_t foundCrc = computeCRC8(bytesToCrc, 2);
|
||||
if (foundCrc != incomingCrc) {
|
||||
error = true;
|
||||
}
|
||||
} else {
|
||||
return (false);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
*correction = ((float)correctionWord) -
|
||||
32768; // FRC correction [ppm CO2] = word[0] – 0x8000
|
||||
|
||||
if (correctionWord == 0xffff) // A return value of 0xffff indicates that
|
||||
// the forced recalibration has failed
|
||||
return (false);
|
||||
|
||||
return (true);
|
||||
}
|
||||
|
||||
// Enable/disable automatic self calibration. See 3.7.2
|
||||
// Set the current state (enabled / disabled) of the automatic
|
||||
// self-calibration. By default, ASC is enabled. To save the setting to the
|
||||
// EEPROM, the persist_setting (see chapter 3.9.1) command must be issued.
|
||||
bool SCD4X::setAutomaticSelfCalibrationEnabled(bool enabled,
|
||||
uint16_t delayMillis) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
uint16_t enabledWord = enabled == true ? 0x0001 : 0x0000;
|
||||
bool success = sendCommand(
|
||||
SCD4x_COMMAND_SET_AUTOMATIC_SELF_CALIBRATION_ENABLED, enabledWord);
|
||||
if (delayMillis > 0) delay(delayMillis);
|
||||
return (success);
|
||||
}
|
||||
|
||||
// Check if automatic self calibration is enabled. See 3.7.3
|
||||
bool SCD4X::getAutomaticSelfCalibrationEnabled(void) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
uint16_t enabled;
|
||||
bool success = getAutomaticSelfCalibrationEnabled(&enabled);
|
||||
if (success == false) {
|
||||
return (false);
|
||||
}
|
||||
return (enabled == 0x0001);
|
||||
}
|
||||
|
||||
// Check if automatic self calibration is enabled. See 3.7.3
|
||||
bool SCD4X::getAutomaticSelfCalibrationEnabled(uint16_t *enabled) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (readRegister(SCD4x_COMMAND_GET_AUTOMATIC_SELF_CALIBRATION_ENABLED,
|
||||
enabled, 1));
|
||||
}
|
||||
|
||||
// Start low power periodic measurements. See 3.8.1
|
||||
// Signal update interval will be 30 seconds instead of 5
|
||||
bool SCD4X::startLowPowerPeriodicMeasurement(void) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
bool success =
|
||||
sendCommand(SCD4x_COMMAND_START_LOW_POWER_PERIODIC_MEASUREMENT);
|
||||
if (success) periodicMeasurementsAreRunning = true;
|
||||
return (success);
|
||||
}
|
||||
|
||||
// Returns true when data is available. See 3.8.2
|
||||
bool SCD4X::getDataReadyStatus(void) {
|
||||
uint16_t response;
|
||||
bool success =
|
||||
readRegister(SCD4x_COMMAND_GET_DATA_READY_STATUS, &response, 1);
|
||||
|
||||
if (success == false) return (false);
|
||||
|
||||
// If the least significant 11 bits of word[0] are 0 → data not ready
|
||||
// else → data ready for read-out
|
||||
if ((response & 0x07ff) == 0x0000) return (false);
|
||||
return (true);
|
||||
}
|
||||
|
||||
// Persist settings: copy settings (e.g. temperature offset) from RAM to
|
||||
// EEPROM. See 3.9.1 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 persistent across
|
||||
// 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. The EEPROM is
|
||||
// guaranteed to endure at least 2000 write cycles before failure.
|
||||
bool SCD4X::persistSettings(uint16_t delayMillis) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
bool success = sendCommand(SCD4x_COMMAND_PERSIST_SETTINGS);
|
||||
if (delayMillis > 0) delay(delayMillis);
|
||||
return (success);
|
||||
}
|
||||
|
||||
// Get 9 bytes from SCD4X. Convert 48-bit serial number to ASCII chars.
|
||||
// See 3.9.2 Returns true if serial number is read successfully Reading out
|
||||
// the serial number can be used to identify the chip and to verify the
|
||||
// presence of the sensor.
|
||||
bool SCD4X::getSerialNumber(char *serialNumber) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
_wire->beginTransmission(_addr);
|
||||
_wire->write(SCD4x_COMMAND_GET_SERIAL_NUMBER >> 8); // MSB
|
||||
_wire->write(SCD4x_COMMAND_GET_SERIAL_NUMBER & 0xFF); // LSB
|
||||
if (_wire->endTransmission() != 0) return (false); // Sensor did not ACK
|
||||
|
||||
delay(1); // Datasheet specifies this
|
||||
|
||||
_wire->requestFrom((uint8_t)_addr, (uint8_t)9);
|
||||
bool error = false;
|
||||
if (_wire->available()) {
|
||||
byte bytesToCrc[2];
|
||||
int digit = 0;
|
||||
for (byte x = 0; x < 9; x++) {
|
||||
byte incoming = _wire->read();
|
||||
|
||||
switch (x) {
|
||||
case 0: // The serial number arrives as: two bytes, CRC,
|
||||
// two bytes, CRC, two bytes, CRC
|
||||
case 1:
|
||||
case 3:
|
||||
case 4:
|
||||
case 6:
|
||||
case 7:
|
||||
serialNumber[digit++] = convertHexToASCII(
|
||||
incoming >> 4); // Convert each nibble to ASCII
|
||||
serialNumber[digit++] = convertHexToASCII(incoming & 0x0F);
|
||||
bytesToCrc[x % 3] = incoming;
|
||||
break;
|
||||
default: // x == 2, 5, 8
|
||||
// Validate CRC
|
||||
uint8_t foundCrc = computeCRC8(
|
||||
bytesToCrc, 2); // Calculate what the CRC should be
|
||||
// for these two bytes
|
||||
if (foundCrc != incoming) // Does this match the CRC
|
||||
// byte from the sensor?
|
||||
{
|
||||
error = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
serialNumber[digit] = 0; // NULL-terminate the string
|
||||
}
|
||||
} else {
|
||||
return (false);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (true); // Success!
|
||||
}
|
||||
|
||||
// PRIVATE: Convert serial number digit to ASCII
|
||||
char SCD4X::convertHexToASCII(uint8_t digit) {
|
||||
if (digit <= 9)
|
||||
return (char(digit + 0x30));
|
||||
else
|
||||
return (char(digit + 0x41 - 10)); // Use upper case for A-F
|
||||
}
|
||||
|
||||
// Perform self test. Takes 10 seconds to complete. See 3.9.3
|
||||
// The perform_self_test feature can be used as an end-of-line test to check
|
||||
// sensor functionality and the customer power supply to the sensor.
|
||||
bool SCD4X::performSelfTest(void) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
uint16_t response;
|
||||
|
||||
bool success =
|
||||
readRegister(SCD4x_COMMAND_PERFORM_SELF_TEST, &response, 10000);
|
||||
|
||||
return (success &&
|
||||
(response == 0x0000)); // word[0] = 0 → no malfunction detected
|
||||
}
|
||||
|
||||
// Peform factory reset. See 3.9.4
|
||||
// The perform_factory_reset command resets all configuration settings
|
||||
// stored in the EEPROM and erases the FRC and ASC algorithm history.
|
||||
bool SCD4X::performFactoryReset(uint16_t delayMillis) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
bool success = sendCommand(SCD4x_COMMAND_PERFORM_FACTORY_RESET);
|
||||
if (delayMillis > 0) delay(delayMillis);
|
||||
return (success);
|
||||
}
|
||||
|
||||
// Reinit. See 3.9.5
|
||||
// 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 the reinit command does not trigger the
|
||||
// desired re-initialization, a power-cycle should be applied to the SCD4X.
|
||||
bool SCD4X::reInit(uint16_t delayMillis) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
bool success = sendCommand(SCD4x_COMMAND_REINIT);
|
||||
if (delayMillis > 0) delay(delayMillis);
|
||||
return (success);
|
||||
}
|
||||
|
||||
// Low Power Single Shot. See 3.10.1
|
||||
// In addition to periodic measurement modes, the SCD41 features a single
|
||||
// shot measurement mode, i.e. allows for on-demand measurements. The
|
||||
// typical communication sequence is as follows:
|
||||
// 1. The sensor is powered up.
|
||||
// 2. The I2C master sends a single shot command and waits for the indicated
|
||||
// max. command duration time.
|
||||
// 3. The I2C master reads out data with the read measurement sequence
|
||||
// (chapter 3.5.2).
|
||||
// 4. Steps 2-3 are repeated as required by the application.
|
||||
bool SCD4X::measureSingleShot(void) {
|
||||
if (_sensorType != SCD4x_SENSOR_SCD41) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
bool success = sendCommand(SCD4x_COMMAND_MEASURE_SINGLE_SHOT);
|
||||
|
||||
return (success);
|
||||
}
|
||||
|
||||
// On-demand measurement of relative humidity and temperature only.
|
||||
// The sensor output is read using the read_measurement command
|
||||
// (chapter 3.5.2). CO2 output is returned as 0 ppm.
|
||||
bool SCD4X::measureSingleShotRHTOnly(void) {
|
||||
if (_sensorType != SCD4x_SENSOR_SCD41) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
bool success = sendCommand(SCD4x_COMMAND_MEASURE_SINGLE_SHOT_RHT_ONLY);
|
||||
|
||||
return (success);
|
||||
}
|
||||
|
||||
scd4x_sensor_type_e SCD4X::getSensorType(void) {
|
||||
return _sensorType;
|
||||
}
|
||||
|
||||
void SCD4X::setSensorType(scd4x_sensor_type_e sensorType) {
|
||||
_sensorType = sensorType;
|
||||
}
|
||||
|
||||
bool SCD4X::getFeatureSetVersion(scd4x_sensor_type_e *sensorType) {
|
||||
if (periodicMeasurementsAreRunning) {
|
||||
return (false);
|
||||
}
|
||||
|
||||
uint16_t featureSet;
|
||||
|
||||
bool success =
|
||||
readRegister(SCD4x_COMMAND_GET_FEATURE_SET_VERSION, &featureSet, 1);
|
||||
|
||||
uint8_t typeOfSensor = ((featureSet & 0x1000) >> 12);
|
||||
|
||||
if (typeOfSensor == 0) {
|
||||
*sensorType = SCD4x_SENSOR_SCD40;
|
||||
} else if (typeOfSensor == 1) {
|
||||
*sensorType = SCD4x_SENSOR_SCD41;
|
||||
} else {
|
||||
*sensorType = SCD4x_SENSOR_INVALID;
|
||||
success = false;
|
||||
}
|
||||
|
||||
return (success);
|
||||
}
|
||||
|
||||
// Sends a command along with arguments and CRC
|
||||
bool SCD4X::sendCommand(uint16_t command, uint16_t arguments) {
|
||||
uint8_t data[2];
|
||||
data[0] = arguments >> 8;
|
||||
data[1] = arguments & 0xFF;
|
||||
uint8_t crc = computeCRC8(
|
||||
data, 2); // Calc CRC on the arguments only, not the command
|
||||
|
||||
_wire->beginTransmission(_addr);
|
||||
_wire->write(command >> 8); // MSB
|
||||
_wire->write(command & 0xFF); // LSB
|
||||
_wire->write(arguments >> 8); // MSB
|
||||
_wire->write(arguments & 0xFF); // LSB
|
||||
_wire->write(crc);
|
||||
if (_wire->endTransmission() != 0) return (false); // Sensor did not ACK
|
||||
|
||||
return (true);
|
||||
}
|
||||
|
||||
// Sends just a command, no arguments, no CRC
|
||||
bool SCD4X::sendCommand(uint16_t command) {
|
||||
_wire->beginTransmission(_addr);
|
||||
_wire->write(command >> 8); // MSB
|
||||
_wire->write(command & 0xFF); // LSB
|
||||
if (_wire->endTransmission() != 0) return (false); // Sensor did not ACK
|
||||
|
||||
return (true);
|
||||
}
|
||||
|
||||
// Gets two bytes from SCD4X plus CRC.
|
||||
// Returns true if endTransmission returns zero _and_ the CRC check is valid
|
||||
bool SCD4X::readRegister(uint16_t registerAddress, uint16_t *response,
|
||||
uint16_t delayMillis) {
|
||||
_wire->beginTransmission(_addr);
|
||||
_wire->write(registerAddress >> 8); // MSB
|
||||
_wire->write(registerAddress & 0xFF); // LSB
|
||||
if (_wire->endTransmission() != 0) return (false); // Sensor did not ACK
|
||||
|
||||
delay(delayMillis);
|
||||
|
||||
_wire->requestFrom((uint8_t)_addr,
|
||||
(uint8_t)3); // Request data and CRC
|
||||
if (_wire->available()) {
|
||||
uint8_t data[2];
|
||||
data[0] = _wire->read();
|
||||
data[1] = _wire->read();
|
||||
uint8_t crc = _wire->read();
|
||||
*response = (uint16_t)data[0] << 8 | data[1];
|
||||
uint8_t expectedCRC = computeCRC8(data, 2);
|
||||
if (crc == expectedCRC) // Return true if CRC check is OK
|
||||
return (true);
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
|
||||
// Given an array and a number of bytes, this calculate CRC8 for those bytes
|
||||
// CRC is only calc'd on the data portion (two bytes) of the four bytes
|
||||
// being sent From:
|
||||
// http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html
|
||||
// Tested with: http://www.sunshine2k.de/coding/javascript/crc/crc_js.html
|
||||
// x^8+x^5+x^4+1 = 0x31
|
||||
uint8_t SCD4X::computeCRC8(uint8_t data[], uint8_t len) {
|
||||
uint8_t crc = 0xFF; // Init with 0xFF
|
||||
|
||||
for (uint8_t x = 0; x < len; x++) {
|
||||
crc ^= data[x]; // XOR-in the next input byte
|
||||
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
if ((crc & 0x80) != 0)
|
||||
crc = (uint8_t)((crc << 1) ^ 0x31);
|
||||
else
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return crc; // No output reflection
|
||||
}
|
||||
233
libraries/M5Unit-ENV/src/SCD4X.h
Normal file
233
libraries/M5Unit-ENV/src/SCD4X.h
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
This is a library written for the SCD4X family of CO2 sensors
|
||||
SparkFun sells these at its website: www.sparkfun.com
|
||||
Do you like this library? Help support SparkFun. Buy a board!
|
||||
https://www.sparkfun.com/products/18365
|
||||
|
||||
Written by Paul Clark @ SparkFun Electronics, June 2nd, 2021
|
||||
Revision by Alex Brudner @ SparkFun Electronics
|
||||
|
||||
The SCD41 measures CO2 from 400ppm to 5000ppm with an accuracy of +/- 40ppm +
|
||||
5% of reading
|
||||
|
||||
This library handles the initialization of the SCD4X and outputs
|
||||
CO2 levels, relative humidty, and temperature.
|
||||
|
||||
https://github.com/sparkfun/SparkFun_SCD4x_Arduino_Library
|
||||
|
||||
Development environment specifics:
|
||||
Arduino IDE 1.8.13 and 2.1.0
|
||||
|
||||
SparkFun code, firmware, and software is released under the MIT License.
|
||||
Please see LICENSE.md for more details.
|
||||
*/
|
||||
|
||||
#ifndef __SCD4X_H__
|
||||
#define __SCD4X_H__
|
||||
|
||||
// Uncomment the next #define if using an Teensy >= 3 or Teensy LC and want to
|
||||
// use the dedicated I2C-Library for it Then you also have to include <i2c_t3.h>
|
||||
// on your application instead of <Wire.h>
|
||||
|
||||
// #define USE_TEENSY3_I2C_LIB
|
||||
|
||||
// Uncomment the next #define to EXclude any debug logging from the code, by
|
||||
// default debug logging code will be included
|
||||
|
||||
// #define SCD4x_ENABLE_DEBUGLOG 0 // OFF/disabled/excluded on demand
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#include "I2C_Class.h"
|
||||
|
||||
// The default I2C address for the SCD4X is 0x62.
|
||||
#define SCD4X_I2C_ADDR 0x62
|
||||
|
||||
// Available commands
|
||||
|
||||
// Basic Commands
|
||||
#define SCD4x_COMMAND_START_PERIODIC_MEASUREMENT 0x21b1
|
||||
#define SCD4x_COMMAND_READ_MEASUREMENT 0xec05 // execution time: 1ms
|
||||
#define SCD4x_COMMAND_STOP_PERIODIC_MEASUREMENT 0x3f86 // execution time: 500ms
|
||||
|
||||
// On-chip output signal compensation
|
||||
#define SCD4x_COMMAND_SET_TEMPERATURE_OFFSET 0x241d // execution time: 1ms
|
||||
#define SCD4x_COMMAND_GET_TEMPERATURE_OFFSET 0x2318 // execution time: 1ms
|
||||
#define SCD4x_COMMAND_SET_SENSOR_ALTITUDE 0x2427 // execution time: 1ms
|
||||
#define SCD4x_COMMAND_GET_SENSOR_ALTITUDE 0x2322 // execution time: 1ms
|
||||
#define SCD4x_COMMAND_SET_AMBIENT_PRESSURE 0xe000 // execution time: 1ms
|
||||
|
||||
// Field calibration
|
||||
#define SCD4x_COMMAND_PERFORM_FORCED_CALIBRATION \
|
||||
0x362f // execution time: 400ms
|
||||
#define SCD4x_COMMAND_SET_AUTOMATIC_SELF_CALIBRATION_ENABLED \
|
||||
0x2416 // execution time: 1ms
|
||||
#define SCD4x_COMMAND_GET_AUTOMATIC_SELF_CALIBRATION_ENABLED \
|
||||
0x2313 // execution time: 1ms
|
||||
|
||||
// Low power
|
||||
#define SCD4x_COMMAND_START_LOW_POWER_PERIODIC_MEASUREMENT 0x21ac
|
||||
#define SCD4x_COMMAND_GET_DATA_READY_STATUS 0xe4b8 // execution time: 1ms
|
||||
|
||||
// Advanced features
|
||||
#define SCD4x_COMMAND_PERSIST_SETTINGS 0x3615 // execution time: 800ms
|
||||
#define SCD4x_COMMAND_GET_SERIAL_NUMBER 0x3682 // execution time: 1ms
|
||||
#define SCD4x_COMMAND_PERFORM_SELF_TEST 0x3639 // execution time: 10000ms
|
||||
#define SCD4x_COMMAND_PERFORM_FACTORY_RESET 0x3632 // execution time: 1200ms
|
||||
#define SCD4x_COMMAND_REINIT 0x3646 // execution time: 20ms
|
||||
#define SCD4x_COMMAND_GET_FEATURE_SET_VERSION 0x202F // execution time: 1ms
|
||||
|
||||
// Low power single shot - SCD41 only
|
||||
#define SCD4x_COMMAND_MEASURE_SINGLE_SHOT 0x219d // execution time: 5000ms
|
||||
#define SCD4x_COMMAND_MEASURE_SINGLE_SHOT_RHT_ONLY \
|
||||
0x2196 // execution time: 50ms
|
||||
|
||||
typedef union {
|
||||
int16_t signed16;
|
||||
uint16_t unsigned16;
|
||||
} scd4x_signedUnsigned16_t; // Avoid any ambiguity casting int16_t to uint16_t
|
||||
|
||||
typedef union {
|
||||
uint16_t unsigned16;
|
||||
uint8_t bytes[2];
|
||||
} scd4x_unsigned16Bytes_t; // Make it easy to convert 2 x uint8_t to uint16_t
|
||||
|
||||
typedef enum {
|
||||
SCD4x_SENSOR_SCD40 = 0,
|
||||
SCD4x_SENSOR_SCD41,
|
||||
SCD4x_SENSOR_INVALID
|
||||
} scd4x_sensor_type_e;
|
||||
|
||||
class SCD4X {
|
||||
private:
|
||||
TwoWire *_wire;
|
||||
uint8_t _addr;
|
||||
I2C_Class _i2c;
|
||||
|
||||
public:
|
||||
SCD4X(scd4x_sensor_type_e sensorType = SCD4x_SENSOR_SCD40);
|
||||
|
||||
bool begin(TwoWire *wire = &Wire, uint8_t addr = SCD4X_I2C_ADDR,
|
||||
uint8_t sda = 21, uint8_t scl = 22, long freq = 400000U,
|
||||
bool measBegin = true, bool autoCalibrate = true,
|
||||
bool skipStopPeriodicMeasurements = false,
|
||||
bool pollAndSetDeviceType = true
|
||||
|
||||
);
|
||||
|
||||
bool update(void);
|
||||
|
||||
bool startPeriodicMeasurement(void); // Signal update interval is 5 seconds
|
||||
|
||||
// stopPeriodicMeasurement can be called before .begin if required
|
||||
// If the sensor has been begun (_i2cPort is not NULL) then _i2cPort is used
|
||||
// If the sensor has not been begun (_i2cPort is NULL) then wirePort and
|
||||
// address are used (which will default to Wire) Note that the sensor will
|
||||
// only respond to other commands after waiting 500 ms after issuing the
|
||||
// stop_periodic_measurement command.
|
||||
|
||||
bool stopPeriodicMeasurement(uint16_t delayMillis = 500,
|
||||
TwoWire &wirePort = Wire);
|
||||
|
||||
bool readMeasurement(void); // Check for fresh data; store it. Returns true
|
||||
// if fresh data is available
|
||||
|
||||
uint16_t getCO2(void); // Return the CO2 PPM. Automatically request fresh
|
||||
// data is the data is 'stale'
|
||||
float getHumidity(void); // Return the RH. Automatically request fresh data
|
||||
// is the data is 'stale'
|
||||
float getTemperature(void); // Return the temperature. Automatically
|
||||
// request fresh data is the data is 'stale'
|
||||
|
||||
// Define how warm the sensor is compared to ambient, so RH and T are
|
||||
// temperature compensated. Has no effect on the CO2 reading Default offset
|
||||
// is 4C
|
||||
bool setTemperatureOffset(
|
||||
float offset,
|
||||
uint16_t delayMillis = 1); // Returns true if I2C transfer was OK
|
||||
float getTemperatureOffset(void); // Will return zero if offset is invalid
|
||||
bool getTemperatureOffset(
|
||||
float *offset); // Returns true if offset is valid
|
||||
|
||||
// Define the sensor altitude in metres above sea level, so RH and CO2 are
|
||||
// compensated for atmospheric pressure Default altitude is 0m
|
||||
bool setSensorAltitude(uint16_t altitude, uint16_t delayMillis = 1);
|
||||
uint16_t getSensorAltitude(
|
||||
void); // Will return zero if altitude is invalid
|
||||
bool getSensorAltitude(
|
||||
uint16_t *altitude); // Returns true if altitude is valid
|
||||
|
||||
// Define the ambient pressure in Pascals, so RH and CO2 are compensated for
|
||||
// atmospheric pressure setAmbientPressure overrides setSensorAltitude
|
||||
bool setAmbientPressure(float pressure, uint16_t delayMillis = 1);
|
||||
|
||||
float performForcedRecalibration(
|
||||
uint16_t concentration); // Returns the FRC correction value
|
||||
bool performForcedRecalibration(
|
||||
uint16_t concentration,
|
||||
float *correction); // Returns true if FRC is successful
|
||||
|
||||
bool setAutomaticSelfCalibrationEnabled(bool enabled = true,
|
||||
uint16_t delayMillis = 1);
|
||||
bool getAutomaticSelfCalibrationEnabled(void);
|
||||
bool getAutomaticSelfCalibrationEnabled(uint16_t *enabled);
|
||||
|
||||
bool startLowPowerPeriodicMeasurement(
|
||||
void); // Start low power measurements - receive data every 30 seconds
|
||||
bool getDataReadyStatus(void); // Returns true if fresh data is available
|
||||
|
||||
bool persistSettings(
|
||||
uint16_t delayMillis = 800); // Copy sensor settings from RAM to EEPROM
|
||||
bool getSerialNumber(
|
||||
char *serialNumber); // Returns true if serial number is read correctly
|
||||
bool performSelfTest(void); // Takes 10 seconds to complete. Returns true
|
||||
// if the test is successful
|
||||
bool performFactoryReset(
|
||||
uint16_t delayMillis =
|
||||
1200); // Reset all settings to the factory values
|
||||
bool reInit(uint16_t delayMillis =
|
||||
20); // Re-initialize the sensor, load settings from EEPROM
|
||||
|
||||
bool measureSingleShot(void); // SCD41 only. Request a single measurement.
|
||||
// Data will be ready in 5 seconds
|
||||
bool measureSingleShotRHTOnly(void); // SCD41 only. Request RH and T data
|
||||
// only. Data will be ready in 50ms
|
||||
|
||||
bool sendCommand(uint16_t command, uint16_t arguments);
|
||||
bool sendCommand(uint16_t command);
|
||||
|
||||
bool readRegister(uint16_t registerAddress, uint16_t *response,
|
||||
uint16_t delayMillis = 1);
|
||||
|
||||
uint8_t computeCRC8(uint8_t data[], uint8_t len);
|
||||
|
||||
bool getFeatureSetVersion(scd4x_sensor_type_e *sensorType);
|
||||
scd4x_sensor_type_e getSensorType(
|
||||
void); // Get the sensor type stored in the struct.
|
||||
void setSensorType(
|
||||
scd4x_sensor_type_e sensorType); // Set the sensor type for the device.
|
||||
|
||||
private:
|
||||
// Sensor type
|
||||
scd4x_sensor_type_e _sensorType;
|
||||
|
||||
// Global main datums
|
||||
float co2 = 0;
|
||||
float temperature = 0;
|
||||
float humidity = 0;
|
||||
|
||||
// These track the staleness of the current data
|
||||
// This allows us to avoid calling readMeasurement() every time individual
|
||||
// datums are requested
|
||||
bool co2HasBeenReported = true;
|
||||
bool humidityHasBeenReported = true;
|
||||
bool temperatureHasBeenReported = true;
|
||||
|
||||
// Keep track of whether periodic measurements are in progress
|
||||
bool periodicMeasurementsAreRunning = false;
|
||||
|
||||
// Convert serial number digit to ASCII
|
||||
char convertHexToASCII(uint8_t digit);
|
||||
};
|
||||
|
||||
#endif
|
||||
43
libraries/M5Unit-ENV/src/SHT3X.cpp
Normal file
43
libraries/M5Unit-ENV/src/SHT3X.cpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#include "SHT3X.h"
|
||||
|
||||
bool SHT3X::begin(TwoWire* wire, uint8_t addr, uint8_t sda, uint8_t scl,
|
||||
long freq) {
|
||||
_i2c.begin(wire, sda, scl, freq);
|
||||
_addr = addr;
|
||||
_wire = wire;
|
||||
return _i2c.exist(_addr);
|
||||
}
|
||||
|
||||
bool SHT3X::update() {
|
||||
unsigned int data[6];
|
||||
|
||||
// Start I2C Transmission
|
||||
_wire->beginTransmission(_addr);
|
||||
// Send measurement command
|
||||
_wire->write(0x2C);
|
||||
_wire->write(0x06);
|
||||
// Stop I2C transmission
|
||||
if (_wire->endTransmission() != 0) return false;
|
||||
|
||||
delay(200);
|
||||
|
||||
// Request 6 bytes of data
|
||||
_wire->requestFrom(_addr, (uint8_t)6);
|
||||
|
||||
// Read 6 bytes of data
|
||||
// cTemp msb, cTemp lsb, cTemp crc, humidity msb, humidity lsb, humidity crc
|
||||
for (int i = 0; i < 6; i++) {
|
||||
data[i] = _wire->read();
|
||||
};
|
||||
|
||||
delay(50);
|
||||
|
||||
if (_wire->available() != 0) return false;
|
||||
|
||||
// Convert the data
|
||||
cTemp = ((((data[0] * 256.0) + data[1]) * 175) / 65535.0) - 45;
|
||||
fTemp = (cTemp * 1.8) + 32;
|
||||
humidity = ((((data[3] * 256.0) + data[4]) * 100) / 65535.0);
|
||||
|
||||
return true;
|
||||
}
|
||||
25
libraries/M5Unit-ENV/src/SHT3X.h
Normal file
25
libraries/M5Unit-ENV/src/SHT3X.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef __SHT3X_H_
|
||||
#define __SHT3X_H_
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "I2C_Class.h"
|
||||
#include "Wire.h"
|
||||
|
||||
#define SHT3X_I2C_ADDR 0x44
|
||||
|
||||
class SHT3X {
|
||||
public:
|
||||
bool begin(TwoWire* wire = &Wire, uint8_t addr = SHT3X_I2C_ADDR,
|
||||
uint8_t sda = 21, uint8_t scl = 22, long freq = 400000U);
|
||||
bool update(void);
|
||||
float cTemp = 0;
|
||||
float fTemp = 0;
|
||||
float humidity = 0;
|
||||
|
||||
private:
|
||||
TwoWire* _wire;
|
||||
uint8_t _addr;
|
||||
I2C_Class _i2c;
|
||||
};
|
||||
|
||||
#endif
|
||||
96
libraries/M5Unit-ENV/src/SHT4X.cpp
Normal file
96
libraries/M5Unit-ENV/src/SHT4X.cpp
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#include "SHT4X.h"
|
||||
|
||||
bool SHT4X::begin(TwoWire* wire, uint8_t addr, uint8_t sda, uint8_t scl,
|
||||
long freq) {
|
||||
_i2c.begin(wire, sda, scl, freq);
|
||||
_addr = addr;
|
||||
_wire = wire;
|
||||
return _i2c.exist(_addr);
|
||||
}
|
||||
|
||||
bool SHT4X::update() {
|
||||
uint8_t readbuffer[6];
|
||||
uint8_t cmd = SHT4x_NOHEAT_HIGHPRECISION;
|
||||
uint16_t duration = 10;
|
||||
|
||||
if (_heater == SHT4X_NO_HEATER) {
|
||||
if (_precision == SHT4X_HIGH_PRECISION) {
|
||||
cmd = SHT4x_NOHEAT_HIGHPRECISION;
|
||||
duration = 10;
|
||||
}
|
||||
if (_precision == SHT4X_MED_PRECISION) {
|
||||
cmd = SHT4x_NOHEAT_MEDPRECISION;
|
||||
duration = 5;
|
||||
}
|
||||
if (_precision == SHT4X_LOW_PRECISION) {
|
||||
cmd = SHT4x_NOHEAT_LOWPRECISION;
|
||||
duration = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (_heater == SHT4X_HIGH_HEATER_1S) {
|
||||
cmd = SHT4x_HIGHHEAT_1S;
|
||||
duration = 1100;
|
||||
}
|
||||
if (_heater == SHT4X_HIGH_HEATER_100MS) {
|
||||
cmd = SHT4x_HIGHHEAT_100MS;
|
||||
duration = 110;
|
||||
}
|
||||
|
||||
if (_heater == SHT4X_MED_HEATER_1S) {
|
||||
cmd = SHT4x_MEDHEAT_1S;
|
||||
duration = 1100;
|
||||
}
|
||||
if (_heater == SHT4X_MED_HEATER_100MS) {
|
||||
cmd = SHT4x_MEDHEAT_100MS;
|
||||
duration = 110;
|
||||
}
|
||||
|
||||
if (_heater == SHT4X_LOW_HEATER_1S) {
|
||||
cmd = SHT4x_LOWHEAT_1S;
|
||||
duration = 1100;
|
||||
}
|
||||
if (_heater == SHT4X_LOW_HEATER_100MS) {
|
||||
cmd = SHT4x_LOWHEAT_100MS;
|
||||
duration = 110;
|
||||
}
|
||||
|
||||
_i2c.writeByte(_addr, cmd, 1);
|
||||
|
||||
delay(duration);
|
||||
|
||||
_wire->requestFrom(_addr, (uint8_t)6);
|
||||
|
||||
for (uint16_t i = 0; i < 6; i++) {
|
||||
readbuffer[i] = _wire->read();
|
||||
}
|
||||
|
||||
if (readbuffer[2] != crc8(readbuffer, 2) ||
|
||||
readbuffer[5] != crc8(readbuffer + 3, 2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float t_ticks = (uint16_t)readbuffer[0] * 256 + (uint16_t)readbuffer[1];
|
||||
float rh_ticks = (uint16_t)readbuffer[3] * 256 + (uint16_t)readbuffer[4];
|
||||
|
||||
cTemp = -45 + 175 * t_ticks / 65535;
|
||||
humidity = -6 + 125 * rh_ticks / 65535;
|
||||
humidity = min(max(humidity, (float)0.0), (float)100.0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SHT4X::setPrecision(sht4x_precision_t prec) {
|
||||
_precision = prec;
|
||||
}
|
||||
|
||||
sht4x_precision_t SHT4X::getPrecision(void) {
|
||||
return _precision;
|
||||
}
|
||||
|
||||
void SHT4X::setHeater(sht4x_heater_t heat) {
|
||||
_heater = heat;
|
||||
}
|
||||
|
||||
sht4x_heater_t SHT4X::getHeater(void) {
|
||||
return _heater;
|
||||
}
|
||||
80
libraries/M5Unit-ENV/src/SHT4X.h
Normal file
80
libraries/M5Unit-ENV/src/SHT4X.h
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#ifndef __SHT4X_H_
|
||||
#define __SHT4X_H_
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "I2C_Class.h"
|
||||
#include "Wire.h"
|
||||
#include "utility.h"
|
||||
|
||||
#define SHT40_I2C_ADDR_44 0x44
|
||||
#define SHT40_I2C_ADDR_45 0x45
|
||||
#define SHT41_I2C_ADDR_44 0x44
|
||||
#define SHT41_I2C_ADDR_45 0x45
|
||||
#define SHT45_I2C_ADDR_44 0x44
|
||||
#define SHT45_I2C_ADDR_45 0x45
|
||||
|
||||
#define SHT4x_DEFAULT_ADDR 0x44 /**< SHT4x I2C Address */
|
||||
#define SHT4x_NOHEAT_HIGHPRECISION \
|
||||
0xFD /**< High precision measurement, no heater */
|
||||
#define SHT4x_NOHEAT_MEDPRECISION \
|
||||
0xF6 /**< Medium precision measurement, no heater */
|
||||
#define SHT4x_NOHEAT_LOWPRECISION \
|
||||
0xE0 /**< Low precision measurement, no heater */
|
||||
|
||||
#define SHT4x_HIGHHEAT_1S \
|
||||
0x39 /**< High precision measurement, high heat for 1 sec */
|
||||
#define SHT4x_HIGHHEAT_100MS \
|
||||
0x32 /**< High precision measurement, high heat for 0.1 sec */
|
||||
#define SHT4x_MEDHEAT_1S \
|
||||
0x2F /**< High precision measurement, med heat for 1 sec */
|
||||
#define SHT4x_MEDHEAT_100MS \
|
||||
0x24 /**< High precision measurement, med heat for 0.1 sec */
|
||||
#define SHT4x_LOWHEAT_1S \
|
||||
0x1E /**< High precision measurement, low heat for 1 sec */
|
||||
#define SHT4x_LOWHEAT_100MS \
|
||||
0x15 /**< High precision measurement, low heat for 0.1 sec */
|
||||
|
||||
#define SHT4x_READSERIAL 0x89 /**< Read Out of Serial Register */
|
||||
#define SHT4x_SOFTRESET 0x94 /**< Soft Reset */
|
||||
|
||||
typedef enum {
|
||||
SHT4X_HIGH_PRECISION,
|
||||
SHT4X_MED_PRECISION,
|
||||
SHT4X_LOW_PRECISION,
|
||||
} sht4x_precision_t;
|
||||
|
||||
/** Optional pre-heater configuration setting */
|
||||
typedef enum {
|
||||
SHT4X_NO_HEATER,
|
||||
SHT4X_HIGH_HEATER_1S,
|
||||
SHT4X_HIGH_HEATER_100MS,
|
||||
SHT4X_MED_HEATER_1S,
|
||||
SHT4X_MED_HEATER_100MS,
|
||||
SHT4X_LOW_HEATER_1S,
|
||||
SHT4X_LOW_HEATER_100MS,
|
||||
} sht4x_heater_t;
|
||||
|
||||
class SHT4X {
|
||||
public:
|
||||
bool begin(TwoWire* wire = &Wire, uint8_t addr = SHT40_I2C_ADDR_44,
|
||||
uint8_t sda = 21, uint8_t scl = 22, long freq = 400000U);
|
||||
bool update(void);
|
||||
|
||||
float cTemp = 0;
|
||||
float humidity = 0;
|
||||
|
||||
void setPrecision(sht4x_precision_t prec);
|
||||
sht4x_precision_t getPrecision(void);
|
||||
void setHeater(sht4x_heater_t heat);
|
||||
sht4x_heater_t getHeater(void);
|
||||
|
||||
private:
|
||||
TwoWire* _wire;
|
||||
uint8_t _addr;
|
||||
I2C_Class _i2c;
|
||||
|
||||
sht4x_precision_t _precision = SHT4X_HIGH_PRECISION;
|
||||
sht4x_heater_t _heater = SHT4X_NO_HEATER;
|
||||
};
|
||||
|
||||
#endif
|
||||
1019
libraries/M5Unit-ENV/src/unit/unit_BME688.cpp
Normal file
1019
libraries/M5Unit-ENV/src/unit/unit_BME688.cpp
Normal file
File diff suppressed because it is too large
Load diff
1012
libraries/M5Unit-ENV/src/unit/unit_BME688.hpp
Normal file
1012
libraries/M5Unit-ENV/src/unit/unit_BME688.hpp
Normal file
File diff suppressed because it is too large
Load diff
594
libraries/M5Unit-ENV/src/unit/unit_BMP280.cpp
Normal file
594
libraries/M5Unit-ENV/src/unit/unit_BMP280.cpp
Normal file
|
|
@ -0,0 +1,594 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_BMP280.cpp
|
||||
@brief BMP280 Unit for M5UnitUnified
|
||||
*/
|
||||
#include "unit_BMP280.hpp"
|
||||
#include <M5Utility.hpp>
|
||||
#include <limits> // NaN
|
||||
#include <array>
|
||||
|
||||
using namespace m5::utility::mmh3;
|
||||
using namespace m5::unit::types;
|
||||
using namespace m5::unit::bmp280;
|
||||
using namespace m5::unit::bmp280::command;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint8_t CHIP_IDENTIFIER{0x58};
|
||||
constexpr uint8_t RESET_VALUE{0xB6};
|
||||
constexpr uint32_t NOT_MEASURED{0x800000};
|
||||
|
||||
constexpr PowerMode mode_table[] = {PowerMode::Sleep, PowerMode::Forced, PowerMode::Forced, // duplicated
|
||||
PowerMode::Normal
|
||||
|
||||
};
|
||||
|
||||
constexpr Oversampling osrs_table[] = {
|
||||
Oversampling::Skipped, Oversampling::X1, Oversampling::X2, Oversampling::X4, Oversampling::X8, Oversampling::X16,
|
||||
Oversampling::X16, // duplicated
|
||||
Oversampling::X16, // duplicated
|
||||
};
|
||||
|
||||
constexpr Oversampling osrss_table[][2] = {
|
||||
// Pressure, Temperature
|
||||
{Oversampling::X1, Oversampling::X1}, {Oversampling::X2, Oversampling::X1}, {Oversampling::X4, Oversampling::X1},
|
||||
{Oversampling::X8, Oversampling::X1}, {Oversampling::X16, Oversampling::X2},
|
||||
};
|
||||
|
||||
constexpr Standby standby_table[] = {
|
||||
Standby::Time0_5ms, Standby::Time62_5ms, Standby::Time125ms, Standby::Time250ms,
|
||||
Standby::Time500ms, Standby::Time1sec, Standby::Time2sec, Standby::Time4sec,
|
||||
|
||||
};
|
||||
|
||||
constexpr uint32_t interval_table[] = {0, 62, 125, 250, 500, 1000, 2000, 4000};
|
||||
|
||||
constexpr Filter filter_table[] = {
|
||||
Filter::Off, Filter::Coeff2, Filter::Coeff4, Filter::Coeff8, Filter::Coeff16,
|
||||
};
|
||||
|
||||
struct UseCaseSetting {
|
||||
OversamplingSetting osrss;
|
||||
Filter filter;
|
||||
Standby st;
|
||||
};
|
||||
constexpr UseCaseSetting uc_table[] = {
|
||||
{OversamplingSetting::UltraHighResolution, Filter::Coeff4, Standby::Time62_5ms},
|
||||
{OversamplingSetting::StandardResolution, Filter::Coeff16, Standby::Time0_5ms},
|
||||
{OversamplingSetting::UltraLowPower, Filter::Off, Standby::Time4sec},
|
||||
{OversamplingSetting::StandardResolution, Filter::Coeff4, Standby::Time125ms},
|
||||
{OversamplingSetting::LowPower, Filter::Off, Standby::Time0_5ms},
|
||||
{OversamplingSetting::UltraHighResolution, Filter::Coeff16, Standby::Time0_5ms},
|
||||
};
|
||||
|
||||
struct CtrlMeas {
|
||||
//
|
||||
inline Oversampling osrs_p() const
|
||||
{
|
||||
return osrs_table[(value >> 2) & 0x07];
|
||||
}
|
||||
inline Oversampling osrs_t() const
|
||||
{
|
||||
return osrs_table[(value >> 5) & 0x07];
|
||||
}
|
||||
inline PowerMode mode() const
|
||||
{
|
||||
return mode_table[value & 0x03];
|
||||
}
|
||||
//
|
||||
inline void osrs_p(const Oversampling os)
|
||||
{
|
||||
value = (value & ~(0x07 << 2)) | ((m5::stl::to_underlying(os) & 0x07) << 2);
|
||||
}
|
||||
inline void osrs_t(const Oversampling os)
|
||||
{
|
||||
value = (value & ~(0x07 << 5)) | ((m5::stl::to_underlying(os) & 0x07) << 5);
|
||||
}
|
||||
inline void mode(const PowerMode m)
|
||||
{
|
||||
value = (value & ~0x03) | (m5::stl::to_underlying(m) & 0x03);
|
||||
}
|
||||
uint8_t value{};
|
||||
};
|
||||
|
||||
struct Config {
|
||||
//
|
||||
inline Standby standby() const
|
||||
{
|
||||
return standby_table[(value >> 5) & 0x07];
|
||||
}
|
||||
inline Filter filter() const
|
||||
{
|
||||
return filter_table[(value >> 2) & 0x07];
|
||||
}
|
||||
//
|
||||
inline void standby(const Standby s)
|
||||
{
|
||||
value = (value & ~(0x07 << 5)) | ((m5::stl::to_underlying(s) & 0x07) << 5);
|
||||
}
|
||||
inline void filter(const Filter f)
|
||||
{
|
||||
value = (value & ~(0x07 << 2)) | ((m5::stl::to_underlying(f) & 0x07) << 2);
|
||||
}
|
||||
uint8_t value{};
|
||||
};
|
||||
|
||||
struct Calculator {
|
||||
inline float temperature(const int32_t adc_P, const int32_t adc_T, const Trimming* t)
|
||||
{
|
||||
return t ? compensate_temperature_f(adc_T, *t) : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
inline float pressure(const int32_t adc_P, const int32_t adc_T, const Trimming* t)
|
||||
{
|
||||
if (!t) {
|
||||
return std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
if (t_fine == 0) {
|
||||
(void)compensate_temperature_f(adc_T, *t); // For t_fine
|
||||
}
|
||||
return compensate_pressure_f(adc_P, *t);
|
||||
}
|
||||
|
||||
private:
|
||||
float compensate_temperature(const int32_t adc_T, const Trimming& trim)
|
||||
{
|
||||
int32_t var1{}, var2{};
|
||||
var1 = ((((adc_T >> 3) - ((int32_t)trim.dig_T1 << 1))) * ((int32_t)trim.dig_T2)) >> 11;
|
||||
var2 = (((((adc_T >> 4) - ((int32_t)trim.dig_T1)) * ((adc_T >> 4) - ((int32_t)trim.dig_T1))) >> 12) *
|
||||
((int32_t)trim.dig_T3)) >>
|
||||
14;
|
||||
t_fine = var1 + var2; // [*1]
|
||||
float T = (t_fine * 5 + 128) >> 8;
|
||||
return T * 0.01f;
|
||||
}
|
||||
|
||||
float compensate_pressure(const int32_t adc_P, const Trimming& trim)
|
||||
{
|
||||
int64_t var1{}, var2{}, p{};
|
||||
var1 = ((int64_t)t_fine) - 128000; // (*1) using it!
|
||||
var2 = var1 * var1 * (int64_t)trim.dig_P6;
|
||||
var2 = var2 + ((var1 * (int64_t)trim.dig_P5) << 17);
|
||||
var2 = var2 + (((int64_t)trim.dig_P4) << 35);
|
||||
var1 = ((var1 * var1 * (int64_t)trim.dig_P3) >> 8) + ((var1 * (int64_t)trim.dig_P2) << 12);
|
||||
var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)trim.dig_P1) >> 33;
|
||||
if (var1) {
|
||||
p = 1048576 - adc_P;
|
||||
p = (((p << 31) - var2) * 3125) / var1;
|
||||
var1 = (((int64_t)trim.dig_P9) * (p >> 13) * (p >> 13)) >> 25;
|
||||
var2 = (((int64_t)trim.dig_P8) * p) >> 19;
|
||||
p = ((p + var1 + var2) >> 8) + (((int64_t)trim.dig_P7) << 4);
|
||||
return p / 256.f;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float compensate_temperature_f(const int32_t adc_T, const Trimming& trim)
|
||||
{
|
||||
float var1{}, var2{}, T{};
|
||||
var1 = (((float)adc_T) / 16384.0f - ((float)trim.dig_T1) / 1024.0f) * ((float)trim.dig_T2);
|
||||
var2 = ((((float)adc_T) / 131072.0f - ((float)trim.dig_T1) / 8192.0f) *
|
||||
(((float)adc_T) / 131072.0f - ((float)trim.dig_T1) / 8192.0f)) *
|
||||
((float)trim.dig_T3);
|
||||
t_fine = (int32_t)(var1 + var2); // [*2]
|
||||
T = (var1 + var2) / 5120.0f;
|
||||
return T;
|
||||
}
|
||||
|
||||
float compensate_pressure_f(const int32_t adc_P, const Trimming& trim)
|
||||
{
|
||||
float var1{}, var2{}, P{};
|
||||
var1 = ((float)t_fine / 2.0f) - 64000.0f; // (*2)
|
||||
var2 = var1 * var1 * ((float)trim.dig_P6) / 32768.0f;
|
||||
var2 = var2 + var1 * ((float)trim.dig_P5) * 2.0f;
|
||||
var2 = (var2 / 4.0f) + (((float)trim.dig_P4) * 65536.0f);
|
||||
var1 = (((float)trim.dig_P3) * var1 * var1 / 524288.0f + ((float)trim.dig_P2) * var1) / 524288.0f;
|
||||
var1 = (1.0f + var1 / 32768.0f) * ((float)trim.dig_P1);
|
||||
if (var1 == 0.0f) {
|
||||
return 0;
|
||||
}
|
||||
P = 1048576.0f - (float)adc_P;
|
||||
P = (P - (var2 / 4096.0f)) * 6250.0f / var1;
|
||||
var1 = ((float)trim.dig_P9) * P * P / 2147483648.0f;
|
||||
var2 = P * ((float)trim.dig_P8) / 32768.0f;
|
||||
P = P + (var1 + var2 + ((float)trim.dig_P7)) / 16.0f;
|
||||
return P;
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
int32_t t_fine{};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
namespace bmp280 {
|
||||
|
||||
float Data::celsius() const
|
||||
{
|
||||
int32_t adc_P = (int32_t)(((uint32_t)raw[0] << 16) | ((uint32_t)raw[1] << 8) | ((uint32_t)raw[2]));
|
||||
int32_t adc_T = (int32_t)(((uint32_t)raw[3] << 16) | ((uint32_t)raw[4] << 8) | ((uint32_t)raw[5]));
|
||||
Calculator c{};
|
||||
|
||||
// adc_T is NOT_MEASURED if orsr Skipped (Not measured)
|
||||
return (adc_T != NOT_MEASURED) ? c.temperature(adc_P >> 4, adc_T >> 4, trimming)
|
||||
: std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
|
||||
float Data::fahrenheit() const
|
||||
{
|
||||
return celsius() * 9.0f / 5.0f + 32.f;
|
||||
}
|
||||
|
||||
float Data::pressure() const
|
||||
{
|
||||
int32_t adc_P = (int32_t)(((uint32_t)raw[0] << 16) | ((uint32_t)raw[1] << 8) | ((uint32_t)raw[2]));
|
||||
int32_t adc_T = (int32_t)(((uint32_t)raw[3] << 16) | ((uint32_t)raw[4] << 8) | ((uint32_t)raw[5]));
|
||||
Calculator c{};
|
||||
|
||||
// adc_T/P is NOT_MEASURED if orsr Skipped (Not measured)
|
||||
return (adc_T != NOT_MEASURED && adc_P != NOT_MEASURED) ? c.pressure(adc_P >> 4, adc_T >> 4, trimming)
|
||||
: std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
|
||||
} // namespace bmp280
|
||||
|
||||
const char UnitBMP280::name[] = "UnitBMP280";
|
||||
const types::uid_t UnitBMP280::uid{"UnitBMP280"_mmh3};
|
||||
const types::attr_t UnitBMP280::attr{attribute::AccessI2C};
|
||||
|
||||
bool UnitBMP280::begin()
|
||||
{
|
||||
auto ssize = stored_size();
|
||||
assert(ssize && "stored_size must be greater than zero");
|
||||
if (ssize != _data->capacity()) {
|
||||
_data.reset(new m5::container::CircularBuffer<Data>(ssize));
|
||||
if (!_data) {
|
||||
M5_LIB_LOGE("Failed to allocate");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t id{};
|
||||
if (!softReset() || !readRegister8(CHIP_ID, id, 0) || id != CHIP_IDENTIFIER) {
|
||||
M5_LIB_LOGE("Can not detect BMP280 %02X", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!read_trimming(_trimming)) {
|
||||
M5_LIB_LOGE("Failed to read trimming");
|
||||
return false;
|
||||
}
|
||||
|
||||
M5_LIB_LOGV(
|
||||
"Trimming\n"
|
||||
"T:%u/%d/%d\n"
|
||||
"P:%u/%d/%d/%d/%d/%d/%d/%d/%d",
|
||||
// T
|
||||
_trimming.dig_T1, _trimming.dig_T2, _trimming.dig_T3,
|
||||
// P
|
||||
_trimming.dig_P1, _trimming.dig_P2, _trimming.dig_P3, _trimming.dig_P4, _trimming.dig_P5, _trimming.dig_P6,
|
||||
_trimming.dig_P7, _trimming.dig_P8, _trimming.dig_P9);
|
||||
|
||||
return _cfg.start_periodic
|
||||
? startPeriodicMeasurement(_cfg.osrs_pressure, _cfg.osrs_temperature, _cfg.filter, _cfg.standby)
|
||||
: true;
|
||||
}
|
||||
|
||||
void UnitBMP280::update(const bool force)
|
||||
{
|
||||
_updated = false;
|
||||
if (inPeriodic()) {
|
||||
elapsed_time_t at{m5::utility::millis()};
|
||||
if (force || !_latest || at >= _latest + _interval) {
|
||||
Data d{};
|
||||
// _updated = is_data_ready() && read_measurement(d);
|
||||
_updated = read_measurement(d);
|
||||
if (_updated) {
|
||||
// auto dur = at - _latest;
|
||||
// M5_LIB_LOGW(">DUR:%ld\n", dur);
|
||||
_latest = at;
|
||||
_data->push_back(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UnitBMP280::start_periodic_measurement(const bmp280::Oversampling osrsPressure,
|
||||
const bmp280::Oversampling osrsTemperature, const bmp280::Filter filter,
|
||||
const bmp280::Standby st)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
Config c{};
|
||||
c.standby(st);
|
||||
c.filter(filter);
|
||||
CtrlMeas cm{};
|
||||
cm.osrs_p(osrsPressure);
|
||||
cm.osrs_t(osrsTemperature);
|
||||
|
||||
return writeRegister8(CONFIG, c.value) && writeRegister8(CONTROL_MEASUREMENT, cm.value) &&
|
||||
start_periodic_measurement();
|
||||
}
|
||||
|
||||
bool UnitBMP280::start_periodic_measurement()
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
Config c{};
|
||||
_periodic = readRegister8(CONFIG, c.value, 0) && writePowerMode(PowerMode::Normal);
|
||||
if (_periodic) {
|
||||
_latest = 0;
|
||||
_interval = interval_table[m5::stl::to_underlying(c.standby())];
|
||||
}
|
||||
return _periodic;
|
||||
}
|
||||
|
||||
bool UnitBMP280::stop_periodic_measurement()
|
||||
{
|
||||
if (inPeriodic() && writePowerMode(PowerMode::Sleep)) {
|
||||
_periodic = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitBMP280::measureSingleshot(bmp280::Data& d, const bmp280::Oversampling osrsPressure,
|
||||
const bmp280::Oversampling osrsTemperature, const bmp280::Filter filter)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
if (osrsTemperature == Oversampling::Skipped) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Config c{};
|
||||
c.filter(filter);
|
||||
CtrlMeas cm{};
|
||||
cm.osrs_p(osrsPressure);
|
||||
cm.osrs_t(osrsTemperature);
|
||||
return writeRegister8(CONFIG, c.value) && writeRegister8(CONTROL_MEASUREMENT, cm.value) && measure_singleshot(d);
|
||||
}
|
||||
|
||||
bool UnitBMP280::measure_singleshot(bmp280::Data& d)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (writePowerMode(PowerMode::Forced)) {
|
||||
auto start_at = m5::utility::millis();
|
||||
auto timeout_at = start_at + 2 * 1000; // 2sec
|
||||
bool done{};
|
||||
do {
|
||||
PowerMode pm{};
|
||||
done = readPowerMode(pm) && (pm == PowerMode::Sleep) && is_data_ready();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
m5::utility::delay(1);
|
||||
} while (!done && m5::utility::millis() <= timeout_at);
|
||||
return done && read_measurement(d);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitBMP280::readOversampling(Oversampling& osrsPressure, Oversampling& osrsTemperature)
|
||||
{
|
||||
CtrlMeas cm{};
|
||||
if (readRegister8(CONTROL_MEASUREMENT, cm.value, 0)) {
|
||||
osrsPressure = cm.osrs_p();
|
||||
osrsTemperature = cm.osrs_t();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitBMP280::writeOversampling(const Oversampling osrsPressure, const Oversampling osrsTemperature)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
CtrlMeas cm{};
|
||||
if (readRegister8(CONTROL_MEASUREMENT, cm.value, 0)) {
|
||||
cm.osrs_p(osrsPressure);
|
||||
cm.osrs_t(osrsTemperature);
|
||||
return writeRegister8(CONTROL_MEASUREMENT, cm.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitBMP280::writeOversamplingPressure(const Oversampling osrsPressure)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
CtrlMeas cm{};
|
||||
if (readRegister8(CONTROL_MEASUREMENT, cm.value, 0)) {
|
||||
cm.osrs_p(osrsPressure);
|
||||
return writeRegister8(CONTROL_MEASUREMENT, cm.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitBMP280::writeOversamplingTemperature(const Oversampling osrsTemperature)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
CtrlMeas cm{};
|
||||
if (readRegister8(CONTROL_MEASUREMENT, cm.value, 0)) {
|
||||
cm.osrs_t(osrsTemperature);
|
||||
return writeRegister8(CONTROL_MEASUREMENT, cm.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitBMP280::writeOversampling(const bmp280::OversamplingSetting osrss)
|
||||
{
|
||||
auto idx = m5::stl::to_underlying(osrss);
|
||||
return writeOversampling(osrss_table[idx][0], osrss_table[idx][1]);
|
||||
}
|
||||
|
||||
bool UnitBMP280::readPowerMode(PowerMode& m)
|
||||
{
|
||||
CtrlMeas cm{};
|
||||
if (readRegister8(CONTROL_MEASUREMENT, cm.value, 0)) {
|
||||
m = cm.mode();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitBMP280::writePowerMode(const PowerMode m)
|
||||
{
|
||||
CtrlMeas cm{};
|
||||
if (readRegister8(CONTROL_MEASUREMENT, cm.value, 0)) {
|
||||
cm.mode(m);
|
||||
|
||||
// Datasheet says
|
||||
// If the device is currently performing ameasurement,
|
||||
// execution of mode switching commands is delayed until the end of the currentlyrunning measurement period
|
||||
bool can{};
|
||||
auto timeout_at = m5::utility::millis() + 1000;
|
||||
do {
|
||||
can = is_data_ready();
|
||||
if (can) {
|
||||
break;
|
||||
}
|
||||
m5::utility::delay(1);
|
||||
} while (!can && m5::utility::millis() <= timeout_at);
|
||||
|
||||
return can && writeRegister8(CONTROL_MEASUREMENT, cm.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitBMP280::readFilter(Filter& f)
|
||||
{
|
||||
Config c{};
|
||||
if (readRegister8(CONFIG, c.value, 0)) {
|
||||
f = c.filter();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitBMP280::writeFilter(const Filter& f)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
PowerMode pm{};
|
||||
if (!readPowerMode(pm) || pm != PowerMode::Sleep) {
|
||||
// Datasheet says
|
||||
// Writes to the config register in normal mode may be ignored. In sleep mode writes are not ignored
|
||||
M5_LIB_LOGE("Invalid power mode %02X", pm);
|
||||
return false;
|
||||
}
|
||||
|
||||
Config c{};
|
||||
if (readRegister8(CONFIG, c.value, 0)) {
|
||||
c.filter(f);
|
||||
return writeRegister8(CONFIG, c.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitBMP280::readStandbyTime(Standby& s)
|
||||
{
|
||||
Config c{};
|
||||
if (readRegister8(CONFIG, c.value, 0)) {
|
||||
s = c.standby();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitBMP280::writeStandbyTime(const Standby s)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
Config c{};
|
||||
if (readRegister8(CONFIG, c.value, 0)) {
|
||||
c.standby(s);
|
||||
return writeRegister8(CONFIG, c.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitBMP280::writeUseCaseSetting(const bmp280::UseCase uc)
|
||||
{
|
||||
const auto& tbl = uc_table[m5::stl::to_underlying(uc)];
|
||||
return writeOversampling(tbl.osrss) && writeFilter(tbl.filter) && writeStandbyTime(tbl.st);
|
||||
}
|
||||
|
||||
bool UnitBMP280::softReset()
|
||||
{
|
||||
if (writeRegister8(SOFT_RESET, RESET_VALUE)) {
|
||||
auto timeout_at = m5::utility::millis() + 100; // 100ms
|
||||
uint8_t s{0xFF};
|
||||
do {
|
||||
if (readRegister8(GET_STATUS, s, 0) && (s & 0x01 /* im update */) == 0x00) {
|
||||
_periodic = false;
|
||||
return true;
|
||||
}
|
||||
} while ((s & 0x01) && m5::utility::millis() < timeout_at);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
bool UnitBMP280::read_trimming(Trimming& t)
|
||||
{
|
||||
return readRegister(TRIMMING_DIG, t.value, m5::stl::size(t.value), 0);
|
||||
}
|
||||
|
||||
bool UnitBMP280::is_data_ready()
|
||||
{
|
||||
uint8_t s{0xFF};
|
||||
return readRegister8(GET_STATUS, s, 0) && ((s & 0x09 /* Measuring, im update */) == 0x00);
|
||||
}
|
||||
|
||||
bool UnitBMP280::read_measurement(bmp280::Data& d)
|
||||
{
|
||||
d.trimming = nullptr;
|
||||
|
||||
// Datasheet says
|
||||
// Shadowing will only work if all data registers are read in a single burst read.
|
||||
// Therefore, the user must use burst reads if he does not synchronize data readout with themeasurement cycle
|
||||
if (readRegister(GET_MEASUREMENT, d.raw.data(), d.raw.size(), 0)) {
|
||||
d.trimming = &_trimming;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
434
libraries/M5Unit-ENV/src/unit/unit_BMP280.hpp
Normal file
434
libraries/M5Unit-ENV/src/unit/unit_BMP280.hpp
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_BMP280.hpp
|
||||
@brief BMP280 Unit for M5UnitUnified
|
||||
*/
|
||||
#ifndef M5_UNIT_ENV_UNIT_BNP280_HPP
|
||||
#define M5_UNIT_ENV_UNIT_BNP280_HPP
|
||||
|
||||
#include <M5UnitComponent.hpp>
|
||||
#include <m5_utility/container/circular_buffer.hpp>
|
||||
#include <limits> // NaN
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
|
||||
/*!
|
||||
@namespace bmp280
|
||||
@brief For BMP280
|
||||
*/
|
||||
namespace bmp280 {
|
||||
|
||||
/*!
|
||||
@enum PowerMode
|
||||
@brief Operation mode
|
||||
*/
|
||||
enum class PowerMode : uint8_t {
|
||||
Sleep, //!< No measurements are performed
|
||||
Forced, //!< Single measurements are performed
|
||||
Normal = 0x03, //!< Periodic measurements are performed
|
||||
};
|
||||
|
||||
/*!
|
||||
@enum Oversampling
|
||||
@brief Oversampling factor
|
||||
*/
|
||||
enum class Oversampling : uint8_t {
|
||||
Skipped, //!< Skipped (No measurements are performed)
|
||||
X1, //!< x1
|
||||
X2, //!< x2
|
||||
X4, //!< x4
|
||||
X8, //!< x8
|
||||
X16, //!< x16
|
||||
};
|
||||
|
||||
/*!
|
||||
@enum OversamplingSetting
|
||||
@brief Oversampling Settings
|
||||
*/
|
||||
enum class OversamplingSetting : uint8_t {
|
||||
UltraLowPower, //!< 16 bit / 2.62 Pa, 16 bit / 0.0050 C
|
||||
LowPower, //!< 17 bit / 1.31 Pa, 16 bit / 0.0050 C
|
||||
StandardResolution, //!< 18 bit / 0.66 Pa, 16 bit / 0.0050 C
|
||||
HighResolution, //!< 19 bit / 0.33 Pa, 16 bit / 0.0050 C
|
||||
UltraHighResolution, //!< 20 bit / 0.16 Pa, 17 bit / 0.0025 C
|
||||
};
|
||||
|
||||
/*!
|
||||
@enum Filter
|
||||
@brief Filter setting
|
||||
*/
|
||||
enum class Filter : uint8_t {
|
||||
Off, //!< Off filter
|
||||
Coeff2, //!< co-efficient 2
|
||||
Coeff4, //!< co-efficient 4
|
||||
Coeff8, //!< co-efficient 8
|
||||
Coeff16, //!< co-efficient 16
|
||||
};
|
||||
|
||||
/*!
|
||||
@enum Standby
|
||||
@brief Measurement standby time for power mode Normal
|
||||
*/
|
||||
enum class Standby : uint8_t {
|
||||
Time0_5ms, //!< 0.5 ms
|
||||
Time62_5ms, //!< 62.5 ms
|
||||
Time125ms, //!< 125 ms
|
||||
Time250ms, //!< 250 ms
|
||||
Time500ms, //!< 500 ms
|
||||
Time1sec, //!< 1 second
|
||||
Time2sec, //!< 2 seconds
|
||||
Time4sec, //!< 4 seconds
|
||||
};
|
||||
|
||||
/*!
|
||||
@enum UseCase
|
||||
@brief Preset settings
|
||||
*/
|
||||
enum class UseCase : uint8_t {
|
||||
LowPower, //!< Handheld device low-power
|
||||
Dynamic, //!< Handheld device dynamic
|
||||
Weather, //!< Weather monitoring
|
||||
Elevator, //!< Elevator / floor change detection
|
||||
Drop, //!< Drop detection
|
||||
Indoor, //!< Indoor navigation
|
||||
};
|
||||
|
||||
/*!
|
||||
@union Trimmming
|
||||
@brief Trimming parameter
|
||||
*/
|
||||
union Trimming {
|
||||
uint8_t value[12 * 2]{};
|
||||
struct {
|
||||
//
|
||||
uint16_t dig_T1;
|
||||
int16_t dig_T2;
|
||||
int16_t dig_T3;
|
||||
//
|
||||
uint16_t dig_P1;
|
||||
int16_t dig_P2;
|
||||
int16_t dig_P3;
|
||||
int16_t dig_P4;
|
||||
int16_t dig_P5;
|
||||
int16_t dig_P6;
|
||||
int16_t dig_P7;
|
||||
int16_t dig_P8;
|
||||
int16_t dig_P9;
|
||||
// uint16_t reserved;
|
||||
} __attribute__((packed));
|
||||
};
|
||||
|
||||
/*!
|
||||
@struct Data
|
||||
@brief Measurement data group
|
||||
*/
|
||||
struct Data {
|
||||
std::array<uint8_t, 6> raw{}; //!< RAW data [0,1,2]:pressure [3,4,5]:temperature
|
||||
const Trimming* trimming{}; //!< For calculate
|
||||
|
||||
//! temperature (Celsius)
|
||||
inline float temperature() const
|
||||
{
|
||||
return celsius();
|
||||
}
|
||||
float celsius() const; //!< temperature (Celsius)
|
||||
float fahrenheit() const; //!< temperature (Fahrenheit)
|
||||
float pressure() const; //!< pressure (Pa)
|
||||
};
|
||||
|
||||
} // namespace bmp280
|
||||
|
||||
/*!
|
||||
@class UnitBMP280
|
||||
@brief Pressure and temperature sensor unit
|
||||
*/
|
||||
class UnitBMP280 : public Component, public PeriodicMeasurementAdapter<UnitBMP280, bmp280::Data> {
|
||||
M5_UNIT_COMPONENT_HPP_BUILDER(UnitBMP280, 0x76);
|
||||
|
||||
public:
|
||||
/*!
|
||||
@struct config_t
|
||||
@brief Settings for begin
|
||||
*/
|
||||
struct config_t {
|
||||
//! Start periodic measurement on begin?
|
||||
bool start_periodic{true};
|
||||
//! Pressure oversampling if start on begin
|
||||
bmp280::Oversampling osrs_pressure{bmp280::Oversampling::X16};
|
||||
//! Temperature oversampling if start on begin
|
||||
bmp280::Oversampling osrs_temperature{bmp280::Oversampling::X2};
|
||||
//! Filter if start on begin
|
||||
bmp280::Filter filter{bmp280::Filter::Coeff16};
|
||||
//! Standby time if start on begin
|
||||
bmp280::Standby standby{bmp280::Standby::Time1sec};
|
||||
};
|
||||
|
||||
explicit UnitBMP280(const uint8_t addr = DEFAULT_ADDRESS)
|
||||
: Component(addr), _data{new m5::container::CircularBuffer<bmp280::Data>(1)}
|
||||
{
|
||||
auto ccfg = component_config();
|
||||
ccfg.clock = 400 * 1000U;
|
||||
component_config(ccfg);
|
||||
}
|
||||
virtual ~UnitBMP280()
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool begin() override;
|
||||
virtual void update(const bool force = false) override;
|
||||
|
||||
///@name Settings for begin
|
||||
///@{
|
||||
/*! @brief Gets the configration */
|
||||
inline config_t config()
|
||||
{
|
||||
return _cfg;
|
||||
}
|
||||
//! @brief Set the configration
|
||||
inline void config(const config_t& cfg)
|
||||
{
|
||||
_cfg = cfg;
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Measurement data by periodic
|
||||
///@{
|
||||
//! @brief Oldest measured temperature (Celsius)
|
||||
inline float temperature() const
|
||||
{
|
||||
return !empty() ? oldest().temperature() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
//! @brief Oldest measured temperature (Celsius)
|
||||
inline float celsius() const
|
||||
{
|
||||
return !empty() ? oldest().celsius() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
//! @brief Oldest measured temperature (Fahrenheit)
|
||||
inline float fahrenheit() const
|
||||
{
|
||||
return !empty() ? oldest().fahrenheit() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
//! @brief Oldest measured pressure (Pa)
|
||||
inline float pressure() const
|
||||
{
|
||||
return !empty() ? oldest().pressure() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Periodic measurement
|
||||
///@{
|
||||
/*!
|
||||
@brief Start periodic measurement
|
||||
@param osrsPressure Oversampling factor for pressure
|
||||
@param osrsTemperature Oversampling factor for temperature
|
||||
@param filter Filter coeff
|
||||
@param st Standby time
|
||||
@return True if successful
|
||||
@warning Measuring pressure requires measuring temperature
|
||||
*/
|
||||
inline bool startPeriodicMeasurement(const bmp280::Oversampling osrsPressure,
|
||||
const bmp280::Oversampling osrsTemperature, const bmp280::Filter filter,
|
||||
const bmp280::Standby st)
|
||||
{
|
||||
return PeriodicMeasurementAdapter<UnitBMP280, bmp280::Data>::startPeriodicMeasurement(
|
||||
osrsPressure, osrsTemperature, filter, st);
|
||||
}
|
||||
//! @brief Start periodic measurement using current settings
|
||||
inline bool startPeriodicMeasurement()
|
||||
{
|
||||
return PeriodicMeasurementAdapter<UnitBMP280, bmp280::Data>::startPeriodicMeasurement();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Stop periodic measurement
|
||||
@return True if successful
|
||||
*/
|
||||
inline bool stopPeriodicMeasurement()
|
||||
{
|
||||
return PeriodicMeasurementAdapter<UnitBMP280, bmp280::Data>::stopPeriodicMeasurement();
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Single shot measurement
|
||||
///@{
|
||||
/*!
|
||||
@brief Measurement single shot
|
||||
@param[out] data Measuerd data
|
||||
@param osrsPressure Oversampling factor for pressure
|
||||
@param osrsTemperature Oversampling factor for temperature
|
||||
@param filter Filter coeff
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
@warning Measuring pressure requires measuring temperature
|
||||
@warning Each setting is overwritten
|
||||
*/
|
||||
bool measureSingleshot(bmp280::Data& d, const bmp280::Oversampling osrsPressure,
|
||||
const bmp280::Oversampling osrsTemperature, const bmp280::Filter filter);
|
||||
//! @brief Measurement single shot using current settings
|
||||
inline bool measureSingleshot(bmp280::Data& d)
|
||||
{
|
||||
return measure_singleshot(d);
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Settings
|
||||
///@{
|
||||
/*!
|
||||
@brief Read the oversampling conditions
|
||||
@param[out] osrsPressure Oversampling for pressure
|
||||
@param[out] osrsTemperature Oversampling for temperature
|
||||
@return True if successful
|
||||
*/
|
||||
bool readOversampling(bmp280::Oversampling& osrsPressure, bmp280::Oversampling& osrsTemperature);
|
||||
/*!
|
||||
@brief Write the oversampling conditions
|
||||
@param osrsPressure Oversampling for pressure
|
||||
@param osrsTemperature Oversampling for temperature
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool writeOversampling(const bmp280::Oversampling osrsPressure, const bmp280::Oversampling osrsTemperature);
|
||||
/*!
|
||||
@brief Write the oversampling conditions for pressure
|
||||
@param osrsPressure Oversampling for pressure
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool writeOversamplingPressure(const bmp280::Oversampling osrsPressure);
|
||||
/*!
|
||||
@brief Write the oversampling conditions for temperature
|
||||
@param osrsTemperature Oversampling for temperature
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool writeOversamplingTemperature(const bmp280::Oversampling osrsTemperature);
|
||||
/*!
|
||||
@brief Write the oversampling by OversamplingSetting
|
||||
@param osrss OversamplingSetting
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool writeOversampling(const bmp280::OversamplingSetting osrss);
|
||||
/*!
|
||||
@brief Read the IIR filter co-efficient
|
||||
@param[out] f filter
|
||||
@return True if successful
|
||||
*/
|
||||
bool readFilter(bmp280::Filter& f);
|
||||
/*!
|
||||
@brief Write the IIR filter co-efficient
|
||||
@param f filter
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool writeFilter(const bmp280::Filter& f);
|
||||
/*!
|
||||
@brief Read the standby time
|
||||
@param[out] s standby time
|
||||
@return True if successful
|
||||
*/
|
||||
bool readStandbyTime(bmp280::Standby& s);
|
||||
/*!
|
||||
@brief Write the standby time
|
||||
@param s standby time
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool writeStandbyTime(const bmp280::Standby s);
|
||||
/*!
|
||||
@brief Read the power mode
|
||||
@param[out] m Power mode
|
||||
@return True if successful
|
||||
*/
|
||||
bool readPowerMode(bmp280::PowerMode& m);
|
||||
/*!
|
||||
@brief Write the power mode
|
||||
@param m Power mode
|
||||
@return True if successful
|
||||
@warning Note that the measurement mode is changed
|
||||
@warning It is recommended to use start/stopPeriodicMeasurement or similar to change the measurement mode
|
||||
*/
|
||||
bool writePowerMode(const bmp280::PowerMode m);
|
||||
/*!
|
||||
@brief Write the settings based on use cases
|
||||
@param uc UseCase
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool writeUseCaseSetting(const bmp280::UseCase uc);
|
||||
///@}
|
||||
|
||||
/*!
|
||||
@brief Soft reset
|
||||
@return True if successful
|
||||
*/
|
||||
bool softReset();
|
||||
|
||||
protected:
|
||||
bool start_periodic_measurement(const bmp280::Oversampling osrsPressure, const bmp280::Oversampling osrsTemperature,
|
||||
const bmp280::Filter filter, const bmp280::Standby st);
|
||||
bool start_periodic_measurement();
|
||||
bool stop_periodic_measurement();
|
||||
bool read_measurement(bmp280::Data& d);
|
||||
bool measure_singleshot(bmp280::Data& d);
|
||||
|
||||
bool read_trimming(bmp280::Trimming& t);
|
||||
bool is_data_ready();
|
||||
|
||||
M5_UNIT_COMPONENT_PERIODIC_MEASUREMENT_ADAPTER_HPP_BUILDER(UnitBMP280, bmp280::Data);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<m5::container::CircularBuffer<bmp280::Data>> _data{};
|
||||
config_t _cfg{};
|
||||
bmp280::Trimming _trimming{};
|
||||
};
|
||||
|
||||
///@cond
|
||||
namespace bmp280 {
|
||||
namespace command {
|
||||
|
||||
constexpr uint8_t CHIP_ID{0xD0};
|
||||
// constexpr uint8_t CHIP_VERSION{0xD1};
|
||||
constexpr uint8_t SOFT_RESET{0xE0};
|
||||
constexpr uint8_t GET_STATUS{0xF3};
|
||||
constexpr uint8_t CONTROL_MEASUREMENT{0xF4};
|
||||
constexpr uint8_t CONFIG{0xF5};
|
||||
|
||||
constexpr uint8_t GET_MEASUREMENT{0XF7}; // 6bytes
|
||||
constexpr uint8_t GET_PRESSURE{0xF7}; // 3byts
|
||||
constexpr uint8_t GET_PRESSURE_MSB{0xF7}; // 7:0
|
||||
constexpr uint8_t GET_PRESSURE_LSB{0xF8}; // 7:0
|
||||
constexpr uint8_t GET_PRESSURE_XLSB{0xF9}; // 7:4
|
||||
constexpr uint8_t GET_TEMPERATURE{0XFA}; // 3 bytes
|
||||
constexpr uint8_t GET_TEMPERATURE_MSB{0XFA}; // 7:0
|
||||
constexpr uint8_t GET_TEMPERATURE_LSB{0XFB}; // 7:0
|
||||
constexpr uint8_t GET_TEMPERATURE_XLSB{0XFC}; // 7:4
|
||||
|
||||
constexpr uint8_t TRIMMING_DIG{0x88}; // 12 bytes
|
||||
constexpr uint8_t TRIMMING_DIG_T1{0x88};
|
||||
constexpr uint8_t TRIMMING_DIG_T2{0x8A};
|
||||
constexpr uint8_t TRIMMING_DIG_T3{0x8C};
|
||||
constexpr uint8_t TRIMMING_DIG_P1{0x8E};
|
||||
constexpr uint8_t TRIMMING_DIG_P2{0x90};
|
||||
constexpr uint8_t TRIMMING_DIG_P3{0x92};
|
||||
constexpr uint8_t TRIMMING_DIG_P4{0x94};
|
||||
constexpr uint8_t TRIMMING_DIG_P5{0x96};
|
||||
constexpr uint8_t TRIMMING_DIG_P6{0x98};
|
||||
constexpr uint8_t TRIMMING_DIG_P7{0x9A};
|
||||
constexpr uint8_t TRIMMING_DIG_P8{0x9C};
|
||||
constexpr uint8_t TRIMMING_DIG_P9{0x9A};
|
||||
constexpr uint8_t TRIMMING_DIG_RESERVED{0xA0};
|
||||
|
||||
} // namespace command
|
||||
} // namespace bmp280
|
||||
///@endcond
|
||||
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
|
||||
#endif
|
||||
48
libraries/M5Unit-ENV/src/unit/unit_ENV3.cpp
Normal file
48
libraries/M5Unit-ENV/src/unit/unit_ENV3.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_ENV3.cpp
|
||||
@brief ENV III Unit for M5UnitUnified
|
||||
*/
|
||||
#include "unit_ENV3.hpp"
|
||||
#include <M5Utility.hpp>
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
|
||||
using namespace m5::utility::mmh3;
|
||||
using namespace m5::unit::types;
|
||||
|
||||
const char UnitENV3::name[] = "UnitENV3";
|
||||
const types::uid_t UnitENV3::uid{"UnitENV3"_mmh3};
|
||||
const types::attr_t UnitENV3::attr{attribute::AccessI2C};
|
||||
|
||||
UnitENV3::UnitENV3(const uint8_t addr) : Component(addr)
|
||||
{
|
||||
// Form a parent-child relationship
|
||||
auto cfg = component_config();
|
||||
cfg.max_children = 2;
|
||||
component_config(cfg);
|
||||
_valid = add(sht30, 0) && add(qmp6988, 1);
|
||||
}
|
||||
|
||||
std::shared_ptr<Adapter> UnitENV3::ensure_adapter(const uint8_t ch)
|
||||
{
|
||||
if (ch > 2) {
|
||||
M5_LIB_LOGE("Invalid channel %u", ch);
|
||||
return std::make_shared<Adapter>(); // Empty adapter
|
||||
}
|
||||
auto unit = child(ch);
|
||||
if (!unit) {
|
||||
M5_LIB_LOGE("Not exists unit %u", ch);
|
||||
return std::make_shared<Adapter>(); // Empty adapter
|
||||
}
|
||||
auto ad = asAdapter<AdapterI2C>(Adapter::Type::I2C);
|
||||
return ad ? std::shared_ptr<Adapter>(ad->duplicate(unit->address())) : std::make_shared<Adapter>();
|
||||
}
|
||||
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
53
libraries/M5Unit-ENV/src/unit/unit_ENV3.hpp
Normal file
53
libraries/M5Unit-ENV/src/unit/unit_ENV3.hpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_ENV3.hpp
|
||||
@brief ENV III Unit for M5UnitUnified
|
||||
*/
|
||||
#ifndef M5_UNIT_ENV_UNIT_ENV3_HPP
|
||||
#define M5_UNIT_ENV_UNIT_ENV3_HPP
|
||||
|
||||
#include <M5UnitComponent.hpp>
|
||||
#include "unit_SHT30.hpp"
|
||||
#include "unit_QMP6988.hpp"
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
|
||||
/*!
|
||||
@class UnitENV3
|
||||
@brief ENV III is an environmental sensor that integrates SHT30 and QMP6988
|
||||
@details This unit itself has no I/O, but holds SHT30 and QMP6988 instance
|
||||
*/
|
||||
class UnitENV3 : public Component {
|
||||
// Must not be 0x00 for ensure and assign adapter to children
|
||||
M5_UNIT_COMPONENT_HPP_BUILDER(UnitENV3, 0xFF /* Dummy address */);
|
||||
|
||||
public:
|
||||
UnitSHT30 sht30; //!< @brief SHT30 instance
|
||||
UnitQMP6988 qmp6988; //!< @brief QMP6988 instance
|
||||
|
||||
explicit UnitENV3(const uint8_t addr = DEFAULT_ADDRESS);
|
||||
virtual ~UnitENV3()
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool begin() override
|
||||
{
|
||||
return _valid;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual std::shared_ptr<Adapter> ensure_adapter(const uint8_t ch);
|
||||
|
||||
private:
|
||||
bool _valid{}; // Did the constructor correctly add the child unit?
|
||||
Component* _children[2]{&sht30, &qmp6988};
|
||||
};
|
||||
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
#endif
|
||||
48
libraries/M5Unit-ENV/src/unit/unit_ENV4.cpp
Normal file
48
libraries/M5Unit-ENV/src/unit/unit_ENV4.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_ENV4.cpp
|
||||
@brief ENV 4 Unit for M5UnitUnified
|
||||
*/
|
||||
#include "unit_ENV4.hpp"
|
||||
#include <M5Utility.hpp>
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
|
||||
using namespace m5::utility::mmh3;
|
||||
using namespace m5::unit::types;
|
||||
|
||||
const char UnitENV4::name[] = "UnitENV4";
|
||||
const types::uid_t UnitENV4::uid{"UnitENV4"_mmh3};
|
||||
const types::attr_t UnitENV4::attr{attribute::AccessI2C};
|
||||
|
||||
UnitENV4::UnitENV4(const uint8_t addr) : Component(addr)
|
||||
{
|
||||
// Form a parent-child relationship
|
||||
auto cfg = component_config();
|
||||
cfg.max_children = 2;
|
||||
component_config(cfg);
|
||||
_valid = add(sht40, 0) && add(bmp280, 1);
|
||||
}
|
||||
|
||||
std::shared_ptr<Adapter> UnitENV4::ensure_adapter(const uint8_t ch)
|
||||
{
|
||||
if (ch > 2) {
|
||||
M5_LIB_LOGE("Invalid channel %u", ch);
|
||||
return std::make_shared<Adapter>(); // Empty adapter
|
||||
}
|
||||
auto unit = child(ch);
|
||||
if (!unit) {
|
||||
M5_LIB_LOGE("Not exists unit %u", ch);
|
||||
return std::make_shared<Adapter>(); // Empty adapter
|
||||
}
|
||||
auto ad = asAdapter<AdapterI2C>(Adapter::Type::I2C);
|
||||
return ad ? std::shared_ptr<Adapter>(ad->duplicate(unit->address())) : std::make_shared<Adapter>();
|
||||
}
|
||||
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
54
libraries/M5Unit-ENV/src/unit/unit_ENV4.hpp
Normal file
54
libraries/M5Unit-ENV/src/unit/unit_ENV4.hpp
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_ENV4.hpp
|
||||
@brief ENV IV Unit for M5UnitUnified
|
||||
*/
|
||||
#ifndef M5_UNIT_ENV_UNIT_ENV4_HPP
|
||||
#define M5_UNIT_ENV_UNIT_ENV4_HPP
|
||||
|
||||
#include <M5UnitComponent.hpp>
|
||||
#include <array>
|
||||
#include "unit_SHT40.hpp"
|
||||
#include "unit_BMP280.hpp"
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
|
||||
/*!
|
||||
@class UnitENV4
|
||||
@brief ENV IV is an environmental sensor that integrates SHT40 and BMP280
|
||||
@details This unit itself has no I/O, but holds SHT40 and BMP280 instance
|
||||
*/
|
||||
class UnitENV4 : public Component {
|
||||
// Must not be 0x00 for ensure and assign adapter to children
|
||||
M5_UNIT_COMPONENT_HPP_BUILDER(UnitENV4, 0xFF /* Dummy address */);
|
||||
|
||||
public:
|
||||
UnitSHT40 sht40; //!< @brief SHT40 instance
|
||||
UnitBMP280 bmp280; //!< @brief BMP280 instance
|
||||
|
||||
explicit UnitENV4(const uint8_t addr = DEFAULT_ADDRESS);
|
||||
virtual ~UnitENV4()
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool begin() override
|
||||
{
|
||||
return _valid;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual std::shared_ptr<Adapter> ensure_adapter(const uint8_t ch);
|
||||
|
||||
private:
|
||||
bool _valid{}; // Did the constructor correctly add the child unit?
|
||||
Component* _children[2]{&sht40, &bmp280};
|
||||
};
|
||||
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
#endif
|
||||
594
libraries/M5Unit-ENV/src/unit/unit_QMP6988.cpp
Normal file
594
libraries/M5Unit-ENV/src/unit/unit_QMP6988.cpp
Normal file
|
|
@ -0,0 +1,594 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_QMP6988.cpp
|
||||
@brief QMP6988 Unit for M5UnitUnified
|
||||
*/
|
||||
#include "unit_QMP6988.hpp"
|
||||
#include <M5Utility.hpp>
|
||||
#include <limits> // NaN
|
||||
#include <cmath>
|
||||
#include <thread>
|
||||
|
||||
using namespace m5::utility::mmh3;
|
||||
using namespace m5::unit::types;
|
||||
using namespace m5::unit::qmp6988;
|
||||
using namespace m5::unit::qmp6988::command;
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t chip_id{0x5C};
|
||||
constexpr size_t calibration_length{25};
|
||||
constexpr uint32_t sub_raw{8388608}; // 2^23
|
||||
|
||||
constexpr Oversampling osrss_table[][2] = {
|
||||
// Pressure, Temperature
|
||||
{Oversampling::X2, Oversampling::X1}, {Oversampling::X4, Oversampling::X1}, {Oversampling::X8, Oversampling::X1},
|
||||
{Oversampling::X16, Oversampling::X2}, {Oversampling::X32, Oversampling::X4},
|
||||
};
|
||||
|
||||
constexpr PowerMode mode_table[] = {
|
||||
PowerMode::Sleep,
|
||||
PowerMode::Forced,
|
||||
PowerMode::Forced, // duplicated
|
||||
PowerMode::Normal,
|
||||
};
|
||||
|
||||
constexpr Filter filter_table[] = {
|
||||
Filter::Off, Filter::Coeff2, Filter::Coeff4, Filter::Coeff8, Filter::Coeff16, Filter::Coeff32,
|
||||
Filter::Coeff32, // duplicated
|
||||
Filter::Coeff32, // duplicated
|
||||
};
|
||||
|
||||
struct UseCaseSetting {
|
||||
OversamplingSetting osrss;
|
||||
Filter filter;
|
||||
};
|
||||
constexpr UseCaseSetting uc_table[] = {
|
||||
{OversamplingSetting::HighSpeed, Filter::Off},
|
||||
{OversamplingSetting::LowPower, Filter::Off},
|
||||
{OversamplingSetting::Standard, Filter::Coeff4},
|
||||
{OversamplingSetting::HighAccuracy, Filter::Coeff8},
|
||||
{OversamplingSetting::UltraHightAccuracy, Filter::Coeff32},
|
||||
};
|
||||
|
||||
#if 1
|
||||
constexpr elapsed_time_t standby_time_table[] = {
|
||||
5, 5, 50, 250, 500, 1000, 2000, 4000,
|
||||
};
|
||||
|
||||
constexpr float ostb{4.4933f};
|
||||
constexpr float oversampling_temp_time_table[] = {
|
||||
0.0f, ostb * 1, ostb * 2, ostb * 4, ostb * 8, ostb * 16, ostb * 32, ostb * 64,
|
||||
};
|
||||
|
||||
constexpr float ospb{0.5032f};
|
||||
constexpr float oversampling_pressure_time_table[] = {
|
||||
0.0f, ospb * 1, ospb * 2, ospb * 4, ospb * 8, ospb * 16, ospb * 32, ospb * 64,
|
||||
};
|
||||
|
||||
constexpr float filter_time_table[] = {
|
||||
0.0f, 0.3f, 0.6f, 1.2f, 2.4f, 4.8f, 9.6f, 9.6f, 9.6f,
|
||||
};
|
||||
#endif
|
||||
|
||||
constexpr uint32_t interval_table[] = {1, 5, 50, 250, 500, 1000, 2000, 4000};
|
||||
|
||||
int16_t convert_temperature256(const int32_t dt, const m5::unit::qmp6988::Calibration& c)
|
||||
{
|
||||
int64_t wk1, wk2;
|
||||
int16_t temp256{};
|
||||
// wk1: 60Q4 // bit size
|
||||
wk1 = ((int64_t)c.a1 * (int64_t)dt); // 31Q23+24-1=54 (54Q23)
|
||||
wk2 = ((int64_t)c.a2 * (int64_t)dt) >> 14; // 30Q47+24-1=53 (39Q33)
|
||||
wk2 = (wk2 * (int64_t)dt) >> 10; // 39Q33+24-1=62 (52Q23)
|
||||
wk2 = ((wk1 + wk2) / 32767) >> 19; // 54,52->55Q23 (20Q04)
|
||||
temp256 = (int16_t)((c.a0 + wk2) >> 4); // 21Q4 -> 17Q0
|
||||
return temp256;
|
||||
}
|
||||
|
||||
int32_t convert_pressure16(const int32_t dp, const int16_t tx, const Calibration& c)
|
||||
{
|
||||
int64_t wk1, wk2, wk3;
|
||||
|
||||
// wk1 = 48Q16 // bit size
|
||||
wk1 = ((int64_t)c.bt1 * (int64_t)tx); // 28Q15+16-1=43 (43Q15)
|
||||
wk2 = ((int64_t)c.bp1 * (int64_t)dp) >> 5; // 31Q20+24-1=54 (49Q15)
|
||||
wk1 += wk2; // 43,49->50Q15
|
||||
wk2 = ((int64_t)c.bt2 * (int64_t)tx) >> 1; // 34Q38+16-1=49 (48Q37)
|
||||
wk2 = (wk2 * (int64_t)tx) >> 8; // 48Q37+16-1=63 (55Q29)
|
||||
wk3 = wk2; // 55Q29
|
||||
wk2 = ((int64_t)c.b11 * (int64_t)tx) >> 4; // 28Q34+16-1=43 (39Q30)
|
||||
wk2 = (wk2 * (int64_t)dp) >> 1; // 39Q30+24-1=62 (61Q29)
|
||||
wk3 += wk2; // 55,61->62Q29
|
||||
wk2 = ((int64_t)c.bp2 * (int64_t)dp) >> 13; // 29Q43+24-1=52 (39Q30)
|
||||
wk2 = (wk2 * (int64_t)dp) >> 1; // 39Q30+24-1=62 (61Q29)
|
||||
wk3 += wk2; // 62,61->63Q29
|
||||
wk1 += wk3 >> 14; // Q29 >> 14 -> Q15
|
||||
wk2 = ((int64_t)c.b12 * (int64_t)tx); // 29Q53+16-1=45 (45Q53)
|
||||
wk2 = (wk2 * (int64_t)tx) >> 22; // 45Q53+16-1=61 (39Q31)
|
||||
wk2 = (wk2 * (int64_t)dp) >> 1; // 39Q31+24-1=62 (61Q30)
|
||||
wk3 = wk2; // 61Q30
|
||||
wk2 = ((int64_t)c.b21 * (int64_t)tx) >> 6; // 29Q60+16-1=45 (39Q54)
|
||||
wk2 = (wk2 * (int64_t)dp) >> 23; // 39Q54+24-1=62 (39Q31)
|
||||
wk2 = (wk2 * (int64_t)dp) >> 1; // 39Q31+24-1=62 (61Q20)
|
||||
wk3 += wk2; // 61,61->62Q30
|
||||
wk2 = ((int64_t)c.bp3 * (int64_t)dp) >> 12; // 28Q65+24-1=51 (39Q53)
|
||||
wk2 = (wk2 * (int64_t)dp) >> 23; // 39Q53+24-1=62 (39Q30)
|
||||
wk2 = (wk2 * (int64_t)dp); // 39Q30+24-1=62 (62Q30)
|
||||
wk3 += wk2; // 62,62->63Q30
|
||||
wk1 += wk3 >> 15; // Q30 >> 15 = Q15
|
||||
wk1 /= 32767L;
|
||||
wk1 >>= 11; // Q15 >> 7 = Q4
|
||||
wk1 += c.b00; // Q4 + 20Q4
|
||||
// Not shifted to set output at 16 Pa
|
||||
// wk1 >>= 4; // 28Q4 -> 24Q0
|
||||
int32_t p16 = (int32_t)wk1;
|
||||
return p16;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct CtrlMeas {
|
||||
Oversampling osrs_t() const
|
||||
{
|
||||
return static_cast<Oversampling>((value >> 5) & 0x07);
|
||||
}
|
||||
Oversampling osrs_p() const
|
||||
{
|
||||
return static_cast<Oversampling>((value >> 2) & 0x07);
|
||||
}
|
||||
PowerMode mode() const
|
||||
{
|
||||
return mode_table[value & 0x03];
|
||||
}
|
||||
void osrs_t(const Oversampling os)
|
||||
{
|
||||
value = (value & ~(0x07 << 5)) | ((m5::stl::to_underlying(os) & 0x07) << 5);
|
||||
}
|
||||
void osrs_p(const Oversampling os)
|
||||
{
|
||||
value = (value & ~(0x07 << 2)) | ((m5::stl::to_underlying(os) & 0x07) << 2);
|
||||
}
|
||||
void mode(const PowerMode m)
|
||||
{
|
||||
value = (value & ~0x03) | (m5::stl::to_underlying(m) & 0x03);
|
||||
}
|
||||
uint8_t value{};
|
||||
};
|
||||
|
||||
struct IOSetup {
|
||||
Standby standby() const
|
||||
{
|
||||
return static_cast<Standby>((value >> 5) & 0x07);
|
||||
}
|
||||
void standby(const Standby s)
|
||||
{
|
||||
value = (value & ~(0x07 << 5)) | ((m5::stl::to_underlying(s) & 0x07) << 5);
|
||||
}
|
||||
uint8_t value{};
|
||||
};
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
|
||||
namespace qmp6988 {
|
||||
float Data::celsius() const
|
||||
{
|
||||
uint32_t rt = (((uint32_t)raw[3]) << 16) | (((uint32_t)raw[4]) << 8) | ((uint32_t)raw[5]);
|
||||
if (calib && rt) {
|
||||
int32_t dt = (int32_t)(rt - sub_raw);
|
||||
int16_t t256 = convert_temperature256(dt, *calib);
|
||||
return (float)t256 / 256.f;
|
||||
}
|
||||
return std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
|
||||
float Data::fahrenheit() const
|
||||
{
|
||||
return celsius() * 9.0f / 5.0f + 32.f;
|
||||
}
|
||||
|
||||
float Data::pressure() const
|
||||
{
|
||||
uint32_t rt = (((uint32_t)raw[3]) << 16) | (((uint32_t)raw[4]) << 8) | ((uint32_t)raw[5]);
|
||||
uint32_t rp = (((uint32_t)raw[0]) << 16) | (((uint32_t)raw[1]) << 8) | ((uint32_t)raw[2]);
|
||||
|
||||
if (calib && rt && rp) {
|
||||
int32_t dt = (int32_t)(rt - sub_raw);
|
||||
int16_t t256 = convert_temperature256(dt, *calib);
|
||||
int32_t dp = (int32_t)(rp - sub_raw);
|
||||
int32_t p16 = convert_pressure16(dp, t256, *calib);
|
||||
return (float)p16 / 16.0f;
|
||||
}
|
||||
return std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
}; // namespace qmp6988
|
||||
|
||||
//
|
||||
const char UnitQMP6988::name[] = "UnitQMP6988";
|
||||
const types::uid_t UnitQMP6988::uid{"UnitQMP6988"_mmh3};
|
||||
const types::attr_t UnitQMP6988::attr{attribute::AccessI2C};
|
||||
|
||||
types::elapsed_time_t calculatInterval(const Standby st, const Oversampling ost, const Oversampling osp, const Filter f)
|
||||
{
|
||||
// M5_LIB_LOGV("ST:%u OST:%u OSP:%u F:%u", st, ost, osp, f);
|
||||
// M5_LIB_LOGV(
|
||||
// "Value ST:%u OST:%u OSP:%u F:%u",
|
||||
// standby_time_table[m5::stl::to_underlying(st)],
|
||||
// (elapsed_time_t)std::ceil(
|
||||
// oversampling_temp_time_table[m5::stl::to_underlying(ost)]),
|
||||
// (elapsed_time_t)std::ceil(
|
||||
// oversampling_pressure_time_table[m5::stl::to_underlying(osp)]),
|
||||
// (elapsed_time_t)std::ceil(
|
||||
// filter_time_table[m5::stl::to_underlying(f)]));
|
||||
|
||||
elapsed_time_t itv = standby_time_table[m5::stl::to_underlying(st)] +
|
||||
(elapsed_time_t)std::ceil(oversampling_temp_time_table[m5::stl::to_underlying(ost)]) +
|
||||
(elapsed_time_t)std::ceil(oversampling_pressure_time_table[m5::stl::to_underlying(osp)]) +
|
||||
(elapsed_time_t)std::ceil(filter_time_table[m5::stl::to_underlying(f)]);
|
||||
return itv;
|
||||
}
|
||||
|
||||
bool UnitQMP6988::begin()
|
||||
{
|
||||
auto ssize = stored_size();
|
||||
assert(ssize && "stored_size must be greater than zero");
|
||||
if (ssize != _data->capacity()) {
|
||||
_data.reset(new m5::container::CircularBuffer<Data>(ssize));
|
||||
if (!_data) {
|
||||
M5_LIB_LOGE("Failed to allocate");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t id{};
|
||||
if (!readRegister8(CHIP_ID, id, 0) || id != chip_id) {
|
||||
M5_LIB_LOGE("This unit is NOT QMP6988 %x", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!softReset()) {
|
||||
M5_LIB_LOGE("Failed to reset");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!read_calibration(_calibration)) {
|
||||
M5_LIB_LOGE("Failed to read_calibration");
|
||||
return false;
|
||||
}
|
||||
|
||||
return _cfg.start_periodic
|
||||
? startPeriodicMeasurement(_cfg.osrs_pressure, _cfg.osrs_temperature, _cfg.filter, _cfg.standby)
|
||||
: true;
|
||||
}
|
||||
|
||||
void UnitQMP6988::update(const bool force)
|
||||
{
|
||||
_updated = false;
|
||||
if (inPeriodic()) {
|
||||
elapsed_time_t at{m5::utility::millis()};
|
||||
if (force || !_latest || at >= _latest + _interval) {
|
||||
Data d{};
|
||||
//_updated = is_data_ready() && read_measurement(d);
|
||||
_updated = read_measurement(d, _only_temperature);
|
||||
if (_updated) {
|
||||
// auto dur = at - _latest;
|
||||
// M5_LIB_LOGW(">DUR:%ld", dur);
|
||||
_latest = at;
|
||||
_data->push_back(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UnitQMP6988::start_periodic_measurement(const qmp6988::Oversampling osrsPressure,
|
||||
const qmp6988::Oversampling osrsTemperature, const qmp6988::Filter f,
|
||||
const Standby st)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Need temperature for measure pressure (Only temperature measurement is acceptable)
|
||||
if (osrsTemperature == Oversampling::Skipped) {
|
||||
return false;
|
||||
}
|
||||
_only_temperature = (osrsPressure == Oversampling::Skipped);
|
||||
|
||||
return writeOversampling(osrsPressure, osrsTemperature) && writeFilter(f) && writeStandbyTime(st) &&
|
||||
start_periodic_measurement();
|
||||
}
|
||||
|
||||
bool UnitQMP6988::start_periodic_measurement()
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IOSetup is{};
|
||||
_periodic = readRegister8(IO_SETUP, is.value, 0) && writePowerMode(PowerMode::Normal);
|
||||
if (_periodic) {
|
||||
_latest = 0;
|
||||
_interval = interval_table[m5::stl::to_underlying(is.standby())];
|
||||
}
|
||||
return _periodic;
|
||||
}
|
||||
|
||||
bool UnitQMP6988::stop_periodic_measurement()
|
||||
{
|
||||
if (inPeriodic() && writePowerMode(PowerMode::Sleep)) {
|
||||
_periodic = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitQMP6988::measureSingleshot(qmp6988::Data& d, const qmp6988::Oversampling osrsPressure,
|
||||
const qmp6988::Oversampling osrsTemperature, const qmp6988::Filter f)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Need temperature for measure pressure (Only temperature measurement is acceptable)
|
||||
if (osrsTemperature == Oversampling::Skipped) {
|
||||
return false;
|
||||
}
|
||||
return writeOversampling(osrsPressure, osrsTemperature) && writeFilter(f) && measureSingleshot(d);
|
||||
}
|
||||
|
||||
bool UnitQMP6988::measureSingleshot(qmp6988::Data& d)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
CtrlMeas cm{};
|
||||
if (readRegister8(CONTROL_MEASUREMENT, cm.value, 0) && writePowerMode(qmp6988::PowerMode::Forced)) {
|
||||
auto timeout_at = m5::utility::millis() + 1 * 1000;
|
||||
bool done{};
|
||||
do {
|
||||
done = is_data_ready();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::yield();
|
||||
// m5::utility::delay(1);
|
||||
} while (!done && m5::utility::millis() <= timeout_at);
|
||||
return done && read_measurement(d, cm.osrs_p() == Oversampling::Skipped);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitQMP6988::readOversampling(qmp6988::Oversampling& osrsPressure, qmp6988::Oversampling& osrsTemperature)
|
||||
{
|
||||
CtrlMeas cm{};
|
||||
if (readRegister8(CONTROL_MEASUREMENT, cm.value, 0)) {
|
||||
osrsPressure = cm.osrs_p();
|
||||
osrsTemperature = cm.osrs_t();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitQMP6988::writeOversampling(const qmp6988::Oversampling osrsPressure,
|
||||
const qmp6988::Oversampling osrsTemperature)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
CtrlMeas cm{};
|
||||
if (readRegister8(CONTROL_MEASUREMENT, cm.value, 0)) {
|
||||
cm.osrs_p(osrsPressure);
|
||||
cm.osrs_t(osrsTemperature);
|
||||
return writeRegister8(CONTROL_MEASUREMENT, cm.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitQMP6988::writeOversamplingPressure(const qmp6988::Oversampling osrsPressure)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
CtrlMeas cm{};
|
||||
if (readRegister8(CONTROL_MEASUREMENT, cm.value, 0)) {
|
||||
cm.osrs_p(osrsPressure);
|
||||
return writeRegister8(CONTROL_MEASUREMENT, cm.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitQMP6988::writeOversamplingTemperature(const qmp6988::Oversampling osrsTemperature)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
CtrlMeas cm{};
|
||||
if (readRegister8(CONTROL_MEASUREMENT, cm.value, 0)) {
|
||||
cm.osrs_t(osrsTemperature);
|
||||
return writeRegister8(CONTROL_MEASUREMENT, cm.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitQMP6988::writeOversampling(const qmp6988::OversamplingSetting osrss)
|
||||
{
|
||||
auto idx = m5::stl::to_underlying(osrss);
|
||||
return writeOversampling(osrss_table[idx][0], osrss_table[idx][1]);
|
||||
}
|
||||
|
||||
bool UnitQMP6988::readPowerMode(qmp6988::PowerMode& m)
|
||||
{
|
||||
CtrlMeas cm{};
|
||||
if (readRegister8(CONTROL_MEASUREMENT, cm.value, 0)) {
|
||||
m = cm.mode();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitQMP6988::writePowerMode(const qmp6988::PowerMode m)
|
||||
{
|
||||
CtrlMeas cm{};
|
||||
if (readRegister8(CONTROL_MEASUREMENT, cm.value, 0)) {
|
||||
cm.mode(m);
|
||||
|
||||
// Changing mode during measurement may result in erratic data the next time
|
||||
auto timeout_at = m5::utility::millis() + 1000;
|
||||
bool can{};
|
||||
do {
|
||||
can = is_data_ready();
|
||||
if (can) {
|
||||
break;
|
||||
}
|
||||
m5::utility::delay(1);
|
||||
} while (!can && m5::utility::millis() <= timeout_at);
|
||||
return can && writeRegister8(CONTROL_MEASUREMENT, cm.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitQMP6988::readFilter(qmp6988::Filter& f)
|
||||
{
|
||||
uint8_t v{};
|
||||
if (readRegister8(IIR_FILTER, v, 0)) {
|
||||
f = filter_table[v & 0x07];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitQMP6988::writeFilter(const qmp6988::Filter f)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
return writeRegister8(IIR_FILTER, m5::stl::to_underlying(f));
|
||||
}
|
||||
|
||||
bool UnitQMP6988::readStandbyTime(qmp6988::Standby& st)
|
||||
{
|
||||
IOSetup is{};
|
||||
if (readRegister8(IO_SETUP, is.value, 0)) {
|
||||
st = is.standby();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitQMP6988::writeStandbyTime(const qmp6988::Standby st)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
IOSetup is{};
|
||||
if (readRegister8(IO_SETUP, is.value, 0)) {
|
||||
is.standby(st);
|
||||
return writeRegister8(IO_SETUP, is.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitQMP6988::writeUseCaseSetting(const qmp6988::UseCase uc)
|
||||
{
|
||||
const auto& tbl = uc_table[m5::stl::to_underlying(uc)];
|
||||
return writeOversampling(tbl.osrss) && writeFilter(tbl.filter);
|
||||
}
|
||||
|
||||
bool UnitQMP6988::softReset()
|
||||
{
|
||||
constexpr uint8_t v{0xE6}; // When inputting "E6h", a soft-reset will be occurred
|
||||
|
||||
auto ret = writeRegister8(SOFT_RESET, v);
|
||||
M5_LIB_LOGD("Reset causes a NO ACK or timeout error, but ignore it");
|
||||
(void)ret;
|
||||
m5::utility::delay(10); // Need delay
|
||||
if (writeRegister(SOFT_RESET, 0x00)) {
|
||||
_periodic = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitQMP6988::is_data_ready()
|
||||
{
|
||||
uint8_t v{};
|
||||
return readRegister8(GET_STATUS, v, 0) && ((v & 0x08 /* measure */) == 0);
|
||||
}
|
||||
|
||||
bool UnitQMP6988::read_measurement(Data& d, const bool only_temperature)
|
||||
|
||||
{
|
||||
if (readRegister(READ_PRESSURE, d.raw.data(), d.raw.size(), 0)) {
|
||||
// If osrs_p is Skipped, but the previous pressure data is still there, so it is deleted
|
||||
if (only_temperature) {
|
||||
d.raw[0] = d.raw[1] = d.raw[2] = 0;
|
||||
}
|
||||
d.calib = &_calibration;
|
||||
// M5_DUMPI(d.raw.data(), d.raw.size());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitQMP6988::read_calibration(qmp6988::Calibration& c)
|
||||
{
|
||||
using namespace m5::utility; // unsigned_to_signed
|
||||
using namespace m5::types; // big_uint16_t
|
||||
|
||||
uint8_t rbuf[calibration_length]{};
|
||||
if (!readRegister(READ_COMPENSATION_COEFFICIENT, rbuf, sizeof(rbuf), 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t b00 = ((uint32_t)(big_uint16_t(rbuf[0], rbuf[1]).get()) << 4) | ((rbuf[24] >> 4) & 0x0F);
|
||||
c.b00 = unsigned_to_signed<20>(b00); // 20Q4
|
||||
c.bt1 = 2982L * (int64_t)unsigned_to_signed<16>(big_uint16_t(rbuf[2], rbuf[3]).get()) + 107370906L; // 28Q15
|
||||
c.bt2 = 329854L * (int64_t)unsigned_to_signed<16>(big_uint16_t(rbuf[4], rbuf[5]).get()) + +108083093L; // 34Q38
|
||||
c.bp1 = 19923L * (int64_t)unsigned_to_signed<16>(big_uint16_t(rbuf[6], rbuf[7]).get()) + 1133836764L; // 31Q20
|
||||
c.b11 = 2406L * (int64_t)unsigned_to_signed<16>(big_uint16_t(rbuf[8], rbuf[9]).get()) + 118215883L; // 28Q34
|
||||
c.bp2 = 3079L * (int64_t)unsigned_to_signed<16>(big_uint16_t(rbuf[10], rbuf[11]).get()) - 181579595L; // 29Q43
|
||||
c.b12 = 6846L * (int64_t)unsigned_to_signed<16>(big_uint16_t(rbuf[12], rbuf[13]).get()) + 85590281L; // 29Q53
|
||||
c.b21 = 13836L * (int64_t)unsigned_to_signed<16>(big_uint16_t(rbuf[14], rbuf[15]).get()) + 79333336L; // 29Q60
|
||||
c.bp3 = 2915L * (int64_t)unsigned_to_signed<16>(big_uint16_t(rbuf[16], rbuf[17]).get()) + 157155561L; // 28Q65
|
||||
uint32_t a0 = ((uint32_t)big_uint16_t(rbuf[18], rbuf[19]).get() << 4) | (rbuf[24] & 0x0F);
|
||||
c.a0 = unsigned_to_signed<20>(a0); // 20Q4
|
||||
c.a1 = 3608L * (int32_t)unsigned_to_signed<16>(big_uint16_t(rbuf[20], rbuf[21]).get()) - 1731677965L; // 31Q23
|
||||
c.a2 = 16889L * (int32_t)unsigned_to_signed<16>(big_uint16_t(rbuf[22], rbuf[23]).get()) - 87619360L; // 31Q47
|
||||
#if 0
|
||||
M5_LIB_LOGI(
|
||||
"\n"
|
||||
"b00:%d\n"
|
||||
"bt1:%d\n"
|
||||
"bt2:%lld\n"
|
||||
"bp1:%d\n"
|
||||
"b11:%d\n"
|
||||
"bp2:%d\n"
|
||||
"b12:%d\n"
|
||||
"b21:%d\n"
|
||||
"bp3:%d\n"
|
||||
"a0:%d\n"
|
||||
"a1:%d\n"
|
||||
"a2:%d",
|
||||
c.b00, c.bt1, c.bt2, c.bp1, c.b11, c.bp2, c.b12, c.b21, c.bp3, c.a0,
|
||||
c.a1, c.a2);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
393
libraries/M5Unit-ENV/src/unit/unit_QMP6988.hpp
Normal file
393
libraries/M5Unit-ENV/src/unit/unit_QMP6988.hpp
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_QMP6988.hpp
|
||||
@brief QMP6988 Unit for M5UnitUnified
|
||||
*/
|
||||
#ifndef M5_UNIT_ENV_UNIT_QMP6988_HPP
|
||||
#define M5_UNIT_ENV_UNIT_QMP6988_HPP
|
||||
#include <M5UnitComponent.hpp>
|
||||
#include <m5_utility/stl/extension.hpp>
|
||||
#include <m5_utility/container/circular_buffer.hpp>
|
||||
#include <limits> // NaN
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
|
||||
namespace qmp6988 {
|
||||
|
||||
/*!
|
||||
@enum PowerMode
|
||||
@brief Operation mode
|
||||
*/
|
||||
enum class PowerMode : uint8_t {
|
||||
Sleep, //!< No measurements are performed
|
||||
Forced, //!< Single measurements are performed
|
||||
Normal = 0x03, //!< Periodic measurements are performed
|
||||
};
|
||||
|
||||
/*!
|
||||
@enum Oversampling
|
||||
@brief Oversampling value
|
||||
@warning
|
||||
*/
|
||||
enum class Oversampling : uint8_t {
|
||||
Skipped, //!< Skipped (No measurements are performed)
|
||||
X1, //!< x1
|
||||
X2, //!< x2
|
||||
X4, //!< x4
|
||||
X8, //!< x8
|
||||
X16, //!< x16
|
||||
X32, //!< x32
|
||||
X64, //!< x64
|
||||
};
|
||||
|
||||
/*!
|
||||
@enum OversamplingSetting
|
||||
@brief Oversampling Settings
|
||||
*/
|
||||
enum class OversamplingSetting : uint8_t {
|
||||
HighSpeed, //!< osrsP:X2 osrsT:X1
|
||||
LowPower, //!< osrsP:X4 osrsT:X1
|
||||
Standard, //!< osrsP:X8 osrsT:X1
|
||||
HighAccuracy, //!< osrsP:X16 osrsT:X2
|
||||
UltraHightAccuracy, //!< osrsP:X32 osrsT:X4
|
||||
};
|
||||
|
||||
/*!
|
||||
@enum Filter
|
||||
@brief Filtter setting
|
||||
*/
|
||||
enum class Filter : uint8_t {
|
||||
Off, //!< Off filter
|
||||
Coeff2, //!< co-efficient 2
|
||||
Coeff4, //!< co-efficient 4
|
||||
Coeff8, //!< co-efficient 8
|
||||
Coeff16, //!< co-efficient 16
|
||||
Coeff32, //!< co-efficient 32
|
||||
};
|
||||
|
||||
/*!
|
||||
@enum Standby
|
||||
@brief Measurement standby time for power mode Normal
|
||||
*/
|
||||
enum class Standby : uint8_t {
|
||||
Time1ms, //!< 1 ms
|
||||
Time5ms, //!< 5 ms
|
||||
Time50ms, //!< 50 ms
|
||||
Time250ms, //!< 250 ms
|
||||
Time500ms, //!< 500 ms
|
||||
Time1sec, //!< 1 seconds
|
||||
Time2sec, //!< 2 seconds
|
||||
Time4sec, //!< 4 seconds
|
||||
};
|
||||
|
||||
/*!
|
||||
@enum UseCase
|
||||
@brief Preset settings
|
||||
*/
|
||||
enum class UseCase : uint8_t {
|
||||
Weather, //!< Weather monitoring
|
||||
Drop, //!< Drop detection
|
||||
Elevator, //!< Elevator / floor change detection
|
||||
Stair, //!< Stair detection
|
||||
Indoor, //!< Indoor navigation
|
||||
};
|
||||
|
||||
///@cond
|
||||
struct Calibration {
|
||||
int32_t b00{}, bt1{}, bp1{};
|
||||
int64_t bt2{};
|
||||
int32_t b11{}, bp2{}, b12{}, b21{}, bp3{}, a0{}, a1{}, a2{};
|
||||
};
|
||||
///@endcond
|
||||
|
||||
/*!
|
||||
@struct Data
|
||||
@brief Measurement data group
|
||||
*/
|
||||
struct Data {
|
||||
std::array<uint8_t, 6> raw{}; //!< RAW data
|
||||
//! temperature (Celsius)
|
||||
inline float temperature() const
|
||||
{
|
||||
return celsius();
|
||||
}
|
||||
float celsius() const; //!< temperature (Celsius)
|
||||
float fahrenheit() const; //!< temperature (Fahrenheit)
|
||||
float pressure() const; //!< pressure (Pa)
|
||||
const Calibration* calib{};
|
||||
};
|
||||
|
||||
}; // namespace qmp6988
|
||||
|
||||
/*!
|
||||
@class UnitQMP6988
|
||||
@brief Barometric pressure sensor to measure atmospheric pressure and altitude estimation
|
||||
*/
|
||||
class UnitQMP6988 : public Component, public PeriodicMeasurementAdapter<UnitQMP6988, qmp6988::Data> {
|
||||
M5_UNIT_COMPONENT_HPP_BUILDER(UnitQMP6988, 0x70);
|
||||
|
||||
public:
|
||||
/*!
|
||||
@struct config_t
|
||||
@brief Settings for begin
|
||||
*/
|
||||
struct config_t {
|
||||
//! Start periodic measurement on begin?
|
||||
bool start_periodic{true};
|
||||
//! pressure oversampling if start on begin
|
||||
qmp6988::Oversampling osrs_pressure{qmp6988::Oversampling::X8};
|
||||
//! temperature oversampling if start on begin
|
||||
qmp6988::Oversampling osrs_temperature{qmp6988::Oversampling::X1};
|
||||
//! Filter if start on begin
|
||||
qmp6988::Filter filter{qmp6988::Filter::Coeff4};
|
||||
//! Standby time if start on begin
|
||||
qmp6988::Standby standby{qmp6988::Standby::Time1sec};
|
||||
};
|
||||
|
||||
explicit UnitQMP6988(const uint8_t addr = DEFAULT_ADDRESS)
|
||||
: Component(addr), _data{new m5::container::CircularBuffer<qmp6988::Data>(1)}
|
||||
{
|
||||
auto ccfg = component_config();
|
||||
ccfg.clock = 400 * 1000U;
|
||||
component_config(ccfg);
|
||||
}
|
||||
virtual ~UnitQMP6988()
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool begin() override;
|
||||
virtual void update(const bool force = false) override;
|
||||
|
||||
///@name Settings for begin
|
||||
///@{
|
||||
/*! @brief Gets the configration */
|
||||
inline config_t config()
|
||||
{
|
||||
return _cfg;
|
||||
}
|
||||
//! @brief Set the configration
|
||||
inline void config(const config_t& cfg)
|
||||
{
|
||||
_cfg = cfg;
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Measurement data by periodic
|
||||
///@{
|
||||
//! @brief Oldest measured temperature (Celsius)
|
||||
inline float temperature() const
|
||||
{
|
||||
return !empty() ? oldest().temperature() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
//! @brief Oldest measured temperature (Celsius)
|
||||
inline float celsius() const
|
||||
{
|
||||
return !empty() ? oldest().celsius() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
//! @brief Oldest measured temperature (Fahrenheit)
|
||||
inline float fahrenheit() const
|
||||
{
|
||||
return !empty() ? oldest().fahrenheit() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
//! @brief Oldest measured pressure (Pa)
|
||||
inline float pressure() const
|
||||
{
|
||||
return !empty() ? oldest().pressure() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Periodic measurement
|
||||
///@{
|
||||
/*!
|
||||
@brief Start periodic measurement
|
||||
@param osrsPressure Oversampling factor for pressure
|
||||
@param osrsTemperature Oversampling factor for temperature
|
||||
@param filter Filter coeff
|
||||
@param st Standby time
|
||||
@return True if successful
|
||||
@warning Measuring pressure requires measuring temperature
|
||||
*/
|
||||
inline bool startPeriodicMeasurement(const qmp6988::Oversampling osrsPressure,
|
||||
const qmp6988::Oversampling osrsTemperature, const qmp6988::Filter f,
|
||||
const qmp6988::Standby st)
|
||||
{
|
||||
return PeriodicMeasurementAdapter<UnitQMP6988, qmp6988::Data>::startPeriodicMeasurement(osrsPressure,
|
||||
osrsTemperature, f, st);
|
||||
}
|
||||
//! @brief Start periodic measurement using current settings
|
||||
inline bool startPeriodicMeasurement()
|
||||
{
|
||||
return PeriodicMeasurementAdapter<UnitQMP6988, qmp6988::Data>::startPeriodicMeasurement();
|
||||
}
|
||||
/*!
|
||||
@brief Stop periodic measurement
|
||||
@return True if successful
|
||||
*/
|
||||
inline bool stopPeriodicMeasurement()
|
||||
{
|
||||
return PeriodicMeasurementAdapter<UnitQMP6988, qmp6988::Data>::stopPeriodicMeasurement();
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Single shot measurement
|
||||
///@{
|
||||
/*!
|
||||
@brief Measurement single shot
|
||||
@param[out] data Measuerd data
|
||||
@param osrsPressure Oversampling factor for pressure
|
||||
@param osrsTemperature Oversampling factor for temperature
|
||||
@param filter Filter coeff
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
@warning Measuring pressure requires measuring temperature
|
||||
@warning Each setting is overwritten
|
||||
*/
|
||||
bool measureSingleshot(qmp6988::Data& d, const qmp6988::Oversampling osrsPressure,
|
||||
const qmp6988::Oversampling osrsTemperature, const qmp6988::Filter f);
|
||||
//! @brief Measurement single shot using current settings
|
||||
bool measureSingleshot(qmp6988::Data& d);
|
||||
///@}
|
||||
|
||||
///@name Settings
|
||||
///@{
|
||||
/*!
|
||||
@brief Read the oversampling conditions
|
||||
@param[out] osrsPressure Oversampling for pressure
|
||||
@param[out] osrsTemperature Oversampling for temperature
|
||||
@return True if successful
|
||||
*/
|
||||
bool readOversampling(qmp6988::Oversampling& osrsPressure, qmp6988::Oversampling& osrsTemperature);
|
||||
/*!
|
||||
@brief Write the oversampling conditions
|
||||
@param osrsPressure Oversampling for pressure
|
||||
@param osrsTemperature Oversampling for temperature
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool writeOversampling(const qmp6988::Oversampling osrsPressure, const qmp6988::Oversampling osrsTemperature);
|
||||
/*!
|
||||
@brief Write the oversampling conditions for pressure
|
||||
@param osrsPressure Oversampling for pressure
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool writeOversamplingPressure(const qmp6988::Oversampling osrsPressure);
|
||||
/*!
|
||||
@brief Write the oversampling conditions for temperature
|
||||
@param osrsTemperature Oversampling for temperature
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool writeOversamplingTemperature(const qmp6988::Oversampling osrsTemperature);
|
||||
/*!
|
||||
@brief Write the oversampling by OversamplingSetting
|
||||
@param osrss OversamplingSetting
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool writeOversampling(const qmp6988::OversamplingSetting osrss);
|
||||
/*!
|
||||
@brief Read the IIR filter co-efficient
|
||||
@param[out] f filter
|
||||
@return True if successful
|
||||
*/
|
||||
bool readFilter(qmp6988::Filter& f);
|
||||
/*!
|
||||
@brief Write the IIR filter co-efficient
|
||||
@param f filter
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool writeFilter(const qmp6988::Filter f);
|
||||
/*!
|
||||
@brief Read the standby time
|
||||
@param[out] st standby time
|
||||
@return True if successful
|
||||
*/
|
||||
bool readStandbyTime(qmp6988::Standby& st);
|
||||
/*!
|
||||
@brief Write the standby time
|
||||
@param st standby time
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool writeStandbyTime(const qmp6988::Standby st);
|
||||
/*!
|
||||
@brief Read the power mode
|
||||
@param[out] mode PowerMode
|
||||
@return True if successful
|
||||
*/
|
||||
bool readPowerMode(qmp6988::PowerMode& mode);
|
||||
/*!
|
||||
@brief Write the power mode
|
||||
@param m Power mode
|
||||
@return True if successful
|
||||
@warning Note that the measurement mode is changed
|
||||
@warning It is recommended to use start/stopPeriodicMeasurement or similar to change the measurement mode
|
||||
*/
|
||||
bool writePowerMode(const qmp6988::PowerMode mode);
|
||||
/*!
|
||||
@brief Write the settings based on use cases
|
||||
@param uc UseCase
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool writeUseCaseSetting(const qmp6988::UseCase uc);
|
||||
///@}
|
||||
|
||||
/*!
|
||||
@brief Soft reset
|
||||
@return True if successful
|
||||
*/
|
||||
bool softReset();
|
||||
|
||||
protected:
|
||||
bool start_periodic_measurement();
|
||||
bool start_periodic_measurement(const qmp6988::Oversampling ost, const qmp6988::Oversampling osp,
|
||||
const qmp6988::Filter f, const qmp6988::Standby st);
|
||||
bool stop_periodic_measurement();
|
||||
|
||||
bool read_calibration(qmp6988::Calibration& c);
|
||||
|
||||
bool read_measurement(qmp6988::Data& d, const bool only_temperature = false);
|
||||
bool is_data_ready();
|
||||
|
||||
M5_UNIT_COMPONENT_PERIODIC_MEASUREMENT_ADAPTER_HPP_BUILDER(UnitQMP6988, qmp6988::Data);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<m5::container::CircularBuffer<qmp6988::Data>> _data{};
|
||||
qmp6988::Calibration _calibration{};
|
||||
config_t _cfg{};
|
||||
bool _only_temperature{};
|
||||
};
|
||||
|
||||
///@cond
|
||||
namespace qmp6988 {
|
||||
namespace command {
|
||||
|
||||
constexpr uint8_t CHIP_ID{0xD1};
|
||||
|
||||
constexpr uint8_t READ_PRESSURE{0xF7}; // ~ F9 3bytes
|
||||
constexpr uint8_t READ_TEMPERATURE{0xFA}; // ~ FC 3bytes
|
||||
|
||||
constexpr uint8_t IO_SETUP{0xF5};
|
||||
constexpr uint8_t CONTROL_MEASUREMENT{0xF4};
|
||||
constexpr uint8_t GET_STATUS{0xF3};
|
||||
constexpr uint8_t IIR_FILTER{0xF1};
|
||||
|
||||
constexpr uint8_t SOFT_RESET{0xE0};
|
||||
|
||||
constexpr uint8_t READ_COMPENSATION_COEFFICIENT{0xA0}; // ~ 0xB8 25 bytes
|
||||
|
||||
} // namespace command
|
||||
} // namespace qmp6988
|
||||
///@endcond
|
||||
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
#endif
|
||||
530
libraries/M5Unit-ENV/src/unit/unit_SCD40.cpp
Normal file
530
libraries/M5Unit-ENV/src/unit/unit_SCD40.cpp
Normal file
|
|
@ -0,0 +1,530 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_SCD40.cpp
|
||||
@brief SCD40 Unit for M5UnitUnified
|
||||
*/
|
||||
#include "unit_SCD40.hpp"
|
||||
#include <M5Utility.hpp>
|
||||
#include <array>
|
||||
|
||||
using namespace m5::utility::mmh3;
|
||||
using namespace m5::unit::types;
|
||||
using namespace m5::unit::scd4x;
|
||||
using namespace m5::unit::scd4x::command;
|
||||
|
||||
namespace {
|
||||
struct Temperature {
|
||||
constexpr static float toFloat(const uint16_t u16)
|
||||
{
|
||||
return u16 * 175.f / 65536.f;
|
||||
}
|
||||
constexpr static uint16_t toUint16(const float f)
|
||||
{
|
||||
return f * 65536 / 175;
|
||||
}
|
||||
constexpr static float OFFSET_MIN{0.0f};
|
||||
constexpr static float OFFSET_MAX{175.0f};
|
||||
};
|
||||
|
||||
constexpr uint16_t mode_reg_table[] = {
|
||||
START_PERIODIC_MEASUREMENT,
|
||||
START_LOW_POWER_PERIODIC_MEASUREMENT,
|
||||
};
|
||||
|
||||
constexpr uint32_t interval_table[] = {
|
||||
5000U, // 5 Sec.
|
||||
30 * 1000U, // 30 Sec.
|
||||
};
|
||||
|
||||
const uint8_t VARIANT_VALUE[2]{0x04, 0x40}; // SCD40
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
|
||||
namespace scd4x {
|
||||
uint16_t Data::co2() const
|
||||
{
|
||||
return m5::types::big_uint16_t(raw[0], raw[1]).get();
|
||||
}
|
||||
|
||||
float Data::celsius() const
|
||||
{
|
||||
return -45 + Temperature::toFloat(m5::types::big_uint16_t(raw[3], raw[4]).get());
|
||||
}
|
||||
|
||||
float Data::fahrenheit() const
|
||||
{
|
||||
return celsius() * 9.0f / 5.0f + 32.f;
|
||||
}
|
||||
|
||||
float Data::humidity() const
|
||||
{
|
||||
return 100.f * m5::types::big_uint16_t(raw[6], raw[7]).get() / 65536.f;
|
||||
}
|
||||
|
||||
} // namespace scd4x
|
||||
|
||||
// class UnitSCD40
|
||||
const char UnitSCD40::name[] = "UnitSCD40";
|
||||
const types::uid_t UnitSCD40::uid{"UnitSCD40"_mmh3};
|
||||
const types::attr_t UnitSCD40::attr{attribute::AccessI2C};
|
||||
|
||||
bool UnitSCD40::begin()
|
||||
{
|
||||
auto ssize = stored_size();
|
||||
assert(ssize && "stored_size must be greater than zero");
|
||||
if (ssize != _data->capacity()) {
|
||||
_data.reset(new m5::container::CircularBuffer<Data>(ssize));
|
||||
if (!_data) {
|
||||
M5_LIB_LOGE("Failed to allocate");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop (to idle mode)
|
||||
if (!writeRegister(STOP_PERIODIC_MEASUREMENT)) {
|
||||
M5_LIB_LOGE("Failed to stop");
|
||||
return false;
|
||||
}
|
||||
m5::utility::delay(STOP_PERIODIC_MEASUREMENT_DURATION);
|
||||
|
||||
if (!is_valid_chip()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!writeAutomaticSelfCalibrationEnabled(_cfg.calibration)) {
|
||||
M5_LIB_LOGE("Failed to writeAutomaticSelfCalibrationEnabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Stop
|
||||
|
||||
return _cfg.start_periodic ? startPeriodicMeasurement(_cfg.mode) : true;
|
||||
}
|
||||
|
||||
bool UnitSCD40::is_valid_chip()
|
||||
{
|
||||
uint8_t var[2]{};
|
||||
if (!read_register(GET_SENSOR_VARIANT, var, 2) || memcmp(var, VARIANT_VALUE, 2) != 0) {
|
||||
M5_LIB_LOGE("Not SCD40 %02X:%02X", var[0], var[1]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void UnitSCD40::update(const bool force)
|
||||
{
|
||||
_updated = false;
|
||||
if (inPeriodic()) {
|
||||
auto at = m5::utility::millis();
|
||||
if (force || !_latest || at >= _latest + _interval) {
|
||||
Data d{};
|
||||
_updated = read_measurement(d);
|
||||
if (_updated) {
|
||||
_latest = m5::utility::millis(); // Data acquisition takes time, so acquire again
|
||||
_data->push_back(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UnitSCD40::start_periodic_measurement(const Mode mode)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
auto m = m5::stl::to_underlying(mode);
|
||||
_periodic = writeRegister(mode_reg_table[m]);
|
||||
if (_periodic) {
|
||||
_interval = interval_table[m];
|
||||
_latest = 0;
|
||||
}
|
||||
return _periodic;
|
||||
}
|
||||
|
||||
bool UnitSCD40::stop_periodic_measurement(const uint32_t duration)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
if (writeRegister(STOP_PERIODIC_MEASUREMENT)) {
|
||||
_periodic = false;
|
||||
m5::utility::delay(duration);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD40::writeTemperatureOffset(const float offset, const uint32_t duration)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
if (offset < Temperature::OFFSET_MIN || offset >= Temperature::OFFSET_MAX) {
|
||||
M5_LIB_LOGE("offset is not a valid scope %f", offset);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t wbuf[2]{};
|
||||
uint16_t tmp16 = Temperature::toUint16(offset);
|
||||
wbuf[0] = tmp16 >> 8;
|
||||
wbuf[1] = tmp16 & 0xFF;
|
||||
return write_register(SET_TEMPERATURE_OFFSET, wbuf, sizeof(wbuf)) && delay_true(duration);
|
||||
}
|
||||
|
||||
bool UnitSCD40::readTemperatureOffset(float& offset)
|
||||
{
|
||||
offset = 0.0f;
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
m5::types::big_uint16_t u16{};
|
||||
if (read_register(GET_TEMPERATURE_OFFSET, u16.data(), u16.size(), GET_TEMPERATURE_OFFSET_DURATION)) {
|
||||
offset = Temperature::toFloat(u16.get());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD40::writeSensorAltitude(const uint16_t altitude, const uint32_t duration)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
m5::types::big_uint16_t u16(altitude);
|
||||
return write_register(SET_SENSOR_ALTITUDE, u16.data(), u16.size()) && delay_true(duration);
|
||||
}
|
||||
|
||||
bool UnitSCD40::readSensorAltitude(uint16_t& altitude)
|
||||
{
|
||||
altitude = 0;
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
m5::types::big_uint16_t u16{};
|
||||
if (read_register(GET_SENSOR_ALTITUDE, u16.data(), u16.size(), GET_SENSOR_ALTITUDE_DURATION)) {
|
||||
altitude = u16.get();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD40::writeAmbientPressure(const uint16_t pressure, const uint32_t duration)
|
||||
{
|
||||
constexpr uint32_t PRESSURE_MIN{700};
|
||||
constexpr uint32_t PRESSURE_MAX{1200};
|
||||
|
||||
#if 0
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (pressure < PRESSURE_MIN || pressure > PRESSURE_MAX) {
|
||||
M5_LIB_LOGE("pressure is not a valid scope (%u - %u) %u", PRESSURE_MIN, PRESSURE_MAX, pressure);
|
||||
return false;
|
||||
}
|
||||
m5::types::big_uint16_t u16(pressure);
|
||||
return write_register(AMBIENT_PRESSURE, u16.data(), u16.size()) && delay_true(duration);
|
||||
}
|
||||
|
||||
bool UnitSCD40::readAmbientPressure(uint16_t& pressure)
|
||||
{
|
||||
pressure = 0;
|
||||
#if 0
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
m5::types::big_uint16_t u16{};
|
||||
if (read_register(AMBIENT_PRESSURE, u16.data(), u16.size(), GET_AMBIENT_PRESSURE_DURATION)) {
|
||||
pressure = u16.get();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD40::performForcedRecalibration(const uint16_t concentration, int16_t& correction)
|
||||
{
|
||||
// 1. Operate the SCD4x in the operation mode later used in normal sensor
|
||||
// operation (periodic measurement, low power periodic measurement or single
|
||||
// shot) for > 3 minutes in an environment with homogenous and constant CO2
|
||||
// concentration.
|
||||
// 2. Issue stop_periodic_measurement. Wait 500 ms for the stop command to
|
||||
// complete.
|
||||
correction = 0;
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
m5::types::big_uint16_t u16(concentration);
|
||||
if (!write_register(PERFORM_FORCED_CALIBRATION, u16.data(), u16.size())) {
|
||||
return false;
|
||||
}
|
||||
#if 0
|
||||
|
||||
// 3. Subsequently issue the perform_forced_recalibration command and
|
||||
// optionally read out the FRC correction (i.e. the magnitude of the
|
||||
// correction) after waiting for 400 ms for the command to complete.
|
||||
m5::utility::delay(PERFORM_FORCED_CALIBRATION_DURATION);
|
||||
|
||||
std::array<uint8_t, 3> rbuf{};
|
||||
if (readWithTransaction(rbuf.data(), rbuf.size()) == m5::hal::error::error_t::OK) {
|
||||
m5::types::big_uint16_t u16{rbuf[0], rbuf[1]};
|
||||
m5::utility::CRC8_Checksum crc{};
|
||||
if (rbuf[2] == crc.range(u16.data(), u16.size()) && u16.get() != 0xFFFF) {
|
||||
correction = (int16_t)(u16.get() - 0x8000);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
|
||||
// 3. Subsequently issue the perform_forced_recalibration command and
|
||||
// optionally read out the FRC correction (i.e. the magnitude of the
|
||||
// correction) after waiting for 400 ms for the command to complete.
|
||||
m5::utility::delay(PERFORM_FORCED_CALIBRATION_DURATION);
|
||||
|
||||
if (read_register(PERFORM_FORCED_CALIBRATION, u16.data(), u16.size()) && u16.get() != 0xFFFF) {
|
||||
correction = (int16_t)(u16.get() - 0x8000);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool UnitSCD40::writeAutomaticSelfCalibrationEnabled(const bool enabled, const uint32_t duration)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
m5::types::big_uint16_t u16(enabled ? 0x0001 : 0x0000);
|
||||
return write_register(SET_AUTOMATIC_SELF_CALIBRATION_ENABLED, u16.data(), u16.size()) && delay_true(duration);
|
||||
}
|
||||
|
||||
bool UnitSCD40::readAutomaticSelfCalibrationEnabled(bool& enabled)
|
||||
{
|
||||
enabled = false;
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
m5::types::big_uint16_t u16{};
|
||||
if (read_register(GET_AUTOMATIC_SELF_CALIBRATION_ENABLED, u16.data(), u16.size(),
|
||||
GET_AUTOMATIC_SELF_CALIBRATION_ENABLED_DURATION)) {
|
||||
enabled = (u16.get() == 0x0001);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD40::writeAutomaticSelfCalibrationTarget(const uint16_t ppm, const uint32_t duration)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
m5::types::big_uint16_t u16{ppm};
|
||||
return write_register(SET_AUTOMATIC_SELF_CALIBRATION_TARGET, u16.data(), u16.size()) && delay_true(duration);
|
||||
}
|
||||
|
||||
bool UnitSCD40::readAutomaticSelfCalibrationTarget(uint16_t& ppm)
|
||||
{
|
||||
ppm = 0;
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
m5::types::big_uint16_t u16{};
|
||||
if (read_register(GET_AUTOMATIC_SELF_CALIBRATION_TARGET, u16.data(), u16.size(),
|
||||
GET_AUTOMATIC_SELF_CALIBRATION_TARGET_DURATION)) {
|
||||
ppm = u16.get();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD40::writePersistSettings(const uint32_t duration)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (writeRegister(PERSIST_SETTINGS)) {
|
||||
m5::utility::delay(duration);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD40::readSerialNumber(char* serialNumber)
|
||||
{
|
||||
if (!serialNumber) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*serialNumber = '\0';
|
||||
uint64_t sno{};
|
||||
if (readSerialNumber(sno)) {
|
||||
uint_fast8_t i{12};
|
||||
while (i--) {
|
||||
*serialNumber++ = m5::utility::uintToHexChar((sno >> (i * 4)) & 0x0F);
|
||||
}
|
||||
*serialNumber = '\0';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD40::readSerialNumber(uint64_t& serialNumber)
|
||||
{
|
||||
std::array<uint8_t, 9> rbuf;
|
||||
serialNumber = 0;
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
m5::utility::CRC8_Checksum crc{};
|
||||
if (readRegister(GET_SERIAL_NUMBER, rbuf.data(), rbuf.size(), GET_SERIAL_NUMBER_DURATION)) {
|
||||
m5::types::big_uint16_t u16[3]{{rbuf[0], rbuf[1]}, {rbuf[3], rbuf[4]}, {rbuf[6], rbuf[7]}};
|
||||
if (crc.range(u16[0].data(), u16[0].size()) == rbuf[2] && crc.range(u16[1].data(), u16[1].size()) == rbuf[5] &&
|
||||
crc.range(u16[2].data(), u16[2].size()) == rbuf[8]) {
|
||||
serialNumber = ((uint64_t)u16[0].get()) << 32 | ((uint64_t)u16[1].get()) << 16 | ((uint64_t)u16[2].get());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD40::performSelfTest(bool& malfunction)
|
||||
{
|
||||
malfunction = true;
|
||||
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
m5::types::big_uint16_t u16{};
|
||||
if (read_register(PERFORM_SELF_TEST, u16.data(), u16.size(), PERFORM_SELF_TEST_DURATION)) {
|
||||
malfunction = (u16.get() != 0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD40::performFactoryReset(const uint32_t duration)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
if (writeRegister(PERFORM_FACTORY_RESET)) {
|
||||
m5::utility::delay(duration);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD40::reInit(const uint32_t duration)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (writeRegister(REINIT)) {
|
||||
m5::utility::delay(duration);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD40::read_data_ready_status()
|
||||
{
|
||||
uint16_t res{};
|
||||
return readRegister16BE(GET_DATA_READY_STATUS, res, GET_DATA_READY_STATUS_DURATION) ? (res & 0x07FF) != 0 : false;
|
||||
}
|
||||
|
||||
// TH only if all is false
|
||||
bool UnitSCD40::read_measurement(Data& d, const bool all)
|
||||
{
|
||||
if (!read_data_ready_status()) {
|
||||
M5_LIB_LOGV("Not ready");
|
||||
return false;
|
||||
}
|
||||
if (!readRegister(READ_MEASUREMENT, d.raw.data(), d.raw.size(), READ_MEASUREMENT_DURATION)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For RHT only, previous Co2 data may be obtained and should be dismissed
|
||||
if (!all) {
|
||||
d.raw[0] = d.raw[1] = d.raw[2] = 0;
|
||||
}
|
||||
|
||||
// Check CRC
|
||||
m5::utility::CRC8_Checksum crc{};
|
||||
for (uint_fast8_t i = all ? 0 : 1; i < 3; ++i) {
|
||||
if (crc.range(d.raw.data() + i * 3, 2U) != d.raw[i * 3 + 2]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnitSCD40::read_register(const uint16_t reg, uint8_t* rbuf, const uint32_t rlen, const uint32_t duration)
|
||||
{
|
||||
uint8_t tmp[rlen + 1]{};
|
||||
if (!rbuf || !rlen || !readRegister(reg, tmp, sizeof(tmp), duration)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m5::utility::CRC8_Checksum crc{};
|
||||
auto crc8 = crc.range(tmp, rlen);
|
||||
if (crc8 != tmp[rlen]) {
|
||||
M5_LIB_LOGE("CRC8 Error:%02X, %02X", tmp[rlen], crc8);
|
||||
return false;
|
||||
}
|
||||
memcpy(rbuf, tmp, rlen);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnitSCD40::write_register(const uint16_t reg, uint8_t* wbuf, const uint32_t wlen)
|
||||
{
|
||||
uint8_t buf[wlen + 1]{};
|
||||
if (!wbuf || !wlen) {
|
||||
return false;
|
||||
}
|
||||
memcpy(buf, wbuf, wlen);
|
||||
m5::utility::CRC8_Checksum crc{};
|
||||
auto crc8 = crc.range(wbuf, wlen);
|
||||
buf[wlen] = crc8;
|
||||
return writeRegister(reg, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
bool UnitSCD40::delay_true(const uint32_t duration)
|
||||
{
|
||||
m5::utility::delay(duration);
|
||||
return true; // Always true
|
||||
}
|
||||
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
390
libraries/M5Unit-ENV/src/unit/unit_SCD40.hpp
Normal file
390
libraries/M5Unit-ENV/src/unit/unit_SCD40.hpp
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
/*
|
||||
* 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 <M5UnitComponent.hpp>
|
||||
#include <m5_utility/container/circular_buffer.hpp>
|
||||
#include <limits> // 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<uint8_t, 9> 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<UnitSCD40, scd4x::Data> {
|
||||
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<scd4x::Data>(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<float>::quiet_NaN();
|
||||
}
|
||||
//! @brief Oldest measured temperature (Celsius)
|
||||
inline float celsius() const
|
||||
{
|
||||
return !empty() ? oldest().celsius() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
//! @brief Oldest measured temperature (Fahrenheit)
|
||||
inline float fahrenheit() const
|
||||
{
|
||||
return !empty() ? oldest().fahrenheit() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
//! @brief Oldest measured humidity (RH)
|
||||
inline float humidity() const
|
||||
{
|
||||
return !empty() ? oldest().humidity() : std::numeric_limits<float>::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<UnitSCD40, scd4x::Data>::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<UnitSCD40, scd4x::Data>::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<m5::container::CircularBuffer<scd4x::Data>> _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
|
||||
178
libraries/M5Unit-ENV/src/unit/unit_SCD41.cpp
Normal file
178
libraries/M5Unit-ENV/src/unit/unit_SCD41.cpp
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_SCD41.cpp
|
||||
@brief SCD41 Unit for M5UnitUnified
|
||||
*/
|
||||
#include "unit_SCD41.hpp"
|
||||
#include <M5Utility.hpp>
|
||||
#include <array>
|
||||
using namespace m5::utility::mmh3;
|
||||
using namespace m5::unit::types;
|
||||
using namespace m5::unit::scd4x;
|
||||
using namespace m5::unit::scd4x::command;
|
||||
using namespace m5::unit::scd41;
|
||||
using namespace m5::unit::scd41::command;
|
||||
|
||||
namespace {
|
||||
// Max command duration(ms)
|
||||
constexpr uint16_t MEASURE_SINGLE_SHOT_DURATION{5000};
|
||||
constexpr uint16_t MEASURE_SINGLE_SHOT_RHT_ONLY_DURATION{50};
|
||||
|
||||
const uint8_t VARIANT_VALUE[2]{0x14, 0x40}; // SCD41
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
// class UnitSCD41
|
||||
const char UnitSCD41::name[] = "UnitSCD41";
|
||||
const types::uid_t UnitSCD41::uid{"UnitSCD41"_mmh3};
|
||||
const types::attr_t UnitSCD41::attr{attribute::AccessI2C};
|
||||
|
||||
bool UnitSCD41::is_valid_chip()
|
||||
{
|
||||
uint8_t var[2]{};
|
||||
if (!read_register(GET_SENSOR_VARIANT, var, 2) || memcmp(var, VARIANT_VALUE, 2) != 0) {
|
||||
M5_LIB_LOGE("Not SCD41 %02X:%02X", var[0], var[1]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnitSCD41::measureSingleshot(Data& d)
|
||||
{
|
||||
d = Data{};
|
||||
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (writeRegister(MEASURE_SINGLE_SHOT)) {
|
||||
m5::utility::delay(MEASURE_SINGLE_SHOT_DURATION);
|
||||
return read_measurement(d);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD41::measureSingleshotRHT(Data& d)
|
||||
{
|
||||
d = Data{};
|
||||
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (writeRegister(MEASURE_SINGLE_SHOT_RHT_ONLY)) {
|
||||
m5::utility::delay(MEASURE_SINGLE_SHOT_RHT_ONLY_DURATION);
|
||||
return read_measurement(d, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD41::powerDown(const uint32_t duration)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
if (writeRegister(POWER_DOWN, nullptr, 0)) {
|
||||
m5::utility::delay(duration);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD41::wakeup()
|
||||
{
|
||||
constexpr uint32_t WAKE_UP_DURATION{30 + 5};
|
||||
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
// Note that the SCD4x does not acknowledge the wake_up command
|
||||
writeRegister(WAKE_UP, nullptr, 0);
|
||||
m5::utility::delay(WAKE_UP_DURATION);
|
||||
|
||||
// The sensor’s idle state after wake up can be verified by reading out the serial numbe
|
||||
auto timeout_at = m5::utility::millis() + 1000;
|
||||
do {
|
||||
uint64_t sn{};
|
||||
if (readSerialNumber(sn)) {
|
||||
return true;
|
||||
}
|
||||
m5::utility::delay(10);
|
||||
} while (m5::utility::millis() <= timeout_at);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD41::writeAutomaticSelfCalibrationInitialPeriod(const uint16_t hours, const uint32_t duration)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
if (hours % 4) {
|
||||
M5_LIB_LOGW("Arguments are modified to multiples of 4");
|
||||
}
|
||||
uint16_t h = (hours >> 2) << 2;
|
||||
m5::types::big_uint16_t u16{h};
|
||||
return write_register(SET_AUTOMATIC_SELF_CALIBRATION_INITIAL_PERIOD, u16.data(), u16.size()) &&
|
||||
delay_true(duration);
|
||||
}
|
||||
|
||||
bool UnitSCD41::readAutomaticSelfCalibrationInitialPeriod(uint16_t& hours)
|
||||
{
|
||||
hours = 0;
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
m5::types::big_uint16_t u16{};
|
||||
if (read_register(GET_AUTOMATIC_SELF_CALIBRATION_INITIAL_PERIOD, u16.data(), u16.size(),
|
||||
GET_AUTOMATIC_SELF_CALIBRATION_INITIAL_PERIOD_DURATION)) {
|
||||
hours = u16.get();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSCD41::writeAutomaticSelfCalibrationStandardPeriod(const uint16_t hours, const uint32_t duration)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
if (hours % 4) {
|
||||
M5_LIB_LOGW("Arguments are modified to multiples of 4");
|
||||
}
|
||||
uint16_t h = (hours >> 2) << 2;
|
||||
m5::types::big_uint16_t u16{h};
|
||||
return write_register(SET_AUTOMATIC_SELF_CALIBRATION_STANDARD_PERIOD, u16.data(), u16.size()) &&
|
||||
delay_true(duration);
|
||||
}
|
||||
|
||||
bool UnitSCD41::readAutomaticSelfCalibrationStandardPeriod(uint16_t& hours)
|
||||
{
|
||||
hours = 0;
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
m5::types::big_uint16_t u16{};
|
||||
if (read_register(GET_AUTOMATIC_SELF_CALIBRATION_STANDARD_PERIOD, u16.data(), u16.size(),
|
||||
GET_AUTOMATIC_SELF_CALIBRATION_STANDARD_PERIOD_DURATION)) {
|
||||
hours = u16.get();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
157
libraries/M5Unit-ENV/src/unit/unit_SCD41.hpp
Normal file
157
libraries/M5Unit-ENV/src/unit/unit_SCD41.hpp
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_SCD41.hpp
|
||||
@brief SCD41 Unit for M5UnitUnified
|
||||
*/
|
||||
#ifndef M5_UNIT_ENV_UNIT_SCD41_HPP
|
||||
#define M5_UNIT_ENV_UNIT_SCD41_HPP
|
||||
|
||||
#include "unit_SCD40.hpp"
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
/*!
|
||||
@namespace scd41
|
||||
@brief For SCD41
|
||||
*/
|
||||
namespace scd41 {
|
||||
///@cond
|
||||
// Max command duration(ms)
|
||||
// For SCD40/41
|
||||
constexpr uint32_t POWER_DOWN_DURATION{1};
|
||||
constexpr uint32_t GET_AUTOMATIC_SELF_CALIBRATION_INITIAL_PERIOD_DURATION{1};
|
||||
constexpr uint32_t SET_AUTOMATIC_SELF_CALIBRATION_INITIAL_PERIOD_DURATION{1};
|
||||
constexpr uint32_t GET_AUTOMATIC_SELF_CALIBRATION_STANDARD_PERIOD_DURATION{1};
|
||||
constexpr uint32_t SET_AUTOMATIC_SELF_CALIBRATION_STANDARD_PERIOD_DURATION{1};
|
||||
|
||||
///@endcond
|
||||
} // namespace scd41
|
||||
|
||||
/*!
|
||||
@class m5::unit::UnitSCD41
|
||||
@brief SCD41 unit component
|
||||
*/
|
||||
class UnitSCD41 : public UnitSCD40 {
|
||||
M5_UNIT_COMPONENT_HPP_BUILDER(UnitSCD41, 0x62);
|
||||
|
||||
public:
|
||||
explicit UnitSCD41(const uint8_t addr = DEFAULT_ADDRESS) : UnitSCD40(addr)
|
||||
{
|
||||
auto ccfg = component_config();
|
||||
ccfg.clock = 400 * 1000U;
|
||||
component_config(ccfg);
|
||||
}
|
||||
virtual ~UnitSCD41()
|
||||
{
|
||||
}
|
||||
|
||||
///@name Single Shot Measurement Mode
|
||||
///@{
|
||||
/*!
|
||||
@brief Request a single measurement
|
||||
@param[out] d Measurement data
|
||||
@return True if successful
|
||||
@note Blocked until measurement results are acquired (5000 ms)
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool measureSingleshot(scd4x::Data& d);
|
||||
/*!
|
||||
@brief Request a single measurement temperature and humidity
|
||||
@param[out] d Measurement data
|
||||
@return True if successful
|
||||
@note Values are updated at 50 ms interval
|
||||
@note Blocked until measurement results are acquired (50 ms)
|
||||
@warning Information on CO2 is invalid.
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool measureSingleshotRHT(scd4x::Data& d);
|
||||
///@}
|
||||
|
||||
///@name Power mode
|
||||
///@{
|
||||
/*!
|
||||
@brief Power down
|
||||
@details The sensor into sleep mode
|
||||
@param duration Max command duration(ms)
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool powerDown(const uint32_t duration = scd41::POWER_DOWN_DURATION);
|
||||
/*!
|
||||
@brief Wake up
|
||||
@details The sensor from sleep mode into idle mode
|
||||
@param duration Max command duration(ms)
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool wakeup();
|
||||
///@}
|
||||
|
||||
///@name For ASC(Auto Self-Calibration)
|
||||
///@{
|
||||
/*!
|
||||
@brief Write the duration of the initial period for ASC correction
|
||||
@param hours ASC initial period
|
||||
@param duration Max command duration(ms)
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
@warning Allowed values are integer multiples of 4 hours
|
||||
*/
|
||||
bool writeAutomaticSelfCalibrationInitialPeriod(
|
||||
const uint16_t hours, const uint32_t duration = scd41::SET_AUTOMATIC_SELF_CALIBRATION_INITIAL_PERIOD_DURATION);
|
||||
/*!
|
||||
@brief Read the duration of the initial period for ASC correction
|
||||
@param[out] hours ASC initial period
|
||||
@param duration Max command duration(ms)
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool readAutomaticSelfCalibrationInitialPeriod(uint16_t& hours);
|
||||
/*!
|
||||
@brief Write the standard period for ASC correction
|
||||
@param hours ASC standard period
|
||||
@param duration Max command duration(ms)
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
@warning Allowed values are integer multiples of 4 hours
|
||||
*/
|
||||
bool writeAutomaticSelfCalibrationStandardPeriod(
|
||||
const uint16_t hours, const uint32_t duration = scd41::SET_AUTOMATIC_SELF_CALIBRATION_STANDARD_PERIOD_DURATION);
|
||||
/*!
|
||||
@brief Red the standard period for ASC correction
|
||||
@param[iut] hours ASC standard period
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
@warning Allowed values are integer multiples of 4 hours
|
||||
*/
|
||||
bool readAutomaticSelfCalibrationStandardPeriod(uint16_t& hours);
|
||||
///@}
|
||||
|
||||
protected:
|
||||
virtual bool is_valid_chip() override;
|
||||
};
|
||||
|
||||
namespace scd41 {
|
||||
///@cond
|
||||
namespace command {
|
||||
// Low power single shot - SCD41 only
|
||||
constexpr uint16_t MEASURE_SINGLE_SHOT{0x219d};
|
||||
constexpr uint16_t MEASURE_SINGLE_SHOT_RHT_ONLY{0x2196};
|
||||
constexpr uint16_t POWER_DOWN{0x36e0};
|
||||
constexpr uint16_t WAKE_UP{0x36f6};
|
||||
constexpr uint16_t SET_AUTOMATIC_SELF_CALIBRATION_INITIAL_PERIOD{0x2445};
|
||||
constexpr uint16_t GET_AUTOMATIC_SELF_CALIBRATION_INITIAL_PERIOD{0x2340};
|
||||
constexpr uint16_t SET_AUTOMATIC_SELF_CALIBRATION_STANDARD_PERIOD{0x244e};
|
||||
constexpr uint16_t GET_AUTOMATIC_SELF_CALIBRATION_STANDARD_PERIOD{0x234b};
|
||||
|
||||
} // namespace command
|
||||
///@endcond
|
||||
} // namespace scd41
|
||||
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
#endif
|
||||
358
libraries/M5Unit-ENV/src/unit/unit_SGP30.cpp
Normal file
358
libraries/M5Unit-ENV/src/unit/unit_SGP30.cpp
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_SGP30.cpp
|
||||
@brief SGP30 Unit for M5UnitUnified
|
||||
*/
|
||||
#include "unit_SGP30.hpp"
|
||||
#include <M5Utility.hpp>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
using namespace m5::utility::mmh3;
|
||||
using namespace m5::unit::types;
|
||||
using namespace m5::unit::sgp30;
|
||||
using namespace m5::unit::sgp30::command;
|
||||
|
||||
namespace {
|
||||
// Supported lower limit version
|
||||
constexpr uint8_t lower_limit_version{0x20};
|
||||
// constexpr elapsed_time_t BASELINE_INTERVAL{1000 * 60 * 60}; // 1 hour (ms)
|
||||
|
||||
inline bool delayMeasurementDuration(const uint16_t ms)
|
||||
{
|
||||
m5::utility::delay(ms);
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
|
||||
namespace sgp30 {
|
||||
uint16_t Data::co2eq() const
|
||||
{
|
||||
// M5_LIB_LOGE(">>> %x:%x => %u", raw[0], raw[1], m5::types::big_uint16_t(raw[0], raw[1]).get());
|
||||
return m5::types::big_uint16_t(raw[0], raw[1]).get();
|
||||
}
|
||||
|
||||
uint16_t Data::tvoc() const
|
||||
{
|
||||
return m5::types::big_uint16_t(raw[3], raw[4]).get();
|
||||
}
|
||||
|
||||
} // namespace sgp30
|
||||
|
||||
// class UnitSGP30
|
||||
const char UnitSGP30::name[] = "UnitSGP30";
|
||||
const types::uid_t UnitSGP30::uid{"UnitSGP30"_mmh3};
|
||||
const types::attr_t UnitSGP30::attr{attribute::AccessI2C};
|
||||
|
||||
bool UnitSGP30::begin()
|
||||
{
|
||||
auto ssize = stored_size();
|
||||
assert(ssize && "stored_size must be greater than zero");
|
||||
if (ssize != _data->capacity()) {
|
||||
_data.reset(new m5::container::CircularBuffer<Data>(ssize));
|
||||
if (!_data) {
|
||||
M5_LIB_LOGE("Failed to allocate");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m5::utility::delay(1);
|
||||
|
||||
Feature f{};
|
||||
if (!readFeatureSet(f)) {
|
||||
M5_LIB_LOGE("Failed to read feature");
|
||||
return false;
|
||||
}
|
||||
if (f.productType() != 0) {
|
||||
// May be SGPC3 gas sensor if value is 1
|
||||
M5_LIB_LOGE("This unit is NOT SGP30");
|
||||
return false;
|
||||
}
|
||||
_version = f.productVersion();
|
||||
if (_version < lower_limit_version) {
|
||||
M5_LIB_LOGE("Not enough the product version %x", _version);
|
||||
return false;
|
||||
}
|
||||
|
||||
return _cfg.start_periodic
|
||||
? startPeriodicMeasurement(_cfg.baseline_co2eq, _cfg.baseline_tvoc, _cfg.humidity, _cfg.interval)
|
||||
: true;
|
||||
}
|
||||
|
||||
void UnitSGP30::update(const bool force)
|
||||
{
|
||||
_updated = false;
|
||||
if (_periodic) {
|
||||
elapsed_time_t at{m5::utility::millis()};
|
||||
if (_waiting) {
|
||||
_waiting = (at < _can_measure_time);
|
||||
return;
|
||||
}
|
||||
if (force || !_latest || at >= _latest + _interval) {
|
||||
Data d{};
|
||||
_updated = read_measurement(d);
|
||||
if (_updated) {
|
||||
_latest = at;
|
||||
_data->push_back(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UnitSGP30::start_periodic_measurement(const uint16_t co2eq, const uint16_t tvoc, const uint16_t humidity,
|
||||
const uint32_t interval, const uint32_t duration)
|
||||
{
|
||||
// Baseline and absolute humidity restoration must take place during
|
||||
// this 15-second period
|
||||
return start_periodic_measurement(interval, duration) && write_iaq_baseline(co2eq, tvoc) &&
|
||||
writeAbsoluteHumidity(humidity);
|
||||
}
|
||||
|
||||
bool UnitSGP30::start_periodic_measurement(const uint32_t interval, const uint32_t duration)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (interval < sgp30::MEASURE_IAQ_DURATION) {
|
||||
M5_LIB_LOGE("Interval too short %u. Must ne greater equal %u", interval, sgp30::MEASURE_IAQ_DURATION);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (writeRegister(IAQ_INIT)) {
|
||||
// For the first 15s after the “sgp30_iaq_init” command the sensor
|
||||
// is an initialization phase during which a “sgp30_measure_iaq”
|
||||
// command returns fixed values of 400 ppm CO2eq and 0 ppb TVOC.A
|
||||
// new “sgp30_iaq_init” command has to be sent after every power-up
|
||||
// or soft reset.
|
||||
|
||||
// Baseline and absolute humidity restoration must take place during
|
||||
// this 15-second period
|
||||
_can_measure_time = m5::utility::millis() + 15 * 1000;
|
||||
_periodic = true;
|
||||
_latest = 0;
|
||||
_waiting = true;
|
||||
_interval = interval;
|
||||
|
||||
m5::utility::delay(duration);
|
||||
}
|
||||
return _periodic;
|
||||
}
|
||||
|
||||
bool UnitSGP30::stop_periodic_measurement()
|
||||
{
|
||||
_periodic = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnitSGP30::readRaw(uint16_t& h2, uint16_t& ethanol)
|
||||
{
|
||||
std::array<uint8_t, 6> rbuf{};
|
||||
h2 = ethanol = 0;
|
||||
if (readRegister(MEASURE_RAW, rbuf.data(), rbuf.size(), MEASURE_RAW_DURATION)) {
|
||||
m5::utility::CRC8_Checksum crc{};
|
||||
if (crc.range(rbuf.data(), 2) == rbuf[2] && crc.range(rbuf.data() + 3, 2) == rbuf[5]) {
|
||||
h2 = m5::types::big_uint16_t(rbuf[0], rbuf[1]).get();
|
||||
ethanol = m5::types::big_uint16_t(rbuf[3], rbuf[4]).get();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSGP30::readRaw(float& h2, float& ethanol)
|
||||
{
|
||||
uint16_t hh{}, et{};
|
||||
h2 = ethanol = std::numeric_limits<float>::quiet_NaN();
|
||||
if (!readRaw(hh, et)) {
|
||||
return false;
|
||||
}
|
||||
h2 = 0.5f * std::exp((13119 - hh) / 512.f);
|
||||
ethanol = 0.4f * std::exp((18472 - et) / 512.f);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnitSGP30::readIaqBaseline(uint16_t& co2eq, uint16_t& tvoc)
|
||||
{
|
||||
std::array<uint8_t, 6> rbuf{};
|
||||
co2eq = tvoc = 0;
|
||||
if (readRegister(GET_IAQ_BASELINE, rbuf.data(), rbuf.size(), GET_IAQ_BASELINE_DURATION)) {
|
||||
m5::utility::CRC8_Checksum crc{};
|
||||
if (crc.range(rbuf.data(), 2) == rbuf[2] && crc.range(rbuf.data() + 3, 2) == rbuf[5]) {
|
||||
co2eq = m5::types::big_uint16_t(rbuf[0], rbuf[1]).get();
|
||||
tvoc = m5::types::big_uint16_t(rbuf[3], rbuf[4]).get();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSGP30::writeAbsoluteHumidity(const uint16_t raw, const uint32_t duration)
|
||||
{
|
||||
m5::utility::CRC8_Checksum crc;
|
||||
std::array<uint8_t, 3> buf{};
|
||||
m5::types::big_uint16_t rr(raw);
|
||||
std::memcpy(buf.data(), rr.data(), 2);
|
||||
buf[2] = crc.range(rr.data(), 2);
|
||||
return writeRegister(SET_ABSOLUTE_HUMIDITY, buf.data(), buf.size()) && delayMeasurementDuration(duration);
|
||||
}
|
||||
|
||||
bool UnitSGP30::writeAbsoluteHumidity(const float gm3, const uint32_t duration)
|
||||
{
|
||||
int32_t tmp = static_cast<int32_t>(std::round(gm3 * 256.f));
|
||||
if (tmp > 32767 || tmp < -32768) {
|
||||
M5_LIB_LOGE("Over/underflow: %f / %d", gm3, tmp);
|
||||
return false;
|
||||
}
|
||||
return writeAbsoluteHumidity(static_cast<uint16_t>(static_cast<int16_t>(tmp)), duration);
|
||||
}
|
||||
|
||||
bool UnitSGP30::measureTest(uint16_t& result)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<uint8_t, 3> rbuf{};
|
||||
if (readRegister(MEASURE_TEST, rbuf.data(), rbuf.size(), MEASURE_TEST_DURATION)) {
|
||||
m5::utility::CRC8_Checksum crc;
|
||||
if (crc.range(rbuf.data(), 2) == rbuf[2]) {
|
||||
result = m5::types::big_uint16_t(rbuf[0], rbuf[1]).get();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
bool UnitSGP30::readTvocInceptiveBaseline(uint16_t& inceptive_tvoc) {
|
||||
if (_version < 0x21) {
|
||||
M5_LIB_LOGE("Not enough the product version %x", _version);
|
||||
return false;
|
||||
}
|
||||
std::array<uint8_t, 3> rbuf{};
|
||||
if (readRegister(GET_TVOC_INCEPTIVE_BASELINE, rbuf.data(), rbuf.size(), GET_TVOC_INCEPTIVE_BASELINE_DURATION)) {
|
||||
m5::utility::CRC8_Checksum crc;
|
||||
if (crc.range(rbuf.data(), 2) == rbuf[2]) {
|
||||
inceptive_tvoc = m5::types::big_uint16_t(rbuf[0], rbuf[1]).get();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSGP30::writeTvocInceptiveBaseline(const uint16_t inceptive_tvoc, const uint32_t duration) {
|
||||
if (_version < 0x21) {
|
||||
M5_LIB_LOGE("Not enough the product version %x", _version);
|
||||
return false;
|
||||
}
|
||||
|
||||
M5_LIB_LOGW(">>>>>> %u", inceptive_tvoc);
|
||||
|
||||
m5::utility::CRC8_Checksum crc;
|
||||
std::array<uint8_t, 3> buf{};
|
||||
m5::types::big_uint16_t tt(inceptive_tvoc);
|
||||
std::memcpy(buf.data(), tt.data(), 2);
|
||||
buf[2] = crc.range(tt.data(), 2);
|
||||
return writeRegister(SET_TVOC_INCEPTIVE_BASELINE, buf.data(), buf.size()) && delayMeasurementDuration(duration);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool UnitSGP30::generalReset()
|
||||
{
|
||||
uint8_t cmd{0x06};
|
||||
if (generalCall(&cmd, 1)) {
|
||||
_periodic = false;
|
||||
m5::utility::delay(10);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSGP30::readFeatureSet(sgp30::Feature& feature)
|
||||
{
|
||||
std::array<uint8_t, 3> rbuf{};
|
||||
if (readRegister(GET_FEATURE_SET, rbuf.data(), rbuf.size(), GET_FEATURE_SET_DURATION)) {
|
||||
m5::utility::CRC8_Checksum crc;
|
||||
if (crc.range(rbuf.data(), 2) == rbuf[2]) {
|
||||
feature.value = m5::types::big_uint16_t(rbuf[0], rbuf[1]).get();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSGP30::readSerialNumber(uint64_t& number)
|
||||
{
|
||||
std::array<uint8_t, 9> rbuf{};
|
||||
number = 0;
|
||||
if (readRegister(GET_SERIAL_ID, rbuf.data(), rbuf.size(), GET_SERIAL_ID_DURATION)) {
|
||||
m5::utility::CRC8_Checksum crc;
|
||||
for (uint_fast8_t i = 0; i < 3; i++) {
|
||||
if (crc.range(rbuf.data() + i * 3, 2) != rbuf[i * 3 + 2]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (uint_fast8_t i = 0; i < 3; ++i) {
|
||||
number |= ((uint64_t)(m5::types::big_uint16_t(rbuf[i * 3], rbuf[i * 3 + 1]).get())) << (16U * (2 - i));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSGP30::readSerialNumber(char* number)
|
||||
{
|
||||
if (!number) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*number = '\0';
|
||||
uint64_t sno{};
|
||||
if (readSerialNumber(sno)) {
|
||||
uint_fast8_t i{12};
|
||||
while (i--) {
|
||||
*number++ = m5::utility::uintToHexChar((sno >> (i * 4)) & 0x0F);
|
||||
}
|
||||
*number = '\0';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
bool UnitSGP30::write_iaq_baseline(const uint16_t co2eq, const uint16_t tvoc)
|
||||
{
|
||||
m5::utility::CRC8_Checksum crc{};
|
||||
m5::types::big_uint16_t cc(co2eq);
|
||||
m5::types::big_uint16_t tt(tvoc);
|
||||
|
||||
std::array<uint8_t, (2 + 1) * 2> buf{};
|
||||
// Note that the order is different for get and set
|
||||
std::memcpy(buf.data() + 0, tt.data(), 2);
|
||||
buf[2] = crc.range(tt.data(), 2);
|
||||
std::memcpy(buf.data() + 3, cc.data(), 2);
|
||||
buf[5] = crc.range(cc.data(), 2);
|
||||
|
||||
return writeRegister(SET_IAQ_BASELINE, buf.data(), buf.size());
|
||||
}
|
||||
|
||||
bool UnitSGP30::read_measurement(Data& d)
|
||||
{
|
||||
if (readRegister(MEASURE_IAQ, d.raw.data(), d.raw.size(), MEASURE_IAQ_DURATION)) {
|
||||
m5::utility::CRC8_Checksum crc{};
|
||||
return crc.range(d.raw.data(), 2) == d.raw[2] && crc.range(d.raw.data() + 3, 2) == d.raw[5];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
360
libraries/M5Unit-ENV/src/unit/unit_SGP30.hpp
Normal file
360
libraries/M5Unit-ENV/src/unit/unit_SGP30.hpp
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_SGP30.hpp
|
||||
@brief SGP30 Unit for M5UnitUnified
|
||||
*/
|
||||
#ifndef M5_UNIT_TVOC_UNIT_SGP30_HPP
|
||||
#define M5_UNIT_TVOC_UNIT_SGP30_HPP
|
||||
|
||||
#include <M5UnitComponent.hpp>
|
||||
#include <m5_utility/container/circular_buffer.hpp>
|
||||
#include <array>
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
|
||||
/*!
|
||||
@namespace sgp30
|
||||
@brief For SGP30
|
||||
*/
|
||||
namespace sgp30 {
|
||||
|
||||
///@cond
|
||||
// Max command duration(ms)
|
||||
// Max measurement duration (ms)
|
||||
constexpr uint16_t IAQ_INIT_DURATION{10};
|
||||
constexpr uint16_t MEASURE_IAQ_DURATION{12};
|
||||
constexpr uint16_t GET_IAQ_BASELINE_DURATION{10};
|
||||
constexpr uint16_t SET_IAQ_BASELINE_DURATION{10};
|
||||
constexpr uint16_t SET_ABSOLUTE_HUMIDITY_DURATION{10};
|
||||
constexpr uint16_t MEASURE_TEST_DURATION{220};
|
||||
constexpr uint16_t GET_FEATURE_SET_DURATION{10};
|
||||
constexpr uint16_t MEASURE_RAW_DURATION{25};
|
||||
constexpr uint16_t GET_TVOC_INCEPTIVE_BASELINE_DURATION{10};
|
||||
constexpr uint16_t SET_TVOC_INCEPTIVE_BASELINE_DURATION{10};
|
||||
constexpr uint16_t GET_SERIAL_ID_DURATION{10};
|
||||
///@endcond
|
||||
|
||||
/*!
|
||||
@struct Feature
|
||||
@brief Structure of the SGP feature set number
|
||||
*/
|
||||
struct Feature {
|
||||
//! @brief product type (SGP30: 0)
|
||||
uint8_t productType() const
|
||||
{
|
||||
return (value >> 12) & 0x0F;
|
||||
}
|
||||
/*!
|
||||
@brief product version
|
||||
@note Please note that the last 5 bits of the productversion (bits 12-16
|
||||
of the LSB) are subject to change
|
||||
@note This is used to track new features added tothe SGP multi-pixel
|
||||
platform
|
||||
*/
|
||||
uint8_t productVersion() const
|
||||
{
|
||||
return value & 0xFF;
|
||||
}
|
||||
uint16_t value{};
|
||||
};
|
||||
|
||||
/*!
|
||||
@struct Data
|
||||
@brief Measurement data group
|
||||
*/
|
||||
struct Data {
|
||||
std::array<uint8_t, 6> raw{}; //!< RAW data
|
||||
uint16_t co2eq() const; //!< Co2Eq (ppm)
|
||||
uint16_t tvoc() const; //!< TVOC (pbb)
|
||||
};
|
||||
|
||||
} // namespace sgp30
|
||||
|
||||
/*!
|
||||
@class UnitSGP30
|
||||
@brief SGP30 unit
|
||||
*/
|
||||
class UnitSGP30 : public Component, public PeriodicMeasurementAdapter<UnitSGP30, sgp30::Data> {
|
||||
M5_UNIT_COMPONENT_HPP_BUILDER(UnitSGP30, 0x58);
|
||||
|
||||
public:
|
||||
/*!
|
||||
@struct config_t
|
||||
@brief Settings for begin
|
||||
*/
|
||||
struct config_t {
|
||||
//! Start periodic measurement on begin()?
|
||||
bool start_periodic{true};
|
||||
//! Baseline CO2eq initial value if start on begin
|
||||
uint16_t baseline_co2eq{};
|
||||
//! Baseline TVOC initial value if start on begin
|
||||
uint16_t baseline_tvoc{};
|
||||
//! Absolute humidity initiali value if start on begin
|
||||
uint16_t humidity{};
|
||||
/*!
|
||||
Inceptive Baseline for TVOC measurements initial value if start on begin
|
||||
@warning The application of this feature is solely limited to the very
|
||||
first start-up period of an SGP sensor
|
||||
*/
|
||||
uint16_t inceptive_tvoc{};
|
||||
//! Periodic measurement interval if start on begin
|
||||
int32_t interval{1000};
|
||||
};
|
||||
|
||||
explicit UnitSGP30(const uint8_t addr = DEFAULT_ADDRESS)
|
||||
: Component(addr), _data{new m5::container::CircularBuffer<sgp30::Data>(1)}
|
||||
{
|
||||
auto ccfg = component_config();
|
||||
ccfg.clock = 400 * 1000U;
|
||||
component_config(ccfg);
|
||||
}
|
||||
virtual ~UnitSGP30()
|
||||
{
|
||||
}
|
||||
|
||||
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 Properties
|
||||
///@{
|
||||
/*!
|
||||
@brief Gets the product version
|
||||
@warning Calling after the call of begin()
|
||||
*/
|
||||
inline uint8_t productVersion() const
|
||||
{
|
||||
return _version;
|
||||
}
|
||||
/*!
|
||||
@brief Can it be measured?
|
||||
@return True if it can
|
||||
@warning After the start of a periodic measurement, it is necessary to wait 15 seconds to obtain a valid
|
||||
measurement value
|
||||
*/
|
||||
inline bool canMeasurePeriodic() const
|
||||
{
|
||||
return inPeriodic() && !_waiting;
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Measurement data by periodic
|
||||
///@{
|
||||
//! @brief Oldest CO2eq (ppm)
|
||||
inline uint16_t co2eq() const
|
||||
{
|
||||
return !empty() ? oldest().co2eq() : 0xFFFF;
|
||||
}
|
||||
//! @brief Oldest TVOC (ppb)
|
||||
inline uint16_t tvoc() const
|
||||
{
|
||||
return !empty() ? oldest().tvoc() : 0xFFFF;
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Periodic measurement
|
||||
///@{
|
||||
/*!
|
||||
@brief Start periodic measurement
|
||||
@details Specify settings and measure
|
||||
@param co2eq iaq baseline for CO2eq
|
||||
@param tvoc iaq baseline for TVOC
|
||||
@param humidity absolute humidity (disable if zero)
|
||||
@param interval Measurement Interval(ms)
|
||||
@param duration Max command duration(ms)
|
||||
@return True if successful
|
||||
@note 15 seconds wait is required before a valid measurement can be initiated (warmup)
|
||||
@note In update(), waiting are taken into account
|
||||
*/
|
||||
inline bool startPeriodicMeasurement(const uint16_t co2eq, const uint16_t tvoc, const uint16_t humidity,
|
||||
const uint32_t interval = 1000U,
|
||||
const uint32_t duration = sgp30::IAQ_INIT_DURATION)
|
||||
{
|
||||
return PeriodicMeasurementAdapter<UnitSGP30, sgp30::Data>::startPeriodicMeasurement(co2eq, tvoc, humidity,
|
||||
interval, duration);
|
||||
}
|
||||
/*!
|
||||
@brief Start periodic measurement
|
||||
@details Measuring in the current settings
|
||||
@param interval Measurement Interval(ms)
|
||||
@param duration Max command duration(ms)
|
||||
@return True if successful
|
||||
@note 15 seconds wait is required before a valid measurement can be
|
||||
initiated.
|
||||
@note In update(), waiting are taken into account
|
||||
*/
|
||||
inline bool startPeriodicMeasurement(const uint32_t interval = 1000U,
|
||||
const uint32_t duration = sgp30::IAQ_INIT_DURATION)
|
||||
{
|
||||
return PeriodicMeasurementAdapter<UnitSGP30, sgp30::Data>::startPeriodicMeasurement(interval, duration);
|
||||
}
|
||||
/*!
|
||||
@brief Stop periodic measurement
|
||||
@return True if successful
|
||||
*/
|
||||
inline bool stopPeriodicMeasurement()
|
||||
{
|
||||
return PeriodicMeasurementAdapter<UnitSGP30, sgp30::Data>::stopPeriodicMeasurement();
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Correction
|
||||
///@{
|
||||
/*!
|
||||
@brief Read the IAQ baseline
|
||||
@param[out] co2eq iaq baseline for CO2
|
||||
@param[out] tvoc iaq baseline for TVOC
|
||||
@return True if successful
|
||||
*/
|
||||
bool readIaqBaseline(uint16_t& co2eq, uint16_t& tvoc);
|
||||
/*!
|
||||
@brief Write the absolute humidity
|
||||
@param raw absolute humidity (disable if zero)
|
||||
@param duration Max command duration(ms)
|
||||
@return True if successful
|
||||
@note Value is a fixed-point 8.8bit number
|
||||
*/
|
||||
bool writeAbsoluteHumidity(const uint16_t raw, const uint32_t duration = sgp30::SET_ABSOLUTE_HUMIDITY_DURATION);
|
||||
/*!
|
||||
@brief Write the absolute humidity
|
||||
@param gm3 absolute humidity (g/m^3)
|
||||
@param duration Max command duration(ms)
|
||||
@return True if successful
|
||||
*/
|
||||
bool writeAbsoluteHumidity(const float gm3, const uint32_t duration = sgp30::SET_ABSOLUTE_HUMIDITY_DURATION);
|
||||
|
||||
#if 0
|
||||
/*!
|
||||
@brief Read the inceptive Basebine for TVOC
|
||||
@param[out] inceptive_tvoc Inceptive baseline
|
||||
@return True if successful
|
||||
@warning Only available if product version is 0x21 or higher
|
||||
*/
|
||||
bool readTvocInceptiveBaseline(uint16_t& inceptive_tvoc);
|
||||
/*!
|
||||
@brief Write the inceptive Basebine for TVOC
|
||||
@param inceptive_tvoc Inceptive baseline
|
||||
@param duration Max command duration(ms)
|
||||
@return True if successful
|
||||
@warning The application of this feature is solely limited to the very
|
||||
first start-up period of an SGP sensor
|
||||
@warning Only available if product version is 0x21 or higher
|
||||
*/
|
||||
bool writeTvocInceptiveBaseline(const uint16_t inceptive_tvoc,
|
||||
const uint32_t duration = sgp30::SET_TVOC_INCEPTIVE_BASELINE_DURATION);
|
||||
#endif
|
||||
///@}
|
||||
|
||||
/*!
|
||||
@brief Read sensor raw signals
|
||||
@param[out] h2 H2 concentration(raw)
|
||||
@param[out] ethanol Ethanol concentration(raw)
|
||||
@return True if successful
|
||||
*/
|
||||
bool readRaw(uint16_t& h2, uint16_t& ethanol);
|
||||
|
||||
/*!
|
||||
@brief Read H2/Ethanol concentration
|
||||
@param[out] h2 H2 concentration(ppm)
|
||||
@param[out] ethanol Ethanol concentration(rppm
|
||||
@return True if successful
|
||||
@note Outputs the value calculated from the output of readRaw
|
||||
*/
|
||||
bool readRaw(float& h2, float& ethanol);
|
||||
|
||||
/*!
|
||||
@brief Run the on-chip self-test
|
||||
@param[out] result self-test return code
|
||||
@return True if successful
|
||||
@note If the test is OK, the result will be 0xd400
|
||||
@warning Must not be executed after startPeriodicMeasurement
|
||||
*/
|
||||
bool measureTest(uint16_t& result);
|
||||
/*!
|
||||
@brief General reset
|
||||
@details Reset using I2C general call
|
||||
@warning This is a reset by General command, the command is also
|
||||
sent to all devices with I2C connections
|
||||
@return True if successful
|
||||
*/
|
||||
bool generalReset();
|
||||
/*!
|
||||
@brief Read the feature set
|
||||
@param feature
|
||||
@return True if successful
|
||||
*/
|
||||
bool readFeatureSet(sgp30::Feature& feature);
|
||||
/*!
|
||||
@brief Read the serial number
|
||||
@return True if successful
|
||||
@note Serial number is 48bits
|
||||
*/
|
||||
bool readSerialNumber(uint64_t& number);
|
||||
/*!
|
||||
@brief Read the serial number string
|
||||
@param[out] number Output buffer
|
||||
@return True if successful
|
||||
@warning number must be at least 13 bytes
|
||||
*/
|
||||
bool readSerialNumber(char* number);
|
||||
|
||||
protected:
|
||||
bool start_periodic_measurement(const uint16_t co2eq, const uint16_t tvoc, const uint16_t humidity,
|
||||
const uint32_t interval, const uint32_t duration = sgp30::IAQ_INIT_DURATION);
|
||||
bool start_periodic_measurement(const uint32_t interval, const uint32_t duration = sgp30::IAQ_INIT_DURATION);
|
||||
bool stop_periodic_measurement();
|
||||
|
||||
bool write_iaq_baseline(const uint16_t co2eq, const uint16_t tvoc);
|
||||
bool read_measurement(sgp30::Data& d);
|
||||
|
||||
M5_UNIT_COMPONENT_PERIODIC_MEASUREMENT_ADAPTER_HPP_BUILDER(UnitSGP30, sgp30::Data);
|
||||
|
||||
protected:
|
||||
uint8_t _version{}; // Chip version
|
||||
|
||||
bool _waiting{};
|
||||
types::elapsed_time_t _can_measure_time{};
|
||||
std::unique_ptr<m5::container::CircularBuffer<sgp30::Data>> _data{};
|
||||
|
||||
config_t _cfg{};
|
||||
};
|
||||
|
||||
namespace sgp30 {
|
||||
namespace command {
|
||||
///@cond
|
||||
constexpr uint16_t IAQ_INIT{0x2003};
|
||||
constexpr uint16_t MEASURE_IAQ{0x2008};
|
||||
constexpr uint16_t GET_IAQ_BASELINE{0x2015};
|
||||
constexpr uint16_t SET_IAQ_BASELINE{0x201E};
|
||||
constexpr uint16_t SET_ABSOLUTE_HUMIDITY{0x2061};
|
||||
constexpr uint16_t MEASURE_TEST{0x2032};
|
||||
constexpr uint16_t GET_FEATURE_SET{0x202F};
|
||||
constexpr uint16_t MEASURE_RAW{0x2050};
|
||||
constexpr uint16_t GET_TVOC_INCEPTIVE_BASELINE{0x20B3};
|
||||
constexpr uint16_t SET_TVOC_INCEPTIVE_BASELINE{0x2077};
|
||||
constexpr uint16_t GET_SERIAL_ID{0x3682};
|
||||
///@endcond
|
||||
} // namespace command
|
||||
} // namespace sgp30
|
||||
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
#endif
|
||||
350
libraries/M5Unit-ENV/src/unit/unit_SHT30.cpp
Normal file
350
libraries/M5Unit-ENV/src/unit/unit_SHT30.cpp
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_SHT30.cpp
|
||||
@brief SHT30 Unit for M5UnitUnified
|
||||
*/
|
||||
#include "unit_SHT30.hpp"
|
||||
#include <M5Utility.hpp>
|
||||
#include <limits> // NaN
|
||||
#include <array>
|
||||
|
||||
using namespace m5::utility::mmh3;
|
||||
using namespace m5::unit::types;
|
||||
using namespace m5::unit::sht30;
|
||||
using namespace m5::unit::sht30::command;
|
||||
|
||||
namespace {
|
||||
struct Temperature {
|
||||
constexpr static float toFloat(const uint16_t u16)
|
||||
{
|
||||
return -45 + u16 * 175 / 65535.f; // -45 + 175 * S / (2^16 - 1)
|
||||
}
|
||||
};
|
||||
|
||||
// After sending a command to the sensor a minimalwaiting time of 1ms is needed
|
||||
// before another commandcan be received by the sensor.
|
||||
bool delay1()
|
||||
{
|
||||
m5::utility::delay(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr uint16_t periodic_cmd[] = {
|
||||
// 0.5 mps
|
||||
START_PERIODIC_MPS_HALF_HIGH,
|
||||
START_PERIODIC_MPS_HALF_MEDIUM,
|
||||
START_PERIODIC_MPS_HALF_LOW,
|
||||
// 1 mps
|
||||
START_PERIODIC_MPS_1_HIGH,
|
||||
START_PERIODIC_MPS_1_MEDIUM,
|
||||
START_PERIODIC_MPS_1_LOW,
|
||||
// 2 mps
|
||||
START_PERIODIC_MPS_2_HIGH,
|
||||
START_PERIODIC_MPS_2_MEDIUM,
|
||||
START_PERIODIC_MPS_2_LOW,
|
||||
// 4 mps
|
||||
START_PERIODIC_MPS_4_HIGH,
|
||||
START_PERIODIC_MPS_4_MEDIUM,
|
||||
START_PERIODIC_MPS_4_LOW,
|
||||
// 10 mps
|
||||
START_PERIODIC_MPS_10_HIGH,
|
||||
START_PERIODIC_MPS_10_MEDIUM,
|
||||
START_PERIODIC_MPS_10_LOW,
|
||||
};
|
||||
constexpr elapsed_time_t interval_table[] = {
|
||||
2000, // 0.5
|
||||
1000, // 1
|
||||
500, // 2
|
||||
250, // 4
|
||||
100, // 10
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
namespace sht30 {
|
||||
|
||||
float Data::celsius() const
|
||||
{
|
||||
return Temperature::toFloat(m5::types::big_uint16_t(raw[0], raw[1]).get());
|
||||
}
|
||||
|
||||
float Data::fahrenheit() const
|
||||
{
|
||||
return celsius() * 9.0f / 5.0f + 32.f;
|
||||
}
|
||||
|
||||
float Data::humidity() const
|
||||
{
|
||||
return 100.f * m5::types::big_uint16_t(raw[3], raw[4]).get() / 65536.f;
|
||||
}
|
||||
} // namespace sht30
|
||||
|
||||
const char UnitSHT30::name[] = "UnitSHT30";
|
||||
const types::uid_t UnitSHT30::uid{"UnitSHT30"_mmh3};
|
||||
const types::attr_t UnitSHT30::attr{attribute::AccessI2C};
|
||||
|
||||
bool UnitSHT30::begin()
|
||||
{
|
||||
auto ssize = stored_size();
|
||||
assert(ssize && "stored_size must be greater than zero");
|
||||
if (ssize != _data->capacity()) {
|
||||
_data.reset(new m5::container::CircularBuffer<Data>(ssize));
|
||||
if (!_data) {
|
||||
M5_LIB_LOGE("Failed to allocate");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!stopPeriodicMeasurement()) {
|
||||
M5_LIB_LOGE("Failed to stop");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!softReset()) {
|
||||
M5_LIB_LOGE("Failed to reset");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t sn{};
|
||||
if (!readSerialNumber(sn)) {
|
||||
M5_LIB_LOGE("Failed to readSerialNumber %x", sn);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto r = _cfg.start_heater ? startHeater() : stopHeater();
|
||||
if (!r) {
|
||||
M5_LIB_LOGE("Failed to heater %d", _cfg.start_heater);
|
||||
return false;
|
||||
}
|
||||
return _cfg.start_periodic ? startPeriodicMeasurement(_cfg.mps, _cfg.repeatability) : true;
|
||||
}
|
||||
|
||||
void UnitSHT30::update(const bool force)
|
||||
{
|
||||
_updated = false;
|
||||
if (inPeriodic()) {
|
||||
elapsed_time_t at{m5::utility::millis()};
|
||||
if (force || !_latest || at >= _latest + _interval) {
|
||||
if (writeRegister(READ_MEASUREMENT)) {
|
||||
Data d{};
|
||||
_updated = read_measurement(d);
|
||||
if (_updated) {
|
||||
_latest = at;
|
||||
_data->push_back(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UnitSHT30::measureSingleshot(Data& d, const sht30::Repeatability rep, const bool stretch)
|
||||
{
|
||||
constexpr uint16_t cmd[] = {
|
||||
// Enable clock stretching
|
||||
SINGLE_SHOT_ENABLE_STRETCH_HIGH,
|
||||
SINGLE_SHOT_ENABLE_STRETCH_MEDIUM,
|
||||
SINGLE_SHOT_ENABLE_STRETCH_LOW,
|
||||
// Disable clock stretching
|
||||
SINGLE_SHOT_DISABLE_STRETCH_HIGH,
|
||||
SINGLE_SHOT_DISABLE_STRETCH_MEDIUM,
|
||||
SINGLE_SHOT_DISABLE_STRETCH_LOW,
|
||||
};
|
||||
// Latency when clock stretching is disabled
|
||||
constexpr elapsed_time_t ms[] = {
|
||||
15,
|
||||
6,
|
||||
4,
|
||||
};
|
||||
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t idx = m5::stl::to_underlying(rep) + (stretch ? 0 : 3);
|
||||
if (idx >= m5::stl::size(cmd)) {
|
||||
M5_LIB_LOGE("Invalid arg : %u", (int)rep);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (writeRegister(cmd[idx])) {
|
||||
m5::utility::delay(stretch ? 1 : ms[m5::stl::to_underlying(rep)]);
|
||||
return read_measurement(d);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSHT30::start_periodic_measurement(const sht30::MPS mps, const sht30::Repeatability rep)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
_periodic = writeRegister(periodic_cmd[m5::stl::to_underlying(mps) * 3 + m5::stl::to_underlying(rep)]);
|
||||
if (_periodic) {
|
||||
_interval = interval_table[m5::stl::to_underlying(mps)];
|
||||
m5::utility::delay(16);
|
||||
return true;
|
||||
}
|
||||
return _periodic;
|
||||
}
|
||||
|
||||
bool UnitSHT30::stop_periodic_measurement()
|
||||
{
|
||||
if (writeRegister(STOP_PERIODIC_MEASUREMENT)) {
|
||||
_periodic = false;
|
||||
_latest = 0;
|
||||
// Upon reception of the break command the sensor will abort the
|
||||
// ongoing measurement and enter the single shot mode. This takes
|
||||
// 1ms
|
||||
return delay1();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSHT30::writeModeAccelerateResponseTime()
|
||||
{
|
||||
if (!inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are NOT running");
|
||||
return false;
|
||||
}
|
||||
if (writeRegister(ACCELERATED_RESPONSE_TIME)) {
|
||||
_interval = 1000 / 4; // 4mps
|
||||
m5::utility::delay(16);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSHT30::readStatus(sht30::Status& s)
|
||||
{
|
||||
std::array<uint8_t, 3> rbuf{};
|
||||
if (readRegister(READ_STATUS, rbuf.data(), rbuf.size(), 0) &&
|
||||
m5::utility::CRC8_Checksum().range(rbuf.data(), 2) == rbuf[2]) {
|
||||
s.value = m5::types::big_uint16_t(rbuf[0], rbuf[1]).get();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSHT30::clearStatus()
|
||||
{
|
||||
return writeRegister(CLEAR_STATUS) && delay1();
|
||||
}
|
||||
|
||||
bool UnitSHT30::softReset()
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGE("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (writeRegister(SOFT_RESET)) {
|
||||
// Max 1.5 ms
|
||||
// Time between ACK of soft reset command and sensor entering idle
|
||||
// state
|
||||
m5::utility::delay(2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSHT30::generalReset()
|
||||
{
|
||||
uint8_t cmd{0x06}; // reset command
|
||||
|
||||
if (!clearStatus()) {
|
||||
return false;
|
||||
}
|
||||
// Reset does not return ACK, which is an error, but should be ignored
|
||||
generalCall(&cmd, 1);
|
||||
|
||||
m5::utility::delay(1);
|
||||
|
||||
auto timeout_at = m5::utility::millis() + 10;
|
||||
bool done{};
|
||||
do {
|
||||
Status s{};
|
||||
// The ALERT pin will also become active (high) after powerup and
|
||||
// after resets
|
||||
if (readStatus(s) && (s.reset() || s.alertPending())) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
m5::utility::delay(1);
|
||||
} while (!done && m5::utility::millis() <= timeout_at);
|
||||
return done;
|
||||
}
|
||||
|
||||
bool UnitSHT30::startHeater()
|
||||
{
|
||||
return writeRegister(START_HEATER) && delay1();
|
||||
}
|
||||
|
||||
bool UnitSHT30::stopHeater()
|
||||
{
|
||||
return writeRegister(STOP_HEATER) && delay1();
|
||||
}
|
||||
|
||||
bool UnitSHT30::readSerialNumber(uint32_t& serialNumber)
|
||||
{
|
||||
serialNumber = 0;
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGE("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<uint8_t, 6> rbuf;
|
||||
if (readRegister(GET_SERIAL_NUMBER_ENABLE_STRETCH, rbuf.data(), rbuf.size(), 0)) {
|
||||
m5::types::big_uint16_t u16[2]{{rbuf[0], rbuf[1]}, {rbuf[3], rbuf[4]}};
|
||||
m5::utility::CRC8_Checksum crc{};
|
||||
if (crc.range(u16[0].data(), u16[0].size()) == rbuf[2] && crc.range(u16[1].data(), u16[1].size()) == rbuf[5]) {
|
||||
serialNumber = ((uint32_t)u16[0].get()) << 16 | ((uint32_t)u16[1].get());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSHT30::readSerialNumber(char* serialNumber)
|
||||
{
|
||||
if (!serialNumber) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*serialNumber = '\0';
|
||||
uint32_t sno{};
|
||||
if (readSerialNumber(sno)) {
|
||||
uint_fast8_t i{8};
|
||||
while (i--) {
|
||||
*serialNumber++ = m5::utility::uintToHexChar((sno >> (i * 4)) & 0x0F);
|
||||
}
|
||||
*serialNumber = '\0';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSHT30::read_measurement(Data& d)
|
||||
{
|
||||
if (readWithTransaction(d.raw.data(), d.raw.size()) == m5::hal::error::error_t::OK) {
|
||||
m5::utility::CRC8_Checksum crc{};
|
||||
for (uint_fast8_t i = 0; i < 2; ++i) {
|
||||
if (crc.range(d.raw.data() + i * 3, 2U) != d.raw[i * 3 + 2]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
360
libraries/M5Unit-ENV/src/unit/unit_SHT30.hpp
Normal file
360
libraries/M5Unit-ENV/src/unit/unit_SHT30.hpp
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_SHT30.hpp
|
||||
@brief SHT30 Unit for M5UnitUnified
|
||||
*/
|
||||
#ifndef M5_UNIT_ENV_UNIT_SHT30_HPP
|
||||
#define M5_UNIT_ENV_UNIT_SHT30_HPP
|
||||
|
||||
#include <M5UnitComponent.hpp>
|
||||
#include <m5_utility/container/circular_buffer.hpp>
|
||||
#include <limits> // NaN
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
|
||||
namespace sht30 {
|
||||
/*!
|
||||
@enum Repeatability
|
||||
@brief Repeatability accuracy level
|
||||
*/
|
||||
enum class Repeatability : uint8_t {
|
||||
High, //!< @brief High repeatability
|
||||
Medium, //!< @brief Medium repeatability
|
||||
Low //!< @brief Low repeatability
|
||||
};
|
||||
|
||||
/*!
|
||||
@enum MPS
|
||||
@brief Measuring frequency
|
||||
*/
|
||||
enum class MPS : uint8_t {
|
||||
Half, //!< @brief 0.5 measurement per second
|
||||
One, //!< @brief 1 measurement per second
|
||||
Two, //!< @brief 2 measurement per second
|
||||
Four, //!< @brief 4 measurement per second
|
||||
Ten, //!< @brief 10 measurement per second
|
||||
};
|
||||
|
||||
/*!
|
||||
@struct Status
|
||||
@brief Accessor for Status
|
||||
@note The order of the bit fields cannot be controlled, so bitwise
|
||||
operations are used to obtain each value.
|
||||
@note Items marked with (*) are subjects to clear status
|
||||
*/
|
||||
struct Status {
|
||||
//! @brief Alert pending status (*)
|
||||
inline bool alertPending() const
|
||||
{
|
||||
return value & (1U << 15);
|
||||
}
|
||||
//! @brief Heater status
|
||||
inline bool heater() const
|
||||
{
|
||||
return value & (1U << 13);
|
||||
}
|
||||
//! @brief RH tracking alert (*)
|
||||
inline bool trackingAlertRH() const
|
||||
{
|
||||
return value & (1U << 11);
|
||||
}
|
||||
//! @brief Tracking alert (*)
|
||||
inline bool trackingAlert() const
|
||||
{
|
||||
return value & (1U << 10);
|
||||
}
|
||||
//! @brief System reset detected (*)
|
||||
inline bool reset() const
|
||||
{
|
||||
return value & (1U << 4);
|
||||
}
|
||||
//! @brief Command staus
|
||||
inline bool command() const
|
||||
{
|
||||
return value & (1U << 1);
|
||||
}
|
||||
//! @brief Write data checksum status
|
||||
inline bool checksum() const
|
||||
{
|
||||
return value & (1U << 0);
|
||||
}
|
||||
uint16_t value{};
|
||||
};
|
||||
|
||||
/*!
|
||||
@struct Data
|
||||
@brief Measurement data group
|
||||
*/
|
||||
struct Data {
|
||||
std::array<uint8_t, 6> raw{}; //!< RAW data
|
||||
//! temperature (Celsius)
|
||||
inline float temperature() const
|
||||
{
|
||||
return celsius();
|
||||
}
|
||||
float celsius() const; //!< temperature (Celsius)
|
||||
float fahrenheit() const; //!< temperature (Fahrenheit)
|
||||
float humidity() const; //!< humidity (RH)
|
||||
};
|
||||
|
||||
} // namespace sht30
|
||||
|
||||
/*!
|
||||
@class UnitSHT30
|
||||
@brief Temperature and humidity, sensor unit
|
||||
*/
|
||||
class UnitSHT30 : public Component, public PeriodicMeasurementAdapter<UnitSHT30, sht30::Data> {
|
||||
M5_UNIT_COMPONENT_HPP_BUILDER(UnitSHT30, 0x44);
|
||||
|
||||
public:
|
||||
/*!
|
||||
@struct config_t
|
||||
@brief Settings for begin
|
||||
*/
|
||||
struct config_t {
|
||||
//! Start periodic measurement on begin?
|
||||
bool start_periodic{true};
|
||||
//! Measuring frequency if start on begin
|
||||
sht30::MPS mps{sht30::MPS::One};
|
||||
//! Repeatability accuracy level if start on begin
|
||||
sht30::Repeatability repeatability{sht30::Repeatability::High};
|
||||
//! start heater on begin?
|
||||
bool start_heater{false};
|
||||
};
|
||||
|
||||
explicit UnitSHT30(const uint8_t addr = DEFAULT_ADDRESS)
|
||||
: Component(addr), _data{new m5::container::CircularBuffer<sht30::Data>(1)}
|
||||
{
|
||||
auto ccfg = component_config();
|
||||
ccfg.clock = 400 * 1000U;
|
||||
component_config(ccfg);
|
||||
}
|
||||
virtual ~UnitSHT30()
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool begin() override;
|
||||
virtual void update(const bool force = false) override;
|
||||
|
||||
///@name Settings for begin
|
||||
///@{
|
||||
/*! @brief Gets the configration */
|
||||
inline config_t config()
|
||||
{
|
||||
return _cfg;
|
||||
}
|
||||
//! @brief Set the configration
|
||||
inline void config(const config_t& cfg)
|
||||
{
|
||||
_cfg = cfg;
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Measurement data by periodic
|
||||
///@{
|
||||
//! @brief Oldest measured temperature (Celsius)
|
||||
inline float temperature() const
|
||||
{
|
||||
return !empty() ? oldest().temperature() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
//! @brief Oldest measured temperature (Celsius)
|
||||
inline float celsius() const
|
||||
{
|
||||
return !empty() ? oldest().celsius() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
//! @brief Oldest measured temperature (Fahrenheit)
|
||||
inline float fahrenheit() const
|
||||
{
|
||||
return !empty() ? oldest().fahrenheit() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
//! @brief Oldest measured humidity (RH)
|
||||
inline float humidity() const
|
||||
{
|
||||
return !empty() ? oldest().humidity() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Periodic measurement
|
||||
///@{
|
||||
/*!
|
||||
@brief Start periodic measurement
|
||||
@param mps Measuring frequency
|
||||
@param rep Repeatability accuracy level
|
||||
@return True if successful
|
||||
*/
|
||||
inline bool startPeriodicMeasurement(const sht30::MPS mps, const sht30::Repeatability rep)
|
||||
{
|
||||
return PeriodicMeasurementAdapter<UnitSHT30, sht30::Data>::startPeriodicMeasurement(mps, rep);
|
||||
}
|
||||
/*!
|
||||
@brief Stop periodic measurement
|
||||
@return True if successful
|
||||
*/
|
||||
inline bool stopPeriodicMeasurement()
|
||||
{
|
||||
return PeriodicMeasurementAdapter<UnitSHT30, sht30::Data>::stopPeriodicMeasurement();
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Single shot measurement
|
||||
///@{
|
||||
/*!
|
||||
@brief Measurement single shot
|
||||
@param[out] data Measuerd data
|
||||
@param rep Repeatability accuracy level
|
||||
@param stretch Enable clock stretching if true
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
@warning After sending a command to the sensor a minimal waiting time of 1ms is needed before another command can
|
||||
be received by the sensor
|
||||
*/
|
||||
bool measureSingleshot(sht30::Data& d, const sht30::Repeatability rep = sht30::Repeatability::High,
|
||||
const bool stretch = true);
|
||||
///@}
|
||||
|
||||
/*!
|
||||
@brief Write the mode to ART
|
||||
@details After issuing the ART command the sensor will start acquiring data with a frequency of 4Hz
|
||||
@return True if successful
|
||||
@warning Only available during periodic measurements
|
||||
*/
|
||||
bool writeModeAccelerateResponseTime();
|
||||
|
||||
///@name Reset
|
||||
///@{
|
||||
/*!
|
||||
@brief Soft reset
|
||||
@details The sensor to reset its system controller and reloads calibration
|
||||
data from the memory.
|
||||
@return True if successful
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool softReset();
|
||||
/*!
|
||||
@brief General reset
|
||||
@details Reset using I2C general call
|
||||
@return True if successful
|
||||
@warning This is a reset by General command, the command is also sent to all devices with I2C connections
|
||||
*/
|
||||
bool generalReset();
|
||||
///@}
|
||||
|
||||
///@name Heater
|
||||
///@{
|
||||
/*!
|
||||
@brief Start heater
|
||||
@return True if successful
|
||||
*/
|
||||
bool startHeater();
|
||||
/*!
|
||||
@brief Stop heater
|
||||
@return True if successful
|
||||
*/
|
||||
bool stopHeater();
|
||||
///@}
|
||||
|
||||
///@name Status
|
||||
///@{
|
||||
/*!
|
||||
@brief Read status
|
||||
@param[out] s Status
|
||||
@return True if successful
|
||||
*/
|
||||
bool readStatus(sht30::Status& s);
|
||||
/*!
|
||||
@brief Clear status
|
||||
@sa sht30::Status
|
||||
@return True if successful
|
||||
*/
|
||||
bool clearStatus();
|
||||
///@}
|
||||
|
||||
///@name Serial number
|
||||
///@{
|
||||
/*!
|
||||
@brief Read the serial number value
|
||||
@param[out] serialNumber serial number value
|
||||
@return True if successful
|
||||
@note The serial number is 32 bit
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool readSerialNumber(uint32_t& serialNumber);
|
||||
/*!
|
||||
@brief Read the serial number string
|
||||
@param[out] serialNumber Output buffer
|
||||
@return True if successful
|
||||
@warning serialNumber must be at least 9 bytes
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool readSerialNumber(char* serialNumber);
|
||||
///@}
|
||||
|
||||
protected:
|
||||
bool start_periodic_measurement(const sht30::MPS mps, const sht30::Repeatability rep);
|
||||
bool stop_periodic_measurement();
|
||||
bool read_measurement(sht30::Data& d);
|
||||
|
||||
M5_UNIT_COMPONENT_PERIODIC_MEASUREMENT_ADAPTER_HPP_BUILDER(UnitSHT30, sht30::Data);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<m5::container::CircularBuffer<sht30::Data>> _data{};
|
||||
config_t _cfg{};
|
||||
};
|
||||
|
||||
///@cond
|
||||
namespace sht30 {
|
||||
namespace command {
|
||||
// Measurement Commands for Single Shot Data Acquisition Mode
|
||||
constexpr uint16_t SINGLE_SHOT_ENABLE_STRETCH_HIGH{0x2C06};
|
||||
constexpr uint16_t SINGLE_SHOT_ENABLE_STRETCH_MEDIUM{0x2C0D};
|
||||
constexpr uint16_t SINGLE_SHOT_ENABLE_STRETCH_LOW{0x2C10};
|
||||
constexpr uint16_t SINGLE_SHOT_DISABLE_STRETCH_HIGH{0x2400};
|
||||
constexpr uint16_t SINGLE_SHOT_DISABLE_STRETCH_MEDIUM{0x240B};
|
||||
constexpr uint16_t SINGLE_SHOT_DISABLE_STRETCH_LOW{0x2416};
|
||||
// Measurement Commands for Periodic Data Acquisition Mode
|
||||
constexpr uint16_t START_PERIODIC_MPS_HALF_HIGH{0x2032};
|
||||
constexpr uint16_t START_PERIODIC_MPS_HALF_MEDIUM{0x2024};
|
||||
constexpr uint16_t START_PERIODIC_MPS_HALF_LOW{0x202f};
|
||||
|
||||
constexpr uint16_t START_PERIODIC_MPS_1_HIGH{0x2130};
|
||||
constexpr uint16_t START_PERIODIC_MPS_1_MEDIUM{0x2126};
|
||||
constexpr uint16_t START_PERIODIC_MPS_1_LOW{0x212D};
|
||||
|
||||
constexpr uint16_t START_PERIODIC_MPS_2_HIGH{0x2236};
|
||||
constexpr uint16_t START_PERIODIC_MPS_2_MEDIUM{0x2220};
|
||||
constexpr uint16_t START_PERIODIC_MPS_2_LOW{0x222B};
|
||||
|
||||
constexpr uint16_t START_PERIODIC_MPS_4_HIGH{0x2334};
|
||||
constexpr uint16_t START_PERIODIC_MPS_4_MEDIUM{0x2322};
|
||||
constexpr uint16_t START_PERIODIC_MPS_4_LOW{0x2329};
|
||||
|
||||
constexpr uint16_t START_PERIODIC_MPS_10_HIGH{0x2737};
|
||||
constexpr uint16_t START_PERIODIC_MPS_10_MEDIUM{0x2721};
|
||||
constexpr uint16_t START_PERIODIC_MPS_10_LOW{0x272A};
|
||||
|
||||
constexpr uint16_t STOP_PERIODIC_MEASUREMENT{0x3093};
|
||||
constexpr uint16_t ACCELERATED_RESPONSE_TIME{0x2B32};
|
||||
constexpr uint16_t READ_MEASUREMENT{0xE000};
|
||||
// Reset
|
||||
constexpr uint16_t SOFT_RESET{0x30A2};
|
||||
// Heater
|
||||
constexpr uint16_t START_HEATER{0x306D};
|
||||
constexpr uint16_t STOP_HEATER{0x3066};
|
||||
// Status
|
||||
constexpr uint16_t READ_STATUS{0xF32D};
|
||||
constexpr uint16_t CLEAR_STATUS{0x3041};
|
||||
// Serial
|
||||
constexpr uint16_t GET_SERIAL_NUMBER_ENABLE_STRETCH{0x3780};
|
||||
constexpr uint16_t GET_SERIAL_NUMBER_DISABLE_STRETCH{0x3682};
|
||||
} // namespace command
|
||||
} // namespace sht30
|
||||
///@endcond
|
||||
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
#endif
|
||||
296
libraries/M5Unit-ENV/src/unit/unit_SHT40.cpp
Normal file
296
libraries/M5Unit-ENV/src/unit/unit_SHT40.cpp
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_SHT40.cpp
|
||||
@brief SHT40 Unit for M5UnitUnified
|
||||
*/
|
||||
#include "unit_SHT40.hpp"
|
||||
#include <M5Utility.hpp>
|
||||
#include <limits> // NaN
|
||||
#include <array>
|
||||
|
||||
using namespace m5::utility::mmh3;
|
||||
using namespace m5::unit::types;
|
||||
using namespace m5::unit::sht40;
|
||||
using namespace m5::unit::sht40::command;
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t periodic_cmd[] = {
|
||||
// HIGH
|
||||
MEASURE_HIGH_HEATER_1S,
|
||||
MEASURE_HIGH_HEATER_100MS,
|
||||
MEASURE_HIGH,
|
||||
// MEDIUM
|
||||
MEASURE_MEDIUM_HEATER_1S,
|
||||
MEASURE_MEDIUM_HEATER_100MS,
|
||||
MEASURE_MEDIUM,
|
||||
// LOW
|
||||
MEASURE_LOW_HEATER_1S,
|
||||
MEASURE_LOW_HEATER_100MS,
|
||||
MEASURE_LOW,
|
||||
};
|
||||
|
||||
constexpr elapsed_time_t interval_table[] = {
|
||||
// HIGH
|
||||
1100, 110,
|
||||
9, // 8.2
|
||||
// MEDIUM
|
||||
1100, 110,
|
||||
5, // 4.5
|
||||
// LOW
|
||||
1100, 110,
|
||||
2, // 1.7
|
||||
};
|
||||
|
||||
constexpr float MAX_HEATER_DUTY{0.05f};
|
||||
} // namespace
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
namespace sht40 {
|
||||
|
||||
float Data::celsius() const
|
||||
{
|
||||
return -45 + 175 * m5::types::big_uint16_t(raw[0], raw[1]).get() / 65535.f;
|
||||
}
|
||||
|
||||
float Data::fahrenheit() const
|
||||
{
|
||||
return -49 + 315 * m5::types::big_uint16_t(raw[0], raw[1]).get() / 65535.f;
|
||||
// return celsius() * 9.0f / 5.0f + 32.f;
|
||||
}
|
||||
|
||||
float Data::humidity() const
|
||||
{
|
||||
return -6 + 125 * m5::types::big_uint16_t(raw[3], raw[4]).get() / 65535.f;
|
||||
}
|
||||
} // namespace sht40
|
||||
|
||||
const char UnitSHT40::name[] = "UnitSHT40";
|
||||
const types::uid_t UnitSHT40::uid{"UnitSHT40"_mmh3};
|
||||
const types::attr_t UnitSHT40::attr{attribute::AccessI2C};
|
||||
|
||||
bool UnitSHT40::begin()
|
||||
{
|
||||
auto ssize = stored_size();
|
||||
assert(ssize && "stored_size must be greater than zero");
|
||||
if (ssize != _data->capacity()) {
|
||||
_data.reset(new m5::container::CircularBuffer<Data>(ssize));
|
||||
if (!_data) {
|
||||
M5_LIB_LOGE("Failed to allocate");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!softReset()) {
|
||||
M5_LIB_LOGE("Failed to reset");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t sn{};
|
||||
if (!readSerialNumber(sn)) {
|
||||
M5_LIB_LOGE("Failed to readSerialNumber %x", sn);
|
||||
return false;
|
||||
}
|
||||
|
||||
return _cfg.start_periodic ? startPeriodicMeasurement(_cfg.precision, _cfg.heater, _cfg.heater_duty) : true;
|
||||
}
|
||||
|
||||
void UnitSHT40::update(const bool force)
|
||||
{
|
||||
_updated = false;
|
||||
if (inPeriodic()) {
|
||||
elapsed_time_t at{m5::utility::millis()};
|
||||
if (force || !_latest || at >= _latest + _interval) {
|
||||
Data d{};
|
||||
_updated = read_measurement(d);
|
||||
|
||||
if (_updated) {
|
||||
_latest = at;
|
||||
d.heater = (_interval != _duration_heater);
|
||||
_data->push_back(d);
|
||||
|
||||
uint8_t cmd{};
|
||||
if (at >= _latest_heater + _interval_heater) {
|
||||
cmd = _cmd;
|
||||
_latest_heater = at;
|
||||
_interval = _duration_heater;
|
||||
} else {
|
||||
cmd = _measureCmd;
|
||||
_interval = _duration_measure;
|
||||
}
|
||||
if (!writeRegister(cmd)) {
|
||||
M5_LIB_LOGE("Failed to write, stop periodic measurement");
|
||||
_periodic = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UnitSHT40::start_periodic_measurement(const sht40::Precision precision, const sht40::Heater heater,
|
||||
const float duty)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
if (duty <= 0.0f || duty > MAX_HEATER_DUTY) {
|
||||
M5_LIB_LOGW("duty range is invalid %f. duty (0.0, 0.05]");
|
||||
return false;
|
||||
}
|
||||
|
||||
_cmd = periodic_cmd[m5::stl::to_underlying(precision) * 3 + m5::stl::to_underlying(heater)];
|
||||
_measureCmd = periodic_cmd[m5::stl::to_underlying(precision) * 3 + m5::stl::to_underlying(Heater::None)];
|
||||
|
||||
_periodic = writeRegister(_cmd);
|
||||
if (_periodic) {
|
||||
_duration_heater = interval_table[m5::stl::to_underlying(precision) * 3 + m5::stl::to_underlying(heater)];
|
||||
_duration_measure =
|
||||
interval_table[m5::stl::to_underlying(precision) * 3 + m5::stl::to_underlying(Heater::None)];
|
||||
|
||||
_interval_heater = _duration_heater / duty;
|
||||
_interval = _duration_heater;
|
||||
_latest_heater = m5::utility::millis();
|
||||
|
||||
m5::utility::delay(_interval); // For first read_measurement in update
|
||||
return true;
|
||||
}
|
||||
return _periodic;
|
||||
}
|
||||
|
||||
bool UnitSHT40::stop_periodic_measurement()
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
// Dismissal of data to be read
|
||||
Data discard{};
|
||||
int64_t wait = (int64_t)(_latest + _interval) - (int64_t)m5::utility::millis();
|
||||
if (wait > 0) {
|
||||
m5::utility::delay(wait);
|
||||
read_measurement(discard);
|
||||
}
|
||||
|
||||
_periodic = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSHT40::measureSingleshot(sht40::Data& d, const sht40::Precision precision, const sht40::Heater heater)
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGD("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
uint8_t cmd = periodic_cmd[m5::stl::to_underlying(precision) * 3 + m5::stl::to_underlying(heater)];
|
||||
auto ms = interval_table[m5::stl::to_underlying(precision) * 3 + m5::stl::to_underlying(heater)];
|
||||
|
||||
if (writeRegister(cmd)) {
|
||||
m5::utility::delay(ms);
|
||||
if (read_measurement(d)) {
|
||||
d.heater = (heater != Heater::None);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSHT40::read_measurement(sht40::Data& d)
|
||||
{
|
||||
if (readWithTransaction(d.raw.data(), d.raw.size()) == m5::hal::error::error_t::OK) {
|
||||
m5::utility::CRC8_Checksum crc{};
|
||||
for (uint_fast8_t i = 0; i < 2; ++i) {
|
||||
if (crc.range(d.raw.data() + i * 3, 2U) != d.raw[i * 3 + 2]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSHT40::softReset()
|
||||
{
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGE("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
return soft_reset();
|
||||
}
|
||||
|
||||
bool UnitSHT40::soft_reset()
|
||||
{
|
||||
if (writeRegister(SOFT_RESET)) {
|
||||
// Max 1 ms
|
||||
// Time between ACK of soft reset command and sensor entering idle state
|
||||
m5::utility::delay(1);
|
||||
reset_status();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSHT40::generalReset()
|
||||
{
|
||||
uint8_t cmd{0x06}; // reset command
|
||||
// Reset does not return ACK, which is an error, but should be ignored
|
||||
generalCall(&cmd, 1);
|
||||
|
||||
m5::utility::delay(1);
|
||||
reset_status();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnitSHT40::readSerialNumber(uint32_t& serialNumber)
|
||||
{
|
||||
serialNumber = 0;
|
||||
if (inPeriodic()) {
|
||||
M5_LIB_LOGE("Periodic measurements are running");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<uint8_t, 6> rbuf;
|
||||
if (readRegister(GET_SERIAL_NUMBER, rbuf.data(), rbuf.size(), 1)) {
|
||||
m5::types::big_uint16_t u16[2]{{rbuf[0], rbuf[1]}, {rbuf[3], rbuf[4]}};
|
||||
m5::utility::CRC8_Checksum crc{};
|
||||
if (crc.range(u16[0].data(), u16[0].size()) == rbuf[2] && crc.range(u16[1].data(), u16[1].size()) == rbuf[5]) {
|
||||
serialNumber = ((uint32_t)u16[0].get()) << 16 | ((uint32_t)u16[1].get());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitSHT40::readSerialNumber(char* serialNumber)
|
||||
{
|
||||
if (!serialNumber) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*serialNumber = '\0';
|
||||
uint32_t sno{};
|
||||
if (readSerialNumber(sno)) {
|
||||
uint_fast8_t i{8};
|
||||
while (i--) {
|
||||
*serialNumber++ = m5::utility::uintToHexChar((sno >> (i * 4)) & 0x0F);
|
||||
}
|
||||
*serialNumber = '\0';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UnitSHT40::reset_status()
|
||||
{
|
||||
_interval = _latest = _interval_heater = _latest_heater = 0;
|
||||
_duration_measure = _duration_heater = 0;
|
||||
_cmd = _measureCmd = 0;
|
||||
_periodic = false;
|
||||
}
|
||||
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
260
libraries/M5Unit-ENV/src/unit/unit_SHT40.hpp
Normal file
260
libraries/M5Unit-ENV/src/unit/unit_SHT40.hpp
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file unit_SHT40.hpp
|
||||
@brief SHT40 Unit for M5UnitUnified
|
||||
*/
|
||||
#ifndef M5_UNIT_ENV_UNIT_SHT40_HPP
|
||||
#define M5_UNIT_ENV_UNIT_SHT40_HPP
|
||||
|
||||
#include <M5UnitComponent.hpp>
|
||||
#include <m5_utility/container/circular_buffer.hpp>
|
||||
#include <limits> // NaN
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
|
||||
/*!
|
||||
@namespace sht40
|
||||
@brief For SHT40
|
||||
*/
|
||||
namespace sht40 {
|
||||
|
||||
/*!
|
||||
@enum Precision
|
||||
@brief precision level
|
||||
*/
|
||||
enum class Precision : uint8_t {
|
||||
High, //!< High precision (high repeatability)
|
||||
Medium, //!< Medium precision (medium repeatability)
|
||||
Low //!< Lowest precision (low repeatability)
|
||||
};
|
||||
|
||||
/*!
|
||||
@enum Heater
|
||||
@brief Heater behavior
|
||||
*/
|
||||
enum class Heater : uint8_t {
|
||||
Long, //!< Activate heater for 1s
|
||||
Short, //!< Activate heater for 0.1s
|
||||
None //!< Not activate heater
|
||||
};
|
||||
|
||||
/*!
|
||||
@struct Data
|
||||
@brief Measurement data group
|
||||
*/
|
||||
struct Data {
|
||||
std::array<uint8_t, 6> raw{}; //!< RAW data
|
||||
bool heater{}; //!< Measured data after heater is activated if true
|
||||
|
||||
//! temperature (Celsius)
|
||||
inline float temperature() const
|
||||
{
|
||||
return celsius();
|
||||
}
|
||||
float celsius() const; //!< temperature (Celsius)
|
||||
float fahrenheit() const; //!< temperature (Fahrenheit)
|
||||
float humidity() const; //!< humidity (RH)
|
||||
};
|
||||
|
||||
} // namespace sht40
|
||||
|
||||
/*!
|
||||
@class UnitSHT40
|
||||
@brief Temperature and humidity, sensor unit
|
||||
*/
|
||||
class UnitSHT40 : public Component, public PeriodicMeasurementAdapter<UnitSHT40, sht40::Data> {
|
||||
M5_UNIT_COMPONENT_HPP_BUILDER(UnitSHT40, 0x44);
|
||||
|
||||
public:
|
||||
/*!
|
||||
@struct config_t
|
||||
@brief Settings for begin
|
||||
*/
|
||||
struct config_t {
|
||||
//! Start periodic measurement on begin?
|
||||
bool start_periodic{true};
|
||||
//! Precision level if start on begin
|
||||
sht40::Precision precision{sht40::Precision::High};
|
||||
//! Heater behavior if start on begin
|
||||
sht40::Heater heater{sht40::Heater::None};
|
||||
//! Heater duty cycle if start on begin [~ 0.05f]
|
||||
float heater_duty{0.05f};
|
||||
};
|
||||
|
||||
explicit UnitSHT40(const uint8_t addr = DEFAULT_ADDRESS)
|
||||
: Component(addr), _data{new m5::container::CircularBuffer<sht40::Data>(1)}
|
||||
{
|
||||
auto ccfg = component_config();
|
||||
ccfg.clock = 400 * 1000U;
|
||||
component_config(ccfg);
|
||||
}
|
||||
virtual ~UnitSHT40()
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool begin() override;
|
||||
virtual void update(const bool force = false) override;
|
||||
|
||||
///@name Settings for begin
|
||||
///@{
|
||||
/*! @brief Gets the configration */
|
||||
inline config_t config()
|
||||
{
|
||||
return _cfg;
|
||||
}
|
||||
//! @brief Set the configration
|
||||
inline void config(const config_t& cfg)
|
||||
{
|
||||
_cfg = cfg;
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Measurement data by periodic
|
||||
///@{
|
||||
//! @brief Oldest measured temperature (Celsius)
|
||||
inline float temperature() const
|
||||
{
|
||||
return !empty() ? oldest().temperature() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
//! @brief Oldest measured temperature (Celsius)
|
||||
inline float celsius() const
|
||||
{
|
||||
return !empty() ? oldest().celsius() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
//! @brief Oldest measured temperature (Fahrenheit)
|
||||
inline float fahrenheit() const
|
||||
{
|
||||
return !empty() ? oldest().fahrenheit() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
//! @brief Oldest measured humidity (RH)
|
||||
inline float humidity() const
|
||||
{
|
||||
return !empty() ? oldest().humidity() : std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Periodic measurement
|
||||
///@{
|
||||
/*!
|
||||
@brief Start periodic measurement
|
||||
@param precision Sensor precision
|
||||
@param heater Heater behavior
|
||||
@param duty Duty for activate heater
|
||||
@return True if successful
|
||||
@note If the heater is Long or SHort, the heater will be active periodically within the specified duty
|
||||
@warning Datasheet says "keepingin mind that the heater is designed for a maximal duty cycle of less than 5%"
|
||||
*/
|
||||
inline bool startPeriodicMeasurement(const sht40::Precision precision, const sht40::Heater heater,
|
||||
const float duty = 0.05f)
|
||||
{
|
||||
return PeriodicMeasurementAdapter<UnitSHT40, sht40::Data>::startPeriodicMeasurement(precision, heater, duty);
|
||||
}
|
||||
/*!
|
||||
@brief Stop periodic measurement
|
||||
@return True if successful
|
||||
*/
|
||||
inline bool stopPeriodicMeasurement()
|
||||
{
|
||||
return PeriodicMeasurementAdapter<UnitSHT40, sht40::Data>::stopPeriodicMeasurement();
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Single shot measurement
|
||||
///@{
|
||||
/*!
|
||||
@brief Measurement single shot
|
||||
@param[out] data Measuerd data
|
||||
@param precision Sensor precision
|
||||
@param heater Heater behavior
|
||||
@return True if successful
|
||||
@note Blocking until the process is complete
|
||||
@warning During periodic detection runs, an error is returned
|
||||
@warning If heater is activated, the accuracy of the returned value is not guaranteed
|
||||
@sa UnitSHT40::startPeriodicMeasurement
|
||||
*/
|
||||
bool measureSingleshot(sht40::Data& d, const sht40::Precision precision = sht40::Precision::High,
|
||||
const sht40::Heater heater = sht40::Heater::None);
|
||||
|
||||
///@}
|
||||
|
||||
///@name Reset
|
||||
///@{
|
||||
/*!
|
||||
@brief Soft reset
|
||||
@return True if successful
|
||||
*/
|
||||
bool softReset();
|
||||
/*!
|
||||
@brief General reset
|
||||
@details Reset using I2C general call
|
||||
@return True if successful
|
||||
@warning This is a reset by General command, the command is also sent to all devices with I2C connections
|
||||
*/
|
||||
bool generalReset();
|
||||
///@}
|
||||
|
||||
///@name Serial number
|
||||
///@{
|
||||
/*!
|
||||
@brief Read the serial number value
|
||||
@param[out] serialNumber serial number value
|
||||
@return True if successful
|
||||
@note The serial number is 32 bit
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool readSerialNumber(uint32_t& serialNumber);
|
||||
/*!
|
||||
@brief Read the serial number string
|
||||
@param[out] serialNumber Output buffer
|
||||
@return True if successful
|
||||
@warning serialNumber must be at least 9 bytes
|
||||
@warning During periodic detection runs, an error is returned
|
||||
*/
|
||||
bool readSerialNumber(char* serialNumber);
|
||||
///@}
|
||||
|
||||
protected:
|
||||
bool start_periodic_measurement(const sht40::Precision precision, const sht40::Heater heater, const float duty);
|
||||
bool stop_periodic_measurement();
|
||||
bool read_measurement(sht40::Data& d);
|
||||
void reset_status();
|
||||
bool soft_reset();
|
||||
|
||||
M5_UNIT_COMPONENT_PERIODIC_MEASUREMENT_ADAPTER_HPP_BUILDER(UnitSHT40, sht40::Data);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<m5::container::CircularBuffer<sht40::Data>> _data{};
|
||||
config_t _cfg{};
|
||||
uint8_t _cmd{}, _measureCmd{};
|
||||
types::elapsed_time_t _latest_heater{}, _interval_heater{};
|
||||
uint32_t _duration_measure{}, _duration_heater{};
|
||||
};
|
||||
|
||||
///@cond
|
||||
namespace sht40 {
|
||||
namespace command {
|
||||
constexpr uint8_t MEASURE_HIGH_HEATER_1S{0x39};
|
||||
constexpr uint8_t MEASURE_HIGH_HEATER_100MS{0x32};
|
||||
constexpr uint8_t MEASURE_HIGH{0xFD};
|
||||
|
||||
constexpr uint8_t MEASURE_MEDIUM_HEATER_1S{0x2F};
|
||||
constexpr uint8_t MEASURE_MEDIUM_HEATER_100MS{0x24};
|
||||
constexpr uint8_t MEASURE_MEDIUM{0xF6};
|
||||
|
||||
constexpr uint8_t MEASURE_LOW_HEATER_1S{0x1E};
|
||||
constexpr uint8_t MEASURE_LOW_HEATER_100MS{0x15};
|
||||
constexpr uint8_t MEASURE_LOW{0xE0};
|
||||
|
||||
constexpr uint8_t GET_SERIAL_NUMBER{0x89};
|
||||
constexpr uint8_t SOFT_RESET{0x94};
|
||||
} // namespace command
|
||||
} // namespace sht40
|
||||
///@endcond
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
#endif
|
||||
27
libraries/M5Unit-ENV/src/utility.cpp
Normal file
27
libraries/M5Unit-ENV/src/utility.cpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
#include "utility.h"
|
||||
|
||||
uint8_t crc8(const uint8_t *data, int len) {
|
||||
/*
|
||||
*
|
||||
* CRC-8 formula from page 14 of SHT spec pdf
|
||||
*
|
||||
* Test data 0xBE, 0xEF should yield 0x92
|
||||
*
|
||||
* Initialization data 0xFF
|
||||
* Polynomial 0x31 (x8 + x5 +x4 +1)
|
||||
* Final XOR 0x00
|
||||
*/
|
||||
|
||||
const uint8_t POLYNOMIAL(0x31);
|
||||
uint8_t crc(0xFF);
|
||||
|
||||
for (int j = len; j; --j) {
|
||||
crc ^= *data++;
|
||||
|
||||
for (int i = 8; i; --i) {
|
||||
crc = (crc & 0x80) ? (crc << 1) ^ POLYNOMIAL : (crc << 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
8
libraries/M5Unit-ENV/src/utility.h
Normal file
8
libraries/M5Unit-ENV/src/utility.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef __M5_UNIT_ENV_UTILITY_H_
|
||||
#define __M5_UNIT_ENV_UTILITY_H_
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
uint8_t crc8(const uint8_t *data, int len);
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue