// ----------------------------------
#include <SSD1306.h>
#include "SparkFun_SCD4x_Arduino_Library.h"
#include <Wire.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
// ==================== I2C POWER CONTROL ====================
// Disable TWI/I2C MODULE + pull-ups
inline void i2cOff() {
// Disable internal pullups on SDA/SCL
pinMode(A4, INPUT); // SDA
digitalWrite(A4, LOW); // pull-up off
pinMode(A5, INPUT); // SCL
digitalWrite(A5, LOW); // pull-up off
// Disable TWI hardware
TWCR &= ~(1 << TWEN); // disable TWI module
}
// Re-enable TWI/I2C module + internal pullups
inline void i2cOn() {
// Re-enable internal pullups on SDA/SCL (optional)
pinMode(A4, INPUT_PULLUP); // SDA
pinMode(A5, INPUT_PULLUP); // SCL
// Re-enable TWI hardware
TWCR |= (1 << TWEN);
// Re-start Wire library
Wire.begin();
}
// ================
// ----------------------------------
// WATCHDOG TIMER
// ----------------------------------
volatile uint32_t elapsedSeconds = 0; // <-- replaces millis()
void setupWatchdogTimer() {
// Enable watchdog configuration changes
WDTCSR = (1 << WDCE) | (1 << WDE);
// Set interrupt mode (NO reset) + 5 second period
// WDP3 + WDP2 = 5 seconds
WDTCSR = (1 << WDIE) | (1 << WDP3) | (1 << WDP2);
}
ISR(WDT_vect) {
elapsedSeconds += 5; // WDT fires every 5 seconds
}
inline void adcOff() {
ADCSRA &= ~(1 << ADEN); // disable ADC
}
inline void adcOn() {
ADCSRA |= (1 << ADEN); // enable ADC
}
void disableBOD() {
MCUCR |= (1 << BODSE) | (1 << BODS);
MCUCR = (MCUCR & ~(1 << BODSE)) | (1 << BODS);
}
void goToSleep() {
i2cOff();
adcOff();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
noInterrupts();
disableBOD();
interrupts();
sleep_cpu();
sleep_disable();
}
// ----------------------------------
// ORIGINAL CODE
// ----------------------------------
SCD4x mySensor(SCD4x_SENSOR_SCD41);
SSD1306 display;
// --- BATTERY ---
const int BAT_PIN = A0;
// Voltage thresholds for 2S LiPo
const float FULL_VOLTAGE = 8.40;
const float EMPTY_VOLTAGE = 6.00;
// intervals converted to SECONDS:
#define MEASURE_INTERVAL_S 10 // 10 seconds
#define AVERAGE_PERIOD_S (30UL * 60UL) // 1800 seconds
#define LIVE_DISPLAY_DURATION_S 30 // 30 seconds
#define HISTORY_DISPLAY_DURATION_S 15 // 15 seconds
#define HISTORY_SIZE 10
float co2History[HISTORY_SIZE];
int historyCount = 0;
int historyIndex = 0;
uint32_t lastMeasure = 0;
uint32_t lastAverage = 0;
uint32_t lastDisplaySwitch = 0;
float co2Sum = 0;
int co2Samples = 0;
uint16_t currentCO2 = 0;
float currentTemp = 0;
float currentHum = 0;
float batteryJauge = 0;
bool showLive = true;
// ======================================================================
// SETUP
// ======================================================================
void setup() {
Serial.begin(9600);
Serial.println(F("SCD41 CO2 Logger"));
Wire.begin();
setupWatchdogTimer(); // now WDT is your master clock
if (!mySensor.begin(false, true, false)) {
Serial.println(F("Sensor not detected. Check wiring. Freezing..."));
while (1);
}
mySensor.measureSingleShot();
display.init();
display.clear();
display.setScale(2);
display.setCaret(17, 0);
display.setText(F("Trafichou"));
display.setCaret(47, 25);
display.setText(F("CO2"));
display.setCaret(35, 55);
display.setScale(1);
display.setText(F("Starting..."));
display.update();
delay(2000);
display.clear();
lastMeasure = elapsedSeconds;
lastAverage = elapsedSeconds;
lastDisplaySwitch = elapsedSeconds;
}
// ======================================================================
// LOOP
// ======================================================================
void loop() {
i2cOn();
uint32_t now = elapsedSeconds;
// ---- MEASUREMENT ----
if (now - lastMeasure >= MEASURE_INTERVAL_S) {
lastMeasure = now;
if (mySensor.readMeasurement()) {
currentCO2 = mySensor.getCO2();
currentTemp = mySensor.getTemperature();
currentHum = mySensor.getHumidity();
Serial.print(F("CO2: "));
Serial.println(currentCO2);
co2Sum += currentCO2;
co2Samples++;
mySensor.measureSingleShot();
}
batteryJauge = calculateBatteryJauge();
}
// ---- 30 MIN AVERAGE ----
if (now - lastAverage >= AVERAGE_PERIOD_S) {
lastAverage = now;
if (co2Samples > 0) {
float avg = co2Sum / co2Samples;
co2Sum = 0;
co2Samples = 0;
co2History[historyIndex] = avg;
historyIndex = (historyIndex + 1) % HISTORY_SIZE;
if (historyCount < HISTORY_SIZE) historyCount++;
Serial.print(F("Stored 30-min avg: "));
Serial.println(avg);
}
}
// ---- SWITCH DISPLAY ----
uint32_t duration = showLive ? LIVE_DISPLAY_DURATION_S : HISTORY_DISPLAY_DURATION_S;
if (now - lastDisplaySwitch >= duration) {
showLive = !showLive;
lastDisplaySwitch = now;
display.clear();
}
// ---- UPDATE DISPLAY ----
if (showLive) showLiveScreen();
else showHistoryScreen();
// ---- ENTER DEEP SLEEP ----
Serial.println(F("Going to sleep..."));
goToSleep();
}
// ======================================================================
// DISPLAY FUNCTIONS
// ======================================================================
void showLiveScreen() {
display.clear();
display.setScale(1);
display.setText(F("Battery:"));
display.setText((int)batteryJauge);
display.setText(F("%"));
display.setCaret(0, 15);
display.setScale(3);
display.setText(F("CO2:"));
display.setText((int)currentCO2);
display.setCaret(0, 45);
display.setScale(1);
display.setText(F("T:"));
display.setText(currentTemp);
display.setText(F(" degres"));
display.setCaret(0, 55);
display.setText(F("H:"));
display.setText(currentHum);
display.setText(F("%"));
display.update();
}
void showHistoryScreen() {
display.clear();
display.setScale(1);
display.setCaret(0, 0);
display.setText(F("30min avg CO2 ppm:"));
const int perLine = 2;
const int lineHeight = 10;
const int colWidth = 65;
for (int i = 0; i < historyCount; i += perLine) {
display.setCaret(0, 10 + (i / perLine) * lineHeight);
for (int j = 0; j < perLine; j++) {
int idx = i + j;
if (idx >= historyCount) break;
int offset = historyCount - 1 - idx;
float hoursAgo = offset * 0.5;
int histIdx = (historyIndex - historyCount + idx + HISTORY_SIZE) % HISTORY_SIZE;
int xPos = j * colWidth;
display.setCaret(xPos, 10 + (i / perLine) * lineHeight);
display.setText(hoursAgo, 1);
display.setText(F("h: "));
display.setText((int)co2History[histIdx]);
}
}
display.update();
}
// ======================================================================
// BATTERY FUNCTIONS
// ======================================================================
float calculateBatteryJauge() {
adcOn();
delay(5);
int raw = analogRead(BAT_PIN);
float vOut = (raw / 1023.0) * 5.0;
float batteryVoltage = vOut * 2.0;
Serial.print(F("Measure battery: "));
Serial.println(batteryVoltage);
return mapBatteryPercent(batteryVoltage);
}
float mapBatteryPercent(float voltage) {
if (voltage > FULL_VOLTAGE) voltage = FULL_VOLTAGE;
if (voltage < EMPTY_VOLTAGE) voltage = EMPTY_VOLTAGE;
return ((voltage - EMPTY_VOLTAGE) / (FULL_VOLTAGE - EMPTY_VOLTAGE)) * 100.0;
}