/* Speeduino - Simple engine management for the Arduino Mega 2560 platform Copyright (C) Josh Stewart A full copy of the license may be found in the projects root directory */ /** @file * Process Incoming and outgoing serial communications. */ #include "globals.h" #include "comms.h" #include "comms_secondary.h" #include "storage.h" #include "maths.h" #include "utilities.h" #include "decoders.h" #include "TS_CommandButtonHandler.h" #include "errors.h" #include "pages.h" #include "page_crc.h" #include "logger.h" #include "comms_legacy.h" #include "src/FastCRC/FastCRC.h" #include #ifdef RTC_ENABLED #include "rtc_common.h" #include "comms_sd.h" #endif #ifdef SD_LOGGING #include "SD_logger.h" #endif // Forward declarations /** @brief Processes a message once it has been fully received */ void processSerialCommand(void); /** @brief Should be called when ::serialStatusFlag == SERIAL_TRANSMIT_TOOTH_INPROGRESS, */ void sendToothLog(void); /** @brief Should be called when ::serialStatusFlag == LOG_SEND_COMPOSITE */ void sendCompositeLog(void); #define SERIAL_RC_OK 0x00 //!< Success #define SERIAL_RC_REALTIME 0x01 //!< Unused #define SERIAL_RC_PAGE 0x02 //!< Unused #define SERIAL_RC_BURN_OK 0x04 //!< EEPROM write succeeded #define SERIAL_RC_TIMEOUT 0x80 //!< Timeout error #define SERIAL_RC_CRC_ERR 0x82 //!< CRC mismatch #define SERIAL_RC_UKWN_ERR 0x83 //!< Unknown command #define SERIAL_RC_RANGE_ERR 0x84 //!< Incorrect range. TS will not retry command #define SERIAL_RC_BUSY_ERR 0x85 //!< TS will wait and retry #define SERIAL_LEN_SIZE 2U #define SERIAL_TIMEOUT 3000 //ms #define SEND_OUTPUT_CHANNELS 48U #if defined(RTC_ENABLED) && defined(SD_LOGGING) #define COMMS_SD #endif //!@{ /** @brief Hard coded response for some TS messages. * @attention Stored in flash (.text segment) and loaded on demand. */ constexpr byte serialVersion[] PROGMEM = {SERIAL_RC_OK, '0', '0', '2'}; constexpr byte canId[] PROGMEM = {SERIAL_RC_OK, 0}; constexpr byte codeVersion[] PROGMEM = { SERIAL_RC_OK, 's','p','e','e','d','u','i','n','o',' ','2','0','2','4','0','5','-','d','e','v'} ; //Note no null terminator in array and statu variable at the start constexpr byte productString[] PROGMEM = { SERIAL_RC_OK, 'S', 'p', 'e', 'e', 'd', 'u', 'i', 'n', 'o', ' ', '2', '0', '2', '4', '.', '0', '5', '-', 'd', 'e', 'v'}; //constexpr byte codeVersion[] PROGMEM = { SERIAL_RC_OK, 's','p','e','e','d','u','i','n','o',' ','2','0','2','4','0','2'} ; //Note no null terminator in array and statu variable at the start //constexpr byte productString[] PROGMEM = { SERIAL_RC_OK, 'S', 'p', 'e', 'e', 'd', 'u', 'i', 'n', 'o', ' ', '2', '0', '2', '4', '.', '0', '2'}; constexpr byte testCommsResponse[] PROGMEM = { SERIAL_RC_OK, 255 }; //!@} /** @brief The number of bytes received or transmitted to date during nonblocking I/O. * * @attention We can share one variable between rx & tx because we only support simpex serial comms. * I.e. we can only be receiving or transmitting at any one time. */ static uint16_t serialBytesRxTx = 0; static uint32_t serialReceiveStartTime = 0; //!< The time at which the serial receive started. Used for calculating whether a timeout has occurred */ static FastCRC32 CRC32_serial; //!< Support accumulation of a CRC during non-blocking operations */ using crc_t = uint32_t; #ifdef COMMS_SD #undef SERIAL_BUFFER_SIZE /** @brief Serial payload buffer must be significantly larger for boards that support SD logging. * * Large enough to contain 4 sectors + overhead */ #define SERIAL_BUFFER_SIZE (2048 + 3) static uint16_t SDcurrentDirChunk; static uint32_t SDreadStartSector; static uint32_t SDreadNumSectors; static uint32_t SDreadCompletedSectors = 0; #endif static uint8_t serialPayload[SERIAL_BUFFER_SIZE]; //!< Serial payload buffer. */ static uint16_t serialPayloadLength = 0; //!< How many bytes in serialPayload were received or sent */ #if defined(CORE_AVR) #pragma GCC push_options // These minimize RAM usage at no performance cost #pragma GCC optimize ("Os") #endif static inline bool isTimeout(void) { return (millis() - serialReceiveStartTime) > SERIAL_TIMEOUT; } // ====================================== Endianness Support ============================= /** * @brief Flush all remaining bytes from the rx serial buffer */ void flushRXbuffer(void) { while (Serial.available() > 0) { Serial.read(); } } /** @brief Reverse the byte order of a uint32_t * * @attention noinline is needed to prevent enlarging callers stack frame, which in turn throws * off free ram reporting. * */ static __attribute__((noinline)) uint32_t reverse_bytes(uint32_t i) { return ((i>>24) & 0xffU) | // move byte 3 to byte 0 ((i<<8 ) & 0xff0000U) | // move byte 1 to byte 2 ((i>>8 ) & 0xff00U) | // move byte 2 to byte 1 ((i<<24) & 0xff000000U); } // ====================================== Blocking IO Support ================================ void writeByteReliableBlocking(byte value) { // Some platforms (I'm looking at you Teensy 3.5) do not mimic the Arduino 1.0 // contract and synchronously block. // https://github.com/PaulStoffregen/cores/blob/master/teensy3/usb_serial.c#L215 while (!Serial.availableForWrite()) { /* Wait for the buffer to free up space */ } Serial.write(value); } // ====================================== Multibyte Primitive IO Support ============================= /** @brief Read a uint32_t from Serial * * @attention noinline is needed to prevent enlarging callers stack frame, which in turn throws * off free ram reporting. * */ static __attribute__((noinline)) uint32_t readSerial32Timeout(void) { union { char raw[sizeof(uint32_t)]; uint32_t value; } buffer; // Teensy 3.5: Serial.available() should only be used as a boolean test // See https://www.pjrc.com/teensy/td_serial.html#singlebytepackets size_t count=0; while (count < sizeof(buffer.value)) { if (Serial.available()!=0) { buffer.raw[count++] =(byte)Serial.read(); } else if(isTimeout()) { return 0; } else { /* MISRA - no-op */ } } return reverse_bytes(buffer.value); } /** @brief Write a uint32_t to Serial * @returns The value as transmitted on the wire */ static uint32_t serialWrite(uint32_t value) { value = reverse_bytes(value); const byte *pBuffer = (const byte*)&value; writeByteReliableBlocking(pBuffer[0]); writeByteReliableBlocking(pBuffer[1]); writeByteReliableBlocking(pBuffer[2]); writeByteReliableBlocking(pBuffer[3]); return value; } /** @brief Write a uint16_t to Serial */ static void serialWrite(uint16_t value) { writeByteReliableBlocking((value >> 8U) & 255U); writeByteReliableBlocking(value & 255U); } // ====================================== Non-blocking IO Support ============================= /** @brief Send as much data as possible without blocking the caller * @return Number of bytes sent */ static uint16_t writeNonBlocking(const byte *buffer, size_t length) { uint16_t bytesTransmitted = 0; while (bytesTransmitted=length && start * ::serialStatusFlag == SERIAL_INACTIVE: send is complete
* ::serialStatusFlag == SERIAL_TRANSMIT_INPROGRESS: partial send, subsequent calls to continueSerialTransmission * will finish sending serialPayload * * @param payloadLength How many bytes to send [0, sizeof(serialPayload)) */ static void sendSerialPayloadNonBlocking(uint16_t payloadLength) { //Start new transmission session serialStatusFlag = SERIAL_TRANSMIT_INPROGRESS; serialWrite(payloadLength); serialPayloadLength = payloadLength; serialBytesRxTx = sendBufferAndCrcNonBlocking(serialPayload, 0, payloadLength); serialStatusFlag = serialBytesRxTx==payloadLength+sizeof(crc_t) ? SERIAL_INACTIVE : SERIAL_TRANSMIT_INPROGRESS; } // ====================================== TS Message Support ============================= /** @brief Send a message to TS containing only a return code. * * This is used when TS asks for an action to happen (E.g. start a logger) or * to signal an error condition to TS * * @attention This is a blocking operation */ static void sendReturnCodeMsg(byte returnCode) { serialWrite((uint16_t)sizeof(returnCode)); writeByteReliableBlocking(returnCode); serialWrite(CRC32_serial.crc32(&returnCode, sizeof(returnCode))); } // ====================================== Command/Action Support ============================= // The functions in this section are abstracted out to prevent enlarging callers stack frame, // which in turn throws off free ram reporting. /** * @brief Update a pages contents from a buffer * * @param pageNum The index of the page to update * @param offset Offset into the page * @param buffer The buffer to read from * @param length The buffer length * @return true if page updated successfully * @return false if page cannot be updated */ static bool updatePageValues(uint8_t pageNum, uint16_t offset, const byte *buffer, uint16_t length) { if ( (offset + length) <= getPageSize(pageNum) ) { for(uint16_t i = 0; i < length; i++) { setPageValue(pageNum, (offset + i), buffer[i]); } deferEEPROMWritesUntil = micros() + EEPROM_DEFER_DELAY; return true; } return false; } /** * @brief Loads a pages contents into a buffer * * @param pageNum The index of the page to update * @param offset Offset into the page * @param buffer The buffer to read from * @param length The buffer length */ static void loadPageValuesToBuffer(uint8_t pageNum, uint16_t offset, byte *buffer, uint16_t length) { for(uint16_t i = 0; i < length; i++) { buffer[i] = getPageValue(pageNum, offset + i); } } /** @brief Send a status record back to tuning/logging SW. * This will "live" information from @ref currentStatus struct. * @param offset - Start field number * @param packetLength - Length of actual message (after possible ack/confirm headers) * E.g. tuning sw command 'A' (Send all values) will send data from field number 0, LOG_ENTRY_SIZE fields. */ static void generateLiveValues(uint16_t offset, uint16_t packetLength) { if(firstCommsRequest) { firstCommsRequest = false; currentStatus.secl = 0; } currentStatus.spark ^= (-currentStatus.hasSync ^ currentStatus.spark) & (1U << BIT_SPARK_SYNC); //Set the sync bit of the Spark variable to match the hasSync variable serialPayload[0] = SERIAL_RC_OK; for(byte x=0; x= 1023 ) { //All chunks have been received (1024 values). Finalise the CRC and burn to EEPROM storeCalibrationCRC32(O2_CALIBRATION_PAGE, ~calibrationCRC); writeCalibrationPage(O2_CALIBRATION_PAGE); } } /** * @brief Convert 2 bytes into an offset temperature in degrees Celsius * @attention Returned value will be offset CALIBRATION_TEMPERATURE_OFFSET */ static uint16_t toTemperature(byte lo, byte hi) { int16_t tempValue = (int16_t)(word(hi, lo)); //Combine the 2 bytes into a single, signed 16-bit value tempValue = tempValue / 10; //TS sends values multiplied by 10 so divide back to whole degrees. tempValue = ((tempValue - 32) * 5) / 9; //Convert from F to C //Apply the temp offset and check that it results in all values being positive return max( tempValue + CALIBRATION_TEMPERATURE_OFFSET, 0 ); } /** * @brief Update a temperature calibration table from serialPayload * * @param calibrationLength The chunk size received from TS * @param calibrationPage Index of the table * @param values The table values * @param bins The table bin values */ static void processTemperatureCalibrationTableUpdate(uint16_t calibrationLength, uint8_t calibrationPage, uint16_t *values, uint16_t *bins) { //Temperature calibrations are sent as 32 16-bit values if(calibrationLength == 64U) { for (uint16_t x = 0; x < 32U; x++) { values[x] = toTemperature(serialPayload[(2U * x) + 7U], serialPayload[(2U * x) + 8U]); bins[x] = (x * 33U); // 0*33=0 to 31*33=1023 } storeCalibrationCRC32(calibrationPage, CRC32_serial.crc32(&serialPayload[7], 64)); writeCalibrationPage(calibrationPage); sendReturnCodeMsg(SERIAL_RC_OK); } else { sendReturnCodeMsg(SERIAL_RC_RANGE_ERR); } } // ====================================== End Internal Functions ============================= /** Processes the incoming data on the serial buffer based on the command sent. Can be either data for a new command or a continuation of data for command that is already in progress: Commands are single byte (letter symbol) commands. */ void serialReceive(void) { //Check for an existing legacy command in progress if(serialStatusFlag==SERIAL_COMMAND_INPROGRESS_LEGACY) { legacySerialCommand(); return; } if (Serial.available()!=0 && serialStatusFlag == SERIAL_INACTIVE) { //New command received //Need at least 2 bytes to read the length of the command byte highByte = (byte)Serial.peek(); //Check if the command is legacy using the call/response mechanism if(highByte == 'F') { //F command is always allowed as it provides the initial serial protocol version. legacySerialCommand(); return; } else if( (((highByte >= 'A') && (highByte <= 'z')) || (highByte == '?')) && (BIT_CHECK(currentStatus.status4, BIT_STATUS4_ALLOW_LEGACY_COMMS)) ) { //Handle legacy cases here legacySerialCommand(); return; } else { Serial.read(); while(Serial.available() == 0) { /* Wait for the 2nd byte to be received (This will almost never happen) */ } serialPayloadLength = word(highByte, Serial.read()); serialBytesRxTx = 2; serialStatusFlag = SERIAL_RECEIVE_INPROGRESS; //Flag the serial receive as being in progress serialReceiveStartTime = millis(); } } //If there is a serial receive in progress, read as much from the buffer as possible or until we receive all bytes while( (Serial.available() > 0) && (serialStatusFlag == SERIAL_RECEIVE_INPROGRESS) ) { if (serialBytesRxTx < (serialPayloadLength + SERIAL_LEN_SIZE) ) { serialPayload[serialBytesRxTx - SERIAL_LEN_SIZE] = (byte)Serial.read(); serialBytesRxTx++; } else { uint32_t incomingCrc = readSerial32Timeout(); serialStatusFlag = SERIAL_INACTIVE; //The serial receive is now complete if (!isTimeout()) // CRC read can timeout also! { if (incomingCrc == CRC32_serial.crc32(serialPayload, serialPayloadLength)) { //CRC is correct. Process the command processSerialCommand(); BIT_CLEAR(currentStatus.status4, BIT_STATUS4_ALLOW_LEGACY_COMMS); //Lock out legacy commands until next power cycle } else { //CRC Error. Need to send an error message sendReturnCodeMsg(SERIAL_RC_CRC_ERR); flushRXbuffer(); } } // else timeout - code below will kick in. } } //Data in serial buffer and serial receive in progress //Check for a timeout if( isTimeout() ) { serialStatusFlag = SERIAL_INACTIVE; //Reset the serial receive flushRXbuffer(); sendReturnCodeMsg(SERIAL_RC_TIMEOUT); } //Timeout } void serialTransmit(void) { switch (serialStatusFlag) { case SERIAL_TRANSMIT_INPROGRESS_LEGACY: sendValues(logItemsTransmitted, inProgressLength, SEND_OUTPUT_CHANNELS, Serial, serialStatusFlag); break; case SERIAL_TRANSMIT_TOOTH_INPROGRESS: sendToothLog(); break; case SERIAL_TRANSMIT_TOOTH_INPROGRESS_LEGACY: sendToothLog_legacy(logItemsTransmitted); break; case SERIAL_TRANSMIT_COMPOSITE_INPROGRESS: sendCompositeLog(); break; case SERIAL_TRANSMIT_INPROGRESS: serialBytesRxTx = sendBufferAndCrcNonBlocking(serialPayload, serialBytesRxTx, serialPayloadLength); serialStatusFlag = serialBytesRxTx==serialPayloadLength+sizeof(crc_t) ? SERIAL_INACTIVE : SERIAL_TRANSMIT_INPROGRESS; break; default: // Nothing to do break; } } void processSerialCommand(void) { switch (serialPayload[0]) { case 'A': // send x bytes of realtime values in legacy support format generateLiveValues(0, LOG_ENTRY_SIZE); break; case 'b': // New EEPROM burn command to only burn a single page at a time if( (micros() > deferEEPROMWritesUntil)) { writeConfig(serialPayload[2]); } //Read the table number and perform burn. Note that byte 1 in the array is unused else { BIT_SET(currentStatus.status4, BIT_STATUS4_BURNPENDING); } sendReturnCodeMsg(SERIAL_RC_BURN_OK); break; case 'B': // Same as above, but for the comms compat mode. Slows down the burn rate and increases the defer time BIT_SET(currentStatus.status4, BIT_STATUS4_COMMS_COMPAT); //Force the compat mode deferEEPROMWritesUntil += (EEPROM_DEFER_DELAY/4); //Add 25% more to the EEPROM defer time if( (micros() > deferEEPROMWritesUntil)) { writeConfig(serialPayload[2]); } //Read the table number and perform burn. Note that byte 1 in the array is unused else { BIT_SET(currentStatus.status4, BIT_STATUS4_BURNPENDING); } sendReturnCodeMsg(SERIAL_RC_BURN_OK); break; case 'C': // test communications. This is used by Tunerstudio to see whether there is an ECU on a given serial port (void)memcpy_P(serialPayload, testCommsResponse, sizeof(testCommsResponse) ); sendSerialPayloadNonBlocking(sizeof(testCommsResponse)); break; case 'd': // Send a CRC32 hash of a given page { uint32_t CRC32_val = reverse_bytes(calculatePageCRC32( serialPayload[2] )); serialPayload[0] = SERIAL_RC_OK; (void)memcpy(&serialPayload[1], &CRC32_val, sizeof(CRC32_val)); sendSerialPayloadNonBlocking(5); break; } case 'E': // receive command button commands (void)TS_CommandButtonsHandler(word(serialPayload[1], serialPayload[2])); sendReturnCodeMsg(SERIAL_RC_OK); break; case 'f': //Send serial capability details serialPayload[0] = SERIAL_RC_OK; serialPayload[1] = 2; //Serial protocol version serialPayload[2] = highByte(BLOCKING_FACTOR); serialPayload[3] = lowByte(BLOCKING_FACTOR); serialPayload[4] = highByte(TABLE_BLOCKING_FACTOR); serialPayload[5] = lowByte(TABLE_BLOCKING_FACTOR); sendSerialPayloadNonBlocking(6); break; case 'F': // send serial protocol version (void)memcpy_P(serialPayload, serialVersion, sizeof(serialVersion) ); sendSerialPayloadNonBlocking(sizeof(serialVersion)); break; case 'H': //Start the tooth logger startToothLogger(); sendReturnCodeMsg(SERIAL_RC_OK); break; case 'h': //Stop the tooth logger stopToothLogger(); sendReturnCodeMsg(SERIAL_RC_OK); break; case 'I': // send CAN ID (void)memcpy_P(serialPayload, canId, sizeof(canId) ); sendSerialPayloadNonBlocking(sizeof(serialVersion)); break; case 'J': //Start the composite logger startCompositeLogger(); sendReturnCodeMsg(SERIAL_RC_OK); break; case 'j': //Stop the composite logger stopCompositeLogger(); sendReturnCodeMsg(SERIAL_RC_OK); break; case 'k': //Send CRC values for the calibration pages { uint32_t CRC32_val = reverse_bytes(readCalibrationCRC32(serialPayload[2])); //Get the CRC for the requested page serialPayload[0] = SERIAL_RC_OK; (void)memcpy(&serialPayload[1], &CRC32_val, sizeof(CRC32_val)); sendSerialPayloadNonBlocking(5); break; } case 'M': { //New write command //7 bytes required: //2 - Page identifier //2 - offset //2 - Length //1 - 1st New value if (updatePageValues(serialPayload[2], word(serialPayload[4], serialPayload[3]), &serialPayload[7], word(serialPayload[6], serialPayload[5]))) { sendReturnCodeMsg(SERIAL_RC_OK); } else { //This should never happen, but just in case sendReturnCodeMsg(SERIAL_RC_RANGE_ERR); } break; } case 'O': //Start the composite logger 2nd cam (teritary) startCompositeLoggerTertiary(); sendReturnCodeMsg(SERIAL_RC_OK); break; case 'o': //Stop the composite logger 2nd cam (tertiary) stopCompositeLoggerTertiary(); sendReturnCodeMsg(SERIAL_RC_OK); break; case 'X': //Start the composite logger 2nd cam (teritary) startCompositeLoggerCams(); sendReturnCodeMsg(SERIAL_RC_OK); break; case 'x': //Stop the composite logger 2nd cam (tertiary) stopCompositeLoggerCams(); sendReturnCodeMsg(SERIAL_RC_OK); break; /* * New method for sending page values (MS command equivalent is 'r') */ case 'p': { //6 bytes required: //2 - Page identifier //2 - offset //2 - Length uint16_t length = word(serialPayload[6], serialPayload[5]); //Setup the transmit buffer serialPayload[0] = SERIAL_RC_OK; loadPageValuesToBuffer(serialPayload[2], word(serialPayload[4], serialPayload[3]), &serialPayload[1], length); sendSerialPayloadNonBlocking(length + 1U); break; } case 'Q': // send code version (void)memcpy_P(serialPayload, codeVersion, sizeof(codeVersion) ); sendSerialPayloadNonBlocking(sizeof(codeVersion)); break; case 'r': //New format for the optimised OutputChannels { uint8_t cmd = serialPayload[2]; uint16_t offset = word(serialPayload[4], serialPayload[3]); uint16_t length = word(serialPayload[6], serialPayload[5]); #ifdef COMMS_SD uint16_t SD_arg1 = word(serialPayload[3], serialPayload[4]); uint16_t SD_arg2 = word(serialPayload[5], serialPayload[6]); #endif if(cmd == SEND_OUTPUT_CHANNELS) //Send output channels command 0x30 is 48dec { generateLiveValues(offset, length); sendSerialPayloadNonBlocking(length + 1U); } else if(cmd == 0x0f) { //Request for signature (void)memcpy_P(serialPayload, codeVersion, sizeof(codeVersion) ); sendSerialPayloadNonBlocking(sizeof(codeVersion)); } #ifdef COMMS_SD else if(cmd == SD_RTC_PAGE) //Request to read SD card RTC { serialPayload[0] = SERIAL_RC_OK; serialPayload[1] = rtc_getSecond(); //Seconds serialPayload[2] = rtc_getMinute(); //Minutes serialPayload[3] = rtc_getHour(); //Hours serialPayload[4] = rtc_getDOW(); //Day of week serialPayload[5] = rtc_getDay(); //Day of month serialPayload[6] = rtc_getMonth(); //Month serialPayload[7] = highByte(rtc_getYear()); //Year serialPayload[8] = lowByte(rtc_getYear()); //Year sendSerialPayloadNonBlocking(9); } else if(cmd == SD_READWRITE_PAGE) //Request SD card extended parameters { //SD read commands use the offset and length fields to indicate the request type if((SD_arg1 == SD_READ_STAT_ARG1) && (SD_arg2 == SD_READ_STAT_ARG2)) { //Read the status of the SD card serialPayload[0] = SERIAL_RC_OK; serialPayload[1] = currentStatus.TS_SD_Status; serialPayload[2] = 0; //Error code //Sector size = 512 serialPayload[3] = 2; serialPayload[4] = 0; //Max blocks (4 bytes) uint32_t sectors = sectorCount(); serialPayload[5] = ((sectors >> 24) & 255); serialPayload[6] = ((sectors >> 16) & 255); serialPayload[7] = ((sectors >> 8) & 255); serialPayload[8] = (sectors & 255); /* serialPayload[5] = 0; serialPayload[6] = 0x20; //1gb dummy card serialPayload[7] = 0; serialPayload[8] = 0; */ //Max roots (Number of files) uint16_t numLogFiles = getNextSDLogFileNumber() - 2; // -1 because this returns the NEXT file name not the current one and -1 because TS expects a 0 based index serialPayload[9] = highByte(numLogFiles); serialPayload[10] = lowByte(numLogFiles); //Dir Start (4 bytes) serialPayload[11] = 0; serialPayload[12] = 0; serialPayload[13] = 0; serialPayload[14] = 0; //Unknown purpose for last 2 bytes serialPayload[15] = 0; serialPayload[16] = 0; sendSerialPayloadNonBlocking(17); } else if((SD_arg1 == SD_READ_DIR_ARG1) && (SD_arg2 == SD_READ_DIR_ARG2)) { //Send file details serialPayload[0] = SERIAL_RC_OK; uint16_t logFileNumber = (SDcurrentDirChunk * 16) + 1; uint8_t filesInCurrentChunk = 0; uint16_t payloadIndex = 1; while((filesInCurrentChunk < 16) && (getSDLogFileDetails(&serialPayload[payloadIndex], logFileNumber) == true)) { logFileNumber++; filesInCurrentChunk++; payloadIndex += 32; } serialPayload[payloadIndex] = lowByte(SDcurrentDirChunk); serialPayload[payloadIndex + 1] = highByte(SDcurrentDirChunk); //Serial.print("Index:"); //Serial.print(payloadIndex); sendSerialPayloadNonBlocking(payloadIndex + 2); } } else if(cmd == SD_READFILE_PAGE) { //Fetch data from file if(SD_arg2 == SD_READ_COMP_ARG2) { //arg1 is the block number to return serialPayload[0] = SERIAL_RC_OK; serialPayload[1] = highByte(SD_arg1); serialPayload[2] = lowByte(SD_arg1); uint32_t currentSector = SDreadStartSector + (SD_arg1 * 4); int32_t numSectorsToSend = 0; if(SDreadNumSectors > SDreadCompletedSectors) { numSectorsToSend = SDreadNumSectors - SDreadCompletedSectors; if(numSectorsToSend > 4) //Maximum of 4 sectors at a time { numSectorsToSend = 4; } } SDreadCompletedSectors += numSectorsToSend; if(numSectorsToSend <= 0) { sendReturnCodeMsg(SERIAL_RC_OK); } else { readSDSectors(&serialPayload[3], currentSector, numSectorsToSend); sendSerialPayloadNonBlocking(numSectorsToSend * SD_SECTOR_SIZE + 3); } } } #endif else { //No other r/ commands should be called } break; } case 'S': // send code version (void)memcpy_P(serialPayload, productString, sizeof(productString) ); sendSerialPayloadNonBlocking(sizeof(productString)); currentStatus.secl = 0; //This is required in TS3 due to its stricter timings break; case 'T': //Send 256 tooth log entries to Tuner Studios tooth logger logItemsTransmitted = 0; if(currentStatus.toothLogEnabled == true) { sendToothLog(); } //Sends tooth log values as ints else if (currentStatus.compositeTriggerUsed > 0) { sendCompositeLog(); } else { /* MISRA no-op */ } break; case 't': // receive new Calibration info. Command structure: "t", . { uint8_t cmd = serialPayload[2]; uint16_t offset = word(serialPayload[3], serialPayload[4]); uint16_t calibrationLength = word(serialPayload[5], serialPayload[6]); // Should be 256 if(cmd == O2_CALIBRATION_PAGE) { loadO2CalibrationChunk(offset, calibrationLength); sendReturnCodeMsg(SERIAL_RC_OK); Serial.flush(); //This is safe because engine is assumed to not be running during calibration } else if(cmd == IAT_CALIBRATION_PAGE) { processTemperatureCalibrationTableUpdate(calibrationLength, IAT_CALIBRATION_PAGE, iatCalibration_values, iatCalibration_bins); } else if(cmd == CLT_CALIBRATION_PAGE) { processTemperatureCalibrationTableUpdate(calibrationLength, CLT_CALIBRATION_PAGE, cltCalibration_values, cltCalibration_bins); } else { sendReturnCodeMsg(SERIAL_RC_RANGE_ERR); } break; } case 'U': //User wants to reset the Arduino (probably for FW update) if (resetControl != RESET_CONTROL_DISABLED) { #ifndef SMALL_FLASH_MODE if (serialStatusFlag == SERIAL_INACTIVE) { Serial.println(F("Comms halted. Next byte will reset the Arduino.")); } #endif while (Serial.available() == 0) { } digitalWrite(pinResetControl, LOW); } else { #ifndef SMALL_FLASH_MODE if (serialStatusFlag == SERIAL_INACTIVE) { Serial.println(F("Reset control is currently disabled.")); } #endif } break; case 'w': { #ifdef COMMS_SD uint8_t cmd = serialPayload[2]; uint16_t SD_arg1 = word(serialPayload[3], serialPayload[4]); uint16_t SD_arg2 = word(serialPayload[5], serialPayload[6]); if(cmd == SD_READWRITE_PAGE) { if((SD_arg1 == SD_WRITE_DO_ARG1) && (SD_arg2 == SD_WRITE_DO_ARG2)) { /* SD DO command. Single byte of data where the commands are: 0 Reset 1 Reset 2 Stop logging 3 Start logging 4 Load status variable 5 Init SD card */ uint8_t command = serialPayload[7]; if(command == 2) { endSDLogging(); manualLogActive = false; } else if(command == 3) { beginSDLogging(); manualLogActive = true; } else if(command == 4) { setTS_SD_status(); } //else if(command == 5) { initSD(); } sendReturnCodeMsg(SERIAL_RC_OK); } else if((SD_arg1 == SD_WRITE_DIR_ARG1) && (SD_arg2 == SD_WRITE_DIR_ARG2)) { //Begin SD directory read. Value in payload represents the directory chunk to read //Directory chunks are each 16 files long SDcurrentDirChunk = word(serialPayload[7], serialPayload[8]); sendReturnCodeMsg(SERIAL_RC_OK); } else if((SD_arg1 == SD_WRITE_READ_SEC_ARG1) && (SD_arg2 == SD_WRITE_READ_SEC_ARG2)) { //Read sector Init? Unsure what this is meant to do however it is sent at the beginning of a Card Format request and requires an OK response //Provided the sector being requested is x0 x0 x0 x0, we treat this as a SD Card format request if( (serialPayload[7] == 0) && (serialPayload[8] == 0) && (serialPayload[9] == 0) && (serialPayload[10] == 0) ) { //SD Card format request formatExFat(); } sendReturnCodeMsg(SERIAL_RC_OK); } else if((SD_arg1 == SD_WRITE_WRITE_SEC_ARG1) && (SD_arg2 == SD_WRITE_WRITE_SEC_ARG2)) { //SD write sector command } else if((SD_arg1 == SD_ERASEFILE_ARG1) && (SD_arg2 == SD_ERASEFILE_ARG2)) { //Erase file command //We just need the 4 ASCII characters of the file name char log1 = serialPayload[7]; char log2 = serialPayload[8]; char log3 = serialPayload[9]; char log4 = serialPayload[10]; deleteLogFile(log1, log2, log3, log4); sendReturnCodeMsg(SERIAL_RC_OK); } else if((SD_arg1 == SD_SPD_TEST_ARG1) && (SD_arg2 == SD_SPD_TEST_ARG2)) { //Perform a speed test on the SD card //First 4 bytes are the sector number to write to /* TODO: Need to write test routine uint32_t sector; uint8_t sector1 = serialPayload[7]; uint8_t sector2 = serialPayload[8]; uint8_t sector3 = serialPayload[9]; uint8_t sector4 = serialPayload[10]; sector = (sector1 << 24) | (sector2 << 16) | (sector3 << 8) | sector4; //Last 4 bytes are the number of sectors to test uint32_t testSize; uint8_t testSize1 = serialPayload[11]; uint8_t testSize2 = serialPayload[12]; uint8_t testSize3 = serialPayload[13]; uint8_t testSize4 = serialPayload[14]; testSize = (testSize1 << 24) | (testSize2 << 16) | (testSize3 << 8) | testSize4; */ sendReturnCodeMsg(SERIAL_RC_OK); } else if((SD_arg1 == SD_WRITE_COMP_ARG1) && (SD_arg2 == SD_WRITE_COMP_ARG2)) { //Prepare to read a 2024 byte chunk of data from the SD card uint8_t sector1 = serialPayload[7]; uint8_t sector2 = serialPayload[8]; uint8_t sector3 = serialPayload[9]; uint8_t sector4 = serialPayload[10]; //SDreadStartSector = (sector1 << 24) | (sector2 << 16) | (sector3 << 8) | sector4; SDreadStartSector = (sector4 << 24) | (sector3 << 16) | (sector2 << 8) | sector1; //SDreadStartSector = sector4 | (sector3 << 8) | (sector2 << 16) | (sector1 << 24); //Next 4 bytes are the number of sectors to write uint8_t sectorCount1 = serialPayload[11]; uint8_t sectorCount2 = serialPayload[12]; uint8_t sectorCount3 = serialPayload[13]; uint8_t sectorCount4 = serialPayload[14]; SDreadNumSectors = (sectorCount1 << 24) | (sectorCount2 << 16) | (sectorCount3 << 8) | sectorCount4; //Reset the sector counter SDreadCompletedSectors = 0; sendReturnCodeMsg(SERIAL_RC_OK); } } else if(cmd == SD_RTC_PAGE) { //Used for setting RTC settings if((SD_arg1 == SD_RTC_WRITE_ARG1) && (SD_arg2 == SD_RTC_WRITE_ARG2)) { //Set the RTC date/time byte second = serialPayload[7]; byte minute = serialPayload[8]; byte hour = serialPayload[9]; //byte dow = serialPayload[10]; //Not used byte day = serialPayload[11]; byte month = serialPayload[12]; uint16_t year = word(serialPayload[13], serialPayload[14]); rtc_setTime(second, minute, hour, day, month, year); sendReturnCodeMsg(SERIAL_RC_OK); } } #endif break; } default: //Unknown command sendReturnCodeMsg(SERIAL_RC_UKWN_ERR); break; } } /** * */ void sendToothLog(void) { //We need TOOTH_LOG_SIZE number of records to send to TunerStudio. If there aren't that many in the buffer then we just return and wait for the next call if (BIT_CHECK(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY) == false) { //If the buffer is not yet full but TS has timed out, pad the rest of the buffer with 0s while(toothHistoryIndex < TOOTH_LOG_SIZE) { toothHistory[toothHistoryIndex] = 0; toothHistoryIndex++; } } uint32_t CRC32_val = 0; if(logItemsTransmitted == 0) { //Transmit the size of the packet (void)serialWrite((uint16_t)(sizeof(toothHistory) + 1U)); //Size of the tooth log (uint32_t values) plus the return code //Begin new CRC hash const uint8_t returnCode = SERIAL_RC_OK; CRC32_val = CRC32_serial.crc32(&returnCode, 1, false); //Send the return code writeByteReliableBlocking(returnCode); } for (; logItemsTransmitted < TOOTH_LOG_SIZE; logItemsTransmitted++) { //Check whether the tx buffer still has space if(Serial.availableForWrite() < 4) { //tx buffer is full. Store the current state so it can be resumed later serialStatusFlag = SERIAL_TRANSMIT_TOOTH_INPROGRESS; return; } //Transmit the tooth time uint32_t transmitted = serialWrite(toothHistory[logItemsTransmitted]); CRC32_val = CRC32_serial.crc32_upd((const byte*)&transmitted, sizeof(transmitted), false); } BIT_CLEAR(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY); serialStatusFlag = SERIAL_INACTIVE; toothHistoryIndex = 0; logItemsTransmitted = 0; //Apply the CRC reflection CRC32_val = ~CRC32_val; //Send the CRC (void)serialWrite(CRC32_val); } void sendCompositeLog(void) { if ( BIT_CHECK(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY) == false ) { //If the buffer is not yet full but TS has timed out, pad the rest of the buffer with 0s while(toothHistoryIndex < TOOTH_LOG_SIZE) { toothHistory[toothHistoryIndex] = toothHistory[toothHistoryIndex-1]; //Composite logger needs a realistic time value to display correctly. Copy the last value compositeLogHistory[toothHistoryIndex] = 0; toothHistoryIndex++; } } uint32_t CRC32_val = 0; if(logItemsTransmitted == 0) { //Transmit the size of the packet (void)serialWrite((uint16_t)(sizeof(toothHistory) + sizeof(compositeLogHistory) + 1U)); //Size of the tooth log (uint32_t values) plus the return code //Begin new CRC hash const uint8_t returnCode = SERIAL_RC_OK; CRC32_val = CRC32_serial.crc32(&returnCode, 1, false); //Send the return code writeByteReliableBlocking(returnCode); } for (; logItemsTransmitted < TOOTH_LOG_SIZE; logItemsTransmitted++) { //Check whether the tx buffer still has space if((uint16_t)Serial.availableForWrite() < sizeof(toothHistory[logItemsTransmitted])+sizeof(compositeLogHistory[logItemsTransmitted])) { //tx buffer is full. Store the current state so it can be resumed later serialStatusFlag = SERIAL_TRANSMIT_COMPOSITE_INPROGRESS; return; } uint32_t transmitted = serialWrite(toothHistory[logItemsTransmitted]); //This combined runtime (in us) that the log was going for by this record CRC32_serial.crc32_upd((const byte*)&transmitted, sizeof(transmitted), false); //The status byte (Indicates the trigger edge, whether it was a pri/sec pulse, the sync status) writeByteReliableBlocking(compositeLogHistory[logItemsTransmitted]); CRC32_val = CRC32_serial.crc32_upd((const byte*)&compositeLogHistory[logItemsTransmitted], sizeof(compositeLogHistory[logItemsTransmitted]), false); } BIT_CLEAR(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY); toothHistoryIndex = 0; serialStatusFlag = SERIAL_INACTIVE; logItemsTransmitted = 0; //Apply the CRC reflection CRC32_val = ~CRC32_val; //Send the CRC (void)serialWrite(CRC32_val); } #if defined(CORE_AVR) #pragma GCC pop_options #endif