SHUAI ZHANG
Published © GPL3+

Automated Lake Environment Data Collection Probe

An automated probe that collect CO2 flux data on lake Mendota, WI.

IntermediateShowcase (no instructions)Over 4 days1,651
Automated Lake Environment Data Collection Probe

Things used in this project

Story

Read more

Custom parts and enclosures

mount_Frv73hqOj6.STL

sensor cap

File missing, please reupload.

Code

read_blink_robuster.ino

Arduino
main program
#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include "Co2Meter_K33.h"

//global variable declarations
Co2Meter_K33 k33;
const int CHIP_SELECT = 4; //CS - pin 4 (for MKRZero SD: SDCARD_SS_PIN)
String logname = "DATA0.txt"; //name of logged data file
//use the blue of an RGB LED to indicate when a measurement cycle is initiated
int ledBlue = 9;           // the PWM pin the RGB LED blue is attached to
//use the red of an RGB LED to indicate errors
int ledRed = 3;           // the PWM pin the RGB LED red is attached to
//use the greed LED to indicate time between sets
int ledGreen = 10;  // the PWM pin the RGB LED green is attached to
int brightness = 100;    // how bright the RGB LED blue, green, and red will be

//establish delays and number of measurements
int measurementWait = 1500; // number of milliseconds to wait between measurements within a set
int setToSetWait = 10000; // number of milliseconds to wait between each set, within a set many measurements are taken
int measurementTake = 3; // number of measurements to take per set, don't exceed setToSetWait time

String dataString = ""; // make a string for assembling the data to log:
double co2 = 0; // holds co2 measurement taken from K33 sensor
double temp = 0; // holds temperature measurement taken from K33 sensor
double relTime = 0; // holds the time, relative to how long the Arduino has been powered on

//relay, pump, and ball valve variables
int prelay = 4; //define output pin for pre-relay
int bvalve = 5; //define output pin for the ball valve
int pump = 6; //define output pin for the pump



//In this example we will do a basic read of the CO2 value and checksum verification. 
//For more advanced applications please see the I2C Comm guide.

void setup() {
  Wire.begin();
  Serial.begin(4800); //start a serial port to communicate with the computer
  Serial.println("What a wonderful day, to read atmospheric CO2 concentrations!");
  pinMode(ledRed, OUTPUT); //declare red LED as an output
  pinMode(ledGreen, OUTPUT); //declare red LED as an output
  pinMode(ledBlue, OUTPUT); //declare blue LED as an output
  pinMode(bvalve,OUTPUT); //declare bvalve as an output
  pinMode(pump,OUTPUT);
  pinMode(prelay,OUTPUT);
 while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
 }

 Serial.print("Initializing SD card...");
  // see if the card is present and can be initialized:
  if (!SD.begin(CHIP_SELECT)) {
    Serial.println("Card failed, or not present");
    analogWrite(ledRed, brightness); // display error - LED Red
    // don't do anything more:
    while (1);
  }
  Serial.println("card initialized.");

  //find a filename that doesn't already exist
  String base = "DATA";  //base name of the log file
  String extension = ".txt"; //extension of the log file
  int iteration = 1; //a counter for which number the log file should be
  while (SD.exists(logname)) { // keep searching until we find a "logname" which won't overwrite an existing file
    ++iteration;
    logname = base + iteration + extension;
    Serial.println(logname); // print "logname"s for debugging purposes
  }

  // record the data text header to a file on the SD card
  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
     File dataFile = SD.open(logname, FILE_WRITE);

  // if the file is available, write to it:
  if (dataFile) {
    dataFile.println("CO2(ppm),Temp(C),RelH(%)");
    dataFile.close();
  }
  // if the file isn't open, pop up an error:
  else {
    Serial.println("error opening: " + logname);
    analogWrite(ledRed, brightness);
  }

  //ensure ball valve is open
  openBValve();
  
}

/*
 * co2 should be measured every few hours, this defines a "set"
 * within this "set" many measurements should be taken
 * after the measurments have been taken in relatively quick succession,
 * wait until next set to take more measurements
 */
void loop() {
  pumpRunThenStop(); // clear out the air
  closeBValve(); // seal air inside the tube
  for (int numMeasures = 0; numMeasures < 4; ++numMeasures) { // take specified number of measurements within the set
      // We keep the sample period >25s or so, else the sensor will start ignoring sample requests.
      analogWrite(ledBlue, brightness); //light blue LED while taking measurments
      Serial.println("Initializing sensor (20sec)...");
      k33.initPoll();
      //wait for sensor to warmup, "i" designates number of seconds to wait
      for (int i = 20; i > 0; --i) {
        Serial.print("" + String(i) + "..");
        delay(1000);
      }
      Serial.println(); //print a new line for visual appeal
      double tempValue = k33.readTemp();
      delay(20); 
      double rhValue = k33.readRh();
      delay(20);
      double co2Value = k33.readCo2();
      relTime = millis();
      dataString = "" + String(co2Value) + "," + String(tempValue) + "," + String(rhValue) + "," + String(relTime); //string that prints to file
      analogWrite(ledBlue, 0); //turn off blue LED when measurement ends
    
    // record the measurement to a file on the SD card
      // open the file. note that only one file can be open at a time,
      // so you have to close this one before opening another.
         File dataFile = SD.open(logname, FILE_WRITE);
    
      // if the file is available, write to it:
      if (dataFile) {
        dataFile.println(dataString);
        dataFile.close();
        // print to the serial port too:
        Serial.println(dataString);
      }
      // if the file isn't open, pop up an error:
      else {
        Serial.println("error opening: " + logname);
        analogWrite(ledRed, brightness);
      }
      
      if (co2Value >= 0) {
        Serial.print("CO2: ");
        Serial.print(co2Value);
        Serial.print("ppm Temp: ");
        Serial.print(tempValue);
        Serial.print("C Rh: ");
        Serial.print(rhValue);
        Serial.println("%");
      }
      else {
        Serial.println("Checksum failed / Communication failure");
        delay(9000);
      }
  }
  // here's the wait in between sets
  analogWrite(ledGreen, brightness); // show green LED in bewteen sets
  openBValve();
  delay(setToSetWait); // this is the amount of time to wait between sets
  analogWrite(ledGreen, 0); // show green LED in bewteen sets
}


///////////////////////////////////////////////////////////////////
// Function : void openBValve()
// Executes : opens the ball valve attached to a relay
/////////////////////////////////////////////////////////////////
void openBValve() {
//open the ball valve
  digitalWrite(prelay, LOW); // closes the relay "prelay" to feed power into relay "bvalve"
                            // since the close pin of the bvalve is attached to normally close,
                            // this action will make sure the bvalve is closed
  delay(4000); // wait 3 seconds before opeing ball valve, in case the ball valve was open when
              // Arduino is first started this time is allotted to physiccally let the ball
              // valve close
  digitalWrite(bvalve, LOW);  // closes the relay "bvalve" and opens the ball valve
  delay(4000); // wait for valve to open
  digitalWrite(prelay, HIGH); // opens the relay "prelay" which cuts power from the relay "bvalve"
                              // therefore leaving the bvalve in an open state w/o consuming extra power
}

///////////////////////////////////////////////////////////////////
// Function : void closeBValve()
// Executes : closes the ball valve attached to a relay
/////////////////////////////////////////////////////////////////
void closeBValve() {
//close the ball valve
  digitalWrite(prelay, LOW); // closes the relay "prelay" to feed power into relay "bvalve"
  delay(4000); // wait 4 seconds before opeing ball valve, in case the ball valve was open/close already
  digitalWrite(bvalve, HIGH);  // opens the relay "bvalve" and closes the ball valve
  delay(4000); // wait for valve to open
  digitalWrite(prelay, HIGH); // opens the relay "prelay" which cuts power from the relay "bvalve"
                              // therefore leaving the bvalve in a closed state w/o consuming extra power
}

///////////////////////////////////////////////////////////////////
// Function : void pumpRunThenStop()
// Executes : closes the ball valve attached to a relay
/////////////////////////////////////////////////////////////////
void pumpRunThenStop() {
  digitalWrite(pump, LOW); // closes relay "pump" to run the pump
  delay(5000); // waits for the pump to run, which ensures the device is working
  digitalWrite(pump, HIGH); // opens the relay "pump" which turns off the pump
}

Co2Meter_K33.cpp

C/C++
// CO2 Meter Kseries Example Interface
// by Andrew Robinson, CO2 Meter <co2meter.com>
// Talks via I2C to K33ELG/BLG Sensors for HostInitiated Data Collection 
// 4.1.2011

//Modified into Arduio Library by Kina Smith <kinasmith.com>
//06.09.2016

#include <inttypes.h>
#include <Wire.h>
#include "Arduino.h"
#include "Co2Meter_K33.h"

//int devAddr = 0x68; // This is the default address of the CO2 sensor, 7bits shifted left. 
// int devAddr = 0x7F;  // This is the broadcast address. There is conflicting addresses with the DS1307 RTC

Co2Meter_K33::Co2Meter_K33() {
	devAddr = K33_BLG_ELG_ADDRESS;
}

Co2Meter_K33::Co2Meter_K33(uint8_t address) {
	devAddr = address;
}

Co2Meter_K33::~Co2Meter_K33(){}


///////////////////////////////////////////////////////////////////
// Function : void wakeSensor()
// Executes : Sends wakeup commands to K33 sensors.
// Note : THIS COMMAND MUST BE MODIFIED FOR THE SPECIFIC AVR YOU ARE USING 
// THE REGISTERS ARE HARDCODED 
/////////////////////////////////////////////////////////////////
void Co2Meter_K33::wakeSensor() {
	// This command serves as a wakeup to the CO2 sensor, for K33ELG/BLG Sensors Only
	// You'll have the look up the registers for your specific device, but the idea here is simple: 
	// 1. Disabled the I2C engine on the AVR
	// 2. Set the Data Direction register to output on the SDA line
	// 3. Toggle the line low for ~1ms to wake the micro up. Enable I2C Engine
	// 4. Wake a millisecond.
	
	TWCR &= ~(1 << 2); // Disable I2C Engine
	DDRC |= (1 << 4); // Set pin to output mode
	PORTC &= ~(1 << 4); // Pull pin low
	delay(1);
	PORTC |= (1 << 4); // Pull pin high again
	TWCR |= (1 << 2); // I2C is now enabled
	delay(1);
}

////////////////////////////////////////////////////////////////// 
// Function : void initPoll()
// Executes : Tells sensor to take a measurement.
// Notes
//
// ///////////////////////////////////////////////////////////////////
void Co2Meter_K33::initPoll() {
	wakeSensor();
	Wire.beginTransmission(devAddr);
	Wire.write(0x11);
	Wire.write(0x00);
	Wire.write(0x60);
	Wire.write(0x35);
	Wire.write(0xA6);
	Wire.endTransmission();
	delay(20);
	Wire.requestFrom(devAddr, 2);
	byte i = 0;
	byte buffer[2] = {0, 0};
	while (Wire.available()) {
		buffer[i] = Wire.read();
		i++;
	}
}

/////////////////////////////////////////////////////////////////// 
// Function : double readCo2()
// Returns : The current CO2 Value, -1 if error has occured 
///////////////////////////////////////////////////////////////////
double Co2Meter_K33::readCo2() {
	wakeSensor();
	int co2_value = 0;
	// We will store the CO2 value inside this variable. digitalWrite(13, HIGH);
	// On most Arduino platforms this pin is used as an indicator light.
	
	//////////////////////////
	/* Begin Write Sequence */
	//////////////////////////
	
	Wire.beginTransmission(devAddr);
	Wire.write(0x22);
	Wire.write(0x00);
	Wire.write(0x08);
	Wire.write(0x2A);
	Wire.endTransmission();
	
	/*
		 We wait 10ms for the sensor to process our command.
		 The sensors's primary duties are to accurately
		 measure CO2 values. Waiting 10ms will ensure the
		 data is properly written to RAM
	*/
	
	delay(20);
	/////////////////////////
	/* Begin Read Sequence */
	/////////////////////////
	/*
		 Since we requested 2 bytes from the sensor we must
		 read in 4 bytes. This includes the payload, checksum,
		 and command status byte.
	*/
	
	Wire.requestFrom(devAddr, 4);
	byte i = 0;
	byte buffer[4] = {0, 0, 0, 0};
	
	/*
		Wire.available() is not nessessary. 
		Implementation is obscure but we leave it in here for portability and to future proof our code
	*/
	
	while (Wire.available()) {
		buffer[i] = Wire.read();
		i++;
	}
	co2_value = 0;
	co2_value |= buffer[1] & 0xFF;
	co2_value = co2_value << 8;
	co2_value |= buffer[2] & 0xFF;
	byte sum = 0;
	sum = buffer[0] + buffer[1] + buffer[2];
	if (sum == buffer[3]) {
		// Success!
		// digitalWrite(13, LOW);
		//Checksum Byte
		//Byte addition utilizes overflow
		return ((double) co2_value / (double) 1);
	}
	else {
		// Failure!
		/*
			Checksum failure can be due to a number of factors,
			fuzzy electrons, sensor busy, etc.
		*/
		
		// digitalWrite(13, LOW);
		return (double) -1;
	}
}

/////////////////////////////////////////////////////////////////// 
// Function : double readTemp()
// Returns : The current Temperture Value, -1 if error has occured 
///////////////////////////////////////////////////////////////////
double Co2Meter_K33::readTemp() {
	wakeSensor();
	int tempVal = 0;
	// digitalWrite(13, HIGH);
	Wire.beginTransmission(devAddr);
	Wire.write(0x22);
	Wire.write(0x00);
	Wire.write(0x12);
	Wire.write(0x34);
	Wire.endTransmission();
	delay(20);

	Wire.requestFrom(devAddr, 4);
	byte i = 0;
	byte buffer[4] = {0, 0, 0, 0};
	while (Wire.available()) {
		buffer[i] = Wire.read();
		i++;
	}
	tempVal = 0;
	tempVal |= buffer[1] & 0xFF;
	tempVal = tempVal << 8;
	tempVal |= buffer[2] & 0xFF;
	byte sum = 0;
	sum = buffer[0] + buffer[1] + buffer[2];
	//Checksum Byte
	//Byte addition utilizes overflow
	if (sum == buffer[3]) {
		// digitalWrite(13, LOW);
		return ((double) tempVal / (double) 100);
	}
	else {
		// digitalWrite(13, LOW);
		return -1;
	}
}

/////////////////////////////////////////////////////////////////// 
// Function : double readRh()
// Returns : The current Rh Value, -1 if error has occured 
///////////////////////////////////////////////////////////////////
double Co2Meter_K33::readRh() {
	wakeSensor();
	int tempVal = 0;
	// digitalWrite(13, HIGH);
	Wire.beginTransmission(devAddr);
	Wire.write(0x22);
	Wire.write(0x00);
	Wire.write(0x14);
	Wire.write(0x36);
	Wire.endTransmission();
	delay(20);
	Wire.requestFrom(devAddr, 4);
	byte i = 0;
	byte buffer[4] = {0, 0, 0, 0};
	while (Wire.available()) {
		buffer[i] = Wire.read();
		i++;
	}
	tempVal = 0;
	tempVal |= buffer[1] & 0xFF;
	tempVal = tempVal << 8;
	tempVal |= buffer[2] & 0xFF;
	byte sum = 0;
	sum = buffer[0] + buffer[1] + buffer[2];
	//Checksum Byte
	//Byte addition utilizes overflow
	if (sum == buffer[3]) {
		// digitalWrite(13, LOW);
		return (double) tempVal / (double) 100;
	}
	else {
		// digitalWrite(13, LOW);
		return -1;
	}
}

Co2Meter_K33.h

C Header File
// CO2 Meter Kseries Example Interface
// by Andrew Robinson, CO2 Meter <co2meter.com>
// Talks via I2C to K33ELG/BLG Sensors for HostInitiated Data Collection 
// 4.1.2011

//Modified into Arduio Library by Kina Smith <kinasmith.com>
//06.09.2016



#ifndef Co2Meter_K33_H
#define Co2Meter_K33_H

#ifdef ARDUINO
	#if ARDUINO < 100
		#include "WProgram.h"
	#else
		#include "Arduino.h"
	#endif
#else
	#include "ArduinoWrapper.h"
#endif

#include <Wire.h>

#define K33_BLG_ELG_ADDRESS 0x7F

class Co2Meter_K33
{
public:
	Co2Meter_K33();
	Co2Meter_K33(uint8_t address);
	~Co2Meter_K33();
	void wakeSensor();
	void initPoll();
	double readCo2();
	double readTemp();
	double readRh();
private:
	int devAddr;
	int co2_value;
	int tempVal;
};

#endif

Credits

SHUAI ZHANG

SHUAI ZHANG

7 projects • 5 followers
Thanks to Calvin Handerson, Galen Giese, Max Roth, Xuejian (Marvin) Zhang, and Yu Dong.

Comments