How to write messages over the I2C bus on two different addresses using Arduino IDE?

2.5k views Asked by At

I have a master-slave setup consisting of a nodeMCU(master) and an arduino nano(slave). I want to sent two different types of data on two different I2C addresses. Datatype 1 are integers and datatype 2 are chars.

I have written these two codes, they somewhat do what I expected but the result isn't consistent.

Master(nodeMCU):

#include <Wire.h>

void setup() {
 Serial.begin(9600); /* begin serial for debug */
 Wire.begin(5, 4); /* join i2c bus with SDA=D1 and SCL=D2 of NodeMCU */
}

void loop() {

 int r =5;
 Wire.beginTransmission(8); /* begin with device address 8 */
 Wire.write(r);  
 Wire.endTransmission();
  delay(100);
 String bod = "message";
 Wire.beginTransmission(9);
 Wire.write(bod.c_str());  
 Wire.endTransmission();
  delay(100);

}

The slave (arduino nano)

#include <Wire.h>


void setup() {
  
 /* register request event */
 Serial.begin(9600);           /* start serial for debug */
}

void loop() {
 Wire.begin(8);                /* join i2c bus with address 8 */
 Wire.onReceive(receiveEvent1); /* register receive event */
 Wire.begin(9);                /* join i2c bus with address 9 */
 Wire.onReceive(receiveEvent2); 

 
}

// function that executes whenever data is received from master

void receiveEvent1(int howMany) {
 while (0 <Wire.available()) {
    int c = Wire.read();   
    /* receive byte as int */
    Serial.print(c);           /* print the int */
  }
 Serial.println();             /* to newline */
}

void receiveEvent2(char howMany) {
 while (0 <Wire.available()) {
   char d = Wire.read();   
    /* receive byte as a character */
    Serial.print(d);           /* print the character */
  }
 Serial.println();             /* to newline */
}

The output I get also stops after a short time:

5
message

5
5
5
10910111511597103101

5
10910111511597103101

message
5
message
5
10910111511597103101
10910111511597103101
10910111511597103101
5
message
message


message
5
message
message


message
5

The expected output is out there, but not in a consistent way like:

5
message
5
message
5
message

Is there a way to get the output like this? And is there a way to fix the output from stopping after a short time?

2

There are 2 answers

0
Ben T On

I want to sent two different types of data on two different I2C addresses.

Arduino's Wire library is designed to have a single I2C address per device. Its implementation uses a single set of global resources. This means that if you dynamically change the address of the receiving Arduino device then you would also need to synchronise the master to know what address to use.

It would be simpler to assign a single address to the slave device and have the master indicate the type of data being sent. This could be done by sending JSON or key/value pairs. e.g. the master can send the prefix int: before any integer and str: before any string. The slave can use the prefix to interpret the mode of transmission.

Explaining the Output

10910111511597103101

This is the ASCII characters for the string message in integers. i.e. 109, 101, 115, 115, 97, 103, 101

Here receiveEvent1() is handling the data being sent over the I2C bus and outputting each byte as an integer.

Invoking Wire.begin() and Wire.onReceive() in loop() will change the settings in the global TwoWire singleton. The underlying twi library also has a single set of resources.

The result of changing addresses and the receive handler (repeatedly in loop()) is a set of race conditions and the undefined use of the two receive handlers.

This appears to include receiveEvent1() handling "5" and receiveEvent2() handling "message" as you intended. It also includes receiveEvent1() handling "message" resulting in the sequence of numbers.

The apparent empty line is likely receiveEvent2() handling "5" so there is probably a non printable ASCII ENQ (enquiry) character being output. It's also likely there are periods where neither handler is configured properly and some messages are being dropped or ignored.

0
Plads E On

You shouldn't use 2 addresses to communicate with a single slave device. Instead, you should use one address, and specify which type of data you are about to send.

How do you specify which type of data you are about to send?

Like Ben T said, you can use JSON for that, but I think it's a bit overkill for your application.

A simpler implementation that needs less bytes to select the datatype your are transmiting would be to define 2 different addresses software-wise; one for receiving integers, and one for receiving characters.

In your master device, you would have something like this:

// First sending the integer
Wire.beginTransmission(8); //slave address is 0x08
Wire.write(0); // this is your "propriatary" address for sending integers!
Wire.write(r); // write your integer
Wire.endTransmission();

// Then sending the characters
Wire.beginTransmission(8);
Wire.write(1); // this is the address for sending characters!
Wire.write(bod.c_str());
Wire.endTransmission();

In your slave's setup() function, you should then have something like:

Wire.begin(8); // slave address
Wire.onReceive(dataReceived);

And this is what the callback dataReceived looks like:

void dataReceived() {
    char address = Wire.read(); // get the "propriatary" address
    switch(address) {
        case 0: // receiving integers
            receiveEvent1();
            break;
        case 1: // receiving characters
            receiveEvent2();
            break;
        default:
            break; // if we received an invalid address, do nothing
    }
}