Basic framework for high pressure fuel pump control (#3476)

* Basic framework for high pressure fuel pump control

* Many changes

Move calculations to fast callback
Move main object into Engine
Respond to pin changes without requiring a reboot

* Use EngineModule for HpfpController
Schedule pin off after executing pin on so we are sure it ends, even if the motor stops.
Test scheduling.
Less RAM use by only having one event and reordering fields.

* Make scheduling test actually useful - need non-0 activation angle.

Co-authored-by: rusefillc <48498823+rusefillc@users.noreply.github.com>
This commit is contained in:
Scott Smith 2021-11-19 20:06:51 -08:00 committed by GitHub
parent f8f52c4c1f
commit 5bc1949aef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 585 additions and 17 deletions

View File

@ -8,6 +8,7 @@
#pragma once
#include "globalaccess.h"
#include "engine_module.h"
#include "engine_state.h"
#include "rpm_calculator.h"
#include "event_registry.h"
@ -18,6 +19,7 @@
#include "local_version_holder.h"
#include "buttonshift.h"
#include "gear_controller.h"
#include "high_pressure_fuel_pump.h"
#include "limp_manager.h"
#include "pin_repository.h"
#include "ac_control.h"
@ -126,8 +128,12 @@ public:
Mockable<InjectorModel>,
#if EFI_IDLE_CONTROL
IdleController,
#endif
#endif // EFI_IDLE_CONTROL
TriggerScheduler,
#if EFI_HPFP && EFI_ENGINE_CONTROL
HpfpController,
#endif // EFI_HPFP && EFI_ENGINE_CONTROL
FuelPumpController,
EngineModule // dummy placeholder so the previous entries can all have commas
> engineModules;
@ -209,7 +215,6 @@ public:
FuelSchedule injectionEvents;
IgnitionEventList ignitionEvents;
scheduling_s tdcScheduler[2];
#endif /* EFI_ENGINE_CONTROL */
bool needToStopEngine(efitick_t nowNt) const;

View File

@ -578,9 +578,6 @@ void commonInitEngineController() {
* This method adds trigger listener which actually schedules ignition
*/
initMainEventListener();
#if EFI_HPFP
initHPFP();
#endif // EFI_HPFP
}
#endif /* EFI_ENGINE_CONTROL */

View File

@ -1,23 +1,195 @@
/*
* @file high_pressure_fuel_pump.cpp
* @brief High Pressure Fuel Pump controller for GDI applications
*
* @date Nov 6, 2021
* @author Scott Smith, (c) 2021
*/
/*
* This file is part of rusEfi - see http://rusefi.com
*
* todo: there is some similarity with aux_valves.cpp and even map_averaging.cpp maybe reduce code duplication?
* 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.
*
* @date Nov 5, 2020
* @author Andrey Belomutskiy, (c) 2012-2020
* 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 "pch.h"
#include "high_pressure_fuel_pump.h"
#include "spark_logic.h"
#include "fuel_computer.h"
#if EFI_HPFP
// A constant we use; doesn't seem important to hoist into engineConfiguration.
static constexpr int rpm_spinning_cutoff = 60; // Below this RPM, we don't run the logic
void initHPFP() {
// Deleted to make new code diff easier
angle_t HpfpLobe::findNextLobe() {
// TODO: Ideally we figure out where we are in the engine cycle and pick the next lobe
// based on that. At least we should do that when cranking, so we can start that much
// sooner.
auto lobes = engineConfiguration->hpfpCamLobes;
if (!lobes) {
return 0;
}
// Which lobe are we on?
int next_index = m_lobe_index + 1;
// Note, this will be insufficient if the # of cam lobes is
// dynamically changed rapidly by more than 2x, but it will
// correct itself rather quickly.
if (next_index >= lobes) {
next_index -= lobes;
}
m_lobe_index = next_index;
// Calculate impact of VVT
angle_t vvt = 0;
if (engineConfiguration->hpfpCam != HPFP_CAM_NONE) {
// TODO: Is the sign correct here? + means ATDC?
vvt = engine->triggerCentral.getVVTPosition(
(engineConfiguration->hpfpCam - 1) / 2 & 1, // Bank
(engineConfiguration->hpfpCam - 1) & 1); // Cam
}
return engineConfiguration->hpfpPeakPos + vvt + next_index * 720 / lobes;
}
// As a percent of the full pump stroke
float HpfpQuantity::calcFuelPercent(int rpm) {
float fuel_requested_cc_per_cycle =
engine->injectionMass[0] * (1.f / fuelDensity) * engineConfiguration->specs.cylindersCount;
float fuel_requested_cc_per_lobe = fuel_requested_cc_per_cycle / engineConfiguration->hpfpCamLobes;
return 100.f *
fuel_requested_cc_per_lobe / engineConfiguration->hpfpPumpVolume +
interpolate3d(engineConfiguration->hpfpCompensation,
engineConfiguration->hpfpCompensationLoadBins, fuel_requested_cc_per_lobe,
engineConfiguration->hpfpCompensationRpmBins, rpm);
}
float HpfpQuantity::calcPI(int rpm, float calc_fuel_percent) {
m_pressureTarget_kPa = std::max<float>(
m_pressureTarget_kPa - (engineConfiguration->hpfpTargetDecay *
(FAST_CALLBACK_PERIOD_MS / 1000.)),
interpolate3d(engineConfiguration->hpfpTarget,
engineConfiguration->hpfpTargetLoadBins, Sensor::get(SensorType::Map).Value, // TODO: allow other load axis, like we claim to
engineConfiguration->hpfpTargetRpmBins, rpm));
float pressureError_kPa =
m_pressureTarget_kPa - Sensor::get(SensorType::FuelPressureHigh).Value;
float p_control_percent = pressureError_kPa * engineConfiguration->hpfpPidP;
float i_factor_divisor =
1000. * // ms/sec
60. * // sec/min -> ms/min
2.; // rev/cycle -> (rev * ms) / (min * cycle)
float i_factor =
engineConfiguration->hpfpPidI * // % / (kPa * lobe)
rpm * // (% * revs) / (kPa * lobe * min)
engineConfiguration->hpfpCamLobes * // lobes/cycle -> (% * revs) / (kPa * min * cycles)
(FAST_CALLBACK_PERIOD_MS / // (% * revs * ms) / (kPa * min * cycles)
i_factor_divisor); // % / kPa
float i_control_percent = m_I_sum_percent + pressureError_kPa * i_factor;
calc_fuel_percent += p_control_percent;
i_control_percent = clampF(-calc_fuel_percent, i_control_percent,
100.f - calc_fuel_percent);
m_I_sum_percent = i_control_percent;
return p_control_percent + i_control_percent;
}
angle_t HpfpQuantity::pumpAngleFuel(int rpm) {
// Math based on fuel requested
float fuel_requested_percent = calcFuelPercent(rpm);
// Apply PI control
fuel_requested_percent += calcPI(rpm, fuel_requested_percent);
// Convert to degrees
return interpolate2d(fuel_requested_percent,
engineConfiguration->hpfpLobeProfileQuantityBins,
engineConfiguration->hpfpLobeProfileAngle);
}
void HpfpController::onFastCallback() {
// Pressure current/target calculation
int rpm = engine->rpmCalculator.getRpm();
// What conditions can we not handle?
if (rpm < rpm_spinning_cutoff ||
engineConfiguration->hpfpCamLobes == 0 ||
engineConfiguration->hpfpPumpVolume == 0 ||
!enginePins.hpfpValve.isInitialized()) {
m_quantity.reset();
m_requested_pump = 0;
m_deadtime = 0;
} else {
// Convert deadtime from ms to degrees based on current RPM
float deadtime_ms = interpolate2d(
Sensor::get(SensorType::BatteryVoltage).value_or(VBAT_FALLBACK_VALUE),
engineConfiguration->hpfpDeadtimeVoltsBins,
engineConfiguration->hpfpDeadtimeMS);
m_deadtime = deadtime_ms * rpm * (360.f / 60.f / 1000.f);
// We set deadtime first, then pump, in case pump used to be 0. Pump is what
// determines whether we do anything or not.
m_requested_pump = m_quantity.pumpAngleFuel(rpm);
if (!m_running) {
m_running = true;
scheduleNextCycle();
}
}
}
void HpfpController::pinTurnOn(HpfpController *self) {
enginePins.hpfpValve.setHigh();
// By scheduling the close after we already open, we don't have to worry if the engine
// stops, the valve will be turned off in a certain amount of time regardless.
scheduleByAngle(&self->m_event.scheduling,
self->m_event.scheduling.momentX,
self->m_deadtime + engineConfiguration->hpfpActivationAngle,
{ pinTurnOff, self });
}
void HpfpController::pinTurnOff(HpfpController *self) {
enginePins.hpfpValve.setLow();
self->scheduleNextCycle();
}
void HpfpController::scheduleNextCycle() {
if (!enginePins.hpfpValve.isInitialized()) {
m_running = false;
return;
}
angle_t lobe = m_lobe.findNextLobe();
angle_t angle_requested = m_requested_pump;
if (angle_requested > engineConfiguration->hpfpMinAngle) {
engine->module<TriggerScheduler>()->scheduleOrQueue(
&m_event, TRIGGER_EVENT_UNDEFINED, 0,
lobe - angle_requested - m_deadtime,
{ pinTurnOn, this });
// Off will be scheduled after turning the valve on
} else {
// Schedule this, even if we aren't opening the valve this time, since this
// will schedule the next lobe.
engine->module<TriggerScheduler>()->scheduleOrQueue(
&m_event, TRIGGER_EVENT_UNDEFINED, 0, lobe,
{ pinTurnOff, this });
}
}
#endif // EFI_HPFP

View File

@ -1,10 +1,96 @@
/*
* @file high_pressure_fuel_pump.h
* @brief High Pressure Fuel Pump controller for GDI applications
*
* @date Nov 5, 2020
* @author Andrey Belomutskiy, (c) 2012-2020
* @date Nov 6, 2021
* @author Scott Smith, (c) 2021
*/
/*
* 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/>.
*/
#pragma once
void initHPFP();
class HpfpLobe {
public:
uint8_t m_lobe_index = 0; ///< 0-based index of the last lobe returned
angle_t findNextLobe(); ///< Calculate the angle (after crank TDC) for the top of the next lobe
};
class HpfpQuantity {
public:
float m_I_sum_percent = 0;
float m_pressureTarget_kPa = 0;
/**
* Calculate where the pump should become active, in degrees before pump lobe TDC
*/
angle_t pumpAngleFuel(int rpm);
/**
* Calculate the percent of the pump stroke needed to replace the fuel injected. Also
* includes pump compensation calculations.
*
* This is used by internal tests and shouldn't be called directly. Instead use
* pumpAngleFuel.
*
* Return value is nominally 0-100, but may be outside that range (including negative) if
* model parameters are not accurate.
*/
float calcFuelPercent(int rpm);
/**
* Calculates the PI controller contribution as a percent. This amount should be added to
* calcFuelPercent() above.
*
* This is used by internal tests and shouldn't be called directly. Instead use
* pumpAngleFuel.
*
* Return value is nominally 0-100, but may be outside that range (including negative) if
* model parameters are not accurate. The sum of this and calc_fuel_percent will be 0-100.
*/
float calcPI(int rpm, float calc_fuel_percent);
/**
* Reset internal state due to a stopped engine.
*/
void reset() {
m_I_sum_percent = 0;
m_pressureTarget_kPa = 0;
}
};
class HpfpController : public EngineModule {
public:
void onFastCallback() final;
#if !EFI_UNIT_TEST
private:
#endif // EFI_UNIT_TEST
AngleBasedEvent m_event;
HpfpQuantity m_quantity;
HpfpLobe m_lobe;
volatile bool m_running = false; ///< Whether events are being scheduled or not
volatile angle_t m_requested_pump = 0; ///< Computed requested pump duration in degrees (not including deadtime)
volatile angle_t m_deadtime = 0; ///< Computed solenoid deadtime in degrees
void scheduleNextCycle();
static void pinTurnOn(HpfpController *self);
static void pinTurnOff(HpfpController *self);
};

View File

@ -8,6 +8,12 @@ bool TriggerScheduler::assertNotInList(AngleBasedEvent *head, AngleBasedEvent *e
}
/**
* Schedules 'action' to occur at engine cycle angle 'angle'.
*
* If you know when a recent trigger occured, you can pass it in as 'trgEventIndex' and
* 'edgeTimestamp'. Otherwise pass in TRIGGER_EVENT_UNDEFINED and the work will be scheduled on
* the next trigger edge.
*
* @return true if event corresponds to current tooth and was time-based scheduler
* false if event was put into queue for scheduling at a later tooth
*/

View File

@ -1495,7 +1495,7 @@ int8_t[MAX_CYLINDER_COUNT iterate] fuelTrim;;"Percent", @@PERCENT_TRIM_BYTE_PACK
uint8_t[HPFP_LOBE_PROFILE_SIZE] autoscale hpfpLobeProfileQuantityBins;;"%", 0.5, 0, 0, 100, 1
uint8_t[HPFP_LOBE_PROFILE_SIZE] autoscale hpfpLobeProfileAngle;;"deg", 0.5, 0, 0, 125, 1
uint8_t[HPFP_DEADTIME_SIZE] hpfpDeadtimeVoltsBins;;"volts", 1, 0, 0, 255, 0
uint16_t[HPFP_DEADTIME_SIZE] autoscale hpfpDeadtimeMS;;"ms", 0.001, 0, 0, 65, 0
uint16_t[HPFP_DEADTIME_SIZE] autoscale hpfpDeadtimeMS;;"ms", 0.001, 0, 0, 65, 3
uint16_t[HPFP_TARGET_SIZE x HPFP_TARGET_SIZE] hpfpTarget;;"kPa", 1, 0, 0, 65000, 0
uint16_t[HPFP_TARGET_SIZE] autoscale hpfpTargetLoadBins;;"load", 0.1, 0, 0, 6500, 1
uint8_t[HPFP_TARGET_SIZE] autoscale hpfpTargetRpmBins;;"RPM", 50, 0, 0, 12500, 0

View File

@ -94,9 +94,9 @@ BinResult getBin(float value, const TBin (&bins)[TSize]) {
return { idx, fraction };
}
template<class TBin, int TSize, int TMult>
BinResult getBin(float value, const scaled_channel<TBin, TMult> (&bins)[TSize]) {
return getBin(value * TMult, *reinterpret_cast<const TBin (*)[TSize]>(&bins));
template<class TBin, int TSize, int TMult, int TDiv>
BinResult getBin(float value, const scaled_channel<TBin, TMult, TDiv> (&bins)[TSize]) {
return getBin(value * (float(TMult) / TDiv), *reinterpret_cast<const TBin (*)[TSize]>(&bins));
}
static float linterp(float low, float high, float frac)

View File

@ -50,6 +50,7 @@
#define EFI_CDM_INTEGRATION FALSE
#define EFI_MC33816 FALSE
#define EFI_HPFP TRUE
#define EFI_BLUETOOTH_SETUP FALSE

View File

@ -74,3 +74,5 @@
#define EFI_MAP_AVERAGING TRUE
#define EFI_LUA TRUE
#define EFI_HPFP TRUE

298
unit_tests/tests/test_hpfp.cpp Executable file
View File

@ -0,0 +1,298 @@
#include "pch.h"
#include "high_pressure_fuel_pump.h"
#include "fuel_computer.h"
using ::testing::_;
using ::testing::StrictMock;
TEST(HPFP, Lobe) {
EngineTestHelper eth(TEST_ENGINE);
engineConfiguration->hpfpCam = HPFP_CAM_NONE;
engineConfiguration->hpfpPeakPos = 123;
engineConfiguration->hpfpCamLobes = 3;
engine->triggerCentral.vvtPosition[0][0] = 20; // Bank 0
engine->triggerCentral.vvtPosition[0][1] = 40;
engine->triggerCentral.vvtPosition[1][0] = 60; // Bank 1
engine->triggerCentral.vvtPosition[1][1] = 80;
HpfpLobe lobe;
// Run through all five CAM modes
for (int cam = 0; cam < 5; cam++) {
static hpfp_cam_e map[5] = { HPFP_CAM_NONE, HPFP_CAM_IN1, HPFP_CAM_EX1,
HPFP_CAM_IN2, HPFP_CAM_EX2};
engineConfiguration->hpfpCam = map[cam];
// Run through several cycles of the engine to make sure we keep wrapping around
for (int i = 0; i < 4; i++) {
EXPECT_EQ(lobe.findNextLobe(), 240 + 123 + cam * 20);
EXPECT_EQ(lobe.findNextLobe(), 480 + 123 + cam * 20);
EXPECT_EQ(lobe.findNextLobe(), 0 + 123 + cam * 20);
}
}
// Can we change the number of lobes?
engineConfiguration->hpfpCam = HPFP_CAM_NONE;
engineConfiguration->hpfpCamLobes = 4;
EXPECT_EQ(lobe.findNextLobe(), 180 + 123);
EXPECT_EQ(lobe.findNextLobe(), 360 + 123);
EXPECT_EQ(lobe.findNextLobe(), 540 + 123);
EXPECT_EQ(lobe.findNextLobe(), 0 + 123);
// Can we change the peak position?
engineConfiguration->hpfpPeakPos = 95;
EXPECT_EQ(lobe.findNextLobe(), 180 + 95);
EXPECT_EQ(lobe.findNextLobe(), 360 + 95);
EXPECT_EQ(lobe.findNextLobe(), 540 + 95);
EXPECT_EQ(lobe.findNextLobe(), 0 + 95);
}
TEST(HPFP, InjectionReplacementFuel) {
EngineTestHelper eth(TEST_ENGINE);
engineConfiguration->specs.cylindersCount = 4;
engineConfiguration->hpfpCamLobes = 4;
engine->injectionMass[0] = 0.05 /* cc/cyl */ * fuelDensity;
engineConfiguration->hpfpPumpVolume = 0.2; // cc/lobe
HpfpQuantity math;
EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 25);
// What if we change the # of lobes?
engineConfiguration->hpfpCamLobes = 3;
EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 25 * 1.333333333f);
// More fuel!
engine->injectionMass[0] = 0.1 /* cc/cyl */ * fuelDensity;
EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 50 * 1.333333333f);
// More cylinders!
engineConfiguration->specs.cylindersCount = 6;
EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 50 * 2.); // Ooops we maxed out
// Compensation testing
engineConfiguration->specs.cylindersCount =
engineConfiguration->hpfpCamLobes; // Make math easier
for (int i = 0; i < HPFP_COMPENSATION_SIZE; i++) {
// one bin every 1000 RPM
engineConfiguration->hpfpCompensationRpmBins[i] = std::min(i * 1000, 8000);
}
for (int i = 0; i < HPFP_COMPENSATION_SIZE; i++) {
// one bin every 0.05 cc/lobe
engineConfiguration->hpfpCompensationLoadBins[i] = std::min(i * 0.05, 60.);
}
engineConfiguration->hpfpCompensation[2][1] = -10;
EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 40); // -10, in cell
EXPECT_FLOAT_EQ(math.calcFuelPercent(1500), 45); // -5, half way
EXPECT_FLOAT_EQ(math.calcFuelPercent(2000), 50); // -0, in next cell
engineConfiguration->hpfpCompensation[2][1] = 20;
EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 70); // +20, in cell
EXPECT_FLOAT_EQ(math.calcFuelPercent(1500), 60); // +10, half way
EXPECT_FLOAT_EQ(math.calcFuelPercent(2000), 50); // +0, in next cell
// 25% more fuel (1.25*50=62.5 base), but half way between comp of 20 and 0 (so comp of 10)
engine->injectionMass[0] = 0.125 /* cc/cyl */ * fuelDensity;
EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 72.5); // +10 comp
EXPECT_FLOAT_EQ(math.calcFuelPercent(1500), 67.5); // +5, half way
EXPECT_FLOAT_EQ(math.calcFuelPercent(2000), 62.5); // +0 base
}
TEST(HPFP, PI) {
EngineTestHelper eth(TEST_ENGINE);
engineConfiguration->specs.cylindersCount = 4;
engineConfiguration->hpfpCamLobes = 4;
engine->injectionMass[0] = 0.05 /* cc/cyl */ * fuelDensity;
engineConfiguration->hpfpPumpVolume = 0.2; // cc/lobe
HpfpQuantity math;
for (int i = 0; i < HPFP_TARGET_SIZE; i++) {
// one bin every 1000 RPM
engineConfiguration->hpfpTargetRpmBins[i] = std::min(i * 1000, 8000);
}
for (int i = 0; i < HPFP_TARGET_SIZE; i++) {
// one bin every 20kPa
engineConfiguration->hpfpTargetLoadBins[i] = std::min(i * 20, 200);
}
for (int r = 0; r < HPFP_TARGET_SIZE; r++) {
for (int c = 0; c < HPFP_TARGET_SIZE; c++) {
engineConfiguration->hpfpTarget[r][c] = 1000 * r + 10 * c;
}
}
Sensor::setMockValue(SensorType::Map, 40);
Sensor::setMockValue(SensorType::FuelPressureHigh, 1000);
EXPECT_FLOAT_EQ(math.calcPI(1000, 120), -20); // Test integral clamp
EXPECT_FLOAT_EQ(math.m_I_sum_percent, -20); // and again
EXPECT_FLOAT_EQ(math.m_pressureTarget_kPa, 2010);
EXPECT_FLOAT_EQ(math.calcPI(1000, -40), 40); // Test integral clamp
EXPECT_FLOAT_EQ(math.m_I_sum_percent, 40); // and again
EXPECT_FLOAT_EQ(math.m_pressureTarget_kPa, 2010);
// Test pressure decay
math.calcPI(4000, 0);
EXPECT_FLOAT_EQ(math.m_pressureTarget_kPa, 2040);
math.calcPI(1000, 0);
EXPECT_FLOAT_EQ(math.m_pressureTarget_kPa, 2040);
engineConfiguration->hpfpTargetDecay = 1000;
math.calcPI(1000, 0);
EXPECT_FLOAT_EQ(math.m_pressureTarget_kPa, 2035); // 5ms of decay
// Proportional gain
math.reset(); // Reset for ease of testing
EXPECT_FLOAT_EQ(math.calcPI(1000, 0), 0); // Validate reset
EXPECT_FLOAT_EQ(math.m_pressureTarget_kPa, 2010);
engineConfiguration->hpfpPidP = 0.01;
EXPECT_FLOAT_EQ(math.calcPI(1000, 0), 10.10);
engineConfiguration->hpfpPidP = 0.02;
EXPECT_FLOAT_EQ(math.calcPI(1000, 0), 20.20);
// Integral gain
engineConfiguration->hpfpPidI = 0.001;
EXPECT_FLOAT_EQ(math.calcPI(1000, 0), 20.368334);
EXPECT_FLOAT_EQ(math.m_I_sum_percent, 0.168333333);
}
TEST(HPFP, Angle) {
EngineTestHelper eth(TEST_ENGINE);
engineConfiguration->specs.cylindersCount = 4;
engineConfiguration->hpfpCamLobes = 4;
engine->injectionMass[0] = 0.05 /* cc/cyl */ * fuelDensity;
engineConfiguration->hpfpPumpVolume = 0.2; // cc/lobe
HpfpQuantity math;
for (int i = 0; i < HPFP_TARGET_SIZE; i++) {
// one bin every 1000 RPM
engineConfiguration->hpfpTargetRpmBins[i] = std::min(i * 1000, 8000);
}
for (int i = 0; i < HPFP_TARGET_SIZE; i++) {
// one bin every 20kPa
engineConfiguration->hpfpTargetLoadBins[i] = std::min(i * 20, 200);
}
for (int r = 0; r < HPFP_TARGET_SIZE; r++) {
for (int c = 0; c < HPFP_TARGET_SIZE; c++) {
engineConfiguration->hpfpTarget[r][c] = 1000 * r + 10 * c;
}
}
for (int i = 0; i < HPFP_LOBE_PROFILE_SIZE; i++) {
engineConfiguration->hpfpLobeProfileQuantityBins[i] = 100. * i / (HPFP_LOBE_PROFILE_SIZE - 1);
engineConfiguration->hpfpLobeProfileAngle[i] = 150. * i / (HPFP_LOBE_PROFILE_SIZE - 1);
}
EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 25); // Double check baseline
EXPECT_FLOAT_EQ(math.calcPI(1000, 10), 0); // Validate no PI
EXPECT_NEAR(math.pumpAngleFuel(1000), 37.5, 0.4); // Given the profile, should be 50% higher
engine->injectionMass[0] = 0.08 /* cc/cyl */ * fuelDensity;
EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 40); // Double check baseline
EXPECT_FLOAT_EQ(math.calcPI(1000, 10), 0); // Validate no PI
EXPECT_NEAR(math.pumpAngleFuel(1000), 60, 0.4); // Given the profile, should be 50% higher
engineConfiguration->hpfpPidP = 0.01;
Sensor::setMockValue(SensorType::Map, 40);
Sensor::setMockValue(SensorType::FuelPressureHigh, 1000);
EXPECT_FLOAT_EQ(math.calcPI(1000, 10), 10.1);
EXPECT_NEAR(math.pumpAngleFuel(1000), 50.1 * 1.5, 0.4); // Given the profile, should be 50% higher
}
TEST(HPFP, Schedule) {
EngineTestHelper eth(TEST_ENGINE);
engineConfiguration->specs.cylindersCount = 4;
engineConfiguration->hpfpCamLobes = 4;
engineConfiguration->hpfpPumpVolume = 0.2; // cc/lobe
for (int i = 0; i < HPFP_TARGET_SIZE; i++) {
// one bin every 1000 RPM
engineConfiguration->hpfpTargetRpmBins[i] = std::min(i * 1000, 8000);
}
for (int i = 0; i < HPFP_TARGET_SIZE; i++) {
// one bin every 20kPa
engineConfiguration->hpfpTargetLoadBins[i] = std::min(i * 20, 200);
}
for (int r = 0; r < HPFP_TARGET_SIZE; r++) {
for (int c = 0; c < HPFP_TARGET_SIZE; c++) {
engineConfiguration->hpfpTarget[r][c] = 1000 * r + 10 * c;
}
}
for (int i = 0; i < HPFP_LOBE_PROFILE_SIZE; i++) {
engineConfiguration->hpfpLobeProfileQuantityBins[i] = 100. * i / (HPFP_LOBE_PROFILE_SIZE - 1);
engineConfiguration->hpfpLobeProfileAngle[i] = 150. * i / (HPFP_LOBE_PROFILE_SIZE - 1);
}
auto & hpfp = *engine->module<HpfpController>();
StrictMock<MockExecutor> mockExec;
engine->executor.setMockExecutor(&mockExec);
engineConfiguration->hpfpActivationAngle = 30;
constexpr angle_t angle0 = 90;
constexpr angle_t angle1 = 270 - 37.6923065;
constexpr angle_t angle2 = angle1 + 30;
constexpr float tick_per_deg = USF2NT(1000000.)*60/360/1000;
constexpr efitick_t nt0 = tick_per_deg * angle0;
constexpr efitick_t nt1 = tick_per_deg * angle1;
constexpr efitick_t nt2 = tick_per_deg * angle2;
{
testing::InSequence is;
// First call to assignRpmValue will cause a dummy call to fast periodic timer.
// Injection Mass will be 0 so expect a no-op.
EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), &hpfp.m_event.scheduling, nt0, action_s(HpfpController::pinTurnOff, &hpfp)));
// Second call will be the start of a real pump event.
EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), &hpfp.m_event.scheduling, nt1, action_s(HpfpController::pinTurnOn, &hpfp)));
// Third call will be off event
EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), &hpfp.m_event.scheduling, nt2, action_s(HpfpController::pinTurnOff, &hpfp)));
}
EXPECT_CALL(mockExec, cancel(_)).Times(2);
// For HPFP to work, events need to be scheduled after the next tooth. This makes sure the
// peak pos occurs after the next tooth.
engineConfiguration->hpfpPeakPos = 90;
// This will call the fast callback routine
engine->rpmCalculator.assignRpmValue(1000);
engine->injectionMass[0] = 0.05 /* cc/cyl */ * fuelDensity;
engineConfiguration->hpfpValvePin = GPIOA_2; // arbitrary
hpfp.onFastCallback();
// First event was scheduled by assignRpmValue with 0 injection mass. So, it's off.
eth.assertTriggerEvent("h0", 0, &hpfp.m_event, (void*)&HpfpController::pinTurnOff,
1, angle0 - 0);
// Make the previous event happen, schedule the next.
engine->module<TriggerScheduler>()->scheduleEventsUntilNextTriggerTooth(
1000, 1, tick_per_deg * 0);
// Mock executor doesn't run events, so we run it manually
HpfpController::pinTurnOff(&hpfp);
// Now we should have a regular on/off event.
eth.assertTriggerEvent("h1", 0, &hpfp.m_event, (void*)&HpfpController::pinTurnOn,
2, angle1 - 180);
// Make it happen
engine->module<TriggerScheduler>()->scheduleEventsUntilNextTriggerTooth(
1000, 2, tick_per_deg * 180);
// Since we have a mock scheduler, lets insert the correct timestamp in the scheduling
// struct.
hpfp.m_event.scheduling.momentX = nt1;
HpfpController::pinTurnOn(&hpfp);
// The off event goes directly to scheduleByAngle and is tested by the last EXPECT_CALL
// above.
}

View File

@ -80,6 +80,7 @@ TESTS_SRC_CPP = \
tests/test_stft.cpp \
tests/test_boost.cpp \
tests/test_gppwm.cpp \
tests/test_hpfp.cpp \
tests/test_fuel_math.cpp \
tests/test_binary_log.cpp \
tests/test_dynoview.cpp \