Arduino I2C to Raspberry Pi

Hi, I need help but not sure where to go for this, I know you guys are the best so maybe someone might know.

I’ve been using Reef-pi aquarium controller software which runs on a Raspberry Pi, this software will read from an Atlas Scientific pH circuit over I2C as it has a microcontroller.

I also have a pH circuit that I made and I’m trying to get the I2C communications setup the same as an Atlas so Reef-pi will read it.

I don’t understand code for a Raspberry Pi so I’ve been using the sample sketches from Atlas Scientific and I’ve been able to get my circuit working with it just like it would with there’s however when I connect to Reef-pi I get a strconv.ParseFloat error.

{"error":"strconv.ParseFloat: parsing 
\"7.26xxxxxxxxxxxxxxxxxxxxxxxxx\": invalid syntax"} | HTTP 404

It seems like it’s not getting a string terminator but I’ve tried all combinations of ‘/r’ ‘/n’ ‘/0’ and 0

This is the code I have running on a Mega 2560 for testing, this would be the pH circuit.

#include <Wire.h>                //enable I2C.
#define address 45               //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 in_char = 0; 
char inData[20];               //used as a 1 byte buffer to store inbound bytes from the pH Circuit.
byte w = 0;                      //counter used for ph_data array.

void setup() {
  Serial.begin(115200);
  Wire.begin(45);
  Wire.onReceive(receiveEvent); // register event
  Wire.onRequest(requestEvent); // register event
}
void requestEvent()  // send data to controller
{
  if ((inData[0] == 'R'))
  {
    computerdata[0] = '7';
    computerdata[1] = '.';
    computerdata[2] = '2';
    computerdata[3] = '6';
    Serial.print("computerdata: "); 
    Serial.println(computerdata);   
    Wire.write(1);
    Wire.write(computerdata);
    Wire.write('\0');
  }
}

void receiveEvent(int howMany)  // controller is asking for something
{
  while (Wire.available()) {            //are there bytes to receive.
    in_char = Wire.read();              //receive a byte.
    Serial.print("in_char: "); 
    Serial.println(in_char);    
    inData[w] = in_char;               //load this byte into our array.
    w += 1;                             //incur the counter for the array element.
    if (in_char == 0) {                 //if we see that we have been sent a null command.
      w = 0;                            //reset the counter i to 0.
      break;                            //exit the while loop.
    }   
  }
}
void loop() {
  // put your main code here, to run repeatedly:

}

Continued on next post…

The Atlas pH circuit is setup to receive codes. When it receives an ‘R’ character it will send out the results in 850ms.

Here’s the sample Arduino sketch Atlas makes available, this reads the pH circuit and is pretty basic, my pH circuit will work with this without an issues that’s why I’m confused.

//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 = 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.
        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.
  //ph_float=atof(ph_data);
}

Thanks

Normally, you do not have to worry yourself about the serialEvent() function since it gets called by the bootloader every time through loop().

You do need to worry about terminating your stings.

#include <Wire.h>                //enable I2C.
#define address 45               //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 in_char = 0;
char inData[20];               //used as a 1 byte buffer to store inbound bytes from the pH Circuit.
byte w = 0;                      //counter used for ph_data array.

void setup() {
  Serial.begin(115200);
  Wire.begin(45);
  Wire.onReceive(receiveEvent); // register event
  Wire.onRequest(requestEvent); // register event
}
void requestEvent()  // send data to controller
{
  if ((inData[0] == 'R'))
  {
    computerdata[0] = '7';
    computerdata[1] = '.';
    computerdata[2] = '2';
    computerdata[3] = '6';
    computerdata[4] = '\0';
    Serial.print("computerdata: ");
    Serial.println(computerdata);
    Wire.write(1);
    Wire.write(computerdata);
    //Wire.write('\0');
  }
}

void receiveEvent(int howMany)  // controller is asking for something
{
  while (Wire.available()) {            //are there bytes to receive.
    in_char = Wire.read();              //receive a byte.
    Serial.print("in_char: ");
    Serial.println(in_char);
    inData[w] = in_char;               //load this byte into our array.
    w += 1;                             //incur the counter for the array element.
    if (in_char == 0) {                 //if we see that we have been sent a null command.
      w = 0;                            //reset the counter i to 0.
      break;                            //exit the while loop.
    }
  }
}
void loop() {
  // put your main code here, to run repeatedly:

}

Thanks, I tried that but still same error with none or one terminator.

Every extra terminator I add will add an extra \x00 after 7.26.

If I change this to wire.write(computerdata,2); the error will be the same but only 7.xxxxxxx as I only send 2 bytes. So it seems to be sending the correct number and no extras.

It sounds like your receiving code is clocking in more bytes than your arduino is sending. The thing with I2C is that the master dictates how may bytes are transferred, not the slave. If you are only sending 2 ("7.") but the master is expecting 8, then the master will continue to clock in data even after the arduino is done sending.

If you weren't a guy I would give you a big smooch. :slight_smile:

It appears Reef-pi is asking for 30 bytes which seems excessive considering the Atlas test sketches only call for 20.

With this it's now working and yes no string terminator needed.

Wire.write(1);
Wire.write(computerdata,30);

But computerdata is delcared as only 20 bytes so you are transmitting some random crap for memory that is unknown. Make computerdata bigger and make sure it is all 0's before stuffing data and transmitting.