/* 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 */ /* This is called when a command is received over serial from TunerStudio / Megatune It parses the command and calls the relevant function A detailed description of each call can be found at: http://www.msextra.com/doc/ms1extra/COM_RS232.htm */ //#include "comms.h" //#include "globals.h" //#include "storage.h" void command() { if (!cmdPending) { currentCommand = Serial.read(); } switch (currentCommand) { case 'A': // send x bytes of realtime values sendValues(0, packetSize,0x30, 0); //send values to serial0 break; case 'B': // Burn current values to eeprom writeConfig(); 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 'E': // receive command button commands cmdPending = true; if(Serial.available() >= 2) { cmdGroup = Serial.read(); cmdValue = Serial.read(); cmdCombined = word(cmdGroup, cmdValue); if (currentStatus.RPM == 0) { commandButtons(); } cmdPending = false; } break; case 'L': // List the contents of current page in human readable form sendPage(true); break; case 'N': // Displays a new line. Like pushing enter in a text editor Serial.println(); break; case 'P': // set the current page //A 2nd byte of data is required after the 'P' specifying the new page number. cmdPending = true; if (Serial.available() > 0) { currentPage = Serial.read(); if (currentPage >= '0') {//This converts the ascii number char into binary currentPage -= '0'; } if ( (currentPage == veMapPage) || (currentPage == ignMapPage) || (currentPage == afrMapPage) ) { // Detecting if the current page is a table/map isMap = true; } else { isMap = false; } cmdPending = false; } break; case 'F': // send serial protocol version Serial.print("001"); break; case 'S': // send code version Serial.print("Speeduino 2017.07-dev"); currentStatus.secl = 0; //This is required in TS3 due to its stricter timings break; case 'Q': // send code version Serial.print("speeduino 201707-dev"); break; case 'V': // send VE table and constants in binary sendPage(false); break; case 'W': // receive new VE obr constant at 'W'++ cmdPending = true; int valueOffset; //cannot use offset as a variable name, it is a reserved word for several teensy libraries 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 't': // receive new Calibration info. Command structure: "t", . This is an MS2/Extra command, NOT part of MS1 spec 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 'Z': //Totally non-standard testing function. Will be removed once calibration testing is completed. This function takes 1.5kb of program space! :S Serial.println(F("Coolant")); for (int x = 0; x < CALIBRATION_TABLE_SIZE; x++) { Serial.print(x); Serial.print(", "); Serial.println(cltCalibrationTable[x]); } Serial.println(F("Inlet temp")); for (int x = 0; x < CALIBRATION_TABLE_SIZE; x++) { Serial.print(x); Serial.print(", "); Serial.println(iatCalibrationTable[x]); } Serial.println(F("O2")); for (int x = 0; x < CALIBRATION_TABLE_SIZE; x++) { Serial.print(x); Serial.print(", "); Serial.println(o2CalibrationTable[x]); } Serial.println(F("WUE")); for (int x = 0; x < 10; x++) { Serial.print(configPage2.wueBins[x]); Serial.print(", "); Serial.println(configPage1.wueValues[x]); } Serial.flush(); break; case 'T': //Send 256 tooth log entries to Tuner Studios tooth logger sendToothLog(false); //Sends tooth log values as ints break; case 'z': //Send 256 tooth log entries to a terminal emulator sendToothLog(true); //Sends tooth log values as chars 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; if(cmd == 0x30) //Send output channels command 0x30 is 48dec { byte tmp; tmp = Serial.read(); offset = word(Serial.read(), tmp); tmp = Serial.read(); length = word(Serial.read(), tmp); sendValues(offset, length,cmd, 0); } else { //No other r/ commands should be called } cmdPending = false; } break; case '?': Serial.println (F( "\n" "===Command Help===\n\n" "All commands are single character and are concatenated with their parameters \n" "without spaces. Some parameters are binary and cannot be entered through this \n" "prompt by conventional means. \n" "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" "L - Displays map page (aka table) or configPage values. Use P to change page (not \n" " every page is a map)\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" "? - Displays this help page" )); break; default: break; } } /* 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) { byte fullStatus[packetSize]; if (portNum == 3) { //CAN serial #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)|| defined(CORE_STM32) || defined (CORE_TEENSY) //ATmega2561 does not have Serial3 if (offset == 0) { CANSerial.write("A"); //confirm cmd type } else { CANSerial.write("r"); //confirm cmd type CANSerial.write(cmd); } #endif } else { if(requestCount == 0) { currentStatus.secl = 0; } requestCount++; } currentStatus.spark ^= (-currentStatus.hasSync ^ currentStatus.spark) & (1 << BIT_SPARK_SYNC); //Set the sync bit of the Spark variable to match the hasSync variable fullStatus[0] = currentStatus.secl; //secl is simply a counter that increments each second. Used to track unexpected resets (Which will reset this count to 0) fullStatus[1] = currentStatus.squirt; //Squirt Bitfield fullStatus[2] = currentStatus.engine; //Engine Status Bitfield fullStatus[3] = (byte)(divu100(currentStatus.dwell)); //Dwell in ms * 10 fullStatus[4] = lowByte(currentStatus.MAP); //2 bytes for MAP fullStatus[5] = highByte(currentStatus.MAP); fullStatus[6] = (byte)(currentStatus.IAT + CALIBRATION_TEMPERATURE_OFFSET); //mat fullStatus[7] = (byte)(currentStatus.coolant + CALIBRATION_TEMPERATURE_OFFSET); //Coolant ADC fullStatus[8] = currentStatus.batCorrection; //Battery voltage correction (%) fullStatus[9] = currentStatus.battery10; //battery voltage fullStatus[10] = currentStatus.O2; //O2 fullStatus[11] = currentStatus.egoCorrection; //Exhaust gas correction (%) fullStatus[12] = currentStatus.iatCorrection; //Air temperature Correction (%) fullStatus[13] = currentStatus.wueCorrection; //Warmup enrichment (%) fullStatus[14] = lowByte(currentStatus.RPM); //rpm HB fullStatus[15] = highByte(currentStatus.RPM); //rpm LB fullStatus[16] = currentStatus.TAEamount; //acceleration enrichment (%) fullStatus[17] = currentStatus.corrections; //Total GammaE (%) fullStatus[18] = currentStatus.VE; //Current VE 1 (%) fullStatus[19] = currentStatus.afrTarget; fullStatus[20] = (byte)(currentStatus.PW1 / 100); //Pulsewidth 1 multiplied by 10 in ms. Have to convert from uS to mS. fullStatus[21] = currentStatus.tpsDOT; //TPS DOT fullStatus[22] = currentStatus.advance; fullStatus[23] = currentStatus.TPS; // TPS (0% to 100%) //Need to split the int loopsPerSecond value into 2 bytes fullStatus[24] = lowByte(currentStatus.loopsPerSecond); fullStatus[25] = highByte(currentStatus.loopsPerSecond); //The following can be used to show the amount of free memory currentStatus.freeRAM = freeRam(); fullStatus[26] = lowByte(currentStatus.freeRAM); //(byte)((currentStatus.loopsPerSecond >> 8) & 0xFF); fullStatus[27] = highByte(currentStatus.freeRAM); fullStatus[28] = currentStatus.boostTarget; fullStatus[29] = currentStatus.boostDuty; fullStatus[30] = currentStatus.spark; //Spark related bitfield //rpmDOT must be sent as a signed integer fullStatus[31] = lowByte(currentStatus.rpmDOT); fullStatus[32] = highByte(currentStatus.rpmDOT); fullStatus[33] = currentStatus.ethanolPct; //Flex sensor value (or 0 if not used) fullStatus[34] = currentStatus.flexCorrection; //Flex fuel correction (% above or below 100) fullStatus[35] = currentStatus.flexIgnCorrection; //Ignition correction (Increased degrees of advance) for flex fuel fullStatus[36] = getNextError(); fullStatus[37] = currentStatus.idleLoad; fullStatus[38] = currentStatus.testOutputs; fullStatus[39] = currentStatus.O2_2; //O2 fullStatus[40] = currentStatus.baro; //Barometer value fullStatus[41] = lowByte(currentStatus.canin[0]); fullStatus[42] = highByte(currentStatus.canin[0]); fullStatus[43] = lowByte(currentStatus.canin[1]); fullStatus[44] = highByte(currentStatus.canin[1]); fullStatus[45] = lowByte(currentStatus.canin[2]); fullStatus[46] = highByte(currentStatus.canin[2]); fullStatus[47] = lowByte(currentStatus.canin[3]); fullStatus[48] = highByte(currentStatus.canin[3]); fullStatus[49] = lowByte(currentStatus.canin[4]); fullStatus[50] = highByte(currentStatus.canin[4]); fullStatus[51] = lowByte(currentStatus.canin[5]); fullStatus[52] = highByte(currentStatus.canin[5]); fullStatus[53] = lowByte(currentStatus.canin[6]); fullStatus[54] = highByte(currentStatus.canin[6]); fullStatus[55] = lowByte(currentStatus.canin[7]); fullStatus[56] = highByte(currentStatus.canin[7]); fullStatus[57] = lowByte(currentStatus.canin[8]); fullStatus[58] = highByte(currentStatus.canin[8]); fullStatus[59] = lowByte(currentStatus.canin[9]); fullStatus[60] = highByte(currentStatus.canin[9]); fullStatus[61] = lowByte(currentStatus.canin[10]); fullStatus[62] = highByte(currentStatus.canin[10]); fullStatus[63] = lowByte(currentStatus.canin[11]); fullStatus[64] = highByte(currentStatus.canin[11]); fullStatus[65] = lowByte(currentStatus.canin[12]); fullStatus[66] = highByte(currentStatus.canin[12]); fullStatus[67] = lowByte(currentStatus.canin[13]); fullStatus[68] = highByte(currentStatus.canin[13]); fullStatus[69] = lowByte(currentStatus.canin[14]); fullStatus[70] = highByte(currentStatus.canin[14]); fullStatus[71] = lowByte(currentStatus.canin[15]); fullStatus[72] = highByte(currentStatus.canin[15]); for(byte x=0; x 255) { tempValue = 255; // Cap the maximum value to prevent overflow when converting to byte } if (tempValue < 0) { tempValue = 0; } pnt_TargetTable[(x / 2)] = (byte)tempValue; //From TS3.x onwards, the EEPROM must be written here as TS restarts immediately after the process completes which is before the EEPROM write completes int y = EEPROM_START + (x / 2); EEPROM.update(y, (byte)tempValue); every2nd = false; #if defined(CORE_STM32) digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); #else analogWrite(LED_BUILTIN, (counter % 50) ); //analogWrite(13, (counter % 50) ); #endif counter++; } else { every2nd = true; } } } /* 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(bool useChar) { //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 (toothHistoryIndex >= TOOTH_LOG_SIZE) //Sanity check. Flagging system means this should always be true { unsigned int tempToothHistory[TOOTH_LOG_BUFFER]; //Create a temporary array that will contain a copy of what is in the main toothHistory array //Copy the working history into the temporary buffer array. This is done so that, if the history loops whilst the values are being sent over serial, it doesn't affect the values memcpy( (void*)tempToothHistory, (void*)toothHistory, sizeof(tempToothHistory) ); toothHistoryIndex = 0; //Reset the history index //Loop only needs to go to half the buffer size if (useChar) { for (int x = 0; x < TOOTH_LOG_SIZE; x++) { Serial.println(tempToothHistory[x]); } } else { for (int x = 0; x < TOOTH_LOG_SIZE; x++) { Serial.write(highByte(tempToothHistory[x])); Serial.write(lowByte(tempToothHistory[x])); } BIT_CLEAR(currentStatus.squirt, BIT_SQUIRT_TOOTHLOG1READY); } toothLogRead = true; } } void testComm() { Serial.write(1); return; } void commandButtons() { switch (cmdCombined) { case 256: // cmd is stop BIT_CLEAR(currentStatus.testOutputs, 1); digitalWrite(pinInjector1, LOW); digitalWrite(pinInjector2, LOW); digitalWrite(pinInjector3, LOW); digitalWrite(pinInjector4, LOW); digitalWrite(pinCoil1, LOW); digitalWrite(pinCoil2, LOW); digitalWrite(pinCoil3, LOW); digitalWrite(pinCoil4, LOW); break; case 257: // cmd is enable // currentStatus.testactive = 1; BIT_SET(currentStatus.testOutputs, 1); break; case 513: // cmd group is for injector1 on actions if( BIT_CHECK(currentStatus.testOutputs, 1) ){ digitalWrite(pinInjector1, HIGH); } break; case 514: // cmd group is for injector1 off actions if( BIT_CHECK(currentStatus.testOutputs, 1) ){digitalWrite(pinInjector1, LOW);} break; case 515: // cmd group is for injector1 50% dc actions //for (byte dcloop = 0; dcloop < 11; dcloop++) //{ // digitalWrite(pinInjector1, HIGH); // delay(500); // digitalWrite(pinInjector1, LOW); // delay(500); //} break; case 516: // cmd group is for injector2 on actions if( BIT_CHECK(currentStatus.testOutputs, 1) ){ digitalWrite(pinInjector2, HIGH); } break; case 517: // cmd group is for injector2 off actions if( BIT_CHECK(currentStatus.testOutputs, 1) ){ digitalWrite(pinInjector2, LOW); } break; case 518: // cmd group is for injector2 50%dc actions break; case 519: // cmd group is for injector3 on actions if( BIT_CHECK(currentStatus.testOutputs, 1) ) { digitalWrite(pinInjector3, HIGH); } break; case 520: // cmd group is for injector3 off actions if( BIT_CHECK(currentStatus.testOutputs, 1) ) { digitalWrite(pinInjector3, LOW); } break; case 521: // cmd group is for injector3 50%dc actions break; case 522: // cmd group is for injector4 on actions if( BIT_CHECK(currentStatus.testOutputs, 1) ){ digitalWrite(pinInjector4, HIGH); } break; case 523: // cmd group is for injector4 off actions if( BIT_CHECK(currentStatus.testOutputs, 1) ){ digitalWrite(pinInjector4, LOW); } break; case 524: // cmd group is for injector4 50% dc actions break; case 769: // cmd group is for spark1 on actions if( BIT_CHECK(currentStatus.testOutputs, 1) ) { digitalWrite(pinCoil1, HIGH); } break; case 770: // cmd group is for spark1 off actions if( BIT_CHECK(currentStatus.testOutputs, 1) ) { digitalWrite(pinCoil1, LOW); } break; case 771: // cmd group is for spark1 50%dc actions break; case 772: // cmd group is for spark2 on actions if( BIT_CHECK(currentStatus.testOutputs, 1) ) { digitalWrite(pinCoil2, HIGH); } break; case 773: // cmd group is for spark2 off actions if( BIT_CHECK(currentStatus.testOutputs, 1) ) { digitalWrite(pinCoil2, LOW); } break; case 774: // cmd group is for spark2 50%dc actions break; case 775: // cmd group is for spark3 on actions if( BIT_CHECK(currentStatus.testOutputs, 1) ) { digitalWrite(pinCoil3, HIGH); } break; case 776: // cmd group is for spark3 off actions if( BIT_CHECK(currentStatus.testOutputs, 1) ) { digitalWrite(pinCoil3, LOW); } break; case 777: // cmd group is for spark3 50%dc actions break; case 778: // cmd group is for spark4 on actions if( BIT_CHECK(currentStatus.testOutputs, 1) ) { digitalWrite(pinCoil4, HIGH); } break; case 779: // cmd group is for spark4 off actions if( BIT_CHECK(currentStatus.testOutputs, 1) ) { digitalWrite(pinCoil4, LOW); } break; case 780: // cmd group is for spark4 50%dc actions default: break; } }