[SOLVED] Atlas Scientific Dissolved Oxygen probe with Arduino Nano IOT 33

Hi Folks

Problem: Arduino Nano IOT 33 does not forward commands (from IDE Serial window) to the Atlas Scientific Probe via I2C

I have done some debugging:

  1. checked I2C connection between UNO & IOT33 - working well
  2. ran the Atlas Scientific code on UNO (with the probe attached)- working very well
  3. checked all the wiring & hardware - no issues (run on 3.3Volts obviously)

so, why does this not work with the IOT33?

  1. I downloaded an example SerialEvent sketch on IDE - not working.

therefore I am assuming that the serial communication between the Arduino IDE and IOT 33 is not working - preventing the commands to get to the probe.
Do I have to do something with SERCOM5? I don't understand it.
could you please help me with this.

pls find the sketches below:

Atlas Scientific Sample code:

//This code was written to be easy to understand.
//Modify this code as you see fit.
//This code will output data to the Arduino serial monitor.
//Type commands into the Arduino serial monitor to control the EZO D.O. Circuit.
//This code was written in the Arduino 1.8.9 IDE
//This code was last tested 7/2019


#include <Wire.h>                //enable I2C.
#define address 97               //default I2C ID number for EZO DO Circuit.



char computerdata[20];           //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 do_data[20];                //we make a 20 byte character array to hold incoming data from the D.O. circuit.
byte in_char = 0;                //used as a 1 byte buffer to store inbound bytes from the D.O. Circuit.
byte i = 0;                      //counter used for DO_data array.
int time_ = 575;                 //used to change the delay needed depending on the command sent to the EZO Class DO Circuit.

char *DO;                        //char pointer used in string parsing.
char *sat;                       //char pointer used in string parsing.

float DO_float;                  //float var used to hold the float value of the D.O.
float sat_float;                 //float var used to hold the float value of the percent saturation.



void setup()                     //hardware initialization.
{
  Serial.begin(9600);            //enable serial port.
  Wire.begin();                  //enable I2C port.
}


void serialEvent() {                                                              //this interrupt will trigger when the data coming from the serial monitor(pc/mac/other) is received.
  received_from_computer = 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[received_from_computer] = 0;                                       //stop the buffer from transmitting leftovers or garbage.
  serial_event = true;                                                            //set the serial event flag.
}


void loop() {                                                                     //the main loop.
 if (serial_event == true) {                                                      //if a command was sent to the EZO device.
    for (i = 0; i <= received_from_computer; i++) {                               //set all char to lower case, this is just so this exact sample code can recognize the "sleep" command.
      computerdata[i] = tolower(computerdata[i]);                                 //"Sleep" ≠ "sleep"
    }
    i=0;                                                                          //reset i, we will need it later 
    if (computerdata[0] == 'c' || computerdata[0] == 'r')time_ = 575;             //if a command has been sent to calibrate or take a reading we wait 575ms so that the circuit has time to take the reading.
    else time_ = 250;                                                             //if any other command has been sent we wait only 250ms.
 

    Wire.beginTransmission(address);                                        //call the circuit by its ID number.
    Wire.write(computerdata);                                               //transmit the command that was sent through the serial port.
    Wire.endTransmission();                                                 //end the I2C data transmission.


    if (strcmp(computerdata, "sleep") != 0) {                               //if the command that has been sent is NOT the sleep command, wait the correct amount of time and request data.
                                                                            //if it is the sleep command, we do nothing. Issuing a sleep command and then requesting data will wake the D.O. circuit.

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

      Wire.requestFrom(address, 20, 1);                                     //call the circuit and request 20 bytes (this is more than we need)
      code = Wire.read();                                                   //the first byte is the response code, we read this separately.

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

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

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

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

      while (Wire.available()) {                 //are there bytes to receive.
        in_char = Wire.read();                   //receive a byte.
        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.
          break;                                 //exit the while loop.
        }
      }

      Serial.println(do_data);                  //print the data.
      Serial.println();                         //this just makes the output easier to read by adding an extra blank line 
    }
    serial_event = false;                       //reset the serial event flag.

    //if (computerdata[0] == 'r') string_pars(); //uncomment this function if you would like to break up the comma separated string into its individual parts.
  }

}
void string_pars() {                  //this function will break up the CSV string into its 2 individual parts. DO|SAT|
                                      //this is done using the C command “strtok”.

  DO = strtok(do_data, ",");          //let's pars the string at each comma.
  sat = strtok(NULL, ",");            //let's pars the string at each comma.
 
  Serial.print("DO:");                //we now print each value we parsed separately.
  Serial.println(DO);                 //this is the D.O. value.

  Serial.print("SAT:");               //we now print each value we parsed separately.
  Serial.println(sat);                //this is the percent saturation.
  Serial.println();                   //this just makes the output easier to read by adding an extra blank line 
    
  //uncomment this section if you want to take the values and convert them into floating point number.
/*  
    DO_float=atof(DO);
    sat_float=atof(sat);
*/  
}

The SerialEvent is not supported on the new boards

There are other examples all over the forum and the Arduino Reference on how to handle serial communication with the new boards. Let me know if you need any more help with this.

Hi Klaus,

Thanks for this hint, great help. My original assumption was correct then. :slight_smile: I have spent some time this morning finding other examples but I still don't understand, how to replace serialEvent. What is the alternative solution for SAMD boards?

Klaus_K:
The SerialEvent is not supported on the new boards

serialEvent() - Arduino Reference

There are other examples all over the forum and the Arduino Reference on how to handle serial communication with the new boards. Let me know if you need any more help with this.

Hi Klaus again

Actually, I just realised what the solution is:

getting rid of serialEvent function and put the commands from the function into the main loop.

Thank you so much guiding me to the right direction... all the best.

For other folks who may want to use the probe with Arduino MKR family or Nano IoT33, fully working UART code below (will add I2C too):

#include "Arduino.h"   
#include "wiring_private.h"

                      
String inputstring = "";                              //a string to hold incoming data from the PC
String sensorstring = "";                             //a string to hold the data from the Atlas Scientific product
boolean input_string_complete = false;                //have we received all the data from the PC
boolean sensor_string_complete = false;               //have we received all the data from the Atlas Scientific product
float DO;                                             //used to hold a floating point number that is the DO


Uart mySerial(&sercom3, 0, 1, SERCOM_RX_PAD_1, UART_TX_PAD_0);


void SERCOM3_Handler()
{
  mySerial.IrqHandler();
}




void setup() {                                          
  //while(!Serial);
  Serial.begin(9600);                                 //set baud rate for the hardware serial port_0 to 9600
  mySerial.begin(9600);                               //set baud rate for the software serial port to 9600
  inputstring.reserve(10);                            //set aside some bytes for receiving data from the PC
  sensorstring.reserve(30);                           //set aside some bytes for receiving data from Atlas Scientific product

  pinPeripheral(0, PIO_SERCOM); //Assign RX function to pin 0
  pinPeripheral(1, PIO_SERCOM); //Assign TX function to pin 1

}




void loop() {    
     
  if(Serial.available()) {
    inputstring = Serial.readStringUntil(13);           //read the string until we see a <CR>
    input_string_complete = true;                       //set the flag used to tell if we have received a completed string from the pc
    
  }                                  

  if (input_string_complete == true) {                //if a string from the PC has been received in its entirety
    mySerial.print(inputstring);                      //send that string to the Atlas Scientific product
    mySerial.print('\r');                             //add a <CR> to the end of the string
    inputstring = "";                                 //clear the string
    input_string_complete = false;                    //reset the flag used to tell if we have received a completed string from the PC
  }

  if (mySerial.available() > 0) {                     //if we see that the Atlas Scientific product has sent a character
    char inchar = (char)mySerial.read();              //get the char we just received
    sensorstring += inchar;                           //add the char to the var called sensorstring
    if (inchar == '\r') {                             //if the incoming character is a <CR>
      sensor_string_complete = true;                  //set the flag
    }
  }
  //delay(1000);

  if (sensor_string_complete == true) {               //if a string from the Atlas Scientific product has been received in its entirety
    Serial.println(sensorstring);                     //send that string to the PC's serial monitor
    /*                                                //uncomment this section to see how to convert the DO reading from a string to a float 
    if (isdigit(sensorstring[0])) {                   //if the first character in the string is a digit
      DO = sensorstring.toFloat();                    //convert the string to a floating point number so it can be evaluated by the Arduino
      if (DO >= 6.0) {                                //if the DO is greater than or equal to 6.0
        Serial.println("high");                       //print "high" this is demonstrating that the Arduino is evaluating the DO as a number and not as a string
      }
      if (DO <= 5.99) {                               //if the DO is less than or equal to 5.99
        Serial.println("low");                        //print "low" this is demonstrating that the Arduino is evaluating the DO as a number and not as a string
      }
    }
   */
    sensorstring = "";                                //clear the string
    sensor_string_complete = false;                   //reset the flag used to tell if we have received a completed string from the Atlas Scientific product
  }
}

Please find the code below to use the probe in I2C mode for Arduinos with SAMD processor:

//This code was written to be easy to understand.
//The original Atlas Scientific sketch was modified by Andras Farkas for ARduinos with SAMD processor
//This code will output data to the Arduino serial monitor.
//Type commands into the Arduino serial monitor to control the EZO D.O. Circuit.
//This code was modified in the Arduino 1.8.1 IDE
//This code was last tested May 2020


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



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



char computerdata[20];           //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 do_data[20];                //we make a 20 byte character array to hold incoming data from the D.O. circuit.
byte in_char = 0;                //used as a 1 byte buffer to store inbound bytes from the D.O. Circuit.
byte i = 0;                      //counter used for DO_data array.
int time_ = 575;                 //used to change the delay needed depending on the command sent to the EZO Class DO Circuit.

char *DO;                        //char pointer used in string parsing.
char *sat;                       //char pointer used in string parsing.

float DO_float;                  //float var used to hold the float value of the D.O.
float sat_float;                 //float var used to hold the float value of the percent saturation.



void setup()                     //hardware initialization.
{
  Serial.begin(9600);            //enable serial port.
  Wire.begin();                  //enable I2C port.
}



void loop() {                                                                       //the main loop.
  if(Serial.available()){
    
    received_from_computer = 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[received_from_computer] = 0;                                       //stop the buffer from transmitting leftovers or garbage.
    serial_event = true;                                                            //set the serial event flag.
    
    }




  
 if (serial_event == true) {                                                      //if a command was sent to the EZO device.
    for (i = 0; i <= received_from_computer; i++) {                               //set all char to lower case, this is just so this exact sample code can recognize the "sleep" command.
      computerdata[i] = tolower(computerdata[i]);                                 //"Sleep" ≠ "sleep"
    }
    i=0;                                                                          //reset i, we will need it later 
    if (computerdata[0] == 'c' || computerdata[0] == 'r')time_ = 575;             //if a command has been sent to calibrate or take a reading we wait 575ms so that the circuit has time to take the reading.
    else time_ = 250;                                                             //if any other command has been sent we wait only 250ms.
 

    Wire.beginTransmission(address);                                        //call the circuit by its ID number.
    Wire.write(computerdata);                                               //transmit the command that was sent through the serial port.
    Wire.endTransmission();                                                 //end the I2C data transmission.


    if (strcmp(computerdata, "sleep") != 0) {                               //if the command that has been sent is NOT the sleep command, wait the correct amount of time and request data.
                                                                            //if it is the sleep command, we do nothing. Issuing a sleep command and then requesting data will wake the D.O. circuit.

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

      Wire.requestFrom(address, 20, 1);                                     //call the circuit and request 20 bytes (this is more than we need)
      code = Wire.read();                                                   //the first byte is the response code, we read this separately.

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

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

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

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

      while (Wire.available()) {                 //are there bytes to receive.
        in_char = Wire.read();                   //receive a byte.
        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.println(do_data);                  //print the data.
      Serial.println();                         //this just makes the output easier to read by adding an extra blank line 
    }
    serial_event = false;                       //reset the serial event flag.

    //if (computerdata[0] == 'r') string_pars(); //uncomment this function if you would like to break up the comma separated string into its individual parts.
  }

}
void string_pars() {                  //this function will break up the CSV string into its 2 individual parts. DO|SAT|
                                      //this is done using the C command “strtok”.

  DO = strtok(do_data, ",");          //let's pars the string at each comma.
  sat = strtok(NULL, ",");            //let's pars the string at each comma.
 
  Serial.print("DO:");                //we now print each value we parsed separately.
  Serial.println(DO);                 //this is the D.O. value.

  Serial.print("SAT:");               //we now print each value we parsed separately.
  Serial.println(sat);                //this is the percent saturation.
  Serial.println();                   //this just makes the output easier to read by adding an extra blank line 
    
  //uncomment this section if you want to take the values and convert them into floating point number.
  
    DO_float=atof(DO);
    sat_float=atof(sat);
 
}
1 Like