// VCC on SD card may need to be wired to the 5V of the SD card module.
#include <FS.h>
#include <SD.h>
#include <SPI.h>
// Try max SPI clock for an SD. Reduce SPI_CLOCK if errors occur.
#define SPI_SPEED SD_SCK_MHZ(50)
// Declare SD card pins
#define SD_MOSI 11 // DI pin
#define SD_MISO 18 // DO pin
#define SD_SCLK 8 // SCK pin
#define SD_CS 10 // CS pin
File root;
int day = 1;
float mileage = 135.3;
const byte numChars = 10;
boolean newData = false;
char notes[numChars] = "";
/*
struct Data {
String date;
String miles;
String note;
};
Data dataArray[100]; // Adjust size as needed
*/
const int maxLen = 50;
char dataArray[25][maxLen]; // Hold 25 line, with maximum character length of 50
int dataIndex = 0; // Will hold number of lines that have been imported upon read
const int linesOnScreen = 4;
char dateL[4][11], milesL[4][8], noteL[4][maxLen];
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if(!root){
Serial.println("Failed to open directory");
return;
}
if(!root.isDirectory()){
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while(file){
if(file.isDirectory()){
Serial.print(" DIR : ");
Serial.println(file.name());
if(levels){
listDir(fs, file.path(), levels -1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
void createDir(fs::FS &fs, const char * path){
Serial.printf("Creating Dir: %s\n", path);
if(fs.mkdir(path)){
Serial.println("Dir created");
} else {
Serial.println("mkdir failed");
}
}
void removeDir(fs::FS &fs, const char * path){
Serial.printf("Removing Dir: %s\n", path);
if(fs.rmdir(path)){
Serial.println("Dir removed");
} else {
Serial.println("rmdir failed");
}
}
void readFile(fs::FS &fs, const char * path){
Serial.printf("Reading file: %s\n", path);
File file = fs.open(path);
if(!file){
Serial.println("Failed to open file for reading");
return;
}
char firstLine[3];
file.readBytesUntil('\n', firstLine, sizeof(firstLine));
firstLine[sizeof(firstLine) - 1] = '\0'; // Ensure null termination
dataIndex = atoi(firstLine);
Serial.printf("Data Index: %d\n", dataIndex);
Serial.println("Read from file: ");
while(file.available()){
Serial.write(file.read());
}
file.close();
}
void writeFile(fs::FS &fs, const char * path, const char * message){
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("Failed to open file for writing");
return;
}
if(file.print(message)){
Serial.println("File written");
} else {
Serial.println("Write failed");
}
file.close();
}
void appendFile(fs::FS &fs, const char * path, const char * message, int newDataIndex){
Serial.printf("Updating file: %s\n", path);
// Open file for reading
File file = fs.open(path, FILE_READ);
if(!file){
Serial.println("Failed to open file for reading");
return;
}
// Create a new file
File newFile = fs.open("/temp.csv", FILE_WRITE);
if(!newFile){
Serial.println("Failed to create new file");
file.close(); // Ensure the original file is closed
return;
}
// Write the new data index to the new file
newFile.println(newDataIndex);
// Skip the first line of the original file
file.readStringUntil('\n');
// Copy the rest of the original file to the new file
while(file.available()){
newFile.write(file.read());
}
if(newFile.print(message)){
Serial.println("Data appended");
} else {
Serial.println("Append failed");
}
// Close both files
file.close();
newFile.close();
// Delete the original file
if(!fs.remove(path)){
Serial.println("Failed to remove original file");
return;
}
// Rename the new file to the original file's name
if(!fs.rename("/temp.csv", path)){
Serial.println("Failed to rename new file");
return;
}
Serial.println("File updated successfully");
}
/*
void renameFile(fs::FS &fs, const char * path1, const char * path2){
Serial.printf("Renaming file %s to %s\n", path1, path2);
if (fs.rename(path1, path2)) {
Serial.println("File renamed");
} else {
Serial.println("Rename failed");
}
}
void deleteFile(fs::FS &fs, const char * path){
Serial.printf("Deleting file: %s\n", path);
if(fs.remove(path)){
Serial.println("File deleted");
} else {
Serial.println("Delete failed");
}
}
void testFileIO(fs::FS &fs, const char * path){
File file = fs.open(path);
static uint8_t buf[512];
size_t len = 0;
uint32_t start = millis();
uint32_t end = start;
if(file){
len = file.size();
size_t flen = len;
start = millis();
while(len){
size_t toRead = len;
if(toRead > 512){
toRead = 512;
}
file.read(buf, toRead);
len -= toRead;
}
end = millis() - start;
Serial.printf("%u bytes read for %u ms\n", flen, end);
file.close();
} else {
Serial.println("Failed to open file for reading");
}
file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("Failed to open file for writing");
return;
}
size_t i;
start = millis();
for(i=0; i<2048; i++){
file.write(buf, 512);
}
end = millis() - start;
Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
file.close();
}
*/
// Read Serial with endmarker '\n'
void recvWithEndMarker() {
boolean exceeded = false;
static byte ndx = 0;
char endMarker = '\n';
char rc;
while (Serial.available() > 0 && newData == false) {
rc = Serial.read();
if (rc != endMarker && ndx <= numChars-1 ) {
notes[ndx] = rc;
ndx++;
}
else if (ndx >= numChars){
ndx++;
exceeded = true;
}
else{
notes[ndx] = '\0'; // terminate the string
ndx = 0;
newData = true;
return;
}
} // end while
if (exceeded){
Serial.println("Number of characters exceeded. Please try again.");
Serial.println("Message till limit reached:");
Serial.println(notes);
ndx = 0;
exceeded = false;
return;
} // end if
}
/*
// Populates data from csv in struct
void read2struct(fs::FS &fs, const char * path){
Serial.printf("Reading file: %s\n", path);
File file = fs.open(path);
if(!file){
Serial.println("Failed to open file for reading");
return;
}
while(file.available()){
// Read a line from the file
String line = file.readStringUntil('\n');
// Parse the CSV data
char* lineChar = &line[0u];
char* dateStr = strtok(lineChar, ",");
char* milesStr = strtok(NULL, ",");
char* noteStr = strtok(NULL, ",");
// Store the data in a struct
Data data;
data.date = String(dateStr);
data.miles = String(milesStr);
data.note = String(noteStr);
// Store the struct in the array
dataArray[dataIndex] = data;
dataIndex++;
// Now you can use data.date, data.data, and data.info
}
file.close();
}
*/
// Populates data from csv in array on a line by line basis
void read2array(fs::FS &fs, const char * path){
Serial.printf("Reading file: %s\n", path);
File file = fs.open(path);
if(!file){
Serial.println("Failed to open file for reading");
return;
}
while(file.available()){
// Read a line from the file
int i = 0;
while(file.available() && i < maxLen-1) { // 49 to leave space for null terminator
char c = file.read();
if(c == '\n') {
break;
}
dataArray[dataIndex][i] = c;
i++;
}
dataArray[dataIndex][i] = '\0'; // Null terminate the string
dataIndex++;
}
file.close();
}
// Function to update the first line with the new number of data lines
void updateNumLines(fs::FS &fs, const char * path, int newDataIndex){
Serial.printf("Updating file: %s\n", path);
// Open file for reading
File file = fs.open(path, FILE_READ);
if(!file){
Serial.println("Failed to open file for reading");
return;
}
// Create a new file
File newFile = fs.open("temp.csv", FILE_WRITE);
if(!newFile){
Serial.println("Failed to create new file");
return;
}
// Write the new data index to the new file
newFile.println(newDataIndex);
// Skip the first line of the original file
file.readStringUntil('\n');
// Copy the rest of the original file to the new file
while(file.available()){
newFile.write(file.read());
}
// Close both files
file.close();
newFile.close();
// Delete the original file
if(!fs.remove(path)){
Serial.println("Failed to remove original file");
return;
}
// Rename the new file to the original file's name
if(!fs.rename("temp.csv", path)){
Serial.println("Failed to rename new file");
return;
}
Serial.println("File updated successfully");
}
void readAndProcess(fs::FS &fs, const char * path){
Serial.printf("Reading file: %s\n", path);
File file = fs.open(path);
if(!file){
Serial.println("Failed to open file for reading");
return;
}
int lineIndex = 0;
while(file.available()){
// Read a line from the file
char line[11 + 8 + maxLen];
int i = 0;
while(file.available() && i < maxLen-1) { // Date, miles and 49 to leave space for null terminator
char c = file.read();
if(c == '\n') {
break;
}
line[i] = c;
i++;
}
line[i] = '\0'; // Null terminate the string
// If this is one of the lines we're interested in, split and store the data
if(lineIndex >= dataIndex - (linesOnScreen-1) && lineIndex <= dataIndex) {
splitData(line, dateL[lineIndex - dataIndex + (linesOnScreen-1)], milesL[lineIndex - dataIndex + (linesOnScreen-1)], noteL[lineIndex - dataIndex + (linesOnScreen-1)]);
}
lineIndex++;
}
file.close();
}
void splitData(char* data, char* date, char* miles, char* note) {
char* token = strtok(data, ",");
strcpy(date, token);
token = strtok(NULL, ",");
strcpy(miles, token);
token = strtok(NULL, ",");
strcpy(note, token);
}
void printLine(char* date, char* miles, char* note) {
Serial.print(date);
Serial.print(",");
Serial.print(miles);
Serial.print(",");
Serial.println(note);
}
void setup() {
Serial.begin(115200);
Serial.print("Initializing SD card...\n");
SPI.begin(SD_SCLK, SD_MISO, SD_MOSI, SD_CS);
if (!SD.begin(SD_CS)) {
Serial.println("Card initialization failed!");
while (true);
}
Serial.println("initialization done.");
uint32_t cardSize = SD.cardSize() / (1024 * 1024);
String str = "SDCard Size: " + String(cardSize) + "MB";
Serial.println(str);
uint8_t cardType = SD.cardType();
if(cardType == CARD_NONE) {
Serial.println("No SD card attached");
} // end if
listDir(SD, "/", 0);
readFile(SD, "/wokwi.csv");
Serial.printf("\nTotal space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
}
void loop() {
// Get message string
recvWithEndMarker();
// If data was received from serial
if (newData == true && strlen(notes) > 1) {
Serial.print("notes: ");
Serial.println(notes);
Serial.print("length: ");
Serial.println(strlen(notes));
// Build date string
// Initialize EMPTY character arrays
char dateString[13]="";
char buffer1[3]="";
// Convert int to strings
itoa(day,buffer1,10);
strcat(dateString, "10/");
strcat(dateString, buffer1);
strcat(dateString, "/2024");
day = day+1;
// Build mileage string
char mileageString[9]="";
dtostrf(mileage,-8,1,mileageString); // floatvalue, minimumwidth (includes . and - signs)(if negative, left justified, otherwise right justified), #decimalpoints, buffer
mileage = mileage + random(1000);
// Append separate strings to format for CSV file
char data[numChars+20]="";
strcat(data, "\n");
strcat(data,dateString);
strcat(data,",");
strcat(data,mileageString);
strcat(data,",");
strcat(data,notes);
// Save dataString to CSV file
dataIndex = dataIndex + 1;
//updateNumLines(SD, "/wokwi.csv", dataIndex);
appendFile(SD, "/wokwi.csv", data, dataIndex); // save to SD card
//// Delete out old message
//notes[0] = '\0';
// Update flag
newData = false;
readFile(SD, "/wokwi.csv"); // Show file
Serial.println("");
Serial.println(dataIndex);
}
// Runs if there's only one character
else if (newData == true && strlen(notes) == 1){
newData = false;
//read2struct(SD, "/wokwi.csv");
//read2array(SD, "/wokwi.csv");
//readFile(SD, "/wokwi.csv"); // Show file
// Get data
readAndProcess(SD, "/wokwi.csv");
// Print data
for(int i = 0; i < 4; i++) {
printLine(dateL[i], milesL[i], noteL[i]);
}
//Serial.println(dataArray[0]);
//Serial.println(dataArray[3]);
/*
// Access struct:
Serial.println(dataArray[0].date); // Arrays are 0-indexed, so index 1 is the second line
Serial.println(dataArray[0].miles); // Arrays are 0-indexed, so index 1 is the second line
Serial.println(dataArray[0].note); // Arrays are 0-indexed, so index 1 is the second line
Serial.println(dataArray[1].date); // Arrays are 0-indexed, so index 1 is the second line
Serial.println(dataArray[1].miles); // Arrays are 0-indexed, so index 1 is the second line
Serial.println(dataArray[1].note); // Arrays are 0-indexed, so index 1 is the second line
*/
}
delay(10); // used to speed up simulation
}