HELP!: Inconsistent data transfer from Slave to Master using I2C

Hello! I've been using Arduinos for hobby projects for a couple of years, but recently I've jumped off the deep end in trying to design something a bit complex for a work-related project. I'm designing an apparatus that will use two Arduinos (a Giga and a UNO R3) to power some lights, drive a couple of stepper motors, take temperature/light intensity/time readings (all of this on the Giga) and drive two Arducam OV5642s (Uno R3) at user-specified times to run some simple biology behavioral experiments. User-specified settings will be incorporated into the programming using the Giga display as well. I've figured out LVGL (for the most part) and have my Master (the Giga) sending messages to the Slave (Uno) reliably. I had hoped to be able to have the Slave send data to its Master when requested by Wire.requestFrom(), to get readings from a sensor that is not compatible with Giga architecture (Sparkfun Spectral Triad) and perhaps even to have the Arducams send a picture to the Giga display in order to allow focusing of the Arducams without needing a computer. Herein lies my problem.

No matter what I do, I have been unable to get reliable data transfer using Wire.requestFrom(). After 1-3 successful transfers the Master begins to receive noise. I've also noticed that this causes any other devices connected through SCL/SDA to also give garbage readings. I tried to reduce the code to the core functions required to transmit a reading from a sensor (Spectral Triad) on the Slave to the Master which has an RTC DS3231 connected to it. I've included the code for this simplified setup along with a fritzing diagram and screenshots of the resultant outputs on my Serial monitor. In my original code the Master will send a data request when a button on the screen is pressed, here the master requests data every 10 seconds, either way I get the same error. I also did already try putting pullup resistors (10k) on both SCL and SCA lines at both the Master and Slave sides of the connection and neither did anything. I also tried adding memset() to "clear" the char array in the Master before each new read, by replacing all data in the array with '0' and that also did not help. Any advice on how to fix this issue is appreciated! (Also, I know this project is overly complex for Arduino and I should just do it on Raspberry Pi, but I don't have the time or energy to learn Raspberry Pi right now and I already have all the Arduino components!) Thank you!

Master code:

#include <Wire.h>
#include "RTClib.h"

RTC_DS3231 rtc;

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
DateTime now;
char msg[5];

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

}

void loop() {
  delay(10000);
  now = rtc.now();
  Wire.beginTransmission(4);
  Wire.write(3);
  Wire.endTransmission();
  Wire.requestFrom(4, 5);
  int i = 0;
  while(Wire.available()){
    char c = Wire.read();
    delay(2);
    if(c== '\0' || i == 4){
      break;
    }
    msg[i] = c;
    i++;
  }
  msg[i] = '\0';
  Serial.println(msg);
  Serial.println(now.hour());

}

Slave code:

#include "SparkFun_AS7265X.h"
AS7265X sensor;

#include <Wire.h>
int Controller;
float A;
const int LED_PIN = 13;

void setup() {
  Serial.begin(115200);
  Wire.begin(4);
  pinMode(LED_PIN, OUTPUT);
  Wire.onReceive(Doit);

  while (sensor.begin() == false){
    sensor.begin();
  }
}

void loop() {
  sensor.takeMeasurements();
  A = sensor.getCalibratedA();
  delay(100);
}

void Doit(int Press){
  Controller = Wire.read();
  if (Controller == 1){
    digitalWrite(LED_PIN, HIGH);
  }
  if (Controller == 2){
    digitalWrite(LED_PIN, LOW);
  }
  if (Controller == 3){
    //delay(100);
    Wire.onRequest(requestEvent);
  }
}

void requestEvent(){
  char buf[4];
  dtostrf(A, 5, 2, buf);
  static const int msgLength = sizeof(buf);
  Wire.write(buf, (msgLength+1));
  //Serial.println(A);
  Serial.println(buf);
}

Fritzing diagram (shows a Mega because I couldn't find a Giga in the software):

Screenshot of outputs: (the master is supposed to print the hour and the data received - notice the hour becomes 165 after the received message becomes garbage):

A bit outside my area of expertise; my thoughts for possible causes of problems:

  1. Address 4 is a reserved address in I2C; change to 8 or higher.
  2. You will need level converters on the I2C bus because the Uno is a 5V device and the Giga is a 3.3V device.
1 Like

Hi sterretje! Thanks so much for these suggestions! I know very little about Wire and I2C so I had no idea 4 was a reserved address, I'll try using higher numbers to see if that helps!

To your second point, I also had been wondering if the difference in logic between Uno and Giga would be a problem but decided to ignore it when the transfer worked from master to slave, however since you pointed this out, I will acquire a 3.3 buck converter and try this out as well.

To add to what @sterretje has said, and I2C isn't an area I have used much, but I wonder if you are seeing a similar issue that comes up when a microcontroller is used as an SPI slave.

The problem is that the microcontroller (as the slave device) can't keep up with the master device.

I2C runs with a slower clock than SPI, which should give the slave microcontroller more time to load each byte into the I2C transmit register.

As a test, if you only request 1 byte, do you get reliable Comms?

Does the transfer go wrong once you request more than 1 byte?

I'm sorry to tell you, but this will not work. Please use a single Arduino board :pleading_face:
Projects fail that have a I2C bus between Arduino boards.
When there is also a motor in the project, then the project will almost certainly fail, because the I2C bus is a weak bus.

The Giga board is the Master for the RTC and for the Uno. But the Uno is also a Master for the Sparkfun driver. Then you have created a multi-master board. That will fail.

There is no need to wait after Wire.requestFrom(). You have a delay(2), but that is not needed.

The onReceive and onRequest handlers should both be installed in setup().

When the Master requests data, then the Slave does not know how much data the Master wants.
It is therefor easier to transfer a fixed number of bytes. No one uses the I2C bus with variable length strings. The I2C bus is used with binary data.

Your stack and data will be overwritten by the way you use dtostrf(). The minimum width is set to 5, yet your "buf" has only 4 bytes. With the zero-terminator, there is only place for three characters.
If the minimum width is 5, then make the buffer 20 or 30 or so. The "sizeof()" does not calculate how many characters there are. It takes the size of the array.

There is much more that can go wrong with a I2C bus between Arduino boards, both in software and hardware. I have not even started yet :scream:

2 Likes

Buck converters will not work; you need level shifters. As far as I know not all work for I2C; I think that something like https://www.sparkfun.com/products/12009 will.

1 Like

Ah, gotcha. Thanks!

Hey markd833! I tried what you suggested, if I transfer a single byte of data I still end up with the same issue, the received message is garbage after 2-3 requests and it causes the RTC to also give noise.

You still need bidirectional level shifters.

Any particular reason you decided to use I2C? I2C wasn't designed to operate between 2 boards like this.

You could use ordinary UART serial comms. Use a software serial port on the UNO at, say, 9600 or 19200 baud and you have the hardware serial port free for debugging etc.

Oof, I was worried I might get this response! Would you be willing to explain just a bit further, I'm an absolute novice in electronics and computer logic... is the issue that all of my other components are jumbling the I2C signal as it is being transferred? Is this something that could be partly accounted for by using insulation and/or shorter wires?

I'm certainly open to using a single board, but I don't know if A) the Giga can properly drive Arducam OV5642s and B) How to go about connecting a motor shield to drive the stepper motors AND the two cameras. Would you have any advice on this?

Thanks for all those corrections, I've been looking for clearer documentation and examples of dtostrf() and I2C protocols, but everything is either lacking on explanation or way over my head.

mark,

All of my previous Arduino projects required a single board and a handful of components/3D printed parts. So to answer your question, I chose the I2C master-slave method because it was the first thing that came up when I searched for "communication between two Arduinos" or "Master-slave Arduino" on Google. I did recently stumble upon alternatives like Softwire and UART but know so little on the topic I was unsure if they were good alternatives or not. Do you have any good recommendations on where I can read about the UART method you suggested? I'll try google search of course, but if you know of a good resource I'd really appreciate a link!

Please explain in first place why you need a "communication between two Arduinos" at all.

Why can't you put all functionality on the Giga/Mega?

You have connected all the GND in the picture, that is good.
However, if the current from the motor through a GND wire can disturb the I2C bus, then it can be hard to fix that.
Grounding is a craftsmanship on its own.

Do you know how to use millis() to do something once in a while ? Then you can do multiple tasks.

The Giga runs Mbed. That means that below the Arduino functions, there is a fully working multitasking system. You can use that to create different tasks.

A. I suppose that the Giga can use the SPI interface for a Arducam. But you better find a project that has it working.

B. I would have to look at the Sparkfun libraries. Can you give links to all the parts that you use ?

Sparkfun does not make all the modules compatible with both 5V and 3.3V boards. You should check all parts and all boards if you can connect them together.

I wrote something on Github, but I deliberately did not provide a full example, because so many things can go wrong.

Using a Serial/UART interface is better, but I still prefer to use a single Arduino board. Two boards is not twice as hard, it can be 10 times as hard.

2 Likes

One board - one problem (the sketch).
Two boards - three problems (two sketches and the communication between them...).

2 Likes

Hi noiasca! I'm using the UNO solely to drive the two cameras which are required for the project and the spectral triad which is not a necessity. I know how to connect a single Arducam OV5642 to an Arduino but have no idea how to connect two without the Arducam multi camera shield. I also don't know yet if Arducam OV5642s can interface with the Giga board, but I'll try to figure that out. If I can use a single board, obviously I would prefer to, but I'm worried doing so is above my level of competence as from the reading I did on driving multiple Arducams on a single Arduino microcontroller, it doesn't seem like a trivial task without the shield.

I have friends who do electrophysiology for a living, and can very well certify this statement. The amount of grounding they have to do is crazy.

I've had limited experience with millis() but I do understand how it's preferable to delay() for leaving the processor available to listen to other tasks. I used delay() because all the I2C examples I found online had delays.

Honestly, I've had the Giga for about four days now, so I still need to figure out what I can do with the Mbed system.

I've tried my Mega 2560 and Uno R4 and neither seemed to run the Arducam OV5642 examples, so I was unsure if the Giga could, but I'll try connecting a single camera on the Giga and find out if the example code works or not.

Most of the components I'm using are from Adafruit, the Triad sensor is my only Sparkfun component at the moment. List of components:

Thanks for the link, this was quite helpful and better documentation on the method than I've been able to find anywhere else.

I'll definitely give UART a try since I think putting everything on the Giga might be a bit above my abilities.

Thanks for the links.

The stepper motors are 350mA and the magnet is 400mA. Those are not high currents. It should be no problem. What do you use as power supply ?

The Adafruit motorshield V2 has been tested with the Due. There is a good chance that it will work with the Giga as well.

The DS3231 RTC works on every I2C bus, with 3.3V or 5V.

The DHT22 is not a good sensor, but you can always replace it with a better one.

The Arducam itself seems to be compatible with many boards.
The Arducam multi camera shield seems to use the SPI header in the middle of the board. I think it is compatible with the Giga board.
However, the motor shield and the multi-camera-shield can not be attached on top of each other :x:

What is wrong with the Sparkfun A57265x ? It should be connected to a 3.3V board. In theory it is compatible with the Giga and not with the Uno. Have you tried it :question: Can it be fixed ?

Right now for the coding and general troubleshooting I'm connecting the Arduinos straight to my laptop, but will connect them to a 12V 10A power supply for the real application. I don't currently have the motor shield connected but have been using the 12v power supply for it when I was coding some of the base functions for the motors.

Agreed, I was considering replacing them with AHT20s at some point but figured that wouldn't be what was causing my problem.

Good to know, my laptop doesn't play nice withe the Arducam host apps (used for testing functionality with their example code) so I can't test the cameras on the Giga right now, but I'll give it a shot tomorrow. If a single camera does work then I guess I could just use jumper cables to connect the camera shield to the Giga/motor shield pins?

Yeah, I tried the same sensor (tested, functional on UNO R3) on the Giga with their simple read test code and it never connected. I'll give it another shot, but I tried several times and then it worked just fine when I re-connected it to my UNO board.

Connecting multiple things to a SPI bus is always scary :grimacing: The hardware should allow to connect multiple things to the SPI bus. In theory, only the ChipSelect line should be a separate signal for each device.

You are asking a lot from your Arduino board, so you have to try it and find out how it works and fix problems.
Most tutorials and Youtube videos show how to connect one thing to one Arduino board.

Do you have a backup plan ? For example the cameras as WiFi webcams ?