diff --git a/comms.h b/comms.h index ae5f7fb..c0228ad 100644 --- a/comms.h +++ b/comms.h @@ -1,22 +1,33 @@ #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 1 -#define veSetPage 2 +#define veSetPage 2//Config Page 1 #define ignMapPage 3 -#define ignSetPage 4 +#define ignSetPage 4//Config Page 2 #define afrMapPage 5 -#define afrSetPage 6 -#define iacPage 7 +#define afrSetPage 6//Config Page 3 +#define iacPage 7//Config Page 4 #define boostvvtPage 8 -byte currentPage; - -void command(); +byte currentPage = 1;//Not the same as the speeduino config page numbers +boolean isMap = true; +const char pageTitles[] PROGMEM //This is being stored in the avr flash instead of SRAM which there is not very much of + { + "\nVolumetric Efficiancy Map\0"//This is an alternative to using a 2D array which would waste space because of the different lengths of the strings + "\nPage 1 Config\0"//The configuration page titles' indexes are found by counting the chars + "\nIgnition Map\0"//The map page titles' indexes are put into a var called currentTitleIndex. That represents the first char of each string. + "\nPage 2 Config\0" + "\nAir/Fuel Ratio Map\0" + "\nPage 3 Config\0" + "\nPage 4 Config"//No need to put a trailing null because it's the last string and the compliler does it for you. + }; + +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(); void receiveValue(int offset, byte newValue); void saveConfig(); -void sendPage(); +void sendPage(bool useChar); void receiveCalibration(byte tableID); void sendToothLog(bool useChar); void testComm(); diff --git a/comms.ino b/comms.ino index 66fd035..cf132c3 100644 --- a/comms.ino +++ b/comms.ino @@ -15,128 +15,178 @@ A detailed description of each call can be found at: http://www.msextra.com/doc/ void command() { - switch (Serial.read()) + switch (Serial.read()) { - case 'A': // send 22 bytes of realtime values - sendValues(22); - break; + case 'A': // send 22 bytes of realtime values + sendValues(22); + break; - case 'B': // Burn current values to eeprom - writeConfig(); - 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 'C': // test communications. This is used by Tunerstudio to see whether there is an ECU on a given serial port + testComm(); + break; - case 'P': // set the current page - //A 2nd byte of data is required after the 'P' specifying the new page number. - //This loop should never need to run as the byte should already be in the buffer, but is here just in case - while (Serial.available() == 0) { } - currentPage = Serial.read(); - break; + case 'L': // List the contents of current page in human readable form + sendPage(true); + break; - case 'R': // send 39 bytes of realtime values - sendValues(39); - break; + case 'N': // Displays a new line. Like pushing enter in a text editor + Serial.println(); + break; - case 'S': // send code version - Serial.print(signature); - break; + case 'P': // set the current page + //A 2nd byte of data is required after the 'P' specifying the new page number. + //This loop should never need to run as the byte should already be in the buffer, but is here just in case + while (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; + } + break; - case 'Q': // send code version - Serial.print(signature); - //Serial.write("Speeduino_0_2"); - break; + case 'R': // send 39 bytes of realtime values + sendValues(39); + break; - case 'V': // send VE table and constants - sendPage(); - break; + case 'S': // send code version + Serial.print(signature); + break; - case 'W': // receive new VE or constant at 'W'++ - int offset; - while (Serial.available() == 0) { } - - if(currentPage == veMapPage || currentPage == ignMapPage || currentPage == afrMapPage ) - { + case 'Q': // send code version + Serial.print(signature); + //Serial.write("Speeduino_0_2"); + break; + + case 'V': // send VE table and constants in binary + sendPage(false); + break; + + case 'W': // receive new VE or constant at 'W'++ + int offset; + while (Serial.available() == 0) { } + + if (isMap) + { byte offset1, offset2; offset1 = Serial.read(); while (Serial.available() == 0) { } offset2 = Serial.read(); offset = word(offset2, offset1); - } - else - { - offset = Serial.read(); - } - while (Serial.available() == 0) { } - - receiveValue(offset, Serial.read()); - break; + } + else + { + offset = Serial.read(); + } + while (Serial.available() == 0) { } - 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 + receiveValue(offset, Serial.read()); + break; - 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; - case 'Z': //Totally non-standard testing function. Will be removed once calibration testing is completed. This function takes 1.5kb of program space! :S - digitalWrite(pinInjector1, HIGH); - digitalWrite(pinInjector2, HIGH); - delay(20); - digitalWrite(pinInjector1, LOW); - digitalWrite(pinInjector2, LOW); - return; - - Serial.println("Coolant"); - for(int x=0; x+++\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; + } } /* @@ -146,12 +196,12 @@ void sendValues(int length) { byte packetSize = 31; byte response[packetSize]; - + response[0] = currentStatus.secl; //secl is simply a counter that increments each second. Used to track unexpected resets (Which will reset this count to 0) response[1] = currentStatus.squirt; //Squirt Bitfield response[2] = currentStatus.engine; //Engine Status Bitfield response[3] = (byte)(divu100(currentStatus.dwell)); //Dwell in ms * 10 - response[4] = (byte)(currentStatus.MAP >> 1); //map value is divided by 2 + response[4] = currentStatus.MAP; //map response[5] = (byte)(currentStatus.IAT + CALIBRATION_TEMPERATURE_OFFSET); //mat response[6] = (byte)(currentStatus.coolant + CALIBRATION_TEMPERATURE_OFFSET); //Coolant ADC response[7] = currentStatus.tpsADC; //TPS (Raw 0-255) @@ -167,312 +217,509 @@ void sendValues(int length) response[17] = currentStatus.corrections; //Total GammaE (%) response[18] = currentStatus.VE; //Current VE 1 (%) response[19] = currentStatus.afrTarget; - response[20] = (byte)(currentStatus.PW / 100); //Pulsewidth 1 multiplied by 10 in ms. Have to convert from uS to mS. + response[20] = (byte)(currentStatus.PW / 100); //Pulsewidth 1 multiplied by 10 in ms. Have to convert from uS to mS. response[21] = currentStatus.tpsDOT; //TPS DOT response[22] = currentStatus.advance; response[23] = currentStatus.TPS; // TPS (0% to 100%) //Need to split the int loopsPerSecond value into 2 bytes response[24] = lowByte(currentStatus.loopsPerSecond); response[25] = highByte(currentStatus.loopsPerSecond); - + //The following can be used to show the amount of free memory currentStatus.freeRAM = freeRam(); response[26] = lowByte(currentStatus.freeRAM); //(byte)((currentStatus.loopsPerSecond >> 8) & 0xFF); response[27] = highByte(currentStatus.freeRAM); - + response[28] = currentStatus.batCorrection; //Battery voltage correction (%) response[29] = (byte)(currentStatus.dwell / 100); response[30] = currentStatus.O2_2; //O2 Serial.write(response, (size_t)packetSize); //Serial.flush(); - return; + return; } void receiveValue(int offset, byte newValue) { - - byte* pnt_configPage; - switch (currentPage) + void* pnt_configPage;//This only stores the address of the value that it's pointing to and not the max size + + switch (currentPage) { - case veMapPage: - if (offset < 256) //New value is part of the fuel map + case veMapPage: + if (offset < 256) //New value is part of the fuel map + { + fuelTable.values[15 - offset / 16][offset % 16] = newValue; + return; + } + else + { + //Check whether this is on the X (RPM) or Y (MAP/TPS) axis + if (offset < 272) { - fuelTable.values[15-offset/16][offset%16] = newValue; - return; + //X Axis + fuelTable.axisX[(offset - 256)] = ((int)(newValue) * 100); //The RPM values sent by megasquirt are divided by 100, need to multiple it back by 100 to make it correct } else { - //Check whether this is on the X (RPM) or Y (MAP/TPS) axis - if (offset < 272) - { - //X Axis - fuelTable.axisX[(offset-256)] = ((int)(newValue) * 100); //The RPM values sent by tunerstudio are divided by 100, need to multiple it back by 100 to make it correct - } - else - { - //Y Axis - offset = 15-(offset-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[offset] = (int)(newValue); - } - return; + //Y Axis + offset = 15 - (offset - 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[offset] = (int)(newValue); } - break; - - case veSetPage: - pnt_configPage = (byte *)&configPage1; //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( offset < page_size) + return; + } + break; + + case veSetPage: + pnt_configPage = &configPage1; //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 ( offset < page_size) + { + *((byte *)pnt_configPage + (byte)offset) = newValue; //Need to subtract 80 because the map and bins (Which make up 80 bytes) aren't part of the config pages + } + break; + + case ignMapPage: //Ignition settings page (Page 2) + if (offset < 256) //New value is part of the ignition map + { + ignitionTable.values[15 - offset / 16][offset % 16] = newValue; + return; + } + else + { + //Check whether this is on the X (RPM) or Y (MAP/TPS) axis + if (offset < 272) { - *(pnt_configPage + (byte)offset) = newValue; //Need to subtract 80 because the map and bins (Which make up 80 bytes) aren't part of the config pages - } - break; - - case ignMapPage: //Ignition settings page (Page 2) - if (offset < 256) //New value is part of the ignition map - { - ignitionTable.values[15-offset/16][offset%16] = newValue; - return; + //X Axis + ignitionTable.axisX[(offset - 256)] = (int)(newValue) * int(100); //The RPM values sent by megasquirt are divided by 100, need to multiple it back by 100 to make it correct } else { - //Check whether this is on the X (RPM) or Y (MAP/TPS) axis - if (offset < 272) - { - //X Axis - ignitionTable.axisX[(offset-256)] = (int)(newValue) * int(100); //The RPM values sent by TunerStudio are divided by 100, need to multiple it back by 100 to make it correct - } - else - { - //Y Axis - offset = 15-(offset-272); //Need to do a translation to flip the order - ignitionTable.axisY[offset] = (int)(newValue); - } - return; + //Y Axis + offset = 15 - (offset - 272); //Need to do a translation to flip the order + ignitionTable.axisY[offset] = (int)(newValue); } - - case ignSetPage: - pnt_configPage = (byte *)&configPage2; - //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( offset < page_size) + return; + } + + case ignSetPage: + pnt_configPage = &configPage2; + //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 ( offset < page_size) + { + *((byte *)pnt_configPage + (byte)offset) = newValue; //Need to subtract 80 because the map and bins (Which make up 80 bytes) aren't part of the config pages + } + break; + + case afrMapPage: //Air/Fuel ratio target settings page + if (offset < 256) //New value is part of the afr map + { + afrTable.values[15 - offset / 16][offset % 16] = newValue; + return; + } + else + { + //Check whether this is on the X (RPM) or Y (MAP/TPS) axis + if (offset < 272) { - *(pnt_configPage + (byte)offset) = newValue; //Need to subtract 80 because the map and bins (Which make up 80 bytes) aren't part of the config pages - } - break; - - case afrMapPage: //Air/Fuel ratio target settings page - if (offset < 256) //New value is part of the afr map - { - afrTable.values[15-offset/16][offset%16] = newValue; - return; + //X Axis + afrTable.axisX[(offset - 256)] = int(newValue) * int(100); //The RPM values sent by megasquirt are divided by 100, need to multiply it back by 100 to make it correct } else { - //Check whether this is on the X (RPM) or Y (MAP/TPS) axis - if (offset < 272) - { - //X Axis - afrTable.axisX[(offset-256)] = int(newValue) * int(100); //The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct - } - else - { - //Y Axis - offset = 15-(offset-272); //Need to do a translation to flip the order - afrTable.axisY[offset] = int(newValue); - - } - return; + //Y Axis + offset = 15 - (offset - 272); //Need to do a translation to flip the order + afrTable.axisY[offset] = int(newValue); + } - - case afrSetPage: - pnt_configPage = (byte *)&configPage3; - //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( offset < page_size) - { - *(pnt_configPage + (byte)offset) = newValue; //Need to subtract 80 because the map and bins (Which make up 80 bytes) aren't part of the config pages - } - break; - - case iacPage: //Idle Air Control settings page (Page 4) - pnt_configPage = (byte *)&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( offset < page_size) - { - *(pnt_configPage + (byte)offset) = newValue; - } - break; - - case boostvvtPage: //Boost and VVT maps (8x8) - if (offset < 64) //New value is part of the boost map - { - boostTable.values[7-offset/8][offset%8] = newValue; - return; - } - else if (offset < 72) //New value is on the X (RPM) axis of the boost table - { - boostTable.axisX[(offset-64)] = int(newValue) * int(100); //The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct - return; - } - else if (offset < 80) //New value is on the Y (TPS) axis of the boost table - { - boostTable.axisY[(7-(offset-72))] = int(newValue); - return; - } - else if (offset < 144) //New value is part of the vvt map - { - offset = offset - 80; - vvtTable.values[7-offset/8][offset%8] = newValue; - return; - } - else if (offset < 152) //New value is on the X (RPM) axis of the vvt table - { - offset = offset - 144; - vvtTable.axisX[offset] = int(newValue) * int(100); //The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct - return; - } - else //New value is on the Y (Load) axis of the vvt table - { - offset = offset - 152; - vvtTable.axisY[(7-offset)] = int(newValue); - return; - } - - default: - break; + return; + } + + case afrSetPage: + pnt_configPage = &configPage3; + //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 ( offset < page_size) + { + *((byte *)pnt_configPage + (byte)offset) = newValue; //Need to subtract 80 because the map and bins (Which make up 80 bytes) aren't part of the config pages + } + break; + + case iacPage: //Idle Air Control settings page (Page 4) + 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 ( offset < page_size) + { + *((byte *)pnt_configPage + (byte)offset) = newValue; + } + break; + case boostvvtPage: //Boost and VVT maps (8x8) + if (offset < 64) //New value is part of the boost map + { + boostTable.values[7 - offset / 8][offset % 8] = newValue; + return; + } + else if (offset < 72) //New value is on the X (RPM) axis of the boost table + { + boostTable.axisX[(offset - 64)] = int(newValue) * int(100); //The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct + return; + } + else if (offset < 80) //New value is on the Y (TPS) axis of the boost table + { + boostTable.axisY[(7 - (offset - 72))] = int(newValue); + return; + } + else if (offset < 144) //New value is part of the vvt map + { + offset = offset - 80; + vvtTable.values[7 - offset / 8][offset % 8] = newValue; + return; + } + else if (offset < 152) //New value is on the X (RPM) axis of the vvt table + { + offset = offset - 144; + vvtTable.axisX[offset] = int(newValue) * int(100); //The RPM values sent by TunerStudio are divided by 100, need to multiply it back by 100 to make it correct + return; + } + else //New value is on the Y (Load) axis of the vvt table + { + offset = offset - 152; + vvtTable.axisY[(7 - offset)] = int(newValue); + return; + } + default: + break; } } /* -sendPage() packs the data within the current page (As set with the 'P' command) +sendPage() 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 +useChar - If true, all values are send as chars, this is for the serial command line interface. TunerStudio expects data as raw values, so this must be set false in that case */ -void sendPage() +void sendPage(bool useChar) { - byte* pnt_configPage; - - switch (currentPage) - { - case veMapPage: - { - //Need to perform a translation of the values[MAP/TPS][RPM] 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] = fuelTable.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 - for(int x=256;x<272;x++) { response[x] = byte(fuelTable.axisX[(x-256)] / 100); } //RPM Bins for VE table (Need to be dvidied by 100) - for(int y=272;y<288;y++) { response[y] = byte(fuelTable.axisY[15-(y-272)]); } //MAP or TPS bins for VE table - Serial.write((byte *)&response, sizeof(response)); - break; - } - - case veSetPage: - { - //All other bytes can simply be copied from the config table - byte response[page_size]; - - pnt_configPage = (byte *)&configPage1; //Create a pointer to Page 1 in memory - for(byte x=0; x 255) { tempValue = 255; } // Cap the maximum value to prevent overflow when converting to byte - if (tempValue < 0) { tempValue = 0; } - + if (tempValue > 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; int y = EEPROM_CALIBRATION_O2 + counter; @@ -550,7 +801,9 @@ void receiveCalibration(byte tableID) analogWrite(13, (counter % 50) ); counter++; } - else { every2nd = true; } + else { + every2nd = true; + } } @@ -564,36 +817,38 @@ Send 256 tooth log entries void sendToothLog(bool useChar) { - //We need 256 records to send to TunerStudio. If there aren't that many in the buffer (Buffer is 512 long) then we just return and wait for the next call - if (toothHistoryIndex < 256) { return; } //Don't believe this is the best way to go. Just display whatever is in the buffer - unsigned int tempToothHistory[512]; //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 + //We need 256 records to send to TunerStudio. If there aren't that many in the buffer (Buffer is 512 long) then we just return and wait for the next call + if (toothHistoryIndex < 256) { + return; //Don't believe this is the best way to go. Just display whatever is in the buffer + } + unsigned int tempToothHistory[512]; //Create a temporary array that will contain a copy of what is in the main toothHistory array - //Loop only needs to go to 256 (Even though the buffer is 512 long) as we only ever send 256 entries at a time - if (useChar) - { - for(int x=0; x<256; x++) - { - Serial.println(tempToothHistory[x]); - } - } - else - { - for(int x=0; x<256; x++) - { - Serial.write(highByte(tempToothHistory[x])); - Serial.write(lowByte(tempToothHistory[x])); - } - } - //Serial.flush(); + //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 256 (Even though the buffer is 512 long) as we only ever send 256 entries at a time + if (useChar) + { + for (int x = 0; x < 256; x++) + { + Serial.println(tempToothHistory[x]); + } + } + else + { + for (int x = 0; x < 256; x++) + { + Serial.write(highByte(tempToothHistory[x])); + Serial.write(lowByte(tempToothHistory[x])); + } + } + //Serial.flush(); } - + void testComm() { Serial.write(1); - return; + return; } diff --git a/decoders.ino b/decoders.ino index 744695e..f8d8b52 100644 --- a/decoders.ino +++ b/decoders.ino @@ -352,6 +352,7 @@ void triggerSetup_4G63() toothAngles[1] = 105; //Rising edge of tooth #2 toothAngles[2] = 175; //Falling edge of tooth #2 toothAngles[3] = 285; //Rising edge of tooth #1 + /* toothAngles[0] = 105; //Falling edge of tooth #1 toothAngles[1] = 175; //Rising edge of tooth #2 @@ -431,6 +432,7 @@ int getCrankAngle_4G63(int timePerDegree) int crankAngle = toothAngles[(tempToothCurrentCount - 1)] + configPage2.triggerAngle; //Perform a lookup of the fixed toothAngles array to find what the angle of the last tooth passed was. crankAngle += ldiv( (micros() - tempToothLastToothTime), timePerDegree).quot; //Estimate the number of degrees travelled since the last tooth if (crankAngle > 360) { crankAngle -= 360; } + if (crankAngle < 0) { crankAngle += 360; } return crankAngle; } diff --git a/globals.h b/globals.h index 717ef61..0370f23 100644 --- a/globals.h +++ b/globals.h @@ -142,7 +142,7 @@ struct config1 { unsigned int inj4Ang; //config1 in ini - byte mapType : 2; + byte mapSample : 2; byte strokes : 1; byte injType : 1; byte nCylinders : 4; //Number of cylinders diff --git a/reference/hardware/v0.4/Speeduino_v04_notes.png b/reference/hardware/v0.4/Speeduino_v04_notes.png new file mode 100644 index 0000000..ad267eb Binary files /dev/null and b/reference/hardware/v0.4/Speeduino_v04_notes.png differ diff --git a/reference/hardware/v0.4/schematic v0.4.1.fzz b/reference/hardware/v0.4/schematic v0.4.1.fzz new file mode 100644 index 0000000..4d186d9 Binary files /dev/null and b/reference/hardware/v0.4/schematic v0.4.1.fzz differ diff --git a/reference/speeduino.ini b/reference/speeduino.ini index 9243273..e316da7 100644 --- a/reference/speeduino.ini +++ b/reference/speeduino.ini @@ -128,7 +128,7 @@ page = 2 inj4Ang = scalar, U16, 34, "deg", 1.0, 0.0, 0.0, 360, 0 ; Config1 - mapType = bits, U08, 36, [0:1], "115 kPa", "250 kPa", "INVALID", "INVALID" + mapSample = bits, U08, 36, [0:1], "Instantaneous", "Cycle Average", "Cycle Minimum", "INVALID" twoStroke = bits, U08, 36, [2:2], "Four-stroke", "Two-stroke" injType = bits, U08, 36, [3:3], "Port", "Throttle Body" nCylinders = bits, U08, 36, [4:8], "INVALID","1","2","3","4","INVALID","6","INVALID","8","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID","INVALID" @@ -215,46 +215,46 @@ page = 4 ; name = scalar, type, offset, units, scale, translate, lo, hi, digits ;Dwell control ;running dwell variable railed to 8 - who needs more than 8ms? - dwellcont = bits, U08, 12, [0:0], "INVALID", "Dwell control" + dwellcont = bits, U08, 12, [0:0], "INVALID", "Dwell control" useDwellLim= bits, U08, 12, [1:1], "Off", "On" - dwellcrank = scalar, U08, 13, "ms", 0.1, 0, 0, 25, 1 - dwellrun = scalar, U08, 14, "ms", 0.1, 0, 0, 8, 1 - numteeth = scalar, U08, 15, "teeth", 1.0, 0.0, 0.0, 255, 0 - onetwo = scalar, U08, 16, "teeth", 1.0, 0.0, 0.0, 255, 0 + dwellcrank = scalar, U08, 13, "ms", 0.1, 0, 0, 25, 1 + dwellrun = scalar, U08, 14, "ms", 0.1, 0, 0, 8, 1 + numteeth = scalar, U08, 15, "teeth", 1.0, 0.0, 0.0, 255, 0 + onetwo = scalar, U08, 16, "teeth", 1.0, 0.0, 0.0, 255, 0 - crankRPM = scalar, U08, 17, "rpm", 100, 0.0, 100, 1000, 0 - tpsflood = scalar, U08, 18, "%", 1.0, 0.0, 0.0, 255.0, 0 + crankRPM = scalar, U08, 17, "rpm", 100, 0.0, 100, 1000, 0 + tpsflood = scalar, U08, 18, "%", 1.0, 0.0, 0.0, 255.0, 0 ;Rev Limits - SoftRevLim = scalar, U08, 19, "rpm", 100, 0.0, 100, 25500, 0 - SoftLimRetard = scalar, U08, 20, "deg", 1.0, 0.0, 0.0, 80, 0 - SoftLimMax = scalar, U08, 21, "s", 0.1, 0.0, 0.0, 25.5, 1 - HardRevLim = scalar, U08, 22, "rpm", 100, 0.0, 100, 25500, 0 + SoftRevLim = scalar, U08, 19, "rpm", 100, 0.0, 100, 25500, 0 + SoftLimRetard = scalar, U08, 20, "deg", 1.0, 0.0, 0.0, 80, 0 + SoftLimMax = scalar, U08, 21, "s", 0.1, 0.0, 0.0, 25.5, 1 + HardRevLim = scalar, U08, 22, "rpm", 100, 0.0, 100, 25500, 0 ;TPS based acceleration enrichment - taeBins = array, U08, 23, [ 4], "%/s", 10.0, 0.00000, 0.00, 2550.0, 0 - taeRates = array, U08, 27, [ 4], "%", 1.0, 0.00000, 0.00, 255.0, 0 ; 4 bytes - wueRates = array, U08, 31, [10], "C", 1.0, 0.0, 100.0, 255.0, 0 + taeBins = array, U08, 23, [ 4], "%/s", 10.0, 0.0, 0.00, 2550.0, 0 + taeRates = array, U08, 27, [ 4], "%", 1.0, 0.0, 0.00, 255.0, 0 ; 4 bytes + wueRates = array, U08, 31, [10], "C", 1.0, 0.0, 100.0, 255.0, 0 ;Dwell config options - dwellLim = scalar, U08, 41, "ms", 1, 0, 0, 32, 0 - dwellRates = array, U08, 42, [6], "%", 1.0, 0.0, 0.00, 255.0, 0 + dwellLim = scalar, U08, 41, "ms", 1, 0, 0, 32, 0 + dwellRates = array, U08, 42, [6], "%", 1.0, 0.0, 0.00, 255.0, 0 - unused48 = scalar, U08, 48, "RPM", 100.0, 0.0, 100, 25500, 0 - unused49 = scalar, U08, 49, "RPM", 100.0, 0.0, 100, 25500, 0 - unused50 = scalar, U08, 50, "RPM", 100.0, 0.0, 100, 25500, 0 - unused51 = scalar, U08, 51, "RPM", 100.0, 0.0, 100, 25500, 0 - unused52 = scalar, U08, 52, "RPM", 100.0, 0.0, 100, 25500, 0 - unused53 = scalar, U08, 53, "RPM", 100.0, 0.0, 100, 25500, 0 - unused54 = scalar, U08, 54, "RPM", 100.0, 0.0, 100, 25500, 0 - unused55 = scalar, U08, 55, "RPM", 100.0, 0.0, 100, 25500, 0 - unused56 = scalar, U08, 56, "RPM", 100.0, 0.0, 100, 25500, 0 - unused57 = scalar, U08, 57, "RPM", 100.0, 0.0, 100, 25500, 0 - unused58 = scalar, U08, 58, "RPM", 100.0, 0.0, 100, 25500, 0 - unused59 = scalar, U08, 59, "RPM", 100.0, 0.0, 100, 25500, 0 - unused60 = scalar, U08, 60, "RPM", 100.0, 0.0, 100, 25500, 0 - unused61 = scalar, U08, 61, "RPM", 100.0, 0.0, 100, 25500, 0 - unused62 = scalar, U08, 62, "RPM", 100.0, 0.0, 100, 25500, 0 - unused63 = scalar, U08, 63, "RPM", 100.0, 0.0, 100, 25500, 0 + unused48 = scalar, U08, 48, "RPM", 100.0, 0.0, 100, 25500, 0 + unused49 = scalar, U08, 49, "RPM", 100.0, 0.0, 100, 25500, 0 + unused50 = scalar, U08, 50, "RPM", 100.0, 0.0, 100, 25500, 0 + unused51 = scalar, U08, 51, "RPM", 100.0, 0.0, 100, 25500, 0 + unused52 = scalar, U08, 52, "RPM", 100.0, 0.0, 100, 25500, 0 + unused53 = scalar, U08, 53, "RPM", 100.0, 0.0, 100, 25500, 0 + unused54 = scalar, U08, 54, "RPM", 100.0, 0.0, 100, 25500, 0 + unused55 = scalar, U08, 55, "RPM", 100.0, 0.0, 100, 25500, 0 + unused56 = scalar, U08, 56, "RPM", 100.0, 0.0, 100, 25500, 0 + unused57 = scalar, U08, 57, "RPM", 100.0, 0.0, 100, 25500, 0 + unused58 = scalar, U08, 58, "RPM", 100.0, 0.0, 100, 25500, 0 + unused59 = scalar, U08, 59, "RPM", 100.0, 0.0, 100, 25500, 0 + unused60 = scalar, U08, 60, "RPM", 100.0, 0.0, 100, 25500, 0 + unused61 = scalar, U08, 61, "RPM", 100.0, 0.0, 100, 25500, 0 + unused62 = scalar, U08, 62, "RPM", 100.0, 0.0, 100, 25500, 0 + unused63 = scalar, U08, 63, "RPM", 100.0, 0.0, 100, 25500, 0 ;-------------------------------------------------- @@ -303,14 +303,22 @@ page = 6 injBatRates = array, U08, 21, [6], "%", 1, 0, 0, 255, 0 ;Values for injector pulsewidth vs voltage airDenBins = array, U08, 27, [9], "C", 1.0, -40, -40, 215, 0 ; Bins for the air density correction curve airDenRates = array, U08, 36, [9], "%", 1.0, 0.0, 0, 255, 0 ; Values for the air density correction curve - boostFreq = scalar, U08, 45, "Hz", 2.0, 0.0, 10, 511, 0 - vvtFreq = scalar, U08, 46, "Hz", 2.0, 0.0, 10, 511, 0 - idleFreq = scalar, U08, 47, "Hz", 2.0, 0.0, 10, 511, 0 - unused48 = scalar, U08, 48, "RPM", 100.0, 0.0, 100, 25500, 0 - unused49 = scalar, U08, 49, "RPM", 100.0, 0.0, 100, 25500, 0 - unused50 = scalar, U08, 50, "RPM", 100.0, 0.0, 100, 25500, 0 - unused51 = scalar, U08, 51, "RPM", 100.0, 0.0, 100, 25500, 0 - unused52 = scalar, U08, 52, "RPM", 100.0, 0.0, 100, 25500, 0 + +; PWM Frequencies + boostFreq = scalar, U08, 45, "Hz", 2.0, 0.0, 10, 511, 0 + vvtFreq = scalar, U08, 46, "Hz", 2.0, 0.0, 10, 511, 0 + idleFreq = scalar, U08, 47, "Hz", 2.0, 0.0, 10, 511, 0 + +; Launch Control + launchPin = bits , U08, 48, [0:5], "Board Default", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "INVALID", "INVALID", "INVALID", "INVALID", "INVALID", "INVALID", "INVALID", "INVALID", "INVALID" + launchEnable= bits, U08, 48, [6:6], "No", "Yes" + unused48h = bits, U08, 48, [7:7], "No", "Yes" + + lnchSoftLim = scalar, U08, 49, "rpm", 100, 0.0, 100, 25500, 0 + lnchRetard = scalar, U08, 50, "deg", 1.0, 0.0, 0.0, 80, 0 + lnchHardLim = scalar, U08, 51, "rpm", 100, 0.0, 100, 25500, 0 + lnchFuelAdd = scalar, U08, 52, "%", 1.0, 0.0, 0.0, 80, 0 + unused53 = scalar, U08, 53, "RPM", 100.0, 0.0, 100, 25500, 0 unused54 = scalar, U08, 54, "RPM", 100.0, 0.0, 100, 25500, 0 unused55 = scalar, U08, 55, "RPM", 100.0, 0.0, 100, 25500, 0 @@ -364,6 +372,11 @@ page = 7 fanSP = scalar, U08, 57, "°F", 1.0, -40, -40, 215.0, 0 fanHyster = scalar, U08, 58, "°F", 1.0, -40, -40, 215.0, 0 #endif + unused59 = scalar, U08, 59, "RPM", 100.0, 0.0, 100, 25500, 0 + unused60 = scalar, U08, 60, "RPM", 100.0, 0.0, 100, 25500, 0 + unused61 = scalar, U08, 61, "RPM", 100.0, 0.0, 100, 25500, 0 + unused62 = scalar, U08, 62, "RPM", 100.0, 0.0, 100, 25500, 0 + unused63 = scalar, U08, 63, "RPM", 100.0, 0.0, 100, 25500, 0 ;-------------------------------------------------- ;Boost and vvt maps (Page 8) @@ -471,6 +484,7 @@ page = 8 menu = "&Accessories" subMenu = fanSettings, "Thermo Fan" + subMenu = LaunchControl, "Launch Control" subMenu = std_separator subMenu = boostSettings, "Boost Control" subMenu = boostTbl, "Boost duty cycle", 8, { boostEnabled } @@ -525,6 +539,7 @@ page = 8 dialog = engine_constants_south field = "Injector Timing", injTiming, { nCylinders <= 4 } field = "Board Layout", pinLayout + field = "MAP Sample method", mapSample dialog = engine_constants, "" panel = std_injection, North @@ -597,92 +612,93 @@ page = 8 panel = stepper_idle - dialog = crankPW, "Cranking Pulsewidths (ms)" - #if CELSIUS - field = "Cranking RPM (Max)", crankRPM - field = "Flood Clear level", tpsflood - field = "" - field = "Priming Pulsewidth", primePulse - field = "Pulsewidth at -40°C", crankCold - field = "Pulsewidth at 77°C", crankHot - field = "" - field = "Cranking Enrichment %", crankingPct - #else - field = "Cranking RPM (Max)", crankRPM - field = "Flood Clear level", tpsflood - field = "" - field = "Priming Pulsewidth", primePulse - field = "Pulsewidth at -40°F", crankCold - field = "Pulsewidth at 170°F", crankHot - field = "" - field = "Cranking Enrichment %", crankingPct - #endif + dialog = crankPW, "Cranking Pulsewidths (ms)" + #if CELSIUS + field = "Cranking RPM (Max)", crankRPM + field = "Flood Clear level", tpsflood + field = "" + field = "Priming Pulsewidth", primePulse + field = "Pulsewidth at -40°C", crankCold + field = "Pulsewidth at 77°C", crankHot + field = "" + field = "Cranking Enrichment %", crankingPct + #else + field = "Cranking RPM (Max)", crankRPM + field = "Flood Clear level", tpsflood + field = "" + field = "Priming Pulsewidth", primePulse + field = "Pulsewidth at -40°F", crankCold + field = "Pulsewidth at 170°F", crankHot + field = "" + field = "Cranking Enrichment %", crankingPct + #endif - dialog = aseSettings, "Afterstart Enrichment" - field = "Enrichment %", asePct - field = "Number of Sec to run", aseCount + dialog = aseSettings, "Afterstart Enrichment" + field = "Enrichment %", asePct + field = "Number of Sec to run", aseCount - - ;dialog = egoSettings, "Exhaust Gas Oxygen" - ;field = "EGO Sensor Type", egoType - ;field = "EGO Switch Point (v)", egoSwitch - ;field = "Coolant Temp Activation", egoTemp - ;field = "Ignition Events Per Step", egoCount - ;field = "EGO Step", egoDelta - ;field = "EGO ± Limit", egoLimit - ;field = "EGO Active Above", egoRPM - dialog = triggerSettings,"Trigger Settings",4 - topicHelp = Shelp3 - field = "Trigger Pattern", TrigPattern - field = "Primary base teeth", numteeth, { TrigPattern == 0 || TrigPattern == 2 } - field = "Primary trigger speed", TrigSpeed, { TrigPattern == 0 } - field = "Missing teeth", onetwo, { TrigPattern == 0 } - field = "Secondary teeth", onetwo, { TrigPattern == 2 } - field = "Trigger Angle ", TrigAng - field = "This number represents the angle ATDC when " - field = "tooth #1 passes the primary sensor." - field = "" - field = "Skip Revolutions", StgCycles - field = "Note: This is the number of revolutions that will be skipped during" - field = "cranking before the injectors and coils are fired" - field = "Trigger edge", TrigEdge + dialog = triggerSettings,"Trigger Settings",4 + topicHelp = Shelp3 + field = "Trigger Pattern", TrigPattern + field = "Primary base teeth", numteeth, { TrigPattern == 0 || TrigPattern == 2 } + field = "Primary trigger speed", TrigSpeed, { TrigPattern == 0 } + field = "Missing teeth", onetwo, { TrigPattern == 0 } + field = "Secondary teeth", onetwo, { TrigPattern == 2 } + field = "Trigger Angle ", TrigAng + field = "This number represents the angle ATDC when " + field = "tooth #1 passes the primary sensor." + field = "" + field = "Skip Revolutions", StgCycles + field = "Note: This is the number of revolutions that will be skipped during" + field = "cranking before the injectors and coils are fired" + field = "Trigger edge", TrigEdge - dialog = sparkSettings,"Spark Settings",4 - topicHelp = Shelp3 - field = "Cranking advance Angle", CrankAng - ;field = "Hold Ignition", IgHold - field = "Spark Outputs triggers", IgInv - field = "" - field = "Fixed Angle (0 = use map)", FixAng - ;field = "Trim Angle", Trim + dialog = sparkSettings,"Spark Settings",4 + topicHelp = Shelp3 + field = "Cranking advance Angle", CrankAng + ;field = "Hold Ignition", IgHold + field = "Spark Outputs triggers", IgInv + field = "" + field = "Fixed Angle (0 = use map)", FixAng + ;field = "Trim Angle", Trim dialog = dwellSettings, "Dwell Settings", 4 - topicHelp = DwellHelp - field = "Dwell control", dwellcont - field = "Or:" - field = " Cranking dwell", dwellcrank, { dwellcont } - field = " Running dwell", dwellrun, { dwellcont } - ;field = "Minimum discharge period", mindischg, { dwellcont } - field = "" - field = "#Note" - field = "The above times are for 12V. Voltage correction" - field = "is applied. At higher voltages the time is reduced" - field = "and when low it is increased" - field = "" - field = "Overdwell protection" - field = "Use Overdwell protection", useDwellLim - field = "Max dwell time", dwellLim, { useDwellLim } - field = "Note: Set the maximum dwell time at least 3ms above" - field = "your desired dwell time (Including cranking)" + topicHelp = DwellHelp + field = "Dwell control", dwellcont + field = "Or:" + field = " Cranking dwell", dwellcrank, { dwellcont } + field = " Running dwell", dwellrun, { dwellcont } + ;field = "Minimum discharge period", mindischg, { dwellcont } + field = "" + field = "#Note" + field = "The above times are for 12V. Voltage correction" + field = "is applied. At higher voltages the time is reduced" + field = "and when low it is increased" + field = "" + field = "Overdwell protection" + field = "Use Overdwell protection", useDwellLim + field = "Max dwell time", dwellLim, { useDwellLim } + field = "Note: Set the maximum dwell time at least 3ms above" + field = "your desired dwell time (Including cranking)" dialog = RevLimiterS, "Rev Limiter", 4 - topicHelp = Fhelp7 - field = "Rev Limiter" - field = "Soft rev limit", SoftRevLim - field = "Soft limit absolute timing", SoftLimRetard - field = "Soft limit max time", SoftLimMax - field = "Hard Rev limit", HardRevLim + topicHelp = Fhelp7 + field = "Rev Limiter" + field = "Soft rev limit", SoftRevLim + field = "Soft limit absolute timing", SoftLimRetard + field = "Soft limit max time", SoftLimMax + field = "Hard Rev limit", HardRevLim + + dialog = LaunchControl, "Launch Control", 6 + topicHelp = Fhelp7 + field = "Launch Control" + field = "Enable Launch", launchEnable + field = "Launch Input Pin", launchPin, { launchEnable } + field = "Soft rev limit", lnchSoftLim, { launchEnable } + field = "Soft limit absolute timing", lnchRetard, { launchEnable } + field = "Hard rev limit", lnchHardLim, { launchEnable } + field = "Fuel adder during launch", lnchFuelAdd, { launchEnable } dialog = OLED, "OLED Display", 2 field = "Display Type", display diff --git a/reference/wiki/v0_4_board_annotated_1.jpg b/reference/wiki/v0_4_board_annotated_1.jpg new file mode 100644 index 0000000..944edcb Binary files /dev/null and b/reference/wiki/v0_4_board_annotated_1.jpg differ diff --git a/speeduino.ino b/speeduino.ino index 6be80df..d3d976d 100644 --- a/speeduino.ino +++ b/speeduino.ino @@ -83,8 +83,11 @@ byte o2CalibrationTable[CALIBRATION_TABLE_SIZE]; unsigned long counter; unsigned long currentLoopTime; //The time the current loop started (uS) unsigned long previousLoopTime; //The time the previous loop started (uS) -unsigned long scheduleStart; -unsigned long scheduleEnd; + +unsigned long MAPrunningValue; //Used for tracking either the total of all MAP readings in this cycle (Event average) or the lowest value detected in this cycle (event minimum) +unsigned int MAPcount; //Number of samples taken in the current MAP cycle +byte MAPcurRev = 0; //Tracks which revolution we're sampling on + byte coilHIGH = HIGH; byte coilLOW = LOW; @@ -493,9 +496,51 @@ void loop() //***SET STATUSES*** //----------------------------------------------------------------------------------------------------- - //currentStatus.MAP = map(analogRead(pinMAP), 0, 1023, 10, 255); //Get the current MAP value - currentStatus.mapADC = analogRead(pinMAP); - currentStatus.MAP = map(currentStatus.mapADC, 0, 1023, configPage1.mapMin, configPage1.mapMax); //Get the current MAP value + //MAP Sampling system + switch(configPage1.mapSample) + { + case 0: + //Instantaneous MAP readings + currentStatus.mapADC = analogRead(pinMAP); + currentStatus.MAP = map(currentStatus.mapADC, 0, 1023, configPage1.mapMin, configPage1.mapMax); //Get the current MAP value + break; + + case 1: + //Average of a cycle + if( (MAPcurRev == startRevolutions) || (MAPcurRev == startRevolutions+1) ) //2 revolutions are looked at for 4 stroke. 2 stroke not currently catered for. + { + MAPrunningValue = MAPrunningValue + analogRead(pinMAP); //Add the current reading onto the total + MAPcount++; + } + else + { + //Reaching here means that the last cylce has completed and the MAP value should be calculated + currentStatus.mapADC = ldiv(MAPrunningValue, MAPcount).quot; + currentStatus.MAP = map(currentStatus.mapADC, 0, 1023, configPage1.mapMin, configPage1.mapMax); //Get the current MAP value + MAPcurRev = startRevolutions; //Reset the current rev count + MAPrunningValue = 0; + MAPcount = 0; + } + break; + + case 2: + //Minimum reading in a cycle + if( (MAPcurRev == startRevolutions) || (MAPcurRev == startRevolutions+1) ) //2 revolutions are looked at for 4 stroke. 2 stroke not currently catered for. + { + int tempValue = analogRead(pinMAP); + if( tempValue < MAPrunningValue) { MAPrunningValue = tempValue; } //Check whether the current reading is lower than the running minimum + MAPcount++; + } + else + { + //Reaching here means that the last cylce has completed and the MAP value should be calculated + currentStatus.mapADC = MAPrunningValue; + currentStatus.MAP = map(currentStatus.mapADC, 0, 1023, configPage1.mapMin, configPage1.mapMax); //Get the current MAP value + MAPcurRev = startRevolutions; //Reset the current rev count + MAPrunningValue = 1023; //Reset the latest value so the next reading will always be lower + } + break; + } //TPS setting to be performed every 32 loops (any faster and it can upset the TPSdot sampling time) if ((mainLoopCount & 31) == 1) @@ -820,7 +865,7 @@ void loop() if( tempCrankAngle < 0) { tempCrankAngle += 360; } tempStartAngle = ignition2StartAngle - channel2IgnDegrees; if ( tempStartAngle < 0) { tempStartAngle += 360; } - if (tempStartAngle > tempCrankAngle) + if ( (tempStartAngle > tempCrankAngle) && ign2LastRev != startRevolutions) { setIgnitionSchedule2(beginCoil2Charge, ((unsigned long)(tempStartAngle - tempCrankAngle) * (unsigned long)timePerDegree), @@ -895,6 +940,5 @@ void openInjector1and4() { digitalWrite(pinInjector1, HIGH); digitalWrite(pinInj void closeInjector1and4() { digitalWrite(pinInjector1, LOW); digitalWrite(pinInjector4, LOW);BIT_CLEAR(currentStatus.squirt, 0); } void openInjector2and3() { digitalWrite(pinInjector2, HIGH); digitalWrite(pinInjector3, HIGH); BIT_SET(currentStatus.squirt, 1); } void closeInjector2and3() { digitalWrite(pinInjector2, LOW); digitalWrite(pinInjector3, LOW); BIT_CLEAR(currentStatus.squirt, 1); } -