fome-fw/firmware/controllers/math/engine_math.cpp

477 lines
14 KiB
C++
Raw Normal View History

2015-07-10 06:01:56 -07:00
/**
* @file engine_math.cpp
* @brief
*
* @date Jul 13, 2013
2015-12-31 13:02:30 -08:00
* @author Andrey Belomutskiy, (c) 2012-2016
2015-07-10 06:01:56 -07:00
*
* This file is part of rusEfi - see http://rusefi.com
*
* rusEfi is free software; you can redistribute it and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "main.h"
#include "engine_math.h"
#include "engine_configuration.h"
#include "interpolation.h"
#include "allsensors.h"
#include "io_pins.h"
#include "trigger_decoder.h"
#include "event_registry.h"
#include "efiGpio.h"
#include "fuel_math.h"
2016-01-08 12:01:38 -08:00
#include "advance_map.h"
2015-07-10 06:01:56 -07:00
EXTERN_ENGINE
;
2016-11-03 20:02:58 -07:00
extern EnginePins enginePins;
2015-07-10 06:01:56 -07:00
/**
2016-01-08 12:01:38 -08:00
* @return number of milliseconds in one crank shaft revolution
2015-07-10 06:01:56 -07:00
*/
2015-09-07 06:03:24 -07:00
floatms_t getCrankshaftRevolutionTimeMs(int rpm) {
2016-08-30 19:02:21 -07:00
if (rpm == 0) {
return NAN;
}
2015-07-10 06:01:56 -07:00
return 360 * getOneDegreeTimeMs(rpm);
}
/**
* @brief Returns engine load according to selected engine_load_mode
*
*/
float getEngineLoadT(DECLARE_ENGINE_PARAMETER_F) {
efiAssert(engine!=NULL, "engine 2NULL", NAN);
efiAssert(engineConfiguration!=NULL, "engineConfiguration 2NULL", NAN);
2016-08-28 13:02:34 -07:00
switch (engineConfiguration->fuelAlgorithm) {
2015-07-10 06:01:56 -07:00
case LM_PLAIN_MAF:
if (!hasMafSensor(PASS_ENGINE_PARAMETER_F)) {
2016-07-13 18:03:05 -07:00
warning(CUSTOM_OBD_17, "MAF sensor needed for current fuel algorithm");
2015-07-10 06:01:56 -07:00
return NAN;
}
return getMafT(engineConfiguration);
case LM_SPEED_DENSITY:
// SD engine load is used for timing lookup but not for fuel calculation
case LM_MAP:
return getMap();
case LM_ALPHA_N:
return getTPS(PASS_ENGINE_PARAMETER_F);
case LM_REAL_MAF: {
return getRealMaf(PASS_ENGINE_PARAMETER_F);
}
default:
2016-08-28 13:02:34 -07:00
warning(CUSTOM_OBD_18, "Unexpected engine load parameter: %d", engineConfiguration->fuelAlgorithm);
2015-07-10 06:01:56 -07:00
return -1;
}
}
void setSingleCoilDwell(engine_configuration_s *engineConfiguration) {
for (int i = 0; i < DWELL_CURVE_SIZE; i++) {
2016-07-09 19:01:35 -07:00
engineConfiguration->sparkDwellBins[i] = i + 1;
engineConfiguration->sparkDwell[i] = 4;
2015-07-10 06:01:56 -07:00
}
2016-07-09 19:01:35 -07:00
engineConfiguration->sparkDwellBins[5] = 10;
2015-07-10 06:01:56 -07:00
engineConfiguration->sparkDwell[5] = 4;
engineConfiguration->sparkDwellBins[6] = 4500;
engineConfiguration->sparkDwell[6] = 4;
engineConfiguration->sparkDwellBins[7] = 12500;
engineConfiguration->sparkDwell[7] = 0;
}
#if EFI_ENGINE_CONTROL || defined(__DOXYGEN__)
2016-11-30 17:02:41 -08:00
FuelSchedule::FuelSchedule() {
clear();
}
void FuelSchedule::clear() {
memset(hasEvents, 0, sizeof(hasEvents));
eventsCount = 0;
usedAtEngineCycle = 0;
}
void FuelSchedule::addFuelEventsForCylinder(int i, injection_mode_e mode DECLARE_ENGINE_PARAMETER_S) {
efiAssertVoid(engine!=NULL, "engine is NULL");
if (cisnan(engine->rpmCalculator.oneDegreeUs)) {
// in order to have fuel schedule we need to have current RPM
// wonder if this line slows engine startup?
return;
}
/**
* injection phase is scheduled by injection end, so we need to step the angle back
* for the duration of the injection
*
* todo: since this method is not invoked within trigger event handler and
* engineState.injectionOffset is calculated from the same utility timer should we more that logic here?
*/
angle_t injectionDuration = MS2US(ENGINE(fuelMs)) / ENGINE(rpmCalculator.oneDegreeUs);
angle_t baseAngle = ENGINE(engineState.injectionOffset) - injectionDuration;
int index;
if (mode == IM_SIMULTANEOUS) {
index = 0;
} else if (mode == IM_SEQUENTIAL) {
index = getCylinderId(engineConfiguration->specs.firingOrder, i) - 1;
} else if (mode == IM_BATCH) {
// does not look exactly right, not too consistent with IM_SEQUENTIAL
index = i % (engineConfiguration->specs.cylindersCount / 2);
} else {
warning(CUSTOM_OBD_21, "Unexpected injection mode %d", mode);
index = 0;
}
bool isSimultanious = mode == IM_SIMULTANEOUS;
float angle = baseAngle
+ i * ENGINE(engineCycle) / CONFIG(specs.cylindersCount);
InjectorOutputPin *secondOutput;
if (mode == IM_BATCH && CONFIG(twoWireBatchInjection)) {
/**
* also fire the 2nd half of the injectors so that we can implement a batch mode on individual wires
*/
int secondIndex = index + (CONFIG(specs.cylindersCount) / 2);
secondOutput = &enginePins.injectors[secondIndex];
} else {
secondOutput = NULL;
}
2015-08-23 19:01:55 -07:00
2016-11-30 17:02:41 -08:00
InjectorOutputPin *output = &enginePins.injectors[index];
2015-08-23 19:01:55 -07:00
2015-07-10 06:01:56 -07:00
if (!isSimultanious && !isPinAssigned(output)) {
2016-08-28 13:02:34 -07:00
// todo: extract method for this index math
2016-07-13 18:03:05 -07:00
warning(CUSTOM_OBD_20, "no_pin_inj #%s", output->name);
2015-07-10 06:01:56 -07:00
}
2015-08-30 11:01:28 -07:00
InjectionEvent *ev = injectionEvents.add();
2015-07-10 06:01:56 -07:00
if (ev == NULL) {
// error already reported
return;
}
2016-09-02 22:02:32 -07:00
fixAngle(angle);
ev->isOverlapping = angle < 720 && (angle + injectionDuration) > 720;
2015-07-10 06:01:56 -07:00
2016-11-30 16:01:43 -08:00
ev->outputs[0] = output;
2016-11-30 17:02:41 -08:00
ev->outputs[1] = secondOutput;
2015-07-10 06:01:56 -07:00
ev->isSimultanious = isSimultanious;
efiAssertVoid(TRIGGER_SHAPE(getSize()) > 0, "uninitialized TriggerShape");
findTriggerPosition(&ev->injectionStart, angle PASS_ENGINE_PARAMETER);
2016-01-30 19:03:36 -08:00
#if EFI_UNIT_TEST
printf("registerInjectionEvent angle=%f index=%d\r\n", angle, ev->injectionStart.eventIndex);
#endif
2015-07-10 06:01:56 -07:00
if (!hasEvents[ev->injectionStart.eventIndex]) {
hasEvents[ev->injectionStart.eventIndex] = true;
eventsCount++;
}
}
void FuelSchedule::addFuelEvents(injection_mode_e mode DECLARE_ENGINE_PARAMETER_S) {
clear(); // this method is relatively heavy
// sourceList->reset();
2015-08-30 11:01:28 -07:00
injectionEvents.reset();
2015-07-10 06:01:56 -07:00
2016-11-30 17:02:41 -08:00
for (int i = 0; i < CONFIG(specs.cylindersCount); i++) {
addFuelEventsForCylinder(i, mode PASS_ENGINE_PARAMETER);
2015-07-10 06:01:56 -07:00
}
}
#endif
2016-11-11 16:02:29 -08:00
floatms_t getCrankingSparkDwell(int rpm DECLARE_ENGINE_PARAMETER_S) {
if (engineConfiguration->useConstantDwellDuringCranking) {
return engineConfiguration->ignitionDwellForCrankingMs;
} else {
// technically this could be implemented via interpolate2d
float angle = engineConfiguration->crankingChargeAngle;
return getOneDegreeTimeMs(rpm) * angle;
}
}
2015-07-10 06:01:56 -07:00
/**
* @return Spark dwell time, in milliseconds.
*/
2016-01-01 14:02:49 -08:00
floatms_t getSparkDwell(int rpm DECLARE_ENGINE_PARAMETER_S) {
2016-11-30 12:02:43 -08:00
float dwellMs;
2015-07-10 06:01:56 -07:00
if (isCrankingR(rpm)) {
2016-11-30 12:02:43 -08:00
dwellMs = getCrankingSparkDwell(rpm PASS_ENGINE_PARAMETER);
} else {
efiAssert(!cisnan(rpm), "invalid rpm", NAN);
dwellMs = interpolate2d(rpm, engineConfiguration->sparkDwellBins, engineConfiguration->sparkDwell, DWELL_CURVE_SIZE);
2015-07-10 06:01:56 -07:00
}
2016-11-30 12:02:43 -08:00
if (cisnan(dwellMs) || dwellMs < 0) {
firmwareError(CUSTOM_ERR_DWELL_DURATION, "invalid dwell: %f at %d", dwellMs, rpm);
}
return dwellMs;
2015-07-10 06:01:56 -07:00
}
2015-09-13 11:02:32 -07:00
static int findAngleIndex(float target DECLARE_ENGINE_PARAMETER_S) {
2015-07-10 06:01:56 -07:00
/**
* Here we rely on this to be pre-calculated, that's a performance optimization
*/
int engineCycleEventCount = engine->engineCycleEventCount;
efiAssert(engineCycleEventCount > 0, "engineCycleEventCount", 0);
uint32_t left = 0;
uint32_t right = engineCycleEventCount - 1;
/**
* Let's find the last trigger angle which is less or equal to the desired angle
* todo: extract binary search as template method?
*/
2015-09-13 11:02:32 -07:00
while (left <= right) {
int middle = (left + right) / 2;
2015-09-12 15:01:13 -07:00
angle_t eventAngle = TRIGGER_SHAPE(eventAngles[middle]);
2015-07-10 06:01:56 -07:00
2015-09-13 11:02:32 -07:00
if (eventAngle < target) {
left = middle + 1;
} else if (eventAngle > target) {
right = middle - 1;
} else {
2016-01-14 20:03:17 -08:00
// Values are equal
return middle; // Key found
2015-09-13 11:02:32 -07:00
}
}
return left - 1;
2015-07-10 06:01:56 -07:00
}
void findTriggerPosition(event_trigger_position_s *position, angle_t angleOffset DECLARE_ENGINE_PARAMETER_S) {
2016-01-14 20:03:17 -08:00
// convert engine cycle angle into trigger cycle angle
2015-07-10 06:01:56 -07:00
angleOffset += tdcPosition();
fixAngle(angleOffset);
int index = TRIGGER_SHAPE(triggerIndexByAngle[(int)angleOffset]);
angle_t eventAngle = TRIGGER_SHAPE(eventAngles[index]);
if (angleOffset < eventAngle) {
2016-07-13 18:03:05 -07:00
warning(CUSTOM_OBD_22, "angle constraint violation in findTriggerPosition(): %f/%f", angleOffset, eventAngle);
2015-07-10 06:01:56 -07:00
return;
}
position->eventIndex = index;
position->eventAngle = eventAngle;
position->angleOffset = angleOffset - eventAngle;
}
static int order_1_THEN_3_THEN_4_THEN2[] = { 1, 3, 4, 2 };
static int order_1_THEN_2_THEN_4_THEN3[] = { 1, 2, 4, 3 };
static int order_1_THEN_3_THEN_2_THEN4[] = { 1, 3, 2, 4 };
static int order_1_2_4_5_3[] = {1, 2, 4, 5, 3};
static int order_1_THEN_5_THEN_3_THEN_6_THEN_2_THEN_4[] = { 1, 5, 3, 6, 2, 4 };
static int order_1_THEN_4_THEN_2_THEN_5_THEN_3_THEN_6[] = { 1, 4, 2, 5, 3, 6 };
2015-09-05 20:02:46 -07:00
static int order_1_THEN_2_THEN_3_THEN_4_THEN_5_THEN_6[] = { 1, 2, 3, 4, 5, 6 };
2015-07-10 06:01:56 -07:00
static int order_1_8_4_3_6_5_7_2[] = { 1, 8, 4, 3, 6, 5, 7, 2 };
2016-05-22 10:07:12 -07:00
static int order_1_8_7_2_6_5_4_3[] = { 1, 8, 7, 2, 6, 5, 4, 3 };
2016-06-05 17:03:16 -07:00
static int order_1_5_4_2_6_3_7_8[] = { 1, 5, 4, 2, 6, 3, 7, 8 };
2016-05-22 10:07:12 -07:00
2015-07-10 06:01:56 -07:00
static int order_1_2[] = {1, 2};
2015-09-10 16:01:32 -07:00
static int order_1_2_3[] = {1, 2, 3};
2015-07-10 06:01:56 -07:00
/**
* @param index from zero to cylindersCount - 1
* @return cylinderId from one to cylindersCount
*/
int getCylinderId(firing_order_e firingOrder, int index) {
switch (firingOrder) {
2016-07-20 16:04:27 -07:00
case FO_1:
2015-07-10 06:01:56 -07:00
return 1;
2015-09-10 16:01:32 -07:00
// 2 cylinder
2016-07-20 16:04:27 -07:00
case FO_1_2:
2015-07-10 06:01:56 -07:00
return order_1_2[index];
2015-09-10 16:01:32 -07:00
// 3 cylinder
case FO_1_2_3:
return order_1_2_3[index];
2015-07-10 06:01:56 -07:00
// 4 cylinder
2016-07-20 16:04:27 -07:00
case FO_1_3_4_2:
2015-07-10 06:01:56 -07:00
return order_1_THEN_3_THEN_4_THEN2[index];
2016-07-20 16:04:27 -07:00
case FO_1_2_4_3:
2015-07-10 06:01:56 -07:00
return order_1_THEN_2_THEN_4_THEN3[index];
2016-07-20 16:04:27 -07:00
case FO_1_3_2_4:
2015-07-10 06:01:56 -07:00
return order_1_THEN_3_THEN_2_THEN4[index];
// 5 cylinder
case FO_1_2_4_5_3:
return order_1_2_4_5_3[index];
// 6 cylinder
2016-07-20 16:04:27 -07:00
case FO_1_5_3_6_2_4:
2015-07-10 06:01:56 -07:00
return order_1_THEN_5_THEN_3_THEN_6_THEN_2_THEN_4[index];
2016-07-20 16:04:27 -07:00
case FO_1_4_2_5_3_6:
2015-07-10 06:01:56 -07:00
return order_1_THEN_4_THEN_2_THEN_5_THEN_3_THEN_6[index];
2016-07-20 16:04:27 -07:00
case FO_1_2_3_4_5_6:
2015-09-05 20:02:46 -07:00
return order_1_THEN_2_THEN_3_THEN_4_THEN_5_THEN_6[index];
2015-07-10 06:01:56 -07:00
// 8 cylinder
case FO_1_8_4_3_6_5_7_2:
return order_1_8_4_3_6_5_7_2[index];
2016-05-22 10:07:12 -07:00
case FO_1_8_7_2_6_5_4_3:
return order_1_8_7_2_6_5_4_3[index];
2016-06-05 17:03:16 -07:00
case FO_1_5_4_2_6_3_7_8:
return order_1_5_4_2_6_3_7_8[index];
2015-07-10 06:01:56 -07:00
default:
2016-07-13 18:03:05 -07:00
warning(CUSTOM_OBD_23, "getCylinderId not supported for %d", firingOrder);
2015-07-10 06:01:56 -07:00
}
return 1;
}
2016-11-11 16:02:29 -08:00
static int getIgnitionPinForIndex(int i DECLARE_ENGINE_PARAMETER_S) {
2015-07-10 06:01:56 -07:00
switch (CONFIG(ignitionMode)) {
case IM_ONE_COIL:
2016-01-16 14:02:38 -08:00
return 0;
2015-07-10 06:01:56 -07:00
break;
case IM_WASTED_SPARK: {
2016-01-16 14:02:38 -08:00
return i % (CONFIG(specs.cylindersCount) / 2);
2015-07-10 06:01:56 -07:00
}
break;
case IM_INDIVIDUAL_COILS:
2016-01-16 14:02:38 -08:00
return i;
2015-07-10 06:01:56 -07:00
break;
default:
2016-07-13 18:03:05 -07:00
warning(CUSTOM_OBD_24, "unsupported ignitionMode %d in initializeIgnitionActions()", engineConfiguration->ignitionMode);
2016-01-16 14:02:38 -08:00
return 0;
2015-07-10 06:01:56 -07:00
}
}
#if EFI_ENGINE_CONTROL || defined(__DOXYGEN__)
2016-01-14 21:01:42 -08:00
/**
* This heavy method is only invoked in case of a configuration change or initialization.
*/
2015-07-10 06:01:56 -07:00
void prepareOutputSignals(DECLARE_ENGINE_PARAMETER_F) {
2016-01-14 21:01:42 -08:00
ENGINE(engineCycle) = getEngineCycle(CONFIG(operationMode));
2015-07-10 06:01:56 -07:00
2016-11-11 16:02:29 -08:00
angle_t maxTimingCorrMap = -720.0f;
angle_t maxTimingMap = -720.0f;
for (int rpmIndex = 0;rpmIndex<IGN_RPM_COUNT;rpmIndex++) {
for (int l = 0;l<IGN_LOAD_COUNT;l++) {
maxTimingCorrMap = maxF(maxTimingCorrMap, config->ignitionIatCorrTable[l][rpmIndex]);
maxTimingMap = maxF(maxTimingMap, config->ignitionTable[l][rpmIndex]);
}
}
#if EFI_UNIT_TEST
floatms_t crankingDwell = getCrankingSparkDwell(CONFIG(cranking.rpm) PASS_ENGINE_PARAMETER);
// dwell at cranking is constant angle or constant time, dwell at cranking threshold is the highest angle duration
// lower RPM angle duration goes up
2016-11-26 17:01:34 -08:00
angle_t maxDwellAngle = crankingDwell / getOneDegreeTimeMs(CONFIG(cranking.rpm));
2016-11-11 16:02:29 -08:00
2016-11-26 17:01:34 -08:00
printf("cranking angle %f\r\n", maxDwellAngle);
2016-11-11 16:02:29 -08:00
for (int i = 0;i<DWELL_CURVE_SIZE;i++) {
int rpm = (int)engineConfiguration->sparkDwellBins[i];
floatms_t dwell = engineConfiguration->sparkDwell[i];
angle_t dwellAngle = dwell / getOneDegreeTimeMs(rpm);
printf("dwell angle %f at %d\r\n", dwellAngle, rpm);
2016-11-26 17:01:34 -08:00
maxDwellAngle = maxF(maxDwellAngle, dwellAngle);
2016-11-11 16:02:29 -08:00
}
2016-11-26 17:01:34 -08:00
angle_t maxIatAdvanceCorr = -720;
for (int r = 0;r<IGN_RPM_COUNT;r++) {
for (int l = 0;l<IGN_LOAD_COUNT;l++) {
maxIatAdvanceCorr = maxF(maxIatAdvanceCorr, config->ignitionIatCorrTable[l][r]);
}
}
angle_t maxAdvance = -720;
for (int r = 0;r<IGN_RPM_COUNT;r++) {
for (int l = 0;l<IGN_LOAD_COUNT;l++) {
maxAdvance = maxF(maxAdvance, config->ignitionTable[l][r]);
}
}
printf("max dwell angle %f/%d/%d\r\n", maxDwellAngle, (int)maxAdvance, (int)maxIatAdvanceCorr);
2016-11-11 16:02:29 -08:00
#endif
2016-10-31 19:02:12 -07:00
#if EFI_UNIT_TEST
2016-11-07 20:01:47 -08:00
printf("prepareOutputSignals %d onlyEdge=%s %s\r\n", engineConfiguration->trigger.type, boolToString(engineConfiguration->useOnlyRisingEdgeForTrigger),
getIgnition_mode_e(engineConfiguration->ignitionMode));
2016-10-31 19:02:12 -07:00
#endif
2015-07-10 06:01:56 -07:00
engine_configuration2_s *engineConfiguration2 = engine->engineConfiguration2;
for (int i = 0; i < CONFIG(specs.cylindersCount); i++) {
2016-01-14 21:01:42 -08:00
ENGINE(angleExtra[i])= ENGINE(engineCycle) * i / CONFIG(specs.cylindersCount);
2015-07-10 06:01:56 -07:00
ENGINE(ignitionPin[i]) = getIgnitionPinForIndex(i PASS_ENGINE_PARAMETER);
}
2016-01-14 21:01:42 -08:00
int engineCycleInt = (int) ENGINE(engineCycle);
for (int angle = 0; angle < engineCycleInt; angle++) {
2015-07-10 06:01:56 -07:00
int triggerShapeIndex = findAngleIndex(angle PASS_ENGINE_PARAMETER);
2016-02-27 20:03:34 -08:00
if (engineConfiguration->useOnlyRisingEdgeForTrigger)
2015-07-10 06:01:56 -07:00
triggerShapeIndex = triggerShapeIndex & 0xFFFFFFFE; // we need even index for front_only
TRIGGER_SHAPE(triggerIndexByAngle[angle]) = triggerShapeIndex;
}
}
#endif
void setFuelRpmBin(float from, float to DECLARE_ENGINE_PARAMETER_S) {
setTableBin(config->fuelRpmBins, FUEL_RPM_COUNT, from, to);
}
void setFuelLoadBin(float from, float to DECLARE_ENGINE_PARAMETER_S) {
setTableBin(config->fuelLoadBins, FUEL_LOAD_COUNT, from, to);
}
void setTimingRpmBin(float from, float to DECLARE_ENGINE_PARAMETER_S) {
2016-01-21 19:01:31 -08:00
setRpmBin(config->ignitionRpmBins, IGN_RPM_COUNT, from, to);
2015-07-10 06:01:56 -07:00
}
void setTimingLoadBin(float from, float to DECLARE_ENGINE_PARAMETER_S) {
setTableBin(config->ignitionLoadBins, IGN_LOAD_COUNT, from, to);
}
/**
* this method sets algorithm and ignition table scale
*/
void setAlgorithm(engine_load_mode_e algo DECLARE_ENGINE_PARAMETER_S) {
2016-08-28 13:02:34 -07:00
engineConfiguration->fuelAlgorithm = algo;
2015-07-10 06:01:56 -07:00
if (algo == LM_ALPHA_N) {
2016-01-21 13:01:34 -08:00
setTimingLoadBin(20, 120 PASS_ENGINE_PARAMETER);
2015-07-10 06:01:56 -07:00
} else if (algo == LM_SPEED_DENSITY) {
2016-01-21 19:01:31 -08:00
setTableBin2(config->ignitionLoadBins, IGN_LOAD_COUNT, 20, 120, 3);
2016-01-08 12:01:38 -08:00
buildTimingMap(35 PASS_ENGINE_PARAMETER);
2015-07-10 06:01:56 -07:00
}
}
2016-07-24 20:02:52 -07:00
void setInjectorLag(float value DECLARE_ENGINE_PARAMETER_S) {
2016-08-28 13:02:34 -07:00
setArrayValues(engineConfiguration->injector.battLagCorr, VBAT_INJECTOR_CURVE_SIZE, value);
2016-07-24 20:02:52 -07:00
}