Uno as an I2C slave sending floats to a Mega and taking commands back: values come out garbled
Me again. Garden project: an Uno out in the shed reads soil moisture, temperature and a water flow meter. A Mega in the same box has the Ethernet-to-Wi-Fi bridge and sends everything on to Hermetry. I want the Mega to ask the Uno for its readings over I2C, and sometimes tell it to open the irrigation valve (the valve relay is on the Uno).
Uno (slave, address 8):
void requestEvent() {
Wire.write(String(moisture).c_str());
Wire.write(String(tempC).c_str());
Wire.write(String(flowLpm).c_str());
}
Mega:
Wire.requestFrom(8, 12);
while (Wire.available()) Serial.print((char)Wire.read());
What I get is things like 4312.5 and ÿÿÿ mixed together, and sometimes only the first value. For the valve I haven’t even started, I don’t know how the slave gets a command.
Following, I have the exact same garbled ÿÿÿ thing with two boards.
Maybe you need pull-up resistors on SDA and SCL? I read I2C needs them.
Both boards are 5 V and the Wire library turns on the AVR’s internal pull-ups, so on short wires in one box that’s not it. The bigger problem is three Wire.write calls in requestEvent. Only one write per request works reliably on AVR, and text strings of unknown length make it worse. Put the values in one buffer and write it once. And no Serial printing inside the handlers.
What Carlos said, here’s the version I use for our line counters (an Uno doing the counting, a bigger board doing the network). Send binary, fixed size, in one struct, and use onReceive for commands.
Shared struct, in both sketches:
struct Reading {
int16_t moisture; // raw ADC
int16_t temp_x10; // 21.5 C -> 215
int16_t flow_x10; // L/min x10
};
Uno (slave):
volatile Reading r;
volatile bool openValve = false;
void requestEvent() { Wire.write((const uint8_t*)&r, sizeof(r)); } // one write
void receiveEvent(int n) { if (Wire.available()) openValve = Wire.read() == 1; }
void setup() { Wire.begin(8); Wire.onRequest(requestEvent); Wire.onReceive(receiveEvent); }
void loop() {
Reading tmp = { readMoisture(), (int16_t)(readTemp() * 10), (int16_t)(readFlow() * 10) };
noInterrupts(); r = tmp; interrupts();
digitalWrite(VALVE_PIN, openValve);
}
Mega (master):
Reading r;
if (Wire.requestFrom(8, (int)sizeof(r)) == sizeof(r)) Wire.readBytes((uint8_t*)&r, sizeof(r));
float tempC = r.temp_x10 / 10.0;
Wire.beginTransmission(8); Wire.write(1); Wire.endTransmission(); // open valve
Both are AVR, so byte order and int sizes match and the struct goes across as-is. The ÿ you saw is 0xFF, what the master reads when the slave has nothing left to send. Asking for exactly sizeof(r) fixes that too.
Works first time, which never happens to me. Readings are clean and the valve opens when the Mega sends 1. I’d been sending text because I thought I2C was “like Serial”. Also fixed my Hermetry payload while I was at it: temperature now goes out as a number with one decimal.
- You must be logged in to reply to this topic.
Join the conversation
Sign in to ask and answer, like helpful posts and mark the reply that solved your problem. Every contribution earns points and levels.

