Purpose: To relate some (hard for me) lessons learned in getting a file from an SD card into an array.
Background: Making an in-car computer for high speed rally events. File on SD card contains target speed and some waypoints (mileages, lat/long) for each specific race course.
Hardware: Arduino MKR Zero.
Key Reference: A Simple Function for Reading CSV Text Files. - #3 by fat16lib A beautifully crafted program from fat16lib. I read many posts before stumbling upon this outstanding approach from fat16lib that works! Yes, I modified it a bit, but what a great resource!
Lessons Learned:
- File names limited to 8 characters!
- Add code to wait for Serial port to open (see code sample appended)
- Array rows and columns are numbered starting at 0 (not 1)
- Wait for uploading to complete before trying to open Serial Monitor
- When your MKR Zero never ends the uploading, hit the reset button twice in quick succession (starts the bootloader, magically fixes things)
- Integers in MKR Zero are NOT limited to +-32,768 like in the Uno
- BTW, the lovely MKR GPS shield has such a hard time getting a GPS fix that I abandoned it and used an Adafruit Ultimate GPS Breakout instead. You can put an external antenna on it! Imagine being at a race start line and not getting a GPS reading.
- The CS PIN for the SD on the MKR Zero is NOT 4 and NOT 10 (I think it's 28) - but just specify it as SDCARD_SS_PIN. For example,
if (!SD.begin (SDCARD_SS_PIN))
{
etc. - You have to close a file before opening a new one.
The code is appended at the end. This is the output on the Serial Monitor:
130 0 38 -115 28 38 -114 60 37 -115 61 37 -115 0
0 0 433614 -13378 61 41172 -967558 811 634958 -21725 380 627386 -271253 0
38.433614
130 0 37 -115 32 38 -114 60 38 -115 61 38 -115 0
0 0 627386 -221253 182 25078 -963764 622 424125 -20431 190 43125 -1515 0
37.627386
startLat should be 38.433614 and is actually: 38.433614
startLng should be -115.13378 and is actually: -115.013378
Done
MarkIIISD2ArraysTest.ino (7.1 KB)
// Example to read a two dimensional array.
// Example to read a two dimensional array.
//
#include <SPI.h>
#include <SD.h>
#define CS_PIN SDCARD_SS_PIN
// 2 X 14 array
#define ROW_DIM 2
#define COL_DIM 14
File file;
double startLat = 0.0;
int startLatD = 0;
double startLatM = 0.0;
double startLng = 0.0;
int startLngD = 0;
double startLngM = 0.0;
/*
Note: Integers in MKR Zero have a range of -2,147,483,648 to + 2,147,483,647
Read a file one field at a time.
NOTE: File names are LIMITED to 8 CHARACTERS!!
file - File to read.
str - Character array for the field.
size - Size of str array.
delim - String containing field delimiters.
return - length of field including terminating delimiter.
Note, the last character of str will not be a delimiter if
a read error occurs, the field is too long, or the file
does not end with a delimiter. Consider this an error
if not at end-of-file.
*/
size_t readField(File* file, char* str, size_t size, char* delim) {
char ch;
size_t n = 0;
while ((n + 1) < size && file->read(&ch, 1) == 1) {
// Delete CR.
if (ch == '\r') {
continue;
}
str[n++] = ch;
if (strchr(delim, ch)) {
break;
}
}
str[n] = '\0';
return n;
}
//------------------------------------------------------------------------------
#define errorHalt(msg) {Serial.println(F(msg)); while(1);}
//------------------------------------------------------------------------------
void setup() {
Serial.begin(115200); // Note that I use 115200 baud although 9600 is more common
// Open serial communications and wait for port to open: ADD THIS WAIT OR NATIVE USB PORT WON'T OPEN
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
// Initialize the SD.
if (!SD.begin(SDCARD_SS_PIN)) // This is where you specify the CS Pin for MKR Zero
// Yes, I know, I did a #define up above, but I wanted to make it clear and specific here
{
errorHalt("begin failed");
}
// Open the file.
file = SD.open("N130L1.txt", FILE_WRITE); // File names limited to 8 characters!
if (!file) {
errorHalt("first open failed"); // You'll see this message if your file name > 8 chars
}
// Rewind file so test data is not appended.
file.seek(0); // Who knew there was a "seek" function? This is useful.
// Write test data. Note that the first row has the most significant parts of the data, and the
// second row the least significant parts. For example, target speed is 130.0 mph. Start line is at
// mileage 0.000. Lat/Lng is 38.433614, -115.433614.
file.print(F(
"130,0,38,-115,28,38,-114,60,37,-115,61,37,-115,0\r\n"
"0,0,433614,-13378,61,41172,-967558,811,634958,-21725,380,627386,-271253,0\r\n"
));
/*
This is fat16lib's original example, much more general than my specific file
"11,12,13,14\r\n"
"21,22,23,24\r\n"
"31,32,33,34\r\n"
"41,42,43,44\r\n"
"51,52,53,54" // Allow missing endl at eof.
));
*/
// Rewind the file for read.
file.seek(0);
// Array for data.
long arrayL1[ROW_DIM][COL_DIM];
int i = 0; // First array index.
int j = 0; // Second array index
size_t n; // Length of returned field with delimiter.
char str[20]; // Must hold longest field with delimiter and zero byte.
char *ptr; // Test for valid field.
// Read the file and store the data.
for (i = 0; i < ROW_DIM; i++) {
for (j = 0; j < COL_DIM; j++) {
n = readField(&file, str, sizeof(str), ",\n");
if (n == 0) {
errorHalt("Too few lines");
}
arrayL1[i][j] = strtol(str, &ptr, 10);
if (ptr == str) {
errorHalt("bad number");
}
if (j < (COL_DIM - 1) && str[n - 1] != ',') {
errorHalt("line with too few fields");
}
}
// Allow missing endl at eof.
if (str[n - 1] != '\n' && file.available()) {
errorHalt("missing endl");
}
}
// Print the array.
for (i = 0; i < ROW_DIM; i++) {
for (j = 0; j < COL_DIM; j++) {
if (j) {
Serial.print(' ');
}
Serial.print(arrayL1[i][j]);
}
Serial.println();
}
//Some samples: extract from the arrays and print (you can also put values into internal variables)
Serial.print(arrayL1[0][2]); // Remember that rows and columns begin at 0
Serial.print(".");
Serial.println(arrayL1[1][2]);
file.close(); // You have to close the file before trying to open a new one.
// Open the second file. In a race, this is the inbound leg of the event.
file = SD.open("N130L2.txt", FILE_WRITE); // File names limited to 8 characters!
if (!file) {
errorHalt("second open failed"); // You'll see this message if your file name > 8 chars
}
// Rewind file so test data is not appended.
file.seek(0); // Who knew there was a "seek" function? This is useful.
// Write test data for the inbound leg of the race.
file.print(F(
"130, 0, 37, -115, 32, 38, -114, 60, 38, -115, 61, 37, -115, 0\r\n"
"0, 0, 627386, -221253, 182, 25078, -963764, 622, 424125, -20431, 190, 43125, -1515, 0\r\n"
));
//Now read the second leg file into another array
// Rewind the file for read.
file.seek(0);
// Array for data.
long arrayL2[ROW_DIM][COL_DIM];
i = 0; // First array index.
j = 0; // Second array index
// Read the file and store the data.
for (i = 0; i < ROW_DIM; i++) {
for (j = 0; j < COL_DIM; j++) {
n = readField(&file, str, sizeof(str), ",\n");
if (n == 0) {
errorHalt("Too few lines");
}
arrayL2[i][j] = strtol(str, &ptr, 10);
if (ptr == str) {
errorHalt("bad number");
}
if (j < (COL_DIM - 1) && str[n - 1] != ',') {
errorHalt("line with too few fields");
}
}
// Allow missing endl at eof.
if (str[n - 1] != '\n' && file.available()) {
errorHalt("missing endl");
}
}
// Print the array.
for (i = 0; i < ROW_DIM; i++) {
for (j = 0; j < COL_DIM; j++) {
if (j) {
Serial.print(' ');
}
Serial.print(arrayL2[i][j]);
}
Serial.println();
}
Serial.print(arrayL2[0][2]); // Remember that rows and columns begin at 0
Serial.print(".");
Serial.println(arrayL2[1][2]);
// Nice thing is you can manipulate the data from the SD file in your program:
startLatD = (arrayL1[0][2]);
startLatM = (arrayL1[1][2]);
startLat = (startLatD + (startLatM / 1000000));
startLngD = (arrayL1[0][3]);
startLngM = (arrayL1[1][3]);
startLng = (startLngD + (startLngM / 1000000)); //NOTE: enter LS longitude as negative nos.
Serial.print("startLat should be 38.433614 and is actually: ");
Serial.println(startLat, 6);
Serial.print("startLng should be -115.13378 and is actually: ");
Serial.println(startLng, 6);
Serial.println("Done");
file.close();
}
//------------------------------------------------------------------------------
void loop() {
}