I2C on an UNO R4 WiFi without using the Qwiic connector?

I was trying to complete an assignment my teacher gave me where I had to get two UNO R4 WiFi boards to communicate with each other over I2C, but it wasn't working at all. This was how I wired the boards (pretend these are R4 boards, TinkerCAD only had R3 boards):


I tried swapping the wiring to be A4 <-> A4 & A5 <-> A5, swapping the R4 boards with brand new R4s, using different wires, no luck. However, as soon as I swapped to using R3 boards, my sketch ran perfectly despite no modifications to it other then changing the board.
I did a quick search and found a few forum posts here with similar issues and I've gathered the following:

  • The R4 boards don't have pull-up resistors on SCL and SDA while the R3 boards do
  • The R4 boards have a qwiic connector which is specifically for connecting I2C devices
  • The qwiic connector does have pull-up resistors
  • I2C devices are active-low, which makes pull-up resistors necessary
  • The R4's microcontroller has pull-up resistors built-in that might be able to handle I2C

I don't have qwiic wires, so how do I use I2C on an Arduino R4 WiFi normally like how it would work on an Arduino R3? Or is that not possible?

Edit: I forgot to include my code snippets.
Here's the sketch I uploaded to the parent/ master board:

#include <Wire.h>

byte childAddress = 8;
byte answerSize = 10;

void setup() {
  Wire.begin();        // Initialize as a parent (no address)
  Serial.begin(9600);  // Start serial communication
  Serial.println("Parent Ready.");
}

void loop() {
  Serial.print("    Sent: ");
  Wire.beginTransmission(childAddress);  // Start transmission to device with address 8
  Wire.write("Hey Kiddo!");              // Send a string
  Wire.endTransmission();                // End the transmission
  Serial.println("Hey Kiddo!");

  delay(1000);  // Wait for a second

  Serial.println("Requesting data... ");
  Wire.requestFrom(childAddress, answerSize);  // Request 6 bytes from the child device
  Serial.print("Recieved: ");
  while (Wire.available()) {  // Child may send less than requested
    char c = Wire.read();     // Receive a byte as character
    Serial.print(c);          // Print the character
  }
  Serial.println();

  Serial.print("    Sent: ");
  Wire.beginTransmission(childAddress);  // Start transmission to device with address 8
  Wire.write("Knock knock");             // Send a string
  Wire.endTransmission();                // End the transmission
  Serial.println("Knock knock");

  delay(1000);

  Serial.println("Requesting data... ");
  Serial.print("Recieved: ");
  Wire.requestFrom(childAddress, answerSize);  // Request 6 bytes from the child device
  while (Wire.available()) {                   // Child may send less than requested
    char c = Wire.read();                      // Receive a byte as character
    Serial.print(c);                           // Print the character
  }
  Serial.println();

  Serial.print("    Sent: ");
  Wire.beginTransmission(childAddress);  // Start transmission to device with address 8
  Wire.write("Chicken butt");            // Send a string
  Wire.endTransmission();                // End the transmission
  Serial.println("Chicken butt");

  delay(1000);

  Serial.println("Requesting data... ");
  Wire.requestFrom(childAddress, answerSize);
  Serial.print("Recieved: ");
  while (Wire.available()) {
    char c = Wire.read();
    Serial.print(c);
  }
  Serial.println();
}

And this the sketch for the child:

#include <Wire.h>

byte childAddress = 8;
String message;  // create a global var to store message from parent

void setup() {
  Wire.begin(childAddress);      // Initialize as a child with address 8
  Wire.onReceive(receiveEvent);  // Function to run when data is received
  Wire.onRequest(requestEvent);  // Function to run when data is requested by parent
  Serial.begin(9600);            // Start serial communication

  Serial.println("Child Ready.");
}

void loop() {
  delay(100);
}

void receiveEvent(int howMany) {
  Serial.print("Received: ");

  message = "";  // set message string to null

  while (Wire.available()) {
    char c = Wire.read();
    message += c;  // concatenate the new character onto the message string
  }
  Serial.println(message);
}

void requestEvent() {
  Serial.print("    Sent: ");
  String reply;

  if (message == "Hey Kiddo!") {
    reply = "Hi big vro";
  };
  if (message == "Knock knock") {
    reply = "Whos there";
  };
  if (message == "Chicken butt") {
    reply = "BAHAHAHAHA";
  };

  Wire.write(reply.c_str(), reply.length());
  Serial.println(reply);
}

If you think it's the lack of pullup resistors causing the problem, why don't you add them? A couple of 4.7Ks and away you go.

Or the problem could be in the sketches you didn't show.

And the solution might be in the exact errors that occurred, which you also didn't show. "It wasn't working at all" does not contain useful information.

Welcome to the forum

Take a look at what https://docs.arduino.cc/tutorials/uno-r4-wifi/cheat-sheet/ has to say about I2C on the Uno R4

Please post your test sketch, using code tags when you do

Apologies, I meant that the sketch uploaded fine and no explicit errors occurred, just that nothing was being transferred over I2C.

Oops, I forgot to add the sketches. Just added them to the post now.

The official cheat sheet says that Wire.begin() should work, calling to SCL and SDA on the pins I wired together in the image I put. However, it was not working when I tried it on my R4 boards.

I don’t know if this will help, but on the R4 board, communication over the Qwiic I2C bus is via “wire1”, while over the other available I2C bus it is “wire“.

When a sketch is failing to perform as expected, usually one of the first things you do is add some Serial.print statements to see what's going on.

Anyhow, I just hooked up two R4 Minimas that were handy and loaded up dead simple master and slave sketches that I had laying around in them. Hooked up ground, SCL and SDA, with 4.7K pullups on the SCL and SDA lines.

Worked just fine.

Master

#include <Wire.h>

void setup() {
   Wire.begin();
}

void loop() {
   Wire.beginTransmission(8);
   Wire.write('1');
   Wire.endTransmission();
   delay(5000);
   Wire.beginTransmission(8);
   Wire.write('0'); 
   Wire.endTransmission();
   delay(5000);
}

Slave

#include <Wire.h>

const byte SLAVE_ADDR = 8;
volatile byte receivedCommand = 0;
unsigned long previousMillis = 0;
byte ledState = LOW;
unsigned interval = 0;
const byte ledPin = 2;

void setup() {
   pinMode(ledPin, OUTPUT);
   for( int i=0; i<20; ++i ) {
      digitalWrite(ledPin, !digitalRead(ledPin));
      delay(250);
   }
   Wire.begin(SLAVE_ADDR);
   Wire.onReceive(receiveEvent);
}

void loop() {
   if( receivedCommand != 0 ) {
      // Set interval based on command
      switch( receivedCommand ) {
         case '1':
            interval = 1000;
            break;  // 1-second blinks
         case '0':
            interval = 100;
            break;  // 0.1-second blinks
         default:
            interval = 0;
            ledState = LOW;
            digitalWrite(ledPin, ledState);
            break;
      }
      receivedCommand = 0;        // Reset command
      previousMillis = millis();  // Reset timer
   }

   // Non-blocking blink logic
   if( interval > 0 && (millis() - previousMillis >= interval) ) {
      previousMillis = millis();
      ledState = !ledState;
      digitalWrite(ledPin, ledState);
   }
}

void receiveEvent(int numBytes) {
   (void)numBytes;
   if( Wire.available() ) {
      receivedCommand = Wire.read();
   }
}

I don't have additional pull-up resistors on the R4. Do I have to add external pull-ups?

Do as you wish. I've told you what the answer is, whether you listen is up to you.

1 Like

I2C is an open drain system, If you don't add the pull ups as mentioned above you will never get the link to work as the resistors are needed for the bus the operate.

There are no pull ups on the UNO R4 so you must add them externally.

The resistors pull the data and clock lines up to VCC and this is a logic 1, Logic 0 is then achieved by using the open drain inputs of the I2C on the Arduinos to pull down to ground

Ah, I see. I thought there might be a way to not have to add external pull-ups, but it seems I assumed incorrectly. Thanks everyone for the help!