From 02cb7bebd66e59eaef353eaa29520c8eaebc1bf7 Mon Sep 17 00:00:00 2001 From: tx_haggis <13982343+adbancroft@users.noreply.github.com> Date: Tue, 20 Apr 2021 23:36:27 -0500 Subject: [PATCH] Isolate table3D member access in comms.ino (#545) * fix!: fix ODR violations * refactor: move page specific code into a separate CPP file * refactor: page getter/setter share mapping logic Extract common page-to-entity mapping logic from getPageValue() & setPageValue() - place in map_page_offset_to_entity() and share. * performance: optimize CRC calc Calculate page CRC by iterating over entities & tables. * CRC table calculation - use table iterator * refactor: use iterators for sendPage() Re-implement sendPage() using page & table iterators Future proof & fast * refactor: sendPageASCII() Pull put shared code into functions. Use table iterator * refactor: use shared axis factor This puts the axis factor usage in one place * refactor: encapsulate page size & count Added getPageCount() & getPageSize() * Added static_assert for all pages. * Remove C++ language elements namesapces, scope resolution, enum struct * Rename comms.ino to comms.cpp Provides better encapsulation of non-global data & functions. INO files are all mashed together by some custom process. So everything becomes global and static functions/variables aren't really private to the translation unit. Thus breaking encapsulation :-( --- speeduino/board_stm32_generic.h | 4 +- speeduino/board_stm32_generic.ino | 8 + speeduino/board_stm32_official.h | 44 +- speeduino/board_stm32_official.ino | 50 + speeduino/board_teensy35.h | 6 +- speeduino/board_teensy35.ino | 7 + speeduino/board_teensy41.h | 6 +- speeduino/board_teensy41.ino | 3 + speeduino/cancomms.h | 18 +- speeduino/cancomms.ino | 28 + speeduino/comms.cpp | 1547 +++++++++++++++++++ speeduino/comms.h | 67 +- speeduino/comms.ino | 2316 ---------------------------- speeduino/errors.h | 4 - speeduino/errors.ino | 3 + speeduino/globals.h | 9 - speeduino/globals.ino | 1 - speeduino/page_crc.cpp | 95 ++ speeduino/page_crc.h | 7 + speeduino/pages.cpp | 372 +++++ speeduino/pages.h | 80 + speeduino/sensors.ino | 1 + speeduino/storage.h | 2 +- speeduino/storage.ino | 99 +- speeduino/table.h | 2 - speeduino/table.ino | 3 + speeduino/table_iterator.h | 101 ++ speeduino/utilities.h | 9 +- speeduino/utilities.ino | 166 +- 29 files changed, 2415 insertions(+), 2643 deletions(-) create mode 100644 speeduino/comms.cpp delete mode 100644 speeduino/comms.ino create mode 100644 speeduino/page_crc.cpp create mode 100644 speeduino/page_crc.h create mode 100644 speeduino/pages.cpp create mode 100644 speeduino/pages.h create mode 100644 speeduino/table_iterator.h diff --git a/speeduino/board_stm32_generic.h b/speeduino/board_stm32_generic.h index b9708515..5e6a6110 100644 --- a/speeduino/board_stm32_generic.h +++ b/speeduino/board_stm32_generic.h @@ -37,9 +37,9 @@ #if defined(FRAM_AS_EEPROM) #include #if defined(STM32F407xx) - FramClass EEPROM(PB5, PB4, PB3, PB0); /*(mosi, miso, sclk, ssel, clockspeed) 31/01/2020*/ + extern FramClass EEPROM; /*(mosi, miso, sclk, ssel, clockspeed) 31/01/2020*/ #else - FramClass EEPROM(PB15, PB14, PB13, PB12); //Blue/Black Pills + extern FramClass EEPROM; //Blue/Black Pills #endif #endif diff --git a/speeduino/board_stm32_generic.ino b/speeduino/board_stm32_generic.ino index 92e88c28..91f89f81 100644 --- a/speeduino/board_stm32_generic.ino +++ b/speeduino/board_stm32_generic.ino @@ -6,6 +6,14 @@ #include "scheduler.h" #include "HardwareTimer.h" + #if defined(FRAM_AS_EEPROM) + #if defined(STM32F407xx) + FramClass EEPROM(PB5, PB4, PB3, PB0); /*(mosi, miso, sclk, ssel, clockspeed) 31/01/2020*/ + #else + FramClass EEPROM(PB15, PB14, PB13, PB12); //Blue/Black Pills + #endif + #endif + void initBoard() { /* diff --git a/speeduino/board_stm32_official.h b/speeduino/board_stm32_official.h index e7b7e53f..47beb0da 100644 --- a/speeduino/board_stm32_official.h +++ b/speeduino/board_stm32_official.h @@ -77,42 +77,42 @@ extern "C" char* sbrk(int incr); #if defined(SRAM_AS_EEPROM) #define EEPROM_LIB_H "src/BackupSram/BackupSramAsEEPROM.h" #include EEPROM_LIB_H - BackupSramAsEEPROM EEPROM; + extern BackupSramAsEEPROM EEPROM; #elif defined(USE_SPI_EEPROM) #define EEPROM_LIB_H "src/SPIAsEEPROM/SPIAsEEPROM.h" #include EEPROM_LIB_H - SPIClass SPI_for_flash(PB5, PB4, PB3); //SPI1_MOSI, SPI1_MISO, SPI1_SCK + extern SPIClass SPI_for_flash; //SPI1_MOSI, SPI1_MISO, SPI1_SCK //windbond W25Q16 SPI flash EEPROM emulation - EEPROM_Emulation_Config EmulatedEEPROMMconfig{255UL, 4096UL, 31, 0x00100000UL}; - Flash_SPI_Config SPIconfig{USE_SPI_EEPROM, SPI_for_flash}; - SPI_EEPROM_Class EEPROM(EmulatedEEPROMMconfig, SPIconfig); + extern EEPROM_Emulation_Config EmulatedEEPROMMconfig; + extern Flash_SPI_Config SPIconfig; + extern SPI_EEPROM_Class EEPROM; #elif defined(FRAM_AS_EEPROM) //https://github.com/VitorBoss/FRAM #define EEPROM_LIB_H #include EEPROM_LIB_H #if defined(STM32F407xx) - FramClass EEPROM(PB5, PB4, PB3, PB0); /*(mosi, miso, sclk, ssel, clockspeed) 31/01/2020*/ + extern FramClass EEPROM; /*(mosi, miso, sclk, ssel, clockspeed) 31/01/2020*/ #else - FramClass EEPROM(PB15, PB14, PB13, PB12); //Blue/Black Pills + extern FramClass EEPROM; //Blue/Black Pills #endif #elif defined(STM32F7xx) #define EEPROM_LIB_H "src/SPIAsEEPROM/SPIAsEEPROM.h" #include EEPROM_LIB_H #if defined(DUAL_BANK) - EEPROM_Emulation_Config EmulatedEEPROMMconfig{4UL, 131072UL, 2047UL, 0x08120000UL}; + extern EEPROM_Emulation_Config EmulatedEEPROMMconfig; #else - EEPROM_Emulation_Config EmulatedEEPROMMconfig{2UL, 262144UL, 4095UL, 0x08180000UL}; + extern EEPROM_Emulation_Config EmulatedEEPROMMconfig; #endif - InternalSTM32F7_EEPROM_Class EEPROM(EmulatedEEPROMMconfig); + extern InternalSTM32F7_EEPROM_Class EEPROM; #elif defined(STM32F411xE) #define EEPROM_LIB_H "src/SPIAsEEPROM/SPIAsEEPROM.h" #include EEPROM_LIB_H - EEPROM_Emulation_Config EmulatedEEPROMMconfig{2UL, 131072UL, 4095UL, 0x08040000UL}; - InternalSTM32F4_EEPROM_Class EEPROM(EmulatedEEPROMMconfig); + extern EEPROM_Emulation_Config EmulatedEEPROMMconfig; + extern InternalSTM32F4_EEPROM_Class EEPROM; #elif defined(STM32F401xC) //when using with internal falsh not enough rom is available so small flash mode is enabled @@ -126,8 +126,8 @@ extern "C" char* sbrk(int incr); #else //default case, internal flash as EEPROM for STM32F407 #define EEPROM_LIB_H "src/SPIAsEEPROM/SPIAsEEPROM.h" #include EEPROM_LIB_H - EEPROM_Emulation_Config EmulatedEEPROMMconfig{4UL, 131072UL, 2047UL, 0x08080000UL}; - InternalSTM32F4_EEPROM_Class EEPROM(EmulatedEEPROMMconfig); + extern EEPROM_Emulation_Config EmulatedEEPROMMconfig; + extern InternalSTM32F4_EEPROM_Class EEPROM; #endif #define RTC_LIB_H "STM32RTC.h" @@ -267,16 +267,16 @@ extern "C" char* sbrk(int incr); * Timers */ -HardwareTimer Timer1(TIM1); -HardwareTimer Timer2(TIM2); -HardwareTimer Timer3(TIM3); -HardwareTimer Timer4(TIM4); +extern HardwareTimer Timer1; +extern HardwareTimer Timer2; +extern HardwareTimer Timer3; +extern HardwareTimer Timer4; #if !defined(ARDUINO_BLUEPILL_F103C8) && !defined(ARDUINO_BLUEPILL_F103CB) //F103 just have 4 timers -HardwareTimer Timer5(TIM5); +extern HardwareTimer Timer5; #if defined(TIM11) -HardwareTimer Timer11(TIM11); +extern HardwareTimer Timer11; #elif defined(TIM7) -HardwareTimer Timer11(TIM7); +extern HardwareTimer Timer11; #endif #endif @@ -328,7 +328,7 @@ void ignitionSchedule8Interrupt(HardwareTimer*); //HardwareSerial CANSerial(PD6, PD5); #include //This activates CAN1 interface on STM32, but it's named as Can0, because that's how Teensy implementation is done -STM32_CAN Can0 (_CAN1,DEF); +extern STM32_CAN Can0; /* Second CAN interface is also available if needed or it can be used also as primary CAN interface. for STM32F4 the default CAN1 pins are PD0 & PD1. Alternative (ALT) pins are PB8 & PB9 and ALT2 pins are PA11 and PA12: diff --git a/speeduino/board_stm32_official.ino b/speeduino/board_stm32_official.ino index f4a5ae3f..70097dec 100644 --- a/speeduino/board_stm32_official.ino +++ b/speeduino/board_stm32_official.ino @@ -6,6 +6,56 @@ #include "scheduler.h" #include "HardwareTimer.h" +#if defined(STM32F407xx) || defined(STM32F103xB) || defined(STM32F405xx) +#define NATIVE_CAN_AVAILABLE +//This activates CAN1 interface on STM32, but it's named as Can0, because that's how Teensy implementation is done +STM32_CAN Can0 (_CAN1,DEF); +#endif + +#if defined(SRAM_AS_EEPROM) + BackupSramAsEEPROM EEPROM; +#elif defined(USE_SPI_EEPROM) + SPIClass SPI_for_flash(PB5, PB4, PB3); //SPI1_MOSI, SPI1_MISO, SPI1_SCK + + //windbond W25Q16 SPI flash EEPROM emulation + EEPROM_Emulation_Config EmulatedEEPROMMconfig{255UL, 4096UL, 31, 0x00100000UL}; + Flash_SPI_Config SPIconfig{USE_SPI_EEPROM, SPI_for_flash}; + SPI_EEPROM_Class EEPROM(EmulatedEEPROMMconfig, SPIconfig); +#elif defined(FRAM_AS_EEPROM) //https://github.com/VitorBoss/FRAM + #if defined(STM32F407xx) + FramClass EEPROM(PB5, PB4, PB3, PB0); /*(mosi, miso, sclk, ssel, clockspeed) 31/01/2020*/ + #else + FramClass EEPROM(PB15, PB14, PB13, PB12); //Blue/Black Pills + #endif +#elif defined(STM32F7xx) + #if defined(DUAL_BANK) + EEPROM_Emulation_Config EmulatedEEPROMMconfig{4UL, 131072UL, 2047UL, 0x08120000UL}; + #else + EEPROM_Emulation_Config EmulatedEEPROMMconfig{2UL, 262144UL, 4095UL, 0x08180000UL}; + #endif + InternalSTM32F7_EEPROM_Class EEPROM(EmulatedEEPROMMconfig); +#elif defined(STM32F401xC) + EEPROM_Emulation_Config EmulatedEEPROMMconfig{2UL, 131072UL, 4095UL, 0x08040000UL}; + InternalSTM32F4_EEPROM_Class EEPROM(EmulatedEEPROMMconfig); +#else //default case, internal flash as EEPROM for STM32F4 + EEPROM_Emulation_Config EmulatedEEPROMMconfig{4UL, 131072UL, 2047UL, 0x08080000UL}; + InternalSTM32F4_EEPROM_Class EEPROM(EmulatedEEPROMMconfig); +#endif + + +HardwareTimer Timer1(TIM1); +HardwareTimer Timer2(TIM2); +HardwareTimer Timer3(TIM3); +HardwareTimer Timer4(TIM4); +#if !defined(ARDUINO_BLUEPILL_F103C8) && !defined(ARDUINO_BLUEPILL_F103CB) //F103 just have 4 timers +HardwareTimer Timer5(TIM5); +#if defined(TIM11) +HardwareTimer Timer11(TIM11); +#elif defined(TIM7) +HardwareTimer Timer11(TIM7); +#endif +#endif + STM32RTC& rtc = STM32RTC::getInstance(); void initBoard() diff --git a/speeduino/board_teensy35.h b/speeduino/board_teensy35.h index aa41fb1d..8554c3b0 100644 --- a/speeduino/board_teensy35.h +++ b/speeduino/board_teensy35.h @@ -144,10 +144,10 @@ #define USE_SERIAL3 // Secondary serial port to use #include #if defined(__MK64FX512__) // use for Teensy 3.5 only - FlexCAN_T4 Can0; + extern FlexCAN_T4 Can0; #elif defined(__MK66FX1M0__) // use for Teensy 3.6 only - FlexCAN_T4 Can0; - FlexCAN_T4 Can1; + extern FlexCAN_T4 Can0; + extern FlexCAN_T4 Can1; #endif static CAN_message_t outMsg; static CAN_message_t inMsg; diff --git a/speeduino/board_teensy35.ino b/speeduino/board_teensy35.ino index a502c33f..ec2ebfcb 100644 --- a/speeduino/board_teensy35.ino +++ b/speeduino/board_teensy35.ino @@ -5,6 +5,13 @@ #include "idle.h" #include "scheduler.h" +#if defined(__MK64FX512__) // use for Teensy 3.5 only + FlexCAN_T4 Can0; +#elif defined(__MK66FX1M0__) // use for Teensy 3.6 only + FlexCAN_T4 Can0; + FlexCAN_T4 Can1; +#endif + void initBoard() { /* diff --git a/speeduino/board_teensy41.h b/speeduino/board_teensy41.h index 9b6cba11..d80d8540 100644 --- a/speeduino/board_teensy41.h +++ b/speeduino/board_teensy41.h @@ -154,9 +154,9 @@ */ #define USE_SERIAL3 #include - FlexCAN_T4 Can0; - FlexCAN_T4 Can1; - FlexCAN_T4 Can2; + extern FlexCAN_T4 Can0; + extern FlexCAN_T4 Can1; + extern FlexCAN_T4 Can2; static CAN_message_t outMsg; static CAN_message_t inMsg; //#define NATIVE_CAN_AVAILABLE //Disable for now as it causes lockup diff --git a/speeduino/board_teensy41.ino b/speeduino/board_teensy41.ino index aa1622e6..2e597bd4 100644 --- a/speeduino/board_teensy41.ino +++ b/speeduino/board_teensy41.ino @@ -5,6 +5,9 @@ #include "idle.h" #include "scheduler.h" +FlexCAN_T4 Can0; +FlexCAN_T4 Can1; +FlexCAN_T4 Can2; void initBoard() { diff --git a/speeduino/cancomms.h b/speeduino/cancomms.h index 8d1a11fa..5ea9d005 100644 --- a/speeduino/cancomms.h +++ b/speeduino/cancomms.h @@ -4,32 +4,22 @@ #define NEW_CAN_PACKET_SIZE 75 #define CAN_PACKET_SIZE 75 -uint8_t currentsecondserialCommand; -uint8_t currentCanPage = 1;//Not the same as the speeduino config page numbers -uint8_t nCanretry = 0; //no of retrys -uint8_t cancmdfail = 0; //command fail yes/no -uint8_t canlisten = 0; -uint8_t Lbuffer[8]; //8 byte buffer to store incomng can data -uint8_t Gdata[9]; -uint8_t Glow, Ghigh; -bool canCmdPending = false; - #if ( defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) ) #define CANSerial_AVAILABLE - HardwareSerial &CANSerial = Serial3; + extern HardwareSerial &CANSerial; #elif defined(CORE_STM32) #define CANSerial_AVAILABLE #ifndef Serial2 #define Serial2 Serial1 #endif #if defined(STM32GENERIC) // STM32GENERIC core - SerialUART &CANSerial = Serial2; + extern SerialUART &CANSerial; #else //libmaple core aka STM32DUINO - HardwareSerial &CANSerial = Serial2; + extern HardwareSerial &CANSerial; #endif #elif defined(CORE_TEENSY) #define CANSerial_AVAILABLE - HardwareSerial &CANSerial = Serial2; + extern HardwareSerial &CANSerial; #endif void secondserial_Command();//This is the heart of the Command Line Interpeter. All that needed to be done was to make it human readable. diff --git a/speeduino/cancomms.ino b/speeduino/cancomms.ino index 575c234b..d2c3faf0 100644 --- a/speeduino/cancomms.ino +++ b/speeduino/cancomms.ino @@ -21,6 +21,34 @@ sendcancommand is called when a command is to be sent either to serial3 #include "errors.h" #include "utilities.h" +uint8_t currentsecondserialCommand; +uint8_t currentCanPage = 1;//Not the same as the speeduino config page numbers +uint8_t nCanretry = 0; //no of retrys +uint8_t cancmdfail = 0; //command fail yes/no +uint8_t canlisten = 0; +uint8_t Lbuffer[8]; //8 byte buffer to store incomng can data +uint8_t Gdata[9]; +uint8_t Glow, Ghigh; +bool canCmdPending = false; + +#if ( defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) ) + #define CANSerial_AVAILABLE + HardwareSerial &CANSerial = Serial3; +#elif defined(CORE_STM32) + #define CANSerial_AVAILABLE + #ifndef Serial2 + #define Serial2 Serial1 + #endif + #if defined(STM32GENERIC) // STM32GENERIC core + SerialUART &CANSerial = Serial2; + #else //libmaple core aka STM32DUINO + HardwareSerial &CANSerial = Serial2; + #endif +#elif defined(CORE_TEENSY) + #define CANSerial_AVAILABLE + HardwareSerial &CANSerial = Serial2; +#endif + void secondserial_Command() { #if defined(CANSerial_AVAILABLE) diff --git a/speeduino/comms.cpp b/speeduino/comms.cpp new file mode 100644 index 00000000..18b09d41 --- /dev/null +++ b/speeduino/comms.cpp @@ -0,0 +1,1547 @@ +/* +Speeduino - Simple engine management for the Arduino Mega 2560 platform +Copyright (C) Josh Stewart +A full copy of the license may be found in the projects root directory +*/ +#include "globals.h" +#include "comms.h" +#include "cancomms.h" +#include "storage.h" +#include "maths.h" +#include "utilities.h" +#include "decoders.h" +#include "TS_CommandButtonHandler.h" +#include "errors.h" +#include "pages.h" +#include "page_crc.h" +#include "table_iterator.h" +#ifdef RTC_ENABLED + #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 chucnk 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; +byte inProgressLength; +uint32_t inProgressCompositeTime; +bool serialInProgress = false; +bool toothLogSendInProgress = false; +bool compositeLogSendInProgress = false; + +/* + Processes the data on the serial buffer. + Can be either a new command or a continuation of one that is already in progress: + * cmdPending = If a command has started but is wairing on further data to complete + * chunkPending = Specifically for the new receive value method where TS will send a known number of contiguous bytes to be written to a table +*/ +void command() +{ + if (cmdPending == false) { currentCommand = Serial.read(); } + + switch (currentCommand) + { + + case 'a': + cmdPending = true; + + 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; + } + break; + + case 'A': // send x bytes of realtime values + sendValues(0, LOG_ENTRY_SIZE, 0x31, 0); //send values to serial0 + break; + + + case 'B': // Burn current values to eeprom + writeAllConfig(); + break; + + case 'b': // New EEPROM burn command to only burn a single page at a time + cmdPending = true; + + if (Serial.available() >= 2) + { + Serial.read(); //Ignore the first table value, it's always 0 + writeConfig(Serial.read()); + cmdPending = false; + } + break; + + case 'C': // test communications. This is used by Tunerstudio to see whether there is an ECU on a given serial port + testComm(); + break; + + case 'c': //Send the current loops/sec value + Serial.write(lowByte(currentStatus.loopsPerSecond)); + Serial.write(highByte(currentStatus.loopsPerSecond)); + break; + + case 'd': // Send a CRC32 hash of a given page + cmdPending = true; + + if (Serial.available() >= 2) + { + Serial.read(); //Ignore the first byte value, it's always 0 + uint32_t CRC32_val = calculateCRC32( Serial.read() ); + + //Split the 4 bytes of the CRC32 value into individual bytes and send + Serial.write( ((CRC32_val >> 24) & 255) ); + Serial.write( ((CRC32_val >> 16) & 255) ); + Serial.write( ((CRC32_val >> 8) & 255) ); + Serial.write( (CRC32_val & 255) ); + + cmdPending = false; + } + break; + + case 'E': // receive command button commands + cmdPending = true; + + 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; + } + } + break; + + case 'F': // send serial protocol version + Serial.print(F("001")); + 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; + toothHistorySerialIndex = 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 ); + + 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 ); + 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; + toothHistorySerialIndex = 0; + compositeLastToothTime = 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 ); + + 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 ); + break; + + case 'L': // List the contents of current page in human readable form + #ifndef SMALL_FLASH_MODE + sendPageASCII(); + #endif + break; + + case 'm': //Send the current free memory + currentStatus.freeRAM = freeRam(); + Serial.write(lowByte(currentStatus.freeRAM)); + Serial.write(highByte(currentStatus.freeRAM)); + break; + + case 'N': // Displays a new line. Like pushing enter in a text editor + Serial.println(); + break; + + 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; + + if (Serial.available() > 0) + { + currentPage = Serial.read(); + //This converts the ascii number char into binary. Note that this will break everyything if there are ever more than 48 pages (48 = asci code for '0') + if ((currentPage >= '0') && (currentPage <= '9')) // 0 - 9 + { + currentPage -= 48; + } + else if ((currentPage >= 'a') && (currentPage <= 'f')) // 10 - 15 + { + currentPage -= 87; + } + else if ((currentPage >= 'A') && (currentPage <= 'F')) + { + 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; + } + break; + + /* + * New method for sending page values + */ + case 'p': + cmdPending = true; + + //6 bytes required: + //2 - Page identifier + //2 - offset + //2 - Length + if(Serial.available() >= 6) + { + byte offset1, offset2, length1, length2; + int length; + byte tempPage; + + Serial.read(); // First byte of the page identifier can be ignored. It's always 0 + tempPage = Serial.read(); + //currentPage = 1; + offset1 = Serial.read(); + offset2 = Serial.read(); + valueOffset = word(offset2, offset1); + length1 = Serial.read(); + length2 = Serial.read(); + length = word(length2, length1); + for(int i = 0; i < length; i++) + { + Serial.write( getPageValue(tempPage, valueOffset + i) ); + } + + cmdPending = false; + } + break; + + case 'Q': // send code version + Serial.print(F("speeduino 202104-dev")); + break; + + case 'r': //New format for the optimised OutputChannels + cmdPending = true; + byte cmd; + if (Serial.available() >= 6) + { + tsCanId = Serial.read(); //Read the $tsCanId + cmd = Serial.read(); // read the command + + uint16_t offset, length; + byte tmp; + tmp = Serial.read(); + offset = word(Serial.read(), tmp); + tmp = Serial.read(); + length = word(Serial.read(), tmp); + + + if(cmd == 0x30) //Send output channels command 0x30 is 48dec + { + sendValues(offset, length, cmd, 0); + } +#ifdef RTC_ENABLED + else if(cmd == SD_RTC_PAGE) //Request to read SD card RTC + { + /* + uint16_t packetSize = 2 + 1 + length + 4; + packetSize = 15; + Serial.write(highByte(packetSize)); + Serial.write(lowByte(packetSize)); + byte packet[length+1]; + + packet[0] = 0; + packet[1] = length; + packet[2] = 0; + packet[3] = 0; + packet[4] = 0; + packet[5] = 0; + packet[6] = 0; + packet[7] = 0; + packet[8] = 0; + Serial.write(packet, 9); + + FastCRC32 CRC32; + uint32_t CRC32_val = CRC32.crc32((byte *)packet, sizeof(packet) );; + + //Split the 4 bytes of the CRC32 value into individual bytes and send + Serial.write( ((CRC32_val >> 24) & 255) ); + Serial.write( ((CRC32_val >> 16) & 255) ); + Serial.write( ((CRC32_val >> 8) & 255) ); + Serial.write( (CRC32_val & 255) ); + */ + Serial.write(rtc_getSecond()); //Seconds + Serial.write(rtc_getMinute()); //Minutes + Serial.write(rtc_getHour()); //Hours + Serial.write(rtc_getDOW()); //Day of Week + Serial.write(rtc_getDay()); //Date + Serial.write(rtc_getMonth()); //Month + Serial.write(lowByte(rtc_getYear())); //Year - NOTE 2 bytes + Serial.write(highByte(rtc_getYear())); //Year + + } + else if(cmd == SD_READWRITE_PAGE) //Request SD card extended parameters + { + //SD read commands use the offset and length fields to indicate the request type + if((offset == SD_READ_STAT_OFFSET) && (length == SD_READ_STAT_LENGTH)) + { + //Read the status of the SD card + + //Serial.write(0); + + + //Serial.write(currentStatus.TS_SD_Status); + Serial.write((uint8_t)5); + Serial.write((uint8_t)0); + + //All other values are 2 bytes + Serial.write((uint8_t)2); //Sector size + Serial.write((uint8_t)0); //Sector size + + //Max blocks (4 bytes) + Serial.write((uint8_t)0); + Serial.write((uint8_t)0x20); //1gb dummy card + Serial.write((uint8_t)0); + Serial.write((uint8_t)0); + + //Max roots (Number of files) + Serial.write((uint8_t)0); + Serial.write((uint8_t)1); + + //Dir Start (4 bytes) + Serial.write((uint8_t)0); //Dir start lower 2 bytes + Serial.write((uint8_t)0); //Dir start lower 2 bytes + Serial.write((uint8_t)0); //Dir start lower 2 bytes + Serial.write((uint8_t)0); //Dir start lower 2 bytes + + //Unkown purpose for last 2 bytes + Serial.write((uint8_t)0); //Dir start lower 2 bytes + Serial.write((uint8_t)0); //Dir start lower 2 bytes + + /* + Serial.write(lowByte(23)); + Serial.write(highByte(23)); + + byte packet[17]; + packet[0] = 0; + packet[1] = 5; + packet[2] = 0; + + packet[3] = 2; + packet[4] = 0; + + packet[5] = 0; + packet[6] = 0x20; + packet[7] = 0; + packet[8] = 0; + + packet[9] = 0; + packet[10] = 1; + + packet[11] = 0; + packet[12] = 0; + packet[13] = 0; + packet[14] = 0; + + packet[15] = 0; + packet[16] = 0; + + Serial.write(packet, 17); + FastCRC32 CRC32; + uint32_t CRC32_val = CRC32.crc32((byte *)packet, sizeof(packet) );; + + //Split the 4 bytes of the CRC32 value into individual bytes and send + Serial.write( ((CRC32_val >> 24) & 255) ); + Serial.write( ((CRC32_val >> 16) & 255) ); + Serial.write( ((CRC32_val >> 8) & 255) ); + Serial.write( (CRC32_val & 255) ); + */ + + } + //else if(length == 0x202) + { + //File info + } + } + else if(cmd == 0x14) + { + //Fetch data from file + } +#endif + else + { + //No other r/ commands should be called + } + cmdPending = false; + } + break; + + case 'S': // send code version + Serial.print(F("Speeduino 2021.04-dev")); + 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 + //6 bytes required: + //2 - Page identifier + //2 - offset + //2 - Length + cmdPending = true; + if(Serial.available() >= 6) + { + Serial.read(); // First byte of the page identifier can be ignored. It's always 0 + Serial.read(); // First byte of the page identifier can be ignored. It's always 0 + Serial.read(); // First byte of the page identifier can be ignored. It's always 0 + Serial.read(); // First byte of the page identifier can be ignored. It's always 0 + Serial.read(); // First byte of the page identifier can be ignored. It's always 0 + Serial.read(); // First byte of the page identifier can be ignored. It's always 0 + + if(currentStatus.toothLogEnabled == true) { sendToothLog(0); } //Sends tooth log values as ints + else if (currentStatus.compositeLogEnabled == true) { sendCompositeLog(0); } + + cmdPending = false; + } + + + + break; + + case 't': // receive new Calibration info. Command structure: "t", . + byte tableID; + //byte canID; + + //The first 2 bytes sent represent the canID and tableID + while (Serial.available() == 0) { } + tableID = Serial.read(); //Not currently used for anything + + receiveCalibration(tableID); //Receive new values and store in memory + writeCalibration(); //Store received values in EEPROM + + break; + + case 'U': //User wants to reset the Arduino (probably for FW update) + if (resetControl != RESET_CONTROL_DISABLED) + { + #ifndef SMALL_FLASH_MODE + if (!cmdPending) { Serial.println(F("Comms halted. Next byte will reset the Arduino.")); } + #endif + + while (Serial.available() == 0) { } + digitalWrite(pinResetControl, LOW); + } + else + { + #ifndef SMALL_FLASH_MODE + if (!cmdPending) { Serial.println(F("Reset control is currently disabled.")); } + #endif + } + break; + + case 'V': // send VE table and constants in binary + sendPage(); + break; + + case 'W': // receive new VE obr constant at 'W'++ + cmdPending = true; + + if (isMap) + { + if(Serial.available() >= 3) // 1 additional byte is required on the MAP pages which are larger than 255 bytes + { + byte offset1, offset2; + offset1 = Serial.read(); + offset2 = Serial.read(); + valueOffset = word(offset2, offset1); + setPageValue(currentPage, valueOffset, Serial.read()); + cmdPending = false; + } + } + else + { + if(Serial.available() >= 2) + { + valueOffset = Serial.read(); + setPageValue(currentPage, valueOffset, Serial.read()); + cmdPending = false; + } + } + + break; + + case 'M': + cmdPending = true; + + if(chunkPending == false) + { + //This means it's a new request + //7 bytes required: + //2 - Page identifier + //2 - offset + //2 - Length + //1 - 1st New value + if(Serial.available() >= 7) + { + byte offset1, offset2, length1, length2; + + Serial.read(); // First byte of the page identifier can be ignored. It's always 0 + currentPage = Serial.read(); + //currentPage = 1; + offset1 = Serial.read(); + offset2 = Serial.read(); + valueOffset = word(offset2, offset1); + length1 = Serial.read(); + length2 = Serial.read(); + chunkSize = word(length2, length1); + + //Regular page data + chunkPending = true; + chunkComplete = 0; + } + } + //This CANNOT be an else of the above if statement as chunkPending gets set to true above + if(chunkPending == true) + { + while( (Serial.available() > 0) && (chunkComplete < chunkSize) ) + { + setPageValue(currentPage, (valueOffset + chunkComplete), Serial.read()); + chunkComplete++; + } + if(chunkComplete >= chunkSize) { cmdPending = false; chunkPending = false; } + } + break; + + case 'w': + if(Serial.available() >= 7) + { + byte offset1, offset2, length1, length2; + + Serial.read(); // First byte of the page identifier can be ignored. It's always 0 + currentPage = Serial.read(); + //currentPage = 1; + offset1 = Serial.read(); + offset2 = Serial.read(); + valueOffset = word(offset2, offset1); + length1 = Serial.read(); + length2 = Serial.read(); + chunkSize = word(length2, length1); + } +#ifdef RTC_ENABLED + if(currentPage == SD_READWRITE_PAGE) + { + cmdPending = false; + + //Reserved for the SD card settings. Appears to be hardcoded into TS. Flush the final byte in the buffer as its not used for now + Serial.read(); + if((valueOffset == SD_WRITE_DO_OFFSET) && (chunkSize == SD_WRITE_DO_LENGTH)) + { + /* + SD DO command. Single byte of data where the commands are: + 0 Reset + 1 Reset + 2 Stop logging + 3 Start logging + 4 Load status variable + 5 Init SD card + */ + Serial.read(); + } + else if((valueOffset == SD_WRITE_SEC_OFFSET) && (chunkSize == SD_WRITE_SEC_LENGTH)) + { + //SD write sector command + } + else if((valueOffset == SD_ERASEFILE_OFFSET) && (chunkSize == SD_ERASEFILE_LENGTH)) + { + //Erase file command + //First 4 bytes are the log number in ASCII + /* + char log1 = Serial.read(); + char log2 = Serial.read(); + char log3 = Serial.read(); + char log4 = Serial.read(); + */ + + //Next 2 bytes are the directory block no + Serial.read(); + Serial.read(); + } + else if((valueOffset == SD_SPD_TEST_OFFSET) && (chunkSize == SD_SPD_TEST_LENGTH)) + { + //Perform a speed test on the SD card + //First 4 bytes are the sector number to write to + Serial.read(); + Serial.read(); + Serial.read(); + Serial.read(); + + //Last 4 bytes are the number of sectors to test + Serial.read(); + Serial.read(); + Serial.read(); + Serial.read(); + } + } + else if(currentPage == SD_RTC_PAGE) + { + cmdPending = false; + //Used for setting RTC settings + if((valueOffset == SD_RTC_WRITE_OFFSET) && (chunkSize == SD_RTC_WRITE_LENGTH)) + { + //Set the RTC date/time + //Need to ensure there are 9 more bytes with the new values + while(Serial.available() < 9) {} //Terrible hack, but RTC values should not be set with the engine running anyway + byte second = Serial.read(); + byte minute = Serial.read(); + byte hour = Serial.read(); + //byte dow = Serial.read(); + Serial.read(); // This is the day of week value, which is currently unused + byte day = Serial.read(); + byte month = Serial.read(); + uint16_t year = Serial.read(); + year = word(Serial.read(), year); + Serial.read(); //Final byte is unused (Always has value 0x5a) + rtc_setTime(second, minute, hour, day, month, year); + } + } +#endif + break; + + case 'Z': //Totally non-standard testing function. Will be removed once calibration testing is completed. This function takes 1.5kb of program space! :S + #ifndef SMALL_FLASH_MODE + Serial.println(F("Coolant")); + for (int x = 0; x < 32; x++) + { + Serial.print(cltCalibration_bins[x]); + Serial.print(", "); + Serial.println(cltCalibration_values[x]); + } + Serial.println(F("Inlet temp")); + for (int x = 0; x < 32; x++) + { + Serial.print(iatCalibration_bins[x]); + Serial.print(", "); + Serial.println(iatCalibration_values[x]); + } + Serial.println(F("O2")); + for (int x = 0; x < 32; x++) + { + Serial.print(o2Calibration_bins[x]); + Serial.print(", "); + Serial.println(o2Calibration_values[x]); + } + Serial.println(F("WUE")); + for (int x = 0; x < 10; x++) + { + Serial.print(configPage4.wueBins[x]); + Serial.print(F(", ")); + Serial.println(configPage2.wueValues[x]); + } + Serial.flush(); + #endif + break; + + case 'z': //Send 256 tooth log entries to a terminal emulator + sendToothLog(0); //Sends tooth log values as chars + break; + + case '`': //Custom 16u2 firmware is making its presence known + cmdPending = true; + + if (Serial.available() >= 1) { + configPage4.bootloaderCaps = Serial.read(); + cmdPending = false; + } + break; + + + case '?': + #ifndef SMALL_FLASH_MODE + Serial.println + (F( + "\n" + "===Command Help===\n\n" + "All commands are single character and are concatenated with their parameters \n" + "without spaces." + "Syntax: +++\n\n" + "===List of Commands===\n\n" + "A - Displays 31 bytes of currentStatus values in binary (live data)\n" + "B - Burn current map and configPage values to eeprom\n" + "C - Test COM port. Used by Tunerstudio to see whether an ECU is on a given serial \n" + " port. Returns a binary number.\n" + "N - Print new line.\n" + "P - Set current page. Syntax: P+\n" + "R - Same as A command\n" + "S - Display signature number\n" + "Q - Same as S command\n" + "V - Display map or configPage values in binary\n" + "W - Set one byte in map or configPage. Expects binary parameters. \n" + " Syntax: W++\n" + "t - Set calibration values. Expects binary parameters. Table index is either 0, \n" + " 1, or 2. Syntax: t++++\n" + "Z - Display calibration values\n" + "T - Displays 256 tooth log entries in binary\n" + "r - Displays 256 tooth log entries\n" + "U - Prepare for firmware update. The next byte received will cause the Arduino to reset.\n" + "? - Displays this help page" + )); + #endif + + break; + + default: + break; + } +} + +byte getStatusEntry(uint16_t byteNum) +{ + byte statusValue = 0; + + switch(byteNum) + { + case 0: statusValue = currentStatus.secl; break; //secl is simply a counter that increments each second. Used to track unexpected resets (Which will reset this count to 0) + case 1: statusValue = currentStatus.status1; break; //status1 Bitfield + case 2: statusValue = currentStatus.engine; break; //Engine Status Bitfield + case 3: statusValue = currentStatus.syncLossCounter; break; + case 4: statusValue = lowByte(currentStatus.MAP); break; //2 bytes for MAP + case 5: statusValue = highByte(currentStatus.MAP); break; + case 6: statusValue = (byte)(currentStatus.IAT + CALIBRATION_TEMPERATURE_OFFSET); break; //mat + case 7: statusValue = (byte)(currentStatus.coolant + CALIBRATION_TEMPERATURE_OFFSET); break; //Coolant ADC + case 8: statusValue = currentStatus.batCorrection; break; //Battery voltage correction (%) + case 9: statusValue = currentStatus.battery10; break; //battery voltage + case 10: statusValue = currentStatus.O2; break; //O2 + case 11: statusValue = currentStatus.egoCorrection; break; //Exhaust gas correction (%) + case 12: statusValue = currentStatus.iatCorrection; break; //Air temperature Correction (%) + case 13: statusValue = currentStatus.wueCorrection; break; //Warmup enrichment (%) + case 14: statusValue = lowByte(currentStatus.RPM); break; //rpm HB + case 15: statusValue = highByte(currentStatus.RPM); break; //rpm LB + case 16: statusValue = (byte)(currentStatus.AEamount >> 1); break; //TPS acceleration enrichment (%) divided by 2 (Can exceed 255) + case 17: statusValue = lowByte(currentStatus.corrections); break; //Total GammaE (%) + case 18: statusValue = highByte(currentStatus.corrections); break; //Total GammaE (%) + case 19: statusValue = currentStatus.VE1; break; //VE 1 (%) + case 20: statusValue = currentStatus.VE2; break; //VE 2 (%) + case 21: statusValue = currentStatus.afrTarget; break; + case 22: statusValue = currentStatus.tpsDOT; break; //TPS DOT + case 23: statusValue = currentStatus.advance; break; + case 24: statusValue = currentStatus.TPS; break; // TPS (0% to 100%) + + case 25: + if(currentStatus.loopsPerSecond > 60000) { currentStatus.loopsPerSecond = 60000;} + statusValue = lowByte(currentStatus.loopsPerSecond); + break; + case 26: + if(currentStatus.loopsPerSecond > 60000) { currentStatus.loopsPerSecond = 60000;} + statusValue = highByte(currentStatus.loopsPerSecond); + break; + + case 27: + currentStatus.freeRAM = freeRam(); + statusValue = lowByte(currentStatus.freeRAM); //(byte)((currentStatus.loopsPerSecond >> 8) & 0xFF); + break; + case 28: + currentStatus.freeRAM = freeRam(); + statusValue = highByte(currentStatus.freeRAM); + break; + + case 29: statusValue = (byte)(currentStatus.boostTarget >> 1); break; //Divide boost target by 2 to fit in a byte + case 30: statusValue = (byte)(currentStatus.boostDuty / 100); break; + case 31: statusValue = currentStatus.spark; break; //Spark related bitfield + + //rpmDOT must be sent as a signed integer + case 32: statusValue = lowByte(currentStatus.rpmDOT); break; + case 33: statusValue = highByte(currentStatus.rpmDOT); break; + + case 34: statusValue = currentStatus.ethanolPct; break; //Flex sensor value (or 0 if not used) + case 35: statusValue = currentStatus.flexCorrection; break; //Flex fuel correction (% above or below 100) + case 36: statusValue = currentStatus.flexIgnCorrection; break; //Ignition correction (Increased degrees of advance) for flex fuel + + case 37: statusValue = currentStatus.idleLoad; break; + case 38: statusValue = currentStatus.testOutputs; break; + + case 39: statusValue = currentStatus.O2_2; break; //O2 + case 40: statusValue = currentStatus.baro; break; //Barometer value + + case 41: statusValue = lowByte(currentStatus.canin[0]); break; + case 42: statusValue = highByte(currentStatus.canin[0]); break; + case 43: statusValue = lowByte(currentStatus.canin[1]); break; + case 44: statusValue = highByte(currentStatus.canin[1]); break; + case 45: statusValue = lowByte(currentStatus.canin[2]); break; + case 46: statusValue = highByte(currentStatus.canin[2]); break; + case 47: statusValue = lowByte(currentStatus.canin[3]); break; + case 48: statusValue = highByte(currentStatus.canin[3]); break; + case 49: statusValue = lowByte(currentStatus.canin[4]); break; + case 50: statusValue = highByte(currentStatus.canin[4]); break; + case 51: statusValue = lowByte(currentStatus.canin[5]); break; + case 52: statusValue = highByte(currentStatus.canin[5]); break; + case 53: statusValue = lowByte(currentStatus.canin[6]); break; + case 54: statusValue = highByte(currentStatus.canin[6]); break; + case 55: statusValue = lowByte(currentStatus.canin[7]); break; + case 56: statusValue = highByte(currentStatus.canin[7]); break; + case 57: statusValue = lowByte(currentStatus.canin[8]); break; + case 58: statusValue = highByte(currentStatus.canin[8]); break; + case 59: statusValue = lowByte(currentStatus.canin[9]); break; + case 60: statusValue = highByte(currentStatus.canin[9]); break; + case 61: statusValue = lowByte(currentStatus.canin[10]); break; + case 62: statusValue = highByte(currentStatus.canin[10]); break; + case 63: statusValue = lowByte(currentStatus.canin[11]); break; + case 64: statusValue = highByte(currentStatus.canin[11]); break; + case 65: statusValue = lowByte(currentStatus.canin[12]); break; + case 66: statusValue = highByte(currentStatus.canin[12]); break; + case 67: statusValue = lowByte(currentStatus.canin[13]); break; + case 68: statusValue = highByte(currentStatus.canin[13]); break; + case 69: statusValue = lowByte(currentStatus.canin[14]); break; + case 70: statusValue = highByte(currentStatus.canin[14]); break; + case 71: statusValue = lowByte(currentStatus.canin[15]); break; + case 72: statusValue = highByte(currentStatus.canin[15]); break; + + case 73: statusValue = currentStatus.tpsADC; break; + case 74: statusValue = getNextError(); break; + + case 75: statusValue = lowByte(currentStatus.PW1); break; //Pulsewidth 1 multiplied by 10 in ms. Have to convert from uS to mS. + case 76: statusValue = highByte(currentStatus.PW1); break; //Pulsewidth 1 multiplied by 10 in ms. Have to convert from uS to mS. + case 77: statusValue = lowByte(currentStatus.PW2); break; //Pulsewidth 2 multiplied by 10 in ms. Have to convert from uS to mS. + case 78: statusValue = highByte(currentStatus.PW2); break; //Pulsewidth 2 multiplied by 10 in ms. Have to convert from uS to mS. + case 79: statusValue = lowByte(currentStatus.PW3); break; //Pulsewidth 3 multiplied by 10 in ms. Have to convert from uS to mS. + case 80: statusValue = highByte(currentStatus.PW3); break; //Pulsewidth 3 multiplied by 10 in ms. Have to convert from uS to mS. + case 81: statusValue = lowByte(currentStatus.PW4); break; //Pulsewidth 4 multiplied by 10 in ms. Have to convert from uS to mS. + case 82: statusValue = highByte(currentStatus.PW4); break; //Pulsewidth 4 multiplied by 10 in ms. Have to convert from uS to mS. + + case 83: statusValue = currentStatus.status3; break; + case 84: statusValue = currentStatus.engineProtectStatus; break; + case 85: statusValue = lowByte(currentStatus.fuelLoad); break; + case 86: statusValue = highByte(currentStatus.fuelLoad); break; + case 87: statusValue = lowByte(currentStatus.ignLoad); break; + case 88: statusValue = highByte(currentStatus.ignLoad); break; + case 89: statusValue = lowByte(currentStatus.dwell); break; + case 90: statusValue = highByte(currentStatus.dwell); break; + case 91: statusValue = currentStatus.CLIdleTarget; break; + case 92: statusValue = currentStatus.mapDOT; break; + case 93: statusValue = (int8_t)currentStatus.vvt1Angle; break; + case 94: statusValue = currentStatus.vvt1TargetAngle; break; + case 95: statusValue = currentStatus.vvt1Duty; break; + case 96: statusValue = lowByte(currentStatus.flexBoostCorrection); break; + case 97: statusValue = highByte(currentStatus.flexBoostCorrection); break; + case 98: statusValue = currentStatus.baroCorrection; break; + case 99: statusValue = currentStatus.VE; break; //Current VE (%). Can be equal to VE1 or VE2 or a calculated value from both of them + case 100: statusValue = currentStatus.ASEValue; break; //Current ASE (%) + case 101: statusValue = lowByte(currentStatus.vss); break; + case 102: statusValue = highByte(currentStatus.vss); break; + case 103: statusValue = currentStatus.gear; break; + case 104: statusValue = currentStatus.fuelPressure; break; + case 105: statusValue = currentStatus.oilPressure; break; + case 106: statusValue = currentStatus.wmiPW; break; + case 107: statusValue = currentStatus.wmiEmpty; break; + case 108: statusValue = (int8_t)currentStatus.vvt2Angle; break; + case 109: statusValue = currentStatus.vvt2TargetAngle; break; + case 110: statusValue = currentStatus.vvt2Duty; break; + case 111: statusValue = currentStatus.outputsStatus; break; + case 112: statusValue = (byte)(currentStatus.fuelTemp + CALIBRATION_TEMPERATURE_OFFSET); break; //Fuel temperature from flex sensor + case 113: statusValue = currentStatus.fuelTempCorrection; break; //Fuel temperature Correction (%) + case 114: statusValue = currentStatus.advance1; break; //advance 1 (%) + case 115: statusValue = currentStatus.advance2; break; //advance 2 (%) + case 116: statusValue = currentStatus.TS_SD_Status; break; //SD card status + } + + return statusValue; + + //Each new inclusion here need to be added on speeduino.ini@L78, only list first byte of an integer and second byte as "INVALID" + //Every 2-byte integer added here should have it's lowByte index added to fsIntIndex array on globals.ino@L116 +} + +/* +This function returns 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) +{ + if (portNum == 3) + { + //CAN serial + #if defined(USE_SERIAL3) + if (cmd == 30) + { + CANSerial.write("r"); //confirm cmd type + CANSerial.write(cmd); + } + else if (cmd == 31) { CANSerial.write("A"); } //confirm cmd type + #endif + } + else + { + 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 + + for(byte x=0; x>8); + bytestosend -= Serial.write(currentStatus.secl); + bytestosend -= Serial.write(currentStatus.PW1>>8); + bytestosend -= Serial.write(currentStatus.PW1); + bytestosend -= Serial.write(currentStatus.PW2>>8); + bytestosend -= Serial.write(currentStatus.PW2); + bytestosend -= Serial.write(currentStatus.RPM>>8); + bytestosend -= Serial.write(currentStatus.RPM); + + temp = currentStatus.advance * 10; + bytestosend -= Serial.write(temp>>8); + bytestosend -= Serial.write(temp); + + bytestosend -= Serial.write(currentStatus.nSquirts); + bytestosend -= Serial.write(currentStatus.engine); + bytestosend -= Serial.write(currentStatus.afrTarget); + bytestosend -= Serial.write(currentStatus.afrTarget); // send twice so afrtgt1 == afrtgt2 + bytestosend -= Serial.write(99); // send dummy data as we don't have wbo2_en1 + bytestosend -= Serial.write(99); // send dummy data as we don't have wbo2_en2 + + temp = currentStatus.baro * 10; + bytestosend -= Serial.write(temp>>8); + bytestosend -= Serial.write(temp); + + temp = currentStatus.MAP * 10; + bytestosend -= Serial.write(temp>>8); + bytestosend -= Serial.write(temp); + + temp = currentStatus.IAT * 10; + bytestosend -= Serial.write(temp>>8); + bytestosend -= Serial.write(temp); + + temp = currentStatus.coolant * 10; + bytestosend -= Serial.write(temp>>8); + bytestosend -= Serial.write(temp); + + temp = currentStatus.TPS * 10; + bytestosend -= Serial.write(temp>>8); + bytestosend -= Serial.write(temp); + + bytestosend -= Serial.write(currentStatus.battery10>>8); + bytestosend -= Serial.write(currentStatus.battery10); + bytestosend -= Serial.write(currentStatus.O2>>8); + bytestosend -= Serial.write(currentStatus.O2); + bytestosend -= Serial.write(currentStatus.O2_2>>8); + bytestosend -= Serial.write(currentStatus.O2_2); + + bytestosend -= Serial.write(99); // knock + bytestosend -= Serial.write(99); // knock + + temp = currentStatus.egoCorrection * 10; + bytestosend -= Serial.write(temp>>8); // egocor1 + bytestosend -= Serial.write(temp); // egocor1 + bytestosend -= Serial.write(temp>>8); // egocor2 + bytestosend -= Serial.write(temp); // egocor2 + + temp = currentStatus.iatCorrection * 10; + bytestosend -= Serial.write(temp>>8); // aircor + bytestosend -= Serial.write(temp); // aircor + + temp = currentStatus.wueCorrection * 10; + bytestosend -= Serial.write(temp>>8); // warmcor + bytestosend -= Serial.write(temp); // warmcor + + bytestosend -= Serial.write(99); // accelEnrich + bytestosend -= Serial.write(99); // accelEnrich + bytestosend -= Serial.write(99); // tpsFuelCut + bytestosend -= Serial.write(99); // tpsFuelCut + bytestosend -= Serial.write(99); // baroCorrection + bytestosend -= Serial.write(99); // baroCorrection + + temp = currentStatus.corrections * 10; + bytestosend -= Serial.write(temp>>8); // gammaEnrich + bytestosend -= Serial.write(temp); // gammaEnrich + + temp = currentStatus.VE * 10; + bytestosend -= Serial.write(temp>>8); // ve1 + bytestosend -= Serial.write(temp); // ve1 + temp = currentStatus.VE2 * 10; + bytestosend -= Serial.write(temp>>8); // ve2 + bytestosend -= Serial.write(temp); // ve2 + + bytestosend -= Serial.write(99); // iacstep + bytestosend -= Serial.write(99); // iacstep + bytestosend -= Serial.write(99); // cold_adv_deg + bytestosend -= Serial.write(99); // cold_adv_deg + + temp = currentStatus.tpsDOT * 10; + bytestosend -= Serial.write(temp>>8); // TPSdot + bytestosend -= Serial.write(temp); // TPSdot + + temp = currentStatus.mapDOT * 10; + bytestosend -= Serial.write(temp >> 8); // MAPdot + bytestosend -= Serial.write(temp); // MAPdot + + temp = currentStatus.dwell * 10; + bytestosend -= Serial.write(temp>>8); // dwell + bytestosend -= Serial.write(temp); // dwell + + bytestosend -= Serial.write(99); // MAF + bytestosend -= Serial.write(99); // MAF + bytestosend -= Serial.write(currentStatus.fuelLoad*10); // fuelload + bytestosend -= Serial.write(99); // fuelcor + bytestosend -= Serial.write(99); // fuelcor + bytestosend -= Serial.write(99); // portStatus + + temp = currentStatus.advance1 * 10; + bytestosend -= Serial.write(temp>>8); + bytestosend -= Serial.write(temp); + temp = currentStatus.advance2 * 10; + bytestosend -= Serial.write(temp>>8); + bytestosend -= Serial.write(temp); + + for(int i = 0; i < bytestosend; i++) + { + // send dummy data to fill remote's buffer + Serial.write(99); + } +} + +namespace { + + void send_raw_entity(const page_iterator_t &entity) + { + Serial.write((byte *)entity.pData, entity.size); + } + + inline void send_table_values(table_row_iterator_t it) + { + while (!at_end(it)) + { + auto row = get_row(it); + Serial.write(row.pValue, row.pEnd-row.pValue); + advance_row(it); + } + } + + inline void send_table_axis(table_axis_iterator_t it) + { + while (!at_end(it)) + { + Serial.write(get_value(it)); + it = advance_axis(it); + } + } + + void send_table_entity(table3D *pTable) + { + send_table_values(rows_begin(pTable)); + send_table_axis(x_begin(pTable)); + send_table_axis(y_begin(pTable)); + } + + void send_entity(const page_iterator_t &entity) + { + switch (entity.type) + { + case Raw: + return send_raw_entity(entity); + break; + + case Table: + return send_table_entity(entity.pTable); + break; + + case NoEntity: + // No-op + break; + + default: + abort(); + break; + } + } +} + +/** + * @brief Packs the data within the current page (As set with the 'P' command) into a buffer and sends it. + * + * Note that some translation of the data is required to lay it out in the way Megasqurit / TunerStudio expect it + * Data is sent in binary format, as defined by in each page in the ini + */ +void sendPage() +{ + page_iterator_t entity = page_begin(currentPage); + + while (entity.type!=End) + { + send_entity(entity); + entity = advance(entity); + } +} + +namespace { + + // Prints each element in the range [first,last) + void serial_println_range(const byte *first, const byte *last) + { + while (first!=last) + { + Serial.println(*first); + ++first; + } + } + void serial_println_range(const uint16_t *first, const uint16_t *last) + { + while (first!=last) + { + Serial.println(*first); + ++first; + } + } + + void serial_print_space_delimited(const byte *first, const byte *last) + { + while (first!=last) + { + Serial.print(*first);// This displays the values horizantially on the screen + Serial.print(F(" ")); + ++first; + } + Serial.println(); + } + #define serial_print_space_delimited_array(array) serial_print_space_delimited(array, _end_range_address(array)) + + void serial_print_prepadding(byte value) + { + if (value < 100) + { + Serial.print(F(" ")); + if (value < 10) + { + Serial.print(F(" ")); + } + } + } + + void serial_print_prepadded_value(byte value) + { + serial_print_prepadding(value); + Serial.print(value); + Serial.print(F(" ")); + } + + void print_row(const table_axis_iterator_t &y_it, table_row_t row) + { + serial_print_prepadded_value(get_value(y_it)); + + while (!at_end(row)) + { + serial_print_prepadded_value(*row.pValue++); + } + Serial.println(); + } + + void print_x_axis(const table3D ¤tTable) + { + Serial.print(F(" ")); + + auto x_it = x_begin(¤tTable); + while(!at_end(x_it)) + { + serial_print_prepadded_value(get_value(x_it)); + advance_axis(x_it); + } + } + + void serial_print_3dtable(const table3D ¤tTable) + { + auto y_it = y_begin(¤tTable); + auto row_it = rows_begin(¤tTable); + + while (!at_end(row_it)) + { + print_row(y_it, get_row(row_it)); + advance_axis(y_it); + advance_row(row_it); + } + + print_x_axis(currentTable); + Serial.println(); + } +} + +/** + * @brief Similar to sendPage(), however data is sent in human readable format + * + * This is used for testing only (Not used by TunerStudio) in order to see current map and config data without the need for TunerStudio. + */ +void sendPageASCII() +{ + switch (currentPage) + { + case veMapPage: + Serial.println(F("\nVE Map")); + serial_print_3dtable(fuelTable); + break; + + case veSetPage: + Serial.println(F("\nPg 2 Cfg")); + // The following loop displays in human readable form of all byte values in config page 1 up to but not including the first array. + serial_println_range((byte *)&configPage2, configPage2.wueValues); + serial_print_space_delimited_array(configPage2.wueValues); + // This displays all the byte values between the last array up to but not including the first unsigned int on config page 1 + serial_println_range(_end_range_byte_address(configPage2.wueValues), (byte*)&configPage2.injAng); + // The following loop displays four unsigned ints + serial_println_range(configPage2.injAng, configPage2.injAng + _countof(configPage2.injAng)); + // Following loop displays byte values between the unsigned ints + serial_println_range(_end_range_byte_address(configPage2.injAng), (byte*)&configPage2.mapMax); + Serial.println(configPage2.mapMax); + // Following loop displays remaining byte values of the page + serial_println_range(&configPage2.fpPrime, (byte *)&configPage2 + sizeof(configPage2)); + break; + + case ignMapPage: + Serial.println(F("\nIgnition Map")); + serial_print_3dtable(ignitionTable); + break; + + case ignSetPage: + Serial.println(F("\nPg 4 Cfg")); + Serial.println(configPage4.triggerAngle);// configPsge2.triggerAngle is an int so just display it without complication + // Following loop displays byte values after that first int up to but not including the first array in config page 2 + serial_println_range((byte*)&configPage4.FixAng, configPage4.taeBins); + serial_print_space_delimited_array(configPage4.taeBins); + serial_print_space_delimited_array(configPage4.taeValues); + serial_print_space_delimited_array(configPage4.wueBins); + Serial.println(configPage4.dwellLimit);// Little lonely byte stuck between two arrays. No complications just display it. + serial_print_space_delimited_array(configPage4.dwellCorrectionValues); + serial_println_range(_end_range_byte_address(configPage4.dwellCorrectionValues), (byte *)&configPage4 + sizeof(configPage4)); + break; + + case afrMapPage: + Serial.println(F("\nAFR Map")); + serial_print_3dtable(afrTable); + break; + + case afrSetPage: + Serial.println(F("\nPg 6 Config")); + serial_println_range((byte *)&configPage6, configPage6.voltageCorrectionBins); + serial_print_space_delimited_array(configPage6.voltageCorrectionBins); + serial_print_space_delimited_array(configPage6.injVoltageCorrectionValues); + serial_print_space_delimited_array(configPage6.airDenBins); + serial_print_space_delimited_array(configPage6.airDenRates); + serial_println_range(_end_range_byte_address(configPage6.airDenRates), configPage6.iacCLValues); + serial_print_space_delimited_array(configPage6.iacCLValues); + serial_print_space_delimited_array(configPage6.iacOLStepVal); + serial_print_space_delimited_array(configPage6.iacOLPWMVal); + serial_print_space_delimited_array(configPage6.iacBins); + serial_print_space_delimited_array(configPage6.iacCrankSteps); + serial_print_space_delimited_array(configPage6.iacCrankDuty); + serial_print_space_delimited_array(configPage6.iacCrankBins); + // Following loop is for remaining byte value of page + serial_println_range(_end_range_byte_address(configPage6.iacCrankBins), (byte *)&configPage6 + sizeof(configPage6)); + break; + + case boostvvtPage: + Serial.println(F("\nBoost Map")); + serial_print_3dtable(boostTable); + Serial.println(F("\nVVT Map")); + serial_print_3dtable(vvtTable); + break; + + case seqFuelPage: + Serial.println(F("\nTrim 1 Table")); + serial_print_3dtable(trim1Table); + break; + + case canbusPage: + Serial.println(F("\nPage 9 Cfg")); + serial_println_range((byte *)&configPage9, (byte *)&configPage9 + sizeof(configPage9)); + break; + + case fuelMap2Page: + Serial.println(F("\n2nd Fuel Map")); + serial_print_3dtable(fuelTable2); + break; + + case ignMap2Page: + Serial.println(F("\n2nd Ignition Map")); + serial_print_3dtable(ignitionTable2); + break; + + case warmupPage: + case progOutsPage: + default: + #ifndef SMALL_FLASH_MODE + Serial.println(F("\nPage has not been implemented yet")); + #endif + break; + } +} + + +/** + * @brief Processes an incoming stream of calibration data from TunerStudio. Result is store in EEPROM and memory + * + * @param tableID Which calibration table to process. 0 = Coolant Sensor. 1 = IAT Sensor. 2 = O2 Sensor. + */ +void receiveCalibration(byte tableID) +{ + void* pnt_TargetTable_values; //Pointer that will be used to point to the required target table values + uint16_t* pnt_TargetTable_bins; //Pointer that will be used to point to the required target table bins + int OFFSET, DIVISION_FACTOR; + + switch (tableID) + { + case 0: + //coolant table + pnt_TargetTable_values = (uint16_t *)&cltCalibration_values; + pnt_TargetTable_bins = (uint16_t *)&cltCalibration_bins; + OFFSET = CALIBRATION_TEMPERATURE_OFFSET; // + DIVISION_FACTOR = 10; + break; + case 1: + //Inlet air temp table + pnt_TargetTable_values = (uint16_t *)&iatCalibration_values; + pnt_TargetTable_bins = (uint16_t *)&iatCalibration_bins; + OFFSET = CALIBRATION_TEMPERATURE_OFFSET; + DIVISION_FACTOR = 10; + break; + case 2: + //O2 table + //pnt_TargetTable = (byte *)&o2CalibrationTable; + pnt_TargetTable_values = (uint8_t *)&o2Calibration_values; + pnt_TargetTable_bins = (uint16_t *)&o2Calibration_bins; + OFFSET = 0; + DIVISION_FACTOR = 1; + break; + + default: + OFFSET = 0; + pnt_TargetTable_values = (uint16_t *)&iatCalibration_values; + pnt_TargetTable_bins = (uint16_t *)&iatCalibration_bins; + DIVISION_FACTOR = 10; + break; //Should never get here, but if we do, just fail back to main loop + } + + int16_t tempValue; + byte tempBuffer[2]; + + if(tableID == 2) + { + //O2 calibration. Comes through as 1024 8-bit values of which we use every 32nd + for (int x = 0; x < 1024; x++) + { + while ( Serial.available() < 1 ) {} + tempValue = Serial.read(); + + if( (x % 32) == 0) + { + ((uint8_t*)pnt_TargetTable_values)[(x/32)] = (byte)tempValue; //O2 table stores 8 bit values + pnt_TargetTable_bins[(x/32)] = (x); + } + + } + } + else + { + //Temperature calibrations are sent as 32 16-bit values + for (uint16_t x = 0; x < 32; x++) + { + while ( Serial.available() < 2 ) {} + tempBuffer[0] = Serial.read(); + tempBuffer[1] = Serial.read(); + + tempValue = (int16_t)(word(tempBuffer[1], tempBuffer[0])); //Combine the 2 bytes into a single, signed 16-bit value + tempValue = div(tempValue, DIVISION_FACTOR).quot; //TS sends values multipled 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 + 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 * 32U); + writeCalibration(); + } + } + + writeCalibration(); +} + +/* +Send 256 tooth log entries + * 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(byte startOffset) +{ + //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 + { + 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(highByte(toothHistory[toothHistorySerialIndex])); + //Serial.write(lowByte(toothHistory[toothHistorySerialIndex])); + Serial.write(toothHistory[toothHistorySerialIndex] >> 24); + Serial.write(toothHistory[toothHistorySerialIndex] >> 16); + Serial.write(toothHistory[toothHistorySerialIndex] >> 8); + Serial.write(toothHistory[toothHistorySerialIndex]); + + if(toothHistorySerialIndex == (TOOTH_LOG_BUFFER-1)) { toothHistorySerialIndex = 0; } + else { toothHistorySerialIndex++; } + } + BIT_CLEAR(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY); + cmdPending = false; + toothLogSendInProgress = false; + } + else + { + //TunerStudio has timed out, send a LOG of all 0s + for(int x = 0; x < (4*TOOTH_LOG_SIZE); x++) + { + Serial.write(static_cast(0x00)); //GCC9 fix + } + cmdPending = false; + } +} + +void sendCompositeLog(byte startOffset) +{ + if (BIT_CHECK(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY)) //Sanity check. Flagging system means this should always be true + { + if(startOffset == 0) { inProgressCompositeTime = 0; } + 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; + return; + } + + inProgressCompositeTime += toothHistory[toothHistorySerialIndex]; //This combined runtime (in us) that the log was going for by this record) + + Serial.write(inProgressCompositeTime >> 24); + Serial.write(inProgressCompositeTime >> 16); + Serial.write(inProgressCompositeTime >> 8); + Serial.write(inProgressCompositeTime); + + Serial.write(compositeLogHistory[toothHistorySerialIndex]); //The status byte (Indicates the trigger edge, whether it was a pri/sec pulse, the sync status) + + if(toothHistorySerialIndex == (TOOTH_LOG_BUFFER-1)) { toothHistorySerialIndex = 0; } + else { toothHistorySerialIndex++; } + } + BIT_CLEAR(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY); + toothHistoryIndex = 0; + toothHistorySerialIndex = 0; + compositeLastToothTime = 0; + cmdPending = false; + compositeLogSendInProgress = false; + inProgressCompositeTime = 0; + } + else + { + //TunerStudio has timed out, send a LOG of all 0s + for(int x = 0; x < (5*TOOTH_LOG_SIZE); x++) + { + Serial.write(static_cast(0x00)); //GCC9 fix + } + cmdPending = false; + } +} + +void testComm() +{ + Serial.write(1); + return; +} diff --git a/speeduino/comms.h b/speeduino/comms.h index 79740aae..82991ed6 100644 --- a/speeduino/comms.h +++ b/speeduino/comms.h @@ -9,21 +9,6 @@ #ifndef COMMS_H #define COMMS_H -//These are the page numbers that the Tuner Studio serial protocol uses to transverse the different map and config pages. -#define veMapPage 2 -#define veSetPage 1 //Note that this and the veMapPage were swapped in Feb 2019 as the 'algorithm' field must be declared in the ini before it's used in the fuel table -#define ignMapPage 3 -#define ignSetPage 4//Config Page 2 -#define afrMapPage 5 -#define afrSetPage 6//Config Page 3 -#define boostvvtPage 7 -#define seqFuelPage 8 -#define canbusPage 9//Config Page 9 -#define warmupPage 10 //Config Page 10 -#define fuelMap2Page 11 -#define wmiMapPage 12 -#define progOutsPage 13 -#define ignMap2Page 14 //Hardcoded TunerStudio addresses/commands for various SD/RTC commands #define SD_READWRITE_PAGE 0x11 @@ -50,45 +35,26 @@ #define SD_RTC_READ_LENGTH 0x0800 -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 chucnk 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; -byte inProgressLength; -uint32_t inProgressCompositeTime; -bool serialInProgress = false; -bool toothLogSendInProgress = false; -bool compositeLogSendInProgress = false; - -const char pageTitles[] PROGMEM //This is being stored in the avr flash instead of SRAM which there is not very much of - { - "\nVE Map\0"//This is an alternative to using a 2D array which would waste space because of the different lengths of the strings - "\nPg 1 Config\0"// 21-The configuration page titles' indexes are found by counting the chars - "\nIgnition Map\0"//35-The map page titles' indexes are put into a var called currentTitleIndex. That represents the first char of each string. - "\nPg 2 Config\0" //48 - "\nAFR Map\0" //56 - "\nPg 3 Config\0" //69 - "\nPg 4 Config\0" //82 - "\nBoost Map\0" //93 - "\nVVT Map\0"//102-No need to put a trailing null because it's the last string and the compliler does it for you. - "\nPg 10 Config\0"//116 - "\n2nd Fuel Map\0"//130 - "\nWMI Map\0"//139 - "\nPrgm IO\0"//148 - "\n2nd Ignition Map" - }; +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 chucnk 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; +extern byte inProgressLength; +extern uint32_t inProgressCompositeTime; +extern bool serialInProgress; +extern bool toothLogSendInProgress; +extern bool compositeLogSendInProgress; void command();//This is the heart of the Command Line Interpeter. All that needed to be done was to make it human readable. void sendValues(uint16_t, uint16_t,byte, byte); void sendValuesLegacy(); -void receiveValue(uint16_t, byte); void saveConfig(); void sendPage(); void sendPageASCII(); @@ -97,7 +63,6 @@ void sendToothLog(uint8_t); void testComm(); void commandButtons(int16_t); void sendCompositeLog(uint8_t); -byte getPageValue(byte, uint16_t); byte getStatusEntry(uint16_t); #endif // COMMS_H diff --git a/speeduino/comms.ino b/speeduino/comms.ino deleted file mode 100644 index 798cb802..00000000 --- a/speeduino/comms.ino +++ /dev/null @@ -1,2316 +0,0 @@ -/* -Speeduino - Simple engine management for the Arduino Mega 2560 platform -Copyright (C) Josh Stewart -A full copy of the license may be found in the projects root directory -*/ -#include "globals.h" -#include "comms.h" -#include "cancomms.h" -#include "storage.h" -#include "maths.h" -#include "utilities.h" -#include "decoders.h" -#include "TS_CommandButtonHandler.h" -#include "errors.h" -#include "src/FastCRC/FastCRC.h" -#ifdef RTC_ENABLED - #include "rtc_common.h" -#endif - -/* - Processes the data on the serial buffer. - Can be either a new command or a continuation of one that is already in progress: - * cmdPending = If a command has started but is wairing on further data to complete - * chunkPending = Specifically for the new receive value method where TS will send a known number of contiguous bytes to be written to a table -*/ -void command() -{ - if (cmdPending == false) { currentCommand = Serial.read(); } - - switch (currentCommand) - { - - case 'a': - cmdPending = true; - - 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; - } - break; - - case 'A': // send x bytes of realtime values - sendValues(0, LOG_ENTRY_SIZE, 0x31, 0); //send values to serial0 - break; - - - case 'B': // Burn current values to eeprom - writeAllConfig(); - break; - - case 'b': // New EEPROM burn command to only burn a single page at a time - cmdPending = true; - - if (Serial.available() >= 2) - { - Serial.read(); //Ignore the first table value, it's always 0 - writeConfig(Serial.read()); - cmdPending = false; - } - break; - - case 'C': // test communications. This is used by Tunerstudio to see whether there is an ECU on a given serial port - testComm(); - break; - - case 'c': //Send the current loops/sec value - Serial.write(lowByte(currentStatus.loopsPerSecond)); - Serial.write(highByte(currentStatus.loopsPerSecond)); - break; - - case 'd': // Send a CRC32 hash of a given page - cmdPending = true; - - if (Serial.available() >= 2) - { - Serial.read(); //Ignore the first byte value, it's always 0 - uint32_t CRC32_val = calculateCRC32( Serial.read() ); - - //Split the 4 bytes of the CRC32 value into individual bytes and send - Serial.write( ((CRC32_val >> 24) & 255) ); - Serial.write( ((CRC32_val >> 16) & 255) ); - Serial.write( ((CRC32_val >> 8) & 255) ); - Serial.write( (CRC32_val & 255) ); - - cmdPending = false; - } - break; - - case 'E': // receive command button commands - cmdPending = true; - - 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; - } - } - break; - - case 'F': // send serial protocol version - Serial.print(F("001")); - 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; - toothHistorySerialIndex = 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 ); - - 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 ); - 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; - toothHistorySerialIndex = 0; - compositeLastToothTime = 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 ); - - 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 ); - break; - - case 'L': // List the contents of current page in human readable form - #ifndef SMALL_FLASH_MODE - sendPageASCII(); - #endif - break; - - case 'm': //Send the current free memory - currentStatus.freeRAM = freeRam(); - Serial.write(lowByte(currentStatus.freeRAM)); - Serial.write(highByte(currentStatus.freeRAM)); - break; - - case 'N': // Displays a new line. Like pushing enter in a text editor - Serial.println(); - break; - - 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; - - if (Serial.available() > 0) - { - currentPage = Serial.read(); - //This converts the ascii number char into binary. Note that this will break everyything if there are ever more than 48 pages (48 = asci code for '0') - if ((currentPage >= '0') && (currentPage <= '9')) // 0 - 9 - { - currentPage -= 48; - } - else if ((currentPage >= 'a') && (currentPage <= 'f')) // 10 - 15 - { - currentPage -= 87; - } - else if ((currentPage >= 'A') && (currentPage <= 'F')) - { - 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; - } - break; - - /* - * New method for sending page values - */ - case 'p': - cmdPending = true; - - //6 bytes required: - //2 - Page identifier - //2 - offset - //2 - Length - if(Serial.available() >= 6) - { - byte offset1, offset2, length1, length2; - int length; - byte tempPage; - - Serial.read(); // First byte of the page identifier can be ignored. It's always 0 - tempPage = Serial.read(); - //currentPage = 1; - offset1 = Serial.read(); - offset2 = Serial.read(); - valueOffset = word(offset2, offset1); - length1 = Serial.read(); - length2 = Serial.read(); - length = word(length2, length1); - for(int i = 0; i < length; i++) - { - Serial.write( getPageValue(tempPage, valueOffset + i) ); - } - - cmdPending = false; - } - break; - - case 'Q': // send code version - Serial.print(F("speeduino 202104-dev")); - break; - - case 'r': //New format for the optimised OutputChannels - cmdPending = true; - byte cmd; - if (Serial.available() >= 6) - { - tsCanId = Serial.read(); //Read the $tsCanId - cmd = Serial.read(); // read the command - - uint16_t offset, length; - byte tmp; - tmp = Serial.read(); - offset = word(Serial.read(), tmp); - tmp = Serial.read(); - length = word(Serial.read(), tmp); - - - if(cmd == 0x30) //Send output channels command 0x30 is 48dec - { - sendValues(offset, length, cmd, 0); - } -#ifdef RTC_ENABLED - else if(cmd == SD_RTC_PAGE) //Request to read SD card RTC - { - /* - uint16_t packetSize = 2 + 1 + length + 4; - packetSize = 15; - Serial.write(highByte(packetSize)); - Serial.write(lowByte(packetSize)); - byte packet[length+1]; - - packet[0] = 0; - packet[1] = length; - packet[2] = 0; - packet[3] = 0; - packet[4] = 0; - packet[5] = 0; - packet[6] = 0; - packet[7] = 0; - packet[8] = 0; - Serial.write(packet, 9); - - FastCRC32 CRC32; - uint32_t CRC32_val = CRC32.crc32((byte *)packet, sizeof(packet) );; - - //Split the 4 bytes of the CRC32 value into individual bytes and send - Serial.write( ((CRC32_val >> 24) & 255) ); - Serial.write( ((CRC32_val >> 16) & 255) ); - Serial.write( ((CRC32_val >> 8) & 255) ); - Serial.write( (CRC32_val & 255) ); - */ - Serial.write(rtc_getSecond()); //Seconds - Serial.write(rtc_getMinute()); //Minutes - Serial.write(rtc_getHour()); //Hours - Serial.write(rtc_getDOW()); //Day of Week - Serial.write(rtc_getDay()); //Date - Serial.write(rtc_getMonth()); //Month - Serial.write(lowByte(rtc_getYear())); //Year - NOTE 2 bytes - Serial.write(highByte(rtc_getYear())); //Year - - } - else if(cmd == SD_READWRITE_PAGE) //Request SD card extended parameters - { - //SD read commands use the offset and length fields to indicate the request type - if((offset == SD_READ_STAT_OFFSET) && (length == SD_READ_STAT_LENGTH)) - { - //Read the status of the SD card - - //Serial.write(0); - - - //Serial.write(currentStatus.TS_SD_Status); - Serial.write((uint8_t)5); - Serial.write((uint8_t)0); - - //All other values are 2 bytes - Serial.write((uint8_t)2); //Sector size - Serial.write((uint8_t)0); //Sector size - - //Max blocks (4 bytes) - Serial.write((uint8_t)0); - Serial.write((uint8_t)0x20); //1gb dummy card - Serial.write((uint8_t)0); - Serial.write((uint8_t)0); - - //Max roots (Number of files) - Serial.write((uint8_t)0); - Serial.write((uint8_t)1); - - //Dir Start (4 bytes) - Serial.write((uint8_t)0); //Dir start lower 2 bytes - Serial.write((uint8_t)0); //Dir start lower 2 bytes - Serial.write((uint8_t)0); //Dir start lower 2 bytes - Serial.write((uint8_t)0); //Dir start lower 2 bytes - - //Unkown purpose for last 2 bytes - Serial.write((uint8_t)0); //Dir start lower 2 bytes - Serial.write((uint8_t)0); //Dir start lower 2 bytes - - /* - Serial.write(lowByte(23)); - Serial.write(highByte(23)); - - byte packet[17]; - packet[0] = 0; - packet[1] = 5; - packet[2] = 0; - - packet[3] = 2; - packet[4] = 0; - - packet[5] = 0; - packet[6] = 0x20; - packet[7] = 0; - packet[8] = 0; - - packet[9] = 0; - packet[10] = 1; - - packet[11] = 0; - packet[12] = 0; - packet[13] = 0; - packet[14] = 0; - - packet[15] = 0; - packet[16] = 0; - - Serial.write(packet, 17); - FastCRC32 CRC32; - uint32_t CRC32_val = CRC32.crc32((byte *)packet, sizeof(packet) );; - - //Split the 4 bytes of the CRC32 value into individual bytes and send - Serial.write( ((CRC32_val >> 24) & 255) ); - Serial.write( ((CRC32_val >> 16) & 255) ); - Serial.write( ((CRC32_val >> 8) & 255) ); - Serial.write( (CRC32_val & 255) ); - */ - - } - //else if(length == 0x202) - { - //File info - } - } - else if(cmd == 0x14) - { - //Fetch data from file - } -#endif - else - { - //No other r/ commands should be called - } - cmdPending = false; - } - break; - - case 'S': // send code version - Serial.print(F("Speeduino 2021.04-dev")); - 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 - //6 bytes required: - //2 - Page identifier - //2 - offset - //2 - Length - cmdPending = true; - if(Serial.available() >= 6) - { - Serial.read(); // First byte of the page identifier can be ignored. It's always 0 - Serial.read(); // First byte of the page identifier can be ignored. It's always 0 - Serial.read(); // First byte of the page identifier can be ignored. It's always 0 - Serial.read(); // First byte of the page identifier can be ignored. It's always 0 - Serial.read(); // First byte of the page identifier can be ignored. It's always 0 - Serial.read(); // First byte of the page identifier can be ignored. It's always 0 - - if(currentStatus.toothLogEnabled == true) { sendToothLog(0); } //Sends tooth log values as ints - else if (currentStatus.compositeLogEnabled == true) { sendCompositeLog(0); } - - cmdPending = false; - } - - - - break; - - case 't': // receive new Calibration info. Command structure: "t", . - byte tableID; - //byte canID; - - //The first 2 bytes sent represent the canID and tableID - while (Serial.available() == 0) { } - tableID = Serial.read(); //Not currently used for anything - - receiveCalibration(tableID); //Receive new values and store in memory - writeCalibration(); //Store received values in EEPROM - - break; - - case 'U': //User wants to reset the Arduino (probably for FW update) - if (resetControl != RESET_CONTROL_DISABLED) - { - #ifndef SMALL_FLASH_MODE - if (!cmdPending) { Serial.println(F("Comms halted. Next byte will reset the Arduino.")); } - #endif - - while (Serial.available() == 0) { } - digitalWrite(pinResetControl, LOW); - } - else - { - #ifndef SMALL_FLASH_MODE - if (!cmdPending) { Serial.println(F("Reset control is currently disabled.")); } - #endif - } - break; - - case 'V': // send VE table and constants in binary - sendPage(); - break; - - case 'W': // receive new VE obr constant at 'W'++ - cmdPending = true; - - if (isMap) - { - if(Serial.available() >= 3) // 1 additional byte is required on the MAP pages which are larger than 255 bytes - { - byte offset1, offset2; - offset1 = Serial.read(); - offset2 = Serial.read(); - valueOffset = word(offset2, offset1); - receiveValue(valueOffset, Serial.read()); - cmdPending = false; - } - } - else - { - if(Serial.available() >= 2) - { - valueOffset = Serial.read(); - receiveValue(valueOffset, Serial.read()); - cmdPending = false; - } - } - - break; - - case 'M': - cmdPending = true; - - if(chunkPending == false) - { - //This means it's a new request - //7 bytes required: - //2 - Page identifier - //2 - offset - //2 - Length - //1 - 1st New value - if(Serial.available() >= 7) - { - byte offset1, offset2, length1, length2; - - Serial.read(); // First byte of the page identifier can be ignored. It's always 0 - currentPage = Serial.read(); - //currentPage = 1; - offset1 = Serial.read(); - offset2 = Serial.read(); - valueOffset = word(offset2, offset1); - length1 = Serial.read(); - length2 = Serial.read(); - chunkSize = word(length2, length1); - - //Regular page data - chunkPending = true; - chunkComplete = 0; - } - } - //This CANNOT be an else of the above if statement as chunkPending gets set to true above - if(chunkPending == true) - { - while( (Serial.available() > 0) && (chunkComplete < chunkSize) ) - { - receiveValue( (valueOffset + chunkComplete), Serial.read()); - chunkComplete++; - } - if(chunkComplete >= chunkSize) { cmdPending = false; chunkPending = false; } - } - break; - - case 'w': - if(Serial.available() >= 7) - { - byte offset1, offset2, length1, length2; - - Serial.read(); // First byte of the page identifier can be ignored. It's always 0 - currentPage = Serial.read(); - //currentPage = 1; - offset1 = Serial.read(); - offset2 = Serial.read(); - valueOffset = word(offset2, offset1); - length1 = Serial.read(); - length2 = Serial.read(); - chunkSize = word(length2, length1); - } -#ifdef RTC_ENABLED - if(currentPage == SD_READWRITE_PAGE) - { - cmdPending = false; - - //Reserved for the SD card settings. Appears to be hardcoded into TS. Flush the final byte in the buffer as its not used for now - Serial.read(); - if((valueOffset == SD_WRITE_DO_OFFSET) && (chunkSize == SD_WRITE_DO_LENGTH)) - { - /* - SD DO command. Single byte of data where the commands are: - 0 Reset - 1 Reset - 2 Stop logging - 3 Start logging - 4 Load status variable - 5 Init SD card - */ - Serial.read(); - } - else if((valueOffset == SD_WRITE_SEC_OFFSET) && (chunkSize == SD_WRITE_SEC_LENGTH)) - { - //SD write sector command - } - else if((valueOffset == SD_ERASEFILE_OFFSET) && (chunkSize == SD_ERASEFILE_LENGTH)) - { - //Erase file command - //First 4 bytes are the log number in ASCII - /* - char log1 = Serial.read(); - char log2 = Serial.read(); - char log3 = Serial.read(); - char log4 = Serial.read(); - */ - - //Next 2 bytes are the directory block no - Serial.read(); - Serial.read(); - } - else if((valueOffset == SD_SPD_TEST_OFFSET) && (chunkSize == SD_SPD_TEST_LENGTH)) - { - //Perform a speed test on the SD card - //First 4 bytes are the sector number to write to - Serial.read(); - Serial.read(); - Serial.read(); - Serial.read(); - - //Last 4 bytes are the number of sectors to test - Serial.read(); - Serial.read(); - Serial.read(); - Serial.read(); - } - } - else if(currentPage == SD_RTC_PAGE) - { - cmdPending = false; - //Used for setting RTC settings - if((valueOffset == SD_RTC_WRITE_OFFSET) && (chunkSize == SD_RTC_WRITE_LENGTH)) - { - //Set the RTC date/time - //Need to ensure there are 9 more bytes with the new values - while(Serial.available() < 9) {} //Terrible hack, but RTC values should not be set with the engine running anyway - byte second = Serial.read(); - byte minute = Serial.read(); - byte hour = Serial.read(); - //byte dow = Serial.read(); - Serial.read(); // This is the day of week value, which is currently unused - byte day = Serial.read(); - byte month = Serial.read(); - uint16_t year = Serial.read(); - year = word(Serial.read(), year); - Serial.read(); //Final byte is unused (Always has value 0x5a) - rtc_setTime(second, minute, hour, day, month, year); - } - } -#endif - break; - - case 'Z': //Totally non-standard testing function. Will be removed once calibration testing is completed. This function takes 1.5kb of program space! :S - #ifndef SMALL_FLASH_MODE - Serial.println(F("Coolant")); - for (int x = 0; x < 32; x++) - { - Serial.print(cltCalibration_bins[x]); - Serial.print(", "); - Serial.println(cltCalibration_values[x]); - } - Serial.println(F("Inlet temp")); - for (int x = 0; x < 32; x++) - { - Serial.print(iatCalibration_bins[x]); - Serial.print(", "); - Serial.println(iatCalibration_values[x]); - } - Serial.println(F("O2")); - for (int x = 0; x < 32; x++) - { - Serial.print(o2Calibration_bins[x]); - Serial.print(", "); - Serial.println(o2Calibration_values[x]); - } - Serial.println(F("WUE")); - for (int x = 0; x < 10; x++) - { - Serial.print(configPage4.wueBins[x]); - Serial.print(F(", ")); - Serial.println(configPage2.wueValues[x]); - } - Serial.flush(); - #endif - break; - - case 'z': //Send 256 tooth log entries to a terminal emulator - sendToothLog(0); //Sends tooth log values as chars - break; - - case '`': //Custom 16u2 firmware is making its presence known - cmdPending = true; - - if (Serial.available() >= 1) { - configPage4.bootloaderCaps = Serial.read(); - cmdPending = false; - } - break; - - - case '?': - #ifndef SMALL_FLASH_MODE - Serial.println - (F( - "\n" - "===Command Help===\n\n" - "All commands are single character and are concatenated with their parameters \n" - "without spaces." - "Syntax: +++\n\n" - "===List of Commands===\n\n" - "A - Displays 31 bytes of currentStatus values in binary (live data)\n" - "B - Burn current map and configPage values to eeprom\n" - "C - Test COM port. Used by Tunerstudio to see whether an ECU is on a given serial \n" - " port. Returns a binary number.\n" - "N - Print new line.\n" - "P - Set current page. Syntax: P+\n" - "R - Same as A command\n" - "S - Display signature number\n" - "Q - Same as S command\n" - "V - Display map or configPage values in binary\n" - "W - Set one byte in map or configPage. Expects binary parameters. \n" - " Syntax: W++\n" - "t - Set calibration values. Expects binary parameters. Table index is either 0, \n" - " 1, or 2. Syntax: t++++\n" - "Z - Display calibration values\n" - "T - Displays 256 tooth log entries in binary\n" - "r - Displays 256 tooth log entries\n" - "U - Prepare for firmware update. The next byte received will cause the Arduino to reset.\n" - "? - Displays this help page" - )); - #endif - - break; - - default: - break; - } -} - -byte getStatusEntry(uint16_t byteNum) -{ - byte statusValue = 0; - - switch(byteNum) - { - case 0: statusValue = currentStatus.secl; break; //secl is simply a counter that increments each second. Used to track unexpected resets (Which will reset this count to 0) - case 1: statusValue = currentStatus.status1; break; //status1 Bitfield - case 2: statusValue = currentStatus.engine; break; //Engine Status Bitfield - case 3: statusValue = currentStatus.syncLossCounter; break; - case 4: statusValue = lowByte(currentStatus.MAP); break; //2 bytes for MAP - case 5: statusValue = highByte(currentStatus.MAP); break; - case 6: statusValue = (byte)(currentStatus.IAT + CALIBRATION_TEMPERATURE_OFFSET); break; //mat - case 7: statusValue = (byte)(currentStatus.coolant + CALIBRATION_TEMPERATURE_OFFSET); break; //Coolant ADC - case 8: statusValue = currentStatus.batCorrection; break; //Battery voltage correction (%) - case 9: statusValue = currentStatus.battery10; break; //battery voltage - case 10: statusValue = currentStatus.O2; break; //O2 - case 11: statusValue = currentStatus.egoCorrection; break; //Exhaust gas correction (%) - case 12: statusValue = currentStatus.iatCorrection; break; //Air temperature Correction (%) - case 13: statusValue = currentStatus.wueCorrection; break; //Warmup enrichment (%) - case 14: statusValue = lowByte(currentStatus.RPM); break; //rpm HB - case 15: statusValue = highByte(currentStatus.RPM); break; //rpm LB - case 16: statusValue = (byte)(currentStatus.AEamount >> 1); break; //TPS acceleration enrichment (%) divided by 2 (Can exceed 255) - case 17: statusValue = lowByte(currentStatus.corrections); break; //Total GammaE (%) - case 18: statusValue = highByte(currentStatus.corrections); break; //Total GammaE (%) - case 19: statusValue = currentStatus.VE1; break; //VE 1 (%) - case 20: statusValue = currentStatus.VE2; break; //VE 2 (%) - case 21: statusValue = currentStatus.afrTarget; break; - case 22: statusValue = currentStatus.tpsDOT; break; //TPS DOT - case 23: statusValue = currentStatus.advance; break; - case 24: statusValue = currentStatus.TPS; break; // TPS (0% to 100%) - - case 25: - if(currentStatus.loopsPerSecond > 60000) { currentStatus.loopsPerSecond = 60000;} - statusValue = lowByte(currentStatus.loopsPerSecond); - break; - case 26: - if(currentStatus.loopsPerSecond > 60000) { currentStatus.loopsPerSecond = 60000;} - statusValue = highByte(currentStatus.loopsPerSecond); - break; - - case 27: - currentStatus.freeRAM = freeRam(); - statusValue = lowByte(currentStatus.freeRAM); //(byte)((currentStatus.loopsPerSecond >> 8) & 0xFF); - break; - case 28: - currentStatus.freeRAM = freeRam(); - statusValue = highByte(currentStatus.freeRAM); - break; - - case 29: statusValue = (byte)(currentStatus.boostTarget >> 1); break; //Divide boost target by 2 to fit in a byte - case 30: statusValue = (byte)(currentStatus.boostDuty / 100); break; - case 31: statusValue = currentStatus.spark; break; //Spark related bitfield - - //rpmDOT must be sent as a signed integer - case 32: statusValue = lowByte(currentStatus.rpmDOT); break; - case 33: statusValue = highByte(currentStatus.rpmDOT); break; - - case 34: statusValue = currentStatus.ethanolPct; break; //Flex sensor value (or 0 if not used) - case 35: statusValue = currentStatus.flexCorrection; break; //Flex fuel correction (% above or below 100) - case 36: statusValue = currentStatus.flexIgnCorrection; break; //Ignition correction (Increased degrees of advance) for flex fuel - - case 37: statusValue = currentStatus.idleLoad; break; - case 38: statusValue = currentStatus.testOutputs; break; - - case 39: statusValue = currentStatus.O2_2; break; //O2 - case 40: statusValue = currentStatus.baro; break; //Barometer value - - case 41: statusValue = lowByte(currentStatus.canin[0]); break; - case 42: statusValue = highByte(currentStatus.canin[0]); break; - case 43: statusValue = lowByte(currentStatus.canin[1]); break; - case 44: statusValue = highByte(currentStatus.canin[1]); break; - case 45: statusValue = lowByte(currentStatus.canin[2]); break; - case 46: statusValue = highByte(currentStatus.canin[2]); break; - case 47: statusValue = lowByte(currentStatus.canin[3]); break; - case 48: statusValue = highByte(currentStatus.canin[3]); break; - case 49: statusValue = lowByte(currentStatus.canin[4]); break; - case 50: statusValue = highByte(currentStatus.canin[4]); break; - case 51: statusValue = lowByte(currentStatus.canin[5]); break; - case 52: statusValue = highByte(currentStatus.canin[5]); break; - case 53: statusValue = lowByte(currentStatus.canin[6]); break; - case 54: statusValue = highByte(currentStatus.canin[6]); break; - case 55: statusValue = lowByte(currentStatus.canin[7]); break; - case 56: statusValue = highByte(currentStatus.canin[7]); break; - case 57: statusValue = lowByte(currentStatus.canin[8]); break; - case 58: statusValue = highByte(currentStatus.canin[8]); break; - case 59: statusValue = lowByte(currentStatus.canin[9]); break; - case 60: statusValue = highByte(currentStatus.canin[9]); break; - case 61: statusValue = lowByte(currentStatus.canin[10]); break; - case 62: statusValue = highByte(currentStatus.canin[10]); break; - case 63: statusValue = lowByte(currentStatus.canin[11]); break; - case 64: statusValue = highByte(currentStatus.canin[11]); break; - case 65: statusValue = lowByte(currentStatus.canin[12]); break; - case 66: statusValue = highByte(currentStatus.canin[12]); break; - case 67: statusValue = lowByte(currentStatus.canin[13]); break; - case 68: statusValue = highByte(currentStatus.canin[13]); break; - case 69: statusValue = lowByte(currentStatus.canin[14]); break; - case 70: statusValue = highByte(currentStatus.canin[14]); break; - case 71: statusValue = lowByte(currentStatus.canin[15]); break; - case 72: statusValue = highByte(currentStatus.canin[15]); break; - - case 73: statusValue = currentStatus.tpsADC; break; - case 74: statusValue = getNextError(); break; - - case 75: statusValue = lowByte(currentStatus.PW1); break; //Pulsewidth 1 multiplied by 10 in ms. Have to convert from uS to mS. - case 76: statusValue = highByte(currentStatus.PW1); break; //Pulsewidth 1 multiplied by 10 in ms. Have to convert from uS to mS. - case 77: statusValue = lowByte(currentStatus.PW2); break; //Pulsewidth 2 multiplied by 10 in ms. Have to convert from uS to mS. - case 78: statusValue = highByte(currentStatus.PW2); break; //Pulsewidth 2 multiplied by 10 in ms. Have to convert from uS to mS. - case 79: statusValue = lowByte(currentStatus.PW3); break; //Pulsewidth 3 multiplied by 10 in ms. Have to convert from uS to mS. - case 80: statusValue = highByte(currentStatus.PW3); break; //Pulsewidth 3 multiplied by 10 in ms. Have to convert from uS to mS. - case 81: statusValue = lowByte(currentStatus.PW4); break; //Pulsewidth 4 multiplied by 10 in ms. Have to convert from uS to mS. - case 82: statusValue = highByte(currentStatus.PW4); break; //Pulsewidth 4 multiplied by 10 in ms. Have to convert from uS to mS. - - case 83: statusValue = currentStatus.status3; break; - case 84: statusValue = currentStatus.engineProtectStatus; break; - case 85: statusValue = lowByte(currentStatus.fuelLoad); break; - case 86: statusValue = highByte(currentStatus.fuelLoad); break; - case 87: statusValue = lowByte(currentStatus.ignLoad); break; - case 88: statusValue = highByte(currentStatus.ignLoad); break; - case 89: statusValue = lowByte(currentStatus.dwell); break; - case 90: statusValue = highByte(currentStatus.dwell); break; - case 91: statusValue = currentStatus.CLIdleTarget; break; - case 92: statusValue = currentStatus.mapDOT; break; - case 93: statusValue = (int8_t)currentStatus.vvt1Angle; break; - case 94: statusValue = currentStatus.vvt1TargetAngle; break; - case 95: statusValue = currentStatus.vvt1Duty; break; - case 96: statusValue = lowByte(currentStatus.flexBoostCorrection); break; - case 97: statusValue = highByte(currentStatus.flexBoostCorrection); break; - case 98: statusValue = currentStatus.baroCorrection; break; - case 99: statusValue = currentStatus.VE; break; //Current VE (%). Can be equal to VE1 or VE2 or a calculated value from both of them - case 100: statusValue = currentStatus.ASEValue; break; //Current ASE (%) - case 101: statusValue = lowByte(currentStatus.vss); break; - case 102: statusValue = highByte(currentStatus.vss); break; - case 103: statusValue = currentStatus.gear; break; - case 104: statusValue = currentStatus.fuelPressure; break; - case 105: statusValue = currentStatus.oilPressure; break; - case 106: statusValue = currentStatus.wmiPW; break; - case 107: statusValue = currentStatus.wmiEmpty; break; - case 108: statusValue = (int8_t)currentStatus.vvt2Angle; break; - case 109: statusValue = currentStatus.vvt2TargetAngle; break; - case 110: statusValue = currentStatus.vvt2Duty; break; - case 111: statusValue = currentStatus.outputsStatus; break; - case 112: statusValue = (byte)(currentStatus.fuelTemp + CALIBRATION_TEMPERATURE_OFFSET); break; //Fuel temperature from flex sensor - case 113: statusValue = currentStatus.fuelTempCorrection; break; //Fuel temperature Correction (%) - case 114: statusValue = currentStatus.advance1; break; //advance 1 (%) - case 115: statusValue = currentStatus.advance2; break; //advance 2 (%) - case 116: statusValue = currentStatus.TS_SD_Status; break; //SD card status - } - - return statusValue; - - //Each new inclusion here need to be added on speeduino.ini@L78, only list first byte of an integer and second byte as "INVALID" - //Every 2-byte integer added here should have it's lowByte index added to fsIntIndex array on globals.ino@L116 -} - -/* -This function returns 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) -{ - if (portNum == 3) - { - //CAN serial - #if defined(USE_SERIAL3) - if (cmd == 30) - { - CANSerial.write("r"); //confirm cmd type - CANSerial.write(cmd); - } - else if (cmd == 31) { CANSerial.write("A"); } //confirm cmd type - #endif - } - else - { - 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 - - for(byte x=0; x>8); - bytestosend -= Serial.write(currentStatus.secl); - bytestosend -= Serial.write(currentStatus.PW1>>8); - bytestosend -= Serial.write(currentStatus.PW1); - bytestosend -= Serial.write(currentStatus.PW2>>8); - bytestosend -= Serial.write(currentStatus.PW2); - bytestosend -= Serial.write(currentStatus.RPM>>8); - bytestosend -= Serial.write(currentStatus.RPM); - - temp = currentStatus.advance * 10; - bytestosend -= Serial.write(temp>>8); - bytestosend -= Serial.write(temp); - - bytestosend -= Serial.write(currentStatus.nSquirts); - bytestosend -= Serial.write(currentStatus.engine); - bytestosend -= Serial.write(currentStatus.afrTarget); - bytestosend -= Serial.write(currentStatus.afrTarget); // send twice so afrtgt1 == afrtgt2 - bytestosend -= Serial.write(99); // send dummy data as we don't have wbo2_en1 - bytestosend -= Serial.write(99); // send dummy data as we don't have wbo2_en2 - - temp = currentStatus.baro * 10; - bytestosend -= Serial.write(temp>>8); - bytestosend -= Serial.write(temp); - - temp = currentStatus.MAP * 10; - bytestosend -= Serial.write(temp>>8); - bytestosend -= Serial.write(temp); - - temp = currentStatus.IAT * 10; - bytestosend -= Serial.write(temp>>8); - bytestosend -= Serial.write(temp); - - temp = currentStatus.coolant * 10; - bytestosend -= Serial.write(temp>>8); - bytestosend -= Serial.write(temp); - - temp = currentStatus.TPS * 10; - bytestosend -= Serial.write(temp>>8); - bytestosend -= Serial.write(temp); - - bytestosend -= Serial.write(currentStatus.battery10>>8); - bytestosend -= Serial.write(currentStatus.battery10); - bytestosend -= Serial.write(currentStatus.O2>>8); - bytestosend -= Serial.write(currentStatus.O2); - bytestosend -= Serial.write(currentStatus.O2_2>>8); - bytestosend -= Serial.write(currentStatus.O2_2); - - bytestosend -= Serial.write(99); // knock - bytestosend -= Serial.write(99); // knock - - temp = currentStatus.egoCorrection * 10; - bytestosend -= Serial.write(temp>>8); // egocor1 - bytestosend -= Serial.write(temp); // egocor1 - bytestosend -= Serial.write(temp>>8); // egocor2 - bytestosend -= Serial.write(temp); // egocor2 - - temp = currentStatus.iatCorrection * 10; - bytestosend -= Serial.write(temp>>8); // aircor - bytestosend -= Serial.write(temp); // aircor - - temp = currentStatus.wueCorrection * 10; - bytestosend -= Serial.write(temp>>8); // warmcor - bytestosend -= Serial.write(temp); // warmcor - - bytestosend -= Serial.write(99); // accelEnrich - bytestosend -= Serial.write(99); // accelEnrich - bytestosend -= Serial.write(99); // tpsFuelCut - bytestosend -= Serial.write(99); // tpsFuelCut - bytestosend -= Serial.write(99); // baroCorrection - bytestosend -= Serial.write(99); // baroCorrection - - temp = currentStatus.corrections * 10; - bytestosend -= Serial.write(temp>>8); // gammaEnrich - bytestosend -= Serial.write(temp); // gammaEnrich - - temp = currentStatus.VE * 10; - bytestosend -= Serial.write(temp>>8); // ve1 - bytestosend -= Serial.write(temp); // ve1 - temp = currentStatus.VE2 * 10; - bytestosend -= Serial.write(temp>>8); // ve2 - bytestosend -= Serial.write(temp); // ve2 - - bytestosend -= Serial.write(99); // iacstep - bytestosend -= Serial.write(99); // iacstep - bytestosend -= Serial.write(99); // cold_adv_deg - bytestosend -= Serial.write(99); // cold_adv_deg - - temp = currentStatus.tpsDOT * 10; - bytestosend -= Serial.write(temp>>8); // TPSdot - bytestosend -= Serial.write(temp); // TPSdot - - temp = currentStatus.mapDOT * 10; - bytestosend -= Serial.write(temp >> 8); // MAPdot - bytestosend -= Serial.write(temp); // MAPdot - - temp = currentStatus.dwell * 10; - bytestosend -= Serial.write(temp>>8); // dwell - bytestosend -= Serial.write(temp); // dwell - - bytestosend -= Serial.write(99); // MAF - bytestosend -= Serial.write(99); // MAF - bytestosend -= Serial.write(currentStatus.fuelLoad*10); // fuelload - bytestosend -= Serial.write(99); // fuelcor - bytestosend -= Serial.write(99); // fuelcor - bytestosend -= Serial.write(99); // portStatus - - temp = currentStatus.advance1 * 10; - bytestosend -= Serial.write(temp>>8); - bytestosend -= Serial.write(temp); - temp = currentStatus.advance2 * 10; - bytestosend -= Serial.write(temp>>8); - bytestosend -= Serial.write(temp); - - for(int i = 0; i < bytestosend; i++) - { - // send dummy data to fill remote's buffer - Serial.write(99); - } -} - -void receiveValue(uint16_t valueOffset, byte newValue) -{ - - void* pnt_configPage;//This only stores the address of the value that it's pointing to and not the max size - int tempOffset; - - switch (currentPage) - { - case veMapPage: - if (valueOffset < 256) //New value is part of the fuel map - { - fuelTable.values[15 - (valueOffset / 16)][valueOffset % 16] = newValue; - } - else - { - //Check whether this is on the X (RPM) or Y (MAP/TPS) axis - if (valueOffset < 272) - { - //X Axis - fuelTable.axisX[(valueOffset - 256)] = ((int)(newValue) * TABLE_RPM_MULTIPLIER); //The RPM values sent by megasquirt are divided by 100, need to multiple it back by 100 to make it correct (TABLE_RPM_MULTIPLIER) - } - else if(valueOffset < 288) - { - //Y Axis - tempOffset = 15 - (valueOffset - 272); //Need to do a translation to flip the order (Due to us using (0,0) in the top left rather than bottom right - fuelTable.axisY[tempOffset] = (int)(newValue) * TABLE_LOAD_MULTIPLIER; - } - else - { - //This should never happen. It means there's an invalid offset value coming through - } - } - fuelTable.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - break; - - case veSetPage: - pnt_configPage = &configPage2; //Setup a pointer to the relevant config page - //For some reason, TunerStudio is sending offsets greater than the maximum page size. I'm not sure if it's their bug or mine, but the fix is to only update the config page if the offset is less than the maximum size - if (valueOffset < npage_size[veSetPage]) - { - *((byte *)pnt_configPage + (byte)valueOffset) = newValue; - } - break; - - case ignMapPage: //Ignition settings page (Page 2) - if (valueOffset < 256) //New value is part of the ignition map - { - ignitionTable.values[15 - (valueOffset / 16)][valueOffset % 16] = newValue; - } - else - { - //Check whether this is on the X (RPM) or Y (MAP/TPS) axis - if (valueOffset < 272) - { - //X Axis - ignitionTable.axisX[(valueOffset - 256)] = (int)(newValue) * TABLE_RPM_MULTIPLIER; //The RPM values sent by TunerStudio are divided by 100, need to multiple it back by 100 to make it correct - } - else if(valueOffset < 288) - { - //Y Axis - tempOffset = 15 - (valueOffset - 272); //Need to do a translation to flip the order - ignitionTable.axisY[tempOffset] = (int)(newValue) * TABLE_LOAD_MULTIPLIER; - } - } - ignitionTable.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - break; - - case ignSetPage: - pnt_configPage = &configPage4; - //For some reason, TunerStudio is sending offsets greater than the maximum page size. I'm not sure if it's their bug or mine, but the fix is to only update the config page if the offset is less than the maximum size - if (valueOffset < npage_size[ignSetPage]) - { - *((byte *)pnt_configPage + (byte)valueOffset) = newValue; - } - break; - - case afrMapPage: //Air/Fuel ratio target settings page - if (valueOffset < 256) //New value is part of the afr map - { - afrTable.values[15 - (valueOffset / 16)][valueOffset % 16] = newValue; - } - else - { - //Check whether this is on the X (RPM) or Y (MAP/TPS) axis - if (valueOffset < 272) - { - //X Axis - afrTable.axisX[(valueOffset - 256)] = int(newValue) * TABLE_RPM_MULTIPLIER; //The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct (TABLE_RPM_MULTIPLIER) - } - else - { - //Y Axis - tempOffset = 15 - (valueOffset - 272); //Need to do a translation to flip the order - afrTable.axisY[tempOffset] = int(newValue) * TABLE_LOAD_MULTIPLIER; - - } - } - afrTable.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - break; - - case afrSetPage: - pnt_configPage = &configPage6; - //For some reason, TunerStudio is sending offsets greater than the maximum page size. I'm not sure if it's their bug or mine, but the fix is to only update the config page if the offset is less than the maximum size - if (valueOffset < npage_size[afrSetPage]) - { - *((byte *)pnt_configPage + (byte)valueOffset) = newValue; - } - break; - - case boostvvtPage: //Boost, VVT and staging maps (all 8x8) - if (valueOffset < 64) //New value is part of the boost map - { - boostTable.values[7 - (valueOffset / 8)][valueOffset % 8] = newValue; - } - else if (valueOffset < 72) //New value is on the X (RPM) axis of the boost table - { - boostTable.axisX[(valueOffset - 64)] = int(newValue) * TABLE_RPM_MULTIPLIER; //The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct (TABLE_RPM_MULTIPLIER) - } - else if (valueOffset < 80) //New value is on the Y (TPS) axis of the boost table - { - boostTable.axisY[(7 - (valueOffset - 72))] = int(newValue); //TABLE_LOAD_MULTIPLIER is NOT used for boost as it is TPS based (0-100) - } - //End of boost table - else if (valueOffset < 144) //New value is part of the vvt map - { - tempOffset = valueOffset - 80; - vvtTable.values[7 - (tempOffset / 8)][tempOffset % 8] = newValue; - } - else if (valueOffset < 152) //New value is on the X (RPM) axis of the vvt table - { - tempOffset = valueOffset - 144; - vvtTable.axisX[tempOffset] = int(newValue) * TABLE_RPM_MULTIPLIER; //The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct (TABLE_RPM_MULTIPLIER) - } - else if (valueOffset < 160) //New value is on the Y (Load) axis of the vvt table - { - tempOffset = valueOffset - 152; - vvtTable.axisY[(7 - tempOffset)] = int(newValue); //TABLE_LOAD_MULTIPLIER is NOT used for vvt as it is TPS based (0-100) - } - //End of vvt table - else if (valueOffset < 224) //New value is part of the staging map - { - tempOffset = valueOffset - 160; - stagingTable.values[7 - (tempOffset / 8)][tempOffset % 8] = newValue; - } - else if (valueOffset < 232) //New value is on the X (RPM) axis of the staging table - { - tempOffset = valueOffset - 224; - stagingTable.axisX[tempOffset] = int(newValue) * TABLE_RPM_MULTIPLIER; //The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct (TABLE_RPM_MULTIPLIER) - } - else if (valueOffset < 240) //New value is on the Y (Load) axis of the staging table - { - tempOffset = valueOffset - 232; - stagingTable.axisY[(7 - tempOffset)] = int(newValue) * TABLE_LOAD_MULTIPLIER; - } - boostTable.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - vvtTable.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - stagingTable.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - break; - - case seqFuelPage: - if (valueOffset < 36) { trim1Table.values[5 - (valueOffset / 6)][valueOffset % 6] = newValue; } //Trim1 values - else if (valueOffset < 42) { trim1Table.axisX[(valueOffset - 36)] = int(newValue) * TABLE_RPM_MULTIPLIER; } //New value is on the X (RPM) axis of the trim1 table. The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct (TABLE_RPM_MULTIPLIER) - else if (valueOffset < 48) { trim1Table.axisY[(5 - (valueOffset - 42))] = int(newValue) * TABLE_LOAD_MULTIPLIER; } //New value is on the Y (TPS) axis of the boost table - //Trim table 2 - else if (valueOffset < 84) { tempOffset = valueOffset - 48; trim2Table.values[5 - (tempOffset / 6)][tempOffset % 6] = newValue; } //New value is part of the trim2 map - else if (valueOffset < 90) { tempOffset = valueOffset - 84; trim2Table.axisX[tempOffset] = int(newValue) * TABLE_RPM_MULTIPLIER; } //New value is on the X (RPM) axis of the table. The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct (TABLE_RPM_MULTIPLIER) - else if (valueOffset < 96) { tempOffset = valueOffset - 90; trim2Table.axisY[(5 - tempOffset)] = int(newValue) * TABLE_LOAD_MULTIPLIER; } //New value is on the Y (Load) axis of the table - //Trim table 3 - else if (valueOffset < 132) { tempOffset = valueOffset - 96; trim3Table.values[5 - (tempOffset / 6)][tempOffset % 6] = newValue; } //New value is part of the trim3 map - else if (valueOffset < 138) { tempOffset = valueOffset - 132; trim3Table.axisX[tempOffset] = int(newValue) * TABLE_RPM_MULTIPLIER; } //New value is on the X (RPM) axis of the table. The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct (TABLE_RPM_MULTIPLIER) - else if (valueOffset < 144) { tempOffset = valueOffset - 138; trim3Table.axisY[(5 - tempOffset)] = int(newValue) * TABLE_LOAD_MULTIPLIER; } //New value is on the Y (Load) axis of the table - //Trim table 4 - else if (valueOffset < 180) { tempOffset = valueOffset - 144; trim4Table.values[5 - (tempOffset / 6)][tempOffset % 6] = newValue; } //New value is part of the trim4 map - else if (valueOffset < 186) { tempOffset = valueOffset - 180; trim4Table.axisX[tempOffset] = int(newValue) * TABLE_RPM_MULTIPLIER; } //New value is on the X (RPM) axis of the table. The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct (TABLE_RPM_MULTIPLIER) - else if (valueOffset < 192) { tempOffset = valueOffset - 186; trim4Table.axisY[(5 - tempOffset)] = int(newValue) * TABLE_LOAD_MULTIPLIER; } //New value is on the Y (Load) axis of the table - //Trim table 5 - else if (valueOffset < 228) { tempOffset = valueOffset - 192; trim5Table.values[5 - (tempOffset / 6)][tempOffset % 6] = newValue; } //New value is part of the trim5 map - else if (valueOffset < 234) { tempOffset = valueOffset - 228; trim5Table.axisX[tempOffset] = int(newValue) * TABLE_RPM_MULTIPLIER; } //New value is on the X (RPM) axis of the table. The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct (TABLE_RPM_MULTIPLIER) - else if (valueOffset < 240) { tempOffset = valueOffset - 234; trim5Table.axisY[(5 - tempOffset)] = int(newValue) * TABLE_LOAD_MULTIPLIER; } //New value is on the Y (Load) axis of the table - //Trim table 6 - else if (valueOffset < 276) { tempOffset = valueOffset - 240; trim6Table.values[5 - (tempOffset / 6)][tempOffset % 6] = newValue; } //New value is part of the trim6 map - else if (valueOffset < 282) { tempOffset = valueOffset - 276; trim6Table.axisX[tempOffset] = int(newValue) * TABLE_RPM_MULTIPLIER; } //New value is on the X (RPM) axis of the table. The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct (TABLE_RPM_MULTIPLIER) - else if (valueOffset < 288) { tempOffset = valueOffset - 282; trim6Table.axisY[(5 - tempOffset)] = int(newValue) * TABLE_LOAD_MULTIPLIER; } //New value is on the Y (Load) axis of the table - //Trim table 7 - else if (valueOffset < 324) { tempOffset = valueOffset - 288; trim7Table.values[5 - (tempOffset / 6)][tempOffset % 6] = newValue; } //New value is part of the trim7 map - else if (valueOffset < 330) { tempOffset = valueOffset - 324; trim7Table.axisX[tempOffset] = int(newValue) * TABLE_RPM_MULTIPLIER; } //New value is on the X (RPM) axis of the table. The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct (TABLE_RPM_MULTIPLIER) - else if (valueOffset < 336) { tempOffset = valueOffset - 330; trim7Table.axisY[(5 - tempOffset)] = int(newValue) * TABLE_LOAD_MULTIPLIER; } //New value is on the Y (Load) axis of the table - //Trim table 8 - else if (valueOffset < 372) { tempOffset = valueOffset - 336; trim8Table.values[5 - (tempOffset / 6)][tempOffset % 6] = newValue; } //New value is part of the trim8 map - else if (valueOffset < 378) { tempOffset = valueOffset - 372; trim8Table.axisX[tempOffset] = int(newValue) * TABLE_RPM_MULTIPLIER; } //New value is on the X (RPM) axis of the table. The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct (TABLE_RPM_MULTIPLIER) - else if (valueOffset < 384) { tempOffset = valueOffset - 378; trim8Table.axisY[(5 - tempOffset)] = int(newValue) * TABLE_LOAD_MULTIPLIER; } //New value is on the Y (Load) axis of the table - - trim1Table.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - trim2Table.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - trim3Table.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - trim4Table.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - trim5Table.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - trim6Table.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - trim7Table.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - trim8Table.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - break; - - case canbusPage: - pnt_configPage = &configPage9; - //For some reason, TunerStudio is sending offsets greater than the maximum page size. I'm not sure if it's their bug or mine, but the fix is to only update the config page if the offset is less than the maximum size - if (valueOffset < npage_size[currentPage]) - { - *((byte *)pnt_configPage + (byte)valueOffset) = newValue; - } - break; - - case warmupPage: - pnt_configPage = &configPage10; - //For some reason, TunerStudio is sending offsets greater than the maximum page size. I'm not sure if it's their bug or mine, but the fix is to only update the config page if the offset is less than the maximum size - if (valueOffset < npage_size[currentPage]) - { - *((byte *)pnt_configPage + (byte)valueOffset) = newValue; - } - break; - - case fuelMap2Page: - if (valueOffset < 256) //New value is part of the fuel map - { - fuelTable2.values[15 - (valueOffset / 16)][valueOffset % 16] = newValue; - } - else - { - //Check whether this is on the X (RPM) or Y (MAP/TPS) axis - if (valueOffset < 272) - { - //X Axis - fuelTable2.axisX[(valueOffset - 256)] = ((int)(newValue) * TABLE_RPM_MULTIPLIER); //The RPM values sent by megasquirt are divided by 100, need to multiple it back by 100 to make it correct (TABLE_RPM_MULTIPLIER) - } - else if(valueOffset < 288) - { - //Y Axis - tempOffset = 15 - (valueOffset - 272); //Need to do a translation to flip the order (Due to us using (0,0) in the top left rather than bottom right - fuelTable2.axisY[tempOffset] = (int)(newValue) * TABLE_LOAD_MULTIPLIER; - } - else - { - //This should never happen. It means there's an invalid offset value coming through - } - } - fuelTable2.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - break; - - case wmiMapPage: - if (valueOffset < 64) //New value is part of the wmi map - { - wmiTable.values[7 - (valueOffset / 8)][valueOffset % 8] = newValue; - } - else if (valueOffset < 72) //New value is on the X (RPM) axis of the wmi table - { - wmiTable.axisX[(valueOffset - 64)] = int(newValue) * TABLE_RPM_MULTIPLIER; - } - else if (valueOffset < 80) //New value is on the Y (MAP) axis of the wmi table - { - wmiTable.axisY[(7 - (valueOffset - 72))] = int(newValue) * TABLE_LOAD_MULTIPLIER; - } - //End of wmi table - else if (valueOffset < 176) //New value is part of the dwell map - { - tempOffset = valueOffset - 160; - dwellTable.values[3 - (tempOffset / 4)][tempOffset % 4] = newValue; - } - else if (valueOffset < 180) //New value is on the X (RPM) axis of the dwell table - { - tempOffset = valueOffset - 176; - dwellTable.axisX[tempOffset] = int(newValue) * TABLE_RPM_MULTIPLIER; - } - else if (valueOffset < 184) //New value is on the Y (Load) axis of the dwell table - { - tempOffset = valueOffset - 180; - dwellTable.axisY[(3 - tempOffset)] = int(newValue) * TABLE_LOAD_MULTIPLIER; - } - //End of dwell table - wmiTable.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - dwellTable.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - break; - - case progOutsPage: - pnt_configPage = &configPage13; - //For some reason, TunerStudio is sending offsets greater than the maximum page size. I'm not sure if it's their bug or mine, but the fix is to only update the config page if the offset is less than the maximum size - if (valueOffset < npage_size[currentPage]) - { - *((byte *)pnt_configPage + (byte)valueOffset) = newValue; - } - break; - - default: - break; - - case ignMap2Page: //Ignition settings page (Page 2) - if (valueOffset < 256) //New value is part of the ignition map - { - ignitionTable2.values[15 - (valueOffset / 16)][valueOffset % 16] = newValue; - } - else - { - //Check whether this is on the X (RPM) or Y (MAP/TPS) axis - if (valueOffset < 272) - { - //X Axis - ignitionTable2.axisX[(valueOffset - 256)] = (int)(newValue) * TABLE_RPM_MULTIPLIER; //The RPM values sent by TunerStudio are divided by 100, need to multiple it back by 100 to make it correct - } - else if(valueOffset < 288) - { - //Y Axis - tempOffset = 15 - (valueOffset - 272); //Need to do a translation to flip the order - ignitionTable2.axisY[tempOffset] = (int)(newValue) * TABLE_LOAD_MULTIPLIER; - } - } - ignitionTable2.cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values - break; - } - //if(Serial.available() > 16) { command(); } -} - -/** - * @brief Packs the data within the current page (As set with the 'P' command) into a buffer and sends it. - * - * Note that some translation of the data is required to lay it out in the way Megasqurit / TunerStudio expect it - * Data is sent in binary format, as defined by in each page in the ini - */ -void sendPage() -{ - void* pnt_configPage = &configPage2; //Default value is for safety only. Will be changed below if needed. - struct table3D currentTable = fuelTable; //Default value is for safety only. Will be changed below if needed. - bool sendComplete = false; //Used to track whether all send operations are complete - - switch (currentPage) - { - case veMapPage: - currentTable = fuelTable; - break; - - case veSetPage: - pnt_configPage = &configPage2; //Create a pointer to Page 1 in memory - break; - - case ignMapPage: - currentTable = ignitionTable; - break; - - case ignSetPage: - pnt_configPage = &configPage4; //Create a pointer to Page 2 in memory - break; - - case afrMapPage: - currentTable = afrTable; - break; - - case afrSetPage: - pnt_configPage = &configPage6; //Create a pointer to Page 3 in memory - break; - - case boostvvtPage: - { - //Need to perform a translation of the values[MAP/TPS][RPM] into the MS expected format - byte response[80]; //Bit hacky, but send 1 map at a time (Each map is 8x8, so 64 + 8 + 8) - - //Boost table - for (int x = 0; x < 64; x++) { response[x] = boostTable.values[7 - (x / 8)][x % 8]; } - for (int x = 64; x < 72; x++) { response[x] = byte(boostTable.axisX[(x - 64)] / TABLE_RPM_MULTIPLIER); } - for (int y = 72; y < 80; y++) { response[y] = byte(boostTable.axisY[7 - (y - 72)]); } - Serial.write((byte *)&response, 80); - //VVT table - for (int x = 0; x < 64; x++) { response[x] = vvtTable.values[7 - (x / 8)][x % 8]; } - for (int x = 64; x < 72; x++) { response[x] = byte(vvtTable.axisX[(x - 64)] / TABLE_RPM_MULTIPLIER); } - for (int y = 72; y < 80; y++) { response[y] = byte(vvtTable.axisY[7 - (y - 72)]); } - Serial.write((byte *)&response, 80); - //Staging table - for (int x = 0; x < 64; x++) { response[x] = stagingTable.values[7 - (x / 8)][x % 8]; } - for (int x = 64; x < 72; x++) { response[x] = byte(stagingTable.axisX[(x - 64)] / TABLE_RPM_MULTIPLIER); } - for (int y = 72; y < 80; y++) { response[y] = byte(stagingTable.axisY[7 - (y - 72)] / TABLE_LOAD_MULTIPLIER); } - Serial.write((byte *)&response, 80); - sendComplete = true; - break; - } - case seqFuelPage: - { - //Need to perform a translation of the values[MAP/TPS][RPM] into the MS expected format - byte response[384]; //Bit hacky, but the size is: (6x6 + 6 + 6) * 8 = 384 - - //trim1 table - for (int x = 0; x < 36; x++) { response[x] = trim1Table.values[5 - (x / 6)][x % 6]; } - for (int x = 36; x < 42; x++) { response[x] = byte(trim1Table.axisX[(x - 36)] / TABLE_RPM_MULTIPLIER); } - for (int y = 42; y < 48; y++) { response[y] = byte(trim1Table.axisY[5 - (y - 42)] / TABLE_LOAD_MULTIPLIER); } - //trim2 table - for (int x = 0; x < 36; x++) { response[x + 48] = trim2Table.values[5 - (x / 6)][x % 6]; } - for (int x = 36; x < 42; x++) { response[x + 48] = byte(trim2Table.axisX[(x - 36)] / TABLE_RPM_MULTIPLIER); } - for (int y = 42; y < 48; y++) { response[y + 48] = byte(trim2Table.axisY[5 - (y - 42)] / TABLE_LOAD_MULTIPLIER); } - //trim3 table - for (int x = 0; x < 36; x++) { response[x + 96] = trim3Table.values[5 - (x / 6)][x % 6]; } - for (int x = 36; x < 42; x++) { response[x + 96] = byte(trim3Table.axisX[(x - 36)] / TABLE_RPM_MULTIPLIER); } - for (int y = 42; y < 48; y++) { response[y + 96] = byte(trim3Table.axisY[5 - (y - 42)] / TABLE_LOAD_MULTIPLIER); } - //trim4 table - for (int x = 0; x < 36; x++) { response[x + 144] = trim4Table.values[5 - (x / 6)][x % 6]; } - for (int x = 36; x < 42; x++) { response[x + 144] = byte(trim4Table.axisX[(x - 36)] / TABLE_RPM_MULTIPLIER); } - for (int y = 42; y < 48; y++) { response[y + 144] = byte(trim4Table.axisY[5 - (y - 42)] / TABLE_LOAD_MULTIPLIER); } - //trim5 table - for (int x = 0; x < 36; x++) { response[x + 192] = trim5Table.values[5 - (x / 6)][x % 6]; } - for (int x = 36; x < 42; x++) { response[x + 192] = byte(trim5Table.axisX[(x - 36)] / TABLE_RPM_MULTIPLIER); } - for (int y = 42; y < 48; y++) { response[y + 192] = byte(trim5Table.axisY[5 - (y - 42)] / TABLE_LOAD_MULTIPLIER); } - //trim6 table - for (int x = 0; x < 36; x++) { response[x + 240] = trim6Table.values[5 - (x / 6)][x % 6]; } - for (int x = 36; x < 42; x++) { response[x + 240] = byte(trim6Table.axisX[(x - 36)] / TABLE_RPM_MULTIPLIER); } - for (int y = 42; y < 48; y++) { response[y + 240] = byte(trim6Table.axisY[5 - (y - 42)] / TABLE_LOAD_MULTIPLIER); } - //trim7 table - for (int x = 0; x < 36; x++) { response[x + 288] = trim7Table.values[5 - (x / 6)][x % 6]; } - for (int x = 36; x < 42; x++) { response[x + 288] = byte(trim7Table.axisX[(x - 36)] / TABLE_RPM_MULTIPLIER); } - for (int y = 42; y < 48; y++) { response[y + 288] = byte(trim7Table.axisY[5 - (y - 42)] / TABLE_LOAD_MULTIPLIER); } - //trim8 table - for (int x = 0; x < 36; x++) { response[x + 336] = trim8Table.values[5 - (x / 6)][x % 6]; } - for (int x = 36; x < 42; x++) { response[x + 336] = byte(trim8Table.axisX[(x - 36)] / TABLE_RPM_MULTIPLIER); } - for (int y = 42; y < 48; y++) { response[y + 336] = byte(trim8Table.axisY[5 - (y - 42)] / TABLE_LOAD_MULTIPLIER); } - - Serial.write((byte *)&response, sizeof(response)); - sendComplete = true; - break; - } - case canbusPage: - pnt_configPage = &configPage9; //Create a pointer to Page 9 in memory - break; - - case warmupPage: - pnt_configPage = &configPage10; //Create a pointer to Page 10 in memory - break; - - case fuelMap2Page: - currentTable = fuelTable2; - break; - - case wmiMapPage: - { - //Need to perform a translation of the values[MAP/TPS][RPM] into the MS expected format - byte response[80]; //Bit hacky, but send 1 map at a time (Each map is 8x8, so 64 + 8 + 8) - - //WMI table - for (int x = 0; x < 64; x++) { response[x] = wmiTable.values[7 - (x / 8)][x % 8]; } - for (int x = 64; x < 72; x++) { response[x] = byte(wmiTable.axisX[(x - 64)] / TABLE_RPM_MULTIPLIER); } - for (int y = 72; y < 80; y++) { response[y] = byte(wmiTable.axisY[7 - (y - 72)] / TABLE_LOAD_MULTIPLIER); } - Serial.write((byte *)&response, 80); - - //Dwell table - for (int x = 0; x < 16; x++) { response[x] = dwellTable.values[3 - (x / 4)][x % 4]; } - for (int x = 16; x < 20; x++) { response[x] = byte(dwellTable.axisX[(x - 16)] / TABLE_RPM_MULTIPLIER); } - for (int y = 20; y < 24; y++) { response[y] = byte(dwellTable.axisY[3 - (y - 20)] / TABLE_LOAD_MULTIPLIER); } - Serial.write((byte *)&response, 24); - break; - } - - case progOutsPage: - pnt_configPage = &configPage13; //Create a pointer to Page 13 in memory - break; - - case ignMap2Page: - currentTable = ignitionTable2; - break; - - default: - #ifndef SMALL_FLASH_MODE - Serial.println(F("\nPage has not been implemented yet")); - #endif - //Just set default Values to avoid warnings - pnt_configPage = &configPage10; - currentTable = fuelTable; - sendComplete = true; - break; - } - if(!sendComplete) - { - if (isMap) - { - //Need to perform a translation of the values[yaxis][xaxis] into the MS expected format - //MS format has origin (0,0) in the bottom left corner, we use the top left for efficiency reasons - byte response[MAP_PAGE_SIZE]; - - for (int x = 0; x < 256; x++) { response[x] = currentTable.values[15 - (x / 16)][x % 16]; } //This is slightly non-intuitive, but essentially just flips the table vertically (IE top line becomes the bottom line etc). Columns are unchanged. Every 16 loops, manually call loop() to avoid potential misses - //loop(); - for (int x = 256; x < 272; x++) { response[x] = byte(currentTable.axisX[(x - 256)] / TABLE_RPM_MULTIPLIER); } //RPM Bins for VE table (Need to be dvidied by 100) - //loop(); - for (int y = 272; y < 288; y++) { response[y] = byte(currentTable.axisY[15 - (y - 272)] / TABLE_LOAD_MULTIPLIER); } //MAP or TPS bins for VE table - //loop(); - Serial.write((byte *)&response, sizeof(response)); - } //is map - else - { - for (byte x = 0; x < npage_size[currentPage]; x++) - { - //response[x] = *((byte *)pnt_configPage + x); - Serial.write(*((byte *)pnt_configPage + x)); //Each byte is simply the location in memory of the configPage + the offset + the variable number (x) - } - - //Serial.write((byte *)&response, npage_size[currentPage]); - // } - } //isMap - } //sendComplete -} - -/** - * @brief Similar to sendPage(), however data is sent in human readable format - * - * This is used for testing only (Not used by TunerStudio) in order to see current map and config data without the need for TunerStudio. - */ -void sendPageASCII() -{ - void* pnt_configPage = &configPage2; //Default value is for safety only. Will be changed below if needed. - struct table3D currentTable = fuelTable; //Default value is for safety only. Will be changed below if needed. - byte currentTitleIndex = 0;// This corresponds to the count up to the first char of a string in pageTitles - bool sendComplete = false; //Used to track whether all send operations are complete - - switch (currentPage) - { - case veMapPage: - currentTitleIndex = 0; - currentTable = fuelTable; - break; - - case veSetPage: - uint16_t* pnt16_configPage; - // To Display Values from Config Page 1 - // When casting to the __FlashStringHelper type Serial.println uses the same subroutine as when using the F macro - Serial.println((const __FlashStringHelper *)&pageTitles[27]);//27 is the index to the first char in the second sting in pageTitles - // The following loop displays in human readable form of all byte values in config page 1 up to but not including the first array. - // incrementing void pointers is cumbersome. Thus we have "pnt_configPage = (byte *)pnt_configPage + 1" - for (pnt_configPage = (byte *)&configPage2; pnt_configPage < &configPage2.wueValues[0]; pnt_configPage = (byte *)pnt_configPage + 1) { Serial.println(*((byte *)pnt_configPage)); } - for (byte x = 10; x; x--)// The x between the ';' has the same representation as the "x != 0" test or comparision - { - Serial.print(configPage2.wueValues[10 - x]);// This displays the values horizantially on the screen - Serial.print(F(" ")); - } - Serial.println(); - for (pnt_configPage = (byte *)&configPage2.wueValues[9] + 1; pnt_configPage < &configPage2.injAng; pnt_configPage = (byte *)pnt_configPage + 1) { - Serial.println(*((byte *)pnt_configPage));// This displays all the byte values between the last array up to but not including the first unsigned int on config page 1 - } - // The following loop displays four unsigned ints - for (pnt16_configPage = (uint16_t *)&configPage2.injAng; pnt16_configPage < (uint16_t*)&configPage2.injAng + 9; pnt16_configPage = (uint16_t*)pnt16_configPage + 1) - { Serial.println(*((uint16_t *)pnt16_configPage)); } - // Following loop displays byte values between the unsigned ints - for (pnt_configPage = (uint16_t *)&configPage2.injAng + 9; pnt_configPage < &configPage2.mapMax; pnt_configPage = (byte *)pnt_configPage + 1) { Serial.println(*((byte *)pnt_configPage)); } - Serial.println(configPage2.mapMax); - // Following loop displays remaining byte values of the page - for (pnt_configPage = (uint16_t *)&configPage2.mapMax + 1; pnt_configPage < (byte *)&configPage2 + npage_size[veSetPage]; pnt_configPage = (byte *)pnt_configPage + 1) { Serial.println(*((byte *)pnt_configPage)); } - sendComplete = true; - break; - - case ignMapPage: - currentTitleIndex = 42;// the index to the first char of the third string in pageTitles - currentTable = ignitionTable; - break; - - case ignSetPage: - //To Display Values from Config Page 2 - Serial.println((const __FlashStringHelper *)&pageTitles[56]); - Serial.println(configPage4.triggerAngle);// configPsge2.triggerAngle is an int so just display it without complication - // Following loop displays byte values after that first int up to but not including the first array in config page 2 - for (pnt_configPage = (byte *)&configPage4 + 1; pnt_configPage < &configPage4.taeBins[0]; pnt_configPage = (byte *)pnt_configPage + 1) { Serial.println(*((byte *)pnt_configPage)); } - for (byte y = 2; y; y--)// Displaying two equal sized arrays - { - byte * currentVar;// A placeholder for each array - if (y == 2) { - currentVar = configPage4.taeBins; - } - else { - currentVar = configPage4.taeValues; - } - - for (byte j = 4; j; j--) - { - Serial.print(currentVar[4 - j]); - Serial.print(' '); - } - Serial.println(); - } - for (byte x = 10; x ; x--) - { - Serial.print(configPage4.wueBins[10 - x]);//Displaying array horizontally across screen - Serial.print(' '); - } - Serial.println(); - Serial.println(configPage4.dwellLimit);// Little lonely byte stuck between two arrays. No complications just display it. - for (byte x = 6; x; x--) - { - Serial.print(configPage4.dwellCorrectionValues[6 - x]); - Serial.print(' '); - } - Serial.println(); - for (pnt_configPage = (byte *)&configPage4.dwellCorrectionValues[5] + 1; pnt_configPage < (byte *)&configPage4 + npage_size[ignSetPage]; pnt_configPage = (byte *)pnt_configPage + 1) - { - Serial.println(*((byte *)pnt_configPage));// Displaying remaining byte values of the page - } - sendComplete = true; - break; - - case afrMapPage: - currentTitleIndex = 71;//Array index to next string - currentTable = afrTable; - break; - - case afrSetPage: - //currentTitleIndex = 91; - //To Display Values from Config Page 3 - Serial.println((const __FlashStringHelper *)&pageTitles[91]);//special typecasting to enable suroutine that the F macro uses - for (pnt_configPage = (byte *)&configPage6; pnt_configPage < &configPage6.voltageCorrectionBins[0]; pnt_configPage = (byte *)pnt_configPage + 1) - { - Serial.println(*((byte *)pnt_configPage));// Displaying byte values of config page 3 up to but not including the first array - } - for (byte y = 2; y; y--)// Displaying two equally sized arrays that are next to each other - { - byte * currentVar; - if (y == 2) { currentVar = configPage6.voltageCorrectionBins; } - else { currentVar = configPage6.injVoltageCorrectionValues; } - - for (byte i = 6; i; i--) - { - Serial.print(currentVar[6 - i]); - Serial.print(' '); - } - Serial.println(); - } - for (byte y = 2; y; y--)// and again - { - byte* currentVar; - if (y == 2) { currentVar = configPage6.airDenBins; } - else { currentVar = configPage6.airDenRates; } - - for (byte i = 9; i; i--) - { - Serial.print(currentVar[9 - i]); - Serial.print(' '); - } - Serial.println(); - } - // Following loop displays the remaining byte values of the page - for (pnt_configPage = (byte *)&configPage6.airDenRates[8] + 1; pnt_configPage < (byte *)&configPage6 + npage_size[afrSetPage]; pnt_configPage = (byte *)pnt_configPage + 1) - { - Serial.println(*((byte *)pnt_configPage)); - } - sendComplete = true; - - //Old configPage4 STARTED HERE! - //currentTitleIndex = 106; - Serial.println((const __FlashStringHelper *)&pageTitles[106]);// F macro hack - for (byte y = 4; y; y--)// Display four equally sized arrays - { - byte * currentVar; - switch (y) - { - case 1: currentVar = configPage6.iacBins; break; - case 2: currentVar = configPage6.iacOLPWMVal; break; - case 3: currentVar = configPage6.iacOLStepVal; break; - case 4: currentVar = configPage6.iacCLValues; break; - default: break; - } - for (byte i = 10; i; i--) - { - Serial.print(currentVar[10 - i]); - Serial.print(' '); - } - Serial.println(); - } - for (byte y = 3; y; y--)// Three equally sized arrays - { - byte * currentVar; - switch (y) - { - case 1: currentVar = configPage6.iacCrankBins; break; - case 2: currentVar = configPage6.iacCrankDuty; break; - case 3: currentVar = configPage6.iacCrankSteps; break; - default: break; - } - for (byte i = 4; i; i--) - { - Serial.print(currentVar[4 - i]); - Serial.print(' '); - } - Serial.println(); - } - // Following loop is for remaining byte value of page - for (pnt_configPage = (byte *)&configPage6.iacCrankBins[3] + 1; pnt_configPage < (byte *)&configPage6 + npage_size[afrSetPage]; pnt_configPage = (byte *)pnt_configPage + 1) { Serial.println(*((byte *)pnt_configPage)); } - sendComplete = true; - break; - - case boostvvtPage: - currentTable = boostTable; - currentTitleIndex = 121; - break; - - case seqFuelPage: - currentTable = trim1Table; - for (int y = 0; y < currentTable.ySize; y++) - { - byte axisY = byte(currentTable.axisY[y]); - if (axisY < 100) - { - Serial.write(" "); - if (axisY < 10) - { - Serial.write(" "); - } - } - Serial.print(axisY);// Vertical Bins - Serial.write(" "); - for (int i = 0; i < currentTable.xSize; i++) - { - byte value = currentTable.values[y][i]; - if (value < 100) - { - Serial.write(" "); - if (value < 10) - { - Serial.write(" "); - } - } - Serial.print(value); - Serial.write(" "); - } - Serial.println(""); - } - sendComplete = true; - break; - - case canbusPage: - //currentTitleIndex = 141; - //To Display Values from Config Page 10 - Serial.println((const __FlashStringHelper *)&pageTitles[103]);//special typecasting to enable suroutine that the F macro uses - for (pnt_configPage = &configPage9; pnt_configPage < ( (byte *)&configPage9 + npage_size[canbusPage]); pnt_configPage = (byte *)pnt_configPage + 1) - { - Serial.println(*((byte *)pnt_configPage));// Displaying byte values of config page 9 up to but not including the first array - } - sendComplete = true; - break; - - case warmupPage: - //NOT WRITTEN YET - #ifndef SMALL_FLASH_MODE - Serial.println(F("\nPage has not been implemented yet")); - #endif - sendComplete = true; - break; - - case fuelMap2Page: - currentTitleIndex = 117;// the index to the first char of the third string in pageTitles - currentTable = fuelTable2; - break; - - case progOutsPage: - //NOT WRITTEN YET - #ifndef SMALL_FLASH_MODE - Serial.println(F("\nPage has not been implemented yet")); - #endif - sendComplete = true; - break; - - case ignMap2Page: - currentTitleIndex = 149;// the index to the first char of the third string in pageTitles - currentTable = ignitionTable2; - break; - - default: - #ifndef SMALL_FLASH_MODE - Serial.println(F("\nPage has not been implemented yet")); - #endif - //Just set default Values to avoid warnings - pnt_configPage = &configPage10; - currentTable = fuelTable; - sendComplete = true; - break; - } - if(!sendComplete) - { - if (isMap) - { - //This is a do while loop that kicks in for the boostvvtPage - do { - const char spaceChar = ' '; - - Serial.println((const __FlashStringHelper *)&pageTitles[currentTitleIndex]);// F macro hack - Serial.println(); - for (int y = 0; y < currentTable.ySize; y++) - { - byte axisY = byte(currentTable.axisY[y]); - if (axisY < 100) - { - Serial.write(spaceChar); - if (axisY < 10) - { - Serial.write(spaceChar); - } - } - Serial.print(axisY);// Vertical Bins - Serial.write(spaceChar); - for (int i = 0; i < currentTable.xSize; i++) - { - byte value = currentTable.values[y][i]; - if (value < 100) - { - Serial.write(spaceChar); - if (value < 10) - { - Serial.write(spaceChar); - } - } - Serial.print(value); - Serial.write(spaceChar); - } - Serial.println(); - } - Serial.print(F(" ")); - for (int x = 0; x < currentTable.xSize; x++)// Horizontal bins - { - byte axisX = byte(currentTable.axisX[x] / 100); - if (axisX < 100) - { - Serial.write(spaceChar); - if (axisX < 10) - { - Serial.write(spaceChar); - } - } - Serial.print(axisX); - Serial.write(spaceChar); - } - Serial.println(); - if(currentTitleIndex == 121) //Check to see if on boostTable - { - currentTitleIndex = 132; //Change over to vvtTable mid display - currentTable = vvtTable; - } - else { currentTitleIndex = 0; } - } while(currentTitleIndex == 132); //Should never loop unless going to display vvtTable - } //is map - else - { - /*if(useChar) - { - while(pageTitles[currentTitleIndex]) - { - Serial.print(pageTitles[currentTitleIndex]); - currentTitleIndex++; - } - Serial.println(); - for(byte x=0;x> 24); - Serial.write(toothHistory[toothHistorySerialIndex] >> 16); - Serial.write(toothHistory[toothHistorySerialIndex] >> 8); - Serial.write(toothHistory[toothHistorySerialIndex]); - - if(toothHistorySerialIndex == (TOOTH_LOG_BUFFER-1)) { toothHistorySerialIndex = 0; } - else { toothHistorySerialIndex++; } - } - BIT_CLEAR(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY); - cmdPending = false; - toothLogSendInProgress = false; - } - else - { - //TunerStudio has timed out, send a LOG of all 0s - for(int x = 0; x < (4*TOOTH_LOG_SIZE); x++) - { - Serial.write(static_cast(0x00)); //GCC9 fix - } - cmdPending = false; - } -} - -void sendCompositeLog(byte startOffset) -{ - if (BIT_CHECK(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY)) //Sanity check. Flagging system means this should always be true - { - if(startOffset == 0) { inProgressCompositeTime = 0; } - 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; - return; - } - - inProgressCompositeTime += toothHistory[toothHistorySerialIndex]; //This combined runtime (in us) that the log was going for by this record) - - Serial.write(inProgressCompositeTime >> 24); - Serial.write(inProgressCompositeTime >> 16); - Serial.write(inProgressCompositeTime >> 8); - Serial.write(inProgressCompositeTime); - - Serial.write(compositeLogHistory[toothHistorySerialIndex]); //The status byte (Indicates the trigger edge, whether it was a pri/sec pulse, the sync status) - - if(toothHistorySerialIndex == (TOOTH_LOG_BUFFER-1)) { toothHistorySerialIndex = 0; } - else { toothHistorySerialIndex++; } - } - BIT_CLEAR(currentStatus.status1, BIT_STATUS1_TOOTHLOG1READY); - toothHistoryIndex = 0; - toothHistorySerialIndex = 0; - compositeLastToothTime = 0; - cmdPending = false; - compositeLogSendInProgress = false; - inProgressCompositeTime = 0; - } - else - { - //TunerStudio has timed out, send a LOG of all 0s - for(int x = 0; x < (5*TOOTH_LOG_SIZE); x++) - { - Serial.write(static_cast(0x00)); //GCC9 fix - } - cmdPending = false; - } -} - -void testComm() -{ - Serial.write(1); - return; -} diff --git a/speeduino/errors.h b/speeduino/errors.h index 22c99389..131d5886 100644 --- a/speeduino/errors.h +++ b/speeduino/errors.h @@ -49,8 +49,4 @@ struct packedError byte getNextError(); byte setError(byte); -byte errorCount = 0; -byte errorCodes[4]; - - #endif diff --git a/speeduino/errors.ino b/speeduino/errors.ino index 335cc3cf..8767f418 100644 --- a/speeduino/errors.ino +++ b/speeduino/errors.ino @@ -11,6 +11,9 @@ A full copy of the license may be found in the projects root directory #include "globals.h" #include "errors.h" +byte errorCount = 0; +byte errorCodes[4]; + byte setError(byte errorID) { if(errorCount < MAX_ERRORS) diff --git a/speeduino/globals.h b/speeduino/globals.h index c1bd3ace..8dd2451b 100644 --- a/speeduino/globals.h +++ b/speeduino/globals.h @@ -377,9 +377,6 @@ extern const char TSfirmwareVersion[] PROGMEM; extern const byte data_structure_version; //This identifies the data structure when reading / writing. -#define NUM_PAGES 15 -extern const uint16_t npage_size[NUM_PAGES]; /**< This array stores the size (in bytes) of each configuration page */ -#define MAP_PAGE_SIZE 288 extern struct table3D fuelTable; //16x16 fuel map extern struct table3D fuelTable2; //16x16 fuel map @@ -1430,10 +1427,4 @@ extern struct table2D cltCalibrationTable; /**< A 32 bin array containing the co extern struct table2D iatCalibrationTable; /**< A 32 bin array containing the inlet air temperature sensor calibration values */ extern struct table2D o2CalibrationTable; /**< A 32 bin array containing the O2 sensor calibration values */ -static_assert(sizeof(struct config2) == 128, "configPage2 size is not 128"); -static_assert(sizeof(struct config4) == 128, "configPage4 size is not 128"); -static_assert(sizeof(struct config6) == 128, "configPage6 size is not 128"); -static_assert(sizeof(struct config9) == 192, "configPage9 size is not 192"); -static_assert(sizeof(struct config10) == 192, "configPage10 size is not 192"); -static_assert(sizeof(struct config13) == 128, "configPage13 size is not 128"); #endif // GLOBALS_H diff --git a/speeduino/globals.ino b/speeduino/globals.ino index 8f458477..160ea4ce 100644 --- a/speeduino/globals.ino +++ b/speeduino/globals.ino @@ -3,7 +3,6 @@ const char TSfirmwareVersion[] PROGMEM = "Speeduino"; const byte data_structure_version = 2; //This identifies the data structure when reading / writing. -const uint16_t npage_size[NUM_PAGES] = {0,128,288,288,128,288,128,240,384,192,192,288,192,128,288}; /**< This array stores the size (in bytes) of each configuration page */ struct table3D fuelTable; //16x16 fuel map struct table3D fuelTable2; //16x16 fuel map diff --git a/speeduino/page_crc.cpp b/speeduino/page_crc.cpp new file mode 100644 index 00000000..fed6dfd1 --- /dev/null +++ b/speeduino/page_crc.cpp @@ -0,0 +1,95 @@ +#include "page_crc.h" +#include "pages.h" +#include "src/FastCRC/FastCRC.h" +#include "table_iterator.h" + +static FastCRC32 CRC32; + +typedef uint32_t (FastCRC32::*pCrcCalc)(const uint8_t *, const uint16_t, bool); + +static inline uint32_t compute_raw_crc(const page_iterator_t &entity, pCrcCalc calcFunc) +{ + return (CRC32.*calcFunc)((uint8_t*)entity.pData, entity.size, false); +} + +static inline uint32_t compute_tablevalues_crc(table_row_iterator_t it, pCrcCalc calcFunc) +{ + table_row_t row = get_row(it); + uint32_t crc = (CRC32.*calcFunc)(row.pValue, row.pEnd-row.pValue, false); + advance_row(it); + + while (!at_end(it)) + { + row = get_row(it); + crc = CRC32.crc32_upd(row.pValue, row.pEnd-row.pValue, false); + advance_row(it); + } + return crc; +} + +static inline uint32_t compute_tableaxis_crc(table_axis_iterator_t it, uint32_t crc) +{ + byte values[32]; // Fingers crossed we don't have a table bigger than 32x32 + byte *pValue = values; + while (!at_end(it)) + { + *pValue++ = get_value(it); + it = advance_axis(it); + } + return pValue-values==0 ? crc : CRC32.crc32_upd(values, pValue-values, false); +} + +static inline uint32_t compute_table_crc(table3D *pTable, pCrcCalc calcFunc) +{ + return compute_tableaxis_crc(y_begin(pTable), + compute_tableaxis_crc(x_begin(pTable), + compute_tablevalues_crc(rows_begin(pTable), calcFunc))); +} + +static inline uint32_t pad_crc(uint16_t padding, uint32_t crc) +{ + uint8_t raw_value = 0u; + while (padding>0) + { + crc = CRC32.crc32_upd(&raw_value, 1, false); + --padding; + } + return crc; +} + +static inline uint32_t compute_crc(page_iterator_t &entity, pCrcCalc calcFunc) +{ + switch (entity.type) + { + case Raw: + return compute_raw_crc(entity, calcFunc); + break; + + case Table: + return compute_table_crc(entity.pTable, calcFunc); + break; + + case NoEntity: + return pad_crc(entity.size, 0U); + break; + + default: + abort(); + break; + } +} + +uint32_t calculateCRC32(byte pageNum) +{ + page_iterator_t entity = page_begin(pageNum); + // Initial CRC calc + uint32_t crc = compute_crc(entity, &FastCRC32::crc32); + + entity = advance(entity); + while (entity.type!=End) + { + crc = compute_crc(entity, &FastCRC32::crc32_upd /* Note that we are *updating* */); + entity = advance(entity); + } + return ~pad_crc(getPageSize(pageNum) - entity.size, crc); +} \ No newline at end of file diff --git a/speeduino/page_crc.h b/speeduino/page_crc.h new file mode 100644 index 00000000..67745214 --- /dev/null +++ b/speeduino/page_crc.h @@ -0,0 +1,7 @@ +#pragma once +#include + +/* + * Calculates and returns the CRC32 value of a given page of memory + */ +uint32_t calculateCRC32(byte pageNum /**< [in] The page number to compute CRC for. */); \ No newline at end of file diff --git a/speeduino/pages.cpp b/speeduino/pages.cpp new file mode 100644 index 00000000..6582d87e --- /dev/null +++ b/speeduino/pages.cpp @@ -0,0 +1,372 @@ +#include "pages.h" +#include "globals.h" +#include "utilities.h" +#include "table_iterator.h" + +// This namespace maps from virtual page "addresses" to addresses/bytes of real in memory entities +// +// For TunerStudio: +// 1. Each page has a numeric identifier (0 to N-1) +// 2. A single page is a continguous block of data. +// So individual bytes are identified by a page number + offset +// +// The TS layout is not what is in memory. E.g. +// +// TS Page 2 |0123456789ABCD|0123456789ABCDEF| +// | | +// Arduino In Memory |--- Entity A ---| |--- Entity B -----| +// +// Further, the in memory entity may also not be contiguous or in the same +// order that TS expects +// +// So there is a 2 stage mapping: +// 1. Page # + Offset to entity +// 2. Offset to intra-entity byte + +// Page sizes as defined in the .ini file +constexpr const uint16_t ini_page_sizes[] = { 0, 128, 288, 288, 128, 288, 128, 240, 384, 192, 192, 288, 192, 128, 288 }; + +// What section of a 3D table the offset mapped to +enum table3D_section_t { + Value, // The values + axisX, // X axis + axisY, // Y axis + TableSectionNone // Should never happen! +}; + +// Stores enough information to access a table element +struct table_entity_t { + table3D *pTable; + uint8_t xIndex; // Value X index or X axis index + uint8_t yIndex; // Value Y index or Y axis index + table3D_section_t section; +}; + +struct entity_t { + // The entity that the offset mapped to + union { + table_entity_t table; + void *pData; + }; + uint8_t page; // The page the entity belongs to + uint16_t start; // The start position of the entity, in bytes, from the start of the page + uint16_t size; // Size of the entity in bytes + entity_type type; +}; + +// This will fail AND print the page number and required size +template +static inline void check_size() { + static_assert(ini_page_sizes[pageNum] >= min, "Size is off!"); +} + +// Handy table macros +#define TABLE_VALUE_END(size) ((uint16_t)size*(uint16_t)size) +#define TABLE_AXISX_END(size) (TABLE_VALUE_END(size)+(uint16_t)size) +#define TABLE_AXISY_END(size) (TABLE_AXISX_END(size)+(uint16_t)size) +#define TABLE_SIZE(size) TABLE_AXISY_END(size) + +// Precompute for performance +#define TABLE16_SIZE TABLE_SIZE(16) +#define TABLE8_SIZE TABLE_SIZE(8) +#define TABLE6_SIZE TABLE_SIZE(6) +#define TABLE4_SIZE TABLE_SIZE(4) + +// Macros + compile time constants = fast division/modulus +// +// The various fast division libraries, E.g. libdivide, use +// 32-bit operations for 16-bit division. Super slow. +#define OFFSET_TOVALUE_YINDEX(offset, size) ((uint8_t)((size-1) - (offset / size))) +#define OFFSET_TOVALUE_XINDEX(offset, size) ((uint8_t)(offset % size)) +#define OFFSET_TOAXIS_XINDEX(offset, size) ((uint8_t)(offset - TABLE_VALUE_END(size))) +#define OFFSET_TOAXIS_YINDEX(offset, size) ((uint8_t)((size-1) - (offset - TABLE_AXISX_END(size)))) + +#define NULL_TABLE \ + { nullptr, 0, 0, TableSectionNone } + +#define CREATE_PAGE_END(pageNum, pageSize) \ + { NULL_TABLE, .page = pageNum, .start = 0, .size = pageSize, .type = End } + +// Signal the end of a page +#define END_OF_PAGE(pageNum, pageSize) \ + check_size(); \ + return CREATE_PAGE_END(pageNum, pageSize); + +// If the offset is in range, create a None entity_t +#define CHECK_NOENTITY(offset, startByte, blockSize, pageNum) \ + if (offset < (startByte)+blockSize) \ + { \ + return { NULL_TABLE, .page = pageNum, .start = (startByte), .size = blockSize, .type = NoEntity }; \ + } + +// +#define TABLE_VALUE(offset, startByte, pTable, tableSize) \ + { pTable, \ + OFFSET_TOVALUE_XINDEX((offset-(startByte)), tableSize), \ + OFFSET_TOVALUE_YINDEX((offset-(startByte)), tableSize), \ + Value } + +#define TABLE_XAXIS(offset, startByte, pTable, tableSize) \ + { pTable, \ + OFFSET_TOAXIS_XINDEX((offset-(startByte)), tableSize), \ + 0U, \ + axisX } + +#define TABLE_YAXIS(offset, startByte, pTable, tableSize) \ + { pTable, \ + 0U, \ + OFFSET_TOAXIS_YINDEX((offset-(startByte)), tableSize), \ + axisY } + +#define TABLE_ENTITY(table_type, pageNum, startByte, tableSize) \ + { table_type, .page = pageNum, .start = (startByte), .size = TABLE_SIZE(tableSize), .type = Table } + +// If the offset is in range, create a Table entity_t +#define CHECK_TABLE(offset, startByte, pTable, tableSize, pageNum) \ + if (offset < (startByte)+TABLE_VALUE_END(tableSize)) \ + { \ + return TABLE_ENTITY(TABLE_VALUE(offset, startByte, pTable, tableSize), pageNum, startByte, tableSize); \ + } \ + if (offset < (startByte)+TABLE_AXISX_END(tableSize)) \ + { \ + return TABLE_ENTITY(TABLE_XAXIS(offset, startByte, pTable, tableSize), pageNum, startByte, tableSize); \ + } \ + if (offset < (startByte)+TABLE_AXISY_END(tableSize)) \ + { \ + return TABLE_ENTITY(TABLE_YAXIS(offset, startByte, pTable, tableSize), pageNum, startByte, tableSize); \ + } + +// If the offset is in range, create a Raw entity_t +#define CHECK_RAW(offset, startByte, pDataBlock, blockSize, pageNum) \ + if (offset < (startByte)+blockSize) \ + { \ + return { { (table3D*)pDataBlock, 0, 0, TableSectionNone }, .page = pageNum, .start = (startByte), .size = blockSize, .type = Raw }; \ + } + +// Does the heavy lifting of mapping page+offset to an entity +// +// Alternative implementation would be to encode the mapping into data structures +// That uses flash memory, which is scarce. And it was too slow. +static inline __attribute__((always_inline)) // <-- this is critical for performance +entity_t map_page_offset_to_entity_inline(uint8_t pageNumber, uint16_t offset) +{ + switch (pageNumber) + { + case 0: + return CREATE_PAGE_END(0, 0); + + case veMapPage: + CHECK_TABLE(offset, 0U, &fuelTable, 16, pageNumber) + END_OF_PAGE(veMapPage, TABLE16_SIZE); + break; + + case ignMapPage: //Ignition settings page (Page 2) + CHECK_TABLE(offset, 0U, &ignitionTable, 16, pageNumber) + END_OF_PAGE(ignMapPage, TABLE16_SIZE); + break; + + case afrMapPage: //Air/Fuel ratio target settings page + CHECK_TABLE(offset, 0U, &afrTable, 16, pageNumber) + END_OF_PAGE(afrMapPage, TABLE16_SIZE); + break; + + case boostvvtPage: //Boost, VVT and staging maps (all 8x8) + CHECK_TABLE(offset, 0U, &boostTable, 8, pageNumber) + CHECK_TABLE(offset, TABLE8_SIZE, &vvtTable, 8, pageNumber) + CHECK_TABLE(offset, TABLE8_SIZE*2, &stagingTable, 8, pageNumber) + END_OF_PAGE(boostvvtPage, TABLE8_SIZE*3); + break; + + case seqFuelPage: + CHECK_TABLE(offset, 0U, &trim1Table, 6, pageNumber) + CHECK_TABLE(offset, TABLE6_SIZE*1, &trim2Table, 6, pageNumber) + CHECK_TABLE(offset, TABLE6_SIZE*2, &trim3Table, 6, pageNumber) + CHECK_TABLE(offset, TABLE6_SIZE*3, &trim4Table, 6, pageNumber) + CHECK_TABLE(offset, TABLE6_SIZE*4, &trim5Table, 6, pageNumber) + CHECK_TABLE(offset, TABLE6_SIZE*5, &trim6Table, 6, pageNumber) + CHECK_TABLE(offset, TABLE6_SIZE*6, &trim7Table, 6, pageNumber) + CHECK_TABLE(offset, TABLE6_SIZE*7, &trim8Table, 6, pageNumber) + END_OF_PAGE(seqFuelPage, TABLE6_SIZE*8); + break; + + case fuelMap2Page: + CHECK_TABLE(offset, 0U, &fuelTable2, 16, pageNumber) + END_OF_PAGE(fuelMap2Page, TABLE16_SIZE); + break; + + case wmiMapPage: + CHECK_TABLE(offset, 0U, &wmiTable, 8, pageNumber) + CHECK_NOENTITY(offset, TABLE8_SIZE, 80, pageNumber) + CHECK_TABLE(offset, TABLE8_SIZE + 80, &dwellTable, 4, pageNumber) + END_OF_PAGE(wmiMapPage, TABLE8_SIZE + 80 + TABLE4_SIZE); + break; + + case ignMap2Page: + CHECK_TABLE(offset, 0U, &ignitionTable2, 16, pageNumber) + END_OF_PAGE(ignMap2Page, TABLE16_SIZE); + break; + + case veSetPage: + CHECK_RAW(offset, 0U, &configPage2, sizeof(configPage2), pageNumber) + END_OF_PAGE(veSetPage, sizeof(configPage2)); + break; + + case ignSetPage: + CHECK_RAW(offset, 0U, &configPage4, sizeof(configPage4), pageNumber) + END_OF_PAGE(ignSetPage, sizeof(configPage4)); + break; + + case afrSetPage: + CHECK_RAW(offset, 0U, &configPage6, sizeof(configPage6), pageNumber) + END_OF_PAGE(afrSetPage, sizeof(configPage6)); + break; + + case canbusPage: + CHECK_RAW(offset, 0U, &configPage9, sizeof(configPage9), pageNumber) + END_OF_PAGE(canbusPage, sizeof(configPage9)); + break; + + case warmupPage: + CHECK_RAW(offset, 0U, &configPage10, sizeof(configPage10), pageNumber) + END_OF_PAGE(warmupPage, sizeof(configPage10)); + break; + + case progOutsPage: + CHECK_RAW(offset, 0U, &configPage13, sizeof(configPage13), pageNumber) + END_OF_PAGE(progOutsPage, sizeof(configPage13)); + break; + + default: + abort(); // Unkown page number. Not a lot we can do. + break; + } +} + + +// Tables do not map linearly to the TS page address space, so special +// handling is necessary (we do not use the normal array layout for +// performance reasons elsewhere) +// +// We take the offset & map it to a single value, x-axis or y-axis element +static inline byte get_table_value(const table_entity_t &table) +{ + switch (table.section) + { + case Value: + return table.pTable->values[table.yIndex][table.xIndex]; + + case axisX: + return (byte)(table.pTable->axisX[table.xIndex] / getTableXAxisFactor(table.pTable)); + + case axisY: + return (byte)(table.pTable->axisY[table.yIndex] / getTableYAxisFactor(table.pTable)); + + default: return 0; // no-op + } + return 0U; +} + +static inline void set_table_value(const table_entity_t &table, int8_t value) +{ + switch (table.section) + { + case Value: + table.pTable->values[table.yIndex][table.xIndex] = value; + break; + + case axisX: + table.pTable->axisX[table.xIndex] = (int16_t)(value) * getTableXAxisFactor(table.pTable); + break; + + case axisY: + table.pTable->axisY[table.yIndex]= (int16_t)(value) * getTableYAxisFactor(table.pTable); + break; + + default: ; // no-op + } + table.pTable->cacheIsValid = false; //Invalid the tables cache to ensure a lookup of new values +} + +static inline byte* get_raw_value(const entity_t &entity, uint16_t offset) +{ + return (byte*)entity.pData + offset; +} + +// ============================ Page iteration support ====================== + +// Because the page iterators will not be called for every single byte +// inlining the mapping function is not performance critical. +// +// So save some memory. +static entity_t map_page_offset_to_entity(uint8_t pageNumber, uint16_t offset) +{ + return map_page_offset_to_entity_inline(pageNumber, offset); +} + +static inline page_iterator_t to_page_entity(entity_t mapped) +{ + return { { mapped.type==Table ? mapped.table.pTable : (table3D*)mapped.pData }, + .page=mapped.page, .start = mapped.start, .size = mapped.size, .type = mapped.type }; +} + +// ====================================== External functions ==================================== + +uint8_t getPageCount() +{ + return _countof(ini_page_sizes); +} + +uint16_t getPageSize(byte pageNum) +{ + return ini_page_sizes[pageNum]; +} + +void setPageValue(byte pageNum, uint16_t offset, byte value) +{ + entity_t entity = map_page_offset_to_entity_inline(pageNum, offset); + + switch (entity.type) + { + case Table: + set_table_value(entity.table, value); + break; + + case Raw: + *get_raw_value(entity, offset-entity.start) = value; + break; + + default: + break; + } +} + +byte getPageValue(byte page, uint16_t offset) +{ + entity_t entity = map_page_offset_to_entity_inline(page, offset); + + switch (entity.type) + { + case Table: + return get_table_value(entity.table); + break; + + case Raw: + return *get_raw_value(entity, offset); + break; + + default: return 0U; + } + return 0U; +} + +// Support iteration over a pages entities. +// Check for entity.type==End +page_iterator_t page_begin(byte pageNum) +{ + return to_page_entity(map_page_offset_to_entity(pageNum, 0U)); +} + +page_iterator_t advance(const page_iterator_t &it) +{ + return to_page_entity(map_page_offset_to_entity(it.page, it.start+it.size)); +} \ No newline at end of file diff --git a/speeduino/pages.h b/speeduino/pages.h new file mode 100644 index 00000000..b53bcfac --- /dev/null +++ b/speeduino/pages.h @@ -0,0 +1,80 @@ +#pragma once +#include +#include "table.h" + +/** + * Page count, as defined in the INI file + */ +uint8_t getPageCount(); + +/** + * Page size in bytes + */ +uint16_t getPageSize(byte pageNum /**< [in] The page number */ ); + +// These are the page numbers that the Tuner Studio serial protocol uses to transverse the different map and config pages. +#define veMapPage 2 +#define veSetPage 1 //Note that this and the veMapPage were swapped in Feb 2019 as the 'algorithm' field must be declared in the ini before it's used in the fuel table +#define ignMapPage 3 +#define ignSetPage 4//Config Page 2 +#define afrMapPage 5 +#define afrSetPage 6//Config Page 3 +#define boostvvtPage 7 +#define seqFuelPage 8 +#define canbusPage 9//Config Page 9 +#define warmupPage 10 //Config Page 10 +#define fuelMap2Page 11 +#define wmiMapPage 12 +#define progOutsPage 13 +#define ignMap2Page 14 + +// ============================== Per-byte page access ========================== + +/** + * Gets a single value from a page, with data aligned as per the ini file + */ +byte getPageValue( byte pageNum, /**< [in] The page number to retrieve data from. */ + uint16_t offset); /**< [in] The address in the page that should be returned. This is as per the page definition in the ini. */ + +/** + * Sets a single value from a page, with data aligned as per the ini file + */ +void setPageValue( byte pageNum, /**< [in] The page number to retrieve data from. */ + uint16_t offset, /**< [in] The address in the page that should be returned. This is as per the page definition in the ini. */ + byte value); /**< [in] The new value */ + +// ============================== Page Iteration ========================== + +// A logical TS page is actually multiple in memory entities. Allow iteration +// over those entities. + +// Type of entity +enum entity_type { + Raw, // A block of memory + Table, // A 3D table + NoEntity, // No entity, but a valid offset + End // The offset was past any known entity for the page +}; + +// A entity on a logical page. +struct page_iterator_t { + union { + table3D *pTable; + void *pData; + }; + uint8_t page; // The page the entity belongs to + uint16_t start; // The start position of the entity, in bytes, from the start of the page + uint16_t size; // Size of the entity in bytes + entity_type type; +}; + +/** + * Initiates iteration over a pages entities. + * Test `entity.type==End` to determine the end of the page. + */ +page_iterator_t page_begin(byte pageNum /**< [in] The page number to iterate over. */); + +/** + * Moves the iterator to the next sub-entity on the page + */ +page_iterator_t advance(const page_iterator_t &it /**< [in] The current iterator */); \ No newline at end of file diff --git a/speeduino/sensors.ino b/speeduino/sensors.ino index 417b2dda..a2ae3b1f 100644 --- a/speeduino/sensors.ino +++ b/speeduino/sensors.ino @@ -12,6 +12,7 @@ A full copy of the license may be found in the projects root directory #include "idle.h" #include "errors.h" #include "corrections.h" +#include "pages.h" void initialiseADC() { diff --git a/speeduino/storage.h b/speeduino/storage.h index 418dc803..6c08e72a 100644 --- a/speeduino/storage.h +++ b/speeduino/storage.h @@ -26,7 +26,7 @@ uint32_t readPageCRC32(byte); #else #define EEPROM_MAX_WRITE_BLOCK 30 //The maximum number of write operations that will be performed in one go. If we try to write to the EEPROM too fast (Each write takes ~3ms) then the rest of the system can hang) #endif -bool eepromWritesPending = false; +extern bool eepromWritesPending; /* Current layout of EEPROM data (Version 3) is as follows (All sizes are in bytes): diff --git a/speeduino/storage.ino b/speeduino/storage.ino index b3dc6e9a..57c2952b 100644 --- a/speeduino/storage.ino +++ b/speeduino/storage.ino @@ -10,6 +10,9 @@ A full copy of the license may be found in the projects root directory #include "comms.h" #include EEPROM_LIB_H //This is defined in the board .h files #include "storage.h" +#include "table_iterator.h" + +bool eepromWritesPending = false; void writeAllConfig() { @@ -78,13 +81,13 @@ namespace { return counter; } - inline int16_t writeTable(const table3D *pTable, int16_t xAxisDivisor, int16_t yAxisDivisor, int &index, int16_t counter) + inline int16_t writeTable(const table3D *pTable, int &index, int16_t counter) { counter = update(index, pTable->xSize, counter); ++index; counter = update(index, pTable->ySize, counter); ++index; counter = writeTableValues(pTable, index, counter); - counter = write_range_divisor(index, xAxisDivisor, pTable->axisX, pTable->axisX+pTable->xSize, counter); - return write_range_divisor(index, yAxisDivisor, pTable->axisY, pTable->axisY+pTable->ySize, counter); + counter = write_range_divisor(index, getTableXAxisFactor(pTable), pTable->axisX, pTable->axisX+pTable->xSize, counter); + return write_range_divisor(index, getTableYAxisFactor(pTable), pTable->axisY, pTable->axisY+pTable->ySize, counter); } } @@ -110,7 +113,7 @@ void writeConfig(byte tableNum) | 16x16 table itself + the 16 values along each of the axis -----------------------------------------------------*/ index = EEPROM_CONFIG1_XSIZE; - writeCounter = writeTable(&fuelTable, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&fuelTable, index, writeCounter); eepromWritesPending = writeCounter > EEPROM_MAX_WRITE_BLOCK; break; //That concludes the writing of the VE table @@ -132,7 +135,7 @@ void writeConfig(byte tableNum) -----------------------------------------------------*/ //Begin writing the Ignition table, basically the same thing as above index = EEPROM_CONFIG3_XSIZE; - writeCounter = writeTable(&ignitionTable, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&ignitionTable, index, writeCounter); eepromWritesPending = writeCounter > EEPROM_MAX_WRITE_BLOCK; break; @@ -153,7 +156,7 @@ void writeConfig(byte tableNum) -----------------------------------------------------*/ //Begin writing the Ignition table, basically the same thing as above index = EEPROM_CONFIG5_XSIZE; - writeCounter = writeTable(&afrTable, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&afrTable, index, writeCounter); eepromWritesPending = writeCounter > EEPROM_MAX_WRITE_BLOCK; break; @@ -174,11 +177,11 @@ void writeConfig(byte tableNum) -----------------------------------------------------*/ //Begin writing the 2 tables, basically the same thing as above but we're doing these 2 together (2 tables per page instead of 1) index = EEPROM_CONFIG7_XSIZE1; - writeCounter = writeTable(&boostTable, TABLE_RPM_MULTIPLIER, 1, index, writeCounter); + writeCounter = writeTable(&boostTable, index, writeCounter); index = EEPROM_CONFIG7_XSIZE2; - writeCounter = writeTable(&vvtTable, TABLE_RPM_MULTIPLIER, 1, index, writeCounter); + writeCounter = writeTable(&vvtTable, index, writeCounter); index = EEPROM_CONFIG7_XSIZE3; - writeCounter = writeTable(&stagingTable, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&stagingTable, index, writeCounter); eepromWritesPending = writeCounter > EEPROM_MAX_WRITE_BLOCK; break; @@ -189,21 +192,21 @@ void writeConfig(byte tableNum) -----------------------------------------------------*/ //Begin writing the 2 tables, basically the same thing as above but we're doing these 2 together (2 tables per page instead of 1) index = EEPROM_CONFIG8_XSIZE1; - writeCounter = writeTable(&trim1Table, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&trim1Table, index, writeCounter); index = EEPROM_CONFIG8_XSIZE2; - writeCounter = writeTable(&trim2Table, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&trim2Table, index, writeCounter); index = EEPROM_CONFIG8_XSIZE3; - writeCounter = writeTable(&trim3Table, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&trim3Table, index, writeCounter); index = EEPROM_CONFIG8_XSIZE4; - writeCounter = writeTable(&trim4Table, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&trim4Table, index, writeCounter); index = EEPROM_CONFIG8_XSIZE5; - writeCounter = writeTable(&trim5Table, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&trim5Table, index, writeCounter); index = EEPROM_CONFIG8_XSIZE6; - writeCounter = writeTable(&trim6Table, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&trim6Table, index, writeCounter); index = EEPROM_CONFIG8_XSIZE7; - writeCounter = writeTable(&trim7Table, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&trim7Table, index, writeCounter); index = EEPROM_CONFIG8_XSIZE8; - writeCounter = writeTable(&trim8Table, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&trim8Table, index, writeCounter); eepromWritesPending = writeCounter > EEPROM_MAX_WRITE_BLOCK; break; @@ -234,7 +237,7 @@ void writeConfig(byte tableNum) | 16x16 table itself + the 16 values along each of the axis -----------------------------------------------------*/ index = EEPROM_CONFIG11_XSIZE; - writeCounter = writeTable(&fuelTable2, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&fuelTable2, index, writeCounter); eepromWritesPending = writeCounter > EEPROM_MAX_WRITE_BLOCK; break; //That concludes the writing of the 2nd fuel table @@ -246,9 +249,9 @@ void writeConfig(byte tableNum) | 4x4 Dwell table itself + the 4 values along each of the axis -----------------------------------------------------*/ index = EEPROM_CONFIG12_XSIZE; - writeCounter = writeTable(&wmiTable, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&wmiTable, index, writeCounter); index = EEPROM_CONFIG12_XSIZE3; - writeCounter = writeTable(&dwellTable, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&dwellTable, index, writeCounter); eepromWritesPending = writeCounter > EEPROM_MAX_WRITE_BLOCK; break; @@ -268,7 +271,7 @@ void writeConfig(byte tableNum) -----------------------------------------------------*/ //Begin writing the Ignition table, basically the same thing as above index = EEPROM_CONFIG14_XSIZE; - writeCounter = writeTable(&ignitionTable2, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER, index, writeCounter); + writeCounter = writeTable(&ignitionTable2, index, writeCounter); eepromWritesPending = writeCounter > EEPROM_MAX_WRITE_BLOCK; break; @@ -319,29 +322,27 @@ namespace return index; } - inline int loadTableAxisX(table3D *pTable, int index, int xAxisMultiplier) + inline int loadTableAxisX(table3D *pTable, int index) { - return load_range_multiplier(index, pTable->axisX, pTable->axisX+pTable->xSize, xAxisMultiplier); + return load_range_multiplier(index, pTable->axisX, pTable->axisX+pTable->xSize, getTableXAxisFactor(pTable)); } - inline int loadTableAxisY(table3D *pTable, int index, int yAxisMultiplier) + inline int loadTableAxisY(table3D *pTable, int index) { - return load_range_multiplier(index, pTable->axisY, pTable->axisY+pTable->ySize, yAxisMultiplier); + return load_range_multiplier(index, pTable->axisY, pTable->axisY+pTable->ySize, getTableYAxisFactor(pTable)); } - inline int loadTable(table3D *pTable, int index, int xAxisMultiplier, int yAxisMultiplier) + inline int loadTable(table3D *pTable, int index) { return loadTableAxisY(pTable, loadTableAxisX(pTable, - loadTableValues(pTable, index), - xAxisMultiplier), - yAxisMultiplier); + loadTableValues(pTable, index))); } } void loadConfig() { - loadTable(&fuelTable, EEPROM_CONFIG1_MAP, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); + loadTable(&fuelTable, EEPROM_CONFIG1_MAP); load_range(EEPROM_CONFIG2_START, (byte *)&configPage2, (byte *)&configPage2+sizeof(configPage2)); //That concludes the reading of the VE table @@ -349,32 +350,32 @@ void loadConfig() //IGNITION CONFIG PAGE (2) //Begin writing the Ignition table, basically the same thing as above - loadTable(&ignitionTable, EEPROM_CONFIG3_MAP, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); + loadTable(&ignitionTable, EEPROM_CONFIG3_MAP); load_range(EEPROM_CONFIG4_START, (byte *)&configPage4, (byte *)&configPage4+sizeof(configPage4)); //********************************************************************************************************************************************************************************* //AFR TARGET CONFIG PAGE (3) //Begin writing the Ignition table, basically the same thing as above - loadTable(&afrTable, EEPROM_CONFIG5_MAP, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); + loadTable(&afrTable, EEPROM_CONFIG5_MAP); load_range(EEPROM_CONFIG6_START, (byte *)&configPage6, (byte *)&configPage6+sizeof(configPage6)); //********************************************************************************************************************************************************************************* // Boost and vvt tables load - loadTable(&boostTable, EEPROM_CONFIG7_MAP1, TABLE_RPM_MULTIPLIER, 1); - loadTable(&vvtTable, EEPROM_CONFIG7_MAP2, TABLE_RPM_MULTIPLIER, 1); - loadTable(&stagingTable, EEPROM_CONFIG7_MAP3, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); + loadTable(&boostTable, EEPROM_CONFIG7_MAP1); + loadTable(&vvtTable, EEPROM_CONFIG7_MAP2); + loadTable(&stagingTable, EEPROM_CONFIG7_MAP3); //********************************************************************************************************************************************************************************* // Fuel trim tables load - loadTable(&trim1Table, EEPROM_CONFIG8_MAP1, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); - loadTable(&trim2Table, EEPROM_CONFIG8_MAP2, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); - loadTable(&trim3Table, EEPROM_CONFIG8_MAP3, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); - loadTable(&trim4Table, EEPROM_CONFIG8_MAP4, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); - loadTable(&trim5Table, EEPROM_CONFIG8_MAP5, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); - loadTable(&trim6Table, EEPROM_CONFIG8_MAP6, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); - loadTable(&trim7Table, EEPROM_CONFIG8_MAP7, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); - loadTable(&trim8Table, EEPROM_CONFIG8_MAP8, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); + loadTable(&trim1Table, EEPROM_CONFIG8_MAP1); + loadTable(&trim2Table, EEPROM_CONFIG8_MAP2); + loadTable(&trim3Table, EEPROM_CONFIG8_MAP3); + loadTable(&trim4Table, EEPROM_CONFIG8_MAP4); + loadTable(&trim5Table, EEPROM_CONFIG8_MAP5); + loadTable(&trim6Table, EEPROM_CONFIG8_MAP6); + loadTable(&trim7Table, EEPROM_CONFIG8_MAP7); + loadTable(&trim8Table, EEPROM_CONFIG8_MAP8); //********************************************************************************************************************************************************************************* //canbus control page load @@ -387,12 +388,12 @@ void loadConfig() //********************************************************************************************************************************************************************************* //Fuel table 2 (See storage.h for data layout) - loadTable(&fuelTable2, EEPROM_CONFIG11_MAP, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); + loadTable(&fuelTable2, EEPROM_CONFIG11_MAP); //********************************************************************************************************************************************************************************* // WMI and Dwell table load - loadTable(&wmiTable, EEPROM_CONFIG12_MAP, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); - loadTable(&dwellTable, EEPROM_CONFIG12_MAP3, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); + loadTable(&wmiTable, EEPROM_CONFIG12_MAP); + loadTable(&dwellTable, EEPROM_CONFIG12_MAP3); //********************************************************************************************************************************************************************************* //CONFIG PAGE (13) @@ -402,7 +403,7 @@ void loadConfig() //SECOND IGNITION CONFIG PAGE (14) //Begin writing the Ignition table, basically the same thing as above - loadTable(&ignitionTable2, EEPROM_CONFIG14_MAP, TABLE_RPM_MULTIPLIER, TABLE_LOAD_MULTIPLIER); + loadTable(&ignitionTable2, EEPROM_CONFIG14_MAP); //********************************************************************************************************************************************************************************* } @@ -469,7 +470,7 @@ Note: Each pages requires 4 bytes for its CRC32. These are stored in reverse pag void storePageCRC32(byte pageNo, uint32_t crc32_val) { uint16_t address; //Start address for the relevant page - address = EEPROM_PAGE_CRC32 + ((NUM_PAGES - pageNo) * 4); + address = EEPROM_PAGE_CRC32 + ((getPageCount() - pageNo) * 4); //One = Most significant -> Four = Least significant byte byte four = (crc32_val & 0xFF); @@ -490,7 +491,7 @@ Retrieves and returns the 4 byte CRC32 for a given page from EEPROM uint32_t readPageCRC32(byte pageNo) { uint16_t address; //Start address for the relevant page - address = EEPROM_PAGE_CRC32 + ((NUM_PAGES - pageNo) * 4); + address = EEPROM_PAGE_CRC32 + ((getPageCount() - pageNo) * 4); //Read the 4 bytes from the eeprom memory. uint32_t four = EEPROM.read(address); diff --git a/speeduino/table.h b/speeduino/table.h index d0a2e04a..7b0aa55a 100644 --- a/speeduino/table.h +++ b/speeduino/table.h @@ -44,8 +44,6 @@ YOU MUST UPDATE THE TABLE COUNTS IN THE LINE BELOW WHENEVER A NEW TABLE IS ADDED */ #define TABLE_HEAP_SIZE ((5 * TABLE3D_SIZE_16) + (4 * TABLE3D_SIZE_8) + (8 * TABLE3D_SIZE_6) + (1 * TABLE3D_SIZE_4) + 1) -static uint8_t _3DTable_heap[TABLE_HEAP_SIZE]; -static uint16_t _heap_pointer = 0; /* The 2D table can contain either 8-bit (byte) or 16-bit (int) values diff --git a/speeduino/table.ino b/speeduino/table.ino index fe17f994..8c6cfeba 100644 --- a/speeduino/table.ino +++ b/speeduino/table.ino @@ -36,6 +36,9 @@ void table2D_setSize(struct table2D* targetTable, byte newSize) } */ +static uint8_t _3DTable_heap[TABLE_HEAP_SIZE]; +static uint16_t _heap_pointer = 0; + void* heap_alloc(uint16_t size) { uint8_t* value = nullptr; diff --git a/speeduino/table_iterator.h b/speeduino/table_iterator.h new file mode 100644 index 00000000..0d92d710 --- /dev/null +++ b/speeduino/table_iterator.h @@ -0,0 +1,101 @@ +#pragma once +#include +#include "table.h" +#include "globals.h" + +inline int8_t getTableYAxisFactor(const table3D *pTable) +{ + return pTable==&boostTable || pTable==&vvtTable ? 1 : TABLE_LOAD_MULTIPLIER; +} + +inline int8_t getTableXAxisFactor(const table3D *) +{ + return TABLE_RPM_MULTIPLIER; +} + +// ========================= AXIS ITERATION ========================= +typedef struct table_axis_iterator_t +{ + int16_t *_pAxis; + int16_t *_pAxisEnd; + int16_t _axisFactor; + int8_t _stride; +} table_axis_iterator_t; + +inline table_axis_iterator_t& advance_axis(table_axis_iterator_t &it) +{ + it._pAxis += it._stride; + return it; +} + +inline bool at_end(const table_axis_iterator_t &it) +{ + return it._pAxis == it._pAxisEnd; +} + +inline byte get_value(const table_axis_iterator_t &it) +{ + return *it._pAxis / it._axisFactor; +} + +inline void set_value(table_axis_iterator_t &it, byte value) +{ + *it._pAxis = value * it._axisFactor; +} + +inline table_axis_iterator_t y_begin(const table3D *pTable) +{ + return { pTable->axisY+(pTable->ySize-1), + (pTable->axisY)-1, getTableYAxisFactor(pTable), + -1 }; +} + +inline table_axis_iterator_t x_begin(const table3D *pTable) +{ + return { pTable->axisX, pTable->axisX+pTable->xSize, getTableXAxisFactor(pTable), 1 }; +} + +// ========================= INTRA-ROW ITERATION ========================= + +// A table row is directly iterable & addressable. +typedef struct table_row_t { + byte *pValue; + byte *pEnd; +} table_row_t; + +inline bool at_end(const table_row_t &it) +{ + return it.pValue == it.pEnd; +} + +// ========================= INTER-ROW ITERATION ========================= +typedef struct table_row_iterator_t +{ + byte **pRowsStart; + byte **pRowsEnd; + uint8_t rowWidth; +} table_row_iterator_t; + +inline table_row_iterator_t rows_begin(const table3D *pTable) +{ + return { pTable->values + (pTable->ySize-1), + pTable->values - 1, + pTable->xSize + }; +}; + +inline bool at_end(const table_row_iterator_t &it) +{ + return it.pRowsStart == it.pRowsEnd; +} + +inline table_row_t get_row(const table_row_iterator_t &it) +{ + return { *it.pRowsStart, (*it.pRowsStart) + it.rowWidth }; +} + +inline table_row_iterator_t& advance_row(table_row_iterator_t &it) +{ + --it.pRowsStart; + return it; +} \ No newline at end of file diff --git a/speeduino/utilities.h b/speeduino/utilities.h index b3f91529..a87d7e32 100644 --- a/speeduino/utilities.h +++ b/speeduino/utilities.h @@ -21,16 +21,19 @@ These are some utility functions and variables used through the main code #define BITWISE_OR 2 #define BITWISE_XOR 3 -uint16_t ioDelay[sizeof(configPage13.outputPin)]; -uint8_t pinIsValid = 0; +extern uint16_t ioDelay[sizeof(configPage13.outputPin)]; +extern uint8_t pinIsValid; //uint8_t outputPin[sizeof(configPage13.outputPin)]; void setResetControlPinState(); byte pinTranslate(byte); byte pinTranslateAnalog(byte); -uint32_t calculateCRC32(byte); void initialiseProgrammableIO(); void checkProgrammableIO(); int16_t ProgrammableIOGetData(uint16_t index); +#define _countof(x) (sizeof(x) / sizeof (x[0])) +#define _end_range_address(array) (array + _countof(array)) +#define _end_range_byte_address(array) (((byte*)array) + sizeof(array)) + #endif // UTILS_H diff --git a/speeduino/utilities.ino b/speeduino/utilities.ino index 77d47414..478b6769 100644 --- a/speeduino/utilities.ino +++ b/speeduino/utilities.ino @@ -9,9 +9,10 @@ #include "utilities.h" #include "decoders.h" #include "comms.h" -#include "src/FastCRC/FastCRC.h" -FastCRC32 CRC32; +uint16_t ioDelay[sizeof(configPage13.outputPin)]; +uint8_t pinIsValid = 0; + //This function performs a translation between the pin list that appears in TS and the actual pin numbers //For the digital IO, this will simply return the same number as the rawPin value as those are mapped directly. @@ -99,167 +100,6 @@ void setResetControlPinState() } } -/* -Calculates and returns the CRC32 value of a given page of memory -*/ -uint32_t calculateCRC32(byte pageNo) -{ - uint32_t CRC32_val; - byte raw_value; - void* pnt_configPage; - - //This sucks (again) for all the 3D map pages that have to have a translation performed - switch(pageNo) - { - case veMapPage: - //Confirmed working - raw_value = getPageValue(veMapPage, 0); - CRC32_val = CRC32.crc32(&raw_value, 1, false); - for(uint16_t x=1; x< npage_size[veMapPage]; x++) - //for(uint16_t x=1; x< 288; x++) - { - raw_value = getPageValue(veMapPage, x); - CRC32_val = CRC32.crc32_upd(&raw_value, 1, false); - } - //Do a manual reflection of the CRC32 value - CRC32_val = ~CRC32_val; - break; - - case veSetPage: - //Confirmed working - pnt_configPage = &configPage2; //Create a pointer to Page 1 in memory - CRC32_val = CRC32.crc32((byte *)pnt_configPage, sizeof(configPage2) ); - break; - - case ignMapPage: - //Confirmed working - raw_value = getPageValue(ignMapPage, 0); - CRC32_val = CRC32.crc32(&raw_value, 1, false); - for(uint16_t x=1; x< npage_size[ignMapPage]; x++) - { - raw_value = getPageValue(ignMapPage, x); - CRC32_val = CRC32.crc32_upd(&raw_value, 1, false); - } - //Do a manual reflection of the CRC32 value - CRC32_val = ~CRC32_val; - break; - - case ignSetPage: - //Confirmed working - pnt_configPage = &configPage4; //Create a pointer to Page 4 in memory - CRC32_val = CRC32.crc32((byte *)pnt_configPage, sizeof(configPage4) ); - break; - - case afrMapPage: - //Confirmed working - raw_value = getPageValue(afrMapPage, 0); - CRC32_val = CRC32.crc32(&raw_value, 1, false); - for(uint16_t x=1; x< npage_size[afrMapPage]; x++) - { - raw_value = getPageValue(afrMapPage, x); - CRC32_val = CRC32.crc32_upd(&raw_value, 1, false); - } - //Do a manual reflection of the CRC32 value - CRC32_val = ~CRC32_val; - break; - - case afrSetPage: - //Confirmed working - pnt_configPage = &configPage6; //Create a pointer to Page 4 in memory - CRC32_val = CRC32.crc32((byte *)pnt_configPage, sizeof(configPage6) ); - break; - - case boostvvtPage: - //Confirmed working - raw_value = getPageValue(boostvvtPage, 0); - CRC32_val = CRC32.crc32(&raw_value, 1, false); - for(uint16_t x=1; x< npage_size[boostvvtPage]; x++) - { - raw_value = getPageValue(boostvvtPage, x); - CRC32_val = CRC32.crc32_upd(&raw_value, 1, false); - } - //Do a manual reflection of the CRC32 value - CRC32_val = ~CRC32_val; - break; - - case seqFuelPage: - //Confirmed working - raw_value = getPageValue(seqFuelPage, 0); - CRC32_val = CRC32.crc32(&raw_value, 1, false); - for(uint16_t x=1; x< npage_size[seqFuelPage]; x++) - { - raw_value = getPageValue(seqFuelPage, x); - CRC32_val = CRC32.crc32_upd(&raw_value, 1, false); - } - //Do a manual reflection of the CRC32 value - CRC32_val = ~CRC32_val; - break; - - case canbusPage: - //Confirmed working - pnt_configPage = &configPage9; //Create a pointer to Page 9 in memory - CRC32_val = CRC32.crc32((byte *)pnt_configPage, sizeof(configPage9) ); - break; - - case warmupPage: - //Confirmed working - pnt_configPage = &configPage10; //Create a pointer to Page 10 in memory - CRC32_val = CRC32.crc32((byte *)pnt_configPage, sizeof(configPage10) ); - break; - - case fuelMap2Page: - //Confirmed working - raw_value = getPageValue(fuelMap2Page, 0); - CRC32_val = CRC32.crc32(&raw_value, 1, false); - for(uint16_t x=1; x< npage_size[fuelMap2Page]; x++) - //for(uint16_t x=1; x< 288; x++) - { - raw_value = getPageValue(fuelMap2Page, x); - CRC32_val = CRC32.crc32_upd(&raw_value, 1, false); - } - //Do a manual reflection of the CRC32 value - CRC32_val = ~CRC32_val; - break; - - case wmiMapPage: - //Confirmed working - raw_value = getPageValue(wmiMapPage, 0); - CRC32_val = CRC32.crc32(&raw_value, 1, false); - for(uint16_t x=1; x< npage_size[wmiMapPage]; x++) - { - raw_value = getPageValue(wmiMapPage, x); - CRC32_val = CRC32.crc32_upd(&raw_value, 1, false); - } - //Do a manual reflection of the CRC32 value - CRC32_val = ~CRC32_val; - break; - - case progOutsPage: - //Confirmed working - pnt_configPage = &configPage13; //Create a pointer to Page 10 in memory - CRC32_val = CRC32.crc32((byte *)pnt_configPage, sizeof(configPage13) ); - break; - - case ignMap2Page: - //Confirmed working - raw_value = getPageValue(ignMap2Page, 0); - CRC32_val = CRC32.crc32(&raw_value, 1, false); - for(uint16_t x=1; x< npage_size[ignMap2Page]; x++) - { - raw_value = getPageValue(ignMap2Page, x); - CRC32_val = CRC32.crc32_upd(&raw_value, 1, false); - } - //Do a manual reflection of the CRC32 value - CRC32_val = ~CRC32_val; - break; - - default: - CRC32_val = 0; - break; - } - - return CRC32_val; -} //********************************************************************************************************************************************************************************* void initialiseProgrammableIO()