I2C issue with RPI Pico and Nano with both as master and slave

Hello everyone,

I have a project where two microcontrollers need to communicate with each other via I2C and both must be master and slave.

While I was using two Arduino Nanos it went fine, but then I had to switch one of them for a Raspberry Pi Pico and it no longer works.

If I use just one of them as a master and the other as a slave, it works. However, when I try to make both master and slave, the Nano stops working as a slave. That is, it no longer receives data sent by the Pico as master, nor it sends data back when Pico asks for it.

To be precise, when both are configured as master and slave, data sent by the Pico as master triggers the onReceive() event on the Nano, but after that, when I perform Wire.read(), there are no bytes on the buffer to be read. When the Pico asks for data as master, the onRequest() event on the Nano isn't even triggered.

What could be the issue here? Below is the code I'm using for both boards. The grounds are connected of course and I'm not using pull-up resistors as it seems that the Rpi Pico already has them. I tried the same setup with a 5k resistor on each line and it didn't work.

For the Pico, I'm using the Earlephilhower library instead of the Arduino one, that is why you see the Wire.setSDA(0) and Wire.setSCL(1) commands on the setup, which configure the pins that are being used for I2C.

Edit: I forgot to mention that I'm using a logic level shifter between the two boards, as Nano works at 5V and Pico at 3.3V.

Pico code:

#include <Wire.h>

void receiveEvent(int nBytes) {
  Serial.print("RECEIVED FROM MASTER: ");

  while (Wire.available())
    Serial.print(Wire.read());

  Serial.println();
}

void requestEvent() {
  byte val = 9;

  Serial.print("SENDING TO MASTER: ");
  Serial.println(val);

  Wire.write(val);
}

void sendCode() {
  byte val = 13;

  Serial.print("SENDING: ");
  Serial.println(val);

  Wire.beginTransmission(0);
  Wire.write(val);
  Wire.endTransmission();
}

void requestCode() {
  Wire.requestFrom(0, 1);

  Serial.print("RECEIVED FROM SLAVE: ");

  while (Wire.available())
    Serial.print(Wire.read());

  Serial.println();
}

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

  Wire.setSDA(0);
  Wire.setSCL(1);

  Wire.begin(1);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);

  delay(100);
}

void loop() {
  requestCode();
  delay(500);
  sendCode();
  delay(500);
}

Nano code:

#include <Wire.h>

void receiveEvent(int nBytes) {
  Serial.print("RECEIVED FROM MASTER: ");

  while (Wire.available())
    Serial.print(Wire.read());

  Serial.println();
}

void requestEvent() {
  byte val = 10;

  Serial.print("SENDING TO MASTER: ");
  Serial.println(val);

  Wire.write(val);
}

void sendCode() {
  byte val = 11;

  Serial.print("SENDING TO SLAVE: ");
  Serial.println(val);

  Wire.beginTransmission(1);
  Wire.write(val);
  Wire.endTransmission();
}

void requestCode() {
  Wire.requestFrom(1, 1);

  Serial.print("RECEIVED FROM SLAVE: ");

  while (Wire.available())
    Serial.print(Wire.read());

  Serial.println();
}

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

  Wire.begin(0);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);

  delay(100);
}

void loop() {  
  delay(500);
}

The result printed on the serial monitor for this code is:

Pico:
image

Nano:
image

In this code, if I just declare the Pico as master only, that is, change Wire.begin(1) to Wire.begin(), it works normally.

Any help is appreciated! Thanks

Why do you want to use the I2C bus to communicate between Arduino boards ? The I2C bus is not designed for that. What is your project ? Can you give us a broader view of your project ?

The Nano runs at 3.3V and the Arduino Nano at 5V. That means you have a voltage conflict on the I2C bus. You can fix that with a I2C level shifter.

When you used two Nano boards, it was not running fine. It was running terrible :scream: but you didn't know it :grimacing:

Do you know if a Raspberry Pi Pico board with the Earlephilhower via a I2C level shifter to a Arduino Nano works ? If you can not find an example of that, it might not work.

You should not turn the Nano into a Master. A multi-Master bus creates a multitude of problems.

The I2C address 0x00 is reserved for the General Call. The General Call is perhaps only partially implemented, so the result of your code is unpredictable. The first valid address might be 0x08, but I suggest to start at 0x20.

Projects fail when using the I2C bus between Arduino boards.
Do you want to use long wires ? Then the I2C bus will fail.
Have you connected the GNDs ? The I2C bus has three wires: SDA, SCL and GND. The most important wire is the GND.

1 Like

Hi Koepel,

Thanks for the reply. I tried to explain it in more details bellow:

Basically I have a robot arm with a main controller using the Pico (it generates trajectories, sends motor commands via UART to bus servos, etc) and a teaching pendant with the Nano. They are connected through a 1m cable. The teaching pendant has 20+ buttons connected to it as a button matrix and also an LCD. Its main job is to send the button currently being pressed by the user to the robot controller (Pico), manage what's being displayed on the LCD, and at times send commands to the controller indicating that the user selected something on the LCD.

Both need to be able to work as master/slave. For example, when the user is pressing a button to move a specific robot joint, the trajectory is generated at a specified sampling rate (10ms), so every 10ms the Pico, as master, requests to the Nano which button the user is pressing and continues to move the robot based on it. At other times, for instance, when the user selects the option on the LCD to create a new trajectory, delete one, or modify something, the Nano needs to act as master and send a message to Pico notifying it so that it will do what's required. The Pico is also connected to an SD Card module via SPI and writes/reads from it.

It's kinda hard to explain here, but I definitely need both to work as master and slave, because in many cases one needs to send a message to the other without the other asking for it. It basically works as a state machine, in which both boards can perform actions that will change the current state, and they have to send a message to the other board letting it know.

I'm using I2C because Nano only has one Serial port that I'm already using to debug the project via the serial monitor and SPI uses four wires and seemed more complicated. Doing a general research the main answer was that I2C was the best choice to communicate between two Arduinos.

Sorry, I forgot to mention that I'm using a logic level shifter between the two boards, I've added that to the original post.

By research I've found out that I2C is indeed intended to work with multi-master, plus it was working for me before, so I don't believe that's the issue.

I've tried your suggestion of using addresses above 0x20 but nothing changed.

As I mentioned, when using two Nanos it was working normally, with no issues whatsoever regarding communication.

And yes the grounds are connected.

Multi master I2C systems are possible but are not too reliable and have over the years wasted a lot of my time. You are best avoiding them.

If you are dead set on using on then this article might help you get started.

And as mentioned you were either lucky or they were not working like you thought they were.

1 Like

Some notes:

Print only after serving the the events. Else the onRequest event may terminate with timeout...

Print the status and number of bytes transferred, as far as available: nBytes and Wire.requestFrom() etc. results.

Never use I2C addresses below 9. On both sides.

Use pullup resistors on both sides of the level shifter.

Is delay() the right way to pause the Pico, without loosing requests arriving in between?

If you configure one as master and one as slave, then only assign an address to the master, does it work as before? If only the assignment of a slave address causes trouble then something is wrong with the library.

Have you tried other pins on the Pico?

1 Like

I'm curious were you get the information that the I2C bus was a good option for your project ? Everyone who understands the I2C bus knows that it is a bad idea.
I can almost guarantee that your project will fail.

I have never seen a good implementation of a multi-Master I2C bus with the Arduino Wire library. You might think that you are the only one in the world who can do that, but then you have to check the return values of "Wire.endTransmssion()" and "Wire.requestFrom()" and implement retries and stop the project after too many retries. I would stay far away from that, because even then not every situation is handled properly.

The I2C bus is not a fault tolerant bus, it should work 100% all the time. If you have motors and a cable of 1 meter, then you will get noise on the I2C bus. If it works all the time, then it will fail when you present your project. A glitch on the I2C bus might halt your project.

I made a page about the I2C bus and its problems to scare people who have the wrong idea with the word "bus" :ghost: : How to make a reliable I2C bus · Koepel/How-to-use-the-Arduino-Wire-library Wiki · GitHub
There is a link on that page to the official I2C standard: UM10204.pdf If you go to page 54, paragraph 7.5, then you see that they give you a length for the I2C bus of 10cm. If you want more, then you have to know how to do that.

When you use Serial communication, then you should not use your I2C level shifter, because it turns a strong RX/TX signal into a weak signal.

1 Like

Sorry for being off topic, but why do you keep on repeating this like a mantra, even if there are plenty of well working examples and applications of inter-µC/Arduino I2C communication? It's simply wrong to give users the impression that inter-µC/Arduino I2C is a bad idea per se.

Jan

2 Likes

Yes, it certainly feels like a mantra to me as well. But I'm trying to keep it mild :heart:

The Slave mode for the I2C bus works for a Arduino Uno (and other ATmega328P boards), but that took many years. The Slave mode for other processors causes still a lot of trouble.
Then I see projects on this forum with long wires and motors that fail. It feels so bad when I see someone working hard on a project that might not work in the end :cry:
So my advice is simple: Don't do it.

I have myself a project with a Arduino Mega as Master and a few ATmega328P as Slaves. It works, but when I add libraries to the Slaves (such as OneWire, DHT, Neopixel) then it can not withstand the most simple stress-test.

If someone does not know the I2C bus, then the official standard gives 10 cm for the length of the I2C bus. You can not argue with that :wink:

Talking about mantras, here is another one: https://www.google.com/search?q=site%3Agithub.com+issue+koepel+requestfrom

This advice is not useful, you're overgeneralizing. Do it right would be an adequate advice.

I continue to have lots of fun with I2C. Up to now, I got everything to work I wanted to. For my area of application, I2C is perfect.

I'm sure I'm doing all kinds of things wrong, but it's sure not wrong enough to break things.

Jan

So far.

That is the attitude taken by so many of the people on the Instructables web site. They think if it will work for them it will work for anyone, this is not true, and you are deluding yourself if you think it is.

Have you tested how close your setup is to breaking? Do you even have the test equipment to verify this?

Do you even lift?

Jan

In the I2C-10cm case it's easy to use a longer cable until it's the cable length that starts making trouble. Similarly for frequency and pullup resistors.

More problematic if the devices on the bus play a role, with clock stretching or weak outputs. Or hostile environment. Such factors have to be checked with every project.

As @Grumpy_Mike and @Koepel have suggested, multi-master I2C is probably not the proper solution to your problem. Why not replace the Nano with another Pico since they are cheap and available and have multiple UARTs? I2C is meant to communicate between closely coupled integrated circuits and just because it’s possible to use as a serial communication medium between microcontroller boards doesn’t mean it’s the right interface. As mantras go, @Koepel’s makes sense.

2 Likes

If I2C works between Nanos but not between Nano and Pico, why use more of those crappy Picos?

1 Like

I believe I was clear in my suggestion that UART serial was a better solution therefore a board with more UARTs would be appropriate.

1 Like

How can we know without knowing the project?

Why do we need a different communication method only because a Pico does not work as expected?

How can we learn about I2C without exercise?
"If you have problems with I2C then don't use it" and
"If Pico has problems with Arduino I2C then don't use Arduino" and
"If you have no problems with I2C still don't use it"
are poor mantras :frowning:

1 Like

OP’s description is clear enough to allow some speculation and OP stated that they decided to use I2C because the Nano only had one hardware serial port and it was being used for debug.

1 Like

That means that any Arduino without multiple Serial ports is unusable (for you)?

That’s a spectacular leap of logic. No, it means that I believe that using a couple of $5 microcontroller boards is a practical solution.

@Grumpy_Mike
Thanks for pointing out this reference, but in my limited knowledge this seems like a solution for when you have both masters continuously working in a scenario where there could be collisions and whatnot. In my case even when only one master (Pico) is sending or asking data it doesn't work. So I don't see how it would solve the issue.

@DrDiettrich
Thanks, I tried everything you said but no luck. I also tried using different pins for the i2c but the issue persists.

This is exactly what happens, when I assign a slave address to the Pico, the Nano stops answering to its onRequest() and onReceive events triggered by Pico asking for data or sending it.

@Koepel
I understand what you're saying and thanks for pointing out all the references, but how can this be the problem if the same setup works when the Pico is not a slave? If cable length or things like that were the issue, then it shouldn't work at all.

@PapaG
I don't have much experience, but from everything that was said, it appears that the "standard" and most reliable way to make two boards communicate through let's say a 1 to 2 meters wire would be UART? I'm asking as a general question since I'll be doing it in other projects. And then use I2C to communicate with peripherals that are close to the board?

Using two Picos instead of a Nano seems to be a practical solution, but I have soldered the buttons, 3D printed the button box, etc, to fit the Nano, so I guess if I can't make both work as master/slave I'll reprogram it using Nano as slave and Pico as master only. It will be harder but it's doable. I through this would be an easy to solve problem lol

1 Like