2015-05-28 16:49:44 -07:00
/*
Speeduino - Simple engine management for the Arduino Mega 2560 platform
Copyright ( C ) Josh Stewart
A full copy of the license may be found in the projects root directory
*/
2015-01-27 15:01:12 -08:00
/*
The corrections functions in this file affect the fuel pulsewidth ( Either increasing or decreasing )
based on factors other than the VE lookup .
These factors include temperature ( Warmup Enrichment and After Start Enrichment ) , Acceleration / Decelleration ,
Flood clear mode etc .
*/
//************************************************************************************************************
2014-01-07 00:02:00 -08:00
2015-02-14 09:04:00 -08:00
# include "corrections.h"
# include "globals.h"
2014-01-07 00:02:00 -08:00
2016-04-08 06:52:32 -07:00
long PID_O2 , PID_output , PID_AFRTarget ;
PID egoPID ( & PID_O2 , & PID_output , & PID_AFRTarget , configPage3 . egoKP , configPage3 . egoKI , configPage3 . egoKD , REVERSE ) ; //This is the PID object if that algorithm is used. Needs to be global as it maintains state outside of each function call
void initialiseCorrections ( )
{
egoPID . SetMode ( AUTOMATIC ) ; //Turn O2 PID on
}
2015-01-27 15:01:12 -08:00
/*
correctionsTotal ( ) calls all the other corrections functions and combines their results .
This is the only function that should be called from anywhere outside the file
*/
2014-01-07 00:02:00 -08:00
byte correctionsTotal ( )
{
2016-06-16 21:13:10 -07:00
unsigned long sumCorrections = 100 ;
byte activeCorrections = 0 ;
2015-01-24 23:03:52 -08:00
byte result ; //temporary variable to store the result of each corrections function
2016-06-16 21:13:10 -07:00
//The values returned by each of the correction functions are multipled together and then divided back to give a single 0-255 value.
2015-02-05 13:11:33 -08:00
currentStatus . wueCorrection = correctionWUE ( ) ;
2016-06-16 21:13:10 -07:00
if ( currentStatus . wueCorrection ! = 100 ) { sumCorrections = ( sumCorrections * currentStatus . wueCorrection ) ; activeCorrections + + ; }
2015-01-24 23:03:52 -08:00
result = correctionASE ( ) ;
2016-06-16 21:13:10 -07:00
if ( result ! = 100 ) { sumCorrections = ( sumCorrections * result ) ; activeCorrections + + ; }
2015-02-06 13:29:51 -08:00
result = correctionCranking ( ) ;
2016-06-16 21:13:10 -07:00
if ( result ! = 100 ) { sumCorrections = ( sumCorrections * result ) ; activeCorrections + + ; }
if ( activeCorrections = = 3 ) { sumCorrections = sumCorrections / powint ( 100 , activeCorrections ) ; activeCorrections = 0 ; } // Need to check this to ensure that sumCorrections doesn't overflow. Can occur when the number of corrections is greater than 3 (Which is 100^4) as 100^5 can overflow
2015-02-05 13:11:33 -08:00
currentStatus . TAEamount = correctionAccel ( ) ;
2016-06-16 21:13:10 -07:00
if ( currentStatus . TAEamount ! = 100 ) { sumCorrections = ( sumCorrections * currentStatus . TAEamount ) ; activeCorrections + + ; }
if ( activeCorrections = = 3 ) { sumCorrections = sumCorrections / powint ( 100 , activeCorrections ) ; activeCorrections = 0 ; }
2015-01-24 23:03:52 -08:00
result = correctionFloodClear ( ) ;
2016-06-16 21:13:10 -07:00
if ( result ! = 100 ) { sumCorrections = ( sumCorrections * result ) ; activeCorrections + + ; }
if ( activeCorrections = = 3 ) { sumCorrections = sumCorrections / powint ( 100 , activeCorrections ) ; activeCorrections = 0 ; }
2015-02-05 13:11:33 -08:00
currentStatus . egoCorrection = correctionsAFRClosedLoop ( ) ;
2016-06-16 21:13:10 -07:00
if ( currentStatus . egoCorrection ! = 100 ) { sumCorrections = ( sumCorrections * currentStatus . egoCorrection ) ; activeCorrections + + ; }
if ( activeCorrections = = 3 ) { sumCorrections = sumCorrections / powint ( 100 , activeCorrections ) ; activeCorrections = 0 ; }
2015-04-04 03:10:13 -07:00
currentStatus . batCorrection = correctionsBatVoltage ( ) ;
2016-06-16 21:13:10 -07:00
if ( currentStatus . batCorrection ! = 100 ) { sumCorrections = ( sumCorrections * currentStatus . batCorrection ) ; activeCorrections + + ; }
if ( activeCorrections = = 3 ) { sumCorrections = sumCorrections / powint ( 100 , activeCorrections ) ; activeCorrections = 0 ; }
2015-10-17 15:29:41 -07:00
currentStatus . iatCorrection = correctionsIATDensity ( ) ;
2016-06-16 21:13:10 -07:00
if ( currentStatus . iatCorrection ! = 100 ) { sumCorrections = ( sumCorrections * currentStatus . iatCorrection ) ; activeCorrections + + ; }
if ( activeCorrections = = 3 ) { sumCorrections = sumCorrections / powint ( 100 , activeCorrections ) ; activeCorrections = 0 ; }
2015-10-18 05:20:16 -07:00
currentStatus . launchCorrection = correctionsLaunch ( ) ;
2016-06-16 21:13:10 -07:00
if ( currentStatus . launchCorrection ! = 100 ) { sumCorrections = ( sumCorrections * currentStatus . launchCorrection ) ; activeCorrections + + ; }
2016-02-29 16:53:08 -08:00
bitWrite ( currentStatus . squirt , BIT_SQUIRT_DFCO , correctionsDFCO ( ) ) ;
if ( bitRead ( currentStatus . squirt , BIT_SQUIRT_DFCO ) ) { sumCorrections = 0 ; }
2016-06-16 21:13:10 -07:00
sumCorrections = sumCorrections / powint ( 100 , activeCorrections ) ;
2015-01-24 23:03:52 -08:00
2015-02-05 18:11:39 -08:00
if ( sumCorrections > 255 ) { sumCorrections = 255 ; } //This is the maximum allowable increase
2014-09-20 15:46:04 -07:00
return ( byte ) sumCorrections ;
2014-01-07 00:02:00 -08:00
}
2015-01-27 15:01:12 -08:00
/*
Warm Up Enrichment ( WUE )
Uses a 2 D enrichment table ( WUETable ) where the X axis is engine temp and the Y axis is the amount of extra fuel to add
*/
2014-01-07 00:02:00 -08:00
byte correctionWUE ( )
{
2014-05-08 06:01:36 -07:00
//Possibly reduce the frequency this runs at (Costs about 50 loops per second)
2015-02-24 18:12:28 -08:00
if ( currentStatus . coolant > ( WUETable . axisX [ 9 ] - CALIBRATION_TEMPERATURE_OFFSET ) ) { BIT_CLEAR ( currentStatus . engine , BIT_ENGINE_WARMUP ) ; return 100 ; } //This prevents us doing the 2D lookup if we're already up to temp
BIT_SET ( currentStatus . engine , BIT_ENGINE_WARMUP ) ;
2015-06-05 01:02:47 -07:00
return table2D_getValue ( & WUETable , currentStatus . coolant + CALIBRATION_TEMPERATURE_OFFSET ) ;
2014-01-07 00:02:00 -08:00
}
2015-02-06 13:29:51 -08:00
/*
Cranking Enrichment
Additional fuel % to be added when the engine is cranking
*/
byte correctionCranking ( )
{
if ( BIT_CHECK ( currentStatus . engine , BIT_ENGINE_CRANK ) ) { return 100 + configPage1 . crankingPct ; }
else { return 100 ; }
}
2015-01-27 15:01:12 -08:00
/*
After Start Enrichment
This is a short period ( Usually < 20 seconds ) immediately after the engine first fires ( But not when cranking )
where an additional amount of fuel is added ( Over and above the WUE amount )
*/
2014-01-07 00:02:00 -08:00
byte correctionASE ( )
{
2015-02-06 13:29:51 -08:00
//Two checks are requiredL:
//1) Is the negine run time less than the configured ase time
//2) Make sure we're not still cranking
if ( ( currentStatus . runSecs < configPage1 . aseCount ) & & ! ( BIT_CHECK ( currentStatus . engine , BIT_ENGINE_CRANK ) ) )
2014-01-29 21:28:21 -08:00
{
2014-12-23 15:25:51 -08:00
BIT_SET ( currentStatus . engine , BIT_ENGINE_ASE ) ; //Mark ASE as active.
2015-02-06 02:18:54 -08:00
return 100 + configPage1 . asePct ;
2014-01-29 21:28:21 -08:00
}
2014-05-06 04:07:49 -07:00
2014-12-23 15:25:51 -08:00
BIT_CLEAR ( currentStatus . engine , BIT_ENGINE_ASE ) ; //Mark ASE as inactive.
2014-05-06 04:07:49 -07:00
return 100 ;
2014-01-07 00:02:00 -08:00
}
2014-02-17 02:54:28 -08:00
/*
TPS based acceleration enrichment
Calculates the % change of the throttle over time ( % / second ) and performs a lookup based on this
2015-01-27 15:01:12 -08:00
When the enrichment is turned on , it runs at that amount for a fixed period of time ( taeTime )
2014-02-17 02:54:28 -08:00
*/
2014-01-07 00:02:00 -08:00
byte correctionAccel ( )
{
2015-01-27 15:01:12 -08:00
//First, check whether the accel. enrichment is already running
if ( BIT_CHECK ( currentStatus . engine , BIT_ENGINE_ACC ) )
{
//If it is currently running, check whether it should still be running or whether it's reached it's end time
2015-01-30 04:51:01 -08:00
if ( currentLoopTime > = currentStatus . TAEEndTime )
2015-01-27 15:01:12 -08:00
{
//Time to turn enrichment off
BIT_CLEAR ( currentStatus . engine , BIT_ENGINE_ACC ) ;
currentStatus . TAEamount = 0 ;
return 100 ;
}
//Enrichment still needs to keep running. Simply return the total TAE amount
2015-02-16 21:31:26 -08:00
return currentStatus . TAEamount ;
2015-01-27 15:01:12 -08:00
}
2015-02-16 21:31:26 -08:00
//Check for deceleration (Deceleration adjustment not yet supported)
if ( currentStatus . TPS < currentStatus . TPSlast ) { return 100 ; }
2015-01-27 15:01:12 -08:00
//If TAE isn't currently turned on, need to check whether it needs to be turned on
2015-02-16 21:31:26 -08:00
int rateOfChange = ldiv ( 1000000 , ( currentStatus . TPS_time - currentStatus . TPSlast_time ) ) . quot * ( currentStatus . TPS - currentStatus . TPSlast ) ; //This is the % per second that the TPS has moved
//currentStatus.tpsDOT = divs10(rateOfChange); //The TAE bins are divided by 10 in order to allow them to be stored in a byte.
currentStatus . tpsDOT = rateOfChange / 10 ;
2014-01-07 00:02:00 -08:00
2015-02-16 21:31:26 -08:00
if ( rateOfChange > configPage1 . tpsThresh )
2014-05-06 04:07:49 -07:00
{
2015-01-27 15:01:12 -08:00
BIT_SET ( currentStatus . engine , BIT_ENGINE_ACC ) ; //Mark accleration enrichment as active.
2015-02-16 21:31:26 -08:00
currentStatus . TAEEndTime = micros ( ) + ( ( unsigned long ) configPage1 . taeTime * 10000 ) ; //Set the time in the future where the enrichment will be turned off. taeTime is stored as mS / 10, so multiply it by 100 to get it in uS
2015-06-05 01:02:47 -07:00
return 100 + table2D_getValue ( & taeTable , currentStatus . tpsDOT ) ;
2014-05-06 04:07:49 -07:00
}
2015-01-27 15:01:12 -08:00
//If we reach here then TAE is neither on, nor does it need to be turned on.
2014-05-06 04:07:49 -07:00
return 100 ;
}
/*
Simple check to see whether we are cranking with the TPS above the flood clear threshold
This function always returns either 100 or 0
*/
byte correctionFloodClear ( )
{
if ( BIT_CHECK ( currentStatus . engine , BIT_ENGINE_CRANK ) )
{
//Engine is currently cranking, check what the TPS is
if ( currentStatus . TPS > = configPage2 . floodClear )
{
//Engine is cranking and TPS is above threshold. Cut all fuel
return 0 ;
}
}
return 100 ;
2014-01-07 00:02:00 -08:00
}
2015-02-05 13:11:33 -08:00
2015-04-04 03:10:13 -07:00
/*
Battery Voltage correction
Uses a 2 D enrichment table ( WUETable ) where the X axis is engine temp and the Y axis is the amount of extra fuel to add
*/
byte correctionsBatVoltage ( )
{
2015-04-10 00:26:38 -07:00
if ( currentStatus . battery10 > ( injectorVCorrectionTable . axisX [ 5 ] ) ) { return injectorVCorrectionTable . values [ injectorVCorrectionTable . xSize - 1 ] ; } //This prevents us doing the 2D lookup if the voltage is above maximum
2015-06-05 01:02:47 -07:00
return table2D_getValue ( & injectorVCorrectionTable , currentStatus . battery10 ) ;
2015-04-04 03:10:13 -07:00
}
2015-10-17 15:29:41 -07:00
/*
2015-10-18 05:20:16 -07:00
Simple temperature based corrections lookup based on the inlet air temperature .
This corrects for changes in air density from movement of the temperature
2015-10-17 15:29:41 -07:00
*/
byte correctionsIATDensity ( )
{
2015-10-18 05:20:16 -07:00
if ( ( currentStatus . IAT + CALIBRATION_TEMPERATURE_OFFSET ) > ( IATDensityCorrectionTable . axisX [ 8 ] ) ) { return IATDensityCorrectionTable . values [ IATDensityCorrectionTable . xSize - 1 ] ; } //This prevents us doing the 2D lookup if the intake temp is above maximum
return table2D_getValue ( & IATDensityCorrectionTable , currentStatus . IAT + CALIBRATION_TEMPERATURE_OFFSET ) ; //currentStatus.IAT is the actual temperature, values in IATDensityCorrectionTable.axisX are temp+offset
}
/*
Launch control has a setting to increase the fuel load to assist in bringing up boost
This simple check applies the extra fuel if we ' re currently launching
*/
byte correctionsLaunch ( )
{
2016-04-05 03:44:11 -07:00
if ( currentStatus . launchingHard | | currentStatus . launchingSoft ) { return ( 100 + configPage3 . lnchFuelAdd ) ; }
2015-10-18 05:20:16 -07:00
else { return 100 ; }
2015-10-17 15:29:41 -07:00
}
2016-02-24 18:11:13 -08:00
/*
* Returns true if decelleration fuel cutoff should be on , false if its off
*/
bool correctionsDFCO ( )
{
if ( ! configPage2 . dfcoEnabled ) { return false ; } //If the DFCO option isn't turned on, always return false (off)
2016-02-29 16:53:08 -08:00
if ( bitRead ( currentStatus . squirt , BIT_SQUIRT_DFCO ) ) { return ( currentStatus . RPM > ( configPage2 . dfcoRPM * 10 ) ) & & ( currentStatus . TPS < configPage2 . dfcoTPSThresh ) ; }
2016-02-28 23:26:53 -08:00
else { return ( currentStatus . RPM > ( ( configPage2 . dfcoRPM * 10 ) + configPage2 . dfcoHyster ) ) & & ( currentStatus . TPS < configPage2 . dfcoTPSThresh ) ; }
2016-02-24 18:11:13 -08:00
}
2015-02-05 13:11:33 -08:00
/*
Lookup the AFR target table and perform either a simple or PID adjustment based on this
Simple ( Best suited to narrowband sensors ) :
If the O2 sensor reports that the mixture is lean / rich compared to the desired AFR target , it will make a 1 % adjustment
It then waits < egoDelta > number of ignition events and compares O2 against the target table again . If it is still lean / rich then the adjustment is increased to 2 %
This continues until either :
a ) the O2 reading flips from lean to rich , at which point the adjustment cycle starts again at 1 % or
b ) the adjustment amount increases to < egoLimit > at which point it stays at this level until the O2 state ( rich / lean ) changes
PID ( Best suited to wideband sensors ) :
*/
2015-02-16 21:32:07 -08:00
2015-02-05 13:11:33 -08:00
byte correctionsAFRClosedLoop ( )
{
2015-02-05 18:11:39 -08:00
if ( ( configPage3 . egoAlgorithm = = 3 ) | | ( configPage3 . egoType = = 0 ) ) { return 100 ; } //An egoAlgorithm value of 3 means NO CORRECTION, egoType of 0 means no O2 sensor
2015-02-05 13:11:33 -08:00
//Check the ignition count to see whether the next step is required
if ( ( ignitionCount & ( configPage3 . egoCount - 1 ) ) = = 1 ) //This is the equivalent of ( (ignitionCount % configPage3.egoCount) == 0 ) but without the expensive modulus operation. ie It results in True every <egoCount> ignition loops. Note that it only works for power of two vlaues for egoCount
{
//Check all other requirements for closed loop adjustments
if ( ( currentStatus . coolant > configPage3 . egoTemp ) & & ( currentStatus . RPM > ( unsigned int ) ( configPage3 . egoRPM * 100 ) ) & & ( currentStatus . TPS < configPage3 . egoTPSMax ) & & ( currentStatus . O2 < configPage3 . ego_max ) & & ( currentStatus . O2 > configPage3 . ego_min ) & & ( currentStatus . runSecs > configPage3 . ego_sdelay ) )
{
//Determine whether the Y axis of the AFR target table tshould be MAP (Speed-Density) or TPS (Alpha-N)
byte yValue ;
if ( configPage1 . algorithm = = 0 ) { yValue = currentStatus . MAP ; }
else { yValue = currentStatus . TPS ; }
2015-06-04 04:54:11 -07:00
currentStatus . afrTarget = get3DTableValue ( & afrTable , yValue , currentStatus . RPM ) ; //Perform the target lookup
2015-02-05 13:11:33 -08:00
2015-02-15 02:32:38 -08:00
//Check which algorithm is used, simple or PID
if ( configPage3 . egoAlgorithm = = 0 )
{
//*************************************************************************************************************************************
//Simple algorithm
if ( currentStatus . O2 > currentStatus . afrTarget )
{
//Running lean
if ( currentStatus . egoCorrection < ( 100 + configPage3 . egoLimit ) ) //Fueling adjustment must be at most the egoLimit amount (up or down)
{
if ( currentStatus . egoCorrection > = 100 ) { return ( currentStatus . egoCorrection + 1 ) ; } //Increase the fueling by 1%
else { return 100 ; } //This means that the last reading had been rich, so simply return back to no adjustment (100%)
}
else { return currentStatus . egoCorrection ; } //Means we're at the maximum adjustment amount, so simply return then again
}
else
//Running Rich
if ( currentStatus . egoCorrection > ( 100 - configPage3 . egoLimit ) ) //Fueling adjustment must be at most the egoLimit amount (up or down)
{
if ( currentStatus . egoCorrection < = 100 ) { return ( currentStatus . egoCorrection - 1 ) ; } //Increase the fueling by 1%
else { return 100 ; } //This means that the last reading had been lean, so simply return back to no adjustment (100%)
}
else { return currentStatus . egoCorrection ; } //Means we're at the maximum adjustment amount, so simply return then again
}
else if ( configPage3 . egoAlgorithm = = 2 )
{
//*************************************************************************************************************************************
//PID algorithm
2016-04-06 20:28:13 -07:00
egoPID . SetOutputLimits ( ( long ) ( - configPage3 . egoLimit ) , ( long ) ( configPage3 . egoLimit ) ) ; //Set the limits again, just incase the user has changed them since the last loop. Note that these are sent to the PID library as (Eg:) -15 and +15
2015-02-16 21:32:07 -08:00
egoPID . SetTunings ( configPage3 . egoKP , configPage3 . egoKI , configPage3 . egoKD ) ; //Set the PID values again, just incase the user has changed them since the last loop
2016-04-06 20:28:13 -07:00
PID_O2 = ( long ) ( currentStatus . O2 ) ;
PID_AFRTarget = ( long ) ( currentStatus . afrTarget ) ;
2015-02-16 21:32:07 -08:00
egoPID . Compute ( ) ;
//currentStatus.egoCorrection = 100 + PID_output;
return ( 100 + PID_output ) ;
2015-02-15 02:32:38 -08:00
}
2015-02-05 13:11:33 -08:00
}
}
2015-02-05 18:11:39 -08:00
2015-02-15 02:32:38 -08:00
return 100 ; //Catch all (Includes when AFR target = current AFR
2015-02-05 13:11:33 -08:00
}