Introduction: How to Make a Data Logger for the Temperature, PH, and Dissolved Oxygen

About: an environmental researcher, learning electronics, programming, and sensors by tinkering

Objectives:

  • Make a data logger for ≤ $500. It stores data for temperature, pH, and DO with a time stamp and using I2C communication.
  • Why I2C (Inter-Integrated Circuit)? One can stack up as many sensors in the same line given that each of them has a unique address.

Step 1:

Step 2: Buy the Parts Below:

  1. Arduino MEGA 2560, $35, https://goo.gl/6EdBGu
  2. Power adapter for Arduino board, $5.98, https://goo.gl/BzuZbk
  3. LCD module I2C (display), $8.99, https://goo.gl/za3cBn
  4. Real-Time Clock (RTC) breakout, $7.5, https://www.adafruit.com/product/3296
  5. MicroSD card breakout board, $7.5, https://www.adafruit.com/product/254
  6. 4GB SD card, $6.98, https://goo.gl/tCY2ys
  7. Waterproof DS18B20 Digital sensor, $9.95, https://www.adafruit.com/product/381
  8. pH probe + Kits+ Standard buffers, $149.15, https://www.atlas-scientific.com/product_pages/kit...
  9. DO probe + Kits+ Standard buffers, $247.45, https://www.atlas-scientific.com/product_pages/kit...
  10. Breadboard, jumper cable, $7.98, https://goo.gl/3g3mTe
  11. (Optional) Voltage Isolator, $24, https://www.atlas-scientific.com/product_pages/cir...

Total: $510.48

* Certain parts (like the generic board) could be bought from other vendors (eBay, Chinese seller) for a lower price. pH and DO probes are recommended to get them from Atlas Scientific.

* A multimeter is recommended to check conductivity and voltage. It costs about $10-15 (https://goo.gl/iAMDJo)

Step 3: Wiring

  • Use jumper/DuPont cables to connect the parts as shown in the sketch below.
  • Use the multimeter to check the conduction.
  • Check the Positive-Voltage Supply (VCC) and Ground (GND) (it is easy to confuse if you are not familiar with circuit)
  • Plug in the power adapter and check the power indicator in each part. When in doubts, use the multi-meter to check the voltage between VCC and GND to be (5V)

Step 4: Prepare the PH, DO Circuits, SD Card

    1. Switch to I2C for pH and DO circuits
    2. The pH and DO breakouts are shipped with Serial communication as the default mode Transmit/Receive (TX/RX). To use the I2C mode Clock line (SCL), and Data line (SDA), switch mode by (1): un-plug VCC, TX, RX cables, (2): jump TX to Ground for Probe, PGND (not GND), (3) plug VCC to the circuit, (4): wait for LED to change from Green to Blue. More details check on page 39 (Datasheet for pH circuit,https://goo.gl/d62Rqv)
    3. Do the same step with DO circuit
    4. (if you know how to upload the sample code to the board, you can do it through Serial monitor)
    5. Format SD card to FAT format

    Step 5: Prepare Software

    1. Download Arduino Integrated Development Environment (IDE), https://www.arduino.cc/en/Main/Software
    2. Install library to Arduino IDE: https://www.arduino.cc/en/Guide/Libraries
    3. Most of them come with Arduino software. LiquidCrystal_I2C.h is available via GitHub https://goo.gl/chLrS7
    4. Install the driver for USB. For genuine Arduino, you may don’t need to install one. For a generic one, you need to install CH340 driver (GitHub: https://goo.gl/BZ7o32)
    5. Check if you connect the board correctly by running a blinking LED test
    6. How to find the MAC address of the 18B20 digital temperature. Using I2C scanner template in Arduino IDE with the probe plugged in. Each device has a unique MAC address, so you can use as many temperature probes with one shared line (#9). 18B20 uses a one wire I2C, so it is a special case of I2C communication method. Below is one method to find MAC – Medical Access Control (“ROM” when you run the procedure below).

    Step 6: Start Coding

    • Copy paste the code below to Arduino IDE:
    • Or download the code (.ino) and a new window should pop up in Arduino IDE.

    /*

    Reference tutorials:

    1. Temperature, ORP, pH logger: https://www.instructables.com/id/ORP-pH-Temperatur...

    2. Secured Digital (SD) Shield: https://learn.adafruit.com/adafruit-micro-sd-brea...

    This code will output data to the Arduino serial monitor. Type commands into the Arduino serial monitor to control the EZO pH Circuit in I2C mode.

    Modified from the referenced tutorials above, mostly from the I2C code by Atlas-Scientific

    Last updated: July 26, 2017 by Binh Nguyen

    */

    #include //enable I2C.

    #define pH_address 99 //default I2C ID number for EZO pH Circuit.

    #define DO_address 97 //default I2C ID number for EZO DO Circuit.

    #include "RTClib.h" // Date and time functions using a DS1307 RTC connected via I2C and Wire lib

    RTC_DS1307 rtc;

    #include // For SD libarary

    #include // SD card to store data

    const int chipSelect = 53; // need to figure out for Adafruit SD breakout//https://learn.adafruit.com/adafruit-micro-sd-breakout-board-card-tutorial/wiring

    //DO=MISO, DI=MOSI, on ATmega pin#: 50(MISO), 51(MOSI), 52(SCK), 53(SS)

    char logFileName[] = "dataLT.txt"; // modify logFileName to identify your experiment, for exampe PBR_01_02, datalog1

    long id = 1; //the id number to enter the log order

    #include

    LiquidCrystal_I2C lcd(0x27, 20, 4);

    #include

    #include

    #define ONE_WIRE_BUS 9 //define the pin # for temperature probe

    OneWire oneWire(ONE_WIRE_BUS);

    DallasTemperature sensors(&oneWire);

    DeviceAddress ProbeP = { 0x28, 0xC2, 0xE8, 0x37, 0x07, 0x00, 0x00, 0xBF }; //MAC address, unique to each probe

    String dataString; // the main variant to store all data

    String dataString2; // a temporary variant to store Temperature/pH/DO for print out

    char computerdata[20]; //instruction from Atlas Scientific: we make a 20 byte character array to hold incoming data from a pc/mac/other.

    byte received_from_computer=0; //we need to know how many characters have been received.

    byte serial_event=0;//a flag to signal when data has been received from the pc/mac/other.

    byte code=0; //used to hold the I2C response code.

    char pH_data[20]; //we make a 20 byte character array to hold incoming data from the pH circuit.

    byte in_char=0; //used as a 1 byte buffer to store in bound bytes from the pH Circuit.

    byte i=0; //counter used for ph_data array.

    int time_=1800; //used to change the delay needed depending on the command sent to the EZO Class pH Circuit.

    float pH_float; //float var used to hold the float value of the pH.

    char DO_data[20];

    //float temp_C;

    void setup() //hardware initialization.

    {

    Serial.begin(9600); //enable serial port.

    Wire.begin(pH_address); //enable I2C port for pH probe

    Wire.begin(DO_address);

    lcd.init();

    lcd.begin(20,4);

    lcd.backlight();

    lcd.home();

    lcd.print("Hello PBR!");

    lcd.setCursor(0,1);

    lcd.print("Initializing...");

    Serial.print("RTC is...");

    if (! rtc.begin())

    {

    Serial.println("RTC: Real-time clock...NOT FOUND");

    while (1);// (Serial.println("RTC: Real-time clock...FOUND"));

    }

    Serial.println("RUNNING");

    Serial.print("Real-time Clock...");

    if (! rtc.isrunning())

    {rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

    }

    Serial.println("WORKING");

    lcd.setCursor(0,0);

    lcd.println("RTC: OK");

    Serial.print("SD card..."); // see if the card is present and can be initialized:

    if (!SD.begin(chipSelect))

    { Serial.println("Failed"); // don't do anything more:

    return;

    }

    Serial.println("OK");

    lcd.setCursor(0,1);

    lcd.println("SD card: OK");

    Serial.print("Log File: ");

    Serial.print(logFileName);

    Serial.print("...");

    File logFile = SD.open(logFileName, FILE_WRITE); // open the file. "datalog" and print the header

    if (logFile)

    {

    logFile.println(", , , "); //indicate there were data in the previous run

    String header = "Date -Time, Temp(C), pH, DO";

    logFile.println(header);

    logFile.close();

    Serial.println("READY");

    //Serial.println(dataString); // print to the serial port too:

    }

    else { Serial.println("error opening datalog"); } // if the file isn't open, pop up an error:

    lcd.setCursor(0,2);

    lcd.print("Log file:");

    lcd.println(logFileName);

    delay(1000);

    sensors.begin();

    sensors.setResolution(ProbeP, 10); //10 is the resolution (10bit)

    lcd.clear();

    id = 0;

    }

    void loop()

    { //the main loop.

    dataString = String(id);

    dataString = String(',');

    DateTime now = rtc.now();

    dataString = String(now.year(), DEC);

    dataString += String('/');

    dataString += String(now.month(), DEC);

    dataString += String('/');

    dataString += String(now.day(), DEC);

    dataString += String(' ');

    dataString += String(now.hour(), DEC);

    dataString += String(':');

    dataString += String(now.minute(), DEC);

    dataString += String(':');

    dataString += String(now.second(), DEC);

    lcd.home();

    lcd.print(dataString);

    sensors.requestTemperatures();

    displayTemperature(ProbeP);

    Wire.beginTransmission(pH_address); //call the circuit by its ID number

    Wire.write('r'); //hard code r to read continually

    Wire.endTransmission(); //end the I2C data transmission.

    delay(time_); //wait the correct amount of time for the circuit to complete its instruction.

    Wire.requestFrom(pH_address,20,1); //call the circuit and request 20 bytes (this may be more than we need)

    while(Wire.available()) //are there bytes to receive

    {

    in_char = Wire.read(); //receive a byte.

    if ((in_char > 31) && (in_char <127)) //check if the char is usable (printable)

    {

    pH_data[i]= in_char; //load this byte into our array.

    i+=1;

    }

    if(in_char==0) //if we see that we have been sent a null command.

    {

    i=0; //reset the counter i to 0.

    Wire.endTransmission(); //end the I2C data transmission.

    break; //exit the while loop.

    }

    }

    serial_event=0; //reset the serial event flag.

    dataString2 += ",";

    dataString2 += String(pH_data);

    Wire.beginTransmission(DO_address); //call the circuit by its ID number

    Wire.write('r');

    Wire.endTransmission(); //end the I2C data transmission

    delay(time_); //wait the correct amount of time for the circuit to complete its instruction

    Wire.requestFrom(DO_address,20,1); //call the circuit and request 20 bytes

    while(Wire.available()) //are there bytes to receive.

    {

    in_char = Wire.read(); //receive a byte.

    if ((in_char > 31) && (in_char <127)) //check if the char is usable (printable), otherwise the in_char contains a symbol at the beginning in the .txt file

    { DO_data[i]= in_char; //load this byte into our array

    i+=1; //incur the counter for the array element

    }

    if(in_char==0)

    { //if we see that we have been sent a null command

    i=0; //reset the counter i to 0.

    Wire.endTransmission(); //end the I2C data transmission.

    break; //exit the while loop.

    }

    }

    serial_event=0; //reset the serial event flag

    pH_float = atof (pH_data);

    dataString2 += ",";

    dataString2 += String(DO_data);

    lcd.setCursor(0,1);

    lcd.print("Temperature/ pH/ DO");

    lcd.setCursor(0,2);

    lcd.print(dataString2);

    dataString += ',';

    dataString += dataString2;

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

    if (dataFile) // if the file is available, write to it:

    {

    dataFile.println(dataString);

    dataFile.close();

    Serial.println(dataString); // print to the serial port too:

    }

    else { Serial.println("error opening datalog file"); } // if the file isn't open, pop up an error:

    lcd.setCursor(0,3);

    lcd.print("Running(x5m):");

    lcd.setCursor(15,3);

    lcd.print(id);

    id ++; // increase one ID next iteration

    dataString = "";

    delay(300000); //delay 5 mintues = 5*60*1000 ms

    lcd.clear();

    } //end main loop

    void displayTemperature(DeviceAddress deviceAddress)

    {

    float tempC = sensors.getTempC(deviceAddress);

    if (tempC == -127.00) lcd.print("Temperature Error");

    else dataString2 = String(tempC);

    }//the code end here

    • Choose the right COM port via Arduino IDE under Tools/Port
    • Choose the right Arduino board. I used Mega 2560 because it has more internal memory. Arduino Nano or Uno works fine with this setup.
    • Check and code and upload code

    Step 7: Results on Wiring (can Be Improved) and LCD Display

    • Notice: I encountered the noise from the DO probe to pH probe after 2-3 months of continuous operation. According to Atlas Scientific, an in-line voltage isolator is recommended when pH, conductivity probes are operating together. More details are on page 9 (https://goo.gl/d62Rqv)
    • The logged data (the first one has unprinted characters before pH and DO data). I filtered to code by allowing only printable characters.

    Step 8: Import Data and Make a Graph

    1. Import data From Text Under DATA tab (Excel 2013)
    2. Separate the data by the comma (that is why having commas after each data input is helpful)
    3. Plot the data. Each data below has about 1700 points. The interval of measuring is 5 minutes (adjustable). The minimum for DO and pH circuits to read the data is 1.8 secs.

    Step 9: Calibration

    1. The digital temperature sensor (18B20) can be calibrated by adjusting the difference directly to the. Otherwise, if the compensation and the slope required calibration, you can do by changing values on line #453, DallasTemperature.cpp in the \libraries\DallasTemperature folder.
    2. For pH and DO probes, you can calibrate the probes with accompanying solutions. You have to use the sample code by Atlas Scientific and follow the instruction by this file.
    3. Please follow pages 26 and 50 for pH probe (https://goo.gl/d62Rqv) for calibration and temperature compensation, and also pages, 7-8 and 50 for DO probe (https://goo.gl/mA32mp). First, please re-upload the generic code provided by Atlas, open the Serial Monitor and key in a proper command.

    Step 10: Too Much Wiring?

    1. You can eliminate the SD card and the real time clock module by using Dragino Yun Shield for Arduino boards (https://goo.gl/J9PBTH). The code needed to be modified to work with Yun Shield. Here is a good place to start (https://goo.gl/c1x8Dm)
    2. Still too much wiring: the Atlas Scientific made a guide for their EZO circuits (https://goo.gl/dGyb12) and solderless board (https://goo.gl/uWF51n). Integrating 18B20 digital temperature is here (https://goo.gl/ATcnGd). You need to be familiar with commands on Raspbian (a version of Debian Linux) running on Raspberry Pi (https://goo.gl/549xvk)

    Step 11: Acknowledgement:

    This is my side project during my postdoctoral research which I worked on an advance photobioreactor to cultivate microalgae. So I thought it is necessary to credit the parties have provided conditions to make this happen. Firstly, the grant, DE-EE0007093: “Atmospheric CO2 Enrichment and Delivery (ACED),” from the U.S. Department of Energy, Office of Energy Efficiency and Renewable Energy Targeted Algal Biofuels and Bioproducts. I thank Dr. Bruce E. Rittmann at Biodesign Swette Center for Environmental Biotechnology, Arizona State Univesity for providing me the opportunity to tinker with electronics and Arduino. I was trained in environmental engineering, mostly chemistry, a bit of microbiology.