diff --git a/platformio.ini b/platformio.ini index e78124a40b..bb08e5f223 100644 --- a/platformio.ini +++ b/platformio.ini @@ -138,6 +138,7 @@ build_flags = -D ENV_INCLUDE_VL53L0X=1 -D ENV_INCLUDE_BME680=1 -D ENV_INCLUDE_BMP085=1 + -D ENV_INCLUDE_OPT3001=1 lib_deps = adafruit/Adafruit INA3221 Library @ ^1.0.1 adafruit/Adafruit INA219 @ ^1.2.3 diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index e2f0d33e72..b2f7ce5804 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -160,6 +160,14 @@ static Adafruit_VL53L0X VL53L0X; static RAK12035_SoilMoisture RAK12035; #endif +#if ENV_INCLUDE_OPT3001 +#ifndef TELEM_OPT3001_ADDRESS +#define TELEM_OPT3001_ADDRESS 0x44 // OPT3001 ambient light sensor (RAK1903) I2C address +#endif +#include "OPT3001.h" +static OPT3001 OPT3001; +#endif + #if ENV_INCLUDE_GPS && defined(RAK_BOARD) && !defined(RAK_WISMESH_TAG) #define RAK_WISBLOCK_GPS #endif @@ -401,6 +409,7 @@ static void query_ina260(uint8_t ch, uint8_t, CayenneLPP& lpp) { static uint8_t init_ina226(TwoWire*, uint8_t) { // INA226 static instance was constructed with address and wire. if (!INA226.begin()) return 0; + if (INA226.getManufacturerID() != 0x5449 || INA226.getDieID() != 0x2260) return 0; INA226.setMaxCurrentShunt(TELEM_INA226_MAX_AMP, TELEM_INA226_SHUNT_VALUE); return 1; } @@ -531,6 +540,24 @@ static void query_bme680_bsec(uint8_t ch, uint8_t, CayenneLPP& lpp) { } #endif +#if ENV_INCLUDE_OPT3001 +static uint8_t init_opt3001(TwoWire* wire, uint8_t addr) { + // probe() is read-only. 0x44 may also be an SHT4X or INA226, + // so chip is identified by manufacturer/device ID before any + // config is written. + if(!OPT3001.probe(wire, addr)) return 0; + return OPT3001.begin() ? 1 : 0; +} + +static void query_opt3001(uint8_t ch, uint8_t, CayenneLPP& lpp) { + float lux; + if (OPT3001.readLux(lux)) { + lpp.addLuminosity(ch, lux); + } +} + +#endif + // ============================================================ // Sensor descriptor table // @@ -599,6 +626,9 @@ static const SensorDef SENSOR_TABLE[] = { #endif #if ENV_INCLUDE_RAK12035 { TELEM_RAK12035_ADDRESS,"RAK12035", init_rak12035, query_rak12035 }, +#endif +#if ENV_INCLUDE_OPT3001 + { TELEM_OPT3001_ADDRESS,"OPT3001", init_opt3001, query_opt3001 }, #endif { 0, nullptr, nullptr, nullptr } // sentinel — keeps the array non-empty }; diff --git a/src/helpers/sensors/OPT3001.cpp b/src/helpers/sensors/OPT3001.cpp new file mode 100644 index 0000000000..9db94bac18 --- /dev/null +++ b/src/helpers/sensors/OPT3001.cpp @@ -0,0 +1,56 @@ +#include "OPT3001.h" + +bool OPT3001::readReg(uint8_t reg, uint16_t& value) { + _wire->beginTransmission(_addr); + _wire->write(reg); + if (_wire->endTransmission() != 0) return false; + + if (_wire->requestFrom(_addr, (uint8_t)2) != 2) return false; + uint16_t msb = _wire->read(); // sequence the two reads + uint16_t lsb = _wire->read(); + value = (msb << 8) | lsb; + return true; +} + +bool OPT3001::writeReg(uint8_t reg, uint16_t value) { + _wire->beginTransmission(_addr); + _wire->write(reg); + _wire->write((uint8_t)(value >> 8)); + _wire->write((uint8_t)(value & 0xFF)); + return _wire->endTransmission() == 0; +} + +bool OPT3001::probe(TwoWire* wire, uint8_t addr) { + _wire = wire; + _addr = addr; + + uint16_t id; + if (!readReg(OPT3001_REG_MANUFACTURER_ID, id) || id != OPT3001_MANUFACTURER_ID) return false; + if (!readReg(OPT3001_REG_DEVICE_ID, id) || id != OPT3001_DEVICE_ID) return false; + return true; +} + +bool OPT3001::begin() { + _ready = false; + return writeReg(OPT3001_REG_CONFIG, OPT3001_CONFIG_CONTINUOUS); +} + +bool OPT3001::readLux(float& lux) { + if (!_ready) { + // Reading the latched config register clears CRF, however + // in continuous mode it is set again after every conversion. + // We only need to gate the very first read after begin(). + uint16_t config; + if (!readReg(OPT3001_REG_CONFIG, config)) return false; + if ((config & OPT3001_CONFIG_CRF) == 0) return false; + _ready = true; + } + + uint16_t raw; + if (!readReg(OPT3001_REG_RESULT, raw)) return false; + + // Result format: exponent E in [15:12], mantissa R in [11:0] + // lux = 0.01 * 2^E * R (full-scale 83865.6 lux) + lux = 0.01f * (float)(1u << (raw >> 12)) * (float)(raw & 0x0FFF); + return true; +} \ No newline at end of file diff --git a/src/helpers/sensors/OPT3001.h b/src/helpers/sensors/OPT3001.h new file mode 100644 index 0000000000..b6ae7cc78a --- /dev/null +++ b/src/helpers/sensors/OPT3001.h @@ -0,0 +1,52 @@ +/** + * @file OPT3001.h + * @author Nick Dunklee + * @brief Minimal driver for the TI OPT3001 ambient light sensor + * (used on the RAK1903 WisBlock module). + * + * Written for MeshCore from the TI OPT3001 datasheet (SBOS681). + * https://www.ti.com/lit/ds/sbos681b/sbos681b.pdf + * + * The default address of 0x44 is shared with other sensors (SHT4x, + * INA226), so probe() performs a read-only identity check and no + * register is written until the chip is positively ID'd. +*/ + +#pragma once + +#include +#include + +#define OPT3001_REG_RESULT 0x00 +#define OPT3001_REG_CONFIG 0x01 +#define OPT3001_REG_MANUFACTURER_ID 0x7E +#define OPT3001_REG_DEVICE_ID 0x7F + +#define OPT3001_MANUFACTURER_ID 0x5449 // TI +#define OPT3001_DEVICE_ID 0x3001 + +// Automatic full-scale range, 800ms conversions, continuous mode, latched flags +#define OPT3001_CONFIG_CONTINUOUS 0xCE10 +#define OPT3001_CONFIG_CRF 0x0080 // conversion ready flag + +class OPT3001 { + TwoWire* _wire; + uint8_t _addr; + bool _ready; // at least one conversion has completed since begin() + + bool readReg(uint8_t reg, uint16_t& value); + bool writeReg(uint8_t reg, uint16_t value); + +public: + OPT3001() : _wire(NULL), _addr(0), _ready(false) { } + + // Read-only identity check (manufacturer + device ID). Safe to call + // on a foreign device that happens to share the I2C address. + bool probe(TwoWire* wire, uint8_t addr); + + // Start continuous conversions. Call only after probe() succeeds. + bool begin(); + + // Return false until the first conversion completes. + bool readLux(float& lux); +}; \ No newline at end of file diff --git a/variants/lilygo_tdeck/platformio.ini b/variants/lilygo_tdeck/platformio.ini index 00571db93b..4c6cc1d917 100644 --- a/variants/lilygo_tdeck/platformio.ini +++ b/variants/lilygo_tdeck/platformio.ini @@ -35,6 +35,7 @@ build_flags = -D ENV_INCLUDE_VL53L0X=0 -D ENV_INCLUDE_BME680=0 -D ENV_INCLUDE_BMP085=0 + -D ENV_INCLUDE_OPT3001=0 -D P_LORA_NSS=9 ; LORA SS pin -D P_LORA_RESET=17 ; LORA RST pin -D P_LORA_BUSY=13 ; LORA Busy pin