Sending multi-byte data over I2C between different processors I have been attempting to read data from an ATTINY84 via an ESP8266. Both are programmed via the Arduino IDE, though as PlatformIO is a proxy for that, I'm not certain what version of the ATTINY core is in use.

I have both devices connected to the I2C bus, with the Arduino as a slave.
The ESP sends a code requesting a particular type of data, then calls Wire.requestFrom() to get 8 bytes of data. I am concerned over both my implementation and about potential endian-ness issues (even if it should turn out not to be an issue on these two particular devices, I'd like to know my future options).

The ATTINY code is a complex function that is called when there is a request (and the right option code, but that's probably not very relevant). It writes eight bytes using Wire.write after uncompressing them from the relevant arrays. (I have a lot of ADC data that technically should not fit in RAM.)
Since this is is a callback function or possibly an interrupt, do I need to be concerned about data corruption if the code is too slow to write the values onto the bus? I have to imagine it's trying to read them even as it writes. I can also provide code snippets if it would help.



I am also unsure if I am running into Endian-ness issues by using bitshift to grab the 2 and 8 bits from the ADC return value. Would this always give me the correct values, or does the C++ compiler (as I assume it does not) simply refuse to handle differences between the data order?
Specifically, if I shift a uint16_t (or any non-trivial datatype) right by 8 bits, and mask out the bottom 8 with &amp;0xFF, do I always get the second-least-significant (in this particular case, high) byte, or can the compiler produce alternate outputs based on byte order?

Does Arduino have an equivalent of network byte order (or does Wire automatically do so for its overrides of the functions) that I can get around this potential issue with?
There seems to be multiple questions in what you are asking. Here I try
to answer specifically this one:


  if I shift a uint16_t (or any non-trivial datatype) right by 8 bits,
  and mask out the bottom 8 with &amp;0xFF, do I always get the
  second-least-significant (in this particular case, high) byte, or can
  the compiler produce alternate outputs based on byte order?


Short answer: yes, you get the second-least-significant byte,
irrespective of the platform's native endianness.

Now, I'll expand on this answer by showing the different ways you
can convert an uint16_t to a sequence of bytes, and the implications
on the byte order.

If you want to control the endianness, and you want your code to work
independently of the native endianness of the platform, then bit
shifting is the way to go. Let's assume you want to convert this:

uint16_t data;


into this:

uint8_t bytes[2];


You can do it this way in order to get the least significant byte first:

bytes[0] = data &gt;&gt; 0;  // least significant byte
bytes[1] = data &gt;&gt; 8;  // most significant byte


Or you can instead do the assignments the other way around
(data &gt;&gt; 8 goes to bytes[0]...) if you want MSB first. Note that
there is no point in masking, as the assignment does it implicitly.

In contrast, if you wanted to put the bytes in native endianness (the
endianness of the platform), the traditional way to do it is to cast the
address of the number to a pointer to bytes, then read from this
pointer:

bytes[0] = ((uint8_t *) &amp;data)[0];  // first byte
bytes[1] = ((uint8_t *) &amp;data)[1];  // last byte


You can swap the indices on one side of the assignments if you want to
instead reverse the byte order. But note that this kind of casting is
now frowned upon, as it violates aliasing rules. A common alternative is
to have the number and the byte array share the memory within an
union:

union {
    uint16_t data;
    uint8_t bytes[2];
} x = { .data = some_16_bit_value };
do_something_with(x.bytes[0]);  // use first byte
do_something_with(x.bytes[1]);  // use second byte


These are the main byte-splitting idioms you may come across. If your
purpose is to have an endianness that you control and is independent of
the platform, then stick with the first option: the old good bit
shifting.