Atlas PH ciruit - I2C example wrong?

Hello friends. I am building a small ph measaurestation for my aquarium

For the pH measurement i am using: The ph-electrode and the ph ciruit from Atlas scientific.
My MC: ESP32.

I used the samplecode directly from the atlas scientific homepage. Since my electode is connected via I2C to my ESP32, i am susing the I2C sample:

//This code will work on an Arduino Uno and Mega
//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 Ph 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 99               //default I2C ID number for EZO pH 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 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 inbound bytes from the pH Circuit.
byte i = 0;                      //counter used for ph_data array.
int time_ = 815;                 //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.

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_ = 815;             //if a command has been sent to calibrate or take a reading we wait 815ms 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 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 may be more than we need)
      code =;                                                    //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 =;           //receive a byte.
        ph_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(ph_data);           //print the data.
    serial_event = false;                   //reset the serial event flag.
  //Uncomment this section if you want to take the pH value and convert it into floating point number.

How can this code work? As far as i understand, the void serialEvent() function, listens, if there is data send via my serial I2C. But since the function is not in the loop, how can this every work? serial_event will never be true?

When i upload the scetch, its not working. I cant imagine that the code perese is wrong from them. So i first look for a error at my side.

But when i modify the code and put serialEvent() into the loop, the example is working
I can send commands like calibration to the circuit and read ph values.

Has anyone also noticed this error?

A problem that i still have. i can only read a single ph value when i send “r” via serialmonitor. ISnt it possible to get continouse values in I2C mode?

Thank you very much

serialEvent() gets called my main() every time loop() exits. It is part of the way sketches work.

int main(void)

	for (;;) {
		if (serialEventRun) serialEventRun();
	return 0;

You really should not be calling it directly. A much better approach is explained in Robin2's tutorial Serial Input Basics

As for continuous data, I2C does not operate like that. As a slave device, your pH sensor has to be queried for its information.

thank you very much for the answer and the link

But i still dont get it, why it isnt a good idea to call the function serialEvent() directly in the loop?

Thank you

I have been searching the site, but I don't know which circuit and where that sketch is. Can you give a link to your probe, to the circuit and where that sketch is ?

Hey, sure

I am using this ciruit:

under documents and downloads u can find all relevant data.

I am using the Arduino I2C Sample Code

best regards

thank you very much for the answer and the link

But i still dont get it, why it isnt a good idea to call the function serialEvent() directly in the loop?

Thank you

Because it is already being called every time loop() finishes. You don't need to repeat the work that the program already does for you.

The example uses serialEvent() to receive commands from the serial monitor on the computer.
The I2C bus is to communicate with the sensor.

Can you run a I2C Scanner, to check if the sensor can be seen on the I2C bus.

The way they use serialEvent() and Serial.readBytesUntil() is weird. I suppose that have tested it and it should work. You have to set the serial monitor to write a Carriage Return at the end.