Line data Source code
1 : #include "pch.h"
2 :
3 : #include "high_pressure_fuel_pump.h"
4 : #include "fuel_computer.h"
5 :
6 : using ::testing::_;
7 : using ::testing::StrictMock;
8 :
9 4 : TEST(HPFP, Lobe) {
10 1 : EngineTestHelper eth(engine_type_e::TEST_ENGINE);
11 :
12 1 : engineConfiguration->hpfpCam = HPFP_CAM_NONE;
13 1 : engineConfiguration->hpfpPeakPos = 123;
14 1 : engineConfiguration->hpfpCamLobes = 3;
15 :
16 1 : engine->triggerCentral.vvtPosition[0][0] = 40; // Bank 0
17 1 : engine->triggerCentral.vvtPosition[0][1] = 80;
18 1 : engine->triggerCentral.vvtPosition[1][0] =120; // Bank 1
19 1 : engine->triggerCentral.vvtPosition[1][1] =160;
20 :
21 1 : HpfpLobe lobe;
22 :
23 : // Run through all five CAM modes
24 6 : for (int cam = 0; cam < 5; cam++) {
25 : static hpfp_cam_e map[5] = { HPFP_CAM_NONE, HPFP_CAM_IN1, HPFP_CAM_EX1,
26 : HPFP_CAM_IN2, HPFP_CAM_EX2};
27 5 : engineConfiguration->hpfpCam = map[cam];
28 :
29 : // Run through several cycles of the engine to make sure we keep wrapping around
30 25 : for (int i = 0; i < 4; i++) {
31 20 : EXPECT_EQ(lobe.findNextLobe(), 240 + 123 + cam * 20);
32 20 : EXPECT_EQ(lobe.findNextLobe(), 480 + 123 + cam * 20);
33 20 : EXPECT_EQ(lobe.findNextLobe(), 0 + 123 + cam * 20);
34 : }
35 : }
36 :
37 : // Can we change the number of lobes?
38 1 : engineConfiguration->hpfpCam = HPFP_CAM_NONE;
39 1 : engineConfiguration->hpfpCamLobes = 4;
40 1 : EXPECT_EQ(lobe.findNextLobe(), 180 + 123);
41 1 : EXPECT_EQ(lobe.findNextLobe(), 360 + 123);
42 1 : EXPECT_EQ(lobe.findNextLobe(), 540 + 123);
43 1 : EXPECT_EQ(lobe.findNextLobe(), 0 + 123);
44 :
45 : // Can we change the peak position?
46 1 : engineConfiguration->hpfpPeakPos = 95;
47 1 : EXPECT_EQ(lobe.findNextLobe(), 180 + 95);
48 1 : EXPECT_EQ(lobe.findNextLobe(), 360 + 95);
49 1 : EXPECT_EQ(lobe.findNextLobe(), 540 + 95);
50 1 : EXPECT_EQ(lobe.findNextLobe(), 0 + 95);
51 2 : }
52 :
53 4 : TEST(HPFP, InjectionReplacementFuel) {
54 1 : EngineTestHelper eth(engine_type_e::TEST_ENGINE);
55 :
56 1 : engineConfiguration->cylindersCount = 4;
57 1 : engineConfiguration->hpfpCamLobes = 4;
58 1 : engine->engineState.injectionMass[0] = 0.05 /* cc/cyl */ * fuelDensity;
59 1 : engineConfiguration->hpfpPumpVolume = 0.2; // cc/lobe
60 :
61 1 : HpfpQuantity math;
62 :
63 1 : EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 25);
64 :
65 : // What if we change the # of lobes?
66 1 : engineConfiguration->hpfpCamLobes = 3;
67 1 : EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 25 * 1.333333333f);
68 :
69 : // More fuel!
70 1 : engine->engineState.injectionMass[0] = 0.1 /* cc/cyl */ * fuelDensity;
71 1 : EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 50 * 1.333333333f);
72 :
73 : // More cylinders!
74 1 : engineConfiguration->cylindersCount = 6;
75 1 : EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 50 * 2.); // Ooops we maxed out
76 :
77 : // Compensation testing
78 1 : engineConfiguration->cylindersCount =
79 1 : engineConfiguration->hpfpCamLobes; // Make math easier
80 11 : for (int i = 0; i < HPFP_COMPENSATION_SIZE; i++) {
81 : // one bin every 1000 RPM
82 10 : config->hpfpCompensationRpmBins[i] = std::min(i * 1000, 8000);
83 : }
84 11 : for (int i = 0; i < HPFP_COMPENSATION_SIZE; i++) {
85 : // one bin every 0.05 cc/lobe
86 10 : config->hpfpCompensationLoadBins[i] = std::min(i * 0.05, 60.);
87 : }
88 :
89 1 : config->hpfpCompensation[2][1] = -10;
90 1 : EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 40); // -10, in cell
91 1 : EXPECT_FLOAT_EQ(math.calcFuelPercent(1500), 45); // -5, half way
92 1 : EXPECT_FLOAT_EQ(math.calcFuelPercent(2000), 50); // -0, in next cell
93 :
94 1 : config->hpfpCompensation[2][1] = 20;
95 1 : EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 70); // +20, in cell
96 1 : EXPECT_FLOAT_EQ(math.calcFuelPercent(1500), 60); // +10, half way
97 1 : EXPECT_FLOAT_EQ(math.calcFuelPercent(2000), 50); // +0, in next cell
98 :
99 : // 25% more fuel (1.25*50=62.5 base), but half way between comp of 20 and 0 (so comp of 10)
100 1 : engine->engineState.injectionMass[0] = 0.125 /* cc/cyl */ * fuelDensity;
101 1 : EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 72.5); // +10 comp
102 1 : EXPECT_FLOAT_EQ(math.calcFuelPercent(1500), 67.5); // +5, half way
103 1 : EXPECT_FLOAT_EQ(math.calcFuelPercent(2000), 62.5); // +0 base
104 2 : }
105 :
106 4 : TEST(HPFP, PI) {
107 1 : EngineTestHelper eth(engine_type_e::TEST_ENGINE);
108 :
109 1 : engineConfiguration->hpfpTargetDecay = 0;
110 1 : engineConfiguration->hpfpPidP = 0;
111 1 : engineConfiguration->hpfpPidI = 0;
112 1 : engineConfiguration->hpfpPeakPos = 0;
113 :
114 1 : engineConfiguration->cylindersCount = 4;
115 1 : engineConfiguration->hpfpCamLobes = 4;
116 1 : engine->engineState.injectionMass[0] = 0.05 /* cc/cyl */ * fuelDensity;
117 1 : engineConfiguration->hpfpPumpVolume = 0.2; // cc/lobe
118 :
119 1 : HpfpQuantity math;
120 :
121 11 : for (int i = 0; i < HPFP_TARGET_SIZE; i++) {
122 : // one bin every 1000 RPM
123 10 : config->hpfpTargetRpmBins[i] = std::min(i * 1000, 8000);
124 : }
125 11 : for (int i = 0; i < HPFP_TARGET_SIZE; i++) {
126 : // one bin every 20kPa
127 10 : config->hpfpTargetLoadBins[i] = std::min(i * 20, 200);
128 : }
129 11 : for (int r = 0; r < HPFP_TARGET_SIZE; r++) {
130 110 : for (int c = 0; c < HPFP_TARGET_SIZE; c++) {
131 100 : config->hpfpTarget[r][c] = 1000 * r + 10 * c;
132 : }
133 : }
134 :
135 1 : Sensor::setMockValue(SensorType::Map, 40);
136 1 : Sensor::setMockValue(SensorType::FuelPressureHigh, 1000);
137 1 : EXPECT_FLOAT_EQ(math.calcPI(1000, 120), -20); // Test integral clamp
138 1 : EXPECT_FLOAT_EQ(math.m_I_sum_percent, -20); // and again
139 1 : EXPECT_FLOAT_EQ(math.m_pressureTarget_kPa, 2010);
140 1 : EXPECT_FLOAT_EQ(math.calcPI(1000, -40), 40); // Test integral clamp
141 1 : EXPECT_FLOAT_EQ(math.m_I_sum_percent, 40); // and again
142 1 : EXPECT_FLOAT_EQ(math.m_pressureTarget_kPa, 2010);
143 :
144 : // Test pressure decay
145 1 : math.calcPI(4000, 0);
146 1 : EXPECT_FLOAT_EQ(math.m_pressureTarget_kPa, 2040);
147 1 : math.calcPI(1000, 0);
148 1 : EXPECT_FLOAT_EQ(math.m_pressureTarget_kPa, 2040);
149 1 : engineConfiguration->hpfpTargetDecay = 1000;
150 1 : math.calcPI(1000, 0);
151 1 : EXPECT_FLOAT_EQ(math.m_pressureTarget_kPa, 2035); // 5ms of decay
152 :
153 : // Proportional gain
154 1 : math.reset(); // Reset for ease of testing
155 1 : EXPECT_FLOAT_EQ(math.calcPI(1000, 0), 0); // Validate reset
156 1 : EXPECT_FLOAT_EQ(math.m_pressureTarget_kPa, 2010);
157 1 : engineConfiguration->hpfpPidP = 0.01;
158 1 : EXPECT_FLOAT_EQ(math.calcPI(1000, 0), 10.10);
159 1 : engineConfiguration->hpfpPidP = 0.02;
160 1 : EXPECT_FLOAT_EQ(math.calcPI(1000, 0), 20.20);
161 :
162 : // Integral gain
163 1 : engineConfiguration->hpfpPidI = 0.001;
164 1 : EXPECT_FLOAT_EQ(math.calcPI(1000, 0), 20.368334);
165 1 : EXPECT_FLOAT_EQ(math.m_I_sum_percent, 0.168333333);
166 2 : }
167 :
168 4 : TEST(HPFP, Angle) {
169 1 : EngineTestHelper eth(engine_type_e::TEST_ENGINE);
170 :
171 1 : engineConfiguration->hpfpTargetDecay = 0;
172 1 : engineConfiguration->hpfpPidP = 0;
173 1 : engineConfiguration->hpfpPidI = 0;
174 1 : engineConfiguration->hpfpPeakPos = 0;
175 :
176 1 : engineConfiguration->cylindersCount = 4;
177 1 : engineConfiguration->hpfpCamLobes = 4;
178 1 : engine->engineState.injectionMass[0] = 0.05 /* cc/cyl */ * fuelDensity;
179 1 : engineConfiguration->hpfpPumpVolume = 0.2; // cc/lobe
180 :
181 1 : HpfpQuantity math;
182 :
183 11 : for (int i = 0; i < HPFP_TARGET_SIZE; i++) {
184 : // one bin every 1000 RPM
185 10 : config->hpfpTargetRpmBins[i] = std::min(i * 1000, 8000);
186 : }
187 11 : for (int i = 0; i < HPFP_TARGET_SIZE; i++) {
188 : // one bin every 20kPa
189 10 : config->hpfpTargetLoadBins[i] = std::min(i * 20, 200);
190 : }
191 11 : for (int r = 0; r < HPFP_TARGET_SIZE; r++) {
192 110 : for (int c = 0; c < HPFP_TARGET_SIZE; c++) {
193 100 : config->hpfpTarget[r][c] = 1000 * r + 10 * c;
194 : }
195 : }
196 17 : for (int i = 0; i < HPFP_LOBE_PROFILE_SIZE; i++) {
197 16 : config->hpfpLobeProfileQuantityBins[i] = 100. * i / (HPFP_LOBE_PROFILE_SIZE - 1);
198 16 : config->hpfpLobeProfileAngle[i] = 150. * i / (HPFP_LOBE_PROFILE_SIZE - 1);
199 : }
200 :
201 1 : HpfpController model;
202 :
203 1 : EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 25); // Double check baseline
204 1 : EXPECT_FLOAT_EQ(math.calcPI(1000, 10), 0); // Validate no PI
205 1 : EXPECT_NEAR(math.pumpAngleFuel(1000, &model), 37.5, 0.4); // Given the profile, should be 50% higher
206 :
207 1 : engine->engineState.injectionMass[0] = 0.08 /* cc/cyl */ * fuelDensity;
208 1 : EXPECT_FLOAT_EQ(math.calcFuelPercent(1000), 40); // Double check baseline
209 1 : EXPECT_FLOAT_EQ(math.calcPI(1000, 10), 0); // Validate no PI
210 1 : EXPECT_NEAR(math.pumpAngleFuel(1000, &model), 60, 0.4); // Given the profile, should be 50% higher
211 :
212 1 : engineConfiguration->hpfpPidP = 0.01;
213 1 : Sensor::setMockValue(SensorType::Map, 40);
214 1 : Sensor::setMockValue(SensorType::FuelPressureHigh, 1000);
215 1 : EXPECT_FLOAT_EQ(math.calcPI(1000, 10), 10.1);
216 1 : EXPECT_NEAR(math.pumpAngleFuel(1000, &model), 50.1 * 1.5, 0.4); // Given the profile, should be 50% higher
217 2 : }
218 :
219 4 : TEST(HPFP, Schedule) {
220 1 : EngineTestHelper eth(engine_type_e::TEST_ENGINE);
221 :
222 1 : engineConfiguration->cylindersCount = 4;
223 1 : engineConfiguration->hpfpCamLobes = 4;
224 1 : engineConfiguration->hpfpPumpVolume = 0.2; // cc/lobe
225 :
226 11 : for (int i = 0; i < HPFP_TARGET_SIZE; i++) {
227 : // one bin every 1000 RPM
228 10 : config->hpfpTargetRpmBins[i] = std::min(i * 1000, 8000);
229 : }
230 11 : for (int i = 0; i < HPFP_TARGET_SIZE; i++) {
231 : // one bin every 20kPa
232 10 : config->hpfpTargetLoadBins[i] = std::min(i * 20, 200);
233 : }
234 11 : for (int r = 0; r < HPFP_TARGET_SIZE; r++) {
235 110 : for (int c = 0; c < HPFP_TARGET_SIZE; c++) {
236 100 : config->hpfpTarget[r][c] = 1000 * r + 10 * c;
237 : }
238 : }
239 17 : for (int i = 0; i < HPFP_LOBE_PROFILE_SIZE; i++) {
240 16 : config->hpfpLobeProfileQuantityBins[i] = 100. * i / (HPFP_LOBE_PROFILE_SIZE - 1);
241 16 : config->hpfpLobeProfileAngle[i] = 150. * i / (HPFP_LOBE_PROFILE_SIZE - 1);
242 : }
243 :
244 1 : auto & hpfp = *engine->module<HpfpController>();
245 :
246 1 : StrictMock<MockExecutor> mockExec;
247 1 : engine->executor.setMockExecutor(&mockExec);
248 1 : engineConfiguration->hpfpActivationAngle = 30;
249 :
250 1 : constexpr angle_t angle0 = 90;
251 1 : constexpr angle_t angle1 = 270 - 37.6923065;
252 1 : constexpr angle_t angle2 = angle1 + 30;
253 :
254 1 : constexpr float tick_per_deg = USF2NT(1000000.)*60/360/1000;
255 :
256 1 : constexpr efitick_t nt0 = tick_per_deg * angle0;
257 1 : constexpr efitick_t nt1 = tick_per_deg * angle1;
258 1 : constexpr efitick_t nt2 = tick_per_deg * angle2;
259 :
260 : {
261 1 : testing::InSequence is;
262 :
263 : // First call to setRpmValue will cause a dummy call to fast periodic timer.
264 : // Injection Mass will be 0 so expect a no-op.
265 1 : EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), &hpfp.m_event.eventScheduling, nt0, action_s(HpfpController::pinTurnOff, &hpfp)));
266 :
267 : // Second call will be the start of a real pump event.
268 1 : EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), &hpfp.m_event.eventScheduling, nt1, action_s(HpfpController::pinTurnOn, &hpfp)));
269 :
270 : // Third call will be off event
271 1 : EXPECT_CALL(mockExec, scheduleByTimestampNt(testing::NotNull(), &hpfp.m_event.eventScheduling, nt2, action_s(HpfpController::pinTurnOff, &hpfp)));
272 1 : }
273 1 : EXPECT_CALL(mockExec, cancel(_)).Times(2);
274 :
275 : // For HPFP to work, events need to be scheduled after the next tooth. This makes sure the
276 : // peak pos occurs after the next tooth.
277 1 : engineConfiguration->hpfpPeakPos = 90;
278 : // This will call the fast callback routine
279 1 : engine->rpmCalculator.setRpmValue(1000);
280 1 : engine->engineState.injectionMass[0] = 0.05 /* cc/cyl */ * fuelDensity;
281 1 : engineConfiguration->hpfpValvePin = Gpio::A2; // arbitrary
282 :
283 1 : hpfp.onFastCallback();
284 : // First event was scheduled by setRpmValue with 0 injection mass. So, it's off.
285 1 : eth.assertTriggerEvent("h0", 0, &hpfp.m_event, (void*)&HpfpController::pinTurnOff, 270);
286 :
287 : // Make the previous event happen, schedule the next.
288 1 : engine->module<TriggerScheduler>()->scheduleEventsUntilNextTriggerTooth(
289 : 1000, tick_per_deg * 0, 180, 360);
290 : // Mock executor doesn't run events, so we run it manually
291 1 : HpfpController::pinTurnOff(&hpfp);
292 :
293 : // Now we should have a regular on/off event.
294 1 : eth.assertTriggerEvent("h1", 0, &hpfp.m_event, (void*)&HpfpController::pinTurnOn, 450 - 37.6923065f);
295 :
296 : // Make it happen
297 1 : engine->module<TriggerScheduler>()->scheduleEventsUntilNextTriggerTooth(
298 : 1000, tick_per_deg * 180, 360, 540);
299 :
300 : // Since we have a mock scheduler, lets insert the correct timestamp in the scheduling
301 : // struct.
302 1 : hpfp.m_event.eventScheduling.setMomentNt(nt1);
303 :
304 1 : HpfpController::pinTurnOn(&hpfp);
305 :
306 : // The off event goes directly to scheduleByAngle and is tested by the last EXPECT_CALL
307 : // above.
308 2 : }
|