2020-11-05 13:34:25 -08:00
/*
* @ file high_pressure_fuel_pump . cpp
2021-11-19 20:06:51 -08:00
* @ brief High Pressure Fuel Pump controller for GDI applications
2020-11-05 13:34:25 -08:00
*
2022-08-25 16:58:15 -07:00
* TL , DR : we have constant displacement mechanical pump driven by camshaft
* here we control desired fuel high pressure by controlling relief / strain / spill valve electronically
*
2021-11-19 20:06:51 -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
*
2021-11-19 20:06:51 -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
*
2021-11-19 20:06:51 -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
*/
2021-08-03 19:05:01 -07: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"
2021-11-19 20:06:51 -08:00
# include "fuel_computer.h"
2020-11-09 19:21:38 -08:00
2020-11-09 19:53:23 -08:00
# if EFI_HPFP
2023-06-22 12:45:55 -07:00
# if !EFI_SHAFT_POSITION_INPUT
fail ( " EFI_SHAFT_POSITION_INPUT required to have EFI_EMULATE_POSITION_SENSORS " )
# endif
2020-11-09 19:53:23 -08:00
2021-11-19 20:06:51 -08:00
// 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 =
2023-03-27 00:58:18 -07:00
engine - > engineState . injectionMass [ 0 ] * ( 1.f / fuelDensity ) * engineConfiguration - > cylindersCount ;
2021-11-19 20:06:51 -08:00
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 ) ;
}
2023-03-05 20:32:04 -08:00
static float getLoad ( ) {
switch ( engineConfiguration - > fuelAlgorithm ) {
// TODO: allow other load axis, like we claim to
case LM_ALPHA_N :
return Sensor : : getOrZero ( SensorType : : DriverThrottleIntent ) ;
default :
return Sensor : : getOrZero ( SensorType : : Map ) ;
}
}
2021-11-19 20:06:51 -08:00
float HpfpQuantity : : calcPI ( int rpm , float calc_fuel_percent ) {
2023-03-05 20:32:04 -08:00
float load = getLoad ( ) ;
float possibleValue = m_pressureTarget_kPa - ( engineConfiguration - > hpfpTargetDecay *
( FAST_CALLBACK_PERIOD_MS / 1000. ) ) ;
m_pressureTarget_kPa = std : : max < float > ( possibleValue ,
2021-11-19 20:06:51 -08:00
interpolate3d ( engineConfiguration - > hpfpTarget ,
2023-03-05 20:32:04 -08:00
engineConfiguration - > hpfpTargetLoadBins , load ,
2021-11-19 20:06:51 -08:00
engineConfiguration - > hpfpTargetRpmBins , rpm ) ) ;
2022-07-28 00:04:28 -07:00
auto fuelPressure = Sensor : : get ( SensorType : : FuelPressureHigh ) ;
if ( ! fuelPressure ) {
return 0 ;
}
2021-11-19 20:06:51 -08:00
float pressureError_kPa =
2022-07-28 00:04:28 -07:00
m_pressureTarget_kPa - fuelPressure . Value ;
2021-11-19 20:06:51 -08:00
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 ;
2022-01-02 15:45:31 -08:00
// 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.
2021-11-19 20:06:51 -08:00
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 ) {
2021-11-19 20:06:51 -08:00
// Math based on fuel requested
2021-12-31 12:28:24 -08:00
model - > fuel_requested_percent = calcFuelPercent ( rpm ) ;
2021-11-19 20:06:51 -08:00
2021-12-31 12:28:24 -08:00
model - > fuel_requested_percent_pi = calcPI ( rpm , model - > fuel_requested_percent ) ;
2023-03-05 20:32:04 -08:00
// todo: streamline this logging field see 'calcPI' comment
model - > m_pressureTarget_kPa = m_pressureTarget_kPa ;
2021-11-19 20:06:51 -08:00
// Apply PI control
2021-12-31 12:28:24 -08:00
float fuel_requested_percentTotal = model - > fuel_requested_percent + model - > fuel_requested_percent_pi ;
2021-11-19 20:06:51 -08:00
// Convert to degrees
2021-12-31 12:28:24 -08:00
return interpolate2d ( fuel_requested_percentTotal ,
2021-11-19 20:06:51 -08:00
engineConfiguration - > hpfpLobeProfileQuantityBins ,
engineConfiguration - > hpfpLobeProfileAngle ) ;
}
void HpfpController : : onFastCallback ( ) {
// Pressure current/target calculation
2022-01-20 19:04:45 -08:00
int rpm = Sensor : : getOrZero ( SensorType : : Rpm ) ;
2021-11-19 20:06:51 -08:00
2022-01-18 17:57:06 -08:00
isHpfpInactive = rpm < rpm_spinning_cutoff | |
2023-07-20 12:40:10 -07:00
! isGdiEngine ( ) | |
2022-01-18 07:16:47 -08:00
engineConfiguration - > hpfpPumpVolume = = 0 | |
! enginePins . hpfpValve . isInitialized ( ) ;
2021-11-19 20:06:51 -08:00
// What conditions can we not handle?
2022-01-18 17:57:06 -08:00
if ( isHpfpInactive ) {
2021-11-19 20:06:51 -08:00
m_quantity . reset ( ) ;
m_requested_pump = 0 ;
m_deadtime = 0 ;
} else {
2022-12-17 15:26:38 -08:00
# if EFI_PROD_CODE && EFI_SHAFT_POSITION_INPUT
2023-09-05 18:28:39 -07:00
criticalAssertVoid ( engine - > triggerCentral . triggerShape . getSize ( ) > engineConfiguration - > hpfpCamLobes * 6 , " Too few trigger tooth for this number of HPFP lobes " ) ;
2022-01-18 18:20:56 -08:00
# endif // EFI_PROD_CODE
2021-11-19 20:06:51 -08:00
// 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 ) ;
2021-11-19 20:06:51 -08:00
if ( ! m_running ) {
m_running = true ;
scheduleNextCycle ( ) ;
}
}
}
2023-06-15 08:46:28 -07:00
# define HPFP_CONTROLLER "hpfp"
2021-11-19 20:06:51 -08:00
void HpfpController : : pinTurnOn ( HpfpController * self ) {
2023-06-15 08:46:28 -07:00
enginePins . hpfpValve . setHigh ( HPFP_CONTROLLER ) ;
2021-11-19 20:06:51 -08:00
// 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 ) {
2023-06-15 08:46:28 -07:00
enginePins . hpfpValve . setLow ( HPFP_CONTROLLER ) ;
2021-11-19 20:06:51 -08:00
self - > scheduleNextCycle ( ) ;
}
void HpfpController : : scheduleNextCycle ( ) {
2022-01-17 21:27:20 -08:00
noValve = ! enginePins . hpfpValve . isInitialized ( ) ;
if ( noValve ) {
2021-11-19 20:06:51 -08:00
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 ) {
2022-06-29 00:13:35 -07:00
di_nextStart = lobe - angle_requested - m_deadtime ;
2023-10-05 20:37:58 -07:00
wrapAngle ( di_nextStart , " di_nextStart " , ObdCode : : CUSTOM_ERR_6557 ) ;
2022-01-17 21:27:20 -08:00
2022-01-18 07:16:47 -08:00
/**
* We are good to use just one m_event instance because new events are scheduled when we turn off valve .
*/
2022-11-28 05:55:38 -08:00
engine - > module < TriggerScheduler > ( ) - > schedule (
2023-10-05 19:58:18 -07:00
" hpfp " ,
2022-11-28 05:55:38 -08:00
& m_event ,
2022-06-29 00:13:35 -07:00
di_nextStart ,
2021-11-19 20:06:51 -08:00
{ pinTurnOn , this } ) ;
2020-11-05 13:34:25 -08:00
2021-11-19 20:06:51 -08:00
// Off will be scheduled after turning the valve on
} else {
2023-10-05 20:37:58 -07:00
wrapAngle ( lobe , " lobe " , ObdCode : : CUSTOM_ERR_6557 ) ;
2021-11-19 20:06:51 -08:00
// Schedule this, even if we aren't opening the valve this time, since this
// will schedule the next lobe.
2022-01-18 07:16:47 -08:00
// todo: would it have been cleaner to schedule 'scheduleNextCycle' directly?
2022-11-28 05:55:38 -08:00
engine - > module < TriggerScheduler > ( ) - > schedule (
2023-10-05 19:58:18 -07:00
" hpfp " ,
2022-11-28 05:55:38 -08:00
& m_event , lobe ,
2021-11-19 20:06:51 -08:00
{ pinTurnOff , this } ) ;
}
2020-11-05 13:42:56 -08:00
}
2020-11-09 19:53:23 -08:00
# endif // EFI_HPFP
2023-07-20 21:53:13 -07:00
bool isGdiEngine ( ) {
2023-08-29 13:42:30 -07:00
# if EFI_PROD_CODE
return enginePins . hpfpValve . isInitialized ( ) ;
# else
2023-07-20 21:53:13 -07:00
return engineConfiguration - > hpfpCamLobes > 0 ;
2023-08-29 13:42:30 -07:00
# endif
2023-07-20 21:53:13 -07:00
}