SPI chip select for SD card, Ethernet and DS1307 RTC together on Mega 2560 R3

Hi,
I have a problem getting the SDcard, Ethernet and a real time clock DS1307 running concurrently on an Arduino Mega 2560 R3.
I know there are several posts regarding the SPI interface and using different CS for each device, but having read them, they haven't helped me solve my problem. Sometimes my code will return success with SD.begin(), most times it won't. I suspect my lack of understanding of SPI has made me write bad code - I would be very grateful if anyone can see the problem. Code below-

#include <SPI.h>
#include <SD.h>
#include <Ethernet.h>
#include <Wire.h>
#include <RTC_DS3234.h>

/* SD card attached to SPI bus as follows: (commented out definitions are defined in pins_arduino.h)*/
/* const int MOSI = 51;
const int MISO = 50;
const int SCLK = 52;
const int SS = 53;*/
const int CS_RTC = 7;  //chip select pin for RTC
const int CS_SD = 4; //chip select for SD card
byte mac[] = {0x90, 0xA2, 0xDA, 0x0D, 0xAB, 0x8F}; // UNIT 2
File dataFile;

RTC_DS3234 RTC(CS_RTC);// Create an RTC instance, using the chip select pin it's connected to

void setup()
{
  Serial.begin(9600);
  SPI_Initialise();
  SdCard_Initialise();
  InitialiseEthernet();
  Initialise_RTC();
}

void loop() 
{
  delay(3000);
  PrintTime();
  PrintTimeToFile();
}

void SPI_Initialise()
{
  pinMode(SS, OUTPUT); //sets Mega in SPI master mode.
  SPI.begin();
  Serial.println("SPI Initialised.");
}

void SdCard_Initialise(){
  if (!SD.begin(CS_SD)) {
    Serial.println("SD Card failed");
    return;
  }
  Serial.println("SD Card initialized");
}

void InitialiseEthernet()
{
  if (Ethernet.begin(mac))
  { 
    Serial.println("Ethernet Initialised.");
    delay(2000);
    Serial.print("My IP address is ");
    Serial.println(Ethernet.localIP());
  }
  else
  {
    Serial.println("Ethernet failed to Initialise.");
  }
}

void Initialise_RTC()
{
  if ( RTC.begin())
  {  
    Serial.println("RTC Initialised.");
  }
  else
  {
    Serial.println("RTC failed to Initialise.");
  }
}

void PrintTimeToFile()
{
  const int len = 32;
  static char buf[len];
  dataFile = SD.open("Time.txt", FILE_WRITE);
  if (dataFile)
  {
    DateTime now = RTC.now();
    dataFile.println(now.toString(buf,len));
    dataFile.close();
  }
  else
  {
    Serial.println("SD failed to open!");
  }
}

void PrintTime()
{
  const int len = 32;
  static char buf[len];
  DateTime now = RTC.now();
  Serial.println(now.toString(buf,len));
}

The output from the serial port is below:-

SPI Initialised.
SD Card failed
Ethernet Initialised.
My IP address is 172.16.205.195
RTC Initialised.
Sep 09 2013 17:26:11
SD failed to open!
Sep 09 2013 17:26:14
SD failed to open!
Sep 09 2013 17:26:17
SD failed to open!

Any help much appreciated,
Fin

You may need to keep this one in
const int SS = 53;

You can also try SDfat.h instead of SD.h
http://code.google.com/p/sdfatlib/

Do you have the SPI pins buffered/translated down to 3.3V for the SD card?

If you use a Mega, try this setup:

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

  // disable w5100 SPI
  pinMode(10,OUTPUT);
  digitalWrite(10,HIGH);

  SPI_Initialise();
  SdCard_Initialise();
  InitialiseEthernet();
  Initialise_RTC();
}

The RTC is not SPI, is it?

DS1307 is not, DS3234 is.

CrossRoads:
DS1307 is not, DS3234 is.
Mixed-signal and digital signal processing ICs | Analog Devices

Ah - I am sorry, I made a mistake in the subject title. I am using a DS3234 and hence SPI.

Still not a problem. What pin are you using for the RTC SPI slave select?

edit: Never mind. I see you are using D7. So new setup

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

  // disable RTC SPI
  pinMode(7,OUTPUT);
  digitalWrite(7,HIGH);

  // disable w5100 SPI
  pinMode(10,OUTPUT);
  digitalWrite(10,HIGH);

  SPI_Initialise();
  SdCard_Initialise();
  InitialiseEthernet();
  Initialise_RTC();
}

I have not checked the SPI mode, bit order, or speed of the RTC. You may need to change one or more of those settings, but that is not a problem.

CrossRoads:
You may need to keep this one in
const int SS = 53;

You can also try SDfat.h instead of SD.h
Google Code Archive - Long-term storage for Google Code Project Hosting.

Do you have the SPI pins buffered/translated down to 3.3V for the SD card?

I have tried keeping the SS = 53 definition in, but it didn't work - I'll put it back though in case.
I am using the microSD card on the Ethernet shield, piggy-backed onto the Mega.

The RTC is SPI mode 3. Everything else looks ok. the bit order and speed are the same. When you access the RTC, use something like this.

SPI.setDataMode(SPI_MODE3);

// do your RTC stuff

SPI.setDataMode(SPI_MODE0);

Here is the setup with the RTC using mode3

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

  // disable RTC SPI
  pinMode(7,OUTPUT);
  digitalWrite(7,HIGH);

  // disable w5100 SPI
  pinMode(10,OUTPUT);
  digitalWrite(10,HIGH);

  SPI_Initialise();
  SdCard_Initialise();
  InitialiseEthernet();

  SPI.setDataMode(SPI_MODE3); 
  Initialise_RTC();
  SPI.setDataMode(SPI_MODE0);
}

edit: The SD and w5100 are both SPI_MODE0, MSBFIRST and SPI_CLOCK_DIV4.

SurferTim:
The RTC is SPI mode 3. Everything else looks ok. the bit order and speed are the same. When you access the RTC, use something like this.

SPI.setDataMode(SPI_MODE3);

// do your RTC stuff

SPI.setDataMode(SPI_MODE0);




Here is the setup with the RTC using mode3


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

// disable RTC SPI
 pinMode(7,OUTPUT);
 digitalWrite(7,HIGH);

// disable w5100 SPI
 pinMode(10,OUTPUT);
 digitalWrite(10,HIGH);

SPI_Initialise();
 SdCard_Initialise();
 InitialiseEthernet();

SPI.setDataMode(SPI_MODE3);
 Initialise_RTC();
 SPI.setDataMode(SPI_MODE0);
}



edit: The SD and w5100 are both SPI_MODE0, MSBFIRST and SPI_CLOCK_DIV4.

Well, I am at a loss now. I have made the changes suggested, but still had no luck, except occasionally after a recompile and upload it would work when I opened the serial monitor (Arduino 1.5.2 IDE). After this recent recompile, it all works as expected, even if I take out half the code. The final code is pasted in below, with the commented out lines originally in, until it started working, at which point I started removing lines to try and get it to fail. From a hardware point of view I have checked all wires etc, and even tried ferrites on the SPI wires to the RTC board, but nothing helped. Following an accidental unplug of power and USB during an upload, I recompiled and uploaded again, and it all just started working. I'm a little concerned about not knowing why it is now working, but fingers crossed.
Thanks again for everyone's help.
Fin

#include <SPI.h>
#include <SdFat.h>
#include <Ethernet.h>
#include <Wire.h>
#include <RTC_DS3234.h>

/* SD card attached to SPI bus as follows: (commented out definitions are defined in pins_arduino.h)*/
/* const int MOSI = 51;
 const int MISO = 50;
 const int SCLK = 52;
 const int SS = 53;*/
const int CS_RTC = 7;  //chip select pin for RTC
const int CS_SD = 4; //chip select for SD card
const int CS_ETH = 10; // chip select for w5100 Ethernet
byte mac[] = {0x90, 0xA2, 0xDA, 0x0D, 0xAB, 0x8F}; // UNIT 2

// file system object
SdFat sd;
SdFile dataFile;

RTC_DS3234 RTC(CS_RTC);// Create an RTC instance, using the chip select pin it's connected to

void setup()
{
  Serial.begin(9600);
  while(!Serial){} //wait for Mega
  //disable RTC SPI
  //pinMode(CS_RTC, OUTPUT);
  //digitalWrite(CS_RTC, HIGH);

  //disable w5100 SPI
  //pinMode(CS_ETH, OUTPUT);
  //digitalWrite(CS_ETH, HIGH);

  SPI_Initialise();
  //SPI.setDataMode(SPI_MODE0);
  SdCard_Initialise();
  InitialiseEthernet();
  //SPI.setDataMode(SPI_MODE3);
  Initialise_RTC();
  //SPI.setDataMode(SPI_MODE0);
}

void loop() 
{
  delay(3000);
  PrintTimeToFile();
  PrintTime();
}

void SPI_Initialise()
{
  pinMode(SS, OUTPUT); //sets Mega in SPI master mode.
  SPI.begin();
  Serial.println("SPI Initialised.");
}

void SdCard_Initialise()
{
  if (!sd.begin(CS_SD, SPI_HALF_SPEED))
  {
    Serial.println("SD Card failed");
    return;
  }
  Serial.println("SD Card Initialised");
}

void InitialiseEthernet()
{
  if (Ethernet.begin(mac))
  { 
    Serial.println("Ethernet Initialised.");
    delay(2000);
    Serial.print("My IP address is ");
    Serial.println(Ethernet.localIP());
  }
  else
  {
    Serial.println("Ethernet failed to Initialise.");
  }
}

void Initialise_RTC()
{
  if ( RTC.begin())
  {  
    RTC.adjust(DateTime(__DATE__, __TIME__));
    Serial.println("RTC Initialised.");
  }
  else
  {
    Serial.println("RTC failed to Initialise.");
  }
  digitalWrite(CS_RTC, HIGH);
}

void PrintTimeToFile()
{
  const int len = 32;
  static char buf[len];
  if (dataFile.open("Time.txt", O_CREAT))
  {
    DateTime now = RTC.now();
    dataFile.println(now.toString(buf,len));
    dataFile.close();
  }
  else
  {
    Serial.println("SD failed to open!");
  }
}

void PrintTime()
{
  const int len = 32;
  static char buf[len];
  DateTime now = RTC.now();
  Serial.println(now.toString(buf,len));
}

So when you try to initialize the SD, you have the SD, the RTC, and the w5100 all enabled (active) on the SPI bus. And you wonder why it doesn't work now and then? Keep your fingers crossed. I guess you go by the mantra "I would rather be lucky than good".

void setup()
{
  Serial.begin(9600);
  while(!Serial){} //wait for Mega

  //disable RTC SPI
  //pinMode(CS_RTC, OUTPUT);
  //digitalWrite(CS_RTC, HIGH);

// without the code above 
// The RTC SPI is active

  //disable w5100 SPI
  //pinMode(CS_ETH, OUTPUT);
  //digitalWrite(CS_ETH, HIGH);

// without the code above 
// The w5100 SPI is active

  SPI_Initialise();
  //SPI.setDataMode(SPI_MODE0);

// Now, with the RTC and the w5100 active, you try to start the SD.
// You do not expect it to be reliable, do you?
  SdCard_Initialise();
  InitialiseEthernet();
  //SPI.setDataMode(SPI_MODE3);
  Initialise_RTC();
  //SPI.setDataMode(SPI_MODE0);
}

Then, after it fails as often as it succeeds, you will feel just like majenko in this post.
http://forum.arduino.cc/index.php?topic=185899.msg1378792#msg1378792
You will blame the hardware, the IDE, the libraries, and anything else except the real reason it is not reliable. Your programming.

How are you powering the SD card? They can need quite a bit of current, 250-300mA, and 3.3V/150mA from the Mega may be insufficient.

@CrossRoads: It is the uSD slot on the ethernet shield. Mine works fine, and the RTC should not overload that. The OP has a problem with the SPI bus and doesn't know it. There are 3 devices active on the SPI bus when the SD is started. The SD, the RTC, and the w5100. The RTC and the w5100 are trashing up the SPI bus during the SD start. It happens to me if I do not disable the w5100 and any other SPI devices before starting the SD. It will fail about half the time, mostly after pressing the reset button. He is just running on luck.

Ah.

SurferTim:
@CrossRoads: It is the uSD slot on the ethernet shield. Mine works fine, and the RTC should not overload that. The OP has a problem with the SPI bus and doesn't know it. There are 3 devices active on the SPI bus when the SD is started. The SD, the RTC, and the w5100. The RTC and the w5100 are trashing up the SPI bus during the SD start. It happens to me if I do not disable the w5100 and any other SPI devices before starting the SD. It will fail about half the time, mostly after pressing the reset button. He is just running on luck.

So I think my last post was misinterpreted. I tried disabling everything before initialising the SD card, but it didn't work; or rather it did very occasionally, until at one point when I recompiled, it worked every time, even after power resets. Commenting out the code I did made no difference in that a recompile still produced a 100% working system when previously it didn't. This left me confused. I have since rewritten some code to investigate further. The following code fails to successfully initialise the SD card. I don't want to run on luck - I want to know what I'm doing wrong. I apologise - I am not a software engineer, and I am not overly familiar with the SPI bus - I am merely getting to know the arduino, and wanting to make some useful projects. I really appreciate all your time getting involved and making useful suggestions.
Fin
By the way, I am powering the Mega with a 9V 1.2A PSU

This code fails;

#include <SPI.h>
#include <SdFat.h>
#include <Ethernet.h>
#include <Wire.h>
#include <RTC_DS3234.h>

/* SD card attached to SPI bus as follows: (commented out definitions are defined in pins_arduino.h)*/
//const int MOSI = 51; //conflicting declaration in pins_arduino.h
//const int MISO = 50; //conflicting declaration in pins_arduino.h
const int SCLK = 52;
//const int SS = 53; //conflicting declaration in pins_arduino.h
const int CS_RTC = 7;  //chip select pin for RTC
const int CS_SD = 4; //chip select for SD card
const int CS_ETH = 10; // chip select for w5100 Ethernet
byte mac[] = {0x90, 0xA2, 0xDA, 0x0D, 0xAB, 0x8F};

// file system object
SdFat sd;
SdFile dataFile;

RTC_DS3234 RTC(CS_RTC);// Create an RTC instance, using the chip select pin it's connected to

void setup()
{
  //set pin mode on all SPI chip select pins
  pinMode(SS, OUTPUT); //sets Mega in SPI master mode.
  pinMode(CS_SD, OUTPUT);  // SD card
  pinMode(CS_RTC, OUTPUT); // DS3234 RTC
  pinMode(CS_ETH, OUTPUT); // w5100 wiznet chip
  
  //disable all SPI devices before initialising SPI bus
  digitalWrite(CS_SD, HIGH);
  digitalWrite(CS_RTC, HIGH);
  digitalWrite(CS_ETH, HIGH);
  
  Serial.begin(9600);
  while(!Serial){} //wait for Mega before generating any serial data
  SPI_Initialise();
  SPI.setDataMode(SPI_MODE0);
  SdCard_Initialise();
  digitalWrite(CS_SD, HIGH); //disable SD just in case it's left active
  InitialiseEthernet();
  digitalWrite(CS_ETH, HIGH); //disable Ethernet just in case it's left active 
  SPI.setDataMode(SPI_MODE3); //mode for RTC
  Initialise_RTC();
  digitalWrite(CS_RTC, HIGH); //disable RTC just in case it's left active
  SPI.setDataMode(SPI_MODE0); //set mode back to 0
}

void loop() 
{
  delay(3000);
  PrintTimeToFile();
  PrintTime();
}

void SPI_Initialise()
{
  SPI.begin();
  Serial.println("SPI Initialised.");
}

void SdCard_Initialise()
{
  if (!sd.begin(CS_SD, SPI_HALF_SPEED))
  {
    Serial.println("SD Card failed");
    return;
  }
  Serial.println("SD Card Initialised");
}

void InitialiseEthernet()
{
  if (Ethernet.begin(mac))
  { 
    Serial.println("Ethernet Initialised.");
    delay(2000);
    Serial.print("My IP address is ");
    Serial.println(Ethernet.localIP());
  }
  else
  {
    Serial.println("Ethernet failed to Initialise.");
  }
}

void Initialise_RTC()
{
  if ( RTC.begin())
  {  
    RTC.adjust(DateTime(__DATE__, __TIME__));
    Serial.println("RTC Initialised.");
  }
  else
  {
    Serial.println("RTC failed to Initialise.");
  }
}

void PrintTimeToFile()
{
  const int len = 32;
  static char buf[len];
  if (dataFile.open("Time.txt", O_CREAT))
  {
    DateTime now = RTC.now();
    dataFile.println(now.toString(buf,len));
    dataFile.close();
  }
  else
  {
    Serial.println("SD failed to open!");
  }
}

void PrintTime()
{
  const int len = 32;
  static char buf[len];
  DateTime now = RTC.now();
  Serial.println(now.toString(buf,len));
}

The serial output;

SPI Initialised.
SD Card failed
Ethernet Initialised.
My IP address is 172.16.214.246
RTC Initialised.
SD failed to open!
Sep 12 2013 09:08:56
SD failed to open!
Sep 12 2013 09:08:59

Try just the setup on the SD and w5100 with the standard libraries. Upload this code, then power down the Mega and power it up again.

#include <SPI.h>
#include <SD.h>
#include <Ethernet.h>

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

  // disable RTC SPI
  pinMode(7,OUTPUT);
  digitalWrite(7,HIGH);

  // disable w5100 SPI
  pinMode(10,OUTPUT);
  digitalWrite(10,HIGH);

  Serial.print(F("Starting SD.."));
  if(!SD.begin(4)) Serial.println(F("failed"));
  else Serial.println(F("ok"));

  Serial.print(F("Starting ethernet..."));
  if(!Ethernet.begin(mac)) Serial.println(F("dhcp failed"));
  else Serial.println(Ethernet.localIP());
}

void loop() {
}

How does it do?

Commenting out the lines that disable the RTC and w5100 code before starting the SD will cause fails. I use a Mega, and it causes fails on mine. The reason it is not predictable is you are leaving those slave select lines floating as an INPUT, so they will wander from HIGH to LOW. If they float to a HIGH state, it will work, If they float to a LOW state, it will fail.

On an Uno, the w5100 is disabled by accident by the SD.begin(4) function when it sets the default SPI slave select (D10) to OUTPUT and HIGH, but on a Mega, it isn't. Its default slave select is D53.

The code you posted works OK (once I added the mac address definition).

(once I added the mac address definition).

Oops! My bad! :blush: I knew I was forgetting something. The coffee is just kicking in now.

Now add the RTC startup to that code just below the ethernet startup. Add the RTC header with the others at the top.

  SPI.setDataMode(SPI_MODE3);
  if ( RTC.begin())
  {  
    RTC.adjust(DateTime(__DATE__, __TIME__));
    Serial.println("RTC Initialised.");
  }
  else
  {
    Serial.println("RTC failed to Initialise.");
  }
  SPI.setDataMode(SPI_MODE0);

edit: I'm not certain how the RTC module sets the slave select pin. Insure it is using D7, and that the RTC slave select is connected to D7.

OK, done that and it initialises everything. So far so good. Code below.

#include <SPI.h>
#include <SD.h>
#include <Ethernet.h>
#include <RTC_DS3234.h>
byte mac[] = {0x90, 0xA2, 0xDA, 0x0D, 0xAB, 0x8F}; // UNIT 2
RTC_DS3234 RTC(7);

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

  // disable RTC SPI
  pinMode(7,OUTPUT);
  digitalWrite(7,HIGH);

  // disable w5100 SPI
  pinMode(10,OUTPUT);
  digitalWrite(10,HIGH);

  Serial.print(F("Starting SD.."));
  if(!SD.begin(4)) Serial.println(F("failed"));
  else Serial.println(F("ok"));

  Serial.print(F("Starting ethernet..."));
  if(!Ethernet.begin(mac)) Serial.println(F("dhcp failed"));
  else Serial.println(Ethernet.localIP());

  SPI.setDataMode(SPI_MODE3);
  if ( RTC.begin())
  {  
    RTC.adjust(DateTime(__DATE__, __TIME__));
    Serial.println("RTC Initialised.");
  }
  else
  {
    Serial.println("RTC failed to Initialise.");
  }
  SPI.setDataMode(SPI_MODE0);
}

void loop() {
}

Sounds like you are good to go! When accessing the RTC, insure you set the SPI mode to 3, then set it back to mode 0 when done. Like this:

SPI.setDataMode(SPI_MODE3);
// get the time from RTC
SPI.setDataMode(SPI_MODE0);

Don't access any other SPI devices (SD and w5100) while accessing the RTC in mode 3. That will also cause a fail.

I've just finished making the relevant repairs to my original code to incorporate a couple of functions to take care of the different SPI modes that the RTC uses. I believe that was the root cause of my original problems.

I now call these functions whenever I access my get-time function which uses the RTC.

void SPI_RTC_pre()
{
  SPI.setDataMode(SPI_MODE3);
  SPI.setClockDivider(SPI_CLOCK_DIV4);
  digitalWrite(CS_RTC, LOW); // enable RTC
}
void SPI_RTC_post()
{
  digitalWrite(CS_RTC, HIGH); //disable RTC
  SPI.setDataMode(SPI_MODE0);
  SPI.setClockDivider(SPI_CLOCK_DIV4);
}

judging by the DS3234 datasheet 4MHz is the max SPI speed as long as you have a decent voltage source going into the chip, otherwise it drops to 2MHz, meaning SPI_CLOCK_DIV8 would be more appropriate. Anyway, with these functions ensuring things get managed properly, my 41k's worth of code now appears to work - every time :wink:

Thank you, SurferTim !