Hi all,
I have a weird issue. I posted before but now it looks different; more info is available. I would once again very much like your help, if anyone has any ideas. Intermittent problems are always tough.
I have a Nano-based device that has an RTC, a temp/humidity/pressure sensor, and a Sensirion SCD90 CO2 sensor, all connected via I2C. I have used most of these (and I2C, and the nano) many times before.
This one works great but it does something weird: It would hang randomly every few days - just stop. It was not a power problem, and all other obvious things have, I think, been excluded.
So I added a watchdog timer, to reboot it if it hangs. That worked. But that was not really a solution, just a fix. So as the next step in my troubleshooting I added a timeout to the Wire (I2C) use:
Wire.setWireTimeout(3000 /* us /, false / reset_on_timeout */);
But now it does REALLY weird stuff. Instead of hanging up, every day it randomly butchers time, temperature and air pressure. Almost every display value is random and entirely nonsensical: just now I got home to see 93ºC, barometric pressure of 1980 hPa, and real time at 49:79. I think CO2 level was wrong too... basically all I2C values were rubbish. And the system did not self-correct. The yellow LED for over-and under-temperature and humidity worked fine, as did the pressure change indicator ("+934"), so it was definitely the actual values, not the displays that were wrong.
ANY idea what is happening here? I have never seen this kind of problem (and I have made quite a few I2C-connected sensor-based devices).
Code below. And attached, the manual (always nice to see what the box looks like etc).
EDIT: To be clear, this manual contains description, background, circuit diagram, photos of construction, etc.
CO2meter-box.pdf (7.8 MB)
The circuit diagram has the wrong name for the sensor on the very left; it is in fact the Sensirion CO2 sensor).
/*-------------------------------------------------------------------------------
New Environment Monitor
--------------------------------------------------------------------------------
- Three 7-segment LED displays
- GY68 Air pressure/temp meter
- DS3231 Real-Time Clock
- SCD30 True CO2 (&c) meter
Code by Michael Willems - michael@willems.ca
Version 1.03
2023-03-11
Can be done later:
- PERHAPS AVOID DELAY AFTER SWITCH CHANGES?
---------------------------------------------------------------------------------*/
// -----------------------------------------------------------------------------------------
// INCLUDES AND DECLARATIONS:
// -----------------------------------------------------------------------------------------
#include <Wire.h> // for I2C communication. A4=SDA; A5=SCL on Nano
#include <DS3231.h> // for real-time clock
#include <Adafruit_BMP085.h> // for air pressure/temp meter
#include <Adafruit_SCD30.h> // for CO2 detector
#include <avr/wdt.h> // for watchdog timer xxx BUGGY ON CLONE NANO
#include <math.h> // for the round() function...
//
Adafruit_BMP085 bmp; // Air P&T
Adafruit_SCD30 scd30; // CO2 (etc) detector
//
int temperature;
int humidity;
int barometer;
int CO2level;
int oldtemperature = 0;
int oldhumidity = 0;
int oldbarometer = 0;
int oldCO2level = 0;
//
/* Code for TM1637 4 digit 7 segment display: */
#include <TM1637Display.h>
#define CLK 2 // Define the connections pins for display 1
#define DIO 3
#define CLK2 4 // Define the connections pins for display 2
#define DIO2 5
#define CLK3 6 // Define the connections pins for display 3
#define DIO3 7
//
#define buzzer 13
//
/////////////////////////////
// DEVICE TUNING SETTINGS //
// ADJUST THESE AS NEEDED: //
/////////////////////////////
#define CO2marginal 700 // when green LED starts flashing
#define CO2yellow 1000 // when yellow LED lights
#define CO2red 1500 // when red LED lights
#define CO2alarm 3000 // when red LED flashes
#define CO2urgentAlarm 5000 // when beeper briefly beeps once a second
#define tempOffset -2 // offset to make thermometer accurate
#define humOffset 3 // offset to make humidity accurate
#define QFEoffset 5 // offset to make air pressure accurate
#define mintemp 19 // below this, top right-side yellow LED lights
#define maxtemp 25 // above this, top right-side yellow LED lights
#define minhum 30 // below this, bottom right-side yellow LED lights
#define maxhum 50 // above this, bottom right-side yellow LED lights
////////////////////////////
//
// Create three display objects of type TM1637Display:
TM1637Display display = TM1637Display(CLK, DIO);
TM1637Display display2 = TM1637Display(CLK2, DIO2);
TM1637Display display3 = TM1637Display(CLK3, DIO3);
//
// Now create arrays and name them:
// Create array that turns all segments on:
const uint8_t data[] = {0xff, 0xff, 0xff, 0xff};
// Create array that turns all segments off:
const uint8_t blank[] = {0x00, 0x00, 0x00, 0x00};
// Create other symbols:
const uint8_t done[] = {
SEG_B | SEG_C | SEG_D | SEG_E | SEG_G, // d
SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, // O
SEG_C | SEG_E | SEG_G, // n
SEG_A | SEG_D | SEG_E | SEG_F | SEG_G // E
};
const uint8_t hPA[] = {
0x00, //
SEG_C | SEG_E | SEG_F | SEG_G, // h
SEG_A | SEG_B | SEG_E | SEG_F | SEG_G, // P
SEG_A | SEG_B | SEG_C | SEG_E | SEG_F | SEG_G // A
};
const uint8_t nd[] = {
SEG_C | SEG_E | SEG_G, // n
SEG_B | SEG_C | SEG_D | SEG_E | SEG_G, // d
0x00, //
0x00 //
};
const uint8_t up[] = {
SEG_C | SEG_D | SEG_E, // u
SEG_A | SEG_B | SEG_E | SEG_F | SEG_G, // P
0x00, //
0x00 //
};
const uint8_t dn[] = {
SEG_B | SEG_C | SEG_D | SEG_E | SEG_G, // d
SEG_C | SEG_E | SEG_G, // n
0x00, //
0x00 //
};
const uint8_t rh[] = {
0x00, //
0x00, //
SEG_E | SEG_G, // r
SEG_C | SEG_E | SEG_F | SEG_G // h
};
const uint8_t dC[] = {
0x00, //
0x00, //
SEG_A | SEG_B | SEG_F | SEG_G, // [deg]
SEG_A | SEG_D | SEG_E | SEG_F // C
};
const uint8_t CO2[] = {
0x00, //
SEG_A | SEG_D | SEG_E | SEG_F, // C
SEG_A | SEG_B | SEG_C | SEG_D | SEG_E | SEG_F, // O
SEG_A | SEG_B | SEG_D | SEG_E | SEG_G, // 2
};
//
// Misc variables etc:
unsigned long counter; // for flashing on-board LED 13 with 1s frequency
unsigned long fastcounter; // for flashing green with fast frequency
unsigned long measurementcounter; // for 3s interval between taking measurements
unsigned long halfhourcounter; // for the "once every half hour" stuff
unsigned long cyclecounter = 0; // to see how often the loop does its thing (DEBUG, UNCOMMENT PRINTLN)
//int onboardled = 9; // for optional heartbeat LED
int greenled = A0;
int yellowled = A1;
int redled = A2;
int sideled1 = A3;
int sideled2 = 8;
int QFEswitch = 10;
int pushbutton = 11; // for clock set
int nightswitch = 12; // for displaying time INSTEAD OF OTHER DATA AT NIGHT
boolean switchstate = false;
boolean oldswitchstate = true;
boolean switch2state = false;
boolean oldswitch2state = false;
boolean ledstate = false; // for clock set
long buttonTimer = 0; // to time how long we have pressed the button
long longPressTime = 1000; // how long to push button before clock enters SET mode?
long exitSetupTime = 3000; // after clock set, how long until we exit set mode?
boolean pushbuttonActive = false;
boolean longPressActive = false;
long baromem[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15} ; // for tracking 8h barometer trend
float barodiff1h = 0;
float barodiff3h = 0;
long uptime = 0;
//clock stuff:
DS3231 Clock;
boolean Century=false;
boolean h12;
boolean PM;
byte ADay, AHour, AMinute, ASecond, ABits;
boolean ADy, A12h, Apm;
char *dayString[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
int second,minute,hour,date,month,year,val,dow,temp3;
// -----------------------------------------------------------------------------------------
// THE SETUP (RUNS ONCE):
// -----------------------------------------------------------------------------------------
void setup() {
// pinMode(onboardled, OUTPUT); //the optional heartbeat LED
pinMode(greenled, OUTPUT);
pinMode(yellowled, OUTPUT);
pinMode(redled, OUTPUT);
pinMode(sideled1, OUTPUT);
pinMode(sideled2, OUTPUT);
pinMode(pushbutton, INPUT_PULLUP);
pinMode(nightswitch, INPUT_PULLUP);
pinMode(QFEswitch, INPUT_PULLUP);
Serial.begin(9600);
Wire.begin(); // Initiate the Wire library
Wire.setWireTimeout(3000 /* us */, false /* reset_on_timeout */);
delay(10);
counter = millis();
measurementcounter = millis();
halfhourcounter = millis();
cyclecounter = 0; //for testing loops/second
//
// now set up, and start, the air pressure detector:
if (!bmp.begin()) {
Serial.println(F("Could not find a BMP AIR PRESSURE sensor, check wiring!"));
digitalWrite (yellowled, HIGH);
while (1) {} // stop here, since sensor not found
}
Serial.println(F("BMP Sensor Found!"));
//
// Now initialize SCD30:
if (!scd30.begin()) {
Serial.println(F("Failed to find SCD30 chip"));
digitalWrite (redled, HIGH);
while (1) { delay(10); } // stop here, since sensor not found
}
Serial.println(F("SCD30 Found!"));
//
// Set the brightness:
display.setBrightness(6);
display2.setBrightness(6);
display3.setBrightness(6);
// All segments on:
display.setSegments(data);
display2.setSegments(data);
display3.setSegments(data);
digitalWrite (greenled, HIGH);
digitalWrite (yellowled, HIGH);
digitalWrite (redled, HIGH);
digitalWrite (sideled1, HIGH);
digitalWrite (sideled2, HIGH);
// clear and display the units (CO2, degrees, rel.hum.)
display.clear();
display2.clear();
display3.clear();
display.setSegments(CO2);
display2.setSegments(dC);
display3.setSegments(rh);
delay(2000);
display.clear();
display2.clear();
display3.clear();
// now we set the barometer memories all the same to start:
baromem[0] = (bmp.readPressure())+QFEoffset;
for (int mem = 1; mem<16; mem++){
baromem[mem] = (baromem[0]);
}
delay(500);
digitalWrite (greenled, LOW);
digitalWrite (yellowled, LOW);
digitalWrite (redled, LOW);
digitalWrite (sideled1, LOW);
digitalWrite (sideled2, LOW);
}
// -----------------------------------------------------------------------------------------
// THE LOOP:
// -----------------------------------------------------------------------------------------
void loop() {
//start the safety watchdog:
wdt_enable(WDTO_4S); //Enable WDT with a timeout of 4 seconds (for genuine arduino nano)
//
// Now we do the once-per-second stuff:
if ((millis() - counter) > 1000) {
counter = millis();
// digitalWrite (onboardled,!(digitalRead(onboardled))); //the on-board heartbeat LED
if (CO2level > CO2alarm) { // high CO2 beep alert
tone(buzzer, 1000, 10);
}
// check how long we have been up (8 hours is the maximum):
uptime = (int(millis()/3600000));
if (uptime > 8) {
uptime = 8;
}
// Serial.println(cyclecounter); //debugging: to see how many times we do the loop. each second
cyclecounter = 0;
} //ok, end of once-per-second stuff.
// Now we do the QUICK FLASH stuff:
if ((millis() - fastcounter) > 500) {
fastcounter = millis();
if ((CO2level > CO2marginal) && (CO2level < CO2yellow)) { // green fast flash
digitalWrite (greenled,!(digitalRead(greenled))); // the green LED
}
if (CO2level > CO2urgentAlarm) { // RED fast flash
digitalWrite (redled,!(digitalRead(redled))); // the red LED
}
} //ok, end of fast flash stuff.
//
// now the once every 3 seconds stuff (measurement interval):
//
if ((millis() - measurementcounter) > 3000) {
measurementcounter = millis();
temperature = ((bmp.readTemperature()) + (tempOffset)); //read temp from the chip & adjust
barometer = ((bmp.readPressure()/100) + (QFEoffset)); //read pressure from the chip & adjust
if (scd30.dataReady()){
if (!scd30.read()){ Serial.println(F("Error reading sensor data")); return; }
humidity = (scd30.relative_humidity + humOffset);
CO2level = int(scd30.CO2);
if (CO2level > 9999) { CO2level = 9999; }
} else { Serial.println(F("No data")); }
// now display the values (+re-display if they have changed):
if (digitalRead(nightswitch) == LOW) { // switch active, so go to night display
switchstate = true;
DisplayNightValues();
}
else { // switch not active, so normal day display please
switchstate = false;
DisplayValues();
}
} // ok, end of the once every three second stuff.
//
// Now the once per half hour stuff:
//
if (millis() - halfhourcounter > 1800000) {
//
// every half hour, update the 16 last half-hourly readings:
halfhourcounter = millis();
for (int i = 15; i>0; i--) {
baromem[i] = baromem[i-1];
}
baromem[0] = (bmp.readPressure());
//
// now here we must calculate if it's dropping or climbing in 1, 3 and 8 hours, and how fast:
barodiff1h = ((baromem[0] - baromem[2]) / 100);
barodiff3h = ((baromem[0] - baromem[6]) / 100);
// (more logic here later)
/* this section for later use:
//
// Display "rising now" or "falling now" line:
//
if (barodiff1h > 50) {
display3.setSegments(up);
}
else if (barodiff1h > 50) {
display3.setSegments(dn);
}
//
// Display the 3 hour trend now:
//
if (barodiff3h > 600) {
//bad high
}
else if (barodiff3h > 350) {
//high
}
else if (barodiff3h > 150) {
//rising
}
else if (barodiff3h < -600) {
//bad low, >600
}
else if (barodiff3h < -350) {
//low, 350-600
}
else if (barodiff3h < -150) {
//falling, 150-350
}
else {
//must be green led, 0-150
}
*/
} //end of once-per-half-hour stuff
//
// Now, any code that has to continously run (not just once a second):
//
//the "long button press detect" stuff...
if (digitalRead(pushbutton) == LOW) {
if (pushbuttonActive == false) {
pushbuttonActive = true;
buttonTimer = millis();
}
if ((millis() - buttonTimer > longPressTime) && (longPressActive == false)) {
longPressActive = true;
ledstate = !ledstate;
// digitalWrite (blueled, ledstate);
setclock(); //the set clock mode
}
} else {
if (pushbuttonActive == true) {
if (longPressActive == true) {
longPressActive = false;
}
}
pushbuttonActive = false;
}
cyclecounter = cyclecounter +1; //to facilitate counting the cycles/second for testing
//reset watchdog:
wdt_reset();
} //end of loop
// -----------------------------------------------------------------------------------------
// THE FUNCTIONS:
// -----------------------------------------------------------------------------------------
//------------------------------
// Read and display time:
//------------------------------
void ReadDS3231() {
second=Clock.getSecond();
minute=Clock.getMinute();
hour=Clock.getHour(h12,PM);
date=Clock.getDate();
month=Clock.getMonth(Century);
year=Clock.getYear();
temp3=Clock.getTemperature();
dow=Clock.getDoW();
display.showNumberDec(minute, true, 2, 2);
display.showNumberDecEx(hour, 0b01000000, true, 2, 0);
}
// -----------------------------------------
// Display all the (daytime) non-time stuff:
// -----------------------------------------
void DisplayValues() {
// set state of QFEswitch:
if (digitalRead(QFEswitch) == LOW) { // switch active, so prepare for barometer display
switch2state = true;
}
else { // switch not active, so normal temp display please
switch2state = false;
}
if ((switch2state != oldswitch2state) && (switch2state == false)) {
display2.setSegments(dC);
display2.showNumberDec (temperature, false, 2, 0);
display3.setSegments(rh);
display3.showNumberDec (humidity, false, 2, 0);
oldswitch2state = switch2state;
}
else if ((switch2state != oldswitch2state) && (switch2state == true)) {
display2.showNumberDec (barometer, false, 4, 0);
if (uptime > 2) {
display3.showNumberDec (round(barodiff3h), false, 4, 0);
}
else {
//display that there is not yet enough data (and how many hours so far; needs to be >2)
display3.setSegments(nd);
display3.showNumberDec (uptime, false, 2, 2);
}
oldswitch2state = switch2state;
}
if (switchstate != oldswitchstate) {
// The switch has just been changed (startup, or back to off, else we would not get here)
// So, do a full new display.
//first, a hello beep:
tone(buzzer, 523, 100);
delay(100);
tone(buzzer, 587, 100);
delay(100);
tone(buzzer, 659, 100);
delay(100);
// now the displays on
display.showNumberDec (CO2level, false, 4, 0);
if (switch2state == true) {
display2.showNumberDec (barometer, false, 4, 0);
if (uptime > 2) {
display3.showNumberDec (round(barodiff3h), false, 4, 0);
}
else {
//display that there is not yet enough data (and how many hours so far; needs to be >2)
display3.setSegments(nd);
display3.showNumberDec (uptime, false, 2, 2);
}
}
else {
display2.setSegments(dC);
display2.showNumberDec (temperature, false, 2, 0);
display3.setSegments(rh);
display3.showNumberDec (humidity, false, 2, 0);
}
display.showNumberDec (CO2level, false, 4, 0);
//Now the CO2 status LEDs:
if (CO2level > CO2red) {
digitalWrite (redled, HIGH);
digitalWrite (yellowled, LOW);
digitalWrite (greenled, LOW);
}
else if (CO2level > CO2yellow) {
digitalWrite (redled, LOW);
digitalWrite (yellowled, HIGH);
digitalWrite (greenled, LOW);
} else {
digitalWrite (redled, LOW);
digitalWrite (yellowled, LOW);
digitalWrite (greenled, HIGH);
}
oldCO2level = CO2level;
oldswitchstate = switchstate;
}
//
// and now, since switch was not changed, we can just display if there's a change:
if (switch2state == false) {
if (temperature != oldtemperature) {
display2.setSegments(dC);
display2.showNumberDec (temperature, false, 2, 0);
oldtemperature = temperature;
}
if (humidity != oldhumidity) {
display3.setSegments(rh);
display3.showNumberDec (humidity, false, 2, 0);
oldhumidity = humidity;
}
}
else {
if (barometer != oldbarometer) {
display2.showNumberDec (barometer, false, 4, 0);
oldbarometer = barometer;
if (uptime > 2) {
display3.showNumberDec (round(barodiff3h), false, 4, 0);
}
else {
//display that there is not yet enough data (and how many hours so far; needs to be >2)
display3.setSegments(nd);
display3.showNumberDec (uptime, false, 2, 2);
}
}
}
if (CO2level != oldCO2level) {
display.showNumberDec (CO2level, false, 4, 0);
if (CO2level > CO2red) {
digitalWrite (redled, HIGH);
digitalWrite (yellowled, LOW);
digitalWrite (greenled, LOW);
} else if (CO2level > CO2yellow) {
digitalWrite (redled, LOW);
digitalWrite (yellowled, HIGH);
digitalWrite (greenled, LOW);
} else {
digitalWrite (redled, LOW);
digitalWrite (yellowled, LOW);
digitalWrite (greenled, HIGH);
}
oldCO2level = CO2level;
}
if ((temperature < mintemp) || (temperature > maxtemp)) {
digitalWrite (sideled1, HIGH);
} else {
digitalWrite (sideled1, LOW);
}
if ((humidity < minhum) || (humidity > maxhum)) {
digitalWrite (sideled2, HIGH);
} else {
digitalWrite (sideled2, LOW);
}
}
// -------------------------------
// Display all the NIGHT stuff:
// -------------------------------
void DisplayNightValues() {
if (switchstate != oldswitchstate) {
// first, a good night beep:
tone(buzzer, 659, 100);
delay(100);
tone(buzzer, 587, 100);
delay(100);
tone(buzzer, 523, 100);
delay(100);
}
// display.clear(); // not neeeded; overwriting also clears it
display2.clear();
display3.clear();
ReadDS3231();
if (temperature != oldtemperature) {
oldtemperature = temperature;
}
if (barometer != oldbarometer) {
oldbarometer = barometer;
}
if (humidity != oldhumidity) {
oldhumidity = humidity;
}
if (CO2level != oldCO2level) {
if (CO2level > CO2red) {
digitalWrite (redled, HIGH);
digitalWrite (yellowled, LOW);
digitalWrite (greenled, LOW);
} else if (CO2level > CO2yellow) {
digitalWrite (redled, LOW);
digitalWrite (yellowled, HIGH);
digitalWrite (greenled, LOW);
} else {
digitalWrite (redled, LOW);
digitalWrite (yellowled, LOW);
digitalWrite (greenled, HIGH);
}
oldCO2level = CO2level;
}
oldswitchstate = switchstate;
digitalWrite (sideled1, LOW);
digitalWrite (sideled2, LOW);
}
//------------------------------
// SET THE CLOCK:
//------------------------------
void setclock() {
long settimer;
//turn off other displays:
display2.setBrightness(1, false);
display3.setBrightness(1, false);
display2.setSegments(dC);
display2.showNumberDec (temperature, false, 2, 0);
// display2.showNumberDec (barometer, false, 4, 0);
display3.setSegments(rh);
// read the clock:
ReadDS3231();
second=Clock.getSecond();
minute=Clock.getMinute();
hour=Clock.getHour(h12,PM);
date=Clock.getDate();
month=Clock.getMonth(Century);
year=Clock.getYear();
temp3=Clock.getTemperature();
dow=Clock.getDoW();
//
//-------------
//SET THE HOUR:
//-------------
wdt_reset();
display.clear();
settimer=millis();
pushbuttonActive = false;
while (millis() - settimer < exitSetupTime) { // keep setting, unless no button pressed for 4s
display.showNumberDec(hour, true, 2, 0);
if (digitalRead(pushbutton) == LOW) {
settimer=millis();
if (pushbuttonActive == false) {
pushbuttonActive = true;
hour++;
if (hour>23){
hour=0;
}
Clock.setHour(hour);
display.showNumberDec(hour, true, 2, 0);
wdt_reset();
}
else{
pushbuttonActive == false;
}
}
else {
if (pushbuttonActive = true) {
pushbuttonActive = false;
}
}
}
//
//---------------
//SET THE MINUTE:
//---------------
wdt_reset();
display.clear();
settimer=millis();
pushbuttonActive = false;
while (millis() - settimer < exitSetupTime) { // here we loop to set something
display.showNumberDec(minute, true, 2, 2);
if (digitalRead(pushbutton) == LOW) {
settimer=millis();
if (pushbuttonActive == false) {
pushbuttonActive = true;
minute++;
if (minute>59){
minute=0;
}
Clock.setMinute(minute);
display.showNumberDec(minute, true, 2, 2);
wdt_reset();
}
else{
pushbuttonActive == false;
}
}
else {
if (pushbuttonActive = true) {
pushbuttonActive = false;
}
}
}
// now we timed out and it's back to displaying the time:
display.setBrightness(6, true);
display2.setBrightness(6, true);
display3.setBrightness(6, true);
display2.setSegments(dC);
display2.showNumberDec (temperature, false, 2, 0);
// display2.showNumberDec (barometer, false, 4, 0);
display3.setSegments(rh);
display3.showNumberDec (humidity, false, 2, 0);
//
// housekeeping the button state etc:
longPressActive = false;
ledstate = !ledstate;
// digitalWrite (blueled, ledstate);
}