Trouble getting two SPI devices working at the same time (nRF24L01 and SD card)

Hi, I’m attempting something and running into some trouble. I have an micro SD card breakout (the type you can give 5V inputs) and an nRF24L01 antenna. I’ve gotten both working separately, but now I want to combine them. What I want to do is, read some data off the SD card, then send it to another Arduino with the nRF, and repeat. I’m using a 5V/16MHz Arduino Pro Mini, and I’m selecting the right board/processor/port in the IDE. I have another totally separate circuit that acts as the receiver, which I’m positive is working correctly, as I’ll show below. Since I know both the nRF and SD cards can be power hungry, I’m powering everything with an external breadboard power stick, which should be able to supply enough current. There is a 3.3V rail for the nRF, and a 5V for both the Pro Mini and SD breakout.

I’ve done some reading on multiple SPI devices on the same Arduino, and I think I understand the concept. Basically, you connect both SPI devices to the same MOSI/MISO/SCK pins, but then you have a “chip select” pin for each device, and it seems like chip select pins are usually active low, meaning I have to keep those pins high to keep them off.

All correct so far?

Basically, I’m following this pic:

from this link: https://www.dorkbotpdx.org/blog/paul/better_spi_bus_design_in_3_steps

So I’ve written to the SD card successfully using the SD library (in one sketch), and I’ve been able to send info over the nRF to another Arduino (in another sketch), but I can’t seem to get them to work at the same time.

Here’s a broad overview of what I’m doing (I posted the code below, but this is just to explain it): I made some enable/disable functions for both the SD and RF, where I set their respective chip select pins (3 and 8 ) to low/high (respectively). Then, in the main loop, I have it enable the SD card, open/write/close it, then disable it, then delay for a couple seconds, then enable the nRF, send an integer, disable it, and repeat. I’m not actually using the SS pin as a chipselect pin, but I know you need to set it to output and high, so I’ve done that too (it’s pin 10 on the Pro Mini). I’ve also added a couple LEDs that should show you when each SPI device is enabled.

Here’s my code, trying to do the above:

#include <SPI.h>
#include "RF24.h"
#include <SD.h>

/****************** User Config ***************************/
/***      Set this radio as radio number 0 or 1         ***/
bool radioNumber = 1;

/* Hardware configuration: Set up nRF24L01 radio on SPI bus plus pins 7 & 8 */
RF24 radio(7,8);
/**********************************************************/

byte addresses[][6] = {"1Node","2Node"};
unsigned long counter = 1;


const int SLAVESELECTPIN_SD = 3;
const int SLAVESELECTPIN_RF = 8;

const int LEDPIN_SD = 5;
const int LEDPIN_RF = 2;

const int SS_pin = 10;



File myFile;

void enableRF(){
  digitalWrite(SLAVESELECTPIN_RF, LOW);
  digitalWrite(LEDPIN_RF, HIGH);
  Serial.println("Enabling RF...");
}

void disableRF(){
  digitalWrite(SLAVESELECTPIN_RF, HIGH);
  digitalWrite(LEDPIN_RF, LOW);
  Serial.println("Disabling RF...");
}

void enableSD(){
  digitalWrite(SLAVESELECTPIN_SD, LOW);
  digitalWrite(LEDPIN_SD, HIGH);
  Serial.println("Enabling SD...");
}

void disableSD(){
  digitalWrite(SLAVESELECTPIN_SD, HIGH);
  digitalWrite(LEDPIN_SD, LOW);
  Serial.println("Disabling SD...");
}


void setup() {
  pinMode(SS_pin, OUTPUT);
  pinMode(LEDPIN_SD, OUTPUT);
  pinMode(LEDPIN_RF, OUTPUT);
  digitalWrite(SS_pin, HIGH);
  digitalWrite(LEDPIN_SD, LOW);
  digitalWrite(LEDPIN_RF, LOW);

  pinMode(SLAVESELECTPIN_SD, OUTPUT);
  pinMode(SLAVESELECTPIN_RF, OUTPUT);
  disableRF();
  disableSD();
  
  Serial.begin(9600);
  
//  enableRF();
  radio.begin();

  // Set the PA Level low to prevent power supply related issues since this is a
 // getting_started sketch, and the likelihood of close proximity of the devices. RF24_PA_MAX is default.
  radio.setPALevel(RF24_PA_HIGH);
  radio.setDataRate(RF24_250KBPS);
  
  // Open a writing and reading pipe on each radio, with opposite addresses
  if(radioNumber){
    radio.openWritingPipe(addresses[1]);
    radio.openReadingPipe(1,addresses[0]);
  }else{
    radio.openWritingPipe(addresses[0]);
    radio.openReadingPipe(1,addresses[1]);
  }

//  disableRF();

  Serial.print("Initializing SD card...");

  if (!SD.begin(SLAVESELECTPIN_SD)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");

  myFile = SD.open("test.txt", FILE_WRITE);

  // if the file opened okay, write to it:
  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.println("testing 1, 2, 3.");
    // close the file:
    myFile.close();
    Serial.println("done.");
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }

  disableSD();

}

void loop() {
  int delayms = 4000;
  delay(delayms);

  
  enableSD();
  Serial.println("Writing to SD...");
  myFile = SD.open("test.txt", FILE_WRITE);

  // if the file opened okay, write to it:
  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.println("testing 1, 2, 3.");
    // close the file:
    myFile.close();
    Serial.println("done.");
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }

  disableSD();

  delay(delayms);
  
  enableRF();
  delay(50);
  Serial.print("Sending over RF...");
  unsigned long msg = counter;
  radio.write(&msg, sizeof(unsigned long) );
  Serial.print("Sent ");
  Serial.println(msg);

  disableRF();  


  
  counter++;
} // Loop

When I upload and run it, here’s the serial output:

Initializing SD card...initialization done.
Writing to test.txt...done.
Disabling SD...
Enabling SD...
Writing to SD...
Writing to test.txt...done.
Disabling SD...
Enabling RF...
Sending over RF...Sent 1
Disabling RF...
Enabling SD...
Writing to SD...
Writing to test.txt...done.
Disabling SD...
Enabling RF...
Sending over RF...Sent 2

(It writes to the SD card before the loop starts, so that’s why there are two at the beginning.)

However, I’m getting nothing on the receiver Arduino/nRF.

Now, here’s a clue… if I remove the SD card and breakout, and leave everything else exactly the same, and restart the sketch… the nRF successfully sends!

Initializing SD card...initialization failed!
Enabling SD...
Writing to SD...
error opening test.txt
Disabling SD...
Enabling RF...
Sending over RF...Sent 1
Disabling RF...
Enabling SD...
Writing to SD...
error opening test.txt
Disabling SD...
Enabling RF...
Sending over RF...Sent 2
Disabling RF...

And it appears on the other Arduino.

So it’s pretty clear that one is interfering with the other, and it seems like the SD card takes precedence when they’re both there. I also hope these two cases (SD module in, SD module out) demonstrate that my wiring is correct, because the SD card works in the first case, the nRF works in the second.

This document (warning, pdf) http://www.diyembedded.com/tutorials/nrf24l01_0/nrf24l01_tutorial_0.pdf says, regarding the nRF’s CSN pin:

The third pin is CSN, which stands for chip select not. This is the enable pin for
the SPI bus, and it is active low (hence the “not” in the name). You always want to keep
this pin high except when you are sending the device an SPI command or getting data on
the SPI bus from the chip.

However, I really don’t know what’s making the nRF not work when the SD card is in there. If I had to guess, I’d say that one of the chip select pins isn’t working correctly, or my enable/disable functions are doing something redundant (for example, maybe when you do radio.write(), it automatically makes the nRF CSN pin go low, so I don’t need to manually, or it would mess it up or something.

Is there something simple I’m missing? I think it’s almost there. Thanks in advance.

The SD library will automatically take care of the Slave Select and the configuration registers. You would have to look at the nRF library to make sure it did the same. If so, you probably shouldn't override the library's use of Slave Select pins.

johnwasser:
The SD library will automatically take care of the Slave Select and the configuration registers. You would have to look at the nRF library to make sure it did the same. If so, you probably shouldn't override the library's use of Slave Select pins.

Thank you for the advice! So just to make sure I understand what you mean, the SD library will make sure it's not communicating on the SPI bus after I do myFile.close() and such, and hopefully the RF library does the same? So, assuming that's true, I may not have to do anything besides just close the SD file before doing the nRF stuff?

I think the nRF handles it on its own as well. I used my voltmeter to monitor the nRF's CSN pin, and it seemed to stay high the entire time (meaning that when it went low, it was probably so fast it didn't catch it), meaning that it probably also wraps itself up.

Hmm, so I guess I'll try it with no manual enable/disable functions.

Hm, so I got rid of all the enable/disable manual functions, just letting it do its own thing. Here’s the code (very similar to before, minus those functions):

#include <SPI.h>
#include "RF24.h"
#include <SD.h>

/****************** User Config ***************************/
/***      Set this radio as radio number 0 or 1         ***/
bool radioNumber = 1;

/* Hardware configuration: Set up nRF24L01 radio on SPI bus plus pins 7 & 8 */
RF24 radio(7,8);
/**********************************************************/

byte addresses[][6] = {"1Node","2Node"};
unsigned long counter = 1;


const int SLAVESELECTPIN_SD = 3;
const int SLAVESELECTPIN_RF = 8;

const int LEDPIN_SD = 5;
const int LEDPIN_RF = 2;

const int SS_pin = 10;



File myFile;

void setup() {
  pinMode(SS_pin, OUTPUT);
  pinMode(LEDPIN_SD, OUTPUT);
  pinMode(LEDPIN_RF, OUTPUT);
  digitalWrite(SS_pin, HIGH);
  digitalWrite(LEDPIN_SD, LOW);
  digitalWrite(LEDPIN_RF, LOW);

  pinMode(SLAVESELECTPIN_SD, OUTPUT);
  pinMode(SLAVESELECTPIN_RF, OUTPUT);

  
  Serial.begin(9600);
  
  radio.begin();

  // Set the PA Level low to prevent power supply related issues since this is a
 // getting_started sketch, and the likelihood of close proximity of the devices. RF24_PA_MAX is default.
  radio.setPALevel(RF24_PA_HIGH);
  radio.setDataRate(RF24_250KBPS);
  
  // Open a writing and reading pipe on each radio, with opposite addresses
  if(radioNumber){
    radio.openWritingPipe(addresses[1]);
    radio.openReadingPipe(1,addresses[0]);
  }else{
    radio.openWritingPipe(addresses[0]);
    radio.openReadingPipe(1,addresses[1]);
  }

  Serial.print("Initializing SD card...");

  if (!SD.begin(SLAVESELECTPIN_SD)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");

  myFile = SD.open("test.txt", FILE_WRITE);

  // if the file opened okay, write to it:
  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.println("testing 1, 2, 3.");
    // close the file:
    myFile.close();
    Serial.println("done.");
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }

}

void loop() {
  int delayms = 1000;
  delay(delayms);

  Serial.println("Writing to SD...");
  myFile = SD.open("test.txt", FILE_WRITE);

  // if the file opened okay, write to it:
  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.println("testing 1, 2, 3.");
    // close the file:
    myFile.close();
    Serial.println("done.");
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }


  delay(delayms);
  
  delay(50);
 
  Serial.print("Sending over RF...");
  unsigned long msg = counter;
  radio.write(&msg, sizeof(unsigned long) );
  Serial.print("Sent ");
  Serial.println(msg);
  
  counter++;
} // Loop

However, it’s still not working. It’s writing to the SD card, but not successfully sending over the nRF again:

Initializing SD card...initialization done.
Writing to test.txt...done.
Writing to SD...
Writing to test.txt...done.
Sending over RF...Sent 1
Writing to SD...
Writing to test.txt...done.
Sending over RF...Sent 2

Hmm… any ideas?

This problem has come up several times. Have a look at this thread as an example.

...R

I decided to explore this and I have had no trouble getting the two devices to work. The following program is a combination of the code in SimpleTxAckPayload.ino (from my Simple nRF24L01+ Tutorial) and the listfiles.ino example that is part of the SDcard library. On another Uno I am running the program SimpleRxAckPayload.ino as the communication partner.

I hope the way I have derived the code is clear.

I should point out that my SD Card is mounted in a DIY holder made on a piece of veroboard - and I don’t know if that matters.

testSDnRF24.ino

// python-build-start
// action, verify
// board, arduino:avr:uno
// port, /dev/ttyACM0
// ide, 1.6.3
// python-build-end


// testSDnRF24.ino
// Combination of SimpleTxAckPayload - the master or the transmitter
//   abd ListFiles.ino

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

#include <SD.h>


#define CE_PIN  6 // for nRF24
#define CSN_PIN 7 // for nRF24

#define csPin 10 // for SDCard

const byte slaveAddress[5] = {'R','x','A','A','A'};

RF24 radio(CE_PIN, CSN_PIN); // Create a Radio

char dataToSend[10] = "Message 0";
char txNum = '0';
int ackData[2] = {-1, -1}; // to hold the two values coming from the slave
bool newData = false;


//===============

void setup() {

    Serial.begin(9600);
    Serial.println(F("Source File /mnt/sdb1/SGT-Prog/Arduino/SDandNRF/testSDnRF24.ino"));
    Serial.println("testSDnRF24 Starting");

}

//=============

void loop() {

    setupNRF();
    send();
    showData();
    delay(1000);

    setupSD();
    listFiles();
    delay(1000);
}

//================
//   These are the nRF24 functions
//================

void setupNRF() {
        radio.begin();
    radio.setDataRate( RF24_250KBPS );

    radio.enableAckPayload();

    radio.setRetries(3,5); // delay, count
}

void send() {

    bool rslt;
    radio.openWritingPipe(slaveAddress);
    rslt = radio.write( &dataToSend, sizeof(dataToSend) );
        // Always use sizeof() as it gives the size as the number of bytes.
        // For example if dataToSend was an int sizeof() would correctly return 2

    Serial.print("Data Sent ");
    Serial.print(dataToSend);
    if (rslt) {
        if ( radio.isAckPayloadAvailable() ) {
            radio.read(&ackData, sizeof(ackData));
            newData = true;
        }
        else {
            Serial.println("  Acknowledge but no data ");
        }
        updateMessage();
    }
    else {
        Serial.println("  Tx failed");
    }
 }


//=================

void showData() {
    if (newData == true) {
        Serial.print("  Acknowledge data ");
        Serial.print(ackData[0]);
        Serial.print(", ");
        Serial.println(ackData[1]);
        Serial.println();
        newData = false;
    }
}

//================

void updateMessage() {
        // so you can see that new data is being sent
    txNum += 1;
    if (txNum > '9') {
        txNum = '0';
    }
    dataToSend[8] = txNum;
}

//============
//  And these are the SD Card functions
//============

void setupSD() {
    if (!SD.begin(csPin)) {
    Serial.println("initialization failed!");
    }
    else {
        Serial.println("initialization done.");
    }
}

//===================

void listFiles() {

    File root = SD.open("/");
    printDirectory(root, 0);
    Serial.println("done! ----------");
    Serial.println();
}

//=================

void printDirectory(File dir, int numTabs) {
  while (true) {

    File entry =  dir.openNextFile();
    if (! entry) {
      // no more files
      break;
    }
    for (uint8_t i = 0; i < numTabs; i++) {
      Serial.print('\t');
    }
    Serial.print(entry.name());
    if (entry.isDirectory()) {
      Serial.println("/");
      printDirectory(entry, numTabs + 1);
    } else {
      // files have sizes, directories do not
      Serial.print("\t\t");
      Serial.println(entry.size(), DEC);
    }
    entry.close();
  }
}

I hope this helps.

…R

From the other thread just mentioned it might be the 5V-to-3.3V hardware on the midroSD breakout board that is not getting off the MISO line when the SD Card is not selected. That would interfere with all other SPI Bus chips. I think we need to know exactly which "microSD Breakout Board" you have. For example the SparkFun board has the OE pin held high so the outputs are enabled all the time:

The Adafruit breakout solves the problem by not level shifting the MISO line:

Hi Robin and John, very good points. I'll try that code, but probably the smarter thing to do first is just use another SD breakout I have, one with no electronics (that therefore runs all at 3.3V). I can just use a 3.3V Pro Mini instead, so I won't have to do any level shifting.

I'll report back soon, thank you!

Hey guys, it worked!! I replaced the SD 5V breakout with a simple one that has no active electronics (so runs at 3.3V), and instead used a 3.3V 8MHz Pro mini! I’m not using any of my enable/disable functions, and they handle it all themselves.

Thank you!

ranchmebro:
Hey guys, it worked!!

For the benefit of others will you be kind enough to post your working program and any necessary connection details.

...R

Sure, it’s nearly the same but with no manual enabling/disabling:

#include <SPI.h>
#include "RF24.h"
#include <SD.h>

/****************** User Config ***************************/
/***      Set this radio as radio number 0 or 1         ***/
bool radioNumber = 1;

/* Hardware configuration: Set up nRF24L01 radio on SPI bus plus pins 7 & 8 */
RF24 radio(7,8);
/**********************************************************/

byte addresses[][6] = {"1Node","2Node"};
unsigned long counter = 1;


const int SLAVESELECTPIN_SD = 3;
const int SLAVESELECTPIN_RF = 8;

const int LEDPIN_SD = 5;
const int LEDPIN_RF = 2;

const int SS_pin = 10;



File myFile;

void setup() {
  pinMode(SS_pin, OUTPUT);
  pinMode(LEDPIN_SD, OUTPUT);
  pinMode(LEDPIN_RF, OUTPUT);
  digitalWrite(SS_pin, HIGH);
  digitalWrite(LEDPIN_SD, LOW);
  digitalWrite(LEDPIN_RF, LOW);

  pinMode(SLAVESELECTPIN_SD, OUTPUT);
  pinMode(SLAVESELECTPIN_RF, OUTPUT);

  
  Serial.begin(9600);
  
  radio.begin();

  // Set the PA Level low to prevent power supply related issues since this is a
 // getting_started sketch, and the likelihood of close proximity of the devices. RF24_PA_MAX is default.
  radio.setPALevel(RF24_PA_HIGH);
  radio.setDataRate(RF24_250KBPS);
  
  // Open a writing and reading pipe on each radio, with opposite addresses
  if(radioNumber){
    radio.openWritingPipe(addresses[1]);
    radio.openReadingPipe(1,addresses[0]);
  }else{
    radio.openWritingPipe(addresses[0]);
    radio.openReadingPipe(1,addresses[1]);
  }

  Serial.print("Initializing SD card...");

  if (!SD.begin(SLAVESELECTPIN_SD)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");

  myFile = SD.open("test.txt", FILE_WRITE);

  // if the file opened okay, write to it:
  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.println("testing 1, 2, 3.");
    // close the file:
    myFile.close();
    Serial.println("done.");
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }

}

void loop() {
  int delayms = 100;
  delay(delayms);

  Serial.println("Writing to SD...");
  myFile = SD.open("test.txt", FILE_WRITE);

  // if the file opened okay, write to it:
  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.println("testing 1, 2, 3.");
    // close the file:
    myFile.close();
    Serial.println("done.");
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }


  delay(delayms);
  
  delay(50);
 
  Serial.print("Sending over RF...");
  unsigned long msg = counter;
  radio.write(&msg, sizeof(unsigned long) );
  Serial.print("Sent ");
  Serial.println(msg);
  
  counter++;
} // Loop

The connections are just the same SPI ones you’d use for any SPI device, and whatever chip select pins you use. It just works easily, I guess.

The main point is that the 5V SD breakout (that level shifts your arduino 5V outputs to 3.3V the SD card can use) doesn’t play nicely, and apparently does something weird.

ranchmebro:
The main point is that the 5V SD breakout (that level shifts your arduino 5V outputs to 3.3V the SD card can use) doesn't play nicely, and apparently does something weird.

Apparently it's like the SparkFun one that has the level shifter driving MISO (Master In/Slave Out) all the time, even when that slave is not selected. That makes it impossible for your other slaves to drive the bus when they are selected.
Bad design, unless you are only using one SPI device. :frowning: