I need to interface an ATTiny88 MCU configured as an I2C slave with a Raspberry Pi configured as an I2C master. I may be using the wrong approach, but I have a structure consisting of four different fixed length data types I want to pass back to the Master whenever it requests data.
Source Code:
#include <Wire.h>
struct Response // 7 bytes
{
byte Command; // 1 byte
float Temperature; // 4 bytes
byte FanValue; // 1 byte
bool CoolStat; // 1 byte
};
struct Response Reply;
bool True = 1;
bool False = 0
void setup()
{
pinMode(ADDR1, INPUT_PULLUP); // Address Offset 0 or 1
pinMode(ADDR2, INPUT_PULLUP); // Address Offset 0 or 2
I2C = (2 * digitalRead(ADDR1)) + digitalRead(ADDR1) + 8; // Set I2C address 8 - 11
Wire.begin(I2C);
Wire.onRequest(requestEvent); // Call requestEvent when data requested
Reply.Command = 1;
Reply.Temperature = 35.62;
Reply.FanValue = 255
Reply.CoolStat = True
}
// Function that executes whenever data is requested from master
void requestEvent() {
Wire.write(Reply); // respond with message as expected by master
Responded = True;
}
This does not work, of course, because the library does not like the fact I am passing a structure, rather than a simple null terminated string. I am not sure how best to pass the structure to the Wire.write() function. I like the ability to change the variables in the structure to different values of different data types at different points in the code, but would I be better served to take a different approach and manually update the binary values in the string?
1 Answer 1
There is an overload of Wire.write
that lets you send an arbitrary
array of bytes:
virtual size_t write(const uint8_t *, size_t);
In order to use it, you have to cast the address of your structure to a
pointer to uint8_t
:
Wire.write((uint8_t *) &Reply, sizeof Reply);
Note, however, that this method, although very common, is not really
conformant to the C++ standard. I suggest you instead use a union
that
lets you access the memory of the structure as a simple array of bytes:
union Response {
struct {
byte Command; // 1 byte
float Temperature; // 4 bytes
byte FanValue; // 1 byte
bool CoolStat; // 1 byte
};
uint8_t bytes[7];
};
This can then be sent with:
Wire.write(Reply.bytes, sizeof Reply);
However: You may have a hard time parsing the structure back on the
Pi. Different computing platforms have different data-type sizes and
different alignments constraints. The structure you are using may be
7-bytes long on an Arduino Uno, but it will be larger on an ARM-based
Arduino, and also on a Raspberry Pi: for alignment reasons, you will
have three padding bytes before Temperature
. Also, the CoolStat
field may be larger than one byte, and add some padding of its own. To
make things easier, I recommend you define the structure in a way that
its layout is platform-independent: use only fixed-size types, put the
larger items first, and manually pad to a multiple of the larger item's
size:
union Response {
struct {
float Temperature; // 4 bytes
uint8_t Command; // 1 byte
uint8_t FanValue; // 1 byte
uint8_t CoolStat; // 1 byte
uint8_t padding; // 1 byte
};
uint8_t bytes[8];
};
This should have the exact same layout on any Arduino, and also on a Raspberry Pi.
-
To avoid alignment, you can use
#pragma pack(1)
SBF– SBF2024年07月17日 10:28:48 +00:00Commented Jul 17, 2024 at 10:28 -
@SBF: This probably works on x86_64. However, I prefer avoid it, as I have used computers where unaligned memory accesses crash the program with
SIGBUS
(Bus error).Edgar Bonet– Edgar Bonet2024年07月17日 14:41:23 +00:00Commented Jul 17, 2024 at 14:41 -
Thank you, Sir! Yours is a perfect answer.LesRhorer– LesRhorer2024年07月22日 18:52:31 +00:00Commented Jul 22, 2024 at 18:52