From f95b4978cd0c0c848d42195563d5fdda7b180b41 Mon Sep 17 00:00:00 2001 From: tx_haggis <13982343+adbancroft@users.noreply.github.com> Date: Mon, 20 Feb 2023 19:55:54 -0600 Subject: [PATCH] Refactor comms: save 130+ bytes RAM (#906) * Remove serialCRC - only used within parseSerial() Also hoist the CRC read into a function. * Minimize global variable visibility * Encapsulate write of multi-byte primitives * Factor out sendBufferAndCrc() * Push safety test into TS_CommandButtonsHandler() * Extract writePage() * Simpler parsing * Remove some functions from public interface * Store constant arrays in progmem * Centralize high speed logger start/stop code * Factor out loadO2Calibration() * Factor out temperature calibration table update functions * Remove dead code * Fix sendToothLog() * Fix sendCompositeLog() * Replace tooth log send booleans with an enum Saves a byte * Remove sendBufferAndCrcProgMem() Use serialPayload to send * Whitespace clean up * Optimize comms.cpp for size * Replace global unsigned long with bool Saves 2 bytes * Replace 2 global bools with an enum Saves a byte, reads better. * Remove global FastCRC instance * Make sendSerialReturnCode blocking. It was using non-blocking functions but was never re-entered. Rename to make blocking & non-blocking calls more obvious. * Use one uint16_t to track RX/TX byte count * Simplify new comms log tx API * Extract loadPageToBuffer function * All endianess changes use the same code * Doxygen comments and code organization * Remove serialWriteUpdateCrc() & updateTmpCalibration() * Combine SerialStatus & logSendStatus enums. Makes sense since we can only be doing one thing at a time. * Remove global inProgressCompositeTime Only used when sending composite log * Replace 3 global bools with expanded SerialStatus enum * Remove unused global tsCanId * Limit scope of some comms globals. * Remove isMap global - replace witth function * Reduce the serial API to only 2 calls transmit & receive * Tidy up #define visibility * Fix Black* build errors * Workaround Teensy code race condition availableForWrite() is not reliable. * Prevent race condition Was pematurely setting the serialStatusFlag to SERIAL_INACTIVE before final CRC ws read from serial. * Use post write buffer availability checks Remove buffer size check prior to writing. * Write multi-byte values as single bytes. (attempt to fix Teensy 3.5 issue) * Only use Serial.available() as a boolean test (Teensy fix) * writeNonBlocking checks Serial.write() return value * Non-blocking CRC write In sendBufferAndCrcNonBlocking(). * Fix compile warning * Set serial status flag prior to transmitting! * Reliable blocking byte writes. * Fix timeout code: not firing under some conditions * MISRA fixes --- Doxyfile | 120 ++- speeduino/TS_CommandButtonHandler.h | 2 +- speeduino/TS_CommandButtonHandler.ino | 16 +- speeduino/comms.cpp | 1122 ++++++++++++------------- speeduino/comms.h | 80 +- speeduino/comms_legacy.cpp | 226 ++--- speeduino/comms_legacy.h | 75 +- speeduino/comms_sd.h | 35 + speeduino/globals.h | 7 +- speeduino/globals.ino | 1 - speeduino/logger.h | 6 + speeduino/logger.ino | 56 +- speeduino/page_crc.cpp | 45 +- speeduino/speeduino.ino | 35 +- 14 files changed, 948 insertions(+), 878 deletions(-) create mode 100644 speeduino/comms_sd.h diff --git a/Doxyfile b/Doxyfile index 6620f9ad..681c040f 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.9.3 +# Doxyfile 1.9.4 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -12,6 +12,15 @@ # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables: +# doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options @@ -60,16 +69,28 @@ PROJECT_LOGO = reference/speeduino_logo.png OUTPUT_DIRECTORY = reference/doxygen -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes -# performance problems for the file system. +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# numer of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode @@ -81,14 +102,14 @@ ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English @@ -389,7 +410,7 @@ IDL_PROPERTY_SUPPORT = YES # all members of a group must be documented explicitly. # The default value is: NO. -DISTRIBUTE_GROUP_DOC = NO +DISTRIBUTE_GROUP_DOC = YES # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option @@ -452,7 +473,7 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 -# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use # during processing. When set to 0 doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing @@ -577,7 +598,7 @@ INTERNAL_DOCS = NO # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that -# are not case sensitive the option should be be set to NO to properly deal with +# are not case sensitive the option should be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On @@ -851,10 +872,21 @@ WARN_AS_ERROR = NO # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). In case the file specified cannot be opened for writing the @@ -874,7 +906,8 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = speeduino/ contributing.md +INPUT = speeduino/ \ + contributing.md # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -1166,6 +1199,46 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS +# tag is set to YES then doxygen will add the directory of each input to the +# include path. +# The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -2164,6 +2237,10 @@ DOCBOOK_OUTPUT = docbook GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- @@ -2238,7 +2315,8 @@ SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by the -# preprocessor. +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. # This tag requires that the tag SEARCH_INCLUDES is set to YES. INCLUDE_PATH = @@ -2259,7 +2337,8 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = __attribute__((x))= \ + PROGMEM # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -2411,7 +2490,8 @@ CLASS_GRAPH = YES COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. +# groups, showing the direct groups dependencies. See also the chapter Grouping +# in the manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. diff --git a/speeduino/TS_CommandButtonHandler.h b/speeduino/TS_CommandButtonHandler.h index aad36c8b..165c8217 100644 --- a/speeduino/TS_CommandButtonHandler.h +++ b/speeduino/TS_CommandButtonHandler.h @@ -71,4 +71,4 @@ #define TS_CMD_VSS_RATIO6 39174 /* the maximum id number is 65,535 */ -void TS_CommandButtonsHandler(uint16_t buttonCommand); +bool TS_CommandButtonsHandler(uint16_t buttonCommand); diff --git a/speeduino/TS_CommandButtonHandler.ino b/speeduino/TS_CommandButtonHandler.ino index c995ab48..20a81693 100644 --- a/speeduino/TS_CommandButtonHandler.ino +++ b/speeduino/TS_CommandButtonHandler.ino @@ -15,13 +15,24 @@ #include "acc_mc33810.h" #endif +static bool commandRequiresStoppedEngine(uint16_t buttonCommand) +{ + return ((buttonCommand >= TS_CMD_INJ1_ON) && (buttonCommand <= TS_CMD_IGN8_50PC)) + || ((buttonCommand == TS_CMD_TEST_ENBL) || (buttonCommand == TS_CMD_TEST_DSBL)); +} + /** * @brief * * @param buttonCommand The command number of the button that was clicked. See TS_CommendButtonHandler.h for a list of button IDs */ -void TS_CommandButtonsHandler(uint16_t buttonCommand) +bool TS_CommandButtonsHandler(uint16_t buttonCommand) { + if (commandRequiresStoppedEngine(buttonCommand) && currentStatus.RPM != 0) + { + return false; + } + switch (buttonCommand) { case TS_CMD_TEST_DSBL: // cmd is stop @@ -363,6 +374,9 @@ void TS_CommandButtonsHandler(uint16_t buttonCommand) #endif default: + return false; break; } + + return true; } diff --git a/speeduino/comms.cpp b/speeduino/comms.cpp index 807eaf8f..0b8b5374 100644 --- a/speeduino/comms.cpp +++ b/speeduino/comms.cpp @@ -4,7 +4,7 @@ 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. + * Process Incoming and outgoing serial communications. */ #include "globals.h" #include "comms.h" @@ -20,31 +20,89 @@ A full copy of the license may be found in the projects root directory #include "logger.h" #include "comms_legacy.h" #include "src/FastCRC/FastCRC.h" -#include "table3d_axis_io.h" +#include #ifdef RTC_ENABLED #include "rtc_common.h" + #include "comms_sd.h" #endif #ifdef SD_LOGGING #include "SD_logger.h" #endif -uint16_t serialPayloadLength = 0; -bool serialReceivePending = false; /**< Whether or not a serial request has only been partially received. This occurs when a the length has been received in the serial buffer, but not all of the payload or CRC has yet been received. */ -uint16_t serialBytesReceived = 0; /**< The number of bytes received in the serial buffer during the current command. */ -uint32_t serialCRC = 0; -bool serialWriteInProgress = false; -uint16_t serialBytesTransmitted = 0; -uint32_t serialReceiveStartTime = 0; /**< The time at which the serial receive started. Used for calculating whether a timeout has occurred */ -FastCRC32 CRC32_serial; //This instance of CRC32 is exclusively used on the comms envelope CRC validations. It is separate to those used for page or calibration calculations to prevent update calls clashing with one another +// Forward declarations + +/** @brief Processes a message once it has been fully recieved */ +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 + +//!@{ +/** @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','2','1','0','-','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', '2', '.', '1', '0', '-', 'd', 'e', 'v'}; +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 RTC_ENABLED - uint8_t serialPayload[SD_FILE_TRANSMIT_BUFFER_SIZE]; /**< Serial payload buffer must be significantly larger for boards that support SD logging. Large enough to contain 4 sectors + overhead */ - uint16_t SDcurrentDirChunk; - uint32_t SDreadStartSector; - uint32_t SDreadNumSectors; - uint32_t SDreadCompletedSectors = 0; -#else - uint8_t serialPayload[SERIAL_BUFFER_SIZE]; /**< Serial payload buffer. */ +#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; +} + +// ====================================== Endianess Support ============================= /** * @brief Flush all remaining bytes from the rx serial buffer @@ -54,192 +112,448 @@ 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= 'A') && (highByte <= 'z')) || (highByte == '?') ) { //Handle legacy cases here - serialReceivePending = false; //Make sure new serial handling does not interfere with legacy handling - legacySerial = true; - currentCommand = highByte; legacySerialCommand(); return; } else { - while(Serial.available() == 0) { } //Wait for the 2nd byte to be received (This will almost never happen) + Serial.read(); + while(Serial.available() == 0) { /* Wait for the 2nd byte to be received (This will almost never happen) */ } - byte lowByte = Serial.read(); - serialPayloadLength = word(highByte, lowByte); - serialBytesReceived = 2; - cmdPending = false; // Make sure legacy handling does not interfere with new serial handling + 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) && (serialReceivePending == true) ) + while( (Serial.available() > 0) && (serialStatusFlag == SERIAL_RECEIVE_INPROGRESS) ) { - if (serialBytesReceived < (serialPayloadLength + SERIAL_LEN_SIZE) ) + if (serialBytesRxTx < (serialPayloadLength + SERIAL_LEN_SIZE) ) { - serialPayload[(serialBytesReceived - SERIAL_LEN_SIZE)] = Serial.read(); - serialBytesReceived++; + serialPayload[serialBytesRxTx - SERIAL_LEN_SIZE] = (byte)Serial.read(); + serialBytesRxTx++; } - else if (Serial.available() >= SERIAL_CRC_LENGTH) + else { - uint32_t crc1 = Serial.read(); - uint32_t crc2 = Serial.read(); - uint32_t crc3 = Serial.read(); - uint32_t crc4 = Serial.read(); - serialCRC = (crc1<<24) | (crc2<<16) | (crc3<<8) | crc4; - - serialReceivePending = false; //The serial receive is now complete + uint32_t incomingCrc = readSerial32Timeout(); + serialStatusFlag = SERIAL_INACTIVE; //The serial receive is now complete - //Test the CRC - uint32_t receivedCRC = CRC32_serial.crc32(serialPayload, serialPayloadLength); - - //receivedCRC++; - if(serialCRC != receivedCRC) + if (!isTimeout()) // CRC read can timeout also! { - //CRC Error. Need to send an error message - sendSerialReturnCode(SERIAL_RC_CRC_ERR); - flushRXbuffer(); + if (incomingCrc == CRC32_serial.crc32(serialPayload, serialPayloadLength)) + { + //CRC is correct. Process the command + processSerialCommand(); + } + else { + //CRC Error. Need to send an error message + sendReturnCodeMsg(SERIAL_RC_CRC_ERR); + flushRXbuffer(); + } } - else - { - //CRC is correct. Process the command - processSerialCommand(); - } //CRC match - } //CRC received in full - - //Check for a timeout - if( (millis() - serialReceiveStartTime) > SERIAL_TIMEOUT) - { - //Timeout occurred - serialReceivePending = false; //Reset the serial receive - - flushRXbuffer(); - sendSerialReturnCode(SERIAL_RC_TIMEOUT); - } //Timeout + // else timeout - code below will kick in. + } } //Data in serial buffer and serial receive in progress -} -void sendSerialReturnCode(byte returnCode) -{ - Serial.write((uint8_t)0); - Serial.write((uint8_t)1); //Size is always 1 - - Serial.write(returnCode); - - //Calculate and send CRC - uint32_t CRC32_val = CRC32_serial.crc32(&returnCode, 1); - Serial.write( ((CRC32_val >> 24) & 255) ); - Serial.write( ((CRC32_val >> 16) & 255) ); - Serial.write( ((CRC32_val >> 8) & 255) ); - Serial.write( (CRC32_val & 255) ); -} - -void sendSerialPayload(void *payload, uint16_t payloadLength) -{ - //Start new transmission session - serialBytesTransmitted = 0; - serialWriteInProgress = false; - - uint16_t totalPayloadLength = payloadLength; - Serial.write(totalPayloadLength >> 8); - Serial.write(totalPayloadLength); - - //Need to handle serial buffer being full. This is just for testing - serialPayloadLength = payloadLength; //Save the payload length in case we need to transmit in multiple steps - for(uint16_t i = 0; i < payloadLength; i++) + //Check for a timeout + if( isTimeout() ) { - Serial.write(((uint8_t*)payload)[i]); - serialBytesTransmitted++; + serialStatusFlag = SERIAL_INACTIVE; //Reset the serial receive - if(Serial.availableForWrite() == 0) - { - //Serial buffer is full. Need to wait for it to be free - serialWriteInProgress = true; + flushRXbuffer(); + sendReturnCodeMsg(SERIAL_RC_TIMEOUT); + } //Timeout +} + +void serialTransmit(void) +{ + switch (serialStatusFlag) + { + case SERIAL_TRANSMIT_INPROGRESS_LEGACY: + sendValues(logItemsTransmitted, inProgressLength, SEND_OUTPUT_CHANNELS, 0); break; - } - } - if(serialWriteInProgress == false) - { - //All data transmitted. Send the CRC - uint32_t CRC32_val = CRC32_serial.crc32((uint8_t*)payload, payloadLength); - Serial.write( ((CRC32_val >> 24) & 255) ); - Serial.write( ((CRC32_val >> 16) & 255) ); - Serial.write( ((CRC32_val >> 8) & 255) ); - Serial.write( (CRC32_val & 255) ); - } -} + case SERIAL_TRANSMIT_TOOTH_INPROGRESS: + sendToothLog(); + break; -void continueSerialTransmission(void) -{ - if(serialWriteInProgress == true) - { - serialWriteInProgress = false; //Assume we will reach the end of the serial buffer. If we run out of buffer, this will be set to true below - //Serial buffer is free. Continue sending the data - for(uint16_t i = serialBytesTransmitted; i < serialPayloadLength; i++) - { - Serial.write(serialPayload[i]); - serialBytesTransmitted++; + case SERIAL_TRANSMIT_TOOTH_INPROGRESS_LEGACY: + sendToothLog_legacy(logItemsTransmitted); + break; - if(Serial.availableForWrite() == 0) - { - //Serial buffer is full. Need to wait for it to be free - serialWriteInProgress = true; - break; - } - } + case SERIAL_TRANSMIT_COMPOSITE_INPROGRESS: + sendCompositeLog(); + break; - if(serialWriteInProgress == false) - { - //All data transmitted. Send the CRC - uint32_t CRC32_val = CRC32_serial.crc32(serialPayload, serialPayloadLength); - Serial.write( ((CRC32_val >> 24) & 255) ); - Serial.write( ((CRC32_val >> 16) & 255) ); - Serial.write( ((CRC32_val >> 8) & 255) ); - Serial.write( (CRC32_val & 255) ); - } + 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) { - currentCommand = serialPayload[0]; - - switch (currentCommand) + switch (serialPayload[0]) { case 'A': // send x bytes of realtime values - //sendValues(0, LOG_ENTRY_SIZE, 0x31, 0); //send values to serial0 generateLiveValues(0, LOG_ENTRY_SIZE); break; @@ -247,145 +561,66 @@ void processSerialCommand(void) 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); } - sendSerialReturnCode(SERIAL_RC_BURN_OK); + 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 - { - uint8_t tempPayload[] = {SERIAL_RC_OK, currentStatus.secl}; - sendSerialPayload(&tempPayload, 2); + (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 = calculatePageCRC32( serialPayload[2] ); - uint8_t payloadCRC32[5]; - - //First byte is the flag - payloadCRC32[0] = SERIAL_RC_OK; - - //Split the 4 bytes of the CRC32 value into individual bytes and send - payloadCRC32[1] = ((CRC32_val >> 24) & 255); - payloadCRC32[2] = ((CRC32_val >> 16) & 255); - payloadCRC32[3] = ((CRC32_val >> 8) & 255); - payloadCRC32[4] = (CRC32_val & 255); - - sendSerialPayload( &payloadCRC32, 5); + 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 - { - uint16_t cmdCombined = word(serialPayload[1], serialPayload[2]); - - if ( ((cmdCombined >= TS_CMD_INJ1_ON) && (cmdCombined <= TS_CMD_IGN8_50PC)) || (cmdCombined == TS_CMD_TEST_ENBL) || (cmdCombined == TS_CMD_TEST_DSBL) ) - { - //Hardware test buttons - if (currentStatus.RPM == 0) { TS_CommandButtonsHandler(cmdCombined); } - } - else if( (cmdCombined >= TS_CMD_VSS_60KMH) && (cmdCombined <= TS_CMD_VSS_RATIO6) ) - { - //VSS Calibration commands - TS_CommandButtonsHandler(cmdCombined); - } - else if( (cmdCombined >= TS_CMD_STM32_REBOOT) && (cmdCombined <= TS_CMD_STM32_BOOTLOADER) ) - { - //STM32 DFU mode button - TS_CommandButtonsHandler(cmdCombined); - } - else if( (cmdCombined >= TS_CMD_SD_FORMAT) && (cmdCombined <= TS_CMD_SD_FORMAT) ) - { - //SD Commands - TS_CommandButtonsHandler(cmdCombined); - } - sendSerialReturnCode(SERIAL_RC_OK); + (void)TS_CommandButtonsHandler(word(serialPayload[1], serialPayload[2])); + sendReturnCodeMsg(SERIAL_RC_OK); break; - } case 'F': // send serial protocol version - { - byte serialVersion[] = {SERIAL_RC_OK, '0', '0', '2'}; - sendSerialPayload(&serialVersion, 4); + (void)memcpy_P(serialPayload, serialVersion, sizeof(serialVersion) ); + sendSerialPayloadNonBlocking(sizeof(serialVersion)); break; - } case 'H': //Start the tooth logger - currentStatus.toothLogEnabled = true; - currentStatus.compositeLogEnabled = false; //Safety first (Should never be required) - toothLogSendInProgress = false; - BIT_CLEAR(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY); - toothHistoryIndex = 0; - - //Disconnect the standard interrupt and add the logger version - detachInterrupt( digitalPinToInterrupt(pinTrigger) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger), loggerPrimaryISR, CHANGE ); - - detachInterrupt( digitalPinToInterrupt(pinTrigger2) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger2), loggerSecondaryISR, CHANGE ); - - sendSerialReturnCode(SERIAL_RC_OK); + startToothLogger(); + sendReturnCodeMsg(SERIAL_RC_OK); break; case 'h': //Stop the tooth logger - currentStatus.toothLogEnabled = false; - - //Disconnect the logger interrupts and attach the normal ones - detachInterrupt( digitalPinToInterrupt(pinTrigger) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger), triggerHandler, primaryTriggerEdge ); - - detachInterrupt( digitalPinToInterrupt(pinTrigger2) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger2), triggerSecondaryHandler, secondaryTriggerEdge ); - sendSerialReturnCode(SERIAL_RC_OK); + stopToothLogger(); + sendReturnCodeMsg(SERIAL_RC_OK); break; case 'I': // send CAN ID - { - byte serialVersion[] = {SERIAL_RC_OK, 0}; - sendSerialPayload(&serialVersion, 2); + (void)memcpy_P(serialPayload, canId, sizeof(canId) ); + sendSerialPayloadNonBlocking(sizeof(serialVersion)); break; - } case 'J': //Start the composite logger - currentStatus.compositeLogEnabled = true; - currentStatus.toothLogEnabled = false; //Safety first (Should never be required) - BIT_CLEAR(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY); - toothHistoryIndex = 0; - - //Disconnect the standard interrupt and add the logger version - detachInterrupt( digitalPinToInterrupt(pinTrigger) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger), loggerPrimaryISR, CHANGE ); - - detachInterrupt( digitalPinToInterrupt(pinTrigger2) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger2), loggerSecondaryISR, CHANGE ); - - sendSerialReturnCode(SERIAL_RC_OK); + startCompositeLogger(); + sendReturnCodeMsg(SERIAL_RC_OK); break; case 'j': //Stop the composite logger - currentStatus.compositeLogEnabled = false; - - //Disconnect the logger interrupts and attach the normal ones - detachInterrupt( digitalPinToInterrupt(pinTrigger) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger), triggerHandler, primaryTriggerEdge ); - - detachInterrupt( digitalPinToInterrupt(pinTrigger2) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger2), triggerSecondaryHandler, secondaryTriggerEdge ); - sendSerialReturnCode(SERIAL_RC_OK); + stopCompositeLogger(); + sendReturnCodeMsg(SERIAL_RC_OK); break; case 'k': //Send CRC values for the calibration pages { - uint32_t CRC32_val = readCalibrationCRC32(serialPayload[2]); //Get the CRC for the requested page + uint32_t CRC32_val = reverse_bytes(readCalibrationCRC32(serialPayload[2])); //Get the CRC for the requested page serialPayload[0] = SERIAL_RC_OK; - serialPayload[1] = ((CRC32_val >> 24) & 255); - serialPayload[2] = ((CRC32_val >> 16) & 255); - serialPayload[3] = ((CRC32_val >> 8) & 255); - serialPayload[4] = (CRC32_val & 255); - sendSerialPayload( &serialPayload, 5); - + (void)memcpy(&serialPayload[1], &CRC32_val, sizeof(CRC32_val)); + sendSerialPayloadNonBlocking(5); break; } @@ -397,32 +632,15 @@ void processSerialCommand(void) //2 - offset //2 - Length //1 - 1st New value - byte offset1, offset2, length1, length2; - - uint8_t currentPage = serialPayload[2]; //Page ID is 2 bytes, but as the first byte is always 0 it can be ignored - offset1 = serialPayload[3]; - offset2 = serialPayload[4]; - uint16_t valueOffset = word(offset2, offset1); - length1 = serialPayload[5]; - length2 = serialPayload[6]; - uint16_t chunkSize = word(length2, length1); - - if( (valueOffset + chunkSize) > getPageSize(currentPage)) + 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 - sendSerialReturnCode(SERIAL_RC_RANGE_ERR); - break; + sendReturnCodeMsg(SERIAL_RC_RANGE_ERR); } - - for(uint16_t i = 0; i < chunkSize; i++) - { - setPageValue(currentPage, (valueOffset + i), serialPayload[7 + i]); - } - - deferEEPROMWritesUntil = micros() + EEPROM_DEFER_DELAY; - - sendSerialReturnCode(SERIAL_RC_OK); - break; } @@ -435,35 +653,19 @@ void processSerialCommand(void) //2 - Page identifier //2 - offset //2 - Length - byte offset1, offset2, length1, length2; - int length; - byte tempPage; - - tempPage = serialPayload[2]; - offset1 = serialPayload[3]; - offset2 = serialPayload[4]; - valueOffset = word(offset2, offset1); - length1 = serialPayload[5]; - length2 = serialPayload[6]; - length = word(length2, length1); + uint16_t length = word(serialPayload[6], serialPayload[5]); //Setup the transmit buffer serialPayload[0] = SERIAL_RC_OK; - for(int i = 0; i < length; i++) - { - serialPayload[i+1] = getPageValue(tempPage, valueOffset + i); - } - sendSerialPayload(&serialPayload, (length + 1)); + loadPageValuesToBuffer(serialPayload[2], word(serialPayload[4], serialPayload[3]), &serialPayload[1], length); + sendSerialPayloadNonBlocking(length + 1U); break; } case 'Q': // send code version - { - char productString[] = { SERIAL_RC_OK, 's','p','e','e','d','u','i','n','o',' ','2','0','2','2','1','0','-','d','e','v'} ; //Note no null terminator in array and statu variable at the start - //char productString[] = { SERIAL_RC_OK, 's','p','e','e','d','u','i','n','o',' ','2','0','2','2','0','7'} ; //Note no null terminator in array and statu variable at the start - sendSerialPayload(&productString, sizeof(productString)); + (void)memcpy_P(serialPayload, codeVersion, sizeof(codeVersion) ); + sendSerialPayloadNonBlocking(sizeof(codeVersion)); break; - } case 'r': //New format for the optimised OutputChannels { @@ -475,10 +677,10 @@ void processSerialCommand(void) uint16_t SD_arg2 = word(serialPayload[5], serialPayload[6]); #endif - if(cmd == 0x30) //Send output channels command 0x30 is 48dec + if(cmd == SEND_OUTPUT_CHANNELS) //Send output channels command 0x30 is 48dec { generateLiveValues(offset, length); - sendSerialPayload(&serialPayload, (length + 1)); + sendSerialPayloadNonBlocking(length + 1U); } #ifdef RTC_ENABLED else if(cmd == SD_RTC_PAGE) //Request to read SD card RTC @@ -492,8 +694,7 @@ void processSerialCommand(void) serialPayload[6] = rtc_getMonth(); //Month serialPayload[7] = highByte(rtc_getYear()); //Year serialPayload[8] = lowByte(rtc_getYear()); //Year - sendSerialPayload(&serialPayload, 9); - + sendSerialPayloadNonBlocking(9); } else if(cmd == SD_READWRITE_PAGE) //Request SD card extended parameters { @@ -539,7 +740,7 @@ void processSerialCommand(void) serialPayload[15] = 0; serialPayload[16] = 0; - sendSerialPayload(&serialPayload, 17); + sendSerialPayloadNonBlocking(17); } else if((SD_arg1 == SD_READ_DIR_ARG1) && (SD_arg2 == SD_READ_DIR_ARG2)) @@ -561,10 +762,8 @@ void processSerialCommand(void) //Serial.print("Index:"); //Serial.print(payloadIndex); - sendSerialPayload(&serialPayload, (payloadIndex + 2)); - + sendSerialPayloadNonBlocking(payloadIndex + 2); } - } else if(cmd == SD_READFILE_PAGE) { @@ -589,11 +788,11 @@ void processSerialCommand(void) } SDreadCompletedSectors += numSectorsToSend; - if(numSectorsToSend <= 0) { sendSerialReturnCode(SERIAL_RC_OK); } + if(numSectorsToSend <= 0) { sendReturnCodeMsg(SERIAL_RC_OK); } else { readSDSectors(&serialPayload[3], currentSector, numSectorsToSend); - sendSerialPayload(&serialPayload, (numSectorsToSend * SD_SECTOR_SIZE + 3)); + sendSerialPayloadNonBlocking(numSectorsToSend * SD_SECTOR_SIZE + 3); } } } @@ -602,147 +801,45 @@ void processSerialCommand(void) { //No other r/ commands should be called } - cmdPending = false; - break; } case 'S': // send code version - { - byte productString[] = { SERIAL_RC_OK, 'S', 'p', 'e', 'e', 'd', 'u', 'i', 'n', 'o', ' ', '2', '0', '2', '2', '.', '1', '0', '-', 'd', 'e', 'v'}; - //byte productString[] = { SERIAL_RC_OK, 'S', 'p', 'e', 'e', 'd', 'u', 'i', 'n', 'o', ' ', '2', '0', '2', '2', '0', '7'}; - sendSerialPayload(&productString, sizeof(productString)); + (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 - if(currentStatus.toothLogEnabled == true) { sendToothLog(0); } //Sends tooth log values as ints - else if (currentStatus.compositeLogEnabled == true) { sendCompositeLog(0); } - + logItemsTransmitted = 0; + if(currentStatus.toothLogEnabled == true) { sendToothLog(); } //Sends tooth log values as ints + else if (currentStatus.compositeLogEnabled == true) { sendCompositeLog(); } + else { /* MISRA no-op */ } break; case 't': // receive new Calibration info. Command structure: "t", . { uint8_t cmd = serialPayload[2]; - uint16_t valueOffset = word(serialPayload[3], serialPayload[4]); + uint16_t offset = word(serialPayload[3], serialPayload[4]); uint16_t calibrationLength = word(serialPayload[5], serialPayload[6]); // Should be 256 - uint32_t calibrationCRC = 0; if(cmd == O2_CALIBRATION_PAGE) { - //TS sends a total of 1024 bytes of calibration data, broken up into 256 byte chunks - //As we're using an interpolated 2D table, we only need to store 32 values out of this 1024 - void* pnt_TargetTable_values = (uint8_t *)&o2Calibration_values; //Pointer that will be used to point to the required target table values - uint16_t* pnt_TargetTable_bins = (uint16_t *)&o2Calibration_bins; //Pointer that will be used to point to the required target table bins - - //Read through the current chunk (Should be 256 bytes long) - for(uint16_t x = 0; x < calibrationLength; x++) - { - uint16_t totalOffset = valueOffset + x; - //Only apply every 32nd value - if( (x % 32) == 0 ) - { - ((uint8_t*)pnt_TargetTable_values)[(totalOffset/32)] = serialPayload[x+7]; //O2 table stores 8 bit values - pnt_TargetTable_bins[(totalOffset/32)] = (totalOffset); - - } - - //Update the CRC - if(totalOffset == 0) - { - calibrationCRC = CRC32.crc32(&serialPayload[7], 1, false); - } - else - { - calibrationCRC = CRC32.crc32_upd(&serialPayload[x+7], 1, false); - } - //Check if CRC is finished - if(totalOffset == 1023) - { - //apply CRC reflection - calibrationCRC = ~calibrationCRC; - storeCalibrationCRC32(O2_CALIBRATION_PAGE, calibrationCRC); - } - } - sendSerialReturnCode(SERIAL_RC_OK); + loadO2CalibrationChunk(offset, calibrationLength); + sendReturnCodeMsg(SERIAL_RC_OK); Serial.flush(); //This is safe because engine is assumed to not be running during calibration - - //Check if this is the final chunk of calibration data - #ifdef CORE_STM32 - //STM32 requires TS to send 16 x 64 bytes chunk rather than 4 x 256 bytes. - if(valueOffset == (64*15)) { writeCalibrationPage(cmd); } //Store received values in EEPROM if this is the final chunk of calibration - #else - if(valueOffset == (256*3)) { writeCalibrationPage(cmd); } //Store received values in EEPROM if this is the final chunk of calibration - #endif } else if(cmd == IAT_CALIBRATION_PAGE) { - void* pnt_TargetTable_values = (uint16_t *)&iatCalibration_values; - uint16_t* pnt_TargetTable_bins = (uint16_t *)&iatCalibration_bins; - - //Temperature calibrations are sent as 32 16-bit values (ie 64 bytes total) - if(calibrationLength == 64) - { - for (uint16_t x = 0; x < 32; x++) - { - int16_t tempValue = (int16_t)(word(serialPayload[((2 * x) + 8)], serialPayload[((2 * x) + 7)])); //Combine the 2 bytes into a single, signed 16-bit value - tempValue = div(tempValue, 10).quot; //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 - tempValue = tempValue + CALIBRATION_TEMPERATURE_OFFSET; - if (tempValue < 0) { tempValue = 0; } - - - ((uint16_t*)pnt_TargetTable_values)[x] = tempValue; //Both temp tables have 16-bit values - pnt_TargetTable_bins[x] = (x * 33U); // 0*33=0 to 31*33=1023 - } - //Update the CRC - calibrationCRC = CRC32.crc32(&serialPayload[7], 64); - storeCalibrationCRC32(IAT_CALIBRATION_PAGE, calibrationCRC); - - writeCalibration(); - sendSerialReturnCode(SERIAL_RC_OK); - } - else { sendSerialReturnCode(SERIAL_RC_RANGE_ERR); } - + processTemperatureCalibrationTableUpdate(calibrationLength, IAT_CALIBRATION_PAGE, iatCalibration_values, iatCalibration_bins); } else if(cmd == CLT_CALIBRATION_PAGE) { - void* pnt_TargetTable_values = (uint16_t *)&cltCalibration_values; - uint16_t* pnt_TargetTable_bins = (uint16_t *)&cltCalibration_bins; - - //Temperature calibrations are sent as 32 16-bit values - if(calibrationLength == 64) - { - for (uint16_t x = 0; x < 32; x++) - { - int16_t tempValue = (int16_t)(word(serialPayload[((2 * x) + 8)], serialPayload[((2 * x) + 7)])); //Combine the 2 bytes into a single, signed 16-bit value - tempValue = div(tempValue, 10).quot; //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 - tempValue = tempValue + CALIBRATION_TEMPERATURE_OFFSET; - if (tempValue < 0) { tempValue = 0; } - - - ((uint16_t*)pnt_TargetTable_values)[x] = tempValue; //Both temp tables have 16-bit values - pnt_TargetTable_bins[x] = (x * 33U); // 0*33=0 to 31*33=1023 - } - //Update the CRC - calibrationCRC = CRC32.crc32(&serialPayload[7], 64); - storeCalibrationCRC32(CLT_CALIBRATION_PAGE, calibrationCRC); - - writeCalibration(); - sendSerialReturnCode(SERIAL_RC_OK); - } - else { sendSerialReturnCode(SERIAL_RC_RANGE_ERR); } + processTemperatureCalibrationTableUpdate(calibrationLength, CLT_CALIBRATION_PAGE, cltCalibration_values, cltCalibration_bins); } else { - sendSerialReturnCode(SERIAL_RC_RANGE_ERR); + sendReturnCodeMsg(SERIAL_RC_RANGE_ERR); } break; } @@ -751,7 +848,7 @@ void processSerialCommand(void) if (resetControl != RESET_CONTROL_DISABLED) { #ifndef SMALL_FLASH_MODE - if (!cmdPending) { Serial.println(F("Comms halted. Next byte will reset the Arduino.")); } + if (serialStatusFlag == SERIAL_INACTIVE) { Serial.println(F("Comms halted. Next byte will reset the Arduino.")); } #endif while (Serial.available() == 0) { } @@ -760,7 +857,7 @@ void processSerialCommand(void) else { #ifndef SMALL_FLASH_MODE - if (!cmdPending) { Serial.println(F("Reset control is currently disabled.")); } + if (serialStatusFlag == SERIAL_INACTIVE) { Serial.println(F("Reset control is currently disabled.")); } #endif } break; @@ -790,14 +887,14 @@ void processSerialCommand(void) else if(command == 4) { setTS_SD_status(); } //else if(command == 5) { initSD(); } - sendSerialReturnCode(SERIAL_RC_OK); + 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]); - sendSerialReturnCode(SERIAL_RC_OK); + sendReturnCodeMsg(SERIAL_RC_OK); } else if((SD_arg1 == SD_WRITE_SEC_ARG1) && (SD_arg2 == SD_WRITE_SEC_ARG2)) { @@ -813,7 +910,7 @@ void processSerialCommand(void) char log4 = serialPayload[10]; deleteLogFile(log1, log2, log3, log4); - sendSerialReturnCode(SERIAL_RC_OK); + sendReturnCodeMsg(SERIAL_RC_OK); } else if((SD_arg1 == SD_SPD_TEST_ARG1) && (SD_arg2 == SD_SPD_TEST_ARG2)) { @@ -835,7 +932,7 @@ void processSerialCommand(void) uint8_t testSize4 = serialPayload[14]; testSize = (testSize1 << 24) | (testSize2 << 16) | (testSize3 << 8) | testSize4; - sendSerialReturnCode(SERIAL_RC_OK); + sendReturnCodeMsg(SERIAL_RC_OK); } else if((SD_arg1 == SD_WRITE_COMP_ARG1) && (SD_arg2 == SD_WRITE_COMP_ARG2)) @@ -859,12 +956,11 @@ void processSerialCommand(void) //Reset the sector counter SDreadCompletedSectors = 0; - sendSerialReturnCode(SERIAL_RC_OK); + sendReturnCodeMsg(SERIAL_RC_OK); } } else if(cmd == SD_RTC_PAGE) { - cmdPending = false; //Used for setting RTC settings if((SD_arg1 == SD_RTC_WRITE_ARG1) && (SD_arg2 == SD_RTC_WRITE_ARG2)) { @@ -877,7 +973,7 @@ void processSerialCommand(void) byte month = serialPayload[12]; uint16_t year = word(serialPayload[13], serialPayload[14]); rtc_setTime(second, minute, hour, day, month, year); - sendSerialReturnCode(SERIAL_RC_OK); + sendReturnCodeMsg(SERIAL_RC_OK); } } #endif @@ -886,213 +982,117 @@ void processSerialCommand(void) default: //Unknown command - sendSerialReturnCode(SERIAL_RC_UKWN_ERR); + sendReturnCodeMsg(SERIAL_RC_UKWN_ERR); break; } } -/** 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. - */ -//void sendValues(int packetlength, byte portNum) -void generateLiveValues(uint16_t offset, uint16_t packetLength) -{ - if(requestCount == 0) { currentStatus.secl = 0; } - requestCount++; - - 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; xto_byte(*it)); - ++it; - } - } - -} - /** * */ -void sendToothLog(uint8_t startOffset) +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)) //Sanity check. Flagging system means this should always be true { uint32_t CRC32_val = 0; - if(startOffset == 0) + if(logItemsTransmitted == 0) { //Transmit the size of the packet - uint16_t totalPayloadLength = (TOOTH_LOG_SIZE * 4) + 1; //Size of the tooth log (uint32_t values) plus the return code - Serial.write(totalPayloadLength >> 8); - Serial.write(totalPayloadLength); - + (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 - Serial.write(returnCode); + writeByteReliableBlocking(returnCode); } - for (int x = startOffset; x < TOOTH_LOG_SIZE; x++) + 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 - inProgressOffset = x; - toothLogSendInProgress = true; - legacySerial = false; + serialStatusFlag = SERIAL_TRANSMIT_TOOTH_INPROGRESS; return; } //Transmit the tooth time - uint32_t tempToothHistory = toothHistory[x]; - uint8_t toothHistory_1 = ((tempToothHistory >> 24) & 255); - uint8_t toothHistory_2 = ((tempToothHistory >> 16) & 255); - uint8_t toothHistory_3 = ((tempToothHistory >> 8) & 255); - uint8_t toothHistory_4 = ((tempToothHistory) & 255); - Serial.write(toothHistory_1); - Serial.write(toothHistory_2); - Serial.write(toothHistory_3); - Serial.write(toothHistory_4); - - //Update the CRC - CRC32_val = CRC32_serial.crc32_upd(&toothHistory_1, 1, false); - CRC32_val = CRC32_serial.crc32_upd(&toothHistory_2, 1, false); - CRC32_val = CRC32_serial.crc32_upd(&toothHistory_3, 1, false); - CRC32_val = CRC32_serial.crc32_upd(&toothHistory_4, 1, false); + 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); - cmdPending = false; - toothLogSendInProgress = false; + serialStatusFlag = SERIAL_INACTIVE; toothHistoryIndex = 0; + logItemsTransmitted = 0; //Apply the CRC reflection CRC32_val = ~CRC32_val; //Send the CRC - Serial.write( ((CRC32_val >> 24) & 255) ); - Serial.write( ((CRC32_val >> 16) & 255) ); - Serial.write( ((CRC32_val >> 8) & 255) ); - Serial.write( (CRC32_val & 255) ); + (void)serialWrite(CRC32_val); } else { - sendSerialReturnCode(SERIAL_RC_BUSY_ERR); - cmdPending = false; - toothLogSendInProgress = false; + sendReturnCodeMsg(SERIAL_RC_BUSY_ERR); + serialStatusFlag = SERIAL_INACTIVE; } } -void sendCompositeLog(uint8_t startOffset) +void sendCompositeLog(void) { - if ( (BIT_CHECK(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY)) || (compositeLogSendInProgress == true) ) //Sanity check. Flagging system means this should always be true + if ( (BIT_CHECK(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY)) || (serialStatusFlag == SERIAL_TRANSMIT_COMPOSITE_INPROGRESS) ) //Sanity check. Flagging system means this should always be true { - BIT_CLEAR(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY); uint32_t CRC32_val = 0; - if(startOffset == 0) + if(logItemsTransmitted == 0) { - inProgressCompositeTime = 0; - //Transmit the size of the packet - uint16_t totalPayloadLength = (TOOTH_LOG_SIZE * 5) + 1; //Size of the tooth log (1x uint32_t + 1x uint8_t values) plus the return code - Serial.write(totalPayloadLength >> 8); - Serial.write(totalPayloadLength); - + (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 - Serial.write(returnCode); + writeByteReliableBlocking(returnCode); } - for (int x = startOffset; x < TOOTH_LOG_SIZE; x++) + + for (; logItemsTransmitted < TOOTH_LOG_SIZE; logItemsTransmitted++) { //Check whether the tx buffer still has space - if(Serial.availableForWrite() < 5) + 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 - inProgressOffset = x; - compositeLogSendInProgress = true; - legacySerial = false; + serialStatusFlag = SERIAL_TRANSMIT_COMPOSITE_INPROGRESS; return; } - inProgressCompositeTime = toothHistory[x]; //This combined runtime (in us) that the log was going for by this record - uint8_t inProgressCompositeTime_1 = (inProgressCompositeTime >> 24) & 255; - uint8_t inProgressCompositeTime_2 = (inProgressCompositeTime >> 16) & 255; - uint8_t inProgressCompositeTime_3 = (inProgressCompositeTime >> 8) & 255; - uint8_t inProgressCompositeTime_4 = (inProgressCompositeTime) & 255; - - //Transmit the tooth time - Serial.write(inProgressCompositeTime_1); - Serial.write(inProgressCompositeTime_2); - Serial.write(inProgressCompositeTime_3); - Serial.write(inProgressCompositeTime_4); - - //Update the CRC - CRC32_val = CRC32_serial.crc32_upd(&inProgressCompositeTime_1, 1, false); - CRC32_val = CRC32_serial.crc32_upd(&inProgressCompositeTime_2, 1, false); - CRC32_val = CRC32_serial.crc32_upd(&inProgressCompositeTime_3, 1, false); - CRC32_val = CRC32_serial.crc32_upd(&inProgressCompositeTime_4, 1, false); + 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) - uint8_t statusByte = compositeLogHistory[x]; - Serial.write(statusByte); - - //Update the CRC with the status byte - CRC32_val = CRC32_serial.crc32_upd(&statusByte, 1, false); + 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; - cmdPending = false; - compositeLogSendInProgress = false; - inProgressCompositeTime = 0; + serialStatusFlag = SERIAL_INACTIVE; + logItemsTransmitted = 0; //Apply the CRC reflection CRC32_val = ~CRC32_val; //Send the CRC - Serial.write( ((CRC32_val >> 24) & 255) ); - Serial.write( ((CRC32_val >> 16) & 255) ); - Serial.write( ((CRC32_val >> 8) & 255) ); - Serial.write( (CRC32_val & 255) ); + (void)serialWrite(CRC32_val); } else { - sendSerialReturnCode(SERIAL_RC_BUSY_ERR); - cmdPending = false; - compositeLogSendInProgress = false; + sendReturnCodeMsg(SERIAL_RC_BUSY_ERR); + serialStatusFlag = SERIAL_INACTIVE; } } + +#if defined(CORE_AVR) +#pragma GCC pop_options +#endif \ No newline at end of file diff --git a/speeduino/comms.h b/speeduino/comms.h index 1cd6878d..88c03dd7 100644 --- a/speeduino/comms.h +++ b/speeduino/comms.h @@ -10,78 +10,14 @@ #ifndef NEW_COMMS_H #define NEW_COMMS_H -//Hardcoded TunerStudio addresses/commands for various SD/RTC commands -#define SD_READWRITE_PAGE 0x11 -#define SD_READFILE_PAGE 0x14 -#define SD_RTC_PAGE 0x07 +/** + * @brief The serial receive pump. Should be called whenever the serial port + * has data available to read. + */ +void serialReceive(void); -#define SD_READ_STAT_ARG1 0x0000 -#define SD_READ_STAT_ARG2 0x0010 -#define SD_READ_DIR_ARG1 0x0000 -#define SD_READ_DIR_ARG2 0x0202 -#define SD_READ_SEC_ARG1 0x0002 -#define SD_READ_SEC_ARG2 0x0004 -#define SD_READ_STRM_ARG1 0x0004 -#define SD_READ_STRM_ARG2 0x0001 -#define SD_READ_COMP_ARG1 0x0000 //Not used for anything -#define SD_READ_COMP_ARG2 0x0800 -#define SD_RTC_READ_ARG1 0x024D -#define SD_RTC_READ_ARG2 0x0008 - -#define SD_WRITE_DO_ARG1 0x0000 -#define SD_WRITE_DO_ARG2 0x0001 -#define SD_WRITE_DIR_ARG1 0x0001 -#define SD_WRITE_DIR_ARG2 0x0002 -#define SD_WRITE_SEC_ARG1 0x0003 -#define SD_WRITE_SEC_ARG2 0x0204 -#define SD_WRITE_COMP_ARG1 0x0005 -#define SD_WRITE_COMP_ARG2 0x0008 -#define SD_ERASEFILE_ARG1 0x0006 -#define SD_ERASEFILE_ARG2 0x0006 -#define SD_SPD_TEST_ARG1 0x0007 -#define SD_SPD_TEST_ARG2 0x0004 -#define SD_RTC_WRITE_ARG1 0x027E -#define SD_RTC_WRITE_ARG2 0x0009 - -#define SERIAL_CRC_LENGTH 4 -#define SERIAL_LEN_SIZE 2 -#define SERIAL_OVERHEAD_SIZE (SERIAL_LEN_SIZE + SERIAL_CRC_LENGTH) //The overhead for each serial command is 6 bytes. 2 bytes for the length and 4 bytes for the CRC -#define SERIAL_TIMEOUT 3000 //ms - -#ifdef RTC_ENABLED - #define SD_FILE_TRANSMIT_BUFFER_SIZE (2048 + 3) - extern uint16_t SDcurrentDirChunk; - extern uint32_t SDreadStartSector; - extern uint32_t SDreadNumSectors; //Number of sectors to read - extern uint32_t SDreadCompletedSectors; //Number of sectors that have been read -#endif - -//Serial return codes -#define SERIAL_RC_OK 0x00 -#define SERIAL_RC_REALTIME 0x01 -#define SERIAL_RC_PAGE 0x02 - -#define SERIAL_RC_BURN_OK 0x04 - -#define SERIAL_RC_TIMEOUT 0x80 //Timeout error -#define SERIAL_RC_CRC_ERR 0x82 -#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 - -extern bool serialWriteInProgress; -extern bool serialReceivePending; /**< Whether or not a serial request has only been partially received. This occurs when a the length has been received in the serial buffer, but not all of the payload or CRC has yet been received. */ - - -void parseSerial(void);//This is the heart of the Command Line Interpreter. All that needed to be done was to make it human readable. -void processSerialCommand(void); -void sendSerialReturnCode(byte returnCode); -void sendSerialPayload(void* payload, uint16_t payloadLength); - -void generateLiveValues(uint16_t offset, uint16_t packetLength); -void flushRXbuffer(void); -void sendToothLog(uint8_t startOffset); -void sendCompositeLog(uint8_t startOffset); -void continueSerialTransmission(void); +/** @brief The serial transmit pump. Should be called when ::serialStatusFlag indicates a transmit + * operation is in progress */ +void serialTransmit(void); #endif // COMMS_H diff --git a/speeduino/comms_legacy.cpp b/speeduino/comms_legacy.cpp index b17a97f2..90a33925 100644 --- a/speeduino/comms_legacy.cpp +++ b/speeduino/comms_legacy.cpp @@ -23,23 +23,22 @@ A full copy of the license may be found in the projects root directory #include "rtc_common.h" #endif -byte currentPage = 1;//Not the same as the speeduino config page numbers -bool isMap = true; /**< Whether or not the currentPage contains only a 3D map that would require translation */ -unsigned long requestCount = 0; /**< The number of times the A command has been issued. This is used to track whether a reset has recently been performed on the controller */ -byte currentCommand; /**< The serial command that is currently being processed. This is only useful when cmdPending=True */ -bool cmdPending = false; /**< Whether or not a serial request has only been partially received. This occurs when a command character has been received in the serial buffer, but not all of its arguments have yet been received. If true, the active command will be stored in the currentCommand variable */ -bool chunkPending = false; /**< Whether or not the current chunk write is complete or not */ -uint16_t chunkComplete = 0; /**< The number of bytes in a chunk write that have been written so far */ -uint16_t chunkSize = 0; /**< The complete size of the requested chunk write */ -int valueOffset; /**< The memory offset within a given page for a value to be read from or written to. Note that we cannot use 'offset' as a variable name, it is a reserved word for several teensy libraries */ -byte tsCanId = 0; // current tscanid requested -byte inProgressOffset; +static byte currentPage = 1;//Not the same as the speeduino config page numbers +bool firstCommsRequest = true; /**< The number of times the A command has been issued. This is used to track whether a reset has recently been performed on the controller */ +static byte currentCommand; /**< The serial command that is currently being processed. This is only useful when cmdPending=True */ +static bool chunkPending = false; /**< Whether or not the current chunk write is complete or not */ +static uint16_t chunkComplete = 0; /**< The number of bytes in a chunk write that have been written so far */ +static uint16_t chunkSize = 0; /**< The complete size of the requested chunk write */ +static int valueOffset; /**< The memory offset within a given page for a value to be read from or written to. Note that we cannot use 'offset' as a variable name, it is a reserved word for several teensy libraries */ +byte logItemsTransmitted; byte inProgressLength; -uint32_t inProgressCompositeTime; -bool serialInProgress = false; -bool toothLogSendInProgress = false; -bool compositeLogSendInProgress = false; -bool legacySerial = false; +SerialStatus serialStatusFlag; + + +static bool isMap(void) { + // Detecting if the current page is a table/map + return (currentPage == veMapPage) || (currentPage == ignMapPage) || (currentPage == afrMapPage) || (currentPage == fuelMap2Page) || (currentPage == ignMap2Page); +} /** 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: @@ -50,20 +49,19 @@ Commands are single byte (letter symbol) commands. */ void legacySerialCommand(void) { - if ( (cmdPending == false) && (legacySerial == false) ) { currentCommand = Serial.read(); } + if ( serialStatusFlag == SERIAL_INACTIVE ) { currentCommand = Serial.read(); } switch (currentCommand) { case 'a': - cmdPending = true; - + serialStatusFlag = SERIAL_COMMAND_INPROGRESS_LEGACY; if (Serial.available() >= 2) { Serial.read(); //Ignore the first value, it's always 0 Serial.read(); //Ignore the second value, it's always 6 sendValuesLegacy(); - cmdPending = false; + serialStatusFlag = SERIAL_INACTIVE; } break; @@ -71,19 +69,20 @@ void legacySerialCommand(void) sendValues(0, LOG_ENTRY_SIZE, 0x31, 0); //send values to serial0 break; - case 'B': // Burn current values to eeprom + serialStatusFlag = SERIAL_COMMAND_INPROGRESS_LEGACY; writeAllConfig(); + serialStatusFlag = SERIAL_INACTIVE; break; case 'b': // New EEPROM burn command to only burn a single page at a time - cmdPending = true; + serialStatusFlag = SERIAL_COMMAND_INPROGRESS_LEGACY; if (Serial.available() >= 2) { Serial.read(); //Ignore the first table value, it's always 0 writeConfig(Serial.read()); - cmdPending = false; + serialStatusFlag = SERIAL_INACTIVE; } break; @@ -97,7 +96,7 @@ void legacySerialCommand(void) break; case 'd': // Send a CRC32 hash of a given page - cmdPending = true; + serialStatusFlag = SERIAL_COMMAND_INPROGRESS_LEGACY; if (Serial.available() >= 2) { @@ -110,37 +109,18 @@ void legacySerialCommand(void) Serial.write( ((CRC32_val >> 8) & 255) ); Serial.write( (CRC32_val & 255) ); - cmdPending = false; + serialStatusFlag = SERIAL_INACTIVE; } break; case 'E': // receive command button commands - cmdPending = true; + serialStatusFlag = SERIAL_COMMAND_INPROGRESS_LEGACY; if(Serial.available() >= 2) { - byte cmdGroup = Serial.read(); - byte cmdValue = Serial.read(); - uint16_t cmdCombined = word(cmdGroup, cmdValue); - - if ( ((cmdCombined >= TS_CMD_INJ1_ON) && (cmdCombined <= TS_CMD_IGN8_50PC)) || (cmdCombined == TS_CMD_TEST_ENBL) || (cmdCombined == TS_CMD_TEST_DSBL) ) - { - //Hardware test buttons - if (currentStatus.RPM == 0) { TS_CommandButtonsHandler(cmdCombined); } - cmdPending = false; - } - else if( (cmdCombined >= TS_CMD_VSS_60KMH) && (cmdCombined <= TS_CMD_VSS_RATIO6) ) - { - //VSS Calibration commands - TS_CommandButtonsHandler(cmdCombined); - cmdPending = false; - } - else if( (cmdCombined >= TS_CMD_STM32_REBOOT) && (cmdCombined <= TS_CMD_STM32_BOOTLOADER) ) - { - //STM32 DFU mode button - TS_CommandButtonsHandler(cmdCombined); - cmdPending = false; - } + byte cmdGroup = (byte)Serial.read(); + (void)TS_CommandButtonsHandler(word(cmdGroup, Serial.read())); + serialStatusFlag = SERIAL_INACTIVE; } break; @@ -160,7 +140,7 @@ void legacySerialCommand(void) { Serial.write(EEPROMReadRaw(x)); } - cmdPending = false; + serialStatusFlag = SERIAL_INACTIVE; break; case 'g': // Receive a dump of raw EEPROM values from the user @@ -182,62 +162,26 @@ void legacySerialCommand(void) EEPROMWriteRaw(x, Serial.read()); } } - cmdPending = false; + serialStatusFlag = SERIAL_INACTIVE; break; } case 'H': //Start the tooth logger - currentStatus.toothLogEnabled = true; - currentStatus.compositeLogEnabled = false; //Safety first (Should never be required) - BIT_CLEAR(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY); - toothHistoryIndex = 0; - - //Disconnect the standard interrupt and add the logger version - detachInterrupt( digitalPinToInterrupt(pinTrigger) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger), loggerPrimaryISR, CHANGE ); - - detachInterrupt( digitalPinToInterrupt(pinTrigger2) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger2), loggerSecondaryISR, CHANGE ); - + startToothLogger(); Serial.write(1); //TS needs an acknowledgement that this was received. I don't know if this is the correct response, but it seems to work break; case 'h': //Stop the tooth logger - currentStatus.toothLogEnabled = false; - - //Disconnect the logger interrupts and attach the normal ones - detachInterrupt( digitalPinToInterrupt(pinTrigger) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger), triggerHandler, primaryTriggerEdge ); - - detachInterrupt( digitalPinToInterrupt(pinTrigger2) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger2), triggerSecondaryHandler, secondaryTriggerEdge ); + stopToothLogger(); break; case 'J': //Start the composite logger - currentStatus.compositeLogEnabled = true; - currentStatus.toothLogEnabled = false; //Safety first (Should never be required) - BIT_CLEAR(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY); - toothHistoryIndex = 0; - - //Disconnect the standard interrupt and add the logger version - detachInterrupt( digitalPinToInterrupt(pinTrigger) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger), loggerPrimaryISR, CHANGE ); - - detachInterrupt( digitalPinToInterrupt(pinTrigger2) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger2), loggerSecondaryISR, CHANGE ); - + startCompositeLogger(); Serial.write(1); //TS needs an acknowledgement that this was received. I don't know if this is the correct response, but it seems to work break; case 'j': //Stop the composite logger - currentStatus.compositeLogEnabled = false; - - //Disconnect the logger interrupts and attach the normal ones - detachInterrupt( digitalPinToInterrupt(pinTrigger) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger), triggerHandler, primaryTriggerEdge ); - - detachInterrupt( digitalPinToInterrupt(pinTrigger2) ); - attachInterrupt( digitalPinToInterrupt(pinTrigger2), triggerSecondaryHandler, secondaryTriggerEdge ); + stopCompositeLogger(); break; case 'L': // List the contents of current page in human readable form @@ -259,7 +203,7 @@ void legacySerialCommand(void) case 'P': // set the current page //This is a legacy function and is no longer used by TunerStudio. It is maintained for compatibility with other systems //A 2nd byte of data is required after the 'P' specifying the new page number. - cmdPending = true; + serialStatusFlag = SERIAL_COMMAND_INPROGRESS_LEGACY; if (Serial.available() > 0) { @@ -277,11 +221,7 @@ void legacySerialCommand(void) { currentPage -= 55; } - - // Detecting if the current page is a table/map - if ( (currentPage == veMapPage) || (currentPage == ignMapPage) || (currentPage == afrMapPage) || (currentPage == fuelMap2Page) || (currentPage == ignMap2Page) ) { isMap = true; } - else { isMap = false; } - cmdPending = false; + serialStatusFlag = SERIAL_INACTIVE; } break; @@ -289,7 +229,7 @@ void legacySerialCommand(void) * New method for sending page values */ case 'p': - cmdPending = true; + serialStatusFlag = SERIAL_COMMAND_INPROGRESS_LEGACY; //6 bytes required: //2 - Page identifier @@ -315,7 +255,7 @@ void legacySerialCommand(void) Serial.write( getPageValue(tempPage, valueOffset + i) ); } - cmdPending = false; + serialStatusFlag = SERIAL_INACTIVE; } break; @@ -325,11 +265,11 @@ void legacySerialCommand(void) break; case 'r': //New format for the optimised OutputChannels - cmdPending = true; + serialStatusFlag = SERIAL_COMMAND_INPROGRESS_LEGACY; byte cmd; if (Serial.available() >= 6) { - tsCanId = Serial.read(); //Read the $tsCanId + Serial.read(); //Read the $tsCanId cmd = Serial.read(); // read the command uint16_t offset, length; @@ -339,6 +279,7 @@ void legacySerialCommand(void) tmp = Serial.read(); length = word(Serial.read(), tmp); + serialStatusFlag = SERIAL_INACTIVE; if(cmd == 0x30) //Send output channels command 0x30 is 48dec { @@ -348,7 +289,6 @@ void legacySerialCommand(void) { //No other r/ commands are supported in legacy mode } - cmdPending = false; } break; @@ -363,7 +303,7 @@ void legacySerialCommand(void) //2 - Page identifier //2 - offset //2 - Length - cmdPending = true; + serialStatusFlag = SERIAL_COMMAND_INPROGRESS_LEGACY; if(Serial.available() >= 6) { Serial.read(); // First byte of the page identifier can be ignored. It's always 0 @@ -375,12 +315,8 @@ void legacySerialCommand(void) if(currentStatus.toothLogEnabled == true) { sendToothLog_legacy(0); } //Sends tooth log values as ints else if (currentStatus.compositeLogEnabled == true) { sendCompositeLog_legacy(0); } - - cmdPending = false; + serialStatusFlag = SERIAL_INACTIVE; } - - - break; case 't': // receive new Calibration info. Command structure: "t", . @@ -400,7 +336,7 @@ void legacySerialCommand(void) if (resetControl != RESET_CONTROL_DISABLED) { #ifndef SMALL_FLASH_MODE - if (!cmdPending) { Serial.println(F("Comms halted. Next byte will reset the Arduino.")); } + if (serialStatusFlag == SERIAL_INACTIVE) { Serial.println(F("Comms halted. Next byte will reset the Arduino.")); } #endif while (Serial.available() == 0) { } @@ -409,7 +345,7 @@ void legacySerialCommand(void) else { #ifndef SMALL_FLASH_MODE - if (!cmdPending) { Serial.println(F("Reset control is currently disabled.")); } + if (serialStatusFlag == SERIAL_INACTIVE) { Serial.println(F("Reset control is currently disabled.")); } #endif } break; @@ -419,9 +355,9 @@ void legacySerialCommand(void) break; case 'W': // receive new VE obr constant at 'W'++ - cmdPending = true; + serialStatusFlag = SERIAL_COMMAND_INPROGRESS_LEGACY; - if (isMap) + if (isMap()) { if(Serial.available() >= 3) // 1 additional byte is required on the MAP pages which are larger than 255 bytes { @@ -430,7 +366,7 @@ void legacySerialCommand(void) offset2 = Serial.read(); valueOffset = word(offset2, offset1); setPageValue(currentPage, valueOffset, Serial.read()); - cmdPending = false; + serialStatusFlag = SERIAL_INACTIVE; } } else @@ -439,14 +375,14 @@ void legacySerialCommand(void) { valueOffset = Serial.read(); setPageValue(currentPage, valueOffset, Serial.read()); - cmdPending = false; + serialStatusFlag = SERIAL_INACTIVE; } } break; case 'M': - cmdPending = true; + serialStatusFlag = SERIAL_COMMAND_INPROGRESS_LEGACY; if(chunkPending == false) { @@ -483,7 +419,7 @@ void legacySerialCommand(void) setPageValue(currentPage, (valueOffset + chunkComplete), Serial.read()); chunkComplete++; } - if(chunkComplete >= chunkSize) { cmdPending = false; chunkPending = false; } + if(chunkComplete >= chunkSize) { serialStatusFlag = SERIAL_INACTIVE; chunkPending = false; } } break; @@ -544,11 +480,11 @@ void legacySerialCommand(void) break; case '`': //Custom 16u2 firmware is making its presence known - cmdPending = true; + serialStatusFlag = SERIAL_COMMAND_INPROGRESS_LEGACY; if (Serial.available() >= 1) { configPage4.bootloaderCaps = Serial.read(); - cmdPending = false; + serialStatusFlag = SERIAL_INACTIVE; } break; @@ -589,7 +525,7 @@ void legacySerialCommand(void) default: Serial.println(F("Err: Unknown cmd")); - cmdPending = false; + serialStatusFlag = SERIAL_INACTIVE; break; } } @@ -603,9 +539,10 @@ void legacySerialCommand(void) * E.g. tuning sw command 'A' (Send all values) will send data from field number 0, LOG_ENTRY_SIZE fields. * @return the current values of a fixed group of variables */ -//void sendValues(int packetlength, byte portNum) void sendValues(uint16_t offset, uint16_t packetLength, byte cmd, byte portNum) { + serialStatusFlag = SERIAL_TRANSMIT_INPROGRESS_LEGACY; + if (portNum == 3) { //CAN serial @@ -622,8 +559,11 @@ void sendValues(uint16_t offset, uint16_t packetLength, byte cmd, byte portNum) } else { - if(requestCount == 0) { currentStatus.secl = 0; } - requestCount++; + 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 @@ -639,17 +579,15 @@ void sendValues(uint16_t offset, uint16_t packetLength, byte cmd, byte portNum) if(Serial.availableForWrite() < 1) { //tx buffer is full. Store the current state so it can be resumed later - inProgressOffset = offset + x + 1; + logItemsTransmitted = offset + x + 1; inProgressLength = packetLength - x - 1; - serialInProgress = true; return; } } - serialInProgress = false; + serialStatusFlag = SERIAL_INACTIVE; // Reset any flags that are being used to trigger page refreshes BIT_CLEAR(currentStatus.status3, BIT_STATUS3_VSS_REFRESH); - } void sendValuesLegacy(void) @@ -1155,33 +1093,21 @@ void receiveCalibration(byte tableID) * if useChar is true, the values are sent as chars to be printed out by a terminal emulator * if useChar is false, the values are sent as a 2 byte integer which is readable by TunerStudios tooth logger */ -void sendToothLog_legacy(byte startOffset) +void sendToothLog_legacy(byte startOffset) /* Blocking */ { //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)) //Sanity check. Flagging system means this should always be true { + serialStatusFlag = SERIAL_TRANSMIT_TOOTH_INPROGRESS_LEGACY; for (int x = startOffset; x < TOOTH_LOG_SIZE; x++) { - //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 - inProgressOffset = x; - toothLogSendInProgress = true; - return; - } - */ - - Serial.write(toothHistory[x] >> 24); Serial.write(toothHistory[x] >> 16); Serial.write(toothHistory[x] >> 8); Serial.write(toothHistory[x]); } BIT_CLEAR(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY); - cmdPending = false; - toothLogSendInProgress = false; + serialStatusFlag = SERIAL_INACTIVE; toothHistoryIndex = 0; } else @@ -1191,27 +1117,27 @@ void sendToothLog_legacy(byte startOffset) { Serial.write(static_cast(0x00)); //GCC9 fix } - cmdPending = false; + serialStatusFlag = SERIAL_INACTIVE; } } -void sendCompositeLog_legacy(byte startOffset) +void sendCompositeLog_legacy(byte startOffset) /* Non-blocking */ { if (BIT_CHECK(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY)) //Sanity check. Flagging system means this should always be true { - if(startOffset == 0) { inProgressCompositeTime = 0; } + serialStatusFlag = SERIAL_TRANSMIT_COMPOSITE_INPROGRESS_LEGACY; + for (int x = startOffset; x < TOOTH_LOG_SIZE; x++) { //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 - inProgressOffset = x; - compositeLogSendInProgress = true; + logItemsTransmitted = x; return; } - inProgressCompositeTime = toothHistory[x]; //This combined runtime (in us) that the log was going for by this record) + uint32_t inProgressCompositeTime = toothHistory[x]; //This combined runtime (in us) that the log was going for by this record) Serial.write(inProgressCompositeTime >> 24); Serial.write(inProgressCompositeTime >> 16); @@ -1222,9 +1148,7 @@ void sendCompositeLog_legacy(byte startOffset) } BIT_CLEAR(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY); toothHistoryIndex = 0; - cmdPending = false; - compositeLogSendInProgress = false; - inProgressCompositeTime = 0; + serialStatusFlag = SERIAL_INACTIVE; } else { @@ -1233,11 +1157,11 @@ void sendCompositeLog_legacy(byte startOffset) { Serial.write(static_cast(0x00)); //GCC9 fix } - cmdPending = false; + serialStatusFlag = SERIAL_INACTIVE; } } -void testComm() +void testComm(void) { Serial.write(1); return; diff --git a/speeduino/comms_legacy.h b/speeduino/comms_legacy.h index 19c73383..5a64ba38 100644 --- a/speeduino/comms_legacy.h +++ b/speeduino/comms_legacy.h @@ -10,24 +10,67 @@ #ifndef COMMS_H #define COMMS_H +/** \enum SerialStatus + * @brief The current state of serial communication + * */ +enum SerialStatus { + /** No serial comms is in progress */ + SERIAL_INACTIVE, + /** A partial write is in progress. */ + SERIAL_TRANSMIT_INPROGRESS, + /** A partial write is in progress (legacy send). */ + SERIAL_TRANSMIT_INPROGRESS_LEGACY, + /** We are part way through transmitting the tooth log */ + SERIAL_TRANSMIT_TOOTH_INPROGRESS, + /** We are part way through transmitting the tooth log (legacy send) */ + SERIAL_TRANSMIT_TOOTH_INPROGRESS_LEGACY, + /** We are part way through transmitting the composite log */ + SERIAL_TRANSMIT_COMPOSITE_INPROGRESS, + /** We are part way through transmitting the composite log (legacy send) */ + SERIAL_TRANSMIT_COMPOSITE_INPROGRESS_LEGACY, + /** Whether or not a serial request has only been partially received. + * This occurs when a the length has been received in the serial buffer, + * but not all of the payload or CRC has yet been received. + * + * Expectation is that ::serialReceive is called until the status reverts + * to SERIAL_INACTIVE + */ + SERIAL_RECEIVE_INPROGRESS, + /** We are part way through processing a legacy serial commang: call ::serialReceive */ + SERIAL_COMMAND_INPROGRESS_LEGACY, +}; +/** @brief Current status of serial comms. */ +extern SerialStatus serialStatusFlag; -extern byte currentPage;//Not the same as the speeduino config page numbers -extern bool isMap; /**< Whether or not the currentPage contains only a 3D map that would require translation */ -extern unsigned long requestCount; /**< The number of times the A command has been issued. This is used to track whether a reset has recently been performed on the controller */ -extern byte currentCommand; /**< The serial command that is currently being processed. This is only useful when cmdPending=True */ -extern bool cmdPending; /**< Whether or not a serial request has only been partially received. This occurs when a command character has been received in the serial buffer, but not all of its arguments have yet been received. If true, the active command will be stored in the currentCommand variable */ -extern bool chunkPending; /**< Whether or not the current chunk write is complete or not */ -extern uint16_t chunkComplete; /**< The number of bytes in a chunk write that have been written so far */ -extern uint16_t chunkSize; /**< The complete size of the requested chunk write */ -extern int valueOffset; /**< THe memory offset within a given page for a value to be read from or written to. Note that we cannot use 'offset' as a variable name, it is a reserved word for several teensy libraries */ -extern byte tsCanId; // current tscanid requested -extern byte inProgressOffset; +/** + * @brief Is a serial write in progress? + * + * Expectation is that ::serialTransmit is called until this + * returns false + */ +inline bool serialTransmitInProgress(void) { + return serialStatusFlag==SERIAL_TRANSMIT_INPROGRESS + || serialStatusFlag==SERIAL_TRANSMIT_INPROGRESS_LEGACY + || serialStatusFlag==SERIAL_TRANSMIT_TOOTH_INPROGRESS + || serialStatusFlag==SERIAL_TRANSMIT_TOOTH_INPROGRESS_LEGACY + || serialStatusFlag==SERIAL_TRANSMIT_COMPOSITE_INPROGRESS + || serialStatusFlag==SERIAL_TRANSMIT_COMPOSITE_INPROGRESS_LEGACY; +} + +/** + * @brief Is a non-blocking serial receive operation in progress? + * + * Expectation is the ::serialReceive is called until this + * returns false. + */ +inline bool serialRecieveInProgress(void) { + return serialStatusFlag==SERIAL_RECEIVE_INPROGRESS + || serialStatusFlag==SERIAL_COMMAND_INPROGRESS_LEGACY; +} + +extern bool firstCommsRequest; /**< The number of times the A command has been issued. This is used to track whether a reset has recently been performed on the controller */ +extern byte logItemsTransmitted; extern byte inProgressLength; -extern bool legacySerial; -extern uint32_t inProgressCompositeTime; -extern bool serialInProgress; -extern bool toothLogSendInProgress; -extern bool compositeLogSendInProgress; void legacySerialCommand(void);//This is the heart of the Command Line Interpreter. All that needed to be done was to make it human readable. void sendValues(uint16_t offset, uint16_t packetLength, byte cmd, byte portNum); diff --git a/speeduino/comms_sd.h b/speeduino/comms_sd.h new file mode 100644 index 00000000..5111dd7f --- /dev/null +++ b/speeduino/comms_sd.h @@ -0,0 +1,35 @@ +#pragma once + + +//Hardcoded TunerStudio addresses/commands for various SD/RTC commands +#define SD_READWRITE_PAGE 0x11 +#define SD_READFILE_PAGE 0x14 +#define SD_RTC_PAGE 0x07 + +#define SD_READ_STAT_ARG1 0x0000 +#define SD_READ_STAT_ARG2 0x0010 +#define SD_READ_DIR_ARG1 0x0000 +#define SD_READ_DIR_ARG2 0x0202 +#define SD_READ_SEC_ARG1 0x0002 +#define SD_READ_SEC_ARG2 0x0004 +#define SD_READ_STRM_ARG1 0x0004 +#define SD_READ_STRM_ARG2 0x0001 +#define SD_READ_COMP_ARG1 0x0000 //Not used for anything +#define SD_READ_COMP_ARG2 0x0800 +#define SD_RTC_READ_ARG1 0x024D +#define SD_RTC_READ_ARG2 0x0008 + +#define SD_WRITE_DO_ARG1 0x0000 +#define SD_WRITE_DO_ARG2 0x0001 +#define SD_WRITE_DIR_ARG1 0x0001 +#define SD_WRITE_DIR_ARG2 0x0002 +#define SD_WRITE_SEC_ARG1 0x0003 +#define SD_WRITE_SEC_ARG2 0x0204 +#define SD_WRITE_COMP_ARG1 0x0005 +#define SD_WRITE_COMP_ARG2 0x0008 +#define SD_ERASEFILE_ARG1 0x0006 +#define SD_ERASEFILE_ARG2 0x0006 +#define SD_SPD_TEST_ARG1 0x0007 +#define SD_SPD_TEST_ARG2 0x0004 +#define SD_RTC_WRITE_ARG1 0x027E +#define SD_RTC_WRITE_ARG2 0x0009 \ No newline at end of file diff --git a/speeduino/globals.h b/speeduino/globals.h index 06516380..3edf726c 100644 --- a/speeduino/globals.h +++ b/speeduino/globals.h @@ -248,9 +248,9 @@ #define TOOTH_LOG_SIZE 1 #endif -#define O2_CALIBRATION_PAGE 2 -#define IAT_CALIBRATION_PAGE 1 -#define CLT_CALIBRATION_PAGE 0 +#define O2_CALIBRATION_PAGE 2U +#define IAT_CALIBRATION_PAGE 1U +#define CLT_CALIBRATION_PAGE 0U #define COMPOSITE_LOG_PRI 0 #define COMPOSITE_LOG_SEC 1 @@ -450,7 +450,6 @@ This is so we can use an unsigned byte (0-255) to represent temperature ranges f extern const char TSfirmwareVersion[] PROGMEM; extern const byte data_structure_version; //This identifies the data structure when reading / writing. Now in use: CURRENT_DATA_VERSION (migration on-the fly) ? -extern FastCRC32 CRC32; //Generic CRC32 instance for general use in pages etc. Note that the serial comms has its own CRC32 instance extern struct table3d16RpmLoad fuelTable; //16x16 fuel map extern struct table3d16RpmLoad fuelTable2; //16x16 fuel map diff --git a/speeduino/globals.ino b/speeduino/globals.ino index c648162b..d33fdc0e 100644 --- a/speeduino/globals.ino +++ b/speeduino/globals.ino @@ -6,7 +6,6 @@ const char TSfirmwareVersion[] PROGMEM = "Speeduino"; const byte data_structure_version = 2; //This identifies the data structure when reading / writing. (outdated ?) -FastCRC32 CRC32; struct table3d16RpmLoad fuelTable; ///< 16x16 fuel map struct table3d16RpmLoad fuelTable2; ///< 16x16 fuel map diff --git a/speeduino/logger.h b/speeduino/logger.h index 3b189524..194c710d 100644 --- a/speeduino/logger.h +++ b/speeduino/logger.h @@ -28,6 +28,12 @@ int16_t getReadableLogEntry(uint16_t logIndex); #endif bool is2ByteEntry(uint8_t key); +void startToothLogger(void); +void stopToothLogger(void); + +void startCompositeLogger(void); +void stopCompositeLogger(void); + // This array indicates which index values from the log are 2 byte values // This array MUST remain in ascending order // !!!! WARNING: If any value above 255 is required in this array, changes MUST be made to is2ByteEntry() function !!!! diff --git a/speeduino/logger.ino b/speeduino/logger.ino index fc596e66..f1a46e28 100644 --- a/speeduino/logger.ino +++ b/speeduino/logger.ino @@ -360,4 +360,58 @@ bool is2ByteEntry(uint8_t key) } return isFound; -} \ No newline at end of file +} + +void startToothLogger(void) +{ + currentStatus.toothLogEnabled = true; + currentStatus.compositeLogEnabled = false; //Safety first (Should never be required) + BIT_CLEAR(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY); + toothHistoryIndex = 0; + + //Disconnect the standard interrupt and add the logger version + detachInterrupt( digitalPinToInterrupt(pinTrigger) ); + attachInterrupt( digitalPinToInterrupt(pinTrigger), loggerPrimaryISR, CHANGE ); + + detachInterrupt( digitalPinToInterrupt(pinTrigger2) ); + attachInterrupt( digitalPinToInterrupt(pinTrigger2), loggerSecondaryISR, CHANGE ); +} + +void stopToothLogger(void) +{ + currentStatus.toothLogEnabled = false; + + //Disconnect the logger interrupts and attach the normal ones + detachInterrupt( digitalPinToInterrupt(pinTrigger) ); + attachInterrupt( digitalPinToInterrupt(pinTrigger), triggerHandler, primaryTriggerEdge ); + + detachInterrupt( digitalPinToInterrupt(pinTrigger2) ); + attachInterrupt( digitalPinToInterrupt(pinTrigger2), triggerSecondaryHandler, secondaryTriggerEdge ); +} + +void startCompositeLogger(void) +{ + currentStatus.compositeLogEnabled = true; + currentStatus.toothLogEnabled = false; //Safety first (Should never be required) + BIT_CLEAR(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY); + toothHistoryIndex = 0; + + //Disconnect the standard interrupt and add the logger version + detachInterrupt( digitalPinToInterrupt(pinTrigger) ); + attachInterrupt( digitalPinToInterrupt(pinTrigger), loggerPrimaryISR, CHANGE ); + + detachInterrupt( digitalPinToInterrupt(pinTrigger2) ); + attachInterrupt( digitalPinToInterrupt(pinTrigger2), loggerSecondaryISR, CHANGE ); +} + +void stopCompositeLogger(void) +{ + currentStatus.compositeLogEnabled = false; + + //Disconnect the logger interrupts and attach the normal ones + detachInterrupt( digitalPinToInterrupt(pinTrigger) ); + attachInterrupt( digitalPinToInterrupt(pinTrigger), triggerHandler, primaryTriggerEdge ); + + detachInterrupt( digitalPinToInterrupt(pinTrigger2) ); + attachInterrupt( digitalPinToInterrupt(pinTrigger2), triggerSecondaryHandler, secondaryTriggerEdge ); +} diff --git a/speeduino/page_crc.cpp b/speeduino/page_crc.cpp index c6afa613..da464a75 100644 --- a/speeduino/page_crc.cpp +++ b/speeduino/page_crc.cpp @@ -3,32 +3,32 @@ #include "pages.h" #include "table3d_axis_io.h" -typedef uint32_t (FastCRC32::*pCrcCalc)(const uint8_t *, const uint16_t, bool); +using pCrcCalc = uint32_t (FastCRC32::*)(const uint8_t *, const uint16_t, bool); -static inline uint32_t compute_raw_crc(const page_iterator_t &entity, pCrcCalc calcFunc) +static inline uint32_t compute_raw_crc(const page_iterator_t &entity, pCrcCalc calcFunc, FastCRC32 &crcCalc) { - return (CRC32.*calcFunc)((uint8_t*)entity.pData, entity.size, false); + return (crcCalc.*calcFunc)((uint8_t*)entity.pData, entity.size, false); } -static inline uint32_t compute_row_crc(const table_row_iterator &row, pCrcCalc calcFunc) +static inline uint32_t compute_row_crc(const table_row_iterator &row, pCrcCalc calcFunc, FastCRC32 &crcCalc) { - return (CRC32.*calcFunc)(&*row, row.size(), false); + return (crcCalc.*calcFunc)(&*row, row.size(), false); } -static inline uint32_t compute_tablevalues_crc(table_value_iterator it, pCrcCalc calcFunc) +static inline uint32_t compute_tablevalues_crc(table_value_iterator it, pCrcCalc calcFunc, FastCRC32 &crcCalc) { - uint32_t crc = compute_row_crc(*it, calcFunc); + uint32_t crc = compute_row_crc(*it, calcFunc, crcCalc); ++it; while (!it.at_end()) { - crc = compute_row_crc(*it, &FastCRC32::crc32_upd); + crc = compute_row_crc(*it, &FastCRC32::crc32_upd, crcCalc); ++it; } return crc; } -static inline uint32_t compute_tableaxis_crc(table_axis_iterator it, uint32_t crc) +static inline uint32_t compute_tableaxis_crc(table_axis_iterator it, uint32_t crc, FastCRC32 &crcCalc) { const int16_byte *pConverter = table3d_axis_io::get_converter(it.get_domain()); @@ -39,41 +39,43 @@ static inline uint32_t compute_tableaxis_crc(table_axis_iterator it, uint32_t cr *pValue++ = pConverter->to_byte(*it); ++it; } - return pValue-values==0 ? crc : CRC32.crc32_upd(values, pValue-values, false); + return pValue-values==0 ? crc : crcCalc.crc32_upd(values, pValue-values, false); } -static inline uint32_t compute_table_crc(page_iterator_t &entity, pCrcCalc calcFunc) +static inline uint32_t compute_table_crc(const page_iterator_t &entity, pCrcCalc calcFunc, FastCRC32 &crcCalc) { return compute_tableaxis_crc(y_begin(entity), compute_tableaxis_crc(x_begin(entity), - compute_tablevalues_crc(rows_begin(entity), calcFunc))); + compute_tablevalues_crc(rows_begin(entity), calcFunc, crcCalc), + crcCalc), + crcCalc); } -static inline uint32_t pad_crc(uint16_t padding, uint32_t crc) +static inline uint32_t pad_crc(uint16_t padding, uint32_t crc, FastCRC32 &crcCalc) { const uint8_t raw_value = 0u; while (padding>0) { - crc = CRC32.crc32_upd(&raw_value, 1, false); + crc = crcCalc.crc32_upd(&raw_value, 1, false); --padding; } return crc; } -static inline uint32_t compute_crc(page_iterator_t &entity, pCrcCalc calcFunc) +static inline uint32_t compute_crc(const page_iterator_t &entity, pCrcCalc calcFunc, FastCRC32 &crcCalc) { switch (entity.type) { case Raw: - return compute_raw_crc(entity, calcFunc); + return compute_raw_crc(entity, calcFunc, crcCalc); break; case Table: - return compute_table_crc(entity, calcFunc); + return compute_table_crc(entity, calcFunc, crcCalc); break; case NoEntity: - return pad_crc(entity.size, 0U); + return pad_crc(entity.size, 0U, crcCalc); break; default: @@ -84,15 +86,16 @@ static inline uint32_t compute_crc(page_iterator_t &entity, pCrcCalc calcFunc) uint32_t calculatePageCRC32(byte pageNum) { + FastCRC32 crcCalc; page_iterator_t entity = page_begin(pageNum); // Initial CRC calc - uint32_t crc = compute_crc(entity, &FastCRC32::crc32); + uint32_t crc = compute_crc(entity, &FastCRC32::crc32, crcCalc); entity = advance(entity); while (entity.type!=End) { - crc = compute_crc(entity, &FastCRC32::crc32_upd /* Note that we are *updating* */); + crc = compute_crc(entity, &FastCRC32::crc32_upd /* Note that we are *updating* */, crcCalc); entity = advance(entity); } - return ~pad_crc(getPageSize(pageNum) - entity.size, crc); + return ~pad_crc(getPageSize(pageNum) - entity.size, crc, crcCalc); } \ No newline at end of file diff --git a/speeduino/speeduino.ino b/speeduino/speeduino.ino index 0b20fe98..be93a0b0 100644 --- a/speeduino/speeduino.ino +++ b/speeduino/speeduino.ino @@ -121,36 +121,15 @@ void loop(void) //SERIAL Comms //Initially check that the last serial send values request is not still outstanding - if (serialInProgress == true) - { - if(Serial.availableForWrite() > 16) { sendValues(inProgressOffset, inProgressLength, 0x30, 0); } - } - //Perform the same check for the tooth and composite logs - if( toothLogSendInProgress == true) + if (serialTransmitInProgress()) { - if(Serial.availableForWrite() > 16) - { - if(legacySerial == true) { sendToothLog_legacy(inProgressOffset); } - else { sendToothLog(inProgressOffset); } - } - } - if( compositeLogSendInProgress == true) - { - if(Serial.availableForWrite() > 16) { sendCompositeLog(inProgressOffset); } - } - if(serialWriteInProgress == true) - { - if(Serial.availableForWrite() > 16) { continueSerialTransmission(); } + serialTransmit(); } - //Check for any new requests from serial. - //if ( (Serial.available()) > 0) { command(); } - if ( (Serial.available()) > 0) { parseSerial(); } - - else if(cmdPending == true) + //Check for any new or in-progress requests from serial. + if (Serial.available()>0 || serialRecieveInProgress()) { - //This is a special case just for the tooth and composite loggers - if (currentCommand == 'T') { legacySerialCommand(); } + serialReceive(); } //Check for any CAN comms requiring action @@ -301,8 +280,6 @@ void loop(void) // Air conditioning control airConControl(); - //if( (isEepromWritePending() == true) && (serialReceivePending == false) && (micros() > deferEEPROMWritesUntil)) { writeAllConfig(); } //Used for slower EEPROM writes (Currently this runs in the 30Hz block) - currentStatus.vss = getSpeed(); currentStatus.gear = getGear(); @@ -334,7 +311,7 @@ void loop(void) #endif //Check for any outstanding EEPROM writes. - if( (isEepromWritePending() == true) && (serialReceivePending == false) && (micros() > deferEEPROMWritesUntil)) { writeAllConfig(); } + if( (isEepromWritePending() == true) && (serialStatusFlag == SERIAL_INACTIVE) && (micros() > deferEEPROMWritesUntil)) { writeAllConfig(); } } if (BIT_CHECK(LOOP_TIMER, BIT_TIMER_4HZ)) {