Continuous Readings

Things you need:

  • Arduino, 5V tolerant
  • 1 or 2 Tentacle Shield(s)
  • 1-8 Atlas Scientific circuits
  • Computer with Arduino IDE installed

Difficulty: easy

You can find all code mentioned on this page, including a special Arduino Yún version of each sketch on GitHub.

All circuits in UART mode

  1. Set all your circuits to UART mode. This is the circuits factory setting.
  2. Set all circuits to a UART baudrate, that all your circuits can understand. For example, older circuits have a fixed baudrate of 38400, so setting all (including the EZO type circuits) to serial,38400. To learn about the communication modes, check out I2C or UART?
  3. Copy the code below to to your Arduino sketch
  4. Adjust the variables in the code to resemble your setup: TOTAL_CIRCUITS, channel_ids, channel_names (see the in-code comments for an explanation on how these work)
  5. Upload the code to your Arduino
  6. Open the Arduino IDE serial monitor @9600 baud
  7. See the stream of data coming in
// WhiteBox Labs -- Tentacle Shield --  UART example
// https://www.whiteboxes.ch/tentacle
//
// This code is based on https://www.atlas-scientific.com/_files/code/4-port-board.pdf
//
// How to use 4 (or 8 if using 2 Tentacle shields) Atlas Scientivic devices in serial mode.
// This sketch assumes all of your devices are either older serial devices or EZO circuits
// in serial mode. You can use the tentacle_setup.ino to autodetect and setup your devices.
//
// This code is intended to work on all 5V-tolerant Arduinos. If using the Arduino Yun, connect
// to it's serial port. If you want to work with the Yun wirelessly, check out the respective
// Yun version of this example.
//
// USAGE:
//---------------------------------------------------------------------------------------------
// - Set host serial terminal to 9600 baud
//  - Serial channel numbers are 0-3
//    ( Channels 4-7 are also available, if you're using two stacked Tentacle shields)
// - To open a channel send the number of the channel, a colon and the command ending with a carriage return.
//
// 0:r<CR>
// 1:i<CR>
// 2:c<CR>
// 3:r<CR>
//
//---------------------------------------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//---------------------------------------------------------------------------------------------

#include <SoftwareSerial.h>         //Include the software serial library  

SoftwareSerial sSerial(11, 10);     // RX, TX  - Name the software serial library sftSerial (this cannot be omitted)
                                    // assigned to pins 10 and 11 for maximum compatibility
int s0 = 7;                         // Tentacle uses pin 7 for multiplexer control S0
int s1 = 6;                         // Tentacle uses pin 6 for multiplexer control S1
int enable_1 = 5;	            // Tentacle uses pin 5 to control pin E on shield 1
int enable_2 = 4;	            // Tentacle uses pin 4 to control pin E on shield 2

char computerdata[20];              // A 20 byte character array to hold incoming data from a pc/mac/other
char sensordata[30];                // A 30 byte character array to hold incoming data from the sensors
byte computer_bytes_received = 0;   // We need to know how many characters bytes have been received
byte sensor_bytes_received = 0;     // We need to know how many characters bytes have been received

char *channel;                      // Char pointer used in string parsing
char *cmd;                          // Char pointer used in string parsing



void setup() {
  pinMode(s0, OUTPUT);             // set the digital output pins for the serial multiplexer
  pinMode(s1, OUTPUT);
  pinMode(enable_1, OUTPUT);
  pinMode(enable_2, OUTPUT);

  Serial.begin(9600);              // Set the hardware serial port to 9600
  sSerial.begin(9600);             // Set the soft serial port to 9600 (change if all your devices use another baudrate)
  intro();                         // display startup message
}


void serialEvent() {               //This interrupt will trigger when the data coming from the serial monitor(pc/mac/other) is received
  computer_bytes_received = Serial.readBytesUntil(13, computerdata, 20); //We read the data sent from the serial monitor(pc/mac/other) until we see a <CR>. We also count how many characters have been received
  computerdata[computer_bytes_received] = 0;      //We add a 0 to the spot in the array just after the last character we received.. This will stop us from transmitting incorrect data that may have been left in the buffer
}


void loop() {

  if (computer_bytes_received != 0) {             // If computer_bytes_received does not equal zero
    channel = strtok(computerdata, ":");          // Let's parse the string at each colon
    cmd = strtok(NULL, ":");
    open_channel();                               // Call the function "open_channel" to open the correct data path
    if (cmd != 0) {                               // if no command has been sent, send nothing
      sSerial.print(cmd);                         // Send the command from the computer to the Atlas Scientific device using the softserial port
      sSerial.print("\r");                        // <CR> carriage return to terminate message
    }
    computer_bytes_received = 0;                  // Reset the var computer_bytes_received
  }

  if (sSerial.available() > 0) {                 // If data has been transmitted from an Atlas Scientific device
    sensor_bytes_received = sSerial.readBytesUntil(13, sensordata, 30); //we read the data sent from the Atlas Scientific device until we see a <CR>. We also count how many character have been received
    sensordata[sensor_bytes_received] = 0;       // we add a 0 to the spot in the array just after the last character we received. This will stop us from transmitting incorrect data that may have been left in the buffer
    Serial.println(sensordata);                  // let’s transmit the data received from the Atlas Scientific device to the serial monitor
  }
}


// Open a channel via the Tentacle serial multiplexer
void open_channel() {

  switch (*channel) {

    case '0':                                // if channel==0 then we open channel 0
      digitalWrite(enable_1, LOW);           // setting enable_1 to low activates primary channels: 0,1,2,3
      digitalWrite(enable_2, HIGH);          // setting enable_2 to high deactivates secondary channels: 4,5,6,7
      digitalWrite(s0, LOW);                 // S0 and S1 control what channel opens
      digitalWrite(s1, LOW);                 // S0 and S1 control what channel opens
      break;

    case '1':
      digitalWrite(enable_1, LOW);
      digitalWrite(enable_2, HIGH);
      digitalWrite(s0, HIGH);
      digitalWrite(s1, LOW);
      break;

    case '2':
      digitalWrite(enable_1, LOW);
      digitalWrite(enable_2, HIGH);
      digitalWrite(s0, LOW);
      digitalWrite(s1, HIGH);
      break;

    case '3':
      digitalWrite(enable_1, LOW);
      digitalWrite(enable_2, HIGH);
      digitalWrite(s0, HIGH);
      digitalWrite(s1, HIGH);
      break;

    case '4':
      digitalWrite(enable_1, HIGH);
      digitalWrite(enable_2, LOW);
      digitalWrite(s0, LOW);
      digitalWrite(s1, LOW);
      break;

    case '5':
      digitalWrite(enable_1, HIGH);
      digitalWrite(enable_2, LOW);
      digitalWrite(s0, HIGH);
      digitalWrite(s1, LOW);
      break;

    case '6':
      digitalWrite(enable_1, HIGH);
      digitalWrite(enable_2, LOW);
      digitalWrite(s0, LOW);
      digitalWrite(s1, HIGH);
      break;

    case '7':
      digitalWrite(enable_1, HIGH);
      digitalWrite(enable_2, LOW);
      digitalWrite(s0, HIGH);
      digitalWrite(s1, HIGH);
      break;
  }
}


// Print intro
void intro() {
  Serial.flush();
  Serial.println(" ");
  Serial.println("READY_");
}

All circuits in I2C mode

  1. Set all your circuits to I2C mode. Learn how to do this in our guide I2C or UART?
  2. Set all circuits to a unique I2C ID (address)
  3. Copy the code below to to your Arduino sketch
  4. Adjust the variables in the code to resemble your setup: TOTAL_CIRCUITS, channel_ids, channel_names (see the in-code comments for an explanation on how these work)
  5. Upload the code to your Arduino
  6. Open the Arduino IDE serial monitor @9600 baud
  7. See the stream of data coming in
// WhiteBox Labs -- Tentacle Shield -- I2C example
// www.whiteboxes.ch
//
// How to retrieve continuous sensr readings from op to 8 Atlas Scientific devices on the I2C bus
// and send the readings to a host computer via serial.
//
// This code is intended to work on all 5V tolerant Arduinos. If using the Arduino Yun, connect
// to it's usb serial port. If you want to work with the Yun wirelessly, check out the respective
// Yun version of this example.
//
// USAGE:
//---------------------------------------------------------------------------------------------
// - Set all your EZO circuits to I2C before using this sketch.
//    - You can use the "tentacle-steup.ino" sketch to do so)
//    - Make sure each circuit has a unique I2C ID set
// - Adjust the variables below to resemble your setup: TOTAL_CIRCUITS, channel_ids, channel_names
// - Set host serial terminal to 9600 baud
//
//---------------------------------------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//---------------------------------------------------------------------------------------------

#include <Wire.h>                     // enable I2C.

char sensordata[30];                  // A 30 byte character array to hold incoming data from the sensors
byte sensor_bytes_received = 0;       // We need to know how many characters bytes have been received

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

#define TOTAL_CIRCUITS 4              // <-- CHANGE THIS | set how many I2C circuits are attached to the Tentacle shield(s): 1-8

int channel_ids[] = {97, 98, 99, 100};// <-- CHANGE THIS.
// A list of I2C ids that you set your circuits to.
// This array should have 1-8 elements (1-8 circuits connected)

char *channel_names[] = {"DO", "ORP", "PH", "EC"}; // <-- CHANGE THIS.
// A list of channel names (must be the same order as in channel_ids[]) 
// it's used to give a name to each sensor ID. This array should have 1-8 elements (1-8 circuits connected).
// {"PH Tank 1", "PH Tank 2", "EC Tank 1", "EC Tank2"}, or {"PH"}



void setup() {                      // startup function
  Serial.begin(9600);	            // Set the hardware serial port.
  Wire.begin();			    // enable I2C port.
}



void loop() {

  for (int channel = 0; channel < TOTAL_CIRCUITS; channel++) {       // loop through all the sensors
  
    Wire.beginTransmission(channel_ids[channel]);     // call the circuit by its ID number.
    Wire.write('r');        		              // request a reading by sending 'r'
    Wire.endTransmission();          	              // end the I2C data transmission.
    
    delay(1000);  // AS circuits need a 1 second before the reading is ready

    sensor_bytes_received = 0;                        // reset data counter
    memset(sensordata, 0, sizeof(sensordata));        // clear sensordata array;

    Wire.requestFrom(channel_ids[channel], 48, 1);    // call the circuit and request 48 bytes (this is more then we need).
    code = Wire.read();

    while (Wire.available()) {          // are there bytes to receive?
      in_char = Wire.read();            // receive a byte.

      if (in_char == 0) {               // null character indicates end of command
        Wire.endTransmission();         // end the I2C data transmission.
        break;                          // exit the while loop, we're done here
      }
      else {
        sensordata[sensor_bytes_received] = in_char;      // append this byte to the sensor data array.
        sensor_bytes_received++;
      }
    }
    
    Serial.print(channel_names[channel]);   // print channel name
    Serial.print(':');

    switch (code) {                  	    // switch case based on what the response code is.
      case 1:                       	    // decimal 1  means the command was successful.
        Serial.println(sensordata);       // print the actual reading
        break;                        	    // exits the switch case.

      case 2:                        	    // decimal 2 means the command has failed.
        Serial.println("command failed");   // print the error
        break;                         	    // exits the switch case.

      case 254:                      	    // decimal 254  means the command has not yet been finished calculating.
        Serial.println("circuit not ready"); // print the error
        break;                         	    // exits the switch case.

      case 255:                      	    // decimal 255 means there is no further data to send.
        Serial.println("no data");          // print the error
        break;                         	    // exits the switch case.
    }

  } // for loop 

}

Found a bug?

Something working not just right? Help us keeping the docs & examples up to date – leave a comment if you find any problems. Thank You.

2 thoughts on “Continuous Readings

  1. Hi, I just testing 4 channel tentacle with DO EZO circuit v1.96. The continuous mode reading work fine with “circuit” command C,1 in Serial mode, but giving same command in I2C mode give no result, so asking circuit C,? give ?C,0 (so it is not changing). I think internal (circuit) implementation of this command is more elegant way rather than query it with command “r”? Is it normal?

    1. Hi Ali,

      Please read the data sheet of your circuit. It will tell you there is no continuous mode on the circuit itself, if it is in i2c mode. In fact, that’s technically impossible for a i2c slave. That’s why we’ve created this example to show you how to do the same in i2c mode.

      Continuous mode on the circuit itself is handy for a quick start. But if you want to do more complex stuff with your Arduino, querying the circuit yourself (by code) is much more flexible.

      Patrick

Leave a Reply

Your email address will not be published. Required fields are marked *