rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
high_pressure_fuel_pump.cpp
Go to the documentation of this file.
1/*
2 * @file high_pressure_fuel_pump.cpp
3 * @brief High Pressure Fuel Pump controller for GDI applications
4 *
5 * TL,DR: we have constant displacement mechanical pump driven by camshaft
6 * here we control desired fuel high pressure by controlling relief/strain/spill valve electronically
7 *
8 * @date Nov 6, 2021
9 * @author Scott Smith, (c) 2021
10 */
11
12/*
13 * This file is part of rusEfi - see http://rusefi.com
14 *
15 * rusEfi is free software; you can redistribute it and/or modify it under the terms of
16 * the GNU General Public License as published by the Free Software Foundation; either
17 * version 3 of the License, or (at your option) any later version.
18 *
19 * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
20 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License along with this program.
24 * If not, see <http://www.gnu.org/licenses/>.
25 */
26
27#include "pch.h"
28
30#include "spark_logic.h"
31#include "fuel_computer.h"
32
33#if EFI_HPFP
34#if !EFI_SHAFT_POSITION_INPUT
35 fail("EFI_SHAFT_POSITION_INPUT required to have EFI_EMULATE_POSITION_SENSORS")
36#endif
37
38// A constant we use; doesn't seem important to hoist into engineConfiguration.
39static constexpr int rpm_spinning_cutoff = 60; // Below this RPM, we don't run the logic
40
42 // TODO: Ideally we figure out where we are in the engine cycle and pick the next lobe
43 // based on that. At least we should do that when cranking, so we can start that much
44 // sooner.
45
47 if (!lobes) {
48 return 0;
49 }
50
51 // Which lobe are we on?
52 int next_index = m_lobe_index + 1;
53 // Note, this will be insufficient if the # of cam lobes is
54 // dynamically changed rapidly by more than 2x, but it will
55 // correct itself rather quickly.
56 if (next_index >= lobes) {
57 next_index -= lobes;
58 }
59 m_lobe_index = next_index;
60
61 // Calculate impact of VVT
62 angle_t vvt = 0;
63 if (engineConfiguration->hpfpCam != HPFP_CAM_NONE) {
64 // pump operates in cam-angle domain which is different speed from crank-angle domain on 4 stroke engines
65 int mult = (int)getEngineCycle(getEngineRotationState()->getOperationMode()) / 360;
66 int camIndex = engineConfiguration->hpfpCam - 1;
67 // TODO: Is the sign correct here? + means ATDC?
69 BANK_BY_INDEX(camIndex),
70 CAM_BY_INDEX(camIndex)) / mult;
71 }
72
73 // heh, looks like we assume 4 stroke here?
74 return engineConfiguration->hpfpPeakPos + vvt + next_index * 720 / lobes;
75}
76
77// As a percent of the full pump stroke
79 float fuel_requested_cc_per_cycle =
81 float fuel_requested_cc_per_lobe = fuel_requested_cc_per_cycle / engineConfiguration->hpfpCamLobes;
82 return 100.f *
83 fuel_requested_cc_per_lobe / engineConfiguration->hpfpPumpVolume +
84 interpolate3d(config->hpfpCompensation,
85 config->hpfpCompensationLoadBins, fuel_requested_cc_per_lobe,
87}
88
89static float getLoad() {
91 // TODO: allow other load axis, like we claim to
92 case engine_load_mode_e::LM_ALPHA_N:
94 default:
96 }
97}
98
99float HpfpQuantity::calcPI(float rpm, float calc_fuel_percent, HpfpController *model) {
100 float load = getLoad();
101
102 float possibleValue = model->m_pressureTarget_kPa - (engineConfiguration->hpfpTargetDecay *
103 (FAST_CALLBACK_PERIOD_MS / 1000.));
104
105 model->m_pressureTarget_kPa = std::max<float>(possibleValue,
106 interpolate3d(config->hpfpTarget,
109
110 auto fuelPressure = Sensor::get(SensorType::FuelPressureHigh);
111 if (!fuelPressure) {
112 return 0;
113 }
114
115 float pressureError_kPa =
116 model->m_pressureTarget_kPa - fuelPressure.Value;
117
118 model->hpfp_p_control_percent = pressureError_kPa * engineConfiguration->hpfpPidP;
119 float i_factor_divisor =
120 1000. * // ms/sec
121 60. * // sec/min -> ms/min
122 2.; // rev/cycle -> (rev * ms) / (min * cycle)
123 float i_factor =
124 engineConfiguration->hpfpPidI * // % / (kPa * lobe)
125 rpm * // (% * revs) / (kPa * lobe * min)
126 engineConfiguration->hpfpCamLobes * // lobes/cycle -> (% * revs) / (kPa * min * cycles)
127 (FAST_CALLBACK_PERIOD_MS / // (% * revs * ms) / (kPa * min * cycles)
128 i_factor_divisor); // % / kPa
129 float unclamped_i_control_percent = model->hpfp_i_control_percent + pressureError_kPa * i_factor;
130 // Clamp the output so that calc_fuel_percent+i_control_percent is within 0% to 100%
131 // That way the I term can override any fuel calculations over the long term.
132 // The P term is still allowed to drive the total output over 100% or under 0% to react to
133 // short term errors.
134 model->hpfp_i_control_percent = clampF(-calc_fuel_percent, unclamped_i_control_percent,
135 100.f - calc_fuel_percent);
136 return model->hpfp_p_control_percent + model->hpfp_i_control_percent;
137}
138
140 // Math based on fuel requested
142
143 model->fuel_requested_percent_pi = calcPI(rpm, model->fuel_requested_percent, model);
144 // Apply PI control
145 float fuel_requested_percentTotal = model->fuel_requested_percent + model->fuel_requested_percent_pi;
146
147 // Convert to degrees
148 return interpolate2d(fuel_requested_percentTotal,
151}
152
154 // Pressure current/target calculation
156
157 isHpfpActive = !(rpm < rpm_spinning_cutoff ||
158 !isGdiEngine() ||
161 // What conditions can we not handle?
162 if (!isHpfpActive) {
165 m_deadangle = 0;
166 } else {
167#if EFI_PROD_CODE && EFI_SHAFT_POSITION_INPUT
168 criticalAssertVoid(engine->triggerCentral.triggerShape.getSize() > engineConfiguration->hpfpCamLobes * 6, "Too few trigger tooth for this number of HPFP lobes");
169#endif // EFI_PROD_CODE
170 // Convert deadtime from ms to degrees based on current RPM
171 float deadtime_ms = interpolate2d(
172 Sensor::get(SensorType::BatteryVoltage).value_or(VBAT_FALLBACK_VALUE),
175 m_deadangle = deadtime_ms * rpm * (360.f / 60.f / 1000.f);
176
177 // We set deadtime first, then pump, in case pump used to be 0. Pump is what
178 // determines whether we do anything or not.
180
181 if (!m_running) {
182 m_running = true;
184 }
185 }
186}
187
188#define HPFP_CONTROLLER "hpfp"
189
191 enginePins.hpfpValve.setHigh(HPFP_CONTROLLER);
192 self->HpfpValveState = true;
194
195 // By scheduling the close after we already open, we don't have to worry if the engine
196 // stops, the valve will be turned off in a certain amount of time regardless.
197 //
198 // We do not need precise control of valve _duration_ since
199 // For the solenoid type pump, the pump has a certain volume. You can activate the solenoid to request that the pump start pressurizing.
200 // Once it reaches a certain pressure, it is effectively self running and won't unlatch until the pump reaches the top.
201 // Since the solenoid latches itself, you don't have to keep it activated for the whole lobe. You just need to activate it until it latches and then let it do the rest of the work.
202 // see also https://rusefi.com/forum/viewtopic.php?f=5&t=2192
203
207 action_s::make<pinTurnOff>( self ));
208}
209
211 enginePins.hpfpValve.setLow(HPFP_CONTROLLER);
212 self->HpfpValveState = false;
214
215 self->scheduleNextCycle();
216}
217
220 if (noValve) {
221 m_running = false;
222 return;
223 }
224
225 angle_t lobeAngle = m_lobe.findNextLobe();
226 //TODO: integrate livedata into HpfpLobe
228 angle_t angle_requested = m_requested_pump;
229
231 if (angleAboveMin) {
232 // TODO: some manuals suggest also substracting peak time (converted to angle)
233 di_nextStart = lobeAngle - angle_requested - m_deadangle;
235
236
237 /**
238 * We are good to use just one m_event instance because new events are scheduled when we turn off valve.
239 */
240 engine->module<TriggerScheduler>()->schedule(
241 "hpfp",
242 &m_event,
244 action_s::make<pinTurnOn>( this ));
245
246 // Off will be scheduled after turning the valve on
247 } else {
248 wrapAngle(lobeAngle, "lobe", ObdCode::CUSTOM_ERR_6557);
249 // Schedule this, even if we aren't opening the valve this time, since this
250 // will schedule the next lobe.
251 // todo: schedule only 'scheduleNextCycle' directly since we do not need output control part of the logic here!
252 engine->module<TriggerScheduler>()->schedule(
253 HPFP_CONTROLLER,
254 &m_event, lobeAngle,
255 action_s::make<pinTurnOff>( this ));
256 }
257}
258
259#endif // EFI_HPFP
260
262#if EFI_PROD_CODE
264#else
266#endif
267}
TriggerCentral triggerCentral
Definition engine.h:318
EngineState engineState
Definition engine.h:344
constexpr auto & module()
Definition engine.h:200
RegisteredNamedOutputPin hpfpValve
Definition efi_gpio.h:80
virtual operation_mode_e getOperationMode() const =0
AngleBasedEvent m_event
volatile bool m_running
Whether events are being scheduled or not.
static void pinTurnOff(HpfpController *self)
static void pinTurnOn(HpfpController *self)
angle_t m_deadangle
Computed solenoid deadtime in degrees.
uint8_t m_lobe_index
0-based index of the last lobe returned
angle_t findNextLobe()
Calculate the angle (after crank TDC) for the top of the next lobe.
float calcPI(float rpm, float calc_fuel_percent, HpfpController *model)
float calcFuelPercent(float rpm)
angle_t pumpAngleFuel(float rpm, HpfpController *model)
virtual void setLow(const char *msg)
Definition efi_gpio.cpp:464
virtual void setHigh(const char *msg)
Definition efi_gpio.cpp:442
bool isInitialized() const
Definition efi_gpio.cpp:559
virtual SensorResult get() const =0
static float getOrZero(SensorType type)
Definition sensor.h:83
angle_t getVVTPosition(uint8_t bankIndex, uint8_t camIndex)
TriggerWaveform triggerShape
expected< float > getCurrentEnginePhase(efitick_t nowNt) const
size_t getSize() const
EnginePins enginePins
Definition efi_gpio.cpp:24
efitick_t getTimeNowNt()
Definition efitime.cpp:19
TriggerCentral * getTriggerCentral()
Definition engine.cpp:590
EngineRotationState * getEngineRotationState()
Definition engine.cpp:573
static EngineAccessor engine
Definition engine.h:413
static constexpr persistent_config_s * config
static constexpr engine_configuration_s * engineConfiguration
constexpr float fuelDensity
fail("EFI_SHAFT_POSITION_INPUT required to have EFI_EMULATE_POSITION_SENSORS") static const expr int rpm_spinning_cutoff
bool isGdiEngine()
static float getLoad()
bool isGdiEngine()
@ CUSTOM_ERR_6557
efitick_t scheduleByAngle(scheduling_s *timer, efitick_t nowNt, angle_t angle, action_s const &action)
@ DriverThrottleIntent
scheduling_s eventScheduling
float injectionMass[MAX_CYLINDER_COUNT]
scaled_channel< uint8_t, 2, 1 > hpfpLobeProfileQuantityBins[HPFP_LOBE_PROFILE_SIZE]
scaled_channel< uint16_t, 1000, 1 > hpfpCompensationLoadBins[HPFP_COMPENSATION_SIZE]
scaled_channel< uint8_t, 1, 50 > hpfpTargetRpmBins[HPFP_TARGET_SIZE]
scaled_channel< uint16_t, 1000, 1 > hpfpDeadtimeMS[HPFP_DEADTIME_SIZE]
int8_t hpfpCompensation[HPFP_COMPENSATION_SIZE][HPFP_COMPENSATION_SIZE]
uint16_t hpfpTarget[HPFP_TARGET_SIZE][HPFP_TARGET_SIZE]
scaled_channel< uint8_t, 1, 50 > hpfpCompensationRpmBins[HPFP_COMPENSATION_SIZE]
scaled_channel< uint16_t, 10, 1 > hpfpTargetLoadBins[HPFP_TARGET_SIZE]
scaled_channel< uint8_t, 2, 1 > hpfpLobeProfileAngle[HPFP_LOBE_PROFILE_SIZE]
efitick_t getMomentNt() const
Definition scheduler.h:270
void wrapAngle(angle_t &angle, const char *msg, ObdCode code)
angle_t getEngineCycle(operation_mode_e operationMode)