diff --git a/firmware/console/tunerstudio/tunerstudio.cpp b/firmware/console/tunerstudio/tunerstudio.cpp index 3333068ca5..8a357d9c9d 100644 --- a/firmware/console/tunerstudio/tunerstudio.cpp +++ b/firmware/console/tunerstudio/tunerstudio.cpp @@ -1,10 +1,50 @@ /** * @file tunerstudio.cpp + * @brief Binary protocol implementation + * + * This implementation would not happen without the documentation + * provided by Jon Zeeff (jon@zeeff.com) + * + * * @brief Integration with EFI Analytics Tuner Studio software * - * todo: merge this file with tunerstudio_algo.c? + * Tuner Studio has a really simple protocol, a minimal implementation + * capable of displaying current engine state on the gauges would + * require only two commands: queryCommand and ochGetCommand + * + * queryCommand: + * Communication initialization command. TunerStudio sends a single byte H + * ECU response: + * One of the known ECU id strings. We are using "MShift v0.01" id string. + * + * ochGetCommand: + * Request for output channels state.TunerStudio sends a single byte O + * ECU response: + * A snapshot of output channels as described in [OutputChannels] section of the .ini file + * The length of this block is 'ochBlockSize' property of the .ini file + * + * These two commands are enough to get working gauges. In order to start configuring the ECU using + * tuner studio, three more commands should be implemented: + * + * todo: merge this file with tunerstudio.c? + * + * + * @date Oct 22, 2013 + * @author Andrey Belomutskiy, (c) 2012-2015 + * + * This file is part of rusEfi - see http://rusefi.com + * + * rusEfi is free software; you can redistribute it and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . * - * @date Aug 26, 2013 * @author Andrey Belomutskiy, (c) 2012-2015 * * This file is part of rusEfi - see http://rusefi.com @@ -30,26 +70,18 @@ #include "main_trigger_callback.h" #include "flash_main.h" -#include "tunerstudio_algo.h" +#include "tunerstudio_io.h" #include "tunerstudio_configuration.h" #include "malfunction_central.h" #include "console_io.h" #include "crc.h" +#include +#include "engine_configuration.h" +#include "svnversion.h" + #if EFI_TUNER_STUDIO || defined(__DOXYGEN__) -#if EFI_PROD_CODE -#include "pin_repository.h" -#include "usbconsole.h" -#include "map_averaging.h" -extern SerialUSBDriver SDU1; -#define CONSOLE_DEVICE &SDU1 - -#define TS_SERIAL_UART_DEVICE &SD3 - -static SerialConfig tsSerialConfig = { 0, 0, USART_CR2_STOP1_BITS | USART_CR2_LINEN, 0 }; -#endif /* EFI_PROD_CODE */ - EXTERN_ENGINE ; @@ -58,21 +90,6 @@ extern short currentPageId; // in MS, that's 10 seconds #define TS_READ_TIMEOUT 10000 -#define PROTOCOL "001" - -BaseChannel * getTsSerialDevice(void) { -#if EFI_PROD_CODE - if (isSerialOverUart()) { - // if console uses UART then TS uses USB - return (BaseChannel *) &SDU1; - } else { - return (BaseChannel *) TS_SERIAL_UART_DEVICE; - } -#else - return (BaseChannel *) TS_SIMULATOR_PORT; -#endif -} - Logging *tsLogger; extern persistent_config_s configWorkingCopy; @@ -80,12 +97,8 @@ extern persistent_config_container_s persistentState; static efitimems_t previousWriteReportMs = 0; -/** - * we use 'blockingFactor = 256' in rusefi.ini - * todo: should we just do (256 + CRC_WRAPPING_SIZE) ? - */ - -static uint8_t crcIoBuffer[300]; +static uint8_t crcReadBuffer[300]; +extern uint8_t crcWriteBuffer[300]; static int ts_serial_ready(void) { #if EFI_PROD_CODE @@ -147,13 +160,6 @@ static void setTsSpeed(int value) { printTsStats(); } -void tunerStudioWriteData(const uint8_t * buffer, int size) { - int transferred = chSequentialStreamWrite(getTsSerialDevice(), buffer, size); - if (transferred != size) { - scheduleMsg(tsLogger, "!!! NOT ACCEPTED %d out of %d !!!", transferred, size); - } -} - void tunerStudioDebug(const char *msg) { #if EFI_TUNER_STUDIO_VERBOSE scheduleMsg(tsLogger, "%s", msg); @@ -348,77 +354,6 @@ static TunerStudioReadRequest readRequest; static TunerStudioWriteChunkRequest writeChunkRequest; static short int pageIn; -/** - * @return true if legacy command was processed, false otherwise - */ -static bool handlePlainCommand(uint8_t command) { - if (command == TS_HELLO_COMMAND || command == TS_HELLO_COMMAND_DEPRECATED) { - scheduleMsg(tsLogger, "Got naked Query command"); - handleQueryCommand(TS_PLAIN); - return true; - } else if (command == 't' || command == 'T') { - handleTestCommand(); - return true; - } else if (command == TS_PAGE_COMMAND) { - int recieved = chSequentialStreamRead(getTsSerialDevice(), (uint8_t * )&pageIn, sizeof(pageIn)); - // todo: validate 'recieved' value - handlePageSelectCommand(TS_PLAIN, pageIn); - return true; - } else if (command == TS_BURN_COMMAND) { - scheduleMsg(tsLogger, "Got naked BURN"); - uint16_t page; - int recieved = chSequentialStreamRead(getTsSerialDevice(), (uint8_t * )&page, - sizeof(page)); - if (recieved != sizeof(page)) { - tunerStudioError("ERROR: Not enough for plain burn"); - return true; - } - handleBurnCommand(TS_PLAIN, page); - return true; - } else if (command == TS_CHUNK_WRITE_COMMAND) { - scheduleMsg(tsLogger, "Got naked CHUNK_WRITE"); - int recieved = chSequentialStreamRead(getTsSerialDevice(), (uint8_t * )&writeChunkRequest, - sizeof(writeChunkRequest)); - if (recieved != sizeof(writeChunkRequest)) { - scheduleMsg(tsLogger, "ERROR: Not enough for plain chunk write header: %d", recieved); - tsState.errorCounter++; - return true; - } - recieved = chSequentialStreamRead(getTsSerialDevice(), (uint8_t * )&crcIoBuffer, writeChunkRequest.count); - if (recieved != writeChunkRequest.count) { - scheduleMsg(tsLogger, "ERROR: Not enough for plain chunk write content: %d while expecting %d", recieved, writeChunkRequest.count); - tsState.errorCounter++; - return true; - } - currentPageId = writeChunkRequest.page; - - handleWriteChunkCommand(TS_PLAIN, writeChunkRequest.offset, writeChunkRequest.count, (uint8_t * )&crcIoBuffer); - return true; - } else if (command == TS_READ_COMMAND) { - //scheduleMsg(logger, "Got naked READ PAGE???"); - int recieved = chSequentialStreamRead(getTsSerialDevice(), (uint8_t * )&readRequest, sizeof(readRequest)); - if (recieved != sizeof(readRequest)) { - tunerStudioError("Not enough for plain read header"); - return true; - } - handlePageReadCommand(TS_PLAIN, readRequest.page, readRequest.offset, readRequest.count); - return true; - } else if (command == TS_OUTPUT_COMMAND) { - //scheduleMsg(logger, "Got naked Channels???"); - handleOutputChannelsCommand(TS_PLAIN); - return true; - } else if (command == TS_LEGACY_HELLO_COMMAND) { - tunerStudioDebug("ignoring LEGACY_HELLO_COMMAND"); - return true; - } else if (command == TS_COMMAND_F) { - tunerStudioDebug("not ignoring F"); - tunerStudioWriteData((const uint8_t *) PROTOCOL, strlen(PROTOCOL)); - return true; - } else { - return false; - } -} - static bool isKnownCommand(char command) { return command == TS_HELLO_COMMAND || command == TS_READ_COMMAND || command == TS_OUTPUT_COMMAND || command == TS_PAGE_COMMAND || command == TS_BURN_COMMAND || command == TS_SINGLE_WRITE_COMMAND @@ -428,32 +363,13 @@ static bool isKnownCommand(char command) { static uint8_t firstByte; static uint8_t secondByte; -#define CRC_VALUE_SIZE 4 -// todo: double-check this -#define CRC_WRAPPING_SIZE 7 - static msg_t tsThreadEntryPoint(void *arg) { (void) arg; chRegSetThreadName("tunerstudio thread"); -#if EFI_PROD_CODE - if (isSerialOverUart()) { - print("TunerStudio over USB serial"); - /** - * This method contains a long delay, that's the reason why this is not done on the main thread - */ - usb_serial_start(); - } else { - - print("TunerStudio over USART"); - mySetPadMode("tunerstudio rx", TS_SERIAL_RX_PORT, TS_SERIAL_RX_PIN, PAL_MODE_ALTERNATE(TS_SERIAL_AF)); - mySetPadMode("tunerstudio tx", TS_SERIAL_TX_PORT, TS_SERIAL_TX_PIN, PAL_MODE_ALTERNATE(TS_SERIAL_AF)); - - tsSerialConfig.speed = boardConfiguration->tunerStudioSerialSpeed; - - sdStart(TS_SERIAL_UART_DEVICE, &tsSerialConfig); - } -#endif /* EFI_PROD_CODE */ +#if EFI_PROD_CODE || defined(__DOXYGEN__) + startTsPort(); +#endif int wasReady = false; while (true) { @@ -489,20 +405,20 @@ static msg_t tsThreadEntryPoint(void *arg) { uint32_t incomingPacketSize = firstByte * 256 + secondByte; - if (incomingPacketSize == 0 || incomingPacketSize > (sizeof(crcIoBuffer) - CRC_WRAPPING_SIZE)) { + if (incomingPacketSize == 0 || incomingPacketSize > (sizeof(crcReadBuffer) - CRC_WRAPPING_SIZE)) { scheduleMsg(tsLogger, "TunerStudio: invalid size: %d", incomingPacketSize); tunerStudioError("ERROR: CRC header size"); sendErrorCode(); continue; } - recieved = chnReadTimeout(getTsSerialDevice(), crcIoBuffer, 1, MS2ST(TS_READ_TIMEOUT)); + recieved = chnReadTimeout(getTsSerialDevice(), crcReadBuffer, 1, MS2ST(TS_READ_TIMEOUT)); if (recieved != 1) { tunerStudioError("ERROR: did not receive command"); continue; } - char command = crcIoBuffer[0]; + char command = crcReadBuffer[0]; if (!isKnownCommand(command)) { scheduleMsg(tsLogger, "unexpected command %x", command); sendErrorCode(); @@ -511,7 +427,7 @@ static msg_t tsThreadEntryPoint(void *arg) { // scheduleMsg(logger, "TunerStudio: reading %d+4 bytes(s)", incomingPacketSize); - recieved = chnReadTimeout(getTsSerialDevice(), (uint8_t * ) (crcIoBuffer + 1), + recieved = chnReadTimeout(getTsSerialDevice(), (uint8_t * ) (crcReadBuffer + 1), incomingPacketSize + CRC_VALUE_SIZE - 1, MS2ST(TS_READ_TIMEOUT)); int expectedSize = incomingPacketSize + CRC_VALUE_SIZE - 1; if (recieved != expectedSize) { @@ -521,17 +437,17 @@ static msg_t tsThreadEntryPoint(void *arg) { continue; } - uint32_t expectedCrc = *(uint32_t*) (crcIoBuffer + incomingPacketSize); + uint32_t expectedCrc = *(uint32_t*) (crcReadBuffer + incomingPacketSize); expectedCrc = SWAP_UINT32(expectedCrc); - uint32_t actualCrc = crc32(crcIoBuffer, incomingPacketSize); + uint32_t actualCrc = crc32(crcReadBuffer, incomingPacketSize); if (actualCrc != expectedCrc) { - scheduleMsg(tsLogger, "TunerStudio: CRC %x %x %x %x", crcIoBuffer[incomingPacketSize + 0], - crcIoBuffer[incomingPacketSize + 1], crcIoBuffer[incomingPacketSize + 2], - crcIoBuffer[incomingPacketSize + 3]); + scheduleMsg(tsLogger, "TunerStudio: CRC %x %x %x %x", crcReadBuffer[incomingPacketSize + 0], + crcReadBuffer[incomingPacketSize + 1], crcReadBuffer[incomingPacketSize + 2], + crcReadBuffer[incomingPacketSize + 3]); - scheduleMsg(tsLogger, "TunerStudio: command %c actual CRC %x/expected %x", crcIoBuffer[0], actualCrc, + scheduleMsg(tsLogger, "TunerStudio: command %c actual CRC %x/expected %x", crcReadBuffer[0], actualCrc, expectedCrc); tunerStudioError("ERROR: CRC issue"); continue; @@ -540,7 +456,7 @@ static msg_t tsThreadEntryPoint(void *arg) { // scheduleMsg(logger, "TunerStudio: P00-07 %x %x %x %x %x %x %x %x", crcIoBuffer[0], crcIoBuffer[1], // crcIoBuffer[2], crcIoBuffer[3], crcIoBuffer[4], crcIoBuffer[5], crcIoBuffer[6], crcIoBuffer[7]); - int success = tunerStudioHandleCrcCommand(crcIoBuffer, incomingPacketSize); + int success = tunerStudioHandleCrcCommand(crcReadBuffer, incomingPacketSize); if (!success) print("got unexpected TunerStudio command %x:%c\r\n", command, command); @@ -554,22 +470,178 @@ void syncTunerStudioCopy(void) { memcpy(&configWorkingCopy, &persistentState.persistentConfiguration, sizeof(persistent_config_s)); } +tunerstudio_counters_s tsState; +TunerStudioOutputChannels tsOutputChannels; /** - * Adds size to the beginning of a packet and a crc32 at the end. Then send the packet. + * this is a local copy of the configuration. Any changes to this copy + * have no effect until this copy is explicitly propagated to the main working copy */ -void tunerStudioWriteCrcPacket(const uint8_t command, const void *buf, const uint16_t size) { - // todo: max size validation - *(uint16_t *) crcIoBuffer = SWAP_UINT16(size + 1); // packet size including command - *(uint8_t *) (crcIoBuffer + 2) = command; - if (size != 0) - memcpy(crcIoBuffer + 3, buf, size); - // CRC on whole packet - uint32_t crc = crc32((void *) (crcIoBuffer + 2), (uint32_t) (size + 1)); - *(uint32_t *) (crcIoBuffer + 2 + 1 + size) = SWAP_UINT32(crc); +persistent_config_s configWorkingCopy; -// scheduleMsg(logger, "TunerStudio: CRC command %x size %d", command, size); +short currentPageId; - tunerStudioWriteData(crcIoBuffer, size + 2 + 1 + 4); // with size, command and CRC +void tunerStudioError(const char *msg) { + tunerStudioDebug(msg); + tsState.errorCounter++; +} + +/** + * Query with CRC takes place while re-establishing connection + * Query without CRC takes place on TunerStudio startup + */ +void handleQueryCommand(ts_response_format_e mode) { + tsState.queryCommandCounter++; +#if EFI_TUNER_STUDIO_VERBOSE + scheduleMsg(tsLogger, "got S/H (queryCommand) mode=%d", mode); + printTsStats(); +#endif + tsSendResponse(mode, (const uint8_t *) TS_SIGNATURE, strlen(TS_SIGNATURE) + 1); +} + +/** + * @brief 'Output' command sends out a snapshot of current values + */ +void handleOutputChannelsCommand(ts_response_format_e mode) { + tsState.outputChannelsCommandCounter++; + // this method is invoked too often to print any debug information + tsSendResponse(mode, (const uint8_t *) &tsOutputChannels, sizeof(TunerStudioOutputChannels)); +} + +void handleTestCommand(void) { + /** + * this is NOT a standard TunerStudio command, this is my own + * extension of the protocol to simplify troubleshooting + */ + tunerStudioDebug("got T (Test)"); + tunerStudioWriteData((const uint8_t *)VCS_VERSION, sizeof(VCS_VERSION)); + /** + * Please note that this response is a magic constant used by dev console for protocol detection + * @see EngineState#TS_PROTOCOL_TAG + */ + tunerStudioWriteData((const uint8_t *) " ts_p_alive\r\n", 8); +} + +/** + * @return true if legacy command was processed, false otherwise + */ +bool handlePlainCommand(uint8_t command) { + if (command == TS_HELLO_COMMAND || command == TS_HELLO_COMMAND_DEPRECATED) { + scheduleMsg(tsLogger, "Got naked Query command"); + handleQueryCommand(TS_PLAIN); + return true; + } else if (command == 't' || command == 'T') { + handleTestCommand(); + return true; + } else if (command == TS_PAGE_COMMAND) { + int recieved = chSequentialStreamRead(getTsSerialDevice(), (uint8_t * )&pageIn, sizeof(pageIn)); + // todo: validate 'recieved' value + handlePageSelectCommand(TS_PLAIN, pageIn); + return true; + } else if (command == TS_BURN_COMMAND) { + scheduleMsg(tsLogger, "Got naked BURN"); + uint16_t page; + int recieved = chSequentialStreamRead(getTsSerialDevice(), (uint8_t * )&page, + sizeof(page)); + if (recieved != sizeof(page)) { + tunerStudioError("ERROR: Not enough for plain burn"); + return true; + } + handleBurnCommand(TS_PLAIN, page); + return true; + } else if (command == TS_CHUNK_WRITE_COMMAND) { + scheduleMsg(tsLogger, "Got naked CHUNK_WRITE"); + int recieved = chSequentialStreamRead(getTsSerialDevice(), (uint8_t * )&writeChunkRequest, + sizeof(writeChunkRequest)); + if (recieved != sizeof(writeChunkRequest)) { + scheduleMsg(tsLogger, "ERROR: Not enough for plain chunk write header: %d", recieved); + tsState.errorCounter++; + return true; + } + recieved = chSequentialStreamRead(getTsSerialDevice(), (uint8_t * )&crcReadBuffer, writeChunkRequest.count); + if (recieved != writeChunkRequest.count) { + scheduleMsg(tsLogger, "ERROR: Not enough for plain chunk write content: %d while expecting %d", recieved, writeChunkRequest.count); + tsState.errorCounter++; + return true; + } + currentPageId = writeChunkRequest.page; + + handleWriteChunkCommand(TS_PLAIN, writeChunkRequest.offset, writeChunkRequest.count, (uint8_t * )&crcWriteBuffer); + return true; + } else if (command == TS_READ_COMMAND) { + //scheduleMsg(logger, "Got naked READ PAGE???"); + int recieved = chSequentialStreamRead(getTsSerialDevice(), (uint8_t * )&readRequest, sizeof(readRequest)); + if (recieved != sizeof(readRequest)) { + tunerStudioError("Not enough for plain read header"); + return true; + } + handlePageReadCommand(TS_PLAIN, readRequest.page, readRequest.offset, readRequest.count); + return true; + } else if (command == TS_OUTPUT_COMMAND) { + //scheduleMsg(logger, "Got naked Channels???"); + handleOutputChannelsCommand(TS_PLAIN); + return true; + } else if (command == TS_LEGACY_HELLO_COMMAND) { + tunerStudioDebug("ignoring LEGACY_HELLO_COMMAND"); + return true; + } else if (command == TS_COMMAND_F) { + tunerStudioDebug("not ignoring F"); + tunerStudioWriteData((const uint8_t *) PROTOCOL, strlen(PROTOCOL)); + return true; + } else { + return false; + } +} + +int tunerStudioHandleCrcCommand(uint8_t *data, int incomingPacketSize) { + char command = data[0]; + data++; + if (command == TS_HELLO_COMMAND || command == TS_HELLO_COMMAND_DEPRECATED) { + tunerStudioDebug("got Query command"); + handleQueryCommand(TS_CRC); + } else if (command == TS_OUTPUT_COMMAND) { + handleOutputChannelsCommand(TS_CRC); + } else if (command == TS_PAGE_COMMAND) { + uint16_t page = *(uint16_t *) data; + handlePageSelectCommand(TS_CRC, page); + } else if (command == TS_CHUNK_WRITE_COMMAND) { + currentPageId = *(uint16_t *) data; + uint16_t offset = *(uint16_t *) (data + 2); + uint16_t count = *(uint16_t *) (data + 4); + handleWriteChunkCommand(TS_CRC, offset, count, data + sizeof(TunerStudioWriteChunkRequest)); + } else if (command == TS_SINGLE_WRITE_COMMAND) { + uint16_t page = *(uint16_t *) data; + uint16_t offset = *(uint16_t *) (data + 2); + uint8_t value = data[4]; + handleWriteValueCommand(TS_CRC, page, offset, value); + } else if (command == TS_BURN_COMMAND) { + uint16_t page = *(uint16_t *) data; + handleBurnCommand(TS_CRC, page); + } else if (command == TS_READ_COMMAND) { + uint16_t page = *(uint16_t *) data; + uint16_t offset = *(uint16_t *) (data + 2); + uint16_t count = *(uint16_t *) (data + 4); + handlePageReadCommand(TS_CRC, page, offset, count); + } else if (command == 't' || command == 'T') { + handleTestCommand(); + } else if (command == TS_LEGACY_HELLO_COMMAND) { + /** + * 'Q' is the query command used for compatibility with older ECUs + */ + tunerStudioDebug("ignoring Q"); + } else if (command == TS_COMMAND_F) { + tunerStudioDebug("ignoring F"); + /** + * http://www.msextra.com/forums/viewtopic.php?f=122&t=48327 + * Response from TS support: This is an optional command * + * "The F command is used to find what ini. file needs to be loaded in TunerStudio to match the controller. + * If you are able to just make your firmware ignore the command that would work. + * Currently on some firmware versions the F command is not used and is just ignored by the firmware as a unknown command." + */ + } else { + tunerStudioError("ERROR: ignoring unexpected command"); + return false; + } + return true; } void startTunerStudioConnectivity(Logging *sharedLogger) { @@ -591,4 +663,4 @@ void startTunerStudioConnectivity(Logging *sharedLogger) { chThdCreateStatic(tsThreadStack, sizeof(tsThreadStack), NORMALPRIO, tsThreadEntryPoint, NULL); } -#endif /* EFI_TUNER_STUDIO */ +#endif diff --git a/firmware/console/tunerstudio/tunerstudio.h b/firmware/console/tunerstudio/tunerstudio.h index 97159140a3..edd0c551b7 100644 --- a/firmware/console/tunerstudio/tunerstudio.h +++ b/firmware/console/tunerstudio/tunerstudio.h @@ -9,18 +9,50 @@ #define TUNERSTUDIO_H_ #include "main.h" +#include "tunerstudio_io.h" #if EFI_TUNER_STUDIO #include "tunerstudio_configuration.h" #include "engine.h" +#include + +typedef struct { + int queryCommandCounter; + int outputChannelsCommandCounter; + int readPageCommandsCounter; + int burnCommandCounter; + int pageCommandCounter; + int writeValueCommandCounter; + int writeChunkCommandCounter; + int errorCounter; + int tsCounter; +} tunerstudio_counters_s; + +bool handlePlainCommand(uint8_t command); +int tunerStudioHandleCrcCommand(uint8_t *data, int incomingPacketSize); + +void handleTestCommand(void); +void handleQueryCommand(ts_response_format_e mode); +void handleOutputChannelsCommand(ts_response_format_e mode); + +char *getWorkingPageAddr(int pageIndex); +int getTunerStudioPageSize(int pageIndex); +void handleWriteValueCommand(ts_response_format_e mode, uint16_t page, uint16_t offset, uint8_t value); +void handleWriteChunkCommand(ts_response_format_e mode, short offset, short count, void *content); +void handlePageSelectCommand(ts_response_format_e mode, uint16_t pageId); +void handlePageReadCommand(ts_response_format_e mode, uint16_t pageId, uint16_t offset, uint16_t count); +void handleBurnCommand(ts_response_format_e mode, uint16_t page); + +void tunerStudioDebug(const char *msg); +void tunerStudioError(const char *msg); + void updateTunerStudioState(TunerStudioOutputChannels *tsOutputChannels DECLARE_ENGINE_PARAMETER_S); void printTsStats(void); void requestBurn(void); void startTunerStudioConnectivity(Logging *sharedLogger); void syncTunerStudioCopy(void); -void tunerStudioWriteCrcPacket(const uint8_t command, const void *buf, const uint16_t size); #if defined __GNUC__ // GCC diff --git a/firmware/console/tunerstudio/tunerstudio.mk b/firmware/console/tunerstudio/tunerstudio.mk index b57efb9b1c..0d9ecefc98 100644 --- a/firmware/console/tunerstudio/tunerstudio.mk +++ b/firmware/console/tunerstudio/tunerstudio.mk @@ -1,3 +1,3 @@ -TUNERSTUDIO_SRC_CPP = $(PROJECT_DIR)/console/tunerstudio/tunerstudio_algo.cpp \ +TUNERSTUDIO_SRC_CPP = $(PROJECT_DIR)/console/tunerstudio/tunerstudio_io.cpp \ $(PROJECT_DIR)/console/tunerstudio/tunerstudio.cpp diff --git a/firmware/console/tunerstudio/tunerstudio_algo.cpp b/firmware/console/tunerstudio/tunerstudio_algo.cpp deleted file mode 100644 index f7d7e8b53c..0000000000 --- a/firmware/console/tunerstudio/tunerstudio_algo.cpp +++ /dev/null @@ -1,180 +0,0 @@ -/** - * @file tunerstudio_algo.cpp - * @brief Tuner Studio plain protocol implementation - * - * This implementation would not happen without the documentation - * provided by Jon Zeeff (jon@zeeff.com) - * - * Tuner Studio has a really simple protocol, a minimal implementation - * capable of displaying current engine state on the gauges would - * require only two commands: queryCommand and ochGetCommand - * - * queryCommand: - * Communication initialization command. TunerStudio sends a single byte H - * ECU response: - * One of the known ECU id strings. We are using "MShift v0.01" id string. - * - * ochGetCommand: - * Request for output channels state.TunerStudio sends a single byte O - * ECU response: - * A snapshot of output channels as described in [OutputChannels] section of the .ini file - * The length of this block is 'ochBlockSize' property of the .ini file - * - * These two commands are enough to get working gauges. In order to start configuring the ECU using - * tuner studio, three more commands should be implemented: - * - * todo: merge this file with tunerstudio.c? - * - * - * @date Oct 22, 2013 - * @author Andrey Belomutskiy, (c) 2012-2015 - * - * This file is part of rusEfi - see http://rusefi.com - * - * rusEfi is free software; you can redistribute it and/or modify it under the terms of - * the GNU General Public License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without - * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program. - * If not, see . - */ - -#include -#include "tunerstudio_algo.h" -#include "tunerstudio_configuration.h" -#include "engine_configuration.h" -#include "tunerstudio.h" -#include "svnversion.h" - -#ifndef FALSE -#define FALSE 0 -#endif - -#ifndef TRUE -#define TRUE (!FALSE) -#endif - -#if EFI_TUNER_STUDIO - -#if EFI_TUNER_STUDIO_VERBOSE -extern Logging *tsLogger; -#include "tunerstudio.h" -#endif - -tunerstudio_counters_s tsState; -TunerStudioOutputChannels tsOutputChannels; -/** - * this is a local copy of the configuration. Any changes to this copy - * have no effect until this copy is explicitly propagated to the main working copy - */ -persistent_config_s configWorkingCopy; - -short currentPageId; - -void tunerStudioError(const char *msg) { - tunerStudioDebug(msg); - tsState.errorCounter++; -} - -int tunerStudioHandleCrcCommand(uint8_t *data, int incomingPacketSize) { - char command = data[0]; - data++; - if (command == TS_HELLO_COMMAND || command == TS_HELLO_COMMAND_DEPRECATED) { - tunerStudioDebug("got Query command"); - handleQueryCommand(TS_CRC); - } else if (command == TS_OUTPUT_COMMAND) { - handleOutputChannelsCommand(TS_CRC); - } else if (command == TS_PAGE_COMMAND) { - uint16_t page = *(uint16_t *) data; - handlePageSelectCommand(TS_CRC, page); - } else if (command == TS_CHUNK_WRITE_COMMAND) { - currentPageId = *(uint16_t *) data; - uint16_t offset = *(uint16_t *) (data + 2); - uint16_t count = *(uint16_t *) (data + 4); - handleWriteChunkCommand(TS_CRC, offset, count, data + sizeof(TunerStudioWriteChunkRequest)); - } else if (command == TS_SINGLE_WRITE_COMMAND) { - uint16_t page = *(uint16_t *) data; - uint16_t offset = *(uint16_t *) (data + 2); - uint8_t value = data[4]; - handleWriteValueCommand(TS_CRC, page, offset, value); - } else if (command == TS_BURN_COMMAND) { - uint16_t page = *(uint16_t *) data; - handleBurnCommand(TS_CRC, page); - } else if (command == TS_READ_COMMAND) { - uint16_t page = *(uint16_t *) data; - uint16_t offset = *(uint16_t *) (data + 2); - uint16_t count = *(uint16_t *) (data + 4); - handlePageReadCommand(TS_CRC, page, offset, count); - } else if (command == 't' || command == 'T') { - handleTestCommand(); - } else if (command == TS_LEGACY_HELLO_COMMAND) { - /** - * 'Q' is the query command used for compatibility with older ECUs - */ - tunerStudioDebug("ignoring Q"); - } else if (command == TS_COMMAND_F) { - tunerStudioDebug("ignoring F"); - /** - * http://www.msextra.com/forums/viewtopic.php?f=122&t=48327 - * Response from TS support: This is an optional command * - * "The F command is used to find what ini. file needs to be loaded in TunerStudio to match the controller. - * If you are able to just make your firmware ignore the command that would work. - * Currently on some firmware versions the F command is not used and is just ignored by the firmware as a unknown command." - */ - } else { - tunerStudioError("ERROR: ignoring unexpected command"); - return false; - } - return true; -} - -void tsSendResponse(ts_response_format_e mode, const uint8_t * buffer, int size) { - if (mode == TS_CRC) { - tunerStudioWriteCrcPacket(TS_RESPONSE_OK, buffer, size); - } else { - if (size > 0) - tunerStudioWriteData(buffer, size); - } -} - -/** - * Query with CRC takes place while re-establishing connection - * Query without CRC takes place on TunerStudio startup - */ -void handleQueryCommand(ts_response_format_e mode) { - tsState.queryCommandCounter++; -#if EFI_TUNER_STUDIO_VERBOSE - scheduleMsg(tsLogger, "got S/H (queryCommand) mode=%d", mode); - printTsStats(); -#endif - tsSendResponse(mode, (const uint8_t *) TS_SIGNATURE, strlen(TS_SIGNATURE) + 1); -} - -/** - * @brief 'Output' command sends out a snapshot of current values - */ -void handleOutputChannelsCommand(ts_response_format_e mode) { - tsState.outputChannelsCommandCounter++; - // this method is invoked too often to print any debug information - tsSendResponse(mode, (const uint8_t *) &tsOutputChannels, sizeof(TunerStudioOutputChannels)); -} - -void handleTestCommand(void) { - /** - * this is NOT a standard TunerStudio command, this is my own - * extension of the protocol to simplify troubleshooting - */ - tunerStudioDebug("got T (Test)"); - tunerStudioWriteData((const uint8_t *)VCS_VERSION, sizeof(VCS_VERSION)); - /** - * Please note that this response is a magic constant used by dev console for protocol detection - * @see EngineState#TS_PROTOCOL_TAG - */ - tunerStudioWriteData((const uint8_t *) " ts_p_alive\r\n", 8); -} - -#endif diff --git a/firmware/console/tunerstudio/tunerstudio_algo.h b/firmware/console/tunerstudio/tunerstudio_algo.h deleted file mode 100644 index 9b31104ed0..0000000000 --- a/firmware/console/tunerstudio/tunerstudio_algo.h +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @file tunerstudio_algo.h - * - * @date Oct 22, 2013 - * @author Andrey Belomutskiy, (c) 2012-2015 - */ - -#ifndef TUNERSTUDIO_ALGO_H_ -#define TUNERSTUDIO_ALGO_H_ - -#define TS_SIGNATURE "MShift v0.01" - -#include - -// response codes - -#define TS_RESPONSE_OK 0x00 -#define TS_RESPONSE_BURN_OK 0x04 -#define TS_RESPONSE_CRC_FAILURE 0x82 - -typedef enum { - TS_PLAIN = 0, - TS_CRC = 1 -} ts_response_format_e; - -typedef struct { - int queryCommandCounter; - int outputChannelsCommandCounter; - int readPageCommandsCounter; - int burnCommandCounter; - int pageCommandCounter; - int writeValueCommandCounter; - int writeChunkCommandCounter; - int errorCounter; - int tsCounter; -} tunerstudio_counters_s; - -int tunerStudioHandleCrcCommand(uint8_t *data, int incomingPacketSize); - -void handleTestCommand(void); -void handleQueryCommand(ts_response_format_e mode); -void tsSendResponse(ts_response_format_e mode, const uint8_t * buffer, int size); -void handleOutputChannelsCommand(ts_response_format_e mode); - -char *getWorkingPageAddr(int pageIndex); -int getTunerStudioPageSize(int pageIndex); -void handleWriteValueCommand(ts_response_format_e mode, uint16_t page, uint16_t offset, uint8_t value); -void handleWriteChunkCommand(ts_response_format_e mode, short offset, short count, void *content); -void handlePageSelectCommand(ts_response_format_e mode, uint16_t pageId); -void handlePageReadCommand(ts_response_format_e mode, uint16_t pageId, uint16_t offset, uint16_t count); -void handleBurnCommand(ts_response_format_e mode, uint16_t page); - -void tunerStudioWriteData(const uint8_t * buffer, int size); -void tunerStudioDebug(const char *msg); - -void tunerStudioError(const char *msg); - -#define TS_HELLO_COMMAND_DEPRECATED 'H' -#define TS_HELLO_COMMAND 'S' -#define TS_LEGACY_HELLO_COMMAND 'Q' -#define TS_OUTPUT_COMMAND 'O' -#define TS_READ_COMMAND 'R' -#define TS_PAGE_COMMAND 'P' -#define TS_COMMAND_F 'F' - -#define TS_SINGLE_WRITE_COMMAND 'W' -#define TS_CHUNK_WRITE_COMMAND 'C' -#define TS_BURN_COMMAND 'B' - -#endif /* TUNERSTUDIO_ALGO_H_ */ diff --git a/firmware/console/tunerstudio/tunerstudio_io.cpp b/firmware/console/tunerstudio/tunerstudio_io.cpp new file mode 100644 index 0000000000..85aa0903a6 --- /dev/null +++ b/firmware/console/tunerstudio/tunerstudio_io.cpp @@ -0,0 +1,101 @@ +/** + * @file tunerstudio_io.cpp + * + * @date Mar 8, 2015 + * @author Andrey Belomutskiy, (c) 2012-2014 + */ + +#include "main.h" +#include "tunerstudio_io.h" +#include "console_io.h" +#include "engine.h" + +EXTERN_ENGINE; + +extern Logging *tsLogger; +/** + * we use 'blockingFactor = 256' in rusefi.ini + * todo: should we just do (256 + CRC_WRAPPING_SIZE) ? + */ + +uint8_t crcWriteBuffer[300]; + +#if EFI_PROD_CODE || defined(__DOXYGEN__) +#include "pin_repository.h" +#include "usbconsole.h" +#include "map_averaging.h" +extern SerialUSBDriver SDU1; +#define CONSOLE_DEVICE &SDU1 + +#define TS_SERIAL_UART_DEVICE &SD3 + +static SerialConfig tsSerialConfig = { 0, 0, USART_CR2_STOP1_BITS | USART_CR2_LINEN, 0 }; + +void startTsPort(void) { + if (isSerialOverUart()) { + print("TunerStudio over USB serial"); + /** + * This method contains a long delay, that's the reason why this is not done on the main thread + */ + usb_serial_start(); + } else { + + print("TunerStudio over USART"); + mySetPadMode("tunerstudio rx", TS_SERIAL_RX_PORT, TS_SERIAL_RX_PIN, PAL_MODE_ALTERNATE(TS_SERIAL_AF)); + mySetPadMode("tunerstudio tx", TS_SERIAL_TX_PORT, TS_SERIAL_TX_PIN, PAL_MODE_ALTERNATE(TS_SERIAL_AF)); + + tsSerialConfig.speed = boardConfiguration->tunerStudioSerialSpeed; + + sdStart(TS_SERIAL_UART_DEVICE, &tsSerialConfig); + } +} + +#endif /* EFI_PROD_CODE */ + +BaseChannel * getTsSerialDevice(void) { +#if EFI_PROD_CODE || defined(__DOXYGEN__) + if (isSerialOverUart()) { + // if console uses UART then TS uses USB + return (BaseChannel *) &SDU1; + } else { + return (BaseChannel *) TS_SERIAL_UART_DEVICE; + } +#else + return (BaseChannel *) TS_SIMULATOR_PORT; +#endif +} + +void tunerStudioWriteData(const uint8_t * buffer, int size) { + int transferred = chSequentialStreamWrite(getTsSerialDevice(), buffer, size); + if (transferred != size) { + scheduleMsg(tsLogger, "!!! NOT ACCEPTED %d out of %d !!!", transferred, size); + } +} + +/** + * Adds size to the beginning of a packet and a crc32 at the end. Then send the packet. + */ +void tunerStudioWriteCrcPacket(const uint8_t command, const void *buf, const uint16_t size) { + // todo: max size validation + *(uint16_t *) crcWriteBuffer = SWAP_UINT16(size + 1); // packet size including command + *(uint8_t *) (crcWriteBuffer + 2) = command; + if (size != 0) + memcpy(crcWriteBuffer + 3, buf, size); + // CRC on whole packet + uint32_t crc = crc32((void *) (crcWriteBuffer + 2), (uint32_t) (size + 1)); + *(uint32_t *) (crcWriteBuffer + 2 + 1 + size) = SWAP_UINT32(crc); + +// scheduleMsg(logger, "TunerStudio: CRC command %x size %d", command, size); + + tunerStudioWriteData(crcWriteBuffer, size + 2 + 1 + 4); // with size, command and CRC +} + +void tsSendResponse(ts_response_format_e mode, const uint8_t * buffer, int size) { + if (mode == TS_CRC) { + tunerStudioWriteCrcPacket(TS_RESPONSE_OK, buffer, size); + } else { + if (size > 0) + tunerStudioWriteData(buffer, size); + } +} + diff --git a/firmware/console/tunerstudio/tunerstudio_io.h b/firmware/console/tunerstudio/tunerstudio_io.h new file mode 100644 index 0000000000..161dd8ba42 --- /dev/null +++ b/firmware/console/tunerstudio/tunerstudio_io.h @@ -0,0 +1,52 @@ +/** + * @file tunerstudio_io.h + * + * @date Mar 8, 2015 + * @author Andrey Belomutskiy, (c) 2012-2014 + */ +#ifndef CONSOLE_TUNERSTUDIO_TUNERSTUDIO_IO_H_ +#define CONSOLE_TUNERSTUDIO_TUNERSTUDIO_IO_H_ + +#include "main.h" + +#if EFI_PROD_CODE +#include "usbconsole.h" +#include "pin_repository.h" +#endif + +#define PROTOCOL "001" +#define TS_SIGNATURE "MShift v0.01" + +#define TS_RESPONSE_OK 0x00 +#define TS_RESPONSE_BURN_OK 0x04 +#define TS_RESPONSE_CRC_FAILURE 0x82 + +typedef enum { + TS_PLAIN = 0, + TS_CRC = 1 +} ts_response_format_e; + +#define TS_HELLO_COMMAND_DEPRECATED 'H' +#define TS_HELLO_COMMAND 'S' +#define TS_LEGACY_HELLO_COMMAND 'Q' +#define TS_OUTPUT_COMMAND 'O' +#define TS_READ_COMMAND 'R' +#define TS_PAGE_COMMAND 'P' +#define TS_COMMAND_F 'F' + +#define TS_SINGLE_WRITE_COMMAND 'W' +#define TS_CHUNK_WRITE_COMMAND 'C' +#define TS_BURN_COMMAND 'B' + +#define CRC_VALUE_SIZE 4 +// todo: double-check this +#define CRC_WRAPPING_SIZE (CRC_VALUE_SIZE + 3) + +BaseChannel * getTsSerialDevice(void); +void startTsPort(void); + +void tunerStudioWriteData(const uint8_t * buffer, int size); +void tunerStudioWriteCrcPacket(const uint8_t command, const void *buf, const uint16_t size); +void tsSendResponse(ts_response_format_e mode, const uint8_t * buffer, int size); + +#endif /* CONSOLE_TUNERSTUDIO_TUNERSTUDIO_IO_H_ */