Line | Branch | Decision | Exec | Source |
---|---|---|---|---|
1 | #include "pch.h" | |||
2 | ||||
3 | #include "injector_model.h" | |||
4 | #include "fuel_computer.h" | |||
5 | ||||
6 | 1130 | void InjectorModelBase::prepare() { | ||
7 | 1130 | float flowRatio = getInjectorFlowRatio(); | ||
8 | ||||
9 | // "large pulse" flow rate | |||
10 | 1130 | m_massFlowRate = flowRatio * getBaseFlowRate(); | ||
11 | 1130 | m_deadtime = getDeadtime(); | ||
12 | ||||
13 |
2/2✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1129 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 1129 times.
|
1130 | if (getNonlinearMode() == INJ_FordModel) { |
14 | 1 | m_smallPulseFlowRate = flowRatio * getSmallPulseFlowRate(); | ||
15 | 1 | m_smallPulseBreakPoint = getSmallPulseBreakPoint(); | ||
16 | ||||
17 | // amount added to small pulses to correct for the "kink" from low flow region | |||
18 | 1 | m_smallPulseOffset = 1000 * ((m_smallPulseBreakPoint / m_massFlowRate) - (m_smallPulseBreakPoint / m_smallPulseFlowRate)); | ||
19 | } | |||
20 | 1130 | } | ||
21 | ||||
22 | 1123 | constexpr float convertToGramsPerSecond(float ccPerMinute) { | ||
23 | 1123 | return ccPerMinute * (fuelDensity / 60.f); | ||
24 | } | |||
25 | ||||
26 | // returns: grams per second flow | |||
27 | 1126 | float InjectorModelWithConfig::getBaseFlowRate() const { | ||
28 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1123 times.
|
2/2✓ Decision 'true' taken 3 times.
✓ Decision 'false' taken 1123 times.
|
1126 | if (engineConfiguration->injectorFlowAsMassFlow) { |
29 | 3 | return m_cfg->flow; | ||
30 | } else { | |||
31 | 1123 | return convertToGramsPerSecond(m_cfg->flow); | ||
32 | } | |||
33 | } | |||
34 | ||||
35 | ✗ | float InjectorModelPrimary::getSmallPulseFlowRate() const { | ||
36 | ✗ | return engineConfiguration->fordInjectorSmallPulseSlope; | ||
37 | } | |||
38 | ||||
39 | ✗ | float InjectorModelPrimary::getSmallPulseBreakPoint() const { | ||
40 | // convert milligrams -> grams | |||
41 | // todo: make UI deal with scaling?! | |||
42 | ✗ | return 0.001f * engineConfiguration->fordInjectorSmallPulseBreakPoint; | ||
43 | } | |||
44 | ||||
45 | 2811 | InjectorNonlinearMode InjectorModelPrimary::getNonlinearMode() const { | ||
46 | 2811 | return engineConfiguration->injectorNonlinearMode; | ||
47 | } | |||
48 | ||||
49 | 2857 | injector_compensation_mode_e InjectorModelPrimary::getInjectorCompensationMode() const { | ||
50 | 2857 | return engineConfiguration->injectorCompensationMode; | ||
51 | } | |||
52 | ||||
53 | 43 | float InjectorModelPrimary::getFuelReferencePressure() const { | ||
54 | 43 | return engineConfiguration->fuelReferencePressure; | ||
55 | } | |||
56 | ||||
57 | ✗ | float InjectorModelSecondary::getSmallPulseFlowRate() const { | ||
58 | // not supported on second bank | |||
59 | ✗ | return 0; | ||
60 | } | |||
61 | ||||
62 | ✗ | float InjectorModelSecondary::getSmallPulseBreakPoint() const { | ||
63 | // not supported on second bank | |||
64 | ✗ | return 0; | ||
65 | } | |||
66 | ||||
67 | 10 | injector_compensation_mode_e InjectorModelSecondary::getInjectorCompensationMode() const { | ||
68 | 10 | return engineConfiguration->secondaryInjectorCompensationMode; | ||
69 | } | |||
70 | ||||
71 | 1 | float InjectorModelSecondary::getFuelReferencePressure() const { | ||
72 | 1 | return engineConfiguration->secondaryInjectorFuelReferencePressure; | ||
73 | } | |||
74 | ||||
75 | 2 | InjectorNonlinearMode InjectorModelSecondary::getNonlinearMode() const { | ||
76 | // nonlinear not supported on second bank | |||
77 | 2 | return InjectorNonlinearMode::INJ_None; | ||
78 | } | |||
79 | ||||
80 | ✗ | void InjectorModelWithConfig::updateState() { | ||
81 | ✗ | pressureCorrectionReference = getFuelDifferentialPressure().Value; | ||
82 | ✗ | } | ||
83 | ||||
84 | ✗ | expected<float> InjectorModelWithConfig::getFuelPressure() const { | ||
85 | ✗ | return getFuelDifferentialPressure().Value + Sensor::get(SensorType::Map).value_or(STD_ATMOSPHERE); | ||
86 | } | |||
87 | ||||
88 | 44 | expected<float> InjectorModelWithConfig::getFuelDifferentialPressure() const { | ||
89 |
1/1✓ Branch 2 taken 44 times.
|
44 | auto map = Sensor::get(SensorType::Map); | |
90 |
1/1✓ Branch 2 taken 44 times.
|
44 | auto baro = Sensor::get(SensorType::BarometricPressure); | |
91 | ||||
92 | 44 | float baroKpa = baro.Value; | ||
93 | // todo: extract baro sensor validation logic | |||
94 |
6/8✓ Branch 1 taken 20 times.
✓ Branch 2 taken 24 times.
✓ Branch 3 taken 20 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 20 times.
✓ Branch 7 taken 24 times.
✓ Branch 8 taken 20 times.
|
2/2✓ Decision 'true' taken 24 times.
✓ Decision 'false' taken 20 times.
|
44 | if (!baro || baro.Value > 120 || baro.Value < 50) { |
95 | 24 | baroKpa = STD_ATMOSPHERE; | ||
96 | } | |||
97 | ||||
98 |
4/4✓ Branch 1 taken 44 times.
✓ Branch 3 taken 20 times.
✓ Branch 4 taken 20 times.
✓ Branch 5 taken 4 times.
|
44 | switch (getInjectorCompensationMode()) { | |
99 |
1/1✓ Decision 'true' taken 20 times.
|
20 | case ICM_FixedRailPressure: | |
100 | // Add barometric pressure, as "fixed" really means "fixed pressure above atmosphere" | |||
101 |
1/1✓ Branch 1 taken 20 times.
|
20 | return getFuelReferencePressure() | |
102 | 20 | + baroKpa | ||
103 | 40 | - map.value_or(STD_ATMOSPHERE); | ||
104 |
1/1✓ Decision 'true' taken 20 times.
|
20 | case ICM_SensedRailPressure: { | |
105 |
3/3✓ Branch 1 taken 20 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 19 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 19 times.
|
20 | if (!Sensor::hasSensor(SensorType::FuelPressureInjector)) { |
106 |
1/1✓ Branch 1 taken 1 time.
|
1 | warning(ObdCode::OBD_Fuel_Pressure_Sensor_Missing, "Fuel pressure compensation is set to use a pressure sensor, but none is configured."); | |
107 | 1 | return unexpected; | ||
108 | } | |||
109 | ||||
110 |
1/1✓ Branch 2 taken 19 times.
|
19 | auto fps = Sensor::get(SensorType::FuelPressureInjector); | |
111 | ||||
112 | // TODO: what happens when the sensor fails? | |||
113 |
2/2✓ Branch 1 taken 1 time.
✓ Branch 2 taken 18 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 18 times.
|
19 | if (!fps) { |
114 | 1 | return unexpected; | ||
115 | } | |||
116 | ||||
117 |
3/3✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 8 times.
|
18 | switch (engineConfiguration->fuelPressureSensorMode) { | |
118 |
1/1✓ Decision 'true' taken 5 times.
|
5 | case FPM_Differential: | |
119 | // This sensor directly measures delta-P, no math needed! | |||
120 | 5 | return fps.Value; | ||
121 |
1/1✓ Decision 'true' taken 5 times.
|
5 | case FPM_Gauge: | |
122 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 5 times.
|
5 | if (!map) { |
123 | ✗ | return unexpected; | ||
124 | } | |||
125 | ||||
126 | 5 | return fps.Value + baroKpa - map.Value; | ||
127 |
1/1✓ Decision 'true' taken 8 times.
|
8 | case FPM_Absolute: | |
128 | default: | |||
129 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 8 times.
|
8 | if (!map) { |
130 | ✗ | return unexpected; | ||
131 | } | |||
132 | ||||
133 | 8 | return fps.Value - map.Value; | ||
134 | } | |||
135 |
1/1✓ Decision 'true' taken 4 times.
|
4 | } default: return unexpected; | |
136 | } | |||
137 | } | |||
138 | ||||
139 | 1136 | float InjectorModelWithConfig::getInjectorFlowRatio() { | ||
140 | // Compensation disabled, use reference flow. | |||
141 |
1/1✓ Branch 1 taken 1136 times.
|
1136 | auto compensationMode = getInjectorCompensationMode(); | |
142 |
4/4✓ Branch 0 taken 26 times.
✓ Branch 1 taken 1110 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 24 times.
|
2/2✓ Decision 'true' taken 1112 times.
✓ Decision 'false' taken 24 times.
|
1136 | if (compensationMode == ICM_None || compensationMode == ICM_HPFP_Manual_Compensation) { |
143 | 1112 | return 1.0f; | ||
144 | } | |||
145 | ||||
146 |
1/1✓ Branch 1 taken 24 times.
|
24 | const float referencePressure = getFuelReferencePressure(); | |
147 | ||||
148 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 24 times.
|
24 | if (referencePressure < 50) { |
149 | // impossibly low fuel ref pressure | |||
150 | ✗ | criticalError("Impossible fuel reference pressure: %f", referencePressure); | ||
151 | ||||
152 | ✗ | return 1.0f; | ||
153 | } | |||
154 | ||||
155 |
1/1✓ Branch 2 taken 24 times.
|
24 | expected<float> diffPressure = getFuelDifferentialPressure(); | |
156 | ||||
157 | // If sensor failed, best we can do is disable correction | |||
158 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 22 times.
|
2/2✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 22 times.
|
24 | if (!diffPressure) { |
159 | 2 | return 1.0f; | ||
160 | } | |||
161 | ||||
162 | 22 | pressureDelta = diffPressure.Value; | ||
163 | ||||
164 | // Somehow pressure delta is less than 0, assume failed sensor and return default flow | |||
165 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 21 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 21 times.
|
22 | if (pressureDelta <= 0) { |
166 | 1 | return 1.0f; | ||
167 | } | |||
168 | ||||
169 | 21 | pressureRatio = pressureDelta / referencePressure; | ||
170 | // todo: live data model? | |||
171 | 21 | float flowRatio = sqrtf(pressureRatio); | ||
172 | ||||
173 | // TODO: should the flow ratio be clamped? | |||
174 | 21 | return flowRatio; | ||
175 | } | |||
176 | ||||
177 | 1131 | float InjectorModelWithConfig::getDeadtime() const { | ||
178 | 2262 | return interpolate3d( | ||
179 | 1131 | m_cfg->battLagCorrTable, | ||
180 | 1131 | m_cfg->battLagCorrPressBins, pressureCorrectionReference, | ||
181 |
2/2✓ Branch 1 taken 1131 times.
✓ Branch 4 taken 1131 times.
|
3393 | m_cfg->battLagCorrBattBins, Sensor::get(SensorType::BatteryVoltage).value_or(VBAT_FALLBACK_VALUE) | |
182 | 2262 | ); | ||
183 | } | |||
184 | ||||
185 | //TODO: only used in the tests, refactor pending to InjectorModelWithConfig | |||
186 | 4 | floatms_t InjectorModelBase::getInjectionDuration(float fuelMassGram) const { | ||
187 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 4 times.
|
4 | if (fuelMassGram <= 0) { |
188 | // If 0 mass, don't do any math, just skip the injection. | |||
189 | ✗ | return 0.0f; | ||
190 | } | |||
191 | ||||
192 | // Get the no-offset duration | |||
193 | 4 | floatms_t baseDuration = getBaseDurationImpl(fuelMassGram); | ||
194 | ||||
195 | 4 | return baseDuration + m_deadtime; | ||
196 | } | |||
197 | ||||
198 | 9587 | floatms_t InjectorModelWithConfig::getInjectionDuration(float fuelMassGram) const { | ||
199 |
2/2✓ Branch 0 taken 7900 times.
✓ Branch 1 taken 1687 times.
|
2/2✓ Decision 'true' taken 7900 times.
✓ Decision 'false' taken 1687 times.
|
9587 | if (fuelMassGram <= 0) { |
200 | // If 0 mass, don't do any math, just skip the injection. | |||
201 | 7900 | return 0.0f; | ||
202 | } | |||
203 | ||||
204 | // hopefully one day we pick between useInjectorFlowLinearizationTable and ICM_HPFP_Manual_Compensation approaches | |||
205 | // and not more than one of these would stay | |||
206 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1687 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1687 times.
|
1687 | if (engineConfiguration->useInjectorFlowLinearizationTable) { |
207 | ✗ | auto fps = Sensor::get(SensorType::FuelPressureInjector); | ||
208 | // todo: KPA vs BAR mess?! | |||
209 | ✗ | return interpolate3d(config->injectorFlowLinearization, | ||
210 | ✗ | config->injectorFlowLinearizationPressureBins, KPA2BAR(fps.Value),// array values are on bar | ||
211 | ✗ | config->injectorFlowLinearizationFuelMassBins, fuelMassGram * 1000); // array values are on mg | ||
212 | } | |||
213 | ||||
214 | // Get the no-offset duration | |||
215 | 1687 | floatms_t baseDuration = getBaseDurationImpl(fuelMassGram); | ||
216 | ||||
217 | // default non GDI case | |||
218 |
2/2✓ Branch 1 taken 1685 times.
✓ Branch 2 taken 2 times.
|
2/2✓ Decision 'true' taken 1685 times.
✓ Decision 'false' taken 2 times.
|
1687 | if (getInjectorCompensationMode() != ICM_HPFP_Manual_Compensation) { |
219 | // Add deadtime offset | |||
220 | 1685 | return baseDuration + m_deadtime; | ||
221 | } | |||
222 | ||||
223 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
1/2✓ Decision 'true' taken 2 times.
✗ Decision 'false' not taken.
|
2 | if (!Sensor::hasSensor(SensorType::FuelPressureHigh)) { |
224 | 2 | return baseDuration + m_deadtime; | ||
225 | } | |||
226 | ||||
227 | ✗ | auto fps = Sensor::get(SensorType::FuelPressureHigh); | ||
228 | // todo: KPA vs BAR mess in code and UI?! | |||
229 | ✗ | float fuelMassCompensation = interpolate3d(config->hpfpFuelMassCompensation, | ||
230 | ✗ | config->hpfpFuelMassCompensationFuelPressure, KPA2BAR(fps.Value),// array values are on bar | ||
231 | ✗ | config->hpfpFuelMassCompensationFuelMass, fuelMassGram * 1000); // array values are on mg | ||
232 | ||||
233 | // recalculate base duration with fuel mass compensation | |||
234 | ✗ | baseDuration = getBaseDurationImpl(fuelMassGram * fuelMassCompensation); | ||
235 | ✗ | return baseDuration + m_deadtime; | ||
236 | } | |||
237 | ||||
238 | 1124 | float InjectorModelBase::getFuelMassForDuration(floatms_t duration) const { | ||
239 | // Convert from ms -> grams | |||
240 | 1124 | return duration * m_massFlowRate * 0.001f; | ||
241 | } | |||
242 | ||||
243 | // todo: all that *1000 and *0.001f is pretty annoying, we need a cleaner approach for units! | |||
244 | 1712 | floatms_t InjectorModelBase::getBaseDurationImpl(float fuelMassGram) const { | ||
245 | 1712 | floatms_t baseDuration = fuelMassGram / m_massFlowRate * 1000; | ||
246 | ||||
247 |
2/3✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1691 times.
|
1712 | switch (getNonlinearMode()) { | |
248 |
1/1✓ Decision 'true' taken 21 times.
|
21 | case INJ_FordModel: | |
249 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 14 times.
|
2/2✓ Decision 'true' taken 7 times.
✓ Decision 'false' taken 14 times.
|
21 | if (fuelMassGram < m_smallPulseBreakPoint) { |
250 | // Small pulse uses a different slope, and adds the "zero fuel pulse" offset | |||
251 | 7 | return (fuelMassGram / m_smallPulseFlowRate * 1000) + m_smallPulseOffset; | ||
252 | } else { | |||
253 | // Large pulse | |||
254 | 14 | return baseDuration; | ||
255 | } | |||
256 | ✗ | case INJ_PolynomialAdder: | ||
257 | ✗ | return correctInjectionPolynomial(baseDuration); | ||
258 |
1/1✓ Decision 'true' taken 1691 times.
|
1691 | case INJ_None: | |
259 | default: | |||
260 |
1/1✓ Decision 'true' taken 1691 times.
|
1691 | return baseDuration; | |
261 | } | |||
262 | } | |||
263 | ||||
264 | 9 | floatms_t InjectorModelBase::correctInjectionPolynomial(floatms_t baseDuration) const { | ||
265 |
2/2✓ Branch 1 taken 1 time.
✓ Branch 2 taken 8 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 8 times.
|
9 | if (baseDuration > engineConfiguration->applyNonlinearBelowPulse) { |
266 | // Large pulse, skip correction. | |||
267 | 1 | return baseDuration; | ||
268 | } | |||
269 | ||||
270 | 8 | auto& is = engineConfiguration->injectorCorrectionPolynomial; | ||
271 | 8 | float xi = 1; | ||
272 | ||||
273 | 8 | float adder = 0; | ||
274 | ||||
275 | // Add polynomial terms, starting with x^0 | |||
276 |
2/2✓ Branch 1 taken 64 times.
✓ Branch 2 taken 8 times.
|
2/2✓ Decision 'true' taken 64 times.
✓ Decision 'false' taken 8 times.
|
72 | for (size_t i = 0; i < efi::size(is); i++) { |
277 | 64 | adder += is[i] * xi; | ||
278 | 64 | xi *= baseDuration; | ||
279 | } | |||
280 | ||||
281 | 8 | return baseDuration + adder; | ||
282 | } | |||
283 | ||||
284 | 1371 | InjectorModelWithConfig::InjectorModelWithConfig(const injector_s* const cfg) | ||
285 | 1371 | : m_cfg(cfg) | ||
286 | { | |||
287 | 1371 | } | ||
288 | ||||
289 | 694 | InjectorModelPrimary::InjectorModelPrimary() | ||
290 | 694 | : InjectorModelWithConfig(&engineConfiguration->injector) | ||
291 | { | |||
292 | 694 | } | ||
293 | ||||
294 | // TODO: actual separate config for second bank! | |||
295 | 677 | InjectorModelSecondary::InjectorModelSecondary() | ||
296 | 677 | : InjectorModelWithConfig(&engineConfiguration->injectorSecondary) | ||
297 | { | |||
298 | 677 | } | ||
299 |