Has anyone had success hooking up an SD card to the Photon and writing/reading data?

I’m looking to expand the memory on my Photon to store some bulk data. Ideally I’d like to buy a 32GB microSD card, attach to a shield of some sort, and save data I’m gathering from some sensors to the card. I was wondering if anyone’s had success doing something similar? If so, could you walk me through the (general) steps I need to get everything up and running?

I tried looking into the SD card libraries available, but it seems like a number of them have failed on the Photon. Does anyone know of one that actually works?

1 Like

@ashiundar, the SDFat library available on the web IDE is extremely stable and fast. :grinning:

I’m pretty new to hooking up SD cards manually…where do I start? Do I need a shield? Do I wire directly into the SPI pins?

I think that feature to tell you if a lib works or not with a particular platform is not cooked very well yet.
It often complains about perfectly running libs.

The wiring for SD card readers is straight forward with the SPI pins A2~A5, 3V3 and GND and that's it.

If you look at this sample you'll find the pin mappings for software and hardware SPI in the comments
https://build.particle.io/libs/56bcef66962076a38d000a0b/tab/TryMeFirst.cpp

So if I understand correctly, I can wire directly from the SD pins to the Photon? No shield needed? I thought I saw somewhere I need some resistors between some of the pins?

Yes, you can.
SD cards work on 3.3V and the Photon does so too.
But I’d still use a microSD card holder board, just for the ease of use.

Like this

You won’t need the fancy ones with level shifters.

Awesome. Thanks for the help and quick replies!

Actually one other question - what is the size limit to the SD card capacity I can use with the SDFAT library? I couldn’t find anywhere if it’s using FAT16 or FAT32.

I’ve successfully used 32GB cards.

You can look at the GitHub repo here

Perfect. Thanks!

Follow up: SDFat did end up working out for me. I reference [this code] (https://github.com/greiman/SdFat-Particle/blob/master/firmware/examples/TryMeFirst.cpp) in the SDFat examples folder in the GitHub repo for SDFat, and it worked pretty well. The breakout board I was using had slightly different labelling schemes, but a little research cleared up the nomenclature pretty quickly.

I was able to create a .txt and successfully write data to the file. I was then able to open up the file in Excel and import the data. If anyone needs help feel free to ask!

3 Likes

Hi,

I am trying to use the SdFat-Particle library and am having a maddening time trying to get it to compile from the CLI. It can't find the #include files...

I have tried:

  • adding the particle.include files with all the **/**.cpp etc.

# particle.include

TryMeFirst.cpp
SdFat/SdFat.h

**/.h
**/
.ino
**/.cpp
**/
.c

  • copying the SdFat.h files directly to the current folder
  • updating my particle-cli (current version: 1.16.0)
$ particle compile photon TryMeFirst.cpp --saveTo firmware.bin
Compiling code for photon
Including:
    TryMeFirst.cpp
attempting to compile firmware 
Compile failed. Exiting.
TryMeFirst.cpp:3:25: fatal error: SdFat/SdFat.h: No such file or directory
 #include "SdFat/SdFat.h"
                         ^
compilation terminated.
make[1]: *** [../build/target/user/platform-6TryMeFirst.o] Error 1
make: *** [user] Error 2

HELP!

Forgive me for being slow, but I’m not sure what you’re doing here:

# particle.include

TryMeFirst.cpp
SdFat/SdFat.h

**/*.h
**/*.ino
**/*.cpp
**/*.c

Is this code from your program? If so, then I don’t believe the IDE will allow you to compile any of those lines. I never used particle.include. All I had to do was include the SdFat library using the libraries icon on the left sidebar, and the IDE usually auto-adds the #include SdFat statement at the top of your code and it tends to work fine. There are some quirky things with the Particle IDE that I may be able to help you work around.

Here’s how mine looked:

#include <SdFat/SdFat.h> 

Also, can you post your full code?

What OS?
Can you provide the list of files CLI sends to the build farm too?

One thing you change for sure. CLI flattens the file structure, so your include should look like this #include "SdFat.h"

Don’t use the angled brackets for non-system headers.

Thank you for the responses guys/gals…

I finally got it to compile and work! WOW, that CLI file-structure-flattening really confused me :disappointed:

What worked was:

  • copy ‘/firmware/examples/TryMeFirst.cpp’ to a new folder ‘NewFolder’
  • copy all the files from ‘/firmware’ to ‘NewFolder/SdFat’

… and compile: particle compile photon . --saveTo firmware.bin

I uploaded to the photon in DFU and confirmed the SDcard functionality… Now on to less trivial things.

Would you mind sharing your code? (I had trouble following the link) I am looking into logging accelerometer data locally to a manually mounted SD card. Any advice along the way would be helpful.

I was able to include the library in my code and compile it correctly. I would like to know how to access my files that are uploaded to the SD card, and is there a way to write the data to the SD card as a CSV file?

Ah, it looks like that link is broken and the owner of that repo completely reorganized that repo…Let me see if I have the TryMeFirst file saved on my computer somewhere.

Edit: Here’s the file:

#include "SdFat/SdFat.h"

// Pick an SPI configuration.
// See SPI configuration section below (comments are for photon).
#define SPI_CONFIGURATION 0
//------------------------------------------------------------------------------
// Setup SPI configuration.
#if SPI_CONFIGURATION == 0
// Primary SPI with DMA
// SCK => A3, MISO => A4, MOSI => A5, SS => A2 (default)
SdFat sd;
const uint8_t chipSelect = SS;
#elif SPI_CONFIGURATION == 1
// Secondary SPI with DMA
// SCK => D4, MISO => D3, MOSI => D2, SS => D1
SdFat sd(1);
const uint8_t chipSelect = D1;
#elif SPI_CONFIGURATION == 2
// Primary SPI with Arduino SPI library style byte I/O.
// SCK => A3, MISO => A4, MOSI => A5, SS => A2 (default)
SdFatLibSpi sd;
const uint8_t chipSelect = SS;
#elif SPI_CONFIGURATION == 3
// Software SPI.  Use any digital pins.
// MISO => D5, MOSI => D6, SCK => D7, SS => D0
SdFatSoftSpi<D5, D6, D7> sd;
const uint8_t chipSelect = D0;
#endif  // SPI_CONFIGURATION
//------------------------------------------------------------------------------

File myFile;

void setup() {
  Serial.begin(9600);
  // Wait for USB Serial 
  while (!Serial) {
    SysCall::yield();
  }
  
  Serial.println("Type any character to start");
  while (Serial.read() <= 0) {
    SysCall::yield();
  }

  // Initialize SdFat or print a detailed error message and halt
  // Use half speed like the native library.
  // Change to SPI_FULL_SPEED for more performance.
  if (!sd.begin(chipSelect, SPI_HALF_SPEED)) {
    sd.initErrorHalt();
  }

  // open the file for write at end like the "Native SD library"
  if (!myFile.open("test.txt", O_RDWR | O_CREAT | O_AT_END)) {
    sd.errorHalt("opening test.txt for write failed");
  }
  // if the file opened okay, write to it:
  Serial.print("Writing to test.txt...");
  myFile.println("testing 1, 2, 3.");
  myFile.printf("fileSize: %d\n", myFile.fileSize());
  
  // close the file:
  myFile.close();
  Serial.println("done.");

  // re-open the file for reading:
  if (!myFile.open("test.txt", O_READ)) {
    sd.errorHalt("opening test.txt for read failed");
  }
  Serial.println("test.txt content:");

  // read from the file until there's nothing else in it:
  int data;
  while ((data = myFile.read()) >= 0) {
    Serial.write(data);
  }
  // close the file:
  myFile.close();
}

void loop() {
  // nothing happens after setup
}

I also have connected to an accelerometer and had to read/write data to an SD card. I posted my code below - please let me know if this helps or if you have questions:

#include <SdFat/SdFat.h> // Particle IDE is strange. To include this particular library, 
// you have to both declare it here, as well as manually add the library to the app
// over in the libraries link on the left sidebar. 
#include <math.h> // to do thermistor math later

//// //// //// //// ////
//// microSD config ////
// Pick an SPI configuration.
// See SPI configuration section below (comments are for photon).
#define SPI_CONFIGURATION 1
//// //// //// //// ////
// Setup SPI configuration.
#if SPI_CONFIGURATION == 0
// Primary SPI with DMA
// SCK => A3, MISO => A4, MOSI => A5, SS => A2 (default)
SdFat sd;
const uint8_t chipSelect = SS;
#elif SPI_CONFIGURATION == 1
// Secondary SPI with DMA
// SCK => D4, MISO => D3, MOSI => D2, SS => D1
SdFat sd(1);
const uint8_t chipSelect = D1;
#elif SPI_CONFIGURATION == 2
// Primary SPI with Arduino SPI library style byte I/O.
// SCK => A3, MISO => A4, MOSI => A5, SS => A2 (default)
SdFatLibSpi sd;
const uint8_t chipSelect = SS;
#elif SPI_CONFIGURATION == 3
// Software SPI.  Use any digital pins.
// MISO => D5, MOSI => D6, SCK => D7, SS => D0
SdFatSoftSpi<D5, D6, D7> sd;
const uint8_t chipSelect = D0;
#endif  // SPI_CONFIGURATION
//// //// //// //// ////
//// //// //// //// ////

SYSTEM_MODE(SEMI_AUTOMATIC); // allows Photon code to execute without being connected
// to a WiFi network. You must manually call Particle.connect() to connect to a network.
// On the other hand, in AUTOMATIC mode, Particle.connect() is automatically called before 
// any code is executed, and the device waits to be connected to WiFi before executing any 
// of your code. 

// variable initalizations
FatFile globalAccelFile;
String globalAccelFileName;
String accelData; 
bool flag; // flag checks whether certain events were successful or not
double accelMagnitude; 
int fileTracker = 1; 
int globalAccelFileTracker = 1;

// checks the accelerometer x y and z analog values, which are connected to A0, A1, and A2, respectively. 
void checkAccel() {
    globalAccelFileName = "globalAccel" + String(globalAccelFileTracker) + ".txt";
    globalAccelFile.open(globalAccelFileName, O_RDWR | O_CREAT | O_AT_END);
    
    accelMagnitude = floor(sqrt(pow(analogRead(A0),2) + pow(analogRead(A1),2) + pow(analogRead(A2),2)) + 0.5); 
    accelData += String(accelMagnitude).remove(4); 
    
    // maximum String length is 622 bytes. Let's just be safe and say 616 so we don't overrun. 
    if (accelData.length() > 616) {
        accelData += " " + Time.timeStr(); 
        globalAccelFile.write(accelData);
        accelData = "";
    }
    
    if (accelData.length() <= 616) { 
        accelData += "\n"; // each data point is on its own line
    }
    
    if (globalAccelFile.fileSize() > 1000000) // if the accel file exceeds 1MB, on to the next one
    {
        globalAccelFileTracker++;
    }
    
    globalAccelFile.close(); // don't forget to close the file, otherwise you run the risk of corrupting the file and card. 
}


// Find out if the SD card is cooperating. Good idea to call this before attempting to do anything major 
// with the SD Card. This function is a little leaky - some SD errors can get by without this function picking them up. 
void checkSdError() { 
    Serial.print("sd.cardErrorCode(): "); 
    flag = sd.cardErrorCode(); // directly related to sd success/failure
    Serial.println(flag);
    Serial.print("sd.cardErrorData(): "); // for additional debugging
    Serial.println(sd.cardErrorData());
    
    // If there's an error, write high to D7. You can plug in an LED at D7 to check the SD status whenever you want. 
    if (!flag) {
        digitalWrite(D7, LOW);
    }
    
    else {
        digitalWrite(D7, HIGH);
    }
} 


// software Timers are preferable to delays, so the code can run concurrently, not sequentially. Along these lines, it's probably 
// a good idea to enable system threading using SYSTEM_THREAD(ENABLED); 
Timer CheckAccelTimer(50, checkAccel); // sample accelerometer at 20 Hz
Timer CheckSdErrorTimer(1000, checkSdError); // checks for SD card errors every 1 second

void setup() {
    // Particle.connect(); // must manually call Particle.connect() if system_mode is semi_automatic
    // Time.zone(-7); // changes time zone. Does not adjust for DST, must manually change.
    Serial.begin(115200); // debugging purposes
    sd.begin(chipSelect, SPI_FULL_SPEED); // init at full speed for best performance
    pinMode(D7, OUTPUT); // initalize D7 as an output for use by checkSdError
    Time.setTime(1473379200); // set time to start on September 9 2016 00:00:00 GMT (UNIX time = 1473379200, or seconds since
    // the UNIX epoch, 1 January 1970)
    
    // Start the timers
    CheckAccelTimer.start();
    CheckSdErrorTimer.start();
}

// keep the loop empty - it's better to use software timers (as I did above) so that you can nest events, 
// instead of running your code line by line (which is what happens in setup and loop). 
void loop() { 

}

Edit: It looks like the owner deleted their original file and reorganized their repo, so here’s the TryMeFirst.ino file:

#include "SdFat/SdFat.h"

// Pick an SPI configuration.
// See SPI configuration section below (comments are for photon).
#define SPI_CONFIGURATION 0
//------------------------------------------------------------------------------
// Setup SPI configuration.
#if SPI_CONFIGURATION == 0
// Primary SPI with DMA
// SCK => A3, MISO => A4, MOSI => A5, SS => A2 (default)
SdFat sd;
const uint8_t chipSelect = SS;
#elif SPI_CONFIGURATION == 1
// Secondary SPI with DMA
// SCK => D4, MISO => D3, MOSI => D2, SS => D1
SdFat sd(1);
const uint8_t chipSelect = D1;
#elif SPI_CONFIGURATION == 2
// Primary SPI with Arduino SPI library style byte I/O.
// SCK => A3, MISO => A4, MOSI => A5, SS => A2 (default)
SdFatLibSpi sd;
const uint8_t chipSelect = SS;
#elif SPI_CONFIGURATION == 3
// Software SPI.  Use any digital pins.
// MISO => D5, MOSI => D6, SCK => D7, SS => D0
SdFatSoftSpi<D5, D6, D7> sd;
const uint8_t chipSelect = D0;
#endif  // SPI_CONFIGURATION
//------------------------------------------------------------------------------

File myFile;

void setup() {
  Serial.begin(9600);
  // Wait for USB Serial 
  while (!Serial) {
    SysCall::yield();
  }
  
  Serial.println("Type any character to start");
  while (Serial.read() <= 0) {
    SysCall::yield();
  }

  // Initialize SdFat or print a detailed error message and halt
  // Use half speed like the native library.
  // Change to SPI_FULL_SPEED for more performance.
  if (!sd.begin(chipSelect, SPI_HALF_SPEED)) {
    sd.initErrorHalt();
  }

  // open the file for write at end like the "Native SD library"
  if (!myFile.open("test.txt", O_RDWR | O_CREAT | O_AT_END)) {
    sd.errorHalt("opening test.txt for write failed");
  }
  // if the file opened okay, write to it:
  Serial.print("Writing to test.txt...");
  myFile.println("testing 1, 2, 3.");
  myFile.printf("fileSize: %d\n", myFile.fileSize());
  
  // close the file:
  myFile.close();
  Serial.println("done.");

  // re-open the file for reading:
  if (!myFile.open("test.txt", O_READ)) {
    sd.errorHalt("opening test.txt for read failed");
  }
  Serial.println("test.txt content:");

  // read from the file until there's nothing else in it:
  int data;
  while ((data = myFile.read()) >= 0) {
    Serial.write(data);
  }
  // close the file:
  myFile.close();
}

void loop() {
  // nothing happens after setup
}