NodeMCU -> Arduino Nano I2c

Greetings, i have been attempting to open communication between my NodeMCU and a Arduino Nano.

The NodeMCU is wired up to shift registers with 16bit, which then controls 16 Relays.

However, i also have a Arduino Nano in a mesh with other nanos Hooked to each own NRF24L01, and actuators like Relays… sensors and LCD’s for showing information.

This whole system communicates together, so information about remote actuators/sensors can flow into the NodeMCU’s website, aswell as being controlled by the NodeMCU.

I am however scratching my head trying to figure out what i did wrong here, my temperature value gets recieved and printed as NaN. while all the other values are recieved just fine.

however, the absolute first loop @ startup of the NodeMCU, recieves a temperature value, then stops recieving anything.

Here’s my Code:

I2C SLAVE(NANO)

bool forced = false;
float outside_temperature;
float outside_humidity;
float outside_pressure;

void setup() {

  Wire.begin(8);                /* join i2c bus with address 8 */
  Wire.onReceive(receiveEvent); /* register receive event */
  Wire.onRequest(requestEvent); /* register request event */

String request = "";
// function that executes whenever data is received from master
void receiveEvent(int howMany) {
 request = "";
 while (0 <Wire.available()) {
    char c = Wire.read(); /* receive byte as a character */
    request += c;
    Serial.print(c);          /* print the character */
  }
 Serial.println(); /* to newline */
 Serial.println(request);
}


union floatToBytes {
    char buffer[4];
    float val;
  } Data;

// function that executes whenever data is requested from master
void requestEvent() {
  
  if (request == "forced"){
    if (forced){
      Data.val = 1.00;
    }else{Data.val = 0.00;}
  }
  if (request == "temperature"){
    Data.val = outside_temperature;
  }
  if (request == "humidity"){
    Data.val = outside_humidity;
  }
  if (request == "pressure"){
    Data.val = outside_pressure;
  }
  
  Wire.write(Data.buffer, sizeof(Data.buffer));
}

I2C MASTER (NodeMCU), it is in the Void loop():

struct Enviroment_variables{
  float temperature;
  float humidity;
  float pressure;
};
Enviroment_variables outside;

bool requested = false;
int request_count = 0;
  
union floatToBytes {
    char buffer[4];
    float val;
  } Data;

void loop() {

unsigned long now = millis();
  if (now - last_sent >= interval) {   // If it's time to send or request data, do it!
    
    if (node_2_forced){
      Serial.println("Node2_Forced");
    }   
    
    website_values_update_node();

    last_sent = now;

    Serial.print("\n");
    Serial.print("Temperature: ");
    Serial.print(outside.temperature);
    Serial.print("\n");
    Serial.print("Humidity: ");
    Serial.print(outside.humidity);
    Serial.print("\n");
    Serial.print("Pressure: ");
    Serial.print(outside.pressure);

  }
  
  unsigned long now_recieve = millis();
  if (now_recieve - last_recieved >= i2c_interval) {
    
    Wire.beginTransmission(8); /* begin with device address 8 */
    
    if (request_count == 0){
      Wire.write("temperature");
    }
    if (request_count == 1){
      Wire.write("humidity");
    }
    if (request_count == 2){
      Wire.write("pressure");
    }
    if (request_count == 3){
      Wire.write("forced");
    }
    Wire.endTransmission(); /* stop transmitting */
  
    //Send request after telling the slave what to send.
    Wire.requestFrom(8, 4);
    requested = true;
   last_recieved = now_recieve;
 }
 
 uint8_t index = 0;
  while (Wire.available()){
    Data.buffer[index] = Wire.read();      /* receive byte as a character */
    index ++;           
  }

  if (requested){
    if (request_count == 0){
      outside.temperature = Data.val;
    }
    if (request_count == 1){
      outside.humidity = Data.val;
      }
    if (request_count == 2){
    outside.pressure = Data.val;
    }
    if (request_count == 3){
      if (Data.val == 1.0){
        node_2_forced = true;
      }else{node_2_forced = false;}
      Serial.print(Data.val);
    }
    request_count ++;
    if (request_count > 3){
      request_count = 0;
    }
    requested = false;
  }
}

I assume you receive the humidity and pressure values OK?
is the temperature on the I2C nano slave correct before it is transmitted?

Try posting the whole sketches rather than just a small section. It is difficult to tell what datatypes are in use from the section you have posted or how values get set.

The Whole scetch is HUGE, this is just a tiny bit of i2C communication.

horace:
I assume you receive the humidity and pressure values OK?
is the temperature on the I2C nano slave correct before it is transmitted?

Yes i recieve all other values ok.

aswell as the temperature during the first loop.

Then the temperature value suddenly is “nan”.

The transmitter is sending all values correct the whole time ( checked with Serial.print() ).

i just cannot figure out why the temperature as the only float of 4 floats doesn’t get recieved properly?

I assume outside is a struct or class - can you post it together with your other declarations/definitions
I looks like you may be getting an array overflow

I have now updated the code in the thread.

i am pretty sure everything is here.

try moving temperature to the end of the struct

struct Enviroment_variables{
  float humidity;
  float pressure;
  float temperature;
};

does temperature still get corrupted?

The ESP8266 runs at 3.3V and the Arduino Nano with ATmega328P runs at 5V. That means you have a voltage mismatch on the I2C bus which makes is less reliable.
The ESP8266 is (more or less) 5V tolerant, so it will not get damaged. However, I prefer to use a I2C level shifter.

Please upgrade your sketch with:

  • Remove all Serial functions from the onReceive and onRequest interrupt functions.
  • Never use a String object in a interrupt function.
  • We don't send commands as text, we use binary data.
  • Use a 'struct' to combine all your data. That also avoids splitting a float into bytes.

Keep interrupt functions always as small and as short as possible. You can read the data and set a flag and process the data in the loop(). Tutorial by Robin2: https://forum.arduino.cc/index.php?topic=683181.0.

Yes i have considered using a level shifter, however i noticed all other data being sent and recieved was spot on and stable, therefore i have not yet gone that way.

The serial functions i have put there in order to find out if i have made any mistake, they are put there after the fault came to be.

I did not know about the string Object inside a interrupt function, how may you have sent a specific request then? using numeric codes in binary? how may you do this? i must admit i have thought about using something as just a byte 1, or 2 ... etc as a specific command, may this be better? anyhow some may say sending readable commands over a protocol might be easier and better for later readability. well,unless one heavily comments the code with every thought in mind.

i did use a struct at first, But it did not end well, so therefore i use union to transfer bytes then reasemble it at reciever. i would enjoy if you would come up with a straight forward way to send a struct succesfully over I2C using 4 Floats in the same struct. and also, using example: 4 floats and some other data type in the same struct. I also attempted to send a union containing a struct, but that either did not end good.

the following code in which a I2C master transmits a struct to an I2C slave was adapted from

I2C Master writer

// Wire Master Writer - Demonstrates use of the Wire library
// adapted from  https://www.arduino.cc/en/Tutorial/MasterWrite
// to transmit/receive a structure over I2C

#include <Wire.h>

// struct to hold data transmitted to slave
struct {
  uint8_t  seq;   // sequence number
  char     ch;    // character
  uint16_t i;     // 16bit unsigned int
  float    x;     // real data
  uint8_t  spare; // pack to a 16bit boundary 
  uint8_t  crc;   // CRC or checksum
}
data ={0,' ', 1, 3.14159};

void setup()
{
  Serial.begin(115200);
  Serial.println("I2C master");
  Wire.begin(); // join i2c bus (address optional for master)
}

void loop()
{
  Serial.print("I2C transmitting seq "); Serial.println(++data.seq);
  if(data.ch++> 127)data.ch=' ';  // setup data
  data.i+=5;
  data.x*=1.01;
  Wire.beginTransmission(4); // transmit to device #4
  Wire.write((uint8_t *)&data, sizeof(data));
  Wire.endTransmission();    // stop transmitting
  delay(500);
}

I2C slave receiver

// Wire Slave Receiver - Demonstrates use of the Wire library
// adapted from https://www.arduino.cc/en/Tutorial/MasterWrite
// to transmit/receive a structure over I2C

#include <Wire.h>

volatile int howMany=0; // indicates how many bytes received by I2C slave

volatile struct { // struct to hold data transmitted to slave
  uint8_t  seq;   // sequence number
  char     ch;    // character
  uint16_t i;     // 16bit unsigned int
  float    x;     // real data
  uint8_t  spare; // pack to a 16bit boundary 
  uint8_t  crc;   // CRC or checksum
}
data;

void setup()
{
  Serial.begin(115200);         // start serial for output
  Serial.println("I2C slave");
  Wire.begin(4);                // join i2c bus with address #4
  Wire.onReceive(receiveEvent); // register event
}

void loop()   // checking for I2C data received
{
  if(howMany) {   // if bytes received display 
    Serial.print("\nreceived howmany= ");Serial.println(howMany);
    Serial.print("seq= "), Serial.println(data.seq);
    Serial.print("i= "), Serial.println(data.i);
    Serial.print("x= "), Serial.println(data.x);
    howMany=0;    // clear ready for next frame
  }
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany)
{
  uint8_t *ptr=(uint8_t *) &data; // pointer to struct for received data
  ::howMany=howMany;
  while(Wire.available())        // loop reading data
    *ptr++= Wire.read();        // read the byte
}

the structure contains

  1. sequence number so receiver can check for lost or duplicate frames
  2. data types char, int and float
  3. a crc check (checksum or CRC8) not implemented

extra code code be added in the receiver

  1. to acknowledge receipt of a valid frame
  2. to check for lost frames or corrupt frames (using crc) - a retransmission request could then be sent to the transmitter requesting retransmission, etc.

a run gives - transmitter

I2C master
I2C transmitting seq 1
I2C transmitting seq 2
I2C transmitting seq 3
I2C transmitting seq 4
I2C transmitting seq 5
I2C transmitting seq 6
I2C transmitting seq 7
I2C transmitting seq 8

receiver

received howmany= 10
seq= 1
i= 6
x= 3.17

received howmany= 10
seq= 2
i= 11
x= 3.20

received howmany= 10
seq= 3
i= 16
x= 3.24

received howmany= 10
seq= 4
i= 21
x= 3.27