July 2022
Adafruit Arduino Data Logging Shield
Note that due to Arduno Uno memory constraints, the real time clock in the Arduino Data Logging Shield was not utilized. The elapsed time in milliseconds was recorded instead.
/*
CO2, Temp, Humidity data logger
Writes data to SD card at 4 Hz rate
Pushbutton starts/stops data recording
(pushbutton LED illuminates when recording,
blinks on each SD card save)
Pushbutton start recording resets max CO2 capture.
Displays CO2 ppm when not recording data
I2C addresses used:
0x20 Adafruit Arduino Data Logging Shield
0x61 SCD30 CO2 sensor
0x68 Adafruit Arduino Data Logging Shield
0x70 LCD I2c backpack
Adafruit Arduino Data Logging Shield (older version)
Product #1141
https://www.adafruit.com/product/1141
https://learn.adafruit.com/adafruit-data-logger-shield?view=all
http://arduino.cc/en/Reference/SD
The big SD card holder can fit any SD/MMC storage up to
32G and and small as 32MB (Anything formatted FAT16 or FAT32)
Pins used:
Digital #13 - SPI clock (DO NOT USE PIN #13 for LED!)
Digital #12 - SPI MISO
Digital #11 - SPI MOSI
Digital #10 - SD Card chip select
SDA connected to A4 (RTC) 10k pullup on shield
SCL connected to A5 (RTX) 10k pullup on shield
Digital #3 red LED
Digital #4 green LED
Sparkfun CO2 Sensor (+ temp & humidity)
Product #SEN-15112
https://www.sparkfun.com/products/15112
https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library
I2C address: 0x61
16x2 LCD
Adafruit i2c/SPI LCD backpack using MCP23008 I2C expander, product #392
FREE Pins: A0, A1, A3, 9, 8, 5, 2
Configure the AArduino IDE board to "Arduino Uno"
*/
#define DEBUG false
//#define PIN_LED_RED 3 // Works, but hidden in enclosure
//#define PIN_LED_GREEN 4 // CANNOT BE CONTROLLED
void blinkLED(byte ledPIN){
// consumes 300 ms.
for(int i = 5; i>0; i--){
digitalWrite(ledPIN, HIGH);
delay(30);
digitalWrite(ledPIN, LOW);
delay(30);
}
} // blinkLED()
void blinkERR(byte ledPIN){
// S-O-S
const int S = 150, O = 300;
for(int i = 3; i>0; i--){
digitalWrite(ledPIN, HIGH);
delay(S);
digitalWrite(ledPIN, LOW);
delay(S);
}
delay(200);
for(int i = 3; i>0; i--){
digitalWrite(ledPIN, HIGH);
delay(O);
digitalWrite(ledPIN, LOW);
delay(O);
}
delay(200);
for(int i = 3; i>0; i--){
digitalWrite(ledPIN, HIGH);
delay(S);
digitalWrite(ledPIN, LOW);
delay(S);
}
delay(200);
} // blinkERR()
#define PIN_PUSHBUTTON_LED 9
#define PIN_PUSHBUTTON_PULLDOWN 8
byte buttonState = LOW; // buttonState & lastButtonState are initialized for a pulldown resistor
byte lastButtonState = LOW;
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
unsigned long debounceDelay = 50; // The switch debounce time. 50 to 100 ms
//////////////////////////////////////////////////////////////////////////////
// The optimal balance between current consumption and response time is 15 seconds.
// Read the CO2 sensor every timerInterval.
// This sketch sets the CO2 sensor update interval to 2 sec (min is 2 sec).
float CO2ppm = 0.0;
float CO2ppmMax = 0.0;
float TempC = 0.0;
float HumidityPct = 0.0;
boolean recToSD = false;
//////////////////////////////////////////////////////////////////////////////
// microSD card
#include <SPI.h>
#include <SD.h>
// Revise the pin # below to match your mSD card hardware for Card Detect
// or chip select (CS), or 'chip/slave select' (SS).
#define PIN_SD_CHIP_SELECT 10
File sdfile;
char filename[14];
void CreateSDfile() {
// Create a filename reference to a file that doesn't exist 'ANALOG00.TXT'..'ANALOG99.TXT'
strcpy(filename, "/LOGGER00.TXT");
for (uint8_t i = 0; i < 100; i++) {
filename[7] = '0' + i/10;
filename[8] = '0' + i%10;
// create if does not exist, do not open existing, write, sync after write
if (SD.exists(filename)){
#if DEBUG
Serial.print("File '");
Serial.print(filename);
Serial.println("' already exists");
#endif
} else {
#if DEBUG
Serial.print("New file will be '");
Serial.print(filename);
Serial.println("'");
#endif
break;
}
}
// Open file on SD Card for writing
sdfile = SD.open(filename, FILE_WRITE);
if (! sdfile) {
#if DEBUG
Serial.print("ERROR - unable to create '");
Serial.print(filename); Serial.println("'");
#endif
while (1) { blinkERR(PIN_PUSHBUTTON_LED);}
}
sdfile.println("Time[ms];CO2[ppm];CO2max[ppm];Temp[C];Humidity[%]");
} // CreateSDfile()
//////////////////////////////////////////////////////////////////////////////
// Sensirion SCD30 CO2 sensor
// Detect 400 to 10000 PPM with an accuracy of ±(30ppm+3%).
// https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library
// Note: 100kHz I2C is fine, but according to the datasheet 400kHz I2C is not supported by the SCD30
#include <Wire.h>
#include "SparkFun_SCD30_Arduino_Library.h"
SCD30 airSensor;
//const byte pinRDY = 9; // DOES NOT WORK !!! Data ready pin. High when data is ready for read-out.
//const byte pinSCL = A5; // I2C Arduino UNO
//const byte pinSDA = A4; // I2C Arduino UNO
//////////////////////////////////////////////////////////////////////////////
void pushButton() {
// Detect a change in the pushbutton state and turn on the
// pushbutton LED in response.
// This logic is for a button using a pulldown resistor.
buttonState = digitalRead(PIN_PUSHBUTTON_PULLDOWN);
// if millis() or timer wraps around, reset it
if (lastDebounceTime > millis()) lastDebounceTime = millis();
if (buttonState != lastButtonState) {
// Multiple changes in the buttonState can occur when a pushbutton is
// pressed, or a switch is toggled. Use a debounce timer to only react
// to a change in button state after an interval of debounceDelay.
lastButtonState = buttonState;
// Check if enough time has passed to evaluate another pushbutton press.
if ((millis() - lastDebounceTime) > debounceDelay) {
lastDebounceTime = millis();
if (buttonState == HIGH) {
recToSD = true;
CO2ppmMax = 0.0;
digitalWrite(PIN_PUSHBUTTON_LED, HIGH);
CreateSDfile();
} else {
recToSD = false;
digitalWrite(PIN_PUSHBUTTON_LED, LOW);
sdfile.close();
} // buttonState
} // millis()
} // buttonState != lastButtonState
} // pushButton()
//////////////////////////////////////////////////////////////////////////////
// 16x2 LCD + Adafruit i2c LCD backpack using MCP23008 I2C expander
//#include "Wire.h"
#include "Adafruit_LiquidCrystal.h"
// Default I2C address #0 (A0 to A2 not jumpered)
Adafruit_LiquidCrystal lcd(0); // 0x70
//////////////////////////////////////////////////////////////////////////////
void setup() {
pinMode(PIN_PUSHBUTTON_LED, OUTPUT);
blinkLED(PIN_PUSHBUTTON_LED);
pinMode(PIN_PUSHBUTTON_PULLDOWN, INPUT);
#if DEBUG
Serial.begin(9600);
while (!Serial) {
delay(1);
}
Serial.println("Serial ready");
#endif
Wire.begin();
// 16x2 LCD + Adafruit i2c LCD backpack
lcd.begin(16,2);
lcd.setBacklight(HIGH);
lcd.clear();
lcd.setCursor(0,0); // 1st row
// "01234567890123456"
lcd.print(" Mechatronic");
lcd.setCursor(0,1); // 2nd row
// "01234567890123456"
lcd.print(" Solutions LLC");
//////////////////////////////////////////////////////////////////////////////
// Sensirion SCD30 CO2 sensor
if (airSensor.begin() == false) {
#if DEBUG
Serial.println("ERROR - CO2 sensor not detected. Check wiring");
#endif
while (1) { blinkERR(PIN_PUSHBUTTON_LED);}
}
airSensor.setMeasurementInterval(2); //Change number of seconds between measurements: 2 to 1800 sec, stored in non-volatile memory of SCD30
//int airSensorInterval = airSensor.getMeasurementInterval();
//#if DEBUG
//Serial.print("airSensorInterval: ");
//Serial.print(airSensorInterval);
//Serial.println(" sec");
//#endif
//////////////////////////////////////////////////////////////////////////////
// see if the SD card is present and can be initialized:
if (!SD.begin(PIN_SD_CHIP_SELECT)) {
#if DEBUG
Serial.println("SD card failed, or not present");
#endif
while (1) { blinkERR(PIN_PUSHBUTTON_LED);}
}
#if DEBUG
Serial.println("SD card initialized.");
#endif
//////////////////////////////////////////////////////////////////////////////
#if DEBUG
Serial.println("Setup complete\n");
Serial.println("Time[ms]; CO2ppm; CO2pppMax; TempC; Humidity%");
#endif
} // setup()
void loop() {
pushButton();
// Read SCD30 sensor data as quickly as possible. See .setMeasurementInterval()
if (airSensor.dataAvailable()) {
// Get the maximum between published readings,
// and the overall session max as quickly as possible.
CO2ppm = airSensor.getCO2();
CO2ppmMax = max(CO2ppmMax, CO2ppm);
TempC = airSensor.getTemperature();
HumidityPct = airSensor.getHumidity();
if (recToSD == true) {
// toggle LED to show write activity
digitalWrite(PIN_PUSHBUTTON_LED, LOW);
#if DEBUG
Serial.print(millis());
Serial.print(";");
Serial.print(CO2ppm);
Serial.print(";");
Serial.print(CO2ppmMax);
Serial.print(";");
Serial.print(TempC);
Serial.print(";");
Serial.println(HumidityPct);
#endif
// "01234567890123456"
// "##### ppm #####"
// " ##.# C ### %
lcd.clear();
lcd.setCursor(0,0); // 1st row
// "01234567890123456"
lcd.print(CO2ppm,0);
lcd.print(" ppm");
lcd.setCursor(11,0); // 2nd row
lcd.print(CO2ppmMax,0);
lcd.setCursor(0,1); // 2nd row
// "01234567890123456"
lcd.setCursor(0,1); // 2nd row
lcd.print(TempC,1);
lcd.print(" C");
lcd.setCursor(11,1); // 2nd row
lcd.print(HumidityPct,0);
lcd.print(" %");
sdfile.print(CO2ppm);
sdfile.print(";");
sdfile.print(CO2ppmMax);
sdfile.print(";");
sdfile.print(TempC);
sdfile.print(";");
sdfile.println(HumidityPct);
// Execute a flush() to insure it is written since no sdfile.close() will be issued.
sdfile.flush();
digitalWrite(PIN_PUSHBUTTON_LED, HIGH);
CO2ppm = 0.0;
} else {
lcd.clear();
// "0123456789012345"
// "REC OFF #####"
// "/filename00.ext"
lcd.setCursor(0,0); // 1st row
lcd.print("REC off ");
lcd.setCursor(11,0); // 1st row
lcd.print(CO2ppm,0);
lcd.print(" ppm");
lcd.setCursor(0,1); // 2nd row
lcd.print(filename);
} // recToSD
// loop time w/o saving to SD card is 138 to 142 ms
// loop time saving to SD card is 156 to 158 ms
} else {
// SCD30 sensor data not ready
} // airSensor.dataAvailable()
} // loop()
Do you need help developing or customizing a IoT product for your needs? Send me an email requesting a free one hour phone / web share consultation.
The information presented on this website is for the author's use only. Use of this information by anyone other than the author is offered as guidelines and non-professional advice only. No liability is assumed by the author or this web site.