// ============================================================================ // I2Csensors.ino // ---------------------------------------------------------------------------- // Drivers for the I2C environmental sensors supported by this codebase. // All sensors below share ONE physical I2C bus (Wire, PIN_SDA/PIN_SCL, set // per-CLOCK_NN in clocks.h) and can be enabled in ANY combination for a given // clock build - several CLOCK_NN configs enable 3-5 of them at once: // // USE_BME280 - Bosch BME280, temperature + humidity + pressure (Adafruit_BME280_Library) // I2C address 0x76 (hardcoded here; chip also supports 0x77) // USE_BMP280 - Bosch BMP280, temperature + pressure only (Adafruit_BMP280_Library) // I2C address 0x77 (hardcoded here; chip also supports 0x76) // -> BME280 and BMP280 are deliberately pinned to different // default addresses so both can coexist on the same bus. // USE_AHTX0 - AHT10/AHT20, temperature + humidity (Adafruit_AHTX0) // I2C address 0x38 (fixed, not configurable on the chip) // USE_SHT21 - Sensirion SHT21, temperature + humidity (custom SHT21 lib) // I2C address 0x40 (7-bit); see getSHT21()/detection code below // USE_BH1750 - Ambient light / lux meter (BH1750 lib) // I2C address 0x23 (hardcoded; chip also supports 0x5C) // // Which CLOCK_NN configs use which sensor (see clocks.h): // USE_SHT21 alone : CLOCK_23, CLOCK_23A, CLOCK_23B // BME280+BMP280+AHTX0+SHT21 : CLOCK_26 // All five (incl. BH1750) : CLOCK_46, CLOCK_48, CLOCK_50 (and, commented // out / inactive by default, CLOCK_51, 52, 54, // 55, 56, 57, 57A), CLOCK_99 // BME280+AHTX0+SHT21 : CLOCK_80, CLOCK_81, CLOCK_82 // // USE_I2CSENSORS (the #ifdef guarding this whole file) is NOT set directly by // any CLOCK_NN block - it is auto-derived in clocks.h: // #if defined(USE_BME280) || defined(USE_BMP280) || defined(USE_AHTX0) || // defined(USE_SHT21) || defined(USE_BH1750) || defined(USE_RTC) // #define USE_I2CSENSORS // i.e. it turns on automatically as soon as any one sensor (or the RTC) is // requested for the selected clock. // // MULTI-SENSOR INDEXING: temperature[6]/humid[6]/pressur[6] (declared in the // core .ino) are shared result arrays. Each sensor type that measures a given // quantity claims the NEXT FREE slot in that quantity's array at boot, using // three independent counters (useTemp/useHumid/usePress) - see the detailed // comment in setupI2Csensors() below for exactly how collisions are avoided // when several sensors are active simultaneously (e.g. CLOCK_26). // // Calibration offsets (prm.corrT0/corrT1/corrH0/corrH1) are NOT applied in // this file - they are added later, at display time, in displayTemp()/ // displayHumid() in the core .ino. This file only stores raw (rounded) // readings, so there is no risk of double-applying the correction here. // ============================================================================ #ifdef USE_I2CSENSORS // NOTE: these are commented-out and inert by design - the real per-build // enabling of each sensor happens in clocks.h inside the relevant CLOCK_NN // block. Since this .ino is compiled into every build regardless of which // clock is selected, uncommenting one of these lines here would force that // sensor ON for EVERY clock (as long as USE_I2CSENSORS ends up defined by // some other means), not just the one you're working on. Leave commented; // they exist only as a quick reference of the five supported macros. //#define USE_BME280 //#define USE_BMP280 //#define USE_AHTX0 //#define USE_SHT21 //#define USE_BH1750 #include #include #ifdef USE_BME280 #include Adafruit_BME280 bme; // use I2C interface address = 0x76 or 0x77 Adafruit_Sensor *bme_temp = bme.getTemperatureSensor(); Adafruit_Sensor *bme_pressure = bme.getPressureSensor(); Adafruit_Sensor *bme_humidity = bme.getHumiditySensor(); // *Ptr variables below hold this sensor's assigned slot (index) in the // shared temperature[]/humid[]/pressur[] arrays. They are filled in once, // in setupI2Csensors(), and stay 0 (default global init) if the sensor // isn't found - harmless, since the getBME280() reader is only ever // called when BME280exist is true. byte BME280tempPtr; byte BME280humidPtr; byte BME280pressPtr; #endif #ifdef USE_BMP280 #include // Hardcoded to 0x77 (BME280 above is hardcoded to 0x76) so that both chips // can be wired to the same bus at once without an address clash - several // CLOCK_NN configs (e.g. CLOCK_26, CLOCK_46/48/50, CLOCK_99) enable both // USE_BME280 and USE_BMP280 simultaneously. Adafruit_BMP280 bmp; // use I2C interface address = 0x76 or 0x77 Adafruit_Sensor *bmp_temp = bmp.getTemperatureSensor(); Adafruit_Sensor *bmp_pressure = bmp.getPressureSensor(); byte BMP280tempPtr; byte BMP280humidPtr; // NOTE: BMP280 has no humidity sensor - this pointer is declared for symmetry but never used/assigned (BMP280 never touches humid[]). byte BMP280pressPtr; #endif #ifdef USE_AHTX0 #include //I2C address: 0x38 Adafruit_AHTX0 aht; Adafruit_Sensor *aht_humidity, *aht_temp; byte AHTX0tempPtr; byte AHTX0humidPtr; #endif #ifdef USE_SHT21 // POSSIBLE BUG (see detection code in setupI2Csensors() below): this header // comment says the 8-bit write/read addresses are 0x80/0x81, which // correspond to 7-bit address 0x40 (the SHT21's actual, standard I2C // address). The Arduino Wire library used here takes a 7-bit address // (0-127), so 0x80 is out of range for Wire.beginTransmission() - see the // detection code further down which probes both 0x40 (correct) and 0x80 // (likely meaningless/dead check for this Wire API). #include //I2C address: 0x80 and 0x81 SHT21 sht; float SHT21temp,SHT21humid =0; byte SHT21tempPtr; byte SHT21humidPtr; #endif #ifdef USE_BH1750 #include BH1750 lightMeter(0x23); // fixed address 0x23 (ADDR pin low/floating); chip's alternate address is 0x5C #endif void setupI2Csensors() { DPRINTLN("Starting I2Csensors sensors..."); // pinMode(OUTPUT) here is just for regPin() bookkeeping (pin-conflict // registration/debug); Wire.begin() below reconfigures both pins for I2C // (open-drain + pull-ups) regardless of this pinMode call. pinMode(PIN_SDA,OUTPUT); regPin(PIN_SDA,"PIN_SDA"); pinMode(PIN_SCL,OUTPUT); regPin(PIN_SCL,"PIN_SCL"); Wire.begin(PIN_SDA,PIN_SCL); delay(100); I2Cscanner(); // debug-only bus scan; logs every ACKing address, does not affect detection below #ifdef USE_RTC DPRINTLN("Starting RTC Clock..."); while(millis()<2000) {yield();} //waiting for 2 sec from startup //I2C_ClearBus(); delay(100); #if defined(PIN_SDA) && defined(PIN_SCL) Wire.beginTransmission(0x68); byte error = Wire.endTransmission(); if (error == 0) { //DS3231_get_addr(0x68) RTCexist = true; DPRINTLN("RTC found on 0x68."); } else { DPRINTLN("!!!No RTC found on 0x68!!!"); } #else RTCexist = false; DPRINTLN("PIN_SDA or PIN_SCL not defined!"); #endif #endif // --------------------------------------------------------------------- // MULTI-SENSOR INDEXING SCHEME (applies to every #ifdef block below): // temperature[]/humid[]/pressur[] are shared 6-slot arrays that can hold // readings from several sensors at once. There are THREE INDEPENDENT // counters - useTemp, useHumid, usePress - one per array/quantity, each // incremented only by sensors that actually measure that quantity. // Each sensor, when found, "reserves" the array slot equal to the CURRENT // value of the relevant counter (its own xxxPtr), then increments // that counter so the NEXT detected sensor of that quantity gets the next // free slot. Because each array has its own counter, two sensors can // never collide/overwrite each other's slot in the SAME array, even when // several are active together (e.g. CLOCK_26: BME280+BMP280+AHTX0+SHT21): // temperature[]: BME280->0, BMP280->1, AHTX0->2, SHT21->3 (useTemp=4) // humid[] : BME280->0, AHTX0->1, SHT21->2 (useHumid=3, BMP280 has none) // pressur[] : BME280->0, BMP280->1 (usePress=2, AHTX0/SHT21 have none) // Detection order below (BME280, BMP280, AHTX0, SHT21, BH1750) therefore // determines slot order. Downstream, only slots 0 and 1 of temperature[]/ // humid[] receive a manual calibration offset (prm.corrT0/corrT1/corrH0/ // corrH1, applied in displayTemp()/displayHumid() in the core .ino) - a // 3rd+ simultaneous temperature/humidity sensor (e.g. SHT21 at slot 2/3 // above) is displayed uncorrected. That is a limitation of the core // display code, not of the indexing here, but worth knowing when wiring // up configs with 3+ temp/humid sensors. // --------------------------------------------------------------------- #ifdef USE_BME280 if (!bme.begin(0x76)) { DPRINTLN("Could not find a valid BME280 sensor on address 0x76, check wiring!"); } else { BME280exist = true; DPRINTLN("BME280 Found!"); bme_temp->printSensorDetails(); bme_pressure->printSensorDetails(); bme_humidity->printSensorDetails(); BME280tempPtr = useTemp; //remember my ID-s BME280humidPtr = useHumid; BME280pressPtr = usePress; useTemp++; //increase the number of sensors useHumid++; usePress++; } #endif #ifdef USE_BMP280 if (!bmp.begin(0x77)) { DPRINTLN("Could not find a valid BMP280 sensor on address 0x77, check wiring!"); } else { BMP280exist = true; /* BMP280 default settings from datasheet. */ DPRINTLN("BMP280 Found!"); bmp.setSampling(Adafruit_BMP280::MODE_NORMAL, /* Operating Mode. */ Adafruit_BMP280::SAMPLING_X2, /* Temp. oversampling */ Adafruit_BMP280::SAMPLING_X16, /* Pressure oversampling */ Adafruit_BMP280::FILTER_X16, /* Filtering. */ Adafruit_BMP280::STANDBY_MS_500); /* Standby time. */ bmp_temp->printSensorDetails(); BMP280tempPtr = useTemp; //remember my ID-s BMP280pressPtr = usePress; //remember my ID-s useTemp++; //increase the number of sensors usePress++; //increase the number of sensors } #endif #ifdef USE_AHTX0 if (!aht.begin()) { DPRINTLN("Could not find a valid AHT10/AHT20 sensor, check wiring!"); } else { AHTX0exist = true; DPRINTLN("AHT10/AHT20 Found!"); aht_temp = aht.getTemperatureSensor(); aht_temp->printSensorDetails(); aht_humidity = aht.getHumiditySensor(); aht_humidity->printSensorDetails(); AHTX0tempPtr = useTemp; //remember my ID-s AHTX0humidPtr = useHumid; useTemp++; //increase the number of sensors useHumid++; } #endif DPRINTLN(" "); #ifdef USE_SHT21 int error1,error2; Wire.beginTransmission(0x40); // correct 7-bit SHT21 address error1 = Wire.endTransmission(); // POSSIBLE BUG: 0x80 is not a valid 7-bit I2C address for Wire.beginTransmission() // (Wire expects 0-127; 0x80/128 is the 8-bit write address, i.e. 0x40<<1). // This second probe is likely a no-op/dead check left over from confusing // 7-bit vs 8-bit addressing (see comment above the #include line). // Harmless as written because of the "&&" below (SHT21exist still gets set // correctly whenever the real 0x40 probe succeeds), but error2 does not // reliably indicate anything about a real device. Wire.beginTransmission(0x80); error2 = Wire.endTransmission(); if ((error1 !=0) && (error2!=0)) { DPRINTLN("Could not find a valid SHT21 sensor on 0x40 / 0x80, check wiring!"); } else { SHT21exist = true; DPRINTLN("SHT21 Found!"); SHT21tempPtr = useTemp; //remember my ID-s SHT21humidPtr = useHumid; useTemp++; //increase the number of sensors useHumid++; } #endif #ifdef USE_BH1750 // BH1750 doesn't share temperature[]/humid[]/pressur[] - it has its own // dedicated useLux counter/flow (see getBH1750() at the bottom of this // file, called from getLightSensor() in the core .ino), so no slot/Ptr // bookkeeping is needed here. if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) { DPRINTLN("BH1750 luxmeter sensor found"); BH1750exist = true; useLux++; } else { DPRINTLN("Error initialising BH1750 on address 0x23"); } #endif DPRINTLN(" "); } //end of SetupI2Csensors() // getBME280()/getBMP280()/getAHTX0()/getSHT21() below each write ONLY into // their own reserved slot (the *Ptr index assigned in setupI2Csensors(), see // the indexing scheme comment there) and store the RAW rounded reading - no // calibration offset (prm.corrT0/corrT1/corrH0/corrH1) is applied here; that // happens later, at display time, in displayTemp()/displayHumid() (core // .ino), so there's no double-correction between this file and that one. void getBME280() { #ifdef USE_BME280 temperature[BME280tempPtr] = round1(bme.readTemperature()); humid[BME280humidPtr] = round1(bme.readHumidity()); pressur[BME280pressPtr] = round1(bme.readPressure()/100); DPRINT("BME280: "); DPRINT("Temperature = "); DPRINT(temperature[BME280tempPtr]); DPRINT(" *C"); DPRINT(" Humidity = "); DPRINT(humid[BME280humidPtr]); DPRINT(" %"); DPRINT(" Pressure = "); DPRINT(pressur[BME280pressPtr]); DPRINTLN(" hPa"); #endif } void getBMP280() { #ifdef USE_BMP280 temperature[BMP280tempPtr] = round1(bmp.readTemperature()); pressur[BMP280pressPtr] = round1(bmp.readPressure()/100); DPRINT("BMP280: "); DPRINT(F("Temperature = ")); DPRINT(temperature[BMP280tempPtr]); DPRINT(" *C"); DPRINT(F(" Pressure = ")); DPRINT(pressur[BMP280pressPtr]); DPRINTLN(" hPa"); #endif } void getAHTX0() { #ifdef USE_AHTX0 // /* Get a new normalized sensor event */ sensors_event_t humidity; sensors_event_t temp; aht_humidity->getEvent(&humidity); aht_temp->getEvent(&temp); temperature[AHTX0tempPtr] = round1(temp.temperature); DPRINT("AHT sensor: "); DPRINT(" Temperature "); DPRINT(temperature[AHTX0tempPtr]); DPRINT(" deg C"); DPRINT(" Humidity: "); /* Display the results (humidity is measured in % relative humidity (% rH) */ humid[AHTX0humidPtr] = round1(humidity.relative_humidity); DPRINT(humid[AHTX0humidPtr]); DPRINTLN(" % rH"); #endif } void getSHT21() { #ifdef USE_SHT21 temperature[SHT21tempPtr] = round1(sht.getTemperature()); DPRINT("SHT21 sensor: "); DPRINT(" Temperature "); DPRINT(temperature[SHT21tempPtr]); DPRINT(" deg C"); DPRINT(" Humidity: "); /* Display the results (humidity is measured in % relative humidity (% rH) */ humid[SHT21humidPtr] = round1(sht.getHumidity()); DPRINT(humid[SHT21humidPtr]); DPRINTLN(" % rH"); #endif } void getI2Csensors() { static unsigned long lastRun = 0; // Called every ~200ms from timeProgram() (core .ino), but the actual I2C // reads only happen once every 30s per sensor - this throttle avoids // hammering the bus/sensors and is independent of the caller's cadence. // lastRun==0 lets the very first call (at millis()==0..~30000) through // immediately so readings are available right after boot. if (((millis()-lastRun)<30000) && (lastRun !=0)) return; // || (second()!=TEMP_START) lastRun = millis(); if (BME280exist) getBME280(); if (BMP280exist) getBMP280(); if (AHTX0exist) getAHTX0(); if (SHT21exist) getSHT21(); if (BH1750exist) getBH1750(); } void I2Cscanner() { byte error, address; int nDevices=0; DPRINTLN(F("________________________________")); DPRINTLN("Scanning I2C devices..."); for(address = 1; address < 127; address++ ) { // The i2c_scanner uses the return value of // Wire.endTransmission() to see if // a device acknowledged the address. Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { DPRINT("I2C device found at address 0x"); if (address<16) DPRINT("0"); DPRINT(address,HEX); DPRINTLN(" !"); nDevices++; } else if (error==4) { DPRINT("Unknown error at address 0x"); if (address<16) DPRINT("0"); DPRINTLN(address,HEX); } } //end for if (nDevices == 0) DPRINTLN("No I2C devices found\n"); DPRINTLN(F("_________________________________")); } #else void setupI2Csensors() {} void getI2Csensors() {} #endif #ifdef USE_BH1750 // Called directly from getLightSensor() (core .ino) whenever BH1750exist is // true - unlike the other get*() functions this is NOT gated through // getI2Csensors()'s 30s throttle, so it can be polled as often as needed; // the BH1750 library itself paces new measurements (measurementReady()). int getBH1750() { static float oldLux = MAXIMUM_LUX; if (lightMeter.measurementReady()) { float lux = lightMeter.readLightLevel(); //DPRINT("BH1750 Light: "); DPRINT(lux); DPRINTLN(" lx"); if (lux>MAXIMUM_LUX) lux = MAXIMUM_LUX; //Limited //Limit lux value to maximum if (lux<0) lux = oldLux; // negative reading = bus glitch/error from the library; ignore and keep last good value oldLux = oldLux + (lux-oldLux)/10; //slow down Lux change (simple exponential smoothing, ~10-sample time constant) } return (int)oldLux; } #else // Stub used when USE_BH1750 is not defined for the selected clock, so // getLightSensor() (core .ino) can call getBH1750() unconditionally. int getBH1750() { return(0); } #endif