rusefi/firmware/controllers/engine_cycle/high_pressure_fuel_pump.cpp

211 lines
7.5 KiB
C++
Raw Normal View History

2020-11-05 13:34:25 -08:00
/*
* @file high_pressure_fuel_pump.cpp
* @brief High Pressure Fuel Pump controller for GDI applications
2020-11-05 13:34:25 -08:00
*
* @date Nov 6, 2021
* @author Scott Smith, (c) 2021
*/
/*
* This file is part of rusEfi - see http://rusefi.com
2020-11-05 13:34:25 -08:00
*
* 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.
2020-11-05 13:34:25 -08:00
*
* 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/>.
2020-11-05 13:34:25 -08:00
*/
#include "pch.h"
2020-11-05 13:34:25 -08:00
#include "high_pressure_fuel_pump.h"
2020-11-09 19:21:38 -08:00
#include "spark_logic.h"
#include "fuel_computer.h"
2020-11-09 19:21:38 -08:00
2020-11-09 19:53:23 -08:00
#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
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;
// Clamp the output so that calc_fuel_percent+i_control_percent is within 0% to 100%
// That way the I term can override any fuel calculations over the long term.
// The P term is still allowed to drive the total output over 100% or under 0% to react to
// short term errors.
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;
}
2021-12-31 12:28:24 -08:00
angle_t HpfpQuantity::pumpAngleFuel(int rpm, HpfpController *model) {
// Math based on fuel requested
2021-12-31 12:28:24 -08:00
model->fuel_requested_percent = calcFuelPercent(rpm);
2021-12-31 12:28:24 -08:00
model->fuel_requested_percent_pi = calcPI(rpm, model->fuel_requested_percent);
// Apply PI control
2021-12-31 12:28:24 -08:00
float fuel_requested_percentTotal = model->fuel_requested_percent + model->fuel_requested_percent_pi;
// Convert to degrees
2021-12-31 12:28:24 -08:00
return interpolate2d(fuel_requested_percentTotal,
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.
2021-12-31 12:28:24 -08:00
m_requested_pump = m_quantity.pumpAngleFuel(rpm, this);
if (!m_running) {
m_running = true;
scheduleNextCycle();
}
}
2021-12-30 16:14:49 -08:00
engine->outputChannels.m_requested_pump = m_requested_pump;
2022-01-01 14:17:56 -08:00
engine->outputChannels.fuel_requested_percent = fuel_requested_percent;
engine->outputChannels.fuel_requested_percent_pi = fuel_requested_percent_pi;
engine->outputChannels.m_I_sum_percent = m_quantity.m_I_sum_percent;
engine->outputChannels.m_pressureTarget_kPa = m_quantity.m_pressureTarget_kPa;
}
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() {
2022-01-17 21:27:20 -08:00
noValve = !enginePins.hpfpValve.isInitialized();
if (noValve) {
m_running = false;
return;
}
angle_t lobe = m_lobe.findNextLobe();
angle_t angle_requested = m_requested_pump;
2022-01-17 21:27:20 -08:00
angleAboveMin = angle_requested > engineConfiguration->hpfpMinAngle;
if (angleAboveMin) {
nextStart = lobe - angle_requested - m_deadtime;
engine->outputChannels.di_nextStart = nextStart;
engine->module<TriggerScheduler>()->scheduleOrQueue(
&m_event, TRIGGER_EVENT_UNDEFINED, 0,
2022-01-17 21:27:20 -08:00
nextStart,
{ pinTurnOn, this });
2020-11-05 13:34:25 -08:00
// 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 });
}
2020-11-05 13:42:56 -08:00
}
2020-11-09 19:53:23 -08:00
#endif // EFI_HPFP