541 lines
15 KiB
C++
541 lines
15 KiB
C++
#include "globals.h"
|
|
#include BOARD_H
|
|
|
|
#ifdef SD_LOGGING
|
|
#include <SPI.h>
|
|
#ifdef __SD_H__
|
|
#include <SD.h>
|
|
#else
|
|
#include "SdFat.h"
|
|
#endif
|
|
#include "SD_logger.h"
|
|
#include "logger.h"
|
|
#include "rtc_common.h"
|
|
#include "maths.h"
|
|
|
|
SdExFat sd;
|
|
ExFile logFile;
|
|
RingBuf<ExFile, RING_BUF_CAPACITY> rb;
|
|
uint8_t SD_status = SD_STATUS_OFF;
|
|
uint16_t currentLogFileNumber;
|
|
bool manualLogActive = false;
|
|
uint32_t logStartTime = 0; //In ms
|
|
|
|
void initSD()
|
|
{
|
|
//Set default state to ready. If any stage of the init fails, this will be changed
|
|
SD_status = SD_STATUS_READY;
|
|
|
|
//Set the RTC callback. This is used to set the correct timestamp on file creation and sync operations
|
|
FsDateTime::setCallback(dateTime);
|
|
|
|
// Initialise the SD.
|
|
if (!sd.begin(SD_CONFIG))
|
|
{
|
|
//sd.initErrorHalt(&Serial);
|
|
//if (sdErrorCode() == SD_CARD_ERROR_CMD0) { SD_status = SD_STATUS_ERROR_NO_CARD;
|
|
SD_status = SD_STATUS_ERROR_NO_CARD;
|
|
}
|
|
|
|
//Set the TunerStudio status variable
|
|
setTS_SD_status();
|
|
}
|
|
|
|
bool createLogFile()
|
|
{
|
|
//TunerStudio only supports 8.3 filename format.
|
|
char filenameBuffer[13]; //8 + 1 + 3 + 1
|
|
bool returnValue = false;
|
|
|
|
//Saving this in case we ever go back to the datestamped filename
|
|
/*
|
|
//Filename format is: YYYY-MM-DD_HH.MM.SS.csv
|
|
char intBuffer[5];
|
|
itoa(rtc_getYear(), intBuffer, 10);
|
|
strcpy(filenameBuffer, intBuffer);
|
|
strcat(filenameBuffer, "-");
|
|
itoa(rtc_getMonth(), intBuffer, 10);
|
|
strcat(filenameBuffer, intBuffer);
|
|
strcat(filenameBuffer, "-");
|
|
itoa(rtc_getDay(), intBuffer, 10);
|
|
strcat(filenameBuffer, intBuffer);
|
|
strcat(filenameBuffer, "_");
|
|
itoa(rtc_getHour(), intBuffer, 10);
|
|
strcat(filenameBuffer, intBuffer);
|
|
strcat(filenameBuffer, ".");
|
|
itoa(rtc_getMinute(), intBuffer, 10);
|
|
strcat(filenameBuffer, intBuffer);
|
|
strcat(filenameBuffer, ".");
|
|
itoa(rtc_getSecond(), intBuffer, 10);
|
|
strcat(filenameBuffer, intBuffer);
|
|
strcat(filenameBuffer, ".csv");
|
|
*/
|
|
|
|
//Lookup the next available file number
|
|
currentLogFileNumber = getNextSDLogFileNumber();
|
|
|
|
//Create the filename
|
|
sprintf(filenameBuffer, "%s%04d.%s", LOG_FILE_PREFIX, currentLogFileNumber, LOG_FILE_EXTENSION);
|
|
|
|
logFile.close();
|
|
if (logFile.open(filenameBuffer, O_RDWR | O_CREAT | O_TRUNC))
|
|
{
|
|
returnValue = true;
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
uint16_t getNextSDLogFileNumber()
|
|
{
|
|
uint16_t nextFileNumber = 1;
|
|
char filenameBuffer[13]; //8 + 1 + 3 + 1
|
|
sprintf(filenameBuffer, "%s%04d.%s", LOG_FILE_PREFIX, nextFileNumber, LOG_FILE_EXTENSION);
|
|
|
|
//Lookup the next available file number
|
|
while( (nextFileNumber < MAX_LOG_FILES) && (sd.exists(filenameBuffer)) )
|
|
{
|
|
nextFileNumber++;
|
|
sprintf(filenameBuffer, "%s%04d.%s", LOG_FILE_PREFIX, nextFileNumber, LOG_FILE_EXTENSION);
|
|
}
|
|
|
|
return nextFileNumber;
|
|
}
|
|
|
|
bool getSDLogFileDetails(uint8_t* buffer, uint16_t logNumber)
|
|
{
|
|
bool fileFound = false;
|
|
|
|
if(logFile.isOpen()) { endSDLogging(); }
|
|
|
|
char filenameBuffer[13]; //8 + 1 + 3 + 1
|
|
sprintf(filenameBuffer, "%s%04d.%s", LOG_FILE_PREFIX, logNumber, LOG_FILE_EXTENSION);
|
|
|
|
if(sd.exists(filenameBuffer))
|
|
{
|
|
fileFound = true;
|
|
|
|
logFile = sd.open(filenameBuffer, O_RDONLY);
|
|
//Copy the filename into the buffer. Note we do not copy the termination character or the fullstop
|
|
for(byte i=0; i<12; i++)
|
|
{
|
|
//We don't copy the fullstop to the buffer
|
|
//As TS requires 8.3 filenames, it's always in the same spot
|
|
if(i < 8) { buffer[i] = filenameBuffer[i]; } //Everything before the fullstop
|
|
else if(i > 8) { buffer[i-1] = filenameBuffer[i]; } //Everything after the fullstop
|
|
}
|
|
|
|
//Maintenance check, truncate the file. This will usually do nothing, but in the case where a prior log was interrupted, this will truncate the file
|
|
//Due to overhead, only bother doing this if the engine isn't running
|
|
if(currentStatus.RPM == 0) { logFile.truncate(); }
|
|
|
|
//Is File or ignore
|
|
buffer[11] = 1;
|
|
|
|
//No idea
|
|
buffer[12] = 0;
|
|
|
|
//5 bytes for FAT creation date/time
|
|
uint16_t pDate = 0;
|
|
uint16_t pTime = 0;
|
|
logFile.getCreateDateTime(&pDate, &pTime);
|
|
buffer[13] = 0; //Not sure what this byte is for yet
|
|
buffer[14] = lowByte(pTime);
|
|
buffer[15] = highByte(pTime);
|
|
buffer[16] = lowByte(pDate);
|
|
buffer[17] = highByte(pDate);
|
|
|
|
//Sector number (4 bytes) - This byte order might be backwards
|
|
uint32_t sector = logFile.firstSector();
|
|
buffer[18] = ((sector) & 255);
|
|
buffer[19] = ((sector >> 8) & 255);
|
|
buffer[20] = ((sector >> 16) & 255);
|
|
buffer[21] = ((sector >> 24) & 255);
|
|
|
|
//Unsure on the below 6 bytes, possibly last accessed or modified date/time?
|
|
buffer[22] = 0;
|
|
buffer[23] = 0;
|
|
buffer[24] = 0;
|
|
buffer[25] = 0;
|
|
buffer[26] = 0;
|
|
buffer[27] = 0;
|
|
|
|
//File size (4 bytes). Little endian
|
|
uint32_t size = logFile.fileSize();
|
|
buffer[28] = ((size) & 255);
|
|
buffer[29] = ((size >> 8) & 255);
|
|
buffer[30] = ((size >> 16) & 255);
|
|
buffer[31] = ((size >> 24) & 255);
|
|
|
|
}
|
|
|
|
return fileFound;
|
|
}
|
|
|
|
void readSDSectors(uint8_t* buffer, uint32_t sectorNumber, uint16_t sectorCount)
|
|
{
|
|
sd.card()->readSectors(sectorNumber, buffer, sectorCount);
|
|
}
|
|
|
|
// Forward declare
|
|
void writeSDLogHeader();
|
|
|
|
void beginSDLogging()
|
|
{
|
|
if(SD_status == SD_STATUS_READY)
|
|
{
|
|
SD_status = SD_STATUS_ACTIVE; //Set the status as being active so that entries will begin to be written. This will be updated below if there is an error
|
|
|
|
// Open or create file - truncate existing file.
|
|
if (!createLogFile())
|
|
{
|
|
SD_status = SD_STATUS_ERROR_NO_WRITE;
|
|
setTS_SD_status();
|
|
return;
|
|
}
|
|
|
|
//Perform pre-allocation on card. This dramatically improves write speed
|
|
if (!logFile.preAllocate(SD_LOG_FILE_SIZE))
|
|
{
|
|
SD_status = SD_STATUS_ERROR_NO_SPACE;
|
|
setTS_SD_status();
|
|
return;
|
|
}
|
|
|
|
//initialise the RingBuf.
|
|
rb.begin(&logFile);
|
|
|
|
//Write a header row
|
|
writeSDLogHeader();
|
|
|
|
//Note the start time
|
|
logStartTime = millis();
|
|
}
|
|
}
|
|
|
|
void endSDLogging()
|
|
{
|
|
if(SD_status == SD_STATUS_ACTIVE)
|
|
{
|
|
// Write any RingBuf data to file.
|
|
rb.sync();
|
|
logFile.truncate();
|
|
logFile.rewind();
|
|
logFile.close();
|
|
logFile.sync(); //This is required to update the sd object. Without this any subsequent logfiles will overwrite this one
|
|
|
|
SD_status = SD_STATUS_READY;
|
|
setTS_SD_status();
|
|
}
|
|
}
|
|
|
|
// Forward declare
|
|
void checkForSDStart();
|
|
void checkForSDStop();
|
|
|
|
void writeSDLogEntry()
|
|
{
|
|
//Check if we're already running a log
|
|
if(SD_status == SD_STATUS_READY)
|
|
{
|
|
//Log not currently running, check if it should be
|
|
checkForSDStart();
|
|
}
|
|
|
|
if(SD_status == SD_STATUS_ACTIVE)
|
|
{
|
|
//Write the timestamp (x.yyy seconds format)
|
|
uint32_t duration = millis() - logStartTime;
|
|
uint32_t seconds = duration / 1000;
|
|
uint32_t milliseconds = duration % 1000;
|
|
rb.print(seconds);
|
|
rb.print('.');
|
|
if (milliseconds < 100) { rb.print("0"); }
|
|
if (milliseconds < 10) { rb.print("0"); }
|
|
rb.print(milliseconds);
|
|
rb.print(',');
|
|
|
|
//Write the line to the ring buffer
|
|
for(byte x=0; x<SD_LOG_NUM_FIELDS; x++)
|
|
{
|
|
#if FPU_MAX_SIZE >= 32
|
|
float entryValue = getReadableFloatLogEntry(x);
|
|
if(IS_INTEGER(entryValue)) { rb.print((uint16_t)entryValue); }
|
|
else { rb.print(entryValue); }
|
|
#else
|
|
rb.print(getReadableLogEntry(x));
|
|
#endif
|
|
if(x < (SD_LOG_NUM_FIELDS - 1)) { rb.print(","); }
|
|
}
|
|
rb.println("");
|
|
|
|
//Check if write to SD from ringbuffer is needed
|
|
//We write to SD when there is more than 1 sector worth of data in the ringbuffer and there is not already a write being performed
|
|
if( (rb.bytesUsed() >= SD_SECTOR_SIZE) && !logFile.isBusy() )
|
|
{
|
|
uint16_t bytesWritten = rb.writeOut(SD_SECTOR_SIZE);
|
|
//Make sure that the entire sector was written successfully
|
|
if (SD_SECTOR_SIZE != bytesWritten)
|
|
{
|
|
SD_status = SD_STATUS_ERROR_WRITE_FAIL;
|
|
}
|
|
}
|
|
|
|
//Check whether we should stop logging
|
|
checkForSDStop();
|
|
|
|
//Check whether the file is full (IE When there is not enough room to write 1 more sector)
|
|
if( (logFile.dataLength() - logFile.curPosition()) < SD_SECTOR_SIZE)
|
|
{
|
|
//Provided the conditions for logging are still met, a new file will be created the next time writeSDLogEntry is called
|
|
endSDLogging();
|
|
beginSDLogging();
|
|
}
|
|
}
|
|
setTS_SD_status();
|
|
}
|
|
|
|
void writeSDLogHeader()
|
|
{
|
|
//Write header for Time field
|
|
rb.print("Time,");
|
|
|
|
//WRite remaining fields based on log definitions
|
|
for(byte x=0; x<SD_LOG_NUM_FIELDS; x++)
|
|
{
|
|
#ifdef CORE_AVR
|
|
//This will probably never be used
|
|
char buffer[30];
|
|
strcpy_P(buffer, (char *)pgm_read_word(&(header_table[x])));
|
|
rb.print(buffer);
|
|
#else
|
|
rb.print(header_table[x]);
|
|
#endif
|
|
if(x < (SD_LOG_NUM_FIELDS - 1)) { rb.print(","); }
|
|
}
|
|
rb.println("");
|
|
}
|
|
|
|
//Sets the status variable for TunerStudio
|
|
void setTS_SD_status()
|
|
{
|
|
if( SD_status == SD_STATUS_ERROR_NO_CARD ) { BIT_CLEAR(currentStatus.TS_SD_Status, SD_STATUS_CARD_PRESENT); } // CARD is not present
|
|
else { BIT_SET(currentStatus.TS_SD_Status, SD_STATUS_CARD_PRESENT); } // CARD present
|
|
|
|
BIT_SET(currentStatus.TS_SD_Status, SD_STATUS_CARD_TYPE); // CARD is SDHC
|
|
|
|
BIT_SET(currentStatus.TS_SD_Status, SD_STATUS_CARD_READY); // CARD is ready
|
|
|
|
if( SD_status == SD_STATUS_ACTIVE ) { BIT_SET(currentStatus.TS_SD_Status, SD_STATUS_CARD_LOGGING); }// CARD is logging
|
|
else { BIT_CLEAR(currentStatus.TS_SD_Status, SD_STATUS_CARD_LOGGING); }// CARD is not logging
|
|
|
|
if( (SD_status >= SD_STATUS_ERROR_NO_FS) ) { BIT_SET(currentStatus.TS_SD_Status, SD_STATUS_CARD_ERROR); }// CARD has an error
|
|
else { BIT_CLEAR(currentStatus.TS_SD_Status, SD_STATUS_CARD_ERROR); }// CARD has no error
|
|
|
|
BIT_SET(currentStatus.TS_SD_Status, SD_STATUS_CARD_FS); // CARD has a FAT32 filesystem (Though this will be exFAT)
|
|
BIT_CLEAR(currentStatus.TS_SD_Status, SD_STATUS_CARD_UNUSED); //Unused bit is always 0
|
|
}
|
|
|
|
/**
|
|
* Checks whether the SD logging should be started based on the logging trigger conditions
|
|
*/
|
|
void checkForSDStart()
|
|
{
|
|
//Logging can only start if we're in the ready state
|
|
//We must check the SD_status each time to prevent trying to init a new log file multiple times
|
|
|
|
if(configPage13.onboard_log_file_style > 0)
|
|
{
|
|
//Check for enable at boot
|
|
if( (configPage13.onboard_log_trigger_boot) && (SD_status == SD_STATUS_READY) )
|
|
{
|
|
//Check that we're not already finished the logging
|
|
if((millis() / 1000) <= configPage13.onboard_log_tr1_duration)
|
|
{
|
|
beginSDLogging(); //Setup the log file, preallocation, header row
|
|
}
|
|
}
|
|
|
|
//Check for RPM based Enable
|
|
if( (configPage13.onboard_log_trigger_RPM) && (SD_status == SD_STATUS_READY) )
|
|
{
|
|
if( (currentStatus.RPMdiv100 >= configPage13.onboard_log_tr2_thr_on) && (currentStatus.RPMdiv100 <= configPage13.onboard_log_tr2_thr_off) ) //Need to check both on and off conditions to prevent logging starting and stopping continually
|
|
{
|
|
beginSDLogging(); //Setup the log file, preallocation, header row
|
|
}
|
|
}
|
|
|
|
//Check for engine protection based enable
|
|
if((configPage13.onboard_log_trigger_prot) && (SD_status == SD_STATUS_READY) )
|
|
{
|
|
if(currentStatus.engineProtectStatus > 0)
|
|
{
|
|
beginSDLogging(); //Setup the log file, preallocation, header row
|
|
}
|
|
}
|
|
|
|
if( (configPage13.onboard_log_trigger_Vbat) && (SD_status == SD_STATUS_READY) )
|
|
{
|
|
|
|
}
|
|
|
|
if((configPage13.onboard_log_trigger_Epin) && (SD_status == SD_STATUS_READY) )
|
|
{
|
|
if(digitalRead(pinSDEnable) == LOW)
|
|
{
|
|
beginSDLogging(); //Setup the log file, preallocation, header row
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks whether the SD logging should be stopped, based on the logging trigger conditions
|
|
*/
|
|
void checkForSDStop()
|
|
{
|
|
//Check the various conditions to see if we should stop logging
|
|
bool log_boot = false;
|
|
bool log_RPM = false;
|
|
bool log_prot = false;
|
|
bool log_Vbat = false;
|
|
bool log_Epin = false;
|
|
|
|
//Logging only needs to be stopped if already active
|
|
if(SD_status == SD_STATUS_ACTIVE)
|
|
{
|
|
//Check for enable at boot
|
|
if(configPage13.onboard_log_trigger_boot)
|
|
{
|
|
//Check if we're past the logging duration
|
|
if((millis() / 1000) <= configPage13.onboard_log_tr1_duration)
|
|
{
|
|
log_boot = true;
|
|
}
|
|
}
|
|
if(configPage13.onboard_log_trigger_RPM)
|
|
{
|
|
if( (currentStatus.RPMdiv100 >= configPage13.onboard_log_tr2_thr_on) && (currentStatus.RPMdiv100 <= configPage13.onboard_log_tr2_thr_off) )
|
|
{
|
|
log_RPM = true;
|
|
}
|
|
}
|
|
if(configPage13.onboard_log_trigger_prot)
|
|
{
|
|
if(currentStatus.engineProtectStatus > 0)
|
|
{
|
|
log_prot = true;
|
|
}
|
|
}
|
|
if(configPage13.onboard_log_trigger_Vbat)
|
|
{
|
|
|
|
}
|
|
|
|
//External Pin
|
|
if(configPage13.onboard_log_trigger_Epin)
|
|
{
|
|
if(digitalRead(pinSDEnable) == LOW)
|
|
{
|
|
log_Epin = true;
|
|
}
|
|
}
|
|
|
|
//Check all conditions to see if we should stop logging
|
|
if( (log_boot == false) && (log_RPM == false) && (log_prot == false) && (log_Vbat == false) && (log_Epin == false) && (manualLogActive == false) )
|
|
{
|
|
endSDLogging();
|
|
}
|
|
//ALso check whether logging has been disabled entirely
|
|
if(configPage13.onboard_log_file_style == 0) { endSDLogging(); }
|
|
}
|
|
|
|
|
|
}
|
|
|
|
void syncSDLog()
|
|
{
|
|
if( (SD_status == SD_STATUS_ACTIVE) && (!logFile.isBusy()) && (!sd.isBusy()) )
|
|
{
|
|
logFile.sync();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Will perform a complete format of the SD card to ExFAT.
|
|
* This will delete all files and create a new empty file system.
|
|
* The SD status will be set to busy when this happens to prevent any other operations
|
|
*/
|
|
void formatExFat()
|
|
{
|
|
bool result = false;
|
|
|
|
//Set the SD status to busy
|
|
BIT_CLEAR(currentStatus.TS_SD_Status, SD_STATUS_CARD_READY);
|
|
|
|
logFile.close();
|
|
|
|
if (sd.cardBegin(SD_CONFIG))
|
|
{
|
|
if(sd.format())
|
|
{
|
|
if (sd.volumeBegin())
|
|
{
|
|
result = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(result == false) { SD_status = SD_STATUS_ERROR_FORMAT_FAIL; }
|
|
else { BIT_SET(currentStatus.TS_SD_Status, SD_STATUS_CARD_READY); }
|
|
}
|
|
|
|
/**
|
|
* @brief Deletes a log file from the SD card
|
|
*
|
|
* Log files all have the same name with a 4 digit number at the end (Eg SPD_0001.csv). TS sends the 4 digits as ASCII characters and they are combined here with the logfile prefix
|
|
*
|
|
* @param log1
|
|
* @param log2
|
|
* @param log3
|
|
* @param log4
|
|
*/
|
|
void deleteLogFile(char log1, char log2, char log3, char log4)
|
|
{
|
|
char logFileName[13];
|
|
strcpy(logFileName, LOG_FILE_PREFIX);
|
|
logFileName[4] = log1;
|
|
logFileName[5] = log2;
|
|
logFileName[6] = log3;
|
|
logFileName[7] = log4;
|
|
logFileName[8] = '.';
|
|
strcpy(logFileName + 9, LOG_FILE_EXTENSION);
|
|
//logFileName[8] = '\0';
|
|
|
|
if(sd.exists(logFileName))
|
|
{
|
|
sd.remove(logFileName);
|
|
}
|
|
}
|
|
|
|
// Call back for file timestamps. Only called for file create and sync().
|
|
void dateTime(uint16_t* date, uint16_t* time, uint8_t* ms10) {
|
|
|
|
// Return date using FS_DATE macro to format fields.
|
|
//*date = FS_DATE(year(), month(), day());
|
|
*date = FS_DATE(rtc_getYear(), rtc_getMonth(), rtc_getDay());
|
|
|
|
// Return time using FS_TIME macro to format fields.
|
|
*time = FS_TIME(rtc_getHour(), rtc_getMinute(), rtc_getSecond());
|
|
|
|
// Return low time bits in units of 10 ms.
|
|
*ms10 = rtc_getSecond() & 1 ? 100 : 0;
|
|
}
|
|
|
|
uint32_t sectorCount()
|
|
{
|
|
return sd.card()->sectorCount();
|
|
}
|
|
|
|
#endif
|