Help with sending values via I2C

Hi,

Looking for some help here with a project I have been working on (for a very long time!)

I need to send numerical values from a Mega to a nano- I am using I2C.

I currently am able to send 0-255 ( I have had troubles with the much more complicated methods used to send more than that so far)

I need to be able to send several values, to be used to update variables on the receiving nano. I need a way to identify which info being sent is for what, what would be a simple method of accomplishing this?

I appreciate any ideas,

Do you want to send several values every time? If so, just send them as a series of bytes. Both programs will have to agree on the order (value1, value2, etc.) and how big each value is.

See the basic Serial input tutorials. You can send command strings via I2C and decode the strings on the receiver end.

You have to find a protocol that identifies the target and value, like "X=123".

Well,

I want to send atleast 2 separate values to update variables at the start of my main loop.

This is what Im currently doing-

Master sends-

  Wire.beginTransmission(SLAVE_1);
  Wire.write(StepsForward);
  Wire.endTransmission();
  delay(1);

Slave Recieves


void receiveEvent()
{
ForwardSteps= Wire.read();
delay(1);
}

I'd like to send StepsForward, StepsReverse, and Ideally Speed and Accel values as well, just need a simple way of doing so.

Thanks!

How far apart are they, hopefully less then 10 inches. Please post an annotated schematic showing exactly how it is wired. I cannot see what you have,

They are in the same enclosure- within inches.

Most I2C devices expect a register number (byte) followed by a value. Associate your variables with such a register number or array index, then you can both receive or transmit related values.

You need to start by defining some protocol. Simply way is for the message to start with an Identifier that is followed by the arguments. Easiest is make the first byte the identifier and the second the number of bytes and the second last the checksum and the last a terminating identifier. You can accumulate the checksum as you receive each byte then test at the end. Time out if you do not get enough bytes. You can set the identifiers to anything you want. This should get you started.

You can use SerialTransfer.h to handle the communication for you. You can install it via the Arduino IDE's Library Manager and it comes with many examples.

1 Like

Hmm, The examples included with SerialTransfer.h just do not make enough sense to me, and they are not commented enough to be of much help.

I see it uses Structs, which I am unfamiliar with. :dizzy_face:

I see it uses Structs, which I am unfamiliar with. :dizzy_face:

Structs are pretty simple, just a pre-defined collection of variables. See here

As for the examples, all you need to do is update the variables in the struct according to your project and you're good to go. All examples are commented but if you have any specific questions, I'd be happy to answer them.

The cool thing about the lib is that you don't need to "define your own protocol". As a newbie to programming, you should rely on a proven library instead (unless you want to define your own protocol).

1 Like

Oh- my apologies if this Library was created by you- I did not mean offense by "not enough comments".

Struggling to understand the i2c_rx/tx_data.ino example currently.

If I only need to send 2 variables, "StepsFwd" and "StepsRev" for example

What would that look like?

"myTransfer.begin( Wire, myConfig)"- is this where the data is sent to the slave?

Im pretty confused at the moment by the "myConfig part." I don't understand how the struct variables are being sent by that.

Thanks for any and all help!

No worries :smile:. Sometimes my comments are sparse, but I try to keep my source files (.cpp and .h) well commented plus use functions with intuitive names to reduce the need for example comments.

Something like this:

TX:

#include "I2CTransfer.h"


I2CTransfer myTransfer;

struct __attribute__((packed)) STRUCT {
  uint16_t StepsFwd;
  uint16_t StepsRev;
} testStruct;


void setup()
{
  Serial.begin(115200);
  Wire.begin();

  myTransfer.begin(Wire);
}


void loop()
{
  StepsFwd = INSERT YOUR NEW VALUE HERE;
  StepsRev = INSERT YOUR NEW VALUE HERE;

  myTransfer.sendDatum(testStruct);
  delay(500);
}

RX:

#include "I2CTransfer.h"


I2CTransfer myTransfer;

struct __attribute__((packed)) STRUCT {
  uint16_t StepsFwd;
  uint16_t StepsRev;
} testStruct;


/////////////////////////////////////////////////////////////////// Callbacks
void hi()
{
  myTransfer.rxObj(testStruct

  // DO STUFF WITH NEW VALUES HERE

  Serial.print(testStruct.StepsFwd);
  Serial.println(testStruct.StepsRev);
}

// supplied as a reference - persistent allocation required
const functionPtr callbackArr[] = { hi };
///////////////////////////////////////////////////////////////////


void setup()
{
  Serial.begin(115200);
  Wire.begin(0);

  ///////////////////////////////////////////////////////////////// Config Parameters
  configST myConfig;
  myConfig.debug        = true;
  myConfig.callbacks    = callbackArr;
  myConfig.callbacksLen = sizeof(callbackArr) / sizeof(functionPtr);
  /////////////////////////////////////////////////////////////////
  
  myTransfer.begin(Wire, myConfig);
}


void loop()
{
  // Do nothing
}

The myConfig part is a custom struct defined in the SerialTransfer library (see here) that is used to "setup" the library easily. It's optional for UART/SPI, but required for I2C for various reasons. The main reason is because the I2C receiver needs to receive data via a function callback. The config struct tells the lib what that callback function is (void hi() in the above case).

I hope this helps

1 Like

That, is immensely helpful.

Could you be so kind as to break down and explain the following as well to help me understand better?

Is there a list of available functions in the SerialTransfer library?


struct __attribute__((packed)) STRUCT

and ".sendDatum" in the following.


  myTransfer.sendDatum(testStruct);

In examples it was the following. I read it as beginning a Wire transfer with myConfig as a parameter. Could you explain it as well?


  myTransfer.begin(Wire, myConfig

);

Thank you so much!!

Hmm,

I'm working on getting the code functional, not quite there yet. I see "Wire.begin(0)" for the RX, but I dont see anything on the TX side that defines which address the data is sent to?

1. Check that MEGA and NANO are connected together using I2C Bus as per Fig-1.
i2cMegaNano
Figure-1:

2. Let us try to send the following comma separated data items from MEGA to NANO: 1234, 78, 14.57. NANO will receive them as a message and then will isolate the tokens (the individual data items) and then will show them ON SM2.

3. Upload the following sketches on MEGA (I2C-Master) and NANO (I2C-Slave).
Master Sketch:

#include <Wire.h>
#define slveAddress 0x08   //0b0001000
int y1 = 1234;
byte y2 = 78;
float y3 = 14.57;

void setup()
{
  Serial.begin(9600);
  Wire.begin();
}

void loop()
{
  Wire.beginTransmission(slveAddress);
  Wire.print(y1, DEC); //queueing 1234 to send
  Wire.print(',');   //seperator
  Wire.print(y2, DEC); //queueing 78 to send
  Wire.print(','); //seperator
  Wire.print(y3, 2); //queueing 14.57 to send
  Wire.endTransmission(); //sending all the queued bytes
  //----------------------
  delay(1000);  //est interval
}

Slave Sketch:

#include <Wire.h>
#define slaveAddress 0x08
char myMsg[20];
volatile bool flag = false;
char *token;

void setup()
{
  Serial.begin(9600);
  Wire.begin(slaveAddress);
  Wire.onReceive(receiveEvent);
}

void loop()
{
  if (flag == HIGH)
  {
    Serial.println(myMsg); //shows: 1234, 78, 14.57
    //--- seperatig the data items; use , as delimitter
    token = strtok(myMsg, ",");
    int y1 = atoi(token); Serial.println(y1, DEC); //shows: 1234
    //--------------
    token = strtok(NULL, ",");
    byte y2 = atoi(token); Serial.println(y2, DEC); //shows: 78
    //-----------------
    token = strtok(NULL, ",");
    float y3 = atof(token); Serial.println(y3, 2); //shows: 14.57
    //--------------------
    Serial.println("======================");
    flag = false;
  }
}

void receiveEvent(int howMany)
{
  int i;
  for (i = 0; i < howMany; i++)
  {
    myMsg[i] = Wire.read();
  }
  myMsg[i] = '\0'; //insert null-charcater
  //---------------
  flag = HIGH;
}

4. Slave Screen

======================
1234,78,14.57
1234
78
14.57
======================
1 Like

you got the source code when you downloaded/installed the library. look in the .h file

the packed attribute of the structure tells the compiler to put all the variables in the structure together as close as possible. Without it, you could get different arrangements, depending on which Arduino platform you are using. An ESP32 is a 32 bit device so it could line up all the variables on 32 bit boundaries. ( StepsFwd byte 1, StepsFwd byte 2, blank, blank, StepsRev byte 1, StepsRev byte 2, blank, blank). An a Uno, which is 8 bit, they would naturally get packed but you could not send/receive between them.

1 Like

Thank you- Your code works fine, I get the same results in the slave serial monitor.

The following sample code I'm trying to get to function that Power_Broker was kind enough to share- does work, but only sends zero values for the 2 variables. The only way I can get the variables values to change is if I set them inside the struct.

The following code gets me an 11 and a 0, which I want to be 100 and 200 as I've tried to define in the loop. If I do not define values in the struct, the slave gets 0's for both.

MASTER Code

//

#include "I2CTransfer.h"
  int StepsFwd=1;
  uint16_t StepsRev=1;

I2CTransfer myTransfer;

struct __attribute__((packed)) STRUCT {
  int StepsFwd=11;
  uint16_t StepsRev;
} testStruct;


void setup()
{
  Serial.begin(115200);
  Wire.begin();

  myTransfer.begin(Wire);
}


void loop()
{
  StepsFwd = 100;
  StepsRev = 200;

delay(10);
  myTransfer.sendDatum(testStruct);
  delay(2000);
}

SLAVE code

#include "I2CTransfer.h"


I2CTransfer myTransfer;

struct __attribute__((packed)) STRUCT {
  uint16_t StepsFwd;
  uint16_t StepsRev;
} testStruct;


/////////////////////////////////////////////////////////////////// Callbacks
void hi()
{
  myTransfer.rxObj(testStruct);

  // DO STUFF WITH NEW VALUES HERE

  Serial.println(testStruct.StepsFwd);
  Serial.println(testStruct.StepsRev);
}

// supplied as a reference - persistent allocation required
const functionPtr callbackArr[] = { hi };
///////////////////////////////////////////////////////////////////


void setup()
{
  Serial.begin(115200);
  Wire.begin(0);

  ///////////////////////////////////////////////////////////////// Config Parameters
  configST myConfig;
  myConfig.debug        = true;
  myConfig.callbacks    = callbackArr;
  myConfig.callbacksLen = sizeof(callbackArr) / sizeof(functionPtr);
  /////////////////////////////////////////////////////////////////
  
  myTransfer.begin(Wire, myConfig);
}


void loop()
{
  // Do nothing
}

Thanks for the clarification, I appreciate it.

There are many ways/tricks to send data from Master to Slave using I2C Bus. First accept one that works and then play with other means.

What is you sample data items that you want to send from MEGA to UNO?