diff --git a/.github/workflows/misra.yml b/.github/workflows/misra.yml index 74f745e1..c2dab1e2 100644 --- a/.github/workflows/misra.yml +++ b/.github/workflows/misra.yml @@ -44,7 +44,7 @@ jobs: env: MISRA_GIST: ${{ secrets.MISRA_GIST }} VIOLATIONS: ${{ env.VIOLATIONS }} - uses: schneegans/dynamic-badges-action@v1.6.0 + uses: schneegans/dynamic-badges-action@v1.7.0 with: auth: ${{ secrets.MISRA_GIST }} gistID: d8a449a3f6d3307dab457431512502f9 diff --git a/reference/speeduino.ini b/reference/speeduino.ini index 6a9ab9ae..2bbb308e 100644 --- a/reference/speeduino.ini +++ b/reference/speeduino.ini @@ -1005,8 +1005,8 @@ page = 9 unused10_110 = scalar, U08, 110, "", 1, 0, 0, 255, 0 unused10_111 = scalar, U08, 111, "", 1, 0, 0, 255, 0 - unused10_112 = scalar, U08, 112, "", 1, 0, 0, 255, 0 - unused10_113 = scalar, U08, 113, "", 1, 0, 0, 255, 0 + egoMAPMax = scalar, U08, 112, "kPa", 2.0, 0.0, 2.0, 511.0, 0 + egoMAPMin = scalar, U08, 113, "kPa", 2.0, 0.0, 2.0, 511.0, 0 speeduino_tsCanId = bits, U08, 114, [0:3], $tsCanId_list true_address = bits, U16, 115, [0:10], $CAN_ADDRESS_HEX @@ -1075,7 +1075,15 @@ page = 9 coolantProtTemp = array, U08, 173, [6], "F", 1.8, -22.23, -40, 419, 0 #endif - unused179_184 = array, U08, 179, [6], "", 1, 0, 0, 255, 0 + unused10_179 = scalar, U08, 179, "", 1, 0, 0, 255, 0 + + dfcoTaperTime = scalar, U08, 180, "S", 0.1, 0.0, 0.0, 25.5, 1 + dfcoTaperFuel = scalar, U08, 181, "%", 1.0, 0.0, 0, 255, 0 + dfcoTaperAdvance = scalar, U08, 182, "deg", 1.0, 0.0, 0, 40, 0 + dfcoTaperEnable = bits, U08, 183, [0:0], "Off", "On" + unused10_182 = bits, U08, 183, [1:7], "" + + unused10_184 = scalar, U08, 184, "", 1, 0, 0, 255, 0 ; AFR engine protection afrProtectEnabled = bits, U08, 185, [0:1], "Off", "Fixed mode", "Table mode", "INVALID" @@ -1839,6 +1847,9 @@ page = 15 defaultValue = rollingProtRPMDelta, -300 -200 -100 -50 defaultValue = rollingProtCutPercent, 50 65 80 95 + defaultValue = egoMAPMax, 100 + defaultValue = egoMAPMin, 26 + #if LAMBDA defaultValue = wueAFR, -0.136 -0.102 -0.082 -0.068 -0.054 -0.041 -0.027 -0.014 -0.007 0.000 #else @@ -2415,6 +2426,9 @@ menuDialog = main boostDCWhenDisabled = "When the closedloop boost controller is disabled by 'enable trigger', this is the Duty cycle set on the boost selenoid. Ususally this is 99% because it keeps the waste gate firmly closed until the threshold and builds boost as fast as possible (no wastegate leak)" boostControlEnableThreshold = "When the 'Boost control enable trigger' is set to 'fixed', this value is used as threshold. Usually the value is set just below the wastgate pressure of the used turbo setup. For example 130kpa for a 0.3 bar wastegate actuator. Below this value the ECU has no control over the boost anyway." + egoMAPMax = "Only Correct below this MAP Value" + egoMAPMin = "Only Correct above this MAP Value" + [UserDefined] ; Enhanced TunerStudio dialogs can be defined here @@ -2610,6 +2624,10 @@ menuDialog = main field = "Cutoff delay", dfcoDelay, { dfcoEnabled } field = "Cutoff RPM", dfcoRPM, { dfcoEnabled } field = "RPM Hysteresis", dfcoHyster, { dfcoEnabled } + field = "Enable cutoff taper", dfcoTaperEnable, { dfcoEnabled } + field = "Cutoff taper time", dfcoTaperTime, { dfcoEnabled && dfcoTaperEnable } + field = "Taper end fuel amount", dfcoTaperFuel, { dfcoEnabled && dfcoTaperEnable } + field = "Taper advance remove", dfcoTaperAdvance, { dfcoEnabled && dfcoTaperEnable } dialog = accelEnrichments_north_south, "" liveGraph = pump_ae_Graph, "AE Graph" @@ -2723,6 +2741,8 @@ menuDialog = main field = "Active Above Coolant", egoTemp, { egoType && (egoAlgorithm < 3) } field = "Active Above RPM", egoRPM, { egoType && (egoAlgorithm < 3) } field = "Active Below TPS", egoTPSMax, { egoType && (egoAlgorithm < 3) } + field = "Active Below MAP", egoMAPMax, { egoType && (egoAlgorithm < 3) } + field = "Active Above MAP", egoMAPMin, { egoType && (egoAlgorithm < 3) } field = "EGO delay after start", ego_sdelay, { (egoAlgorithm < 3) } field = "PID Proportional Gain", egoKP, { egoType && (egoAlgorithm == 2) } field = "PID Integral", egoKI, { egoType && (egoAlgorithm == 2) } diff --git a/speeduino/comms_legacy.cpp b/speeduino/comms_legacy.cpp index 093aa88f..2a5ada2b 100644 --- a/speeduino/comms_legacy.cpp +++ b/speeduino/comms_legacy.cpp @@ -190,6 +190,10 @@ void legacySerialCommand(void) Serial.write(highByte(currentStatus.freeRAM)); break; + case 'M': + legacySerialHandler(currentCommand, Serial, serialStatusFlag); + break; + case 'N': // Displays a new line. Like pushing enter in a text editor Serial.println(); break; @@ -391,48 +395,6 @@ void legacySerialCommand(void) break; - case 'M': - serialStatusFlag = SERIAL_COMMAND_INPROGRESS_LEGACY; - - if(chunkPending == false) - { - //This means it's a new request - //7 bytes required: - //2 - Page identifier - //2 - offset - //2 - Length - //1 - 1st New value - if(Serial.available() >= 7) - { - byte offset1, offset2, length1, length2; - - Serial.read(); // First byte of the page identifier can be ignored. It's always 0 - currentPage = Serial.read(); - //currentPage = 1; - offset1 = Serial.read(); - offset2 = Serial.read(); - valueOffset = word(offset2, offset1); - length1 = Serial.read(); - length2 = Serial.read(); - chunkSize = word(length2, length1); - - //Regular page data - chunkPending = true; - chunkComplete = 0; - } - } - //This CANNOT be an else of the above if statement as chunkPending gets set to true above - if(chunkPending == true) - { - while( (Serial.available() > 0) && (chunkComplete < chunkSize) ) - { - setPageValue(currentPage, (valueOffset + chunkComplete), Serial.read()); - chunkComplete++; - } - if(chunkComplete >= chunkSize) { serialStatusFlag = SERIAL_INACTIVE; chunkPending = false; } - } - break; - case 'w': //No w commands are supported in legacy mode. This should never be called if(Serial.available() >= 7) @@ -586,6 +548,48 @@ void legacySerialHandler(byte cmd, Stream &targetPort, SerialStatus &targetStatu } break; + case 'M': + targetStatusFlag = SERIAL_COMMAND_INPROGRESS_LEGACY; + + if(chunkPending == false) + { + //This means it's a new request + //7 bytes required: + //2 - Page identifier + //2 - offset + //2 - Length + //1 - 1st New value + if(targetPort.available() >= 7) + { + byte offset1, offset2, length1, length2; + + targetPort.read(); // First byte of the page identifier can be ignored. It's always 0 + currentPage = targetPort.read(); + //currentPage = 1; + offset1 = targetPort.read(); + offset2 = targetPort.read(); + valueOffset = word(offset2, offset1); + length1 = targetPort.read(); + length2 = targetPort.read(); + chunkSize = word(length2, length1); + + //Regular page data + chunkPending = true; + chunkComplete = 0; + } + } + //This CANNOT be an else of the above if statement as chunkPending gets set to true above + if(chunkPending == true) + { + while( (targetPort.available() > 0) && (chunkComplete < chunkSize) ) + { + setPageValue(currentPage, (valueOffset + chunkComplete), targetPort.read()); + chunkComplete++; + } + if(chunkComplete >= chunkSize) { targetStatusFlag = SERIAL_INACTIVE; chunkPending = false; } + } + break; + case 'p': targetStatusFlag = SERIAL_COMMAND_INPROGRESS_LEGACY; diff --git a/speeduino/comms_secondary.cpp b/speeduino/comms_secondary.cpp index 610d82a9..c3d77172 100644 --- a/speeduino/comms_secondary.cpp +++ b/speeduino/comms_secondary.cpp @@ -137,6 +137,10 @@ void secondserial_Command(void) */ break; } + + case 'M': + legacySerialHandler(currentSecondaryCommand, secondarySerial, serialSecondaryStatusFlag); + break; case 'n': // sends the bytes of realtime values from the NEW CAN list //sendValues(0, NEW_CAN_PACKET_SIZE, 0x32, secondarySerial, serialSecondaryStatusFlag); //send values to serial3 diff --git a/speeduino/corrections.cpp b/speeduino/corrections.cpp index f1edba50..77caa3e7 100644 --- a/speeduino/corrections.cpp +++ b/speeduino/corrections.cpp @@ -48,9 +48,10 @@ byte lastKnockCount; int16_t knockWindowMin; //The current minimum crank angle for a knock pulse to be valid int16_t knockWindowMax;//The current maximum crank angle for a knock pulse to be valid uint8_t aseTaper; -uint8_t dfcoTaper; +uint8_t dfcoDelay; uint8_t idleAdvTaper; uint8_t crankingEnrichTaper; +uint8_t dfcoTaper; /** Initialise instances and vars related to corrections (at ECU boot-up). */ @@ -122,7 +123,9 @@ uint16_t correctionsFuel(void) if (currentStatus.launchCorrection != 100) { sumCorrections = div100(sumCorrections * currentStatus.launchCorrection); } bitWrite(currentStatus.status1, BIT_STATUS1_DFCO, correctionDFCO()); - if ( BIT_CHECK(currentStatus.status1, BIT_STATUS1_DFCO) == 1 ) { sumCorrections = 0; } + byte dfcoTaperCorrection = correctionDFCOfuel(); + if (dfcoTaperCorrection == 0) { sumCorrections = 0; } + else if (dfcoTaperCorrection != 100) { sumCorrections = div100(sumCorrections * dfcoTaperCorrection); } if(sumCorrections > 1500) { sumCorrections = 1500; } //This is the maximum allowable increase during cranking return (uint16_t)sumCorrections; @@ -539,6 +542,27 @@ byte correctionLaunch(void) return launchValue; } +/** +*/ +byte correctionDFCOfuel(void) +{ + byte scaleValue = 100; + if ( BIT_CHECK(currentStatus.status1, BIT_STATUS1_DFCO) ) + { + if ( (configPage9.dfcoTaperEnable == 1) && (dfcoTaper != 0) ) + { + //Do a check if the user reduced the duration while active to avoid overflow + if (dfcoTaper > configPage9.dfcoTaperTime) { dfcoTaper = configPage9.dfcoTaperTime; } + scaleValue = map(dfcoTaper, configPage9.dfcoTaperTime, 0, 100, configPage9.dfcoTaperFuel); + if( BIT_CHECK(LOOP_TIMER, BIT_TIMER_10HZ) ) { dfcoTaper--; } + } + else { scaleValue = 0; } //Taper ended or disabled, disable fuel + } + else { dfcoTaper = configPage9.dfcoTaperTime; } //Keep updating the duration until DFCO is active + + return scaleValue; +} + /* * Returns true if deceleration fuel cutoff should be on, false if its off */ @@ -550,19 +574,19 @@ bool correctionDFCO(void) if ( BIT_CHECK(currentStatus.status1, BIT_STATUS1_DFCO) == 1 ) { DFCOValue = ( currentStatus.RPM > ( configPage4.dfcoRPM * 10) ) && ( currentStatus.TPS < configPage4.dfcoTPSThresh ); - if ( DFCOValue == false) { dfcoTaper = 0; } + if ( DFCOValue == false) { dfcoDelay = 0; } } else { if ( (currentStatus.TPS < configPage4.dfcoTPSThresh) && (currentStatus.coolant >= (int)(configPage2.dfcoMinCLT - CALIBRATION_TEMPERATURE_OFFSET)) && ( currentStatus.RPM > (unsigned int)( (configPage4.dfcoRPM * 10) + (configPage4.dfcoHyster * 2)) ) ) { - if( dfcoTaper < configPage2.dfcoDelay ) + if( dfcoDelay < configPage2.dfcoDelay ) { - if( BIT_CHECK(LOOP_TIMER, BIT_TIMER_10HZ) ) { dfcoTaper++; } + if( BIT_CHECK(LOOP_TIMER, BIT_TIMER_10HZ) ) { dfcoDelay++; } } else { DFCOValue = true; } } - else { dfcoTaper = 0; } //Prevent future activation right away if previous time wasn't activated + else { dfcoDelay = 0; } //Prevent future activation right away if previous time wasn't activated } // DFCO active check } // DFCO enabled check return DFCOValue; @@ -631,7 +655,7 @@ byte correctionAFRClosedLoop(void) AFRnextCycle = ignitionCount + configPage6.egoCount; //Set the target ignition event for the next calculation //Check all other requirements for closed loop adjustments - if( (currentStatus.coolant > (int)(configPage6.egoTemp - CALIBRATION_TEMPERATURE_OFFSET)) && (currentStatus.RPM > (unsigned int)(configPage6.egoRPM * 100)) && (currentStatus.TPS <= configPage6.egoTPSMax) && (currentStatus.O2 < configPage6.ego_max) && (currentStatus.O2 > configPage6.ego_min) && (currentStatus.runSecs > configPage6.ego_sdelay) && (BIT_CHECK(currentStatus.status1, BIT_STATUS1_DFCO) == 0) ) + if( (currentStatus.coolant > (int)(configPage6.egoTemp - CALIBRATION_TEMPERATURE_OFFSET)) && (currentStatus.RPM > (unsigned int)(configPage6.egoRPM * 100)) && (currentStatus.TPS <= configPage6.egoTPSMax) && (currentStatus.O2 < configPage6.ego_max) && (currentStatus.O2 > configPage6.ego_min) && (currentStatus.runSecs > configPage6.ego_sdelay) && (BIT_CHECK(currentStatus.status1, BIT_STATUS1_DFCO) == 0) && ( currentStatus.MAP <= (configPage9.egoMAPMax * 2U) ) && ( currentStatus.MAP >= (configPage9.egoMAPMin * 2U) ) ) { //Check which algorithm is used, simple or PID @@ -702,6 +726,8 @@ int8_t correctionsIgn(int8_t base_advance) advance = correctionSoftFlatShift(advance); advance = correctionKnock(advance); + advance = correctionDFCOignition(advance); + //Fixed timing check must go last advance = correctionFixedTiming(advance); advance = correctionCrankingFixedTiming(advance); //This overrides the regular fixed timing, must come last @@ -936,6 +962,23 @@ int8_t correctionKnock(int8_t advance) return advance - knockRetard; } +/** Ignition DFCO taper correction. + */ +int8_t correctionDFCOignition(int8_t advance) +{ + int8_t dfcoRetard = advance; + if ( (configPage9.dfcoTaperEnable == 1) && BIT_CHECK(currentStatus.status1, BIT_STATUS1_DFCO) ) + { + if ( dfcoTaper != 0 ) + { + dfcoRetard -= map(dfcoTaper, configPage9.dfcoTaperTime, 0, 0, configPage9.dfcoTaperAdvance); + } + else { dfcoRetard -= configPage9.dfcoTaperAdvance; } //Taper ended, use full value + } + else { dfcoTaper = configPage9.dfcoTaperTime; } //Keep updating the duration until DFCO is active + return dfcoRetard; +} + /** Ignition Dwell Correction. */ uint16_t correctionsDwell(uint16_t dwell) diff --git a/speeduino/corrections.h b/speeduino/corrections.h index f87d2aab..e2f6e0c5 100644 --- a/speeduino/corrections.h +++ b/speeduino/corrections.h @@ -21,6 +21,7 @@ byte correctionBatVoltage(void); //Battery voltage correction byte correctionIATDensity(void); //Inlet temp density correction byte correctionBaro(void); //Barometric pressure correction byte correctionLaunch(void); //Launch control correction +byte correctionDFCOfuel(void); //DFCO taper correction bool correctionDFCO(void); //Decelleration fuel cutoff @@ -37,6 +38,7 @@ int8_t correctionNitrous(int8_t advance); int8_t correctionSoftLaunch(int8_t advance); int8_t correctionSoftFlatShift(int8_t advance); int8_t correctionKnock(int8_t advance); +int8_t correctionDFCOignition(int8_t advance); uint16_t correctionsDwell(uint16_t dwell); @@ -49,8 +51,9 @@ extern byte lastKnockCount; extern int16_t knockWindowMin; //The current minimum crank angle for a knock pulse to be valid extern int16_t knockWindowMax;//The current maximum crank angle for a knock pulse to be valid extern uint8_t aseTaper; -extern uint8_t dfcoTaper; +extern uint8_t dfcoDelay; extern uint8_t idleAdvTaper; extern uint8_t crankingEnrichTaper; +extern uint8_t dfcoTaper; #endif // CORRECTIONS_H diff --git a/speeduino/globals.h b/speeduino/globals.h index db61972e..d9711486 100644 --- a/speeduino/globals.h +++ b/speeduino/globals.h @@ -1098,8 +1098,8 @@ struct config9 { byte unused10_110; byte unused10_111; - byte unused10_112; - byte unused10_113; + byte egoMAPMax; //needs to be multiplied by 2 to get the proper value + byte egoMAPMin; //needs to be multiplied by 2 to get the proper value byte speeduino_tsCanId:4; //speeduino TS canid (0-14) uint16_t true_address; //speeduino 11bit can address uint16_t realtime_base_address; //speeduino 11 bit realtime base address @@ -1128,12 +1128,16 @@ struct config9 { byte hardRevMode : 2; byte coolantProtRPM[6]; byte coolantProtTemp[6]; + byte unused10_179; - byte unused10_180; - byte unused10_181; - byte unused10_182; - byte unused10_183; + byte dfcoTaperTime; + byte dfcoTaperFuel; + byte dfcoTaperAdvance; + byte dfcoTaperEnable : 1; + byte unused10_183 : 6; + byte unused10_184; + byte afrProtectEnabled : 2; /* < AFR protection enabled status. 0 = disabled, 1 = fixed mode, 2 = table mode */ byte afrProtectMinMAP; /* < Minimum MAP. Stored value is divided by 2. Increments of 2 kPa, maximum 511 (?) kPa */ byte afrProtectMinRPM; /* < Minimum RPM. Stored value is divded by 100. Increments of 100 RPM, maximum 25500 RPM */ diff --git a/speeduino/updates.cpp b/speeduino/updates.cpp index 0710903e..16f04776 100644 --- a/speeduino/updates.cpp +++ b/speeduino/updates.cpp @@ -732,6 +732,16 @@ void doUpdates(void) configPage13.hwTestInjDuration = 8; configPage13.hwTestIgnDuration = 4; + //DFCO taper default values (Feature disabled by default) + configPage9.dfcoTaperEnable = 0; //Disable + configPage9.dfcoTaperTime = 10; //1 second + configPage9.dfcoTaperFuel = 100; //Don't scale fuel + configPage9.dfcoTaperAdvance = 20; //Reduce 20deg until full fuel cut + + //EGO MAP Limits + configPage9.egoMAPMax = 255, // 255 will be 510 kpa + configPage9.egoMAPMin = 0, // 0 will be 0 kpa + writeAllConfig(); storeEEPROMVersion(23); } diff --git a/test/test_fuel/test_corrections.cpp b/test/test_fuel/test_corrections.cpp index b8810bf2..1205c973 100644 --- a/test/test_fuel/test_corrections.cpp +++ b/test/test_fuel/test_corrections.cpp @@ -137,9 +137,9 @@ void setup_DFCO_on() configPage2.dfcoMinCLT = 40; //Actually 0 with offset configPage2.dfcoDelay = 10; - dfcoTaper = 1; + dfcoDelay = 1; correctionDFCO(); - dfcoTaper = 20; + dfcoDelay = 20; } //********************************************************************************************************************** void test_corrections_dfco_on(void) @@ -178,6 +178,65 @@ void test_corrections_dfco_off_delay() TEST_ASSERT_FALSE(correctionDFCO()); //Make sure DFCO does not come on } +void setup_DFCO_taper_on() +{ + //Test that DFCO comes will not activate if there has not been a long enough delay + //The steup function below simulates a 2 second delay + setup_DFCO_on(); + + configPage9.dfcoTaperEnable = 1; //Enable + configPage9.dfcoTaperTime = 20; //2.0 second + configPage9.dfcoTaperFuel = 0; //Scale fuel to 0% + configPage9.dfcoTaperAdvance = 20; //Reduce 20deg until full fuel cut + + BIT_CLEAR(currentStatus.status1, BIT_STATUS1_DFCO); + //Set the threshold to be 2.5 seconds, above the simulated delay of 2s + configPage2.dfcoDelay = 250; +} +void test_corrections_dfco_taper() +{ + setup_DFCO_taper_on(); + + TEST_ASSERT_FALSE(correctionDFCO()); //Make sure DFCO does not come on + correctionDFCOfuel(); + TEST_ASSERT_EQUAL(20, dfcoTaper); //Check if value was reset to setting +} +void test_corrections_dfco_taper_fuel() +{ + setup_DFCO_taper_on(); + + correctionDFCOfuel(); + TEST_ASSERT_EQUAL(20, dfcoTaper); //Check if value was reset to setting + + BIT_SET(currentStatus.status1, BIT_STATUS1_DFCO); + dfcoTaper = 10; + TEST_ASSERT_EQUAL(50, correctionDFCOfuel()); + dfcoTaper = 5; + TEST_ASSERT_EQUAL(25, correctionDFCOfuel()); + + configPage9.dfcoTaperTime = 10; //1.0 second + dfcoTaper = 15; //Check for overflow + TEST_ASSERT_EQUAL(100, correctionDFCOfuel()); + configPage9.dfcoTaperEnable = 0; //Disable + TEST_ASSERT_EQUAL(0, correctionDFCOfuel()); +} +void test_corrections_dfco_taper_ign() +{ + setup_DFCO_taper_on(); + + dfcoTaper = 20; + BIT_SET(currentStatus.status1, BIT_STATUS1_DFCO); + + TEST_ASSERT_EQUAL(20, correctionDFCOignition(20)); + dfcoTaper = 15; + TEST_ASSERT_EQUAL(15, correctionDFCOignition(20)); + dfcoTaper = 10; + TEST_ASSERT_EQUAL(10, correctionDFCOignition(20)); + dfcoTaper = 5; + TEST_ASSERT_EQUAL(5, correctionDFCOignition(20)); + configPage9.dfcoTaperEnable = 0; //Disable + TEST_ASSERT_EQUAL(20, correctionDFCOignition(20)); +} void test_corrections_dfco() { @@ -185,6 +244,9 @@ void test_corrections_dfco() RUN_TEST(test_corrections_dfco_off_RPM); RUN_TEST(test_corrections_dfco_off_TPS); RUN_TEST(test_corrections_dfco_off_delay); + RUN_TEST(test_corrections_dfco_taper); + RUN_TEST(test_corrections_dfco_taper_fuel); + RUN_TEST(test_corrections_dfco_taper_ign); } //********************************************************************************************************************** //Setup a basic TAE enrichment curve, threshold etc that are common to all tests. Specifica values maybe updated in each individual test