Afternoon All, Hi @meathome2017.
I did say that I liked a challenge, however I never thought that the Arduino SD card system, at least for an Uno board, was so fsck
'd! (Unix joke!)
Here’s what I found:
- Opening a file on the SD card with mode
FILE_WRITE
will open it at the end, it is effectively FILE_APPEND (which doesn’t exist in my SD library.)
- Using
write()
to write a byte, as in fred
, works fine and my readByte()
can read it back happily.
-
Contrary to the Sd
write()
function’s docs on the Arduino Reference site, the number of bytes written is always 1 no matter what type of variable data you write. So int wilma = 123245; myFile.write(wilma);
only writes one byte.
- In order to write an
int
you have to myFile.write((uint8_t *)&wilma, sizeof(int));
which will write an int
, and – even better – that int
value can be read back happily!
I am not happy with the code in the ReadWrite example sketch, so I rewrote it, with lots of commentary as it was going along, the working version is as follows:
//----------------------------------------------------------
// SD Card "exerciser". Based on the ReadWrite sketch under
// File->Examples->SD->ReadWrite in the Arduino IDE. There
// are problems with that sketch!
//
// Pinout:
//
// D13 = CLK
// D12 = MISO
// D11 = MOSI
// D4 = Card Select pin.
//
// Opening with FILE_WRITE appends to the file. (Fixed below)
//
// The write() function only ever write one byte unless you
// give it an array and a size. Write(myInt) will only do a
// single byte. The docs say otherwise!
//
//
// Norman Dunbar
// 29th August 2020.
//----------------------------------------------------------
#include <SPI.h>
#include <SD.h>
Nothing has changed in the above from the original, except the comments! However, I’ve had to add the folloiwng “bug” fix (feature fix?) as opening a file in FILE_WRITE mode always opened at the end.
//----------------------------------------------------------
// How to fix the Arduino SD library, it's borked!
//
// FILE_READ is ok.
// FILE_WRITE is actually an APPEND. I've renamed that below
// to FILE_APPEND.
// The new FILE_WRITE will open at the start of the file.
//----------------------------------------------------------
#ifdef ARDUINO
#ifdef FILE_WRITE
#undef FILE_WRITE
#define FILE_WRITE (O_READ | O_WRITE | O_CREAT)
#define FILE_APPEND ((FILE_WRITE) | O_APPEND)
#endif // FILE_WRITE
#endif // ARDUINO
The above has to go after the above includes. I removed the O_APPEND
from FILE_WRITE
and added in a new FILE_APPEND
macro to do what the current FILE_WRITE
does. Opening a file with FILE_WRITE
mode will correctly start writing from the beginning of the file.
Moving on. In the next chunk of code, I’ve pulled out all the useful functions from the original setup()
code. I prefer it that way. These functions are very wordy in that they tell you what’s happening as we go along. It was this that helped me track down the write()
problem. I’ve parameterised the fileOpen()
and cardInit()
functions too.
//----------------------------------------------------------
// File name for testing purposes.
//----------------------------------------------------------
#define FILE_NAME "test.txt"
//----------------------------------------------------------
// Card select pin.
//----------------------------------------------------------
#define CARD_SELECT_PIN 4
//----------------------------------------------------------
// Initialise the SD Card system, returns true on success.
// It's a very wordy function this one!
//----------------------------------------------------------
bool cardInit(byte cardSelectPin) {
Serial.print("Initializing SD card...");
if (!SD.begin(cardSelectPin)) {
Serial.println("initialization failed!");
return false;
}
Serial.println("initialization done.");
return true;
}
//----------------------------------------------------------
// Open the test file on the card, returns handle on success
// or NULL on failure.
//
// WARNING: Calling with mode = FILE_WRITE will actually
// open at the end. For this reason, I'm setting
// the position to the start after opening. Reading
// the file opens at the start anyway.
//
// NOTE: Only one file can be open at any time.
//----------------------------------------------------------
File openFile(const char *fileName, uint8_t mode) {
File theFile;
Serial.print("\nOpening file: ");
Serial.print(fileName);
Serial.print(" ...");
theFile = SD.open(fileName, mode);
if (theFile) {
Serial.println("opened successfully.");
} else {
Serial.println("failed to open.");
return theFile;
}
// File is open, where are we?
Serial.print("File position is currently: ");
Serial.println(theFile.position());
// If the mode is FILE_WRITE, reset to the start.
if (mode == FILE_WRITE) {
theFile.seek(0);
Serial.print("File position is now: ");
Serial.println(theFile.position());
}
return theFile;
}
The WARNING in the comments above fileOpen()
apply to the original FILE_WRITE
in the Arduino library. It no longer applies if you are using my version as per the correction back at the start of the code. The code also prints out to Serial
the file position after the open and after resetting to the start in FILE_WRITE
mode. With the bug fix above, that can all be removed.
//----------------------------------------------------------
// Close the test file on the card.
//----------------------------------------------------------
void closeFile(const File fileHandle) {
Serial.print("Closing file...");
fileHandle.close();
Serial.println("closed");
}
//----------------------------------------------------------
// This will read a single byte from the SD card file.
//----------------------------------------------------------
void readByte(File dataFile, byte *data) {
dataFile.read((uint8_t *)data, sizeof(byte));
}
//----------------------------------------------------------
// This will read a single int from the SD card file.
//----------------------------------------------------------
void readInt(File dataFile, int *data) {
dataFile.read((uint8_t *)data, sizeof(int));
}
Now we get to setup()
where it all happens. In the event of the SD Card system no initialising, or not being able to open the file, the code just enters a busy loop and stops further execution.
I’ve had to change my original myFile.write((int)12345);
line to be myFile.write((uint8_t *)&Wilma, sizeof(int));
to get write()
to actually write the two bytes that make up an int
. This is contrary to the docs.
//----------------------------------------------------------
// Setup. Does everything here.
//----------------------------------------------------------
void setup() {
Serial.begin(9600);
// Initialise the SD card system.
if (!cardInit(CARD_SELECT_PIN)) {
while (1) ;
}
// Open our test file.
File myFile = openFile(FILE_NAME, FILE_WRITE);
if (!myFile) {
while (1) ;
}
// Test data.
byte Fred = 0xA5;
int Wilma = 12345;
uint32_t sizeWritten = 0;
// Write a byte and an int.
sizeWritten = myFile.write(Fred);
Serial.print("Writing byte: Size written = ");
Serial.println(sizeWritten);
sizeWritten = myFile.write((uint8_t *)&Wilma, sizeof(int));
Serial.print("Writing int: Size written = ");
Serial.println(sizeWritten);
// Close, re-open for reading.
closeFile(myFile);
myFile = openFile(FILE_NAME, FILE_READ);
if (!myFile) {
while (1) ;
}
// Read back one byte and one int.
Fred = Wilma = 0;
readByte(myFile, &Fred);
Serial.print("Fred = 0x");
Serial.println(Fred, HEX);
readInt(myFile, &Wilma);
Serial.print("Wilma (signed int) = ");
Serial.println(Wilma, DEC);
Serial.print("Wilma (unsigned int) = ");
Serial.println((unsigned)Wilma, DEC);
// All done, shutdown.
closeFile(myFile);
}
void loop() {
// There's nothing to see here! Move along!
}
So, there you have it. An SD Card example that works. Now, as for Paul’s original query about writing and reading from a struct…
//----------------------------------------------------------
// SD Card exampel of writing a struct to, and reading it
// from an SD Card file.
//
// Pinout:
//
// D13 = CLK
// D12 = MISO
// D11 = MOSI
// D4 = Card Select pin.
//
// This code uses my fix for the FILE_WRITE problem. Also,
// The write() function only ever writes one byte unless you
// give it an array and a size.
//
//
// Norman Dunbar
// 29th August 2020.
//----------------------------------------------------------
#include <SPI.h>
#include <SD.h>
//----------------------------------------------------------
// How to fix the Arduino SD library, it's borked!
//
// FILE_READ is ok.
// FILE_WRITE is actually an APPEND. I've renamed that below
// to FILE_APPEND.
// The new FILE_WRITE will open at the start of the file.
//----------------------------------------------------------
#ifdef ARDUINO
#ifdef FILE_WRITE
#undef FILE_WRITE
#define FILE_WRITE (O_READ | O_WRITE | O_CREAT)
#define FILE_APPEND ((FILE_WRITE) | O_APPEND)
#endif // FILE_WRITE
#endif // ARDUINO
//----------------------------------------------------------
// File name for testing purposes.
//----------------------------------------------------------
#define FILE_NAME "struct.txt"
//----------------------------------------------------------
// Card select pin.
//----------------------------------------------------------
#define CARD_SELECT_PIN 4
//----------------------------------------------------------
// Initialise the SD Card system, returns true on success.
// It's a very wordy function this one!
//----------------------------------------------------------
bool cardInit(byte cardSelectPin) {
Serial.print("Initializing SD card...");
if (!SD.begin(cardSelectPin)) {
Serial.println("initialization failed!");
return false;
}
Serial.println("initialization done.");
return true;
}
//----------------------------------------------------------
// Open the test file on the card, returns handle on success
// or NULL on failure.
//
// WARNING: Calling with mode = FILE_WRITE will actually
// open at the end. For this reason, I'm setting
// the position to the start after opening. Reading
// the file opens at the start anyway.
//
// NOTE: Only one file can be open at any time.
//----------------------------------------------------------
File openFile(const char *fileName, uint8_t mode) {
File theFile;
Serial.print("\nOpening file: ");
Serial.print(fileName);
Serial.print(" ...");
theFile = SD.open(fileName, mode);
if (theFile) {
Serial.println("opened successfully.");
} else {
Serial.println("failed to open.");
return theFile;
}
// File is open, where are we?
Serial.print("File position is currently: ");
Serial.println(theFile.position());
// If the mode is FILE_WRITE, reset to the start.
// No longer required with the above fix. Was appending.
if (mode == FILE_WRITE) {
theFile.seek(0);
Serial.print("File position is now: ");
Serial.println(theFile.position());
}
return theFile;
}
//----------------------------------------------------------
// Close the test file on the card.
//----------------------------------------------------------
void closeFile(const File fileHandle) {
Serial.print("Closing file...");
fileHandle.close();
Serial.println("closed");
}
//----------------------------------------------------------
// Setup. Does everything here.
//----------------------------------------------------------
void setup() {
Serial.begin(9600);
// Initialise the SD card system.
if (!cardInit(CARD_SELECT_PIN)) {
while (1) ;
}
// Open our test file.
File myFile = openFile(FILE_NAME, FILE_WRITE);
if (!myFile) {
while (1) ;
}
// Test data.
typedef struct testData {
byte aByte;
short aShort;
int anInt;
long aLong;
float aFloat;
char charArray[15];
String aString;
} testData_t;
testData_t myData;
myData.aByte = 1;
myData.aShort = 2;
myData.anInt = 3;
myData.aLong = 4;
myData.aFloat = 3.14;
strcpy(myData.charArray, "Norman Dunbar");
myData.aString = "Hello World, again!";
uint32_t sizeWritten = myFile.write((uint8_t *)&myData, sizeof(myData));
Serial.print("Writing myData struct: Size written = ");
Serial.println(sizeWritten);
// Close, re-open for reading.
closeFile(myFile);
myFile = openFile(FILE_NAME, FILE_READ);
if (!myFile) {
while (1) ;
}
// Read back testdata. Wipe it first though.
memset(&myData, 0, sizeof(testData_t));
uint32_t sizeRead = myFile.read((uint8_t *)&myData, sizeof(myData));
Serial.print("Reading myData struct: Size read = ");
Serial.println(sizeRead);
// All done, shutdown.
closeFile(myFile);
// Print the test data.
Serial.println("\nYour test data looks like this:");
Serial.print("myData.aByte = ");
Serial.println(myData.aByte);
Serial.print("myData.aShort = ");
Serial.println(myData.aShort);
Serial.print("myData.anInt = ");
Serial.println(myData.anInt);
Serial.print("myData.aLong = ");
Serial.println(myData.aLong);
Serial.print("myData.aFloat = ");
Serial.println(myData.aFloat);
Serial.print("myData.charArray = ");
Serial.println(myData.charArray);
Serial.print("myData.aString = ");
Serial.println(myData.aString);
}
void loop() {
// There's nothing to see here! Move along!
}
The code outputs the folloiwng:
Initializing SD card...initialization done.
Opening file: struct.txt ...opened successfully.
File position is currently: 0
File position is now: 0
Writing myData struct: Size written = 34
Closing file...closed
Opening file: struct.txt ...opened successfully.
File position is currently: 0
Reading myData struct: Size read = 34
Closing file...closed
Your test data looks like this:
myData.aByte = 1
myData.aShort = 2
myData.anInt = 3
myData.aLong = 4
myData.aFloat = 3.14
myData.charArray = Norman Dunbar
myData.aString = Hello World, again!
Dumping out the contents of the file, I see the following:
00000000 01 02 00 03 00 04 00 00 00 c3 f5 48 40 4e 6f 72 |...........H@Nor|
00000010 6d 61 6e 20 44 75 6e 62 61 72 00 a3 44 06 13 00 |man Dunbar..D...|
00000020 13 00 |..|
Byte 0 is the struct member aByte
.
Bytes 1 and 2 are the struct member aShort
in little endian format.
Bytes 3 and 4 are the struct member anInt
in little endian format.
Bytes 5 to 8 are aLong
in little endian format.
Bytes 9 to 12 are aFloat
in who cares what format! Floats are 32 bits or 4 bytes.
Bytes 13 to 25 are the contents of charArray
.
Byte 26 is the zero byte terminator of the array.
Bytes 27 to 33 are aString
. However, do you see “Hello World, again!”? No neither do I. I suspect that given there are only 7 bytes we are looking at the internal representation of the String class, and not the actual data it (should) contain.
So, how come it worked on the print back? I would suspect that somewhere in that 7 bytes is a pointer, or the address, of wherever the String
's data was created in Static RAM. As the code didn’t affect it in any way, it was still present at the address when we read the String
back from file.
That’s what I suspect, don’t quote me on that though!
Hopefully, this helps? And don;t forget, stay away from String
s!
Cheers,
Norm.