I2C Failing When Using SPI Devices At The Same Time

Hello everyone, I have been very grateful to all the help I've received by reading through this forum, but I have run into a problem that I cannot figure out. For some background information, my project is creating a remote for controlling a B-17 turret (this is a senior design project in college).

I'm using an Arduino Uno R3 and I have a display (working, with a navigable menu), RF Module (working), and a CAN module (working). All these components are on a SPI bus (important later). However, to move the turret, I have a joystick. Since this project has some budget and they want the remote to last as long as possible, I got this joystick off DigiKey: Grayhill 67A-DF-3C-030C (datasheet). It's a hall-effect joystick that communicates over I2C.

I created a test file and it works great- I can read the position of the joystick reliably over the I2C bus (and it's fun to move around). However, when I added the joystick code to the rest of the code (with the display, RF module, and CAN module), the program hung/froze when I called Wire.readFrom().

After much digging (using my test file as a playground), I found that once I created a display, RF module, or CAN module object and initialized it, that's when the I2C stopped working. It didn't matter which one I initialized they all made the I2C bus stop working.

After some more digging, I narrowed the problem down to a while() loop in twi_readFrom() (part of the Wire library that does more of the heavy lifting). The while() loop essentially just waits for all the data to be read. However, it never exits the loop. I'm not entirely sure how I2C works, but it looks like while this loop is running, each time the Arduino gets I2C data, an interrupt runs in the background and stores the data. Once the data transfer is complete, it sets a variable to exit that while() loop. This makes me think that the Arduino is never receiving the data.

Anyways, below is my test code and a snippet of the while() loop that it gets stuck in. Has anyone else run into this problem? Is it a problem with using the SPI and I2C busses at the same time? I'm using the default pins for both protocols. Any help would be appreciated!

My code (tft is the display, and radio is the RF module):

#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <SPI.h>
#include <TFT_ILI9341.h>
#include <TFT_FastPin.h>
#include <turret_protocol.h>
#include <Wire.h>
#include <RF24.h>

int8_t joy_x, joy_y = 0;

TFT_ILI9341 tft = TFT_ILI9341();
//static RF24 radio(RADIO_CE_PIN,RADIO_CSN_PIN);

void setup()
{
  //Wire.begin();
  Serial.begin(115200);
  tft.init();
  //tft.setRotation(3);
/*
  if(!radio.begin())
  {
    Serial.println("Radio not functional");
  } else {
  }
  */
}

void loop()
{
  handle_joystick();  

  delay(500);
}


void handle_joystick()
{
  if(true)
  {

    if(Wire.requestFrom(64, 2) == 0)
    {
      // error
    }
    else
    {
      joy_x = Wire.read();
      joy_y = Wire.read();
      // Handle X
      if((joy_x & 0x80) != 0)
      {
        // Negative X
        joy_x = joy_x - 255;
      }
      // Handle Y
      if((joy_y & 0x80) != 0)
      {
        // Negative Y
        joy_y = joy_y - 255;
      }
      Serial.print("Joystick X = ");
      Serial.print(joy_x);
      Serial.print(" / ");      
      Serial.print("Joystick Y = ");
      Serial.println(joy_y);      
    }
  }
}

And here is the while() loop that it gets stuck in (in twi_readFrom() function in twi.c):

  // wait for read operation to complete
  startMicros = micros();

  while(TWI_MRX == twi_state){
    if((twi_timeout_us > 0ul) && ((micros() - startMicros) > twi_timeout_us)) {
      twi_handleTimeout(twi_do_reset_on_timeout);
      return 0;
    }
  }
  

Look closer at the returned byte count. You can not read 2 bytes if only 1 was received. But this only helps to keep your code running.

Possibly a longer I2C timeout can help - setWireTimeout().

How on earth can you run all those libraries on a Arduino Uno ?
If you add a OLED display to a Arduino Uno, then most of the SRAM is already in use. You have a ILI9341 display, that is not for an Arduino Uno.

If you want, you could add a function to measure the amount of free SRAM:
https://learn.adafruit.com/memories-of-an-arduino/measuring-free-memory#sram-370031

1 Like

You are aware that there is a bug in that code? See Classic Nano has too much RAM. I did report (at the time) it but it still does not seem to be fixed.

Thank you. I remembered there was a problem with it, but forgot what it was. I will try to remember it this time :grimacing:

1 Like

Hello everyone, I figured it out. I'll detail what happened below for anyone else stuck in the same boat. Sorry for the late response - this project was for school and with finals finally done, I have some time to respond to this. I looked into everyone's replies to no avail.

I tried increasing the I2C timeout but that didn't help. I used an oscilloscope on the data pin and saw that there was an initial burst of data sent, then it was just noise on the line. When I didn't initialize SPI and I2C worked, there was never any 'noise on the line', it was square pulses like it should be.

After some more testing, I narrowed the problem down to when SPI.begin() was called. As soon as that was called, I2C stopped working. I dug into the SPI.begin() function and couldn't find anything that would impede I2C. The only thing I noticed was that it called noInterrupts() at the beginning of the function but never Interrupts() at the end of the function. I tried messing around with that, but that got me nowhere.

I looked into the SRAM and all signs pointed to the Uno having enough. The library I used for the ILI9341 display was specifically designed for the Uno, and worked very well (I inherited the library from a previous team, so I do not have a link. I could try searching for one if people are interested in the library). And after getting it to work, I can confidently say that the SRAM was not an issue (an observable issue, at least).

I found the solution while on KiCad looking at the pin assignment for another component. There, in the schematic, I saw it. Next to some of the analog input pins, there was this:
SDA / A4
SCL / A5

Judging from that, A4 and A5 were directly tied into the SCL and SDA pins of the I2C interface. And wouldn't you know it, I had a button connected to pin A4 (I was using it as a digital pin). I broke that trace and routed that button to use the only other pin I had available, and tried to use SPI and I2C at the same time. Lo and behold, it worked! I could use both SPI and I2C at the same time.

I'm not sure why calling SPI.begin() broke it if there was a button connected to A4 (it wasn't attached to an interrupt or anything), since it worked just fine with the button connected and SPI.begin() not called. I had not pressed the button while trying to troubleshoot since the button wasn't set up yet (it had a specific function that I had not written at the time). Anyways, at least there is a happy ending to this story (my team was the only one to receive 100% on our senior design project).

TL;DR: if your I2C isn't working, especially if you call SPI.begin(), make sure you have nothing connected to pins A4 and A5 as the Uno ties those to SDA and SCL.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.