Skip to content

Commit

Permalink
Unit test all corrections (speeduino#1207)
Browse files Browse the repository at this point in the history
* Add unit tests for correctionCranking

* Unit test correctionASE

* Add test_maths_div10_s16_perf

* Make TPS AE tests independent

* Add test_corrections_MAE

* Use RUN_TEST_P to reduce test RAM usage

* Unit test correctionFloodClear

* Unit test battery correction

* Add unit tests for correctionLaunch

* Test repetability - initialiseAll at the start of every test

* Unit test flex & fuel temp corrections, plus minor cleanup

* Unit tests - avoid forward declarations

* Separate out populate_table() from 3D table tests

* Separate calculation of AFR target from correction (and unit test the calculation).
Separation of concerns and will make unit test AFR correction easier.

* Unit test correctionAFRClosedLoop

* Unit test correctionsFuel

* Add populate_2dtable_P test utility function

* Unit test all ignition corrections

* Add compile time buffer overflow check to RUN_TEST_P

* DFCO - test behavior not implementation

* Tweak tests to pass on Teensy 3.5

* Shrink unit test for faster build/upload/execute
We just need the 2D tables wired up, not the whole system initialized.
1. Factor out construct2dTables() from initialiseAll()
2. In the unit tests,  call construct2dTables() instead of initialiseAll()
Linker then does the heavy lifting of removing unused symbols

* Add test_correctionsDwell_uses_batvcorrection

* Use TEST_ASSERT_BIT_[HIGH_LOW] instead of TEST_ASSERT_[TRUE|FALSE]
Better failure messages, declares intent

* Igniton corrction test: expand assertions: make sure all corrections test both positive an negative advance values.

* Add AE test assertion: especially for MAE

* Add AE timeout unit tests

* Convert copy/paste 2D table construction code into shared functions

* Disbale knock unit tests until knock implementation is stable.
  • Loading branch information
adbancroft authored Jul 30, 2024
1 parent fc5397b commit 96dce44
Show file tree
Hide file tree
Showing 17 changed files with 2,638 additions and 449 deletions.
42 changes: 31 additions & 11 deletions speeduino/corrections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,16 @@ uint8_t dfcoTaper;
*/
void initialiseCorrections(void)
{
egoPID.SetMode(AUTOMATIC); //Turn O2 PID on
PID_output = 0L;
PID_O2 = 0L;
PID_AFRTarget = 0L;
// Toggling between modes resets the PID internal state
// This is required by the unit tests
// TODO: modify PID code to provide a method to reset it.
egoPID.SetMode(AUTOMATIC);
egoPID.SetMode(MANUAL);
egoPID.SetMode(AUTOMATIC);

currentStatus.flexIgnCorrection = 0;
currentStatus.egoCorrection = 100; //Default value of no adjustment must be set to avoid randomness on first correction cycle after startup
AFRnextCycle = 0;
Expand Down Expand Up @@ -584,6 +593,26 @@ byte correctionFuelTemp(void)
return fuelTempValue;
}


// ============================= Air Fuel Ratio (AFR) correction =============================

uint8_t calculateAfrTarget(table3d16RpmLoad &afrLookUpTable, const statuses &current, const config2 &page2, const config6 &page6) {
//afrTarget value lookup must be done if O2 sensor is enabled, and always if incorporateAFR is enabled
if (page2.incorporateAFR == true) {
return get3DTableValue(&afrLookUpTable, current.fuelLoad, current.RPM);
}
if (page6.egoType!=EGO_TYPE_OFF)
{
//Determine whether the Y axis of the AFR target table tshould be MAP (Speed-Density) or TPS (Alpha-N)
//Note that this should only run after the sensor warmup delay when using Include AFR option,
if( current.runSecs > page6.ego_sdelay) {
return get3DTableValue(&afrLookUpTable, current.fuelLoad, current.RPM);
}
return current.O2; //Catch all
}
return current.afrTarget;
}

/** Lookup the AFR target table and perform either a simple or PID adjustment based on this.
Simple (Best suited to narrowband sensors):
Expand All @@ -599,17 +628,8 @@ PID (Best suited to wideband sensors):
*/
byte correctionAFRClosedLoop(void)
{
byte AFRValue = 100;

if( (configPage6.egoType > 0) || (configPage2.incorporateAFR == true) ) //afrTarget value lookup must be done if O2 sensor is enabled, and always if incorporateAFR is enabled
{
currentStatus.afrTarget = currentStatus.O2; //Catch all in case the below doesn't run. This prevents the Include AFR option from doing crazy things if the AFR target conditions aren't met. This value is changed again below if all conditions are met.
byte AFRValue = 100U;

//Determine whether the Y axis of the AFR target table tshould be MAP (Speed-Density) or TPS (Alpha-N)
//Note that this should only run after the sensor warmup delay when using Include AFR option, but on Incorporate AFR option it needs to be done at all times
if( (currentStatus.runSecs > configPage6.ego_sdelay) || (configPage2.incorporateAFR == true) ) { currentStatus.afrTarget = get3DTableValue(&afrTable, currentStatus.fuelLoad, currentStatus.RPM); } //Perform the target lookup
}

if((configPage6.egoType > 0) && (BIT_CHECK(currentStatus.status1, BIT_STATUS1_DFCO) != 1 ) ) //egoType of 0 means no O2 sensor. If DFCO is active do not run the ego controllers to prevent iterator wind-up.
{
AFRValue = currentStatus.egoCorrection; //Need to record this here, just to make sure the correction stays 'on' even if the nextCycle count isn't ready
Expand Down
2 changes: 1 addition & 1 deletion speeduino/corrections.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ All functions in the gamma file return

void initialiseCorrections(void);
uint16_t correctionsFuel(void);
uint8_t calculateAfrTarget(table3d16RpmLoad &afrLookUpTable, const statuses &current, const config2 &page2, const config6 &page6);
byte correctionWUE(void); //Warmup enrichment
uint16_t correctionCranking(void); //Cranking enrichment
byte correctionASE(void); //After Start Enrichment
Expand All @@ -24,7 +25,6 @@ byte correctionLaunch(void); //Launch control correction
byte correctionDFCOfuel(void); //DFCO taper correction
bool correctionDFCO(void); //Decelleration fuel cutoff


int8_t correctionsIgn(int8_t advance);
int8_t correctionFixedTiming(int8_t advance);
int8_t correctionCrankingFixedTiming(int8_t advance);
Expand Down
13 changes: 11 additions & 2 deletions speeduino/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,10 @@
#define EVEN_FIRE 0
#define ODD_FIRE 1

#define EGO_ALGORITHM_SIMPLE 0
#define EGO_ALGORITHM_PID 2
#define EGO_ALGORITHM_SIMPLE 0U
#define EGO_ALGORITHM_INVALID1 1U
#define EGO_ALGORITHM_PID 2U
#define EGO_ALGORITHM_NONE 3U

#define STAGING_MODE_TABLE 0
#define STAGING_MODE_AUTO 1
Expand Down Expand Up @@ -873,6 +875,13 @@ struct config2 {
} __attribute__((__packed__)); //The 32 bit systems require all structs to be fully packed
#endif

#define IDLEADVANCE_MODE_OFF 0U
#define IDLEADVANCE_MODE_ADDED 1U
#define IDLEADVANCE_MODE_SWITCHED 2U

#define IDLEADVANCE_ALGO_TPS 0U
#define IDLEADVANCE_ALGO_CTPS 1U

/** Page 4 of the config - variables required for ignition and rpm/crank phase /cam phase decoding.
* See the ini file for further reference.
*/
Expand Down
210 changes: 40 additions & 170 deletions speeduino/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,44 @@
#include "rtc_common.h"
#endif

#if !defined(UNIT_TEST)
static inline
#endif
void construct2dTables(void) {
//Repoint the 2D table structs to the config pages that were just loaded
construct2dTable(taeTable, _countof(configPage4.taeValues), configPage4.taeValues, configPage4.taeBins);
construct2dTable(maeTable, _countof(configPage4.maeRates), configPage4.maeRates, configPage4.maeBins);
construct2dTable(WUETable, _countof(configPage2.wueValues), configPage2.wueValues, configPage4.wueBins);
construct2dTable(ASETable, _countof(configPage2.asePct), configPage2.asePct, configPage2.aseBins);
construct2dTable(ASECountTable, _countof(configPage2.aseCount), configPage2.aseCount, configPage2.aseBins);
construct2dTable(PrimingPulseTable, _countof(configPage2.primePulse), configPage2.primePulse, configPage2.primeBins);
construct2dTable(crankingEnrichTable, _countof(configPage10.crankingEnrichValues), configPage10.crankingEnrichValues, configPage10.crankingEnrichBins);
construct2dTable(dwellVCorrectionTable, _countof(configPage4.dwellCorrectionValues), configPage4.dwellCorrectionValues, configPage6.voltageCorrectionBins);
construct2dTable(injectorVCorrectionTable, _countof(configPage6.injVoltageCorrectionValues), configPage6.injVoltageCorrectionValues, configPage6.voltageCorrectionBins);
construct2dTable(IATDensityCorrectionTable, _countof(configPage6.airDenRates), configPage6.airDenRates, configPage6.airDenBins);
construct2dTable(baroFuelTable, _countof(configPage4.baroFuelValues), configPage4.baroFuelValues, configPage4.baroFuelBins);
construct2dTable(IATRetardTable, _countof(configPage4.iatRetValues), configPage4.iatRetValues, configPage4.iatRetBins);
construct2dTable(CLTAdvanceTable, _countof(configPage4.cltAdvValues), configPage4.cltAdvValues, configPage4.cltAdvBins);
construct2dTable(idleTargetTable, _countof(configPage6.iacCLValues), configPage6.iacCLValues, configPage6.iacBins);
construct2dTable(idleAdvanceTable, _countof(configPage4.idleAdvValues), configPage4.idleAdvValues, configPage4.idleAdvBins);
construct2dTable(rotarySplitTable, _countof(configPage10.rotarySplitValues), configPage10.rotarySplitValues, configPage10.rotarySplitBins);
construct2dTable(flexFuelTable, _countof(configPage10.flexFuelAdj), configPage10.flexFuelAdj, configPage10.flexFuelBins);
construct2dTable(flexAdvTable, _countof(configPage10.flexAdvAdj), configPage10.flexAdvAdj, configPage10.flexAdvBins);
construct2dTable(fuelTempTable, _countof(configPage10.fuelTempValues), configPage10.fuelTempValues, configPage10.fuelTempBins);
construct2dTable(oilPressureProtectTable, _countof(configPage10.oilPressureProtMins), configPage10.oilPressureProtMins, configPage10.oilPressureProtRPM);
construct2dTable(coolantProtectTable, _countof(configPage9.coolantProtRPM), configPage9.coolantProtRPM, configPage9.coolantProtTemp);
construct2dTable(fanPWMTable, _countof(configPage9.PWMFanDuty), configPage9.PWMFanDuty, configPage6.fanPWMBins);
construct2dTable(wmiAdvTable, _countof(configPage10.wmiAdvAdj), configPage10.wmiAdvAdj, configPage10.wmiAdvBins);
construct2dTable(rollingCutTable, _countof(configPage15.rollingProtCutPercent), configPage15.rollingProtCutPercent, configPage15.rollingProtRPMDelta);
construct2dTable(injectorAngleTable, _countof(configPage2.injAng), configPage2.injAng, configPage2.injAngRPM);
construct2dTable(flexBoostTable, _countof(configPage10.flexBoostAdj), configPage10.flexBoostAdj, configPage10.flexBoostBins);
construct2dTable(knockWindowStartTable, _countof(configPage10.knock_window_angle), configPage10.knock_window_angle, configPage10.knock_window_rpms);
construct2dTable(knockWindowDurationTable, _countof(configPage10.knock_window_dur), configPage10.knock_window_dur, configPage10.knock_window_rpms);
construct2dTable(cltCalibrationTable, _countof(cltCalibration_values), cltCalibration_values, cltCalibration_bins);
construct2dTable(iatCalibrationTable, _countof(iatCalibration_values), iatCalibration_values, iatCalibration_bins);
construct2dTable(o2CalibrationTable, _countof(o2Calibration_values), o2Calibration_values, o2Calibration_bins);
}

/** Initialise Speeduino for the main loop.
* Top level init entry point for all initialisations:
* - Initialise and set sizes of 3D tables
Expand Down Expand Up @@ -121,178 +159,10 @@ void initialiseAll(void)
BIT_SET(currentStatus.status4, BIT_STATUS4_ALLOW_LEGACY_COMMS); //Flag legacy comms as being allowed on startup

//Repoint the 2D table structs to the config pages that were just loaded
taeTable.valueSize = SIZE_BYTE; //Set this table to use byte values
taeTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
taeTable.xSize = 4;
taeTable.values = configPage4.taeValues;
taeTable.axisX = configPage4.taeBins;
maeTable.valueSize = SIZE_BYTE; //Set this table to use byte values
maeTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
maeTable.xSize = 4;
maeTable.values = configPage4.maeRates;
maeTable.axisX = configPage4.maeBins;
WUETable.valueSize = SIZE_BYTE; //Set this table to use byte values
WUETable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
WUETable.xSize = 10;
WUETable.values = configPage2.wueValues;
WUETable.axisX = configPage4.wueBins;
ASETable.valueSize = SIZE_BYTE;
ASETable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
ASETable.xSize = 4;
ASETable.values = configPage2.asePct;
ASETable.axisX = configPage2.aseBins;
ASECountTable.valueSize = SIZE_BYTE;
ASECountTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
ASECountTable.xSize = 4;
ASECountTable.values = configPage2.aseCount;
ASECountTable.axisX = configPage2.aseBins;
PrimingPulseTable.valueSize = SIZE_BYTE;
PrimingPulseTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
PrimingPulseTable.xSize = 4;
PrimingPulseTable.values = configPage2.primePulse;
PrimingPulseTable.axisX = configPage2.primeBins;
crankingEnrichTable.valueSize = SIZE_BYTE;
crankingEnrichTable.axisSize = SIZE_BYTE;
crankingEnrichTable.xSize = 4;
crankingEnrichTable.values = configPage10.crankingEnrichValues;
crankingEnrichTable.axisX = configPage10.crankingEnrichBins;

dwellVCorrectionTable.valueSize = SIZE_BYTE;
dwellVCorrectionTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
dwellVCorrectionTable.xSize = 6;
dwellVCorrectionTable.values = configPage4.dwellCorrectionValues;
dwellVCorrectionTable.axisX = configPage6.voltageCorrectionBins;
injectorVCorrectionTable.valueSize = SIZE_BYTE;
injectorVCorrectionTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
injectorVCorrectionTable.xSize = 6;
injectorVCorrectionTable.values = configPage6.injVoltageCorrectionValues;
injectorVCorrectionTable.axisX = configPage6.voltageCorrectionBins;
injectorAngleTable.valueSize = SIZE_INT;
injectorAngleTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
injectorAngleTable.xSize = 4;
injectorAngleTable.values = configPage2.injAng;
injectorAngleTable.axisX = configPage2.injAngRPM;
IATDensityCorrectionTable.valueSize = SIZE_BYTE;
IATDensityCorrectionTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
IATDensityCorrectionTable.xSize = 9;
IATDensityCorrectionTable.values = configPage6.airDenRates;
IATDensityCorrectionTable.axisX = configPage6.airDenBins;
baroFuelTable.valueSize = SIZE_BYTE;
baroFuelTable.axisSize = SIZE_BYTE;
baroFuelTable.xSize = 8;
baroFuelTable.values = configPage4.baroFuelValues;
baroFuelTable.axisX = configPage4.baroFuelBins;
IATRetardTable.valueSize = SIZE_BYTE;
IATRetardTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
IATRetardTable.xSize = 6;
IATRetardTable.values = configPage4.iatRetValues;
IATRetardTable.axisX = configPage4.iatRetBins;
CLTAdvanceTable.valueSize = SIZE_BYTE;
CLTAdvanceTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
CLTAdvanceTable.xSize = 6;
CLTAdvanceTable.values = (byte*)configPage4.cltAdvValues;
CLTAdvanceTable.axisX = configPage4.cltAdvBins;
idleTargetTable.valueSize = SIZE_BYTE;
idleTargetTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
idleTargetTable.xSize = 10;
idleTargetTable.values = configPage6.iacCLValues;
idleTargetTable.axisX = configPage6.iacBins;
idleAdvanceTable.valueSize = SIZE_BYTE;
idleAdvanceTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
idleAdvanceTable.xSize = 6;
idleAdvanceTable.values = (byte*)configPage4.idleAdvValues;
idleAdvanceTable.axisX = configPage4.idleAdvBins;
rotarySplitTable.valueSize = SIZE_BYTE;
rotarySplitTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
rotarySplitTable.xSize = 8;
rotarySplitTable.values = configPage10.rotarySplitValues;
rotarySplitTable.axisX = configPage10.rotarySplitBins;

flexFuelTable.valueSize = SIZE_BYTE;
flexFuelTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
flexFuelTable.xSize = 6;
flexFuelTable.values = configPage10.flexFuelAdj;
flexFuelTable.axisX = configPage10.flexFuelBins;
flexAdvTable.valueSize = SIZE_BYTE;
flexAdvTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
flexAdvTable.xSize = 6;
flexAdvTable.values = configPage10.flexAdvAdj;
flexAdvTable.axisX = configPage10.flexAdvBins;
flexBoostTable.valueSize = SIZE_INT;
flexBoostTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins (NOTE THIS IS DIFFERENT TO THE VALUES!!)
flexBoostTable.xSize = 6;
flexBoostTable.values = configPage10.flexBoostAdj;
flexBoostTable.axisX = configPage10.flexBoostBins;
fuelTempTable.valueSize = SIZE_BYTE;
fuelTempTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
fuelTempTable.xSize = 6;
fuelTempTable.values = configPage10.fuelTempValues;
fuelTempTable.axisX = configPage10.fuelTempBins;

knockWindowStartTable.valueSize = SIZE_BYTE;
knockWindowStartTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
knockWindowStartTable.xSize = 6;
knockWindowStartTable.values = configPage10.knock_window_angle;
knockWindowStartTable.axisX = configPage10.knock_window_rpms;
knockWindowDurationTable.valueSize = SIZE_BYTE;
knockWindowDurationTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
knockWindowDurationTable.xSize = 6;
knockWindowDurationTable.values = configPage10.knock_window_dur;
knockWindowDurationTable.axisX = configPage10.knock_window_rpms;

oilPressureProtectTable.valueSize = SIZE_BYTE;
oilPressureProtectTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
oilPressureProtectTable.xSize = 4;
oilPressureProtectTable.values = configPage10.oilPressureProtMins;
oilPressureProtectTable.axisX = configPage10.oilPressureProtRPM;

coolantProtectTable.valueSize = SIZE_BYTE;
coolantProtectTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
coolantProtectTable.xSize = 6;
coolantProtectTable.values = configPage9.coolantProtRPM;
coolantProtectTable.axisX = configPage9.coolantProtTemp;


fanPWMTable.valueSize = SIZE_BYTE;
fanPWMTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
fanPWMTable.xSize = 4;
fanPWMTable.values = configPage9.PWMFanDuty;
fanPWMTable.axisX = configPage6.fanPWMBins;

rollingCutTable.valueSize = SIZE_BYTE;
rollingCutTable.axisSize = SIZE_SIGNED_BYTE; //X axis is SIGNED for this table.
rollingCutTable.xSize = 4;
rollingCutTable.values = configPage15.rollingProtCutPercent;
rollingCutTable.axisX = configPage15.rollingProtRPMDelta;

wmiAdvTable.valueSize = SIZE_BYTE;
wmiAdvTable.axisSize = SIZE_BYTE; //Set this table to use byte axis bins
wmiAdvTable.xSize = 6;
wmiAdvTable.values = configPage10.wmiAdvAdj;
wmiAdvTable.axisX = configPage10.wmiAdvBins;

cltCalibrationTable.valueSize = SIZE_INT;
cltCalibrationTable.axisSize = SIZE_INT;
cltCalibrationTable.xSize = 32;
cltCalibrationTable.values = cltCalibration_values;
cltCalibrationTable.axisX = cltCalibration_bins;

iatCalibrationTable.valueSize = SIZE_INT;
iatCalibrationTable.axisSize = SIZE_INT;
iatCalibrationTable.xSize = 32;
iatCalibrationTable.values = iatCalibration_values;
iatCalibrationTable.axisX = iatCalibration_bins;

o2CalibrationTable.valueSize = SIZE_BYTE;
o2CalibrationTable.axisSize = SIZE_INT;
o2CalibrationTable.xSize = 32;
o2CalibrationTable.values = o2Calibration_values;
o2CalibrationTable.axisX = o2Calibration_bins;
construct2dTables();

//Setup the calibration tables
loadCalibration();


loadCalibration();

//Set the pin mappings
if((configPage2.pinMapping == 255) || (configPage2.pinMapping == 0)) //255 = EEPROM value in a blank AVR; 0 = EEPROM value in new FRAM
Expand Down
1 change: 1 addition & 0 deletions speeduino/speeduino.ino
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ void loop(void)

//Begin the fuel calculation
//Calculate an injector pulsewidth from the VE
currentStatus.afrTarget = calculateAfrTarget(afrTable, currentStatus, configPage2, configPage6);
currentStatus.corrections = correctionsFuel();

currentStatus.PW1 = PW(req_fuel_uS, currentStatus.VE, currentStatus.MAP, currentStatus.corrections, inj_opentime_uS);
Expand Down
Loading

0 comments on commit 96dce44

Please sign in to comment.