Air Fuel Ratio Calculation
This article presents my reverse engineering of the MEMS3 AFR calculation. All of this was reverse engineered by hand directly from the binary code. All variable and function names are my own, and have been assigned as I have been able to understand each section of the code. As a reverse engineering project, this represents my best understanding of the code at the point it was written and may contain inaccuracies, although I think all of the major points are correct.
The code is presented in "Pseudo C" for readability. Syntax is mostly C. Strict attention to data types and variable declarations are omitted. Some functions such as MIN() and MAX() which are not defined in C but are broadly understood are used to simplify the code listings where appropriate. In all cases, the original binary code (or an accurately disassembled copy of it) are the final reference for the logic. In order to facilitate code lookups, hexadecimal addresses are shown (in VVC firmware ksr3p004) for each function (6-digit addresses in EEPROM), variable (4-digit addresses in RAM, followed by a period and bit number for bit flags), map table (4-digit offsets relative to the map pointer $13C000) or map scalar (3-digit offsets relative to the map pointer $13C000).
Top Level & Fuelling Calculation State Machine
The air fuel ratio calculation is embedded into the larger fuelling calculation. Specifically, the state machine funFuellingCalculationStateMachine makes decisions on which fuelling calculation runs are appropriate given the current engine conditions (principally STOPPED, IDLE or DRIVE). In each case, the part of the state machine relevant to the air fuel ratio calculation is shown below. Each part begins with a call to either funCalculateAirFuelRatioDrive or funCalculateAirFuelRatioIdle and ends with a call to the common routine funCalculateFuellingIdleOrDrive. This in turn calls routine funFinaliseAirFuelRatioIdleOrDrive which writes the final calculated AFR value to variable wordAirFuelRatio.
/* FUELLING CALCULATION STATE MACHINE */
funFuellingCalculationStateMachine[0x13105a] {
...
/* WHEN STOPPED */
funCalculateAirFuelRatioDrive[0x131200]();
funLookupInjectorFlowRate[0x13159a]();
funCalculateFuellingIdleOrDrive[0x131260]();
...
/ * WHEN IDLE */
funCalculateAirFuelRatioIdle[0x1311f2]();
funLookupInjectorFlowRate[0x13159a]();
funCalculateFuellingIdleOrDrive[0x131260]();
...
/* WHEN DRIVE */
funCalculateAirFuelRatioDrive[0x131200]();
funLookupInjectorFlowRate[0x13159a]();
funCalculateFuellingIdleOrDrive[0x131260]();
...
}
Calculate Air Fuel Ratio Idle
Under idle conditions, most of the calculations below are skipped. Instead, a fixed AFR given by a scalar in the map is used and copied directly into workAirFuelRatioAdjustedForOverride. This scalar is normally equal to nominal stoichiometric AFR.
/* CALCULATE AIR FUEL RATIO IDLE */
void funCalculateAirFuelRatioIdle[0x1311f2](void) {
funLookupAirFuelRatioIdle[0x1311f8]();
}
/* LOOKUP AIR FUEL RATIO IDLE */
void funLookupAirFuelRatioIdle[0x1311f8](void) {
wordAirFuelRatioAdjustedForOverride[0x0dca] = scaWordAirFuelRatioIdle[0x6c8];
}
Calculate Air Fuel Ratio Drive
Under drive (not idle) conditions the full calculation is performed. This routine forms the main calculation run for the AFR for drive (not idle). Calls a series of the routines below in succession.
/* CALCULATE AIR FUEL RATIO DRIVE */
void funCalculateAirFuelRatioDrive[0x131200](void) {
funLookupAirFuelRatioBaseMap[0x13131a]();
funFuellingInletAirTempStateMachine[0x131142]();
funCalculateAirFuelRatioAdjustedForInletAirTemp[0x131214]();
funCalculateAirFuelRatioAdjustedForOveride[0x130b8c]();
}
Calculate Fuelling Idle or Drive
This routine contains the remainder of the common air fuel ratio calculation run. It is called in all cases after either funCalculateAirFuelRatioDrive or funCalculateAirFuelRatioIdle and funLookupInjectorFlowRate. It in turn calls funFinaliseAirFuelRatioIdleOrDrive which makes the final assignment of the calculated AFR to the variable used by the rest of the code.
/* CALCULATE FUELLING IDLE OR DRIVE */
void funCalculateFuellingIdleOrDrive[0x131260](void) {
funCalculateMassAirIntake[0x131332]();
funCalculateOverheatEnrichmentAfrAdjustment[0x131420]();
funFinaliseAirFuelRatioIdleOrDrive[0x131260]();
funCalculateFuelMassInjectedForAirFuelRatio[0x13145a]();
funCalculateLowSpeedColdEnrichment[0x131286]();
funLookupWarmUpEnrichment[0x1312ea]();
funLookupFuellingBaseScaleFactor[0x131302]();
funCalculateBaseInjectorPulseWidth[0x13147e]();
funApplyFuellingMultipliers[0x1314ba]();
}
Finalise Air Fuel Ratio Idle or Drive
This routine applies a possible final enrichment to the AFR, which appears to be a sort of emergency measure to bring the temperature down under severe overheating conditions. This mechanism is usually mapped out and under all normal circumstances has no impact on the AFR calculation anyway.
The calculation also looks rather strange, in that it is normally base on the ambient air temperature, but if this sensor fails or gives implausible readings, it falls back onto the engine oil temperature. The ambient air temperature and oil temperature will normally be very different, covering completely different ranges. The oil temperature does not feel like a suitable substitute for ambient air temperature under any circumstances.
My best explanation for this is: I've identified that ambient air temperature and air conditioning evaporator temperature have scalar flags in the maps that let you specify they come from the instrument cluster over CAN instead of from the analog inputs. Those flags are set in Rover 75 / MG ZT and Land Rover Freelander maps but no others. Only the Rover 75 actually uses ambient air temperature data in its air conditioning logic, other cars don't have the sensor and don't use it in the algorithm. So I suspect that on any cars that the overheat protection was targeted at, the usual ambient air temperature input would have been wired to something else. Unfortunately, it looks as though that feature that was never used in production, it's disabled or not fully mapped in every map that I have seen. So I have no idea what it would have been reading. I've gone through all the wiring diagrams in RAVE to see if anything has anything else on that pin, but found nothing relevant. All of the maps I've looked at configure the analog channels in the same way, so I doubt the same data was pointed a different pin (even though that's dead easy to configure in the scalars).
The way in which this enrichment is calculated in a completely different area of the code and then bolted onto the end of the AFR calculation after all other considerations feels to me as though it was a bit of an afterthought, maybe targeted at motorsport (high temperatures expected, possible additional sensors fitted, no air conditioning) or factory development cars (possible non-standard sensors, likely to be operated under extreme conditions).
For these reasons the calculation of the overheat enrichment is nor presented here.
The routine then finally writes the final calculated AFR to variable wordAirFuelRatio which is used by the rest of the code in the ECU.
/* FINALISE AIR FUEL RATIO IDLE OR DRIVE */
void funFinaliseAirFuelRatioIdleOrDrive[0x131432](void) {
/* USE FIXED AIR FUEL RATIO WHEN REV LIMITER ACTIVE
if (bitRevLimiterActive[0x1316.7]) {
ratio = scaWordAirFuelRatioRevLimiterActive[0x6ce];
}
/* USE CALCULATED AIR FUEL RATIO OTHERWISE
else {
ratio = wordAirFuelRatioAdjustedForOverride[0x0dca];
}
/* ADD OVERHEAT ENRICHMENT AFR ADJUSTMENT */
ratio = ratio + wordOverheatEnrichmentAfrAdjustment[0x0dd6];
/* ENSURE FINAL RATIO CAN NEVER BE ZERO OR NEGATIVE */
// Avoids any possibility of division by zero exceptions downstream.
if (ratio < 1) {
ratio = 1;
}
/* SET FINAL AIR FUEL RATIO */
wordAirFuelRatio[0x0dcc] = ratio ; // This is the final assignment of the calculated AFR to the variable used by the rest of the code.
}
Lookup Air Fuel Ratio Base Map
/* LOOKUP AIR FUEL RATIO BASE MAP */
void funLookupAirFuelRatioBaseMap[0x13131a](void) {
wordAirFuelRatioBaseMap[0x0dce] =
funTableLookup[0x115ccc](Table151[0x3c1a], wordMap2[0x0e76], wordEngineSpeed[0x0540]);
}
Inlet Air Temperature State Machine
Classifies the inlet air temperature as "normal", "abnormally hot" or "abnormally cold", with hysteresis, for the purposes of the air fuel ratio calculation. The "abnormally cold" side is often unused, and is then mapped with transition temperatures of -40°C.
/* INLET AIR TEMPERATURE STATE MACHINE */
// This state machine is responsible for determining whether the inlet air temperature is within the normal range or abnormally hot or
// cold. Although it updates the state variable and sets flag bits accordingly, its main function is to call either
// funFuellingInletAirTempNormal or funFuellingInletAirTempAbnormal on each pass.
void funFuellingInletAirTempStateMachine[0x131142](void) {
switch(byteFuellingInletAirTempState[0x11a7]) {
/* STATE INITIAL */
case FUELLING_INLET_AIR_TEMP_STATE_INITIAL[0]:
/* CLEAR INLET AIR TEMPERATURE COLD AND HOT FLAG BITS */
bitFuellingInletAirTempCold[0x1318.5] = 0;
bitFuellingInletAirTempHot[0x1326.5] = 0;
/* TRANSITION TO FUELLING_INLET_AIR_TEMP_STATE_NORMAL */
byteFuellingInletAirTempState[0x11a7] = FUELLING_INLET_AIR_TEMP_STATE_NORMAL[1];
break;
/* STATE NORMAL */
case FUELLING_INLET_AIR_TEMP_STATE_NORMAL[1]:
/* IF INLET AIR TEMPERATURE IS BELOW TRANSITION TEMPERATURE TO LOW */
if (wordInletAirTempDirectOrEst[0x09fe] < scaWordFuellingInletAirTempNormalToCold[0x6d4]) {
/* SET INLET AIR TEMPERATURE COLD FLAG BIT */
bitFuellingInletAirTempCold[0x1318.5] = 1;
/* TRANSITION TO FUELLING_INLET_AIR_TEMP_STATE_COLD */
byteFuellingInletAirTempState[0x11a7] = FUELLING_INLET_AIR_TEMP_STATE_COLD[3];
/* INLET AIR TEMPERATURE IS ABNORMAL */
funFuellingInletAirTempAbnormal[0x131242]();
return;
/* IF INLET AIR TEMPERATURE IS ABOVE TRANSITION TEMPERATURE TO HIGH */
} else if (wordInletAirTempDirectOrEst[0x09fe] > scaWordFuellingInletAirTempNormalToHot[0x6da]) {
/* SET INLET AIR TEMPERATURE HOT FLAG BIT */
bitFuellingInletAirTempHot[0x1326.5] = 1;
/* TRANSITION TO FUELLING_INLET_AIR_TEMP_STATE_HOT */
byteFuellingInletAirTempState[0x11a7] = FUELLING_INLET_AIR_TEMP_STATE_HOT[2];
/* INLET AIR TEMPERATURE IS ABNORMAL */
funFuellingInletAirTempAbnormal[0x131242]();
return;
}
break;
/* STATE HOT */
case FUELLING_INLET_AIR_TEMP_STATE_HOT[2]:
/* IF INLET AIR TEMPERATURE IS STILL ABOVE TRANSITION TEMPERATURE TO NORMAL */
if wordInletAirTempDirectOrEst[0x09fe] >= scaWordFuellingInletAirTemperatureHotToNormal[0x6d8]) {
/* INLET AIR TEMPERATURE IS ABNORMAL */
funFuellingInletAirTempAbnormal[0x131242]();
return;
}
/* CLEAR INLET AIR TEMPERATURE HOT FLAG BIT */
bitFuellingInletAirTempHot[0x1326.5] = 0;
/* TRANSITION TO FUELLING_INLET_AIR_TEMP_STATE_NORMAL */
byteFuellingInletAirTempState[0x11a7] = FUELLING_INLET_AIR_TEMP_STATE_NORMAL[1];
break;
/* STATE COLD */
case FUELLING_INLET_AIR_TEMP_STATE_COLD[3]:
/* IF INLET AIR TEMPERATURE IS STILL BELOW TRANSITION TEMPERATURE TO NORMAL */
if (wordInletAirTempDirectOrEst[0x09fe] <= scaWordFuellingInletAirTemperatureColdToNormal[0x6d6]) {
/* INLET AIR TEMPERATURE IS ABNORMAL */
funFuellingInletAirTempAbnormal[0x131242]();
return;
}
/* CLEAR INLET AIR TEMPERATURE COLD FLAG BIT */
bitFuellingInletAirTempCold[0x1318.5] = 0;
/* TRANSITION TO FUELLING_INLET_AIR_TEMP_STATE_NORMAL */
byteFuellingInletAirTempState[0x11a7] = FUELLING_INLET_AIR_TEMP_STATE_NORMAL[1];
break;
/* STATE DEFAULT */
default:
return;
}
/* INLET AIR TEMPERATURE IS NORMAL */
funFuellingInletAirTempNormal[0x13125a]();
}
Calculate AFR Adjusted for Inlet Air Temperature
The adjustment calculated above is applied to give an adjusted air fuel ratio figure.
/* CALCULATE AFR ADJUSTED FOR INLET AIR TEMPERATURE */
// This routine adjusts the target air fuel ratio to take account of abnormal inlet air temperatures. A zero adjustment is applied over the
// entire normal IAT range. Adjustments are only applied when the IAT has been flagged as grossly abnormal above. Note that this logic is built
// into the calculation of wordAirFuelRatioAdjustmentPerInletAirTempDeviation above.
void funCalculateAirFuelRatioAdjustedForInletAirTemp[0x131214](void) {
/* CALCULATE INLET AIR TEMPERATURE DEVIATION FROM NOMINAL */
deviation = wordInletAirTempDirectOrEst[0x09fe] - scaWordAirFuelRatioNominalInletAirTemp[0x6dc];
wordAirFuelRatioInletAirTempDeviationFromNominal[0x0de4] = deviation;
/* CALCULATE AFR ADJUSTMENT FOR INLET AIR TEMPERATURE */
adjustment = (deviation * wordAirFuelRatioAdjustmentPerInletAirTempDeviation[0x0de6]) / 500;
wordAirFuelRatioAdjustmentForInletAirTemp[0x0de8] = adjustment;
/* APPLY ADJUSTMENT TO AFR */
wordAirFuelRatioAdjustedForInletAirTemp[0x0dd0] =
min(wordAirFuelRatioBaseMap[0x0dce] - adjustment, scaWordAirFuelRatioNominalStoichiometric[0x2d8]);
}
Calculate Air Fuel Ratio Adjusted For Override
This routine forms part of the main calculation run for calculating the AFR for drive (not idle). Calls a series of the routines below in succession.
/* CALCULATE AIR FUEL RATIO ADJUSTED FOR OVERRIDE */
void funCalculateAirFuelRatioAdjustedForOveride[0x13b8c](void) {
funLookupAirFuelRatioUpperLimitThresholdThrottleAngle[0x130e1c]();
funLookupAirFuelRatioUpperLimit[0x130dbe]();
funCalculateAirFuelRatioApplyUpperLimit[0x130dee]();
funAirFuelRatioUpperLimitStateMachine[0x130ba2]();
funAirFuelRatioOverrideStateMachine[0x130ee2]();
}
Lookup Air Fuel Ratio Limit Threshold Throttle Angle
Looks up the threshold throttle angle used later by routine funCalculateAirFuelRatioApplyUpperLimit. This is looked up from a map table by engine speed and engine oil temperature.
/* LOOKUP AIR FUEL RATIO UPPER LIMIT THRESHOLD THROTTLE ANGLE */
void funLookupAirFuelRatioUpperLimitThresholdThrottleAngle[0x130e1c](void) {
wordAirFuelRatioUpperLimitThresholdThrottleAngle[0x0dc2] =
funTableLookup[0x115ccc](Table145[0x3c0e], wordEngineSpeed[0x0540], wordEngineOilTemp[0x0a04]);
}
Lookup Air Fuel Ratio Upper Limit
Under some (fairly complex) conditions (described below), an upper limit is applied to the AFR. This appears to be a safety limit, targeting the areas of the map prone to detonation. This routine looks up the upper limit which applied to the AFR by funAirFuelRatioUpperLimitStateMachine. This is looked up from a map table by MAP and engine speed. Where the throttle angle exceeds a threshold, a fixed MAP is assumed when performing the lookup.
/* LOOKUP AIR FUEL RATIO UPPER LIMIT */
void funLookupAirFuelRatioUpperLimit[0x130dbe](void) {
/* USE ACTUAL MAP FOR SMALLER THROTTLE ANGLES */
if (wordThrottleAngle[0x0c2c] < scaWordFuellingAfrMapAssumedAboveThrottleAngle[0x6c2]) {
wordAirFuelRatioManifoldAbsolutePressure[0x0dc6] = wordMap1[0x0a02];
/* ASSUME FIXED MAP FOR LARGER THROTTLE ANGLES */
} else {
wordAirFuelRatioManifoldAbsolutePressure[0x0dc6] = scaWordFuellingAfrMapAssumedAtHighThrottle[0x6c0];
}
/* LOOKUP UPPER LIMIT */
wordAirFuelRatioUpperLimit[0x0dc0] =
funTableLookup[0x115ccc](Table146[0x3c10], wordAirFuelRatioManifoldAbsolutePressure[0x0dc6], wordEngineSpeed[0x0540]);
}
Calculate Apply Upper Limit Flag
Under some (fairly complex) conditions (described below), an upper limit is applied to the AFR. This appears to be a safety limit, targeting the areas of the map prone to detonation. The decisions on the limit applied are based MAP, throttle angle, coolant temperature and the time spent (measured in crank rotations) under the particular conditions. However, an initial decision is made on whether any limiting is required, based on whether either the MAP or throttle angle exceed threshold values. The whole limiting mechanism only comes into play under these conditions and where the AFR calculated above exceeds the limit appropriate to the conditions.
/* CALCULATE APPLY UPPER LIMIT FLAG */
// This routine is called BEFORE funAirFuelRatioUpperLimitStateMachine so the value of bitAirFuelRatioApplyUpperLimit does not change
// during the execution of funAirFuelRatioUpperLimitStateMachine. Bit flag bitAirFuelRatioApplyUpperLimit is set to 1 where the AFR upper
// limit from tabAirFuelRatioUpperLimit is to be fully or partially applied, or cleared to 0 where no AFR limiting is to take place.
void funCalculateAirFuelRatioApplyUpperLimit[0x130dee](void) {
bitAirFuelRatioApplyUpperLimit[0x12fd.4] =
/* CHECK WHETHER UPPER LIMIT EXCEEDED */
// AFR limiting only applied where tabAirFuelRatioUpperLimit specifies a lower AFR than that already calculated in
// wordAirFuelRatioAdjustedForInletAirTemp.
((wordAirFuelRatioAdjustedForInletAirTemp[0x0dd0] > wordAirFuelRatioUpperLimit[0x0dc0]) &&
/* CHECK WHETHER MAP OR THROTTLE ANGLE EXCEEDS THRESHOLD */
// AFR limiting is only applied where EITHER the MAP exceeds the value given by scalar scaWordAirFuelRatioUpperLimitThresholdMap OR
// the throttle angle exceeds the value looked up from table tabAirFuelRatioUpperLimitThresholdThrottleAngle.
((wordMap1[0x0a02] >= scaWordAirFuelRatioUpperLimitThresholdMap[0x6b6]) ||
(wordThrottleAngle[0x0c2c] >= wordAirFuelRatioUpperLimitThresholdThrottleAngle[0x0dc2])));
}
Calculate AFR Adjustment per Degree
An adjustment to the AFR is calculated per degree of deviation from a nominal value. When the IAT is classified as "normal" the adjustment is always 0, whatever the exact temperature, otherwise it is looked up from a table based on the MAP and RPM.
/* CALCULATE AFR ADJUSTMENT PER DEGREE INLET TEMPERATURE DEVIATION WHEN IAT NORMAL */
// Utility routine for funFuellingInletAirTempStateMachine. This routine is only called when byteFuellingInletAirTempState is
// FUELLING_INLET_AIR_TEMP_STATE_NORMAL in funFuellingInletAirTempStateMachine. This ensures that a zero adjustment is applied over the entire
// normal IAT range. Adjustments are only applied when the IAT has been flagged as grossly abnormal.
void funFuellingInletAirTempNormal[0x13125a](void) {
wordAirFuelRatioAdjustmentPerInletAirTempDeviation[0x0de6] = 0;
}
/* CALCULATE AFR ADJUSTMENT PER DEGREE INLET TEMPERATURE DEVIATION WHEN IAT ABNORMAL */
// Utility routine for funFuellingInletAirTempStateMachine. This routine is only called when byteFuellingInletAirTempState is
// FUELLING_INLET_AIR_TEMP_STATE_HOT or FUELLING_INLET_AIR_TEMP_STATE_COLD in funFuellingInletAirTempStateMachine. This ensures that a zero
// adjustment is applied over the entire normal IAT range. Adjustments are only applied when the IAT has been flagged as grossly abnormal.
void funFuellingInletAirTempAbnormal[0x131242](void) {
wordAirFuelRatioAdjustmentPerInletAirTempDeviation[0x0de6] =
funTableLookup[0x115ccc](Table149[0x3c16], wordMap2[0x0e76], wordEngineSpeed[0x0540]);
}
Air Fuel Ratio Upper Limit State Machine
This state machine managed the application of the upper limit to the AFR. The conditions are rather complicated and are described by the diagram below. Different routines are used to apply the limit in State LOW_TT_MEDIUM (partial application, ramped up over crank cycles) and in State HIGH_TT_LONG (full application). The crank cycle counter seems to be to allow for the fact that the combustion chambers, valves etc. will heat up beyond the expected temperature based on the measured coolant temperature over time under high load conditions.
/* AIR FUEL RATIO UPPER LIMIT STATE MACHINE */
// NB: byteAirFuelRatioUpperLimitState is AFR_UPPER_LIMIT_STATE_INITIAL on boot and is externally reset to AFR_UPPER_LIMIT_STATE_INITIAL by
// funFuellingCalculationStateMachine when byteFuellingCalculationState is set to FUELLING_CALC_STATE_STOPPED or FUELLING_CALC_STATE_IDLE.
void funAirFuelRatioUpperLimitStateMachine[0x130ba2](void) {
switch(byteAirFuelRatioUpperLimitState[0x11a5]) {
/* STATE INITIAL */
case AFR_UPPER_LIMIT_STATE_INITIAL[0]:
/* SETUP FOR TRANSITION */
byteAfrUpperLimitStateMachineCrankFlags[0x08e3] = COUNTER_STOP[0];
wordAfrUpperLimitStateMachineCrankCounter[0x06ea] = 0;
/* TRANSITION TO AFR_UPPER_LIMIT_STATE_NO_UPPER_LIMIT */
byteAirFuelRatioUpperLimitState[0x11a5] = AFR_UPPER_LIMIT_STATE_NO_UPPER_LIMIT[1];
/* DO NOT APPLY UPPER LIMIT TO AIR FUEL RATIO */
funDoNotApplyUpperLimitToAirFuelRatio[0x130e34]();
break;
/* STATE NO UPPER LIMIT */
// In this state, no upper limit is applied to the AFR.
case AFR_UPPER_LIMIT_STATE_NO_UPPER_LIMIT[1]:
/* CHECK APPLY UPPER LIMIT FLAG */
if (bitAirFuelRatioApplyUpperLimit[0x12fd.4]) {
/* CHECK FOR HIGH COOLANT TEMPERATURE OR THROTTLE ANGLE */
if ((wordEngineCoolantTemp[0x0a38] >= scaWordAirFuelRatioThresholdCoolantTemp[0x6c4]) ||
(wordThrottleAngle[0x0c2c] >= wordAirFuelRatioUpperLimitThresholdThrottleAngle[0x0dc2])) {
/* SETUP CRANK COUNTER COUNTING UP FROM 1100 */
byteAfrUpperLimitStateMachineCrankFlags[0x08e3] = COUNTER_STOP[0]; // Stop counter.
wordAfrUpperLimitStateMachineCrankCounter[0x06ea] =
scaWordAirFuelRatioUpperLimitCrankCountBase[0x6ae] + scaWordAirFuelRatioUpperLimitCrankCountOffset[0x6b0]; // Set counter.
byteAfrUpperLimitStateMachineCrankFlags[0x08e3] = COUNTER_COUNT_UP[3]; // Start counter.
/* SETUP ORFCO RESUME CONTROL FLAG */
bit1317.0[0x1317.0] = 1;
/* TRANSITION TO AFR_UPPER_LIMIT_STATE_HIGH_TT_LONG */
byteAirFuelRatioUpperLimitState[0x11a5] = AFR_UPPER_LIMIT_STATE_HIGH_TT_LONG[4];
/* APPLY UPPER LIMIT TO AIR FUEL RATIO LONG */
funApplyUpperLimitToAirFuelRatioLong[0x130e3c]();
/* LOW COOLANT TEMPERATURE AND THROTTLE ANGLE */
} else {
/* SETUP CRANK COUNTER COUNTING DOWN FROM CURRENT VALUE */
wordAfrUpperLimitStateMachineCrankSaved[0x0dc8] = wordAfrUpperLimitStateMachineCrankCounter[0x06ea]; // Read counter.
byteAfrUpperLimitStateMachineCrankFlags[0x08e3] = COUNTER_STOP[0]; // Stop counter.
wordAfrUpperLimitStateMachineCrankCounter[0x06ea] = wordAfrUpperLimitStateMachineCrankSaved[0x0dc8]; // Set counter.
byteAfrUpperLimitStateMachineCrankFlags[0x08e3] = COUNTER_COUNT_UP[3]; // Start counter.
/* TRANSITION TO AFR_UPPER_LIMIT_STATE_LOW_TT_SHORT */
byteAirFuelRatioUpperLimitState[0x11a5] = AFR_UPPER_LIMIT_STATE_LOW_TT_SHORT[2];
/* DO NOT APPLY UPPER LIMIT TO AIR FUEL RATIO */
funDoNotApplyUpperLimitToAirFuelRatio[0x130e34]();
}
} else {
/* DO NOT APPLY UPPER LIMIT TO AIR FUEL RATIO */
funDoNotApplyUpperLimitToAirFuelRatio[0x130e34]();
}
break;
/* STATE UPPER LIMIT WITH LOW COOLANT TEMPERATURE AND THROTTLE OVER SHORT TERM */
// This state represents where bitAirFuelRatioApplyUpperLimit has been set to 1 by funCalculateAirFuelRatioApplyUpperLimit but both the
// coolant temperature and throttle angle are below specified limits and this state has persisted for a short period in terms of
// crankshaft revolutions (roughly 1000 revolutions cumulative up/down count based on the logic described in the diagram above). In this
// state, no upper limit is applied to the AFR.
case AFR_UPPER_LIMIT_STATE_LOW_TT_SHORT[2]:
/* CHECK APPLY UPPER LIMIT FLAG */
if (bitAirFuelRatioApplyUpperLimit[0x12fd.4]) {
/* CHECK FOR HIGH COOLANT TEMPERATURE OR THROTTLE ANGLE */
if ((wordEngineCoolantTemp[0x0a38] >= scaWordAirFuelRatioThresholdCoolantTemp[0x6c4]) ||
(wordThrottleAngle[0x0c2c] >= wordAirFuelRatioUpperLimitThresholdThrottleAngle[0x0dc2])) {
/* SETUP CRANK COUNTER COUNTING UP FROM 1100 */
byteAfrUpperLimitStateMachineCrankFlags[0x08e3] = COUNTER_STOP[0]; // Stop counter.
wordAfrUpperLimitStateMachineCrankCounter[0x06ea] =
scaWordAirFuelRatioUpperLimitCrankCountBase[0x6ae] + scaWordAirFuelRatioUpperLimitCrankCountOffset[0x6b0]; // Set counter.
byteAfrUpperLimitStateMachineCrankFlags[0x08e3] = COUNTER_COUNT_UP[3]; // Start counter.
/* SETUP ORFCO RESUME CONTROL FLAG */
bit1317.0[0x1317.0] = 1;
/* TRANSITION TO AFR_UPPER_LIMIT_STATE_HIGH_TT_LONG */
byteAirFuelRatioUpperLimitState[0x11a5] = AFR_UPPER_LIMIT_STATE_HIGH_TT_LONG[4];
/* APPLY UPPER LIMIT TO AIR FUEL RATIO LONG */
funApplyUpperLimitToAirFuelRatioLong[0x130e3c]();
/* CHECK FOR MEDIUM OR HIGH CRANK ROTATION COUNT */
} else if (wordAfrUpperLimitStateMachineCrankCounter[0x06ea] >= scaWordAirFuelRatioUpperLimitCrankCountBase[0x6ae]) {
/* TRANSITION TO AFR_UPPER_LIMIT_STATE_LOW_TT_MEDIUM */
byteAirFuelRatioUpperLimitState[0x11a5] = AFR_UPPER_LIMIT_STATE_LOW_TT_MEDIUM[3];
/* APPLY UPPER LIMIT TO AIR FUEL RATIO SHORT */
funApplyUpperLimitToAirFuelRatioMedium[0x130e76]();
/* LOW COOLANT TEMPERATURE THROTTLE ANGLE AND COUNT */
} else {
/* DO NOT APPLY UPPER LIMIT TO AIR FUEL RATIO */
funDoNotApplyUpperLimitToAirFuelRatio[0x130e34]();
}
/* TRANSITION BACK TO AFR_UPPER_LIMIT_STATE_NO_UPPER_LIMIT IF APPLY UPPER LIMIT FLAG NOT SET */
} else {
/* SETUP CRANK COUNTER COUNTING DOWN FROM CURRENT VALUE */
wordAfrUpperLimitStateMachineCrankSaved[0x0dc8] = wordAfrUpperLimitStateMachineCrankCounter[0x06ea]; // Read counter.
byteAfrUpperLimitStateMachineCrankFlags[0x08e3] = COUNTER_STOP[0]; // Stop counter.
wordAfrUpperLimitStateMachineCrankCounter[0x06ea] = wordAfrUpperLimitStateMachineCrankSaved[0x0dc8]; // Set counter.
byteAfrUpperLimitStateMachineCrankFlags[0x08e3] = COUNTER_COUNT_DOWN[1]; // Start counter.
/* TRANSITION TO AFR_UPPER_LIMIT_STATE_NO_UPPER_LIMIT */
byteAirFuelRatioUpperLimitState[0x11a5] = AFR_UPPER_LIMIT_STATE_NO_UPPER_LIMIT[1];
/* DO NOT APPLY UPPER LIMIT TO AIR FUEL RATIO */
funDoNotApplyUpperLimitToAirFuelRatio[0x130e34]();
}
break;
/* STATE UPPER LIMIT WITH LOW COOLANT TEMPERATURE AND THROTTLE OVER MEDIUM TERM */
// This state represents where bitAirFuelRatioApplyUpperLimit has been set to 1 by funCalculateAirFuelRatioApplyUpperLimit but both the
// coolant temperature and throttle angle are below specified limits and this state has persisted for a medium period in terms of
// crankshaft revolutions (roughly between 1000 and 1100 revolutions cumulative up/down count based on the logic described in the
// diagram above). In this state, funApplyUpperLimitToAirFuelRatioMedium is used to apply the upper limit to the AFR.
case AFR_UPPER_LIMIT_STATE_LOW_TT_MEDIUM[3]:
/* CHECK APPLY UPPER LIMIT FLAG */
if (bitAirFuelRatioApplyUpperLimit[0x12fd.4]) {
/* CHECK FOR HIGH COOLANT TEMPERATURE OR THROTTLE ANGLE */
if ((wordEngineCoolantTemp[0x0a38] >= scaWordAirFuelRatioThresholdCoolantTemp[0x6c4]) ||
(wordThrottleAngle[0x0c2c] >= wordAirFuelRatioThresholdThrottleAngle[0x0dc2])) {
/* SETP CRANK COUNTER COUNTING UP FROM 1100 */
byteAfrUpperLimitStateMachineCrankFlags[0x08e3] = COUNTER_STOP[0]; // Stop counter.
wordAfrUpperLimitStateMachineCrankCounter[0x06ea] =
scaWordAirFuelRatioUpperLimitCrankCountBase[0x6ae] + scaWordAirFuelRatioUpperLimitCrankCountOffset[0x6b0]; // Set counter.
byteAfrUpperLimitStateMachineCrankFlags[0x08e3] = COUNTER_COUNT_UP[3]; // Start counter.
/* SETUP ORFCO RESUME CONTROL FLAG */
bit1317.0[0x1317.0] = 1;
/* TRANSITION TO AFR_UPPER_LIMIT_STATE_HIGH_TT_LONG */
byteAirFuelRatioUpperLimitState[0x11a5] = AFR_UPPER_LIMIT_STATE_HIGH_TT_LONG[4];
/* APPLY UPPER LIMIT TO AIR FUEL RATIO LONG */
funApplyUpperLimitToAirFuelRatioLong[0x130e3c]();
/* CHECK FOR HIGH CRANK ROTATION COUNT */
} else if (wordAfrUpperLimitStateMachineCrankCounter[0x06ea] >=
scaWordAirFuelRatioUpperLimitCrankCountBase[0x6ae] + scaWordAirFuelRatioUpperLimitCrankCountOffset[0x6b0]) {
/* SETUP ORFCO RESUME CONTROL FLAG */
bit1317.0[0x1317.0] = 1;
/* TRANSITION TO AFR_UPPER_LIMIT_STATE_HIGH_TT_LONG */
byteAirFuelRatioUpperLimitState[0x11a5] = AFR_UPPER_LIMIT_STATE_HIGH_TT_LONG[4];
/* APPLY UPPER LIMIT TO AIR FUEL RATIO LONG */
funApplyUpperLimitToAirFuelRatioLong[0x130e3c]();
/* LOW COOLANT TEMPERATURE THROTTLE ANGLE AND COUNT */
} else {
/* APPLY UPPER LIMIT TO AIR FUEL RATIO SHORT */
funApplyUpperLimitToAirFuelRatioMedium[0x130e76]();
}
/* TRANSITION BACK TO AFR_UPPER_LIMIT_STATE_NO_UPPER_LIMIT IF APPLY UPPER LIMIT FLAG NOT SET */
} else {
/* SETUP CRANK COUNTER COUNTING DOWN FROM CURRENT VALUE */
wordAfrUpperLimitStateMachineCrankSaved[0x0dc8] = wordAfrUpperLimitStateMachineCrankCounter[0x06ea]; // Read counter.
byteAfrUpperLimitStateMachineCrankFlags[0x08e3] = COUNTER_STOP[0]; // Stop counter.
wordAfrUpperLimitStateMachineCrankCounter[0x06ea] = wordAfrUpperLimitStateMachineCrankSaved[0x0dc8]; // Set counter.
byteAfrUpperLimitStateMachineCrankFlags[0x08e3] = COUNTER_COUNT_DOWN[1]; // Start counter.
/* TRANSITION TO AFR_UPPER_LIMIT_STATE_NO_UPPER_LIMIT */
byteAirFuelRatioUpperLimitState[0x11a5] = AFR_UPPER_LIMIT_STATE_NO_UPPER_LIMIT[1];
/* DO NOT APPLY UPPER LIMIT TO AIR FUEL RATIO */
funDoNotApplyUpperLimitToAirFuelRatio[0x130e34]();
}
break;
/* STATE UPPER LIMIT WITH HIGH COOLANT TEMPERATURE OR THROTTLE OR OVER LONG TERM */
// This state represents where bitAirFuelRatioApplyUpperLimit has been set to 1 by funCalculateAirFuelRatioApplyUpperLimit and either the
// coolant temperature or throttle angle are above specified limits or the bitAirFuelRatioApplyUpperLimit state has persisted for a long
// period in terms of crankshaft revolutions (roughly 1100 revolutions cumulative up/down count based on the logic described in the diagram
// above). In this state, funApplyUpperLimitToAirFuelRatioLong is used to apply the upper limit to the AFR.
case AFR_UPPER_LIMIT_STATE_HIGH_TT_LONG[4]:
/* CHECK APPLY UPPER LIMIT FLAG */
if (bitAirFuelRatioApplyUpperLimit[0x12fd.4]) {
/* APPLY UPPER LIMIT TO AIR FUEL RATIO LONG */
funApplyUpperLimitToAirFuelRatioLong[0x130e3c]();
/* TRANSITION BACK TO AFR_UPPER_LIMIT_STATE_NO_UPPER_LIMIT IF APPLY UPPER LIMIT FLAG NOT SET */
} else {
/* CALCULATE AFR 0DCB8 FROM 06EA */
funAirFuelRatioCalculateDC8From6EA[0x130e98]();
/* SETUP CRANK COUNTER COUNTING DOWN FROM CALCULATED VALUE */
byteAfrUpperLimitStateMachineCrankFlags[0x08e3] = COUNTER_STOP[0]; // Stop counter.
wordAfrUpperLimitStateMachineCrankCounter[0x06ea] = wordAfrUpperLimitStateMachineCrankSaved[0x0dc8]; // Set counter.
byteAfrUpperLimitStateMachineCrankFlags[0x08e3] = COUNTER_COUNT_DOWN[1]; // Start counter.
/* CLEAR ORFCO RESUME CONTROL FLAG */
bit1317.0[0x1317.0] = 0;
/* DO NOT APPLY UPPER LIMIT TO AIR FUEL RATIO */
funDoNotApplyUpperLimitToAirFuelRatio[0x130e34]();
/* TRANSITION TO AFR_UPPER_LIMIT_STATE_NO_UPPER_LIMIT */
byteAirFuelRatioUpperLimitState[0x11a5] = AFR_UPPER_LIMIT_STATE_NO_UPPER_LIMIT[1];
}
break;
/* STATE DEFAULT */
default:
break;
}
}
Apply Upper Limit to Air Fuel Ratio
These routines are called by the state machine above to apply the upper limit to the AFR in a manner appropriate to the conditions.
/* APPLY UPPER LIMIT TO AIR FUEL RATIO STATE LOW COOLANT TEMPERATURE AND THROTTLE OVER MEDIUM TERM */
// Utility routine for funFuellingInletAirTempStateMachine. This routine partially applies the upper limit, ramped up over crank cycles.
void funApplyUpperLimitToAirFuelRatioMedium[0x130e76](void) {
wordAirFuelRatioAdjustedForUpperLimit[0x0dc4] =
wordAirFuelRatioAdjustedForInletAirTemp[0x0dd0] -
(wordAfrUpperLimitStateMachineCrankCounter[0x06ea] - scaWordAirFuelRatioUpperLimitCrankCountBase[0x6ae]) *
(wordAirFuelRatioAdjustedForInletAirTemp[0x0dd0] -
wordAirFuelRatioUpperLimit[0x0dc0]) / scaWordAirFuelRatioUpperLimitCrankCountOffset[0x6b0];
}
/* APPLY UPPER LIMIT TO AIR FUEL RATIO STATE HIGH COOLANT TEMPERATURE OR THROTTLE OR OVER LONG TERM */
// Utility routine for funFuellingInletAirTempStateMachine. This routine fully applies the upper limit to the air fuel ratio.
void funApplyUpperLimitToAirFuelRatioLong[0x130e3c](void) {
/* CHECK FOR LOW COOLANT TEMPERATURE */
if (wordEngineCoolantTemp[0x0a38] <=
scaWordAirFuelRatioThresholdCoolantTemp[0x6c4] - scaWordAirFuelRatioThresholdCoolantTempHyst[0x6c6]) {
bitApplyUpperLimitToAirFuelRatioLongHighCoolantTemp[0x12fe.1] = 0;
}
/* CHECK FOR HIGH COOLANT TEMPERATURE */
if (wordEngineCoolantTemp[0x0a38] > scaWordAirFuelRatioThresholdCoolantTemp[0x6c4]) {
bitApplyUpperLimitToAirFuelRatioLongHighCoolantTemp[0x12fe.1] = 1;
}
/* APPLY UPPER LIMIT */
adjusted = wordAirFuelRatioUpperLimit[0x0dc0];
if (bitApplyUpperLimitToAirFuelRatioLongHighCoolantTemp[0x12fe.1]) {
adjusted = adjusted + scaWordAirFuelRatioUpperLimitHighCoolantTempAdjust[0x6b8];
}
wordAirFuelRatioAdjustedForUpperLimit[0x0dc4] = adjusted;
}
Calculate Effective Crank Counter on Transition
When the state machine above transition back from State HIGH_TT_LONG to State NO_UPPER_LIMIT, the crank counter is only partially reset, presumably to allow for the fact that the combustion chambers will already be hotter than expected if the load increases again. This routine calculates the new starting point for the counter.
/* CALCULATE EFFECTIVE CRANK COUNTER ON TRANSITION FROM STATE 4 TO STATE 1 */
// Utility routine for funFuellingInletAirTempStateMachine.
void funAirFuelRatioCalculateCrankSavedFromCounter[0x130e98](void) {
/* HIGH CRANK COUNT */
if (wordAfrUpperLimitStateMachineCrankCounter[0x06ea] >
scaWordAirFuelRatioUpperLimitCrankCountDenominator[0x6b4] * scaWordAirFuelRatioUpperLimitCrankCountNumerator[0x6b2]) {
wordAfrUpperLimitStateMachineCrankSaved[0x0dc8] =
scaWordAirFuelRatioUpperLimitCrankCountNumerator[0x6b4] / scaWordAirFuelRatioUpperLimitCrankCountDenominator[0x6b2] +
scaWordAirFuelRatioUpperLimitCrankCountBase[0x6ae] + scaWordAirFuelRatioUpperLimitCrankCountOffset[0x6b0];
/* LOW CRANK COUNT */
} else {
wordAfrUpperLimitStateMachineCrankSaved[0x0dc8] =
(wordAfrUpperLimitStateMachineCrankCounter[0x06ea] -
scaWordAirFuelRatioUpperLimitCrankCountBase[0x6ae] - scaWordAirFuelRatioUpperLimitCrankCountOffset[0x6b0]) /
scaWordAirFuelRatioUpperLimitCrankCountDenominator[0x6b2] + scaWordAirFuelRatioUpperLimitCrankCountBase[0x6ae] +
scaWordAirFuelRatioUpperLimitCrankCountOffset[0x6b0];
}
}
Air Fuel Ratio Override State Machine
This state machine is designed to prevent the main AFR calculation from applying enrichment beyond nominal stoichiometric AFR during the first N seconds of engine running time in a specific RPM range (typically 2600rpm to 4200rpm in ECUs where this feature is enabled). Only time in this RPM range during which the calculation above is returning an AFR less (richer) than nominal stoichiometric decrements the timer. Once the timer expires, or if the RPM exceeds the upper limit (typically 4200rpm), or if the RPM falls below the lower limit (typically 2600rpm) while the calculation above is still requesting enrichment (unexpected abnormal condition), the state machine moves to the final state AFR_OVERRIDE_STATE_FINAL where enrichment is permanently enabled (there are no transitions out of this state) until the ECU is reset. This is the normal running condition of the ECU.
This feature is typically disabled in maps on all vehicles other than Rover 75 / MG ZT and Land Rover Freelander by setting the timeout N to 0 seconds. In Rover 75 / MG ZT maps the timeout is typically 30 seconds. Other enrichment mechanisms such as after start enrichment and low coolant temperature enrichment are applied AFTER the AFR calculation, affecting the injector pulse width calculation directly rather than adjusting the target AFR. This mechanism may be designed to prevent these from compounding with main AFR calculation enrichment leading to rich cuts.
However, in Freelander maps the timeout is set to 1600 seconds, which is more than 26 minutes. The effect of this has been verified on an engine simulator; the ECU does indeed block enrichment for the first 26 minutes of running between 2600rpm and 4200rpm, unless the engine is revved beyond 4200rpm, at which point it immediately reverts to normal behaviour. This is unreasonable for a warm-up period; there is a suspicion that the RPM range and timeout correspond largely to the drive cycles used during homologation emissions tests and this feature, already present and used for other reasons on the Rover 75, may have been abused as an emissions test defeat device. Whether this was the intended effect or not, it has the effect of locking the engine to stoichiometric AFR whilst under controlled test conditions.
/* AIR FUEL RATIO OVERRIDE STATE MACHINE */
void funAirFuelRatioOverrideStateMachine[0x130ee2](void) {
switch(byteAirFuelRatioOverrideState[0x11a6]) {
/* STATE INITIAL */
// AFR_OVERRIDE_STATE_INIITIAL is initialised when the ECU boots. It selects AFR adjusted for the upper limit (normal calculation)
// and transitions immediately to AFR_OVERRIDE_STATE_STARTUP.
case AFR_OVERRIDE_STATE_INITIAL[0]:
/* TRANSITION TO AFR_OVERRIDE_STATE_STARTUP */
byteAirFuelRatioOverrideState[0x11a6] = AFR_OVERRIDE_STATE_STARTUP[1];
/* ENABLE ENRICHMENT */
funAirFuelRatioOverrideEnableEnrichment[0x131046]();
return;
/* STATE STARTUP */
case AFR_OVERRIDE_STATE_STARTUP[1]:
/* CHECK FOR CALCULATED AFR LESS THAN NOMINAL STOICHIOMETRIC */
if (wordAirFuelRatioAdjustedForUpperLimit[0x0dc4] < scaWordAirFuelRatioNominalStoichiometric[0x2d8]) {
/* CHECK FOR ENGINE SPEED IN OVERRIDE RANGE */
if ((wordEngineSpeed[0x0540] > scaWordAirFuelRatioInitialOverrideMaximumEngineSpeed[0x6bc][=2600]) &&
(wordEngineSpeed[0x0540] < scaWordAirFuelRatioInitialOverrideMinimumEngineSpeed[0x6ba][=4200])) {
/* TRANSITION TO AFR_OVERRIDE_STATE_TIMER_RUNNING */
byteAirFuelRatioOverrideState[0x11a6] = AFR_OVERRIDE_STATE_TIMER_RUNNING[2];
/* DISABLE ENRICHMENT */
funAirFuelRatioOverrideDisableEnrichment[0x131050]();
/* INITIALISE COUNTDOWN TIMER */
// A countdown timer may be started here, decremented once per second, although this is typically mapped to 0s.
wordAfrOverrideStateMachine1sCountdownFlags[0x08c2] = COUNTER_STOP[0]; // Stop counter.
wordAfrOverrideStateMachine1sCountdownCounter[0x06a8] = scaWordAirFuelRatioInitialOverrideTimeoutPeriod[0x6be]; // Set counter.
wordAfrOverrideStateMachine1sCountdownFlags[0x08c2] = COUNTER_COUNT_DOWN[1]; // Start counter.
return;
} else {
/* TRANSITION TO AFR_OVERRIDE_STATE_FINAL BY DEFAULT */
break;
}
} else {
/* ENABLE ENRICHMENT */
funAirFuelRatioOverrideEnableEnrichment[0x131046]();
return;
}
/* STATE AFR_OVERRIDE_STATE_TIMER_RUNNING */
case AFR_OVERRIDE_STATE_TIMER_RUNNING[2]:
/* CHECK FOR COUNTDOWN TIMER RUNNING AND ENGINE SPEED BELOW MAXIMUM NORMAL DRIVING RNAGE */
if ((wordAfrOverrideStateMachine1sCountdownCounter[0x06a8] > 0) && (wordEngineSpeed[0x0540] <=
scaWordAirFuelRatioInitialOverrideMinimumEngineSpeed[0x6ba][=6000])) {
/* CHECK FOR ENGINE SPEED BELWO MINIMUM NORMAL DRIVING RANGE */
if (wordEngineSpeed[0x0540] < scaWordAirFuelRatioInitialOverrideMaximumEngineSpeed[0x6bc][=2600]) {
/* ENABLE ENRICHMENT */
funAirFuelRatioOverrideEnableEnrichment[0x131046]();
/* TRANSITION TO AFR_OVERRIDE_STATE_FINAL */
byteAirFuelRatioOverrideState[0x11a6] = AFR_OVERRIDE_STATE_FINAL[3];
return;
} else if (wordAirFuelRatioAdjustedForUpperLimit[0x0dc4] == scaWordAirFuelRatioNominalStoichiometric[0x2d8]) {
/* ENABLE ENRICHMENT */
funAirFuelRatioOverrideEnableEnrichment[0x131046]();
/* STOP COUNTDOWN TIMER */
wordAfrOverrideStateMachine1sCountdownFlags[0x08c2] = wordAfrOverrideStateMachine1sCountdownFlags[0x08c2]
& ~COUNTER_COUNT_DOWN[1] // Stop counter.
| 4; // Set flag bit 2. Significance unknown. Appears to be unused.
/* TRANSITION TO AFR_OVERRIDE_STATE_TIMER_STOPPED */
byteAirFuelRatioOverrideState[0x11a6] = AFR_OVERRIDE_STATE_TIMER_STOPPED[4];
}
/* DISABLE ENRICHMENT */
funAirFuelRatioOverrideDisableEnrichment[0x131050]();
return;
}
/* TRANSITION TO AFR_OVERRIDE_STATE_FINAL BY DEFAULT */
break;
/* STATE FINAL */
case AFR_OVERRIDE_STATE_FINAL[3]:
/* ENABLE ENRICHMENT */
funAirFuelRatioOverrideEnableEnrichment[0x131046]();
return;
/* STATE TIMER_STOPPED */
case AFR_OVERRIDE_STATE_TIMER_STOPPED[4]:
/* CHECK FOR CALCULATED AFR LESS THAN NOMINAL STOICHIOMETRIC */
if (wordAirFuelRatioAdjustedForUpperLimit[0x0dc4] < scaWordAirFuelRatioNominalStoichiometric[0x2d8])) {
/* CHECK FOR ENGINE SPEED IN NORMAL DRIVING RANGE */
if ((wordEngineSpeed[0x0540] > scaWordAirFuelRatioInitialOverrideMaximumEngineSpeed[0x6bc][=2600]) &&
(wordEngineSpeed[0x0540] < scaWordAirFuelRatioInitialOverrideMinimumEngineSpeed[0x6ba][=4200]) {
/* TRANSITION TO AFR_OVERRIDE_STATE_TIMER_RUNNING */
byteAirFuelRatioOverrideState[0x11a6] = AFR_OVERRIDE_STATE_TIMER_RUNNING[2];
/* DISABLE ENRICHMENT */
funAirFuelRatioOverrideDisableEnrichment[0x131050]();
/* START COUNTDOWN TIMER */
wordAfrOverrideStateMachine1sCountdownFlags[0x08c2] = wordAfrOverrideStateMachine1sCountdownFlags[0x08c2]
& ~4 // Clear flag bit 2. Significance unknown. Appears to be unused.
| COUNTER_COUNT_DOWN[1]; // Start counter.
} else {
/* TRANSITION TO AFR_OVERRIDE_STATE_FINAL */
byteAirFuelRatioOverrideState[0x11a6] = AFR_OVERRIDE_STATE_FINAL[3];
/* ENABLE ENRICHMENT */
funAirFuelRatioOverrideEnableEnrichment[0x131046]();
}
} else {
/* ENABLE ENRICHMENT */
funAirFuelRatioOverrideEnableEnrichment[0x131046]();
}
return;
/* STATE DEFAULT */
default:
return;
}
/* TRANSITION TO AFR_OVERRIDE_STATE_FINAL BY DEFAULT */
byteAirFuelRatioOverrideState[0x11a6] = AFR_OVERRIDE_STATE_FINAL[3];
/* ENABLE ENRICHMENT */
funAirFuelRatioOverrideEnableEnrichment[0x131046]();
}
Air Fuel Ratio Override Enable / Disable Enrichment
These routines are used by the state machine above to select an appropriate AFR.
/* AIR FUEL RATIO OVERRIDE ENABLE ENRICHMENT */
// Utility routine for funAirFuelRatioOverrideStateMachine. When enrichment is enabled, variable wordAirFuelRatioAdjustedForUpperLimit calculated
// above is copied directly into wordAirFuelRatioAdjustedForOverride.
void funAirFuelRatioOverrideEnableEnrichment[0x131046](void) {
wordAirFuelRatioAdjustedForOverride[0x0dca] = wordAirFuelRatioAdjustedForUpperLimit[0x0dc4];
}
/* AIR FUEL RATIO OVERRIDE DISABLE ENRICHMENT */
// Utility routine for funAirFuelRatioOverrideStateMachine. When enrichment is disabled, scalar scaWordAirFuelRatioNominalStoichiometric is
// copied into wordAirFuelRatioAdjustedForOverride instead.
void funAirFuelRatioOverrideDisableEnrichment[0x131050](void) {
wordAirFuelRatioAdjustedForOverride[0x0dca] = scaWordAirFuelRatioNominalStoichiometric[0x2d8];
}