diff --git a/src/configuration.h b/src/configuration.h index 84dabee4e83..c104bc679cc 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -241,6 +241,8 @@ along with this program. If not, see . #define QMC5883L_ADDR 0x0D #define HMC5883L_ADDR 0x1E #define SHTC3_ADDR 0x70 +#define QMP6988_ADDR 0x70 +#define QMP6988_ADDR_ALT 0x56 #define LPS22HB_ADDR 0x5C #define LPS22HB_ADDR_ALT 0x5D #define SFA30_ADDR 0x5D diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 054c7854baf..bc40a936b25 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -97,6 +97,7 @@ class ScanI2C CW2015, SCD30, ADS1115, + QMP6988 } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index e298663a0c0..3f673b29831 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -179,6 +179,9 @@ String readSEN5xProductName(TwoWire *i2cBus, uint8_t address) #endif #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR +constexpr uint8_t QMP6988_DETECT_CHIP_ID = 0x5C; +constexpr uint8_t QMP6988_DETECT_CHIP_ID_REG = 0xD1; + bool detectSHT21SerialNumber(TwoWire *i2cBus, uint8_t address) { @@ -215,6 +218,19 @@ bool detectSHT21SerialNumber(TwoWire *i2cBus, uint8_t address) // Assume we detect the SHT21 if something came back from the request return true; } + +bool detectQMP6988ChipId(TwoWire *i2cBus, uint8_t address) +{ + i2cBus->beginTransmission(address); + i2cBus->write(QMP6988_DETECT_CHIP_ID_REG); + if (i2cBus->endTransmission() != 0) + return false; + + if (i2cBus->requestFrom((int)address, 1) != 1) + return false; + + return i2cBus->available() && i2cBus->read() == QMP6988_DETECT_CHIP_ID; +} #endif #define SCAN_SIMPLE_CASE(ADDR, T, ...) \ @@ -506,8 +522,21 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) } break; - - SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTXX, "SHTXX", (uint8_t)addr.address) + case QMP6988_ADDR_ALT: + if (detectQMP6988ChipId(i2cBus, (uint8_t)addr.address)) { + type = QMP6988; + logFoundDevice("QMP6988", (uint8_t)addr.address); + } + break; + case SHTC3_ADDR: + if (detectQMP6988ChipId(i2cBus, (uint8_t)addr.address)) { + type = QMP6988; + logFoundDevice("QMP6988", (uint8_t)addr.address); + } else { + type = SHTXX; + logFoundDevice("SHTXX", (uint8_t)addr.address); + } + break; case RCWL9620_ADDR: // get MAX30102 PARTID registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFF), 1); diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 8c0fdd56314..68f9fe4c1f4 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -115,7 +115,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* SHT family of sensors for temperature and humidity */ meshtastic_TelemetrySensorType_SHTXX = 50, /* DS248X Bridge for one-wire temperature sensors */ - meshtastic_TelemetrySensorType_DS248X = 51 + meshtastic_TelemetrySensorType_DS248X = 51, + /* QST QMP6988 temperature and pressure sensor */ + meshtastic_TelemetrySensorType_QMP6988 = 52 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -496,8 +498,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_DS248X -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_DS248X+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_QMP6988 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_QMP6988+1)) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 684d408a1cc..53ce28dbd9a 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -62,6 +62,8 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c #include "Sensor/DPS310Sensor.h" #endif +#include "Sensor/QMP6988Sensor.h" + #if __has_include() #include "Sensor/MCP9808Sensor.h" #endif @@ -200,6 +202,7 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) #if __has_include() addSensor(i2cScanner, ScanI2C::DeviceType::DPS310); #endif + addSensor(i2cScanner, ScanI2C::DeviceType::QMP6988); #if __has_include() addSensor(i2cScanner, ScanI2C::DeviceType::MCP9808); #endif diff --git a/src/modules/Telemetry/Sensor/QMP6988Sensor.cpp b/src/modules/Telemetry/Sensor/QMP6988Sensor.cpp new file mode 100644 index 00000000000..05d9af667d2 --- /dev/null +++ b/src/modules/Telemetry/Sensor/QMP6988Sensor.cpp @@ -0,0 +1,294 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "QMP6988Sensor.h" +#include "TelemetrySensor.h" +#include + +namespace +{ + +constexpr uint8_t QMP6988_CHIP_ID = 0x5C; +constexpr uint8_t QMP6988_CHIP_ID_REG = 0xD1; +constexpr uint8_t QMP6988_RESET_REG = 0xE0; +constexpr uint8_t QMP6988_CONFIG_REG = 0xF1; +constexpr uint8_t QMP6988_CTRL_MEAS_REG = 0xF4; +constexpr uint8_t QMP6988_PRESSURE_MSB_REG = 0xF7; +constexpr uint8_t QMP6988_CALIBRATION_START_REG = 0xA0; +constexpr uint8_t QMP6988_CALIBRATION_LENGTH = 25; + +constexpr uint8_t QMP6988_MODE_NORMAL = 0x03; +constexpr uint8_t QMP6988_FILTER_4 = 0x02; +constexpr uint8_t QMP6988_OVERSAMPLING_1X = 0x01; +constexpr uint8_t QMP6988_OVERSAMPLING_8X = 0x04; + +constexpr int32_t QMP6988_SUBTRACTOR = 8388608; + +} // namespace + +QMP6988Sensor::QMP6988Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_QMP6988, "QMP6988") {} + +bool QMP6988Sensor::writeByte(uint8_t reg, uint8_t value) +{ + bus->beginTransmission(address); + bus->write(reg); + bus->write(value); + return bus->endTransmission() == 0; +} + +bool QMP6988Sensor::readByte(uint8_t reg, uint8_t &value) +{ + if (!readBytes(reg, &value, 1)) { + return false; + } + return true; +} + +bool QMP6988Sensor::readBytes(uint8_t reg, uint8_t *buffer, uint8_t length) +{ + bus->beginTransmission(address); + bus->write(reg); + if (bus->endTransmission() != 0) { + return false; + } + + if (bus->requestFrom((int)address, (int)length) != length) { + return false; + } + + for (uint8_t i = 0; i < length; ++i) { + if (!bus->available()) { + return false; + } + buffer[i] = bus->read(); + } + + return true; +} + +bool QMP6988Sensor::reset() +{ + if (!writeByte(QMP6988_RESET_REG, 0xE6)) { + return false; + } + delay(20); + writeByte(QMP6988_RESET_REG, 0x00); + return true; +} + +bool QMP6988Sensor::readCalibrationData() +{ + uint8_t data[QMP6988_CALIBRATION_LENGTH] = {}; + for (uint8_t i = 0; i < sizeof(data); ++i) { + if (!readBytes(QMP6988_CALIBRATION_START_REG + i, &data[i], 1)) { + return false; + } + } + + calibration.a0 = (int32_t)(((data[18] << 12) | (data[19] << 4) | (data[24] & 0x0F)) << 12) >> 12; + calibration.a1 = (int16_t)((data[20] << 8) | data[21]); + calibration.a2 = (int16_t)((data[22] << 8) | data[23]); + calibration.b00 = (int32_t)(((data[0] << 12) | (data[1] << 4) | ((data[24] & 0xF0) >> 4)) << 12) >> 12; + calibration.bt1 = (int16_t)((data[2] << 8) | data[3]); + calibration.bt2 = (int16_t)((data[4] << 8) | data[5]); + calibration.bp1 = (int16_t)((data[6] << 8) | data[7]); + calibration.b11 = (int16_t)((data[8] << 8) | data[9]); + calibration.bp2 = (int16_t)((data[10] << 8) | data[11]); + calibration.b12 = (int16_t)((data[12] << 8) | data[13]); + calibration.b21 = (int16_t)((data[14] << 8) | data[15]); + calibration.bp3 = (int16_t)((data[16] << 8) | data[17]); + + integerCalibration.a0 = calibration.a0; + integerCalibration.b00 = calibration.b00; + integerCalibration.a1 = 3608L * calibration.a1 - 1731677965L; + integerCalibration.a2 = 16889L * calibration.a2 - 87619360L; + integerCalibration.bt1 = 2982LL * calibration.bt1 + 107370906LL; + integerCalibration.bt2 = 329854LL * calibration.bt2 + 108083093LL; + integerCalibration.bp1 = 19923LL * calibration.bp1 + 1133836764LL; + integerCalibration.b11 = 2406LL * calibration.b11 + 118215883LL; + integerCalibration.bp2 = 3079LL * calibration.bp2 - 181579595LL; + integerCalibration.b12 = 6846LL * calibration.b12 + 85590281LL; + integerCalibration.b21 = 13836LL * calibration.b21 + 79333336LL; + integerCalibration.bp3 = 2915LL * calibration.bp3 + 157155561LL; + + return true; +} + +void QMP6988Sensor::setPowerMode(uint8_t powerMode) +{ + uint8_t control = 0; + if (!readByte(QMP6988_CTRL_MEAS_REG, control)) { + return; + } + + control &= 0xFC; + control |= (powerMode & 0x03); + writeByte(QMP6988_CTRL_MEAS_REG, control); + delay(20); +} + +void QMP6988Sensor::setFilter(uint8_t filter) +{ + writeByte(QMP6988_CONFIG_REG, filter & 0x03); + delay(20); +} + +void QMP6988Sensor::setOversamplingPressure(uint8_t oversampling) +{ + uint8_t control = 0; + if (!readByte(QMP6988_CTRL_MEAS_REG, control)) { + return; + } + + control &= 0xE3; + control |= (oversampling << 2); + writeByte(QMP6988_CTRL_MEAS_REG, control); + delay(20); +} + +void QMP6988Sensor::setOversamplingTemperature(uint8_t oversampling) +{ + uint8_t control = 0; + if (!readByte(QMP6988_CTRL_MEAS_REG, control)) { + return; + } + + control &= 0x1F; + control |= (oversampling << 5); + writeByte(QMP6988_CTRL_MEAS_REG, control); + delay(20); +} + +int16_t QMP6988Sensor::convertTemperature(int32_t rawTemperature) const +{ + int64_t wk1 = (int64_t)integerCalibration.a1 * rawTemperature; + int64_t wk2 = ((int64_t)integerCalibration.a2 * rawTemperature) >> 14; + wk2 = (wk2 * rawTemperature) >> 10; + wk2 = ((wk1 + wk2) / 32767) >> 19; + return (int16_t)((integerCalibration.a0 + wk2) >> 4); +} + +int32_t QMP6988Sensor::convertPressure(int32_t rawPressure, int16_t compensatedTemperature) const +{ + int64_t wk1 = (int64_t)integerCalibration.bt1 * compensatedTemperature; + int64_t wk2 = ((int64_t)integerCalibration.bp1 * rawPressure) >> 5; + wk1 += wk2; + + wk2 = ((int64_t)integerCalibration.bt2 * compensatedTemperature) >> 1; + wk2 = (wk2 * compensatedTemperature) >> 8; + int64_t wk3 = wk2; + + wk2 = ((int64_t)integerCalibration.b11 * compensatedTemperature) >> 4; + wk2 = (wk2 * rawPressure) >> 1; + wk3 += wk2; + + wk2 = ((int64_t)integerCalibration.bp2 * rawPressure) >> 13; + wk2 = (wk2 * rawPressure) >> 1; + wk3 += wk2; + wk1 += wk3 >> 14; + + wk2 = (int64_t)integerCalibration.b12 * compensatedTemperature; + wk2 = (wk2 * compensatedTemperature) >> 22; + wk2 = (wk2 * rawPressure) >> 1; + wk3 = wk2; + + wk2 = ((int64_t)integerCalibration.b21 * compensatedTemperature) >> 6; + wk2 = (wk2 * rawPressure) >> 23; + wk2 = (wk2 * rawPressure) >> 1; + wk3 += wk2; + + wk2 = ((int64_t)integerCalibration.bp3 * rawPressure) >> 12; + wk2 = (wk2 * rawPressure) >> 23; + wk2 = wk2 * rawPressure; + wk3 += wk2; + + wk1 += wk3 >> 15; + wk1 /= 32767; + wk1 >>= 11; + wk1 += integerCalibration.b00; + + return (int32_t)wk1; +} + +bool QMP6988Sensor::readRawMeasurement(int32_t &rawPressure, int32_t &rawTemperature) +{ + uint8_t data[6] = {}; + if (!readBytes(QMP6988_PRESSURE_MSB_REG, data, sizeof(data))) { + return false; + } + + uint32_t pressure = ((uint32_t)data[0] << 16) | ((uint16_t)data[1] << 8) | data[2]; + uint32_t temperature = ((uint32_t)data[3] << 16) | ((uint16_t)data[4] << 8) | data[5]; + + rawPressure = (int32_t)pressure - QMP6988_SUBTRACTOR; + rawTemperature = (int32_t)temperature - QMP6988_SUBTRACTOR; + return true; +} + +bool QMP6988Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) +{ + LOG_INFO("Init sensor: %s", sensorName); + + this->bus = bus; + address = dev->address.address; + + uint8_t chipId = 0; + if (!readByte(QMP6988_CHIP_ID_REG, chipId) || chipId != QMP6988_CHIP_ID) { + LOG_ERROR("%s: chip id mismatch at 0x%x", sensorName, address); + initI2CSensor(); + return status; + } + + if (!reset()) { + LOG_DEBUG("%s: reset failed at 0x%x, continuing without reset", sensorName, address); + } + + if (!readCalibrationData()) { + LOG_ERROR("%s: calibration read failed at 0x%x", sensorName, address); + initI2CSensor(); + return status; + } + + status = true; + if (!status) { + LOG_ERROR("%s: init failed at 0x%x", sensorName, address); + initI2CSensor(); + return status; + } + + setPowerMode(QMP6988_MODE_NORMAL); + setFilter(QMP6988_FILTER_4); + setOversamplingPressure(QMP6988_OVERSAMPLING_8X); + setOversamplingTemperature(QMP6988_OVERSAMPLING_1X); + + initI2CSensor(); + return status; +} + +bool QMP6988Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + int32_t rawPressure = 0; + int32_t rawTemperature = 0; + + if (!readRawMeasurement(rawPressure, rawTemperature)) { + LOG_ERROR("%s: read sample failed", sensorName); + return false; + } + + int16_t compensatedTemperature = convertTemperature(rawTemperature); + int32_t compensatedPressure = convertPressure(rawPressure, compensatedTemperature); + + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.has_barometric_pressure = true; + measurement->variant.environment_metrics.temperature = (float)compensatedTemperature / 256.0f; + measurement->variant.environment_metrics.barometric_pressure = (float)compensatedPressure / 1600.0f; + + LOG_INFO("%s: Got temp:%fdegC pressure:%fhPa", sensorName, measurement->variant.environment_metrics.temperature, + measurement->variant.environment_metrics.barometric_pressure); + + return true; +} + +#endif diff --git a/src/modules/Telemetry/Sensor/QMP6988Sensor.h b/src/modules/Telemetry/Sensor/QMP6988Sensor.h new file mode 100644 index 00000000000..11ad818477f --- /dev/null +++ b/src/modules/Telemetry/Sensor/QMP6988Sensor.h @@ -0,0 +1,67 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#pragma once + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" + +class QMP6988Sensor : public TelemetrySensor +{ + public: + QMP6988Sensor(); + bool getMetrics(meshtastic_Telemetry *measurement) override; + bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; + + private: + struct CalibrationData { + int32_t a0; + int16_t a1; + int16_t a2; + int32_t b00; + int16_t bt1; + int16_t bt2; + int16_t bp1; + int16_t b11; + int16_t bp2; + int16_t b12; + int16_t b21; + int16_t bp3; + }; + + struct IntegerCalibrationData { + int32_t a0; + int32_t b00; + int32_t a1; + int32_t a2; + int64_t bt1; + int64_t bt2; + int64_t bp1; + int64_t b11; + int64_t bp2; + int64_t b12; + int64_t b21; + int64_t bp3; + }; + + bool readBytes(uint8_t reg, uint8_t *buffer, uint8_t length); + bool writeByte(uint8_t reg, uint8_t value); + bool readByte(uint8_t reg, uint8_t &value); + bool reset(); + bool readCalibrationData(); + void setPowerMode(uint8_t powerMode); + void setFilter(uint8_t filter); + void setOversamplingPressure(uint8_t oversampling); + void setOversamplingTemperature(uint8_t oversampling); + int16_t convertTemperature(int32_t rawTemperature) const; + int32_t convertPressure(int32_t rawPressure, int16_t compensatedTemperature) const; + bool readRawMeasurement(int32_t &rawPressure, int32_t &rawTemperature); + + TwoWire *bus{}; + uint8_t address{}; + CalibrationData calibration{}; + IntegerCalibrationData integerCalibration{}; +}; + +#endif