Sending and receiving long numbers over i2c or eeprom.

Hi.
Is there a recommended or preferred way to split a long or unsigned long number up for sending over i2c (or storing in eeprom) and then reassembling it at the other end ( or recalling from eeprom?
Cheers!

There's no real right way just as long as it works. Basically just divide and subtract to send the multiply and add when you receive. It's all done in a few micro seconds so unless your is mega time critical, it doesn't matter how you write it.

Regards

Bill

But once you have split your large number into smaller pieces, which part do you send first?
Which part do you store in the first memory location?

This is covered by: Endianness

The point is that it is up to you to decide. :roll_eyes:

Either way will work, unless you have to be compatible with someone else's system or code, you only have to be consistent in your own use.

There is no "standard" as the Wikipedia article clearly explains.

Even the software floating point for the Arduino Uno is IEEE compatible. So you can store any variable that you want and everyone else can use it.

If you want to use a Arduino as Master on the I2C bus and a Arduino as Slave on the I2C bus, then you can use a pointer to the variable and send a number of bytes. The receiving Arduino puts the data in a similar variable and then you have the same data. There is no need to split something, just send the bytes of the variable with Wire.write().
The variable can be a unsigned long, or float, or an array or struct or array of struct or anything.

When using different platforms, then specify the size of the variable. For example uint32_t instead of unsigned long and int16_t instead of int.

Some I2C sensors have the higest byte at the lower register address, and some the lowest byte. That is always in the datasheet.
I don't know which byte order the Raspberry Pi uses.

I'm not on my PC and attempting to type code on a small.phone with fat fingers
So reading bigendian could be something like

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

void loop() {
 
   long val=0; 
   byte in[4]={0,0,0,2};//from i2c
    for(int i=3;i>=0;i--){
       val +=in[3-i]<<(8*i);
       }
   Serial.print (val);
}

except I can't get it to work properly. :frowning:

Who is making that data ? and how did that data get into an array of bytes ?

If one Arduino sends a long and the other Arduino receives a long, then there is no need to split or combine something.
Here is a tutorial by Robin2: Use I2C for communication between Arduinos - Exhibition / Gallery - Arduino Forum.
That tutorial uses a 'struct', but you can use a 'long' or array of long as well. There is no splitting into bytes or something like that.

When the data is from a sensor, please tell which sensor that is.

If you want an example that shows how to combine 4 bytes into a long, just to learn from it, then just ask.

OK. I thought would be an easy fix. How wrong can one be! There's so much in the link you posted that I've never got to grips with such as *and& pointers, structures, strcpy, txData.val etc etc It's going to take quite some time. I thought I got really close with a simple wire.write(stuff); at one end and a wire.read(theSameStuff); at the other.
I use Arduinos as a tool to facilitate my 'making' hobby. I enjoy the programming side of it, but I would never call myself a programmer. I learn enough to do what I want, wire it up, then get back to making.

In the interests of brevity, I cut out a load of what I believed to be irrelevant stuff.

Essentially, the data is a predetermined sequence of motor target positions loaded from the master Arduino pro mini's EEPROM then stored in an array at startup, It sends these motor commands to 23 motors controlled by 5 slave pro minis in the form of 1 byte for the motor(device) number and 4 bytes for the long target position value. These slaves also monitor sensors (switch on/off) and when requested by the master, return a status byte where each bit represents the status (running or stationary) of each of the motors that slave address is driving and any sensors it is monitoring. Using the information in the status byte of each slave the master determines which motor must move next. The array in my example code represents the value of the 5 byte motor command. The motor sequence is a const int array[] which looks up the target value for each position from the loaded EEPROM array. I'm sure there's a more efficient way but it's the way it has grown, and I need a way of calibrating positions in real time, the sequence can be altered if necessary by altering the code.
This is a working model inspired a Welsh slate quarry rail truck lift Dinorwic Vivian Quarry incline V2 - YouTube
The movement of trucks is achieved by raising (servo) moving (steppers) and lowering magnets just under the rails.
there are 4 tracks at the top and 6 tracks at the bottom. The lifts are extensions of the tracks. the sliders are moved by a direct drive thread DC motors with shaft encoders, the lift is a geared servo motor. other servos look after truck queueing and decoupling.
here's the rest of my master code - it's no where near finished but working enough for proof of concept. it still contains loads of debugging lines.

This forum won't let me upload more than 9000 chars and I have to wait 5 mins between posts. My code is way more than 9000 chars - mainly due to the fact that I like to pepper my code with comments. I can post it in bits if it helps....

Reply, use the Attachments and other options, and Attach your .ino file then.

Have attached incMaster.ino - the master sketch and inc7to10stepper1.ino - this is the first of what will be 5 slave sketches. it controls devices 7 to 10 and 2 limit switches

incMaster.ino (24.5 KB)

inc7To10UpprStep1.ino (11.4 KB)

What about a 'struct' ? Can you read about a 'struct' and check if you understand the next code.

struct toSlaveStruct
{
  byte dev;   // device number
  long val;    // 32 bit value with bits
};

toSlaveStruct toSlave;

...

Wire.beginTransmission( devAdd[dev]);
Wire.write( (byte *) &toSlave, sizeof( toSlaveStruct));
Wire.endTransmission();

The &toSlave means the pointer to the data.
The sizeof( toSlaveStruct) is for the compiler, it fills that with the size of the struct. You can also use 5 for the second parameter, but the sizeof() is more fun :wink:

In the Slave, make the same struct and receiving is this:

void receiveEvent( int howMany) 
{
  if( howMany == sizeof( toSlaveStruct))
  {
    Wire.readBytes( (byte *) &toSlave, sizeof( toSlaveStruct));
  }
  
  // set a flag that is read in the loop()
  some_kind_of_bool_flag = true;
}

If this makes sense, then read the tutorial of Robin2 once more: Use I2C for communication between Arduinos - Exhibition / Gallery - Arduino Forum.

When you hate a struct, then I can do the same thing for the 'byte' and for the 'long'. The byte can be send and received in a normal way and for the long I would still use pointers.

When you want code without pointers, then the long can be seperated into 4 bytes with a 'union' ;D

When you don't want a struct or union or pointers, but want to shift the data, that is also possible. No problem at all, but what fun is that ?

Did you know that the Wire library is the worst Arduino library of all ? That the Arduino examples for the Wire library are very confusing and the comments are wrong and no one uses the Wire library like that ? Sorry, I just had to say this :smiling_imp: You seem to have used the Arduino examples, and went down the rabbit hole :sob:

OK. Ive been reading up on struct and pointers
If I understand it right..

toSlaveStruct toSlave;

creates an unitialised instance of the struct toSlaveStruct named toSlave, where toSlave.dev = 0; and toSlave.val=0; . Presumably I can set values within the program e.g.
toSlave.dev= aDeviceNumber;
toSlave.Val = aPositionValue;

Wire.write( (byte *) &toSlave, sizeof( toSlaveStruct));
I'm not 100% happy with this, but I sort of get it - i think .
does this send an array of sizeof( toSlaveStruct) data values, cast as bytes, starting at the first address of the data being pointed at by the pointer that points to the data held in toSlave? :o

Not sure what you mean here:

When you hate a struct, then I can do the same thing for the 'byte' and for the 'long'.

Wire.readBytes( (byte *) &toSlave, sizeof( toSlaveStruct));
reads a stream of sizeof (toSlaveStruct bytes) and stores them in the pointer pointing to the address holding the data for the instance toSlave of the struct toSlaveStruct.
Why is Wire.readBytes and not wire.read complementary to Wire.write?

Did you know that the Wire library is the worst Arduino library of all ? That the Arduino examples for the Wire library are very confusing and the comments are wrong and no one uses the Wire library like that ?

This is shocking.I thought the whole point of the library examples are that they are to be used as fundamental building blocks. It's been around for ages, If it's so bad why has it not been replaced or rewritten?
Ive had another close look at Use I2C for communication between Arduinos - Exhibition / Gallery - Arduino Forum and it all seem much clearer now.
Thanks :slight_smile:

]Breaking up a 32 bit number into 8 bit chunks to send over a CAN bus:

 if (!PassTwo)
        {
          rx_frame.FIR.B.FF = CAN_frame_std;
          rx_frame.MsgID = 1;
          rx_frame.FIR.B.DLC = 8;
          rx_frame.data.u8[0] = *item & 0xFF;
          rx_frame.data.u8[1] = (*item >> 8) & 0xFF;
          rx_frame.data.u8[2] = (*item >> 16) & 0xFF;
          rx_frame.data.u8[3] = (*item >> 24) & 0xFF;
          PassTwo = true;
        } else {
          rx_frame.data.u8[4] = *item & 0xFF;;
          rx_frame.data.u8[5] = (*item >> 8) & 0xFF;
          rx_frame.data.u8[6] = (*item >> 16) & 0xFF;
          rx_frame.data.u8[7] = (*item >> 24) & 0xFF;
          ESP32Can.CANWriteFrame(&rx_frame); // send items over CAN buss
          PassTwo = false;
        }

Reverse to reassemble

@blewtobits, I think you understand it. I will try to explain it in a very simple way.

The Wire.write() can do a few things, one of them is with a pointer and the number of bytes.
This is the one that is used: Wire.write( pointer-to-data, number-of-bytes).
It needs a pointer to the first memory location, and a number for the number of bytes to use.

Suppose the data is a 'int' or a 'long' or a 'toSlaveStruct', then the '&' is used to make a pointer to it.

int i = 3;         // an integer is two bytes
long l = 100;      // a long a 4 bytes
toSlaveStruct t;         // the struct is 5 bytes in size

Wire.write( &i, 2);
Wire.write( &l, 4);
Wire.write( &t, 5);

The compiler does not like that and we have to stay close friends with the compiler :wink:
The compiler wants a pointer to a 'char' or a pointer to a 'byte'. So the (byte *) cast converts one pointer to another pointer. It is the same pointer, pointing to the same memory location. But if the compiler likes that, then we give the compiler that :stuck_out_tongue:

An array is not the same. The name of an array is already a pointer !

int list[10];
Wire.write( list, 20);

The Wire.write() is not really complementary Wire.readBytes(). The Wire.readBytes() is a level higher, that is from the "Stream" class. They can be made to do almost the same thing, but internally there is a difference.
The sooner the Wire library is rewritten, the better. I'm afraid that will not happen.
It has to be simple for new users, and still be able to handle all weird things of all the sensors.
Functions similar to EEPROM.put() and EEPROM.get() would be nice. For example I2C.put() and I2C.get() without the Wire.beginTransmssion() or Wire.endTransmission(). However, those EEPROM.put() and EEPROM.get() functions can be misused with wrong code.

@Koepel. Yes, I recall a Youtube video mentioning having to keep the compiler happy. ::slight_smile:
I get the idea of having to make a pointer to the data by using &
I used the word array but my mind was grappling with the idea it was like a string of chars.
I was brought up in BASIC and paper tape. Old habits are hard to break.
I will let let it all sink in, have another look to cement in the cracks and then change my code. I may even change the way I access eeprom, despite the fact it seems to be working fine! Many thanks.

@Idahowalker. Indeed. That's the same principal I have always previously used. I tried something new, but it's giving strange results. I came in here looking for a new tube of glue - I'm leaving with a cement mixer. But its a cement mixer that I might be able to refill my tube of glue with many times over. I've learned a new trick, which is not so bad for an old dog like me ... :slight_smile: