GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Warnings: 1 unchecked decisions!
Coverage Exec / Excl / Total
Lines: 86.9% 425 / 0 / 489
Functions: 84.4% 38 / 0 / 45
Branches: 83.2% 332 / 0 / 399
Decisions: 81.0% 162 / - / 200

firmware/controllers/trigger/trigger_central.cpp
Line Branch Decision Exec Source
1 /*
2 * @file trigger_central.cpp
3 * Here we have a bunch of higher-level methods which are not directly related to actual signal decoding
4 *
5 * @date Feb 23, 2014
6 * @author Andrey Belomutskiy, (c) 2012-2020
7 */
8
9 #include "pch.h"
10
11 #include "trigger_central.h"
12 #include "trigger_decoder.h"
13 #include "main_trigger_callback.h"
14 #include "listener_array.h"
15 #include "logic_analyzer.h"
16
17 #include "local_version_holder.h"
18 #include "trigger_simulator.h"
19 #include "trigger_emulator_algo.h"
20
21 #include "map_averaging.h"
22 #include "main_trigger_callback.h"
23 #include "status_loop.h"
24 #include "engine_sniffer.h"
25 #include "auto_generated_sync_edge.h"
26
27 #if EFI_TUNER_STUDIO
28 #include "tunerstudio.h"
29 #endif /* EFI_TUNER_STUDIO */
30
31 #if EFI_ENGINE_SNIFFER
32 WaveChart waveChart;
33 #endif /* EFI_ENGINE_SNIFFER */
34
35 #define TRIGGER_WAVEFORM(x) getTriggerCentral()->triggerShape.x
36
37 #if EFI_SHAFT_POSITION_INPUT
38
39 696 TriggerCentral::TriggerCentral() :
40
4/4
✓ Branch 0 taken 2784 times.
✓ Branch 1 taken 1392 times.
✓ Branch 2 taken 1392 times.
✓ Branch 3 taken 696 times.
4872 vvtPosition(),
41
2/2
✓ Branch 14 taken 1392 times.
✓ Branch 15 taken 696 times.
2784 triggerState("TRG")
42 {
43 696 setArrayValues(hwEventCounters, 0);
44 696 triggerState.resetState();
45 696 noiseFilter.resetAccumSignalData();
46 696 }
47
48 816 void TriggerNoiseFilter::resetAccumSignalData() {
49 816 memset(lastSignalTimes, 0xff, sizeof(lastSignalTimes)); // = -1
50 816 memset(accumSignalPeriods, 0, sizeof(accumSignalPeriods));
51 816 memset(accumSignalPrevPeriods, 0, sizeof(accumSignalPrevPeriods));
52 816 }
53
54 2124312 int TriggerCentral::getHwEventCounter(int index) const {
55 2124312 return hwEventCounters[index];
56 }
57
58 2132286 angle_t TriggerCentral::getVVTPosition(uint8_t bankIndex, uint8_t camIndex) {
59
2/4
✓ Branch 0 taken 2132286 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2132286 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 2132286 times.
2132286 if (bankIndex >= BANKS_COUNT || camIndex >= CAMS_PER_BANK) {
60 return NAN;
61 }
62 2132286 return vvtPosition[bankIndex][camIndex];
63 }
64
65 /**
66 * @return angle since trigger synchronization point, NOT angle since TDC.
67 */
68 42694 expected<float> TriggerCentral::getCurrentEnginePhase(efitick_t nowNt) const {
69 42694 floatus_t oneDegreeUs = engine->rpmCalculator.oneDegreeUs;
70
71
2/2
✓ Branch 1 taken 3093 times.
✓ Branch 2 taken 39601 times.
2/2
✓ Decision 'true' taken 3093 times.
✓ Decision 'false' taken 39601 times.
42694 if (std::isnan(oneDegreeUs)) {
72 3093 return unexpected;
73 }
74
75 float elapsed;
76 float toothPhase;
77
78 {
79 // under lock to avoid mismatched tooth phase and time
80 chibios_rt::CriticalSectionLocker csl;
81
82 39601 elapsed = m_lastToothTimer.getElapsedUs(nowNt);
83 39601 toothPhase = m_lastToothPhaseFromSyncPoint;
84 }
85
86 39601 return toothPhase + elapsed / oneDegreeUs;
87 }
88
89 /**
90 * todo: why is this method NOT reciprocal to getRpmMultiplier?!
91 */
92 34104 int getCrankDivider(operation_mode_e operationMode) {
93
5/7
✓ Branch 0 taken 19551 times.
✓ Branch 1 taken 3214 times.
✓ Branch 2 taken 1974 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 213 times.
✓ Branch 5 taken 9152 times.
✗ Branch 6 not taken.
34104 switch (operationMode) {
94
1/1
✓ Decision 'true' taken 19551 times.
19551 case FOUR_STROKE_CRANK_SENSOR:
95 19551 return 2;
96
1/1
✓ Decision 'true' taken 3214 times.
3214 case FOUR_STROKE_SYMMETRICAL_CRANK_SENSOR:
97 3214 return SYMMETRICAL_CRANK_SENSOR_DIVIDER;
98
1/1
✓ Decision 'true' taken 1974 times.
1974 case FOUR_STROKE_THREE_TIMES_CRANK_SENSOR:
99 1974 return SYMMETRICAL_THREE_TIMES_CRANK_SENSOR_DIVIDER;
100 case FOUR_STROKE_SIX_TIMES_CRANK_SENSOR:
101 return SYMMETRICAL_SIX_TIMES_CRANK_SENSOR_DIVIDER;
102
1/1
✓ Decision 'true' taken 213 times.
213 case FOUR_STROKE_TWELVE_TIMES_CRANK_SENSOR:
103 213 return SYMMETRICAL_TWELVE_TIMES_CRANK_SENSOR_DIVIDER;
104
1/1
✓ Decision 'true' taken 9152 times.
9152 case OM_NONE:
105 case FOUR_STROKE_CAM_SENSOR:
106 case TWO_STROKE:
107 // That's easy - trigger cycle matches engine cycle
108
1/1
✓ Decision 'true' taken 9152 times.
9152 return 1;
109 /* let's NOT handle default in order to benefit from -Werror=switch */
110 }
111 /**
112 wow even while we explicitly handle all enumerations in the switch above we still need a return statement due to
113 https://stackoverflow.com/questions/34112483/gcc-how-best-to-handle-warning-about-unreachable-end-of-function-after-switch
114 */
115 criticalError("unreachable getCrankDivider");
116 return 1;
117 }
118
119 6125 PUBLIC_API_WEAK bool boardIsSpecialVvtDecoder(vvt_mode_e vvtMode) {
120 UNUSED(vvtMode);
121
122 6125 return false;
123 }
124
125 33453 PUBLIC_API_WEAK void boardTriggerCallback(efitick_t timestamp, float currentPhase) { UNUSED(timestamp); UNUSED(currentPhase); }
126
127 6393 static bool vvtWithRealDecoder(vvt_mode_e vvtMode) {
128 return vvtMode != VVT_INACTIVE
129
2/2
✓ Branch 0 taken 6320 times.
✓ Branch 1 taken 73 times.
6393 && vvtMode != VVT_TOYOTA_3_TOOTH /* VVT_2JZ is an unusual 3/0 missed tooth symmetrical wheel */
130
2/2
✓ Branch 0 taken 6129 times.
✓ Branch 1 taken 191 times.
6320 && vvtMode != VVT_HONDA_K_INTAKE
131
2/2
✓ Branch 0 taken 6125 times.
✓ Branch 1 taken 4 times.
6129 && vvtMode != VVT_MAP_V_TWIN
132
1/2
✓ Branch 1 taken 6125 times.
✗ Branch 2 not taken.
6125 && !boardIsSpecialVvtDecoder(vvtMode)
133
3/4
✓ Branch 0 taken 6393 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5801 times.
✓ Branch 3 taken 324 times.
12786 && vvtMode != VVT_SINGLE_TOOTH;
134 }
135
136 651 angle_t TriggerCentral::syncEnginePhaseAndReport(int divider, int remainder) {
137 651 angle_t engineCycle = getEngineCycle(getEngineRotationState()->getOperationMode());
138
139 651 angle_t totalShift = triggerState.syncEnginePhase(divider, remainder, engineCycle);
140
2/2
✓ Branch 0 taken 38 times.
✓ Branch 1 taken 613 times.
2/2
✓ Decision 'true' taken 38 times.
✓ Decision 'false' taken 613 times.
651 if (totalShift != 0) {
141 // Reset instant RPM, since the engine phase has now changed, invalidating the tooth history buffer
142 // maybe TODO: could/should we rotate the buffer around to re-align it instead? Is that worth it?
143 38 instantRpm.resetInstantRpm();
144 }
145 651 return totalShift;
146 }
147
148 PUBLIC_API_WEAK angle_t customAdjustCustom(TriggerCentral *tc, vvt_mode_e vvtMode) {
149 UNUSED(tc);
150 UNUSED(vvtMode);
151
152 return 0;
153 }
154
155 648 static angle_t adjustCrankPhase(int camIndex) {
156 648 float maxSyncThreshold = engineConfiguration->maxCamPhaseResolveRpm;
157
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 648 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 648 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 648 times.
648 if (maxSyncThreshold != 0 && Sensor::getOrZero(SensorType::Rpm) > maxSyncThreshold) {
158 // The user has elected to stop trying to resolve crank phase after some RPM.
159 // Maybe their cam sensor only works at low RPM or something.
160 // Anyway, don't try to change crank phase at all, and return that we made no change.
161 return 0;
162 }
163
164 648 operation_mode_e operationMode = getEngineRotationState()->getOperationMode();
165
166 648 auto crankDivider = getCrankDivider(operationMode);
167
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 648 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 648 times.
648 if (crankDivider == 1) {
168 // Crank divider of 1 means there's no ambiguity, so don't try to resolve it
169 return 0;
170 }
171
172 648 TriggerCentral *tc = getTriggerCentral();
173
174 648 vvt_mode_e vvtMode = engineConfiguration->vvtMode[camIndex];
175
2/6
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 632 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
648 switch (vvtMode) {
176
1/1
✓ Decision 'true' taken 16 times.
16 case VVT_MAP_V_TWIN:
177 case VVT_MITSUBISHI_4G63:
178
1/1
✓ Decision 'true' taken 16 times.
16 return tc->syncEnginePhaseAndReport(crankDivider, 1);
179
1/1
✓ Decision 'true' taken 632 times.
632 case VVT_SINGLE_TOOTH:
180 case VVT_NISSAN_VQ:
181 case VVT_BOSCH_QUICK_START:
182 case VVT_BMW_N63TU:
183 case VVT_MIATA_NB:
184 case VVT_TOYOTA_3TOOTH_UZ:
185 case VVT_TOYOTA_3_TOOTH:
186 case VVT_TOYOTA_4_1:
187 case VVT_FORD_COYOTE:
188 case VVT_DEV:
189 case VVT_FORD_ST170:
190 case VVT_BARRA_3_PLUS_1:
191 case VVT_NISSAN_MR:
192 case VVT_HR12DDR_IN:
193 case VVT_MAZDA_SKYACTIV:
194 case VVT_MAZDA_L:
195 case VVT_MITSUBISHI_4G69:
196 case VVT_MITSUBISHI_3A92:
197 case VVT_MITSUBISHI_6G72:
198 case VVT_CHRYSLER_PHASER:
199 case VVT_HONDA_K_EXHAUST:
200 case VVT_HONDA_CBR_600:
201 case VVT_SUBARU_7TOOTH:
202
1/1
✓ Decision 'true' taken 632 times.
632 return tc->syncEnginePhaseAndReport(crankDivider, 0);
203 case VVT_CUSTOM_25:
204 case VVT_CUSTOM_26:
205 return customAdjustCustom(tc, vvtMode);
206
207 case VVT_HONDA_K_INTAKE:
208 // with 4 evenly spaced tooth we cannot use this wheel for engine sync
209 criticalError("Honda K Intake is not suitable for engine sync");
210 [[fallthrough]];
211 case VVT_CUSTOM_1:
212 case VVT_CUSTOM_2:
213 case VVT_INACTIVE:
214 // do nothing
215 return 0;
216 }
217 return 0;
218 }
219
220 /**
221 * See also wrapAngle
222 */
223 822 static angle_t wrapVvt(angle_t vvtPosition, int period) {
224 // Wrap VVT position in to the range [-360, 360)
225
2/2
✓ Branch 0 taken 3641 times.
✓ Branch 1 taken 822 times.
2/2
✓ Decision 'true' taken 3641 times.
✓ Decision 'false' taken 822 times.
4463 while (vvtPosition < -period / 2) {
226 3641 vvtPosition += period;
227 }
228
2/2
✓ Branch 0 taken 198 times.
✓ Branch 1 taken 822 times.
2/2
✓ Decision 'true' taken 198 times.
✓ Decision 'false' taken 822 times.
1020 while (vvtPosition >= period / 2) {
229 198 vvtPosition -= period;
230 }
231 822 return vvtPosition;
232 }
233
234 6393 static void logVvtFront(bool useOnlyRise, bool isImportantFront, TriggerValue front, efitick_t nowNt, int index) {
235
4/4
✓ Branch 0 taken 4738 times.
✓ Branch 1 taken 1655 times.
✓ Branch 2 taken 416 times.
✓ Branch 3 taken 4322 times.
2/2
✓ Decision 'true' taken 2071 times.
✓ Decision 'false' taken 4322 times.
6393 if (!useOnlyRise || engineConfiguration->displayLogicLevelsInEngineSniffer) {
236 // If we care about both edges OR displayLogicLevel is set, log every front exactly as it is
237 2071 addEngineSnifferVvtEvent(index, front == TriggerValue::RISE ? FrontDirection::UP : FrontDirection::DOWN);
238
239 #if EFI_TOOTH_LOGGER
240 2071 LogTriggerCamTooth(front == TriggerValue::RISE, nowNt, index);
241 #endif /* EFI_TOOTH_LOGGER */
242 } else {
243
2/2
✓ Branch 0 taken 2317 times.
✓ Branch 1 taken 2005 times.
2/2
✓ Decision 'true' taken 2317 times.
✓ Decision 'false' taken 2005 times.
4322 if (isImportantFront) {
244 // On the important edge, log a rise+fall pair, and nothing on the real falling edge
245 2317 addEngineSnifferVvtEvent(index, FrontDirection::UP);
246 2317 addEngineSnifferVvtEvent(index, FrontDirection::DOWN);
247
248 #if EFI_TOOTH_LOGGER
249 2317 LogTriggerCamTooth(true, nowNt, index);
250 2317 LogTriggerCamTooth(false, nowNt, index);
251 #endif /* EFI_TOOTH_LOGGER */
252 }
253 }
254 6393 }
255
256 69508 static bool tooSoonToHandleSignal() {
257 #if EFI_PROD_CODE
258 extern bool main_loop_started;
259 if (!main_loop_started) {
260 warning(ObdCode::CUSTOM_ERR_INPUT_DURING_INITIALISATION, "event too early");
261 return true;
262 }
263 #endif //EFI_PROD_CODE
264 69508 return false;
265 }
266
267 /**
268 * This function is called by all "hardware" trigger inputs:
269 * - Hardware triggers
270 * - Trigger replay from CSV (unit tests)
271 */
272 5887 void hwHandleVvtCamSignal(bool isRising, efitick_t nowNt, int index) {
273 5887 int camIndex = CAM_BY_INDEX(index);
274
2/2
✓ Branch 0 taken 5662 times.
✓ Branch 1 taken 225 times.
5887 bool invertSetting = camIndex == 0 ? engineConfiguration->invertCamVVTSignal : engineConfiguration->invertExhaustCamVVTSignal;
275
276
2/2
✓ Branch 0 taken 2950 times.
✓ Branch 1 taken 2937 times.
2/2
✓ Decision 'true' taken 2950 times.
✓ Decision 'false' taken 2937 times.
5887 if (isRising ^ invertSetting) {
277 2950 hwHandleVvtCamSignal(TriggerValue::RISE, nowNt, index);
278 } else {
279 2937 hwHandleVvtCamSignal(TriggerValue::FALL, nowNt, index);
280 }
281 5887 }
282
283 // 'invertCamVVTSignal' is already accounted by the time this method is invoked
284 6393 void hwHandleVvtCamSignal(TriggerValue front, efitick_t nowNt, int index) {
285
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6393 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 6393 times.
6393 if (tooSoonToHandleSignal()) {
286 return;
287 }
288 6393 TriggerCentral *tc = getTriggerCentral();
289
2/4
✓ Branch 0 taken 6393 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6393 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 6393 times.
6393 if (tc->directSelfStimulation || !tc->hwTriggerInputEnabled) {
290 // sensor noise + self-stim = loss of trigger sync
291 return;
292 }
293 6393 handleVvtCamSignal(front, nowNt, index);
294 }
295
296 /**
297 * @returns true if tooth should be ignored
298 */
299 779 PUBLIC_API_WEAK bool skipToothSpecialShape(size_t index, vvt_mode_e vvtMode, angle_t currentPosition) {
300 UNUSED(index);
301
302
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 746 times.
779 switch(vvtMode) {
303
1/1
✓ Decision 'true' taken 33 times.
33 case VVT_TOYOTA_3_TOOTH:
304 {
305 33 int from = engineConfiguration->camDecoder2jzPosition - engineConfiguration->camDecoder2jzPrecision;
306 33 int to = engineConfiguration->camDecoder2jzPosition + engineConfiguration->camDecoder2jzPrecision;
307 // we do not know if we are in sync or out of sync, so we have to be looking for both possibilities
308
3/4
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
✓ Branch 3 taken 1 time.
2/2
✓ Decision 'true' taken 32 times.
✓ Decision 'false' taken 1 time.
33 if ((currentPosition < from || currentPosition > to) &&
309
3/4
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 21 times.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
32 (currentPosition < from + 360 || currentPosition > to + 360)) {
310 // outside of the expected range
311 32 return true;
312 }
313 }
314 1 break;
315
1/1
✓ Decision 'true' taken 746 times.
746 default:
316
317 // else, do nothing
318 746 break;
319 }
320 747 return false;
321 }
322
323 6393 void handleVvtCamSignal(TriggerValue front, efitick_t nowNt, int index) {
324
1/1
✓ Branch 1 taken 6393 times.
6393 TriggerCentral *tc = getTriggerCentral();
325
2/2
✓ Branch 0 taken 5932 times.
✓ Branch 1 taken 461 times.
2/2
✓ Decision 'true' taken 5932 times.
✓ Decision 'false' taken 461 times.
6393 if (index == 0) {
326 5932 engine->outputChannels.vvtChannel1 = front == TriggerValue::RISE;
327
2/2
✓ Branch 0 taken 227 times.
✓ Branch 1 taken 234 times.
2/2
✓ Decision 'true' taken 227 times.
✓ Decision 'false' taken 234 times.
461 } else if (index == 1) {
328 227 engine->outputChannels.vvtChannel2 = front == TriggerValue::RISE;
329
2/2
✓ Branch 0 taken 232 times.
✓ Branch 1 taken 2 times.
2/2
✓ Decision 'true' taken 232 times.
✓ Decision 'false' taken 2 times.
234 } else if (index == 2) {
330 232 engine->outputChannels.vvtChannel3 = front == TriggerValue::RISE;
331
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 2 times.
✗ Decision 'false' not taken.
2 } else if (index == 3) {
332 2 engine->outputChannels.vvtChannel4 = front == TriggerValue::RISE;
333 }
334
335 6393 int bankIndex = BANK_BY_INDEX(index);
336 6393 int camIndex = CAM_BY_INDEX(index);
337
2/2
✓ Branch 0 taken 3357 times.
✓ Branch 1 taken 3036 times.
2/2
✓ Decision 'true' taken 3357 times.
✓ Decision 'false' taken 3036 times.
6393 if (front == TriggerValue::RISE) {
338 3357 tc->vvtEventRiseCounter[index]++;
339 } else {
340 3036 tc->vvtEventFallCounter[index]++;
341 }
342
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6393 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 6393 times.
6393 if (engineConfiguration->vvtMode[camIndex] == VVT_INACTIVE) {
343 warning(ObdCode::CUSTOM_VVT_MODE_NOT_SELECTED, "VVT: event on %d but no mode", camIndex);
344 }
345
346 6393 const auto& vvtShape = tc->vvtShape[camIndex];
347
348
1/1
✓ Branch 2 taken 6393 times.
6393 bool isVvtWithRealDecoder = vvtWithRealDecoder(engineConfiguration->vvtMode[camIndex]);
349
350 // Non real decoders only use the rising edge
351
4/4
✓ Branch 0 taken 5801 times.
✓ Branch 1 taken 592 times.
✓ Branch 2 taken 4146 times.
✓ Branch 3 taken 1655 times.
6393 bool vvtUseOnlyRise = !isVvtWithRealDecoder || vvtShape.useOnlyRisingEdges;
352
4/4
✓ Branch 0 taken 4738 times.
✓ Branch 1 taken 1655 times.
✓ Branch 2 taken 2526 times.
✓ Branch 3 taken 2212 times.
6393 bool isImportantFront = !vvtUseOnlyRise || (front == TriggerValue::RISE);
353
354
1/1
✓ Branch 1 taken 6393 times.
6393 logVvtFront(vvtUseOnlyRise, isImportantFront, front, nowNt, index);
355
356
2/2
✓ Branch 0 taken 2212 times.
✓ Branch 1 taken 4181 times.
2/2
✓ Decision 'true' taken 2212 times.
✓ Decision 'false' taken 4181 times.
6393 if (!isImportantFront) {
357 // This edge is unimportant, ignore it.
358 2212 return;
359 }
360
361 // If the main trigger is not synchronized, don't decode VVT yet
362
3/3
✓ Branch 1 taken 4181 times.
✓ Branch 3 taken 235 times.
✓ Branch 4 taken 3946 times.
2/2
✓ Decision 'true' taken 235 times.
✓ Decision 'false' taken 3946 times.
4181 if (!tc->triggerState.getShaftSynchronized()) {
363 235 return;
364 }
365
366 3946 TriggerDecoderBase& vvtDecoder = tc->vvtState[bankIndex][camIndex];
367
368
2/2
✓ Branch 0 taken 3517 times.
✓ Branch 1 taken 429 times.
2/2
✓ Decision 'true' taken 3517 times.
✓ Decision 'false' taken 429 times.
3946 if (isVvtWithRealDecoder) {
369
3/3
✓ Branch 0 taken 2778 times.
✓ Branch 1 taken 739 times.
✓ Branch 3 taken 3517 times.
7034 vvtDecoder.decodeTriggerEvent(
370 "vvt",
371 vvtShape,
372 nullptr,
373 3517 tc->vvtTriggerConfiguration[camIndex],
374 front == TriggerValue::RISE ? SHAFT_PRIMARY_RISING : SHAFT_PRIMARY_FALLING, nowNt);
375 3517 vvtDecoder.vvtToothDurations0 = (uint32_t)NT2US(vvtDecoder.toothDurations[0]);
376 }
377
378 // here we count all cams together
379 3946 tc->vvtCamCounter++;
380
381
1/1
✓ Branch 2 taken 3946 times.
3946 auto currentPhase = tc->getCurrentEnginePhase(nowNt);
382
2/2
✓ Branch 1 taken 66 times.
✓ Branch 2 taken 3880 times.
2/2
✓ Decision 'true' taken 66 times.
✓ Decision 'false' taken 3880 times.
3946 if (!currentPhase) {
383 // If we couldn't resolve engine speed (yet primary trigger is sync'd), this
384 // probably means that we have partial crank sync, but not RPM information yet
385 66 return;
386 }
387
388 3880 angle_t angleFromPrimarySyncPoint = currentPhase.Value;
389 // convert trigger cycle angle into engine cycle angle
390
3/3
✓ Branch 1 taken 3880 times.
✓ Branch 3 taken 166 times.
✓ Branch 4 taken 3714 times.
3880 angle_t currentPosition = angleFromPrimarySyncPoint - tdcPosition();
391 // https://github.com/rusefi/rusefi/issues/1713 currentPosition could be negative that's expected
392
393 #if EFI_UNIT_TEST
394 3880 tc->currentVVTEventPosition[bankIndex][camIndex] = currentPosition;
395 #endif // EFI_UNIT_TEST
396
397 3880 tc->triggerState.vvtCurrentPosition = currentPosition;
398
399
4/4
✓ Branch 0 taken 3455 times.
✓ Branch 1 taken 425 times.
✓ Branch 2 taken 3101 times.
✓ Branch 3 taken 354 times.
2/2
✓ Decision 'true' taken 3101 times.
✓ Decision 'false' taken 779 times.
3880 if (isVvtWithRealDecoder && vvtDecoder.currentCycle.current_index != 0) {
400 // this is not sync tooth - exiting
401 3101 return;
402 }
403
404 779 auto vvtPosition = engineConfiguration->vvtOffsets[bankIndex * CAMS_PER_BANK + camIndex] - currentPosition;
405 779 tc->triggerState.vvtToothPosition[index] = vvtPosition;
406
407
1/1
✓ Branch 2 taken 779 times.
779 bool skipTooth = skipToothSpecialShape(index, engineConfiguration->vvtMode[camIndex], currentPosition);
408
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 747 times.
2/2
✓ Decision 'true' taken 32 times.
✓ Decision 'false' taken 747 times.
779 if (skipTooth) {
409 32 return;
410 }
411
412 // this could be just an 'if' but let's have it expandable for future use :)
413
2/2
✓ Branch 1 taken 75 times.
✓ Branch 2 taken 672 times.
747 switch(engineConfiguration->vvtMode[camIndex]) {
414
1/1
✓ Decision 'true' taken 75 times.
75 case VVT_HONDA_K_INTAKE:
415 // honda K has four tooth in VVT intake trigger, so we just wrap each of those to 720 / 4
416 75 vvtPosition = wrapVvt(vvtPosition, 180);
417 75 break;
418
1/1
✓ Decision 'true' taken 672 times.
672 default:
419 // else, do nothing
420 672 break;
421 }
422
423 #if EFI_PROD_CODE
424 if (!isBrainPinValid(engineConfiguration->camInputs[engineConfiguration->engineSyncCam]) &&
425 engineConfiguration->vvtMode[engineConfiguration->engineSyncCam] != VVT_MAP_V_TWIN) {
426 criticalError("Selected engine sync input not configured: %d", engineConfiguration->engineSyncCam);
427 }
428 #endif // EFI_PROD_CODE
429
430 // Only do engine sync using one cam, other cams just provide VVT position.
431
2/2
✓ Branch 0 taken 648 times.
✓ Branch 1 taken 99 times.
2/2
✓ Decision 'true' taken 648 times.
✓ Decision 'false' taken 99 times.
747 if (index == engineConfiguration->engineSyncCam) {
432
1/1
✓ Branch 1 taken 648 times.
648 angle_t crankOffset = adjustCrankPhase(camIndex);
433 // vvtPosition was calculated against wrong crank zero position. Now that we have adjusted crank position we
434 // shall adjust vvt position as well
435 648 vvtPosition -= crankOffset;
436 648 vvtPosition = wrapVvt(vvtPosition, FOUR_STROKE_CYCLE_DURATION);
437
438
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 648 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 648 times.
648 if (absF(angleFromPrimarySyncPoint) < 7) {
439 /**
440 * we prefer not to have VVT sync right at trigger sync so that we do not have phase detection error if things happen a bit in
441 * wrong order due to belt flex or else
442 * https://github.com/rusefi/rusefi/issues/3269
443 */
444 warning(ObdCode::CUSTOM_VVT_SYNC_POSITION, "VVT sync position too close to trigger sync");
445 }
446 } else {
447 // Not using this cam for engine sync, just wrap the value in to the reasonable range
448 99 vvtPosition = wrapVvt(vvtPosition, FOUR_STROKE_CYCLE_DURATION);
449 }
450
451 // Only record VVT position if we have full engine sync - may be bogus before that point
452
2/2
✓ Branch 1 taken 731 times.
✓ Branch 2 taken 16 times.
2/2
✓ Decision 'true' taken 731 times.
✓ Decision 'false' taken 16 times.
747 if (tc->triggerState.hasSynchronizedPhase()) {
453 731 tc->vvtPosition[bankIndex][camIndex] = vvtPosition;
454 } else {
455 16 tc->vvtPosition[bankIndex][camIndex] = 0;
456 }
457 }
458
459 int triggerReentrant = 0;
460 int maxTriggerReentrant = 0;
461 uint32_t triggerDuration;
462 uint32_t triggerMaxDuration = 0;
463
464 /**
465 * This function is called by all "hardware" trigger inputs:
466 * - Hardware triggers
467 * - Trigger replay from CSV (unit tests)
468 */
469 63115 void hwHandleShaftSignal(int signalIndex, bool isRising, efitick_t timestamp) {
470
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 63115 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 63115 times.
63115 if (tooSoonToHandleSignal()) {
471 return;
472 }
473
1/1
✓ Branch 1 taken 63115 times.
63115 TriggerCentral *tc = getTriggerCentral();
474 63115 ScopePerf perf(PE::HandleShaftSignal);
475
476
2/4
✓ Branch 0 taken 63115 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 63115 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 63115 times.
63115 if (tc->directSelfStimulation || !tc->hwTriggerInputEnabled) {
477 // sensor noise + self-stim = loss of trigger sync
478 return;
479 }
480
481
1/1
✓ Branch 1 taken 63115 times.
63115 handleShaftSignal(signalIndex, isRising, timestamp);
482 }
483
484 // Handle all shaft signals - hardware or emulated both
485 71952 void handleShaftSignal(int signalIndex, bool isRising, efitick_t timestamp) {
486 71952 bool isPrimary = signalIndex == 0;
487
4/6
✓ Branch 0 taken 726 times.
✓ Branch 1 taken 71226 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 726 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 71952 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 71952 times.
71952 if (!isPrimary && !TRIGGER_WAVEFORM(needSecondTriggerInput)) {
488 return;
489 }
490
491 trigger_event_e signal;
492 // todo: add support for 3rd channel
493
2/2
✓ Branch 0 taken 35996 times.
✓ Branch 1 taken 35956 times.
2/2
✓ Decision 'true' taken 35996 times.
✓ Decision 'false' taken 35956 times.
71952 if (isRising) {
494
2/2
✓ Branch 0 taken 35632 times.
✓ Branch 1 taken 364 times.
71992 signal = isPrimary ?
495
2/2
✓ Branch 0 taken 1207 times.
✓ Branch 1 taken 34425 times.
35632 (engineConfiguration->invertPrimaryTriggerSignal ? SHAFT_PRIMARY_FALLING : SHAFT_PRIMARY_RISING) :
496
2/2
✓ Branch 0 taken 154 times.
✓ Branch 1 taken 210 times.
364 (engineConfiguration->invertSecondaryTriggerSignal ? SHAFT_SECONDARY_FALLING : SHAFT_SECONDARY_RISING);
497 } else {
498
2/2
✓ Branch 0 taken 35594 times.
✓ Branch 1 taken 362 times.
71912 signal = isPrimary ?
499
2/2
✓ Branch 0 taken 1205 times.
✓ Branch 1 taken 34389 times.
35594 (engineConfiguration->invertPrimaryTriggerSignal ? SHAFT_PRIMARY_RISING : SHAFT_PRIMARY_FALLING) :
500
2/2
✓ Branch 0 taken 153 times.
✓ Branch 1 taken 209 times.
362 (engineConfiguration->invertSecondaryTriggerSignal ? SHAFT_SECONDARY_RISING : SHAFT_SECONDARY_FALLING);
501 }
502
2/2
✓ Branch 0 taken 71226 times.
✓ Branch 1 taken 726 times.
2/2
✓ Decision 'true' taken 71226 times.
✓ Decision 'false' taken 726 times.
71952 if (isPrimary) {
503 71226 engine->outputChannels.triggerChannel1 = signal == SHAFT_PRIMARY_RISING;
504 } else {
505 726 engine->outputChannels.triggerChannel2 = signal == SHAFT_SECONDARY_RISING;
506 }
507
508 // Don't accept trigger input in case of some problems
509
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 71952 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 71952 times.
71952 if (!getLimpManager()->allowTriggerInput()) {
510 return;
511 }
512
513 #if EFI_TOOTH_LOGGER
514 // Log to the Tunerstudio tooth logger
515 // We want to do this before anything else as we
516 // actually want to capture any noise/jitter that may be occurring
517
518
3/4
✓ Branch 0 taken 966 times.
✓ Branch 1 taken 70986 times.
✓ Branch 3 taken 966 times.
✗ Branch 4 not taken.
71952 bool logLogicState = engineConfiguration->displayLogicLevelsInEngineSniffer && getTriggerCentral()->triggerShape.useOnlyRisingEdges;
519
520
2/2
✓ Branch 0 taken 70986 times.
✓ Branch 1 taken 966 times.
2/2
✓ Decision 'true' taken 70986 times.
✓ Decision 'false' taken 966 times.
71952 if (!logLogicState) {
521 // we log physical state even if displayLogicLevelsInEngineSniffer if both fronts are used by decoder
522 70986 LogTriggerTooth(signal, timestamp);
523 }
524
525 #endif /* EFI_TOOTH_LOGGER */
526
527 // for effective noise filtering, we need both signal edges,
528 // so we pass them to handleShaftSignal() and defer this test
529
2/2
✓ Branch 0 taken 70044 times.
✓ Branch 1 taken 1908 times.
2/2
✓ Decision 'true' taken 70044 times.
✓ Decision 'false' taken 1908 times.
71952 if (!engineConfiguration->useNoiselessTriggerDecoder) {
530
2/2
✓ Branch 2 taken 32274 times.
✓ Branch 3 taken 37770 times.
2/2
✓ Decision 'true' taken 32274 times.
✓ Decision 'false' taken 37770 times.
70044 if (!isUsefulSignal(signal, getTriggerCentral()->triggerShape)) {
531 /**
532 * no need to process VR falls further
533 */
534 32274 return;
535 }
536 }
537
538 #if EFI_TOOTH_LOGGER
539
2/2
✓ Branch 0 taken 483 times.
✓ Branch 1 taken 39195 times.
2/2
✓ Decision 'true' taken 483 times.
✓ Decision 'false' taken 39195 times.
39678 if (logLogicState) {
540 // first log rising normally
541 483 LogTriggerTooth(signal, timestamp);
542 // in 'logLogicState' mode we log opposite front right after logical rising away
543
1/2
✓ Branch 0 taken 483 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 483 times.
✗ Decision 'false' not taken.
483 if (signal == SHAFT_PRIMARY_RISING) {
544 483 LogTriggerTooth(SHAFT_PRIMARY_FALLING, timestamp);
545 } else {
546 LogTriggerTooth(SHAFT_SECONDARY_FALLING, timestamp);
547 }
548 }
549 #endif /* EFI_TOOTH_LOGGER */
550
551 39678 uint32_t triggerHandlerEntryTime = getTimeNowLowerNt();
552
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39678 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 39678 times.
39678 if (triggerReentrant > maxTriggerReentrant)
553 maxTriggerReentrant = triggerReentrant;
554 39678 triggerReentrant++;
555
556 39678 getTriggerCentral()->handleShaftSignal(signal, timestamp);
557
558 39678 triggerReentrant--;
559 39678 triggerDuration = getTimeNowLowerNt() - triggerHandlerEntryTime;
560 39678 triggerMaxDuration = maxI(triggerMaxDuration, triggerDuration);
561 }
562
563 void TriggerCentral::resetCounters() {
564 memset(hwEventCounters, 0, sizeof(hwEventCounters));
565 }
566
567 static const int wheelIndeces[4] = { 0, 0, 1, 1};
568
569 38710 static void reportEventToWaveChart(trigger_event_e ckpSignalType, int triggerEventIndex, bool addOppositeEvent) {
570
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 38710 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 38710 times.
38710 if (!getTriggerCentral()->isEngineSnifferEnabled) { // this is here just as a shortcut so that we avoid engine sniffer as soon as possible
571 return; // engineSnifferRpmThreshold is accounted for inside getTriggerCentral()->isEngineSnifferEnabled
572 }
573
574 38710 int wheelIndex = wheelIndeces[(int )ckpSignalType];
575
576 38710 bool isUp = isTriggerUpEvent(ckpSignalType);
577
578
2/2
✓ Branch 0 taken 35977 times.
✓ Branch 1 taken 2733 times.
38710 addEngineSnifferCrankEvent(wheelIndex, triggerEventIndex, isUp ? FrontDirection::UP : FrontDirection::DOWN);
579
2/2
✓ Branch 0 taken 33248 times.
✓ Branch 1 taken 5462 times.
2/2
✓ Decision 'true' taken 33248 times.
✓ Decision 'false' taken 5462 times.
38710 if (addOppositeEvent) {
580 // let's add the opposite event right away
581
1/2
✓ Branch 0 taken 33248 times.
✗ Branch 1 not taken.
33248 addEngineSnifferCrankEvent(wheelIndex, triggerEventIndex, isUp ? FrontDirection::DOWN : FrontDirection::UP);
582 }
583 }
584
585 /**
586 * This is used to filter noise spikes (interference) in trigger signal. See
587 * The basic idea is to use not just edges, but the average amount of time the signal stays in '0' or '1'.
588 * So we update 'accumulated periods' to track where the signal is.
589 * And then compare between the current period and previous, with some tolerance (allowing for the wheel speed change).
590 * @return true if the signal is passed through.
591 */
592 1908 bool TriggerNoiseFilter::noiseFilter(efitick_t nowNt,
593 TriggerDecoderBase * triggerState,
594 trigger_event_e signal) {
595 // todo: find a better place for these defs
596 static const trigger_event_e opposite[4] = { SHAFT_PRIMARY_RISING, SHAFT_PRIMARY_FALLING, SHAFT_SECONDARY_RISING, SHAFT_SECONDARY_FALLING };
597 static const TriggerWheel triggerIdx[4] = { TriggerWheel::T_PRIMARY, TriggerWheel::T_PRIMARY, TriggerWheel::T_SECONDARY, TriggerWheel:: T_SECONDARY };
598 // we process all trigger channels independently
599 1908 TriggerWheel ti = triggerIdx[signal];
600 // falling is opposite to rising, and vise versa
601 1908 trigger_event_e os = opposite[signal];
602
603 // todo: currently only primary channel is filtered, because there are some weird trigger types on other channels
604
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1908 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1908 times.
1908 if (ti != TriggerWheel::T_PRIMARY)
605 return true;
606
607 // update period accumulator: for rising signal, we update '0' accumulator, and for falling - '1'
608
2/2
✓ Branch 1 taken 1894 times.
✓ Branch 2 taken 14 times.
2/2
✓ Decision 'true' taken 1894 times.
✓ Decision 'false' taken 14 times.
1908 if (lastSignalTimes[signal] != -1)
609 1894 accumSignalPeriods[signal] += nowNt - lastSignalTimes[signal];
610 // save current time for this trigger channel
611 1908 lastSignalTimes[signal] = nowNt;
612
613 // now we want to compare current accumulated period to the stored one
614 1908 efitick_t currentPeriod = accumSignalPeriods[signal];
615 // the trick is to compare between different
616 1908 efitick_t allowedPeriod = accumSignalPrevPeriods[os];
617
618 // but first check if we're expecting a gap
619
3/4
✓ Branch 1 taken 1908 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1771 times.
✓ Branch 5 taken 137 times.
3679 bool isGapExpected = TRIGGER_WAVEFORM(isSynchronizationNeeded) && triggerState->getShaftSynchronized() &&
620
2/2
✓ Branch 3 taken 32 times.
✓ Branch 4 taken 1739 times.
1771 (triggerState->currentCycle.eventCount[(int)ti] + 1) == TRIGGER_WAVEFORM(getExpectedEventCount(ti));
621
622
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 1876 times.
2/2
✓ Decision 'true' taken 32 times.
✓ Decision 'false' taken 1876 times.
1908 if (isGapExpected) {
623 // usually we need to extend the period for gaps, based on the trigger info
624 32 allowedPeriod *= TRIGGER_WAVEFORM(syncRatioAvg);
625 }
626
627 // also we need some margin for rapidly changing trigger-wheel speed,
628 // that's why we expect the period to be no less than 2/3 of the previous period (this is just an empirical 'magic' coef.)
629 1908 efitick_t minAllowedPeriod = 2 * allowedPeriod / 3;
630 // but no longer than 5/4 of the previous 'normal' period
631 1908 efitick_t maxAllowedPeriod = 5 * allowedPeriod / 4;
632
633 // above all, check if the signal comes not too early
634
2/2
✓ Branch 0 taken 1864 times.
✓ Branch 1 taken 44 times.
2/2
✓ Decision 'true' taken 1864 times.
✓ Decision 'false' taken 44 times.
1908 if (currentPeriod >= minAllowedPeriod) {
635 // now we store this period as a reference for the next time,
636 // BUT we store only 'normal' periods, and ignore too long periods (i.e. gaps)
637
6/6
✓ Branch 0 taken 1847 times.
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 1825 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 1580 times.
✓ Branch 5 taken 245 times.
2/2
✓ Decision 'true' taken 1602 times.
✓ Decision 'false' taken 262 times.
1864 if (!isGapExpected && (maxAllowedPeriod == 0 || currentPeriod <= maxAllowedPeriod)) {
638 1602 accumSignalPrevPeriods[signal] = currentPeriod;
639 }
640 // reset accumulator
641 1864 accumSignalPeriods[signal] = 0;
642 1864 return true;
643 }
644 // all premature or extra-long events are ignored - treated as interference
645 44 return false;
646 }
647
648 46 bool TriggerCentral::isMapCamSync(efitick_t timestamp, float currentPhase) {
649 UNUSED(timestamp);
650
651 // we are trying to figure out which 360 half of the total 720 degree cycle is which, so we compare those in 360 degree sense.
652 46 auto toothAngle360 = currentPhase;
653
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 46 times.
2/2
✓ Decision 'true' taken 20 times.
✓ Decision 'false' taken 46 times.
66 while (toothAngle360 >= 360) {
654 20 toothAngle360 -= 360;
655 }
656
657 bool result;
658
4/4
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 21 times.
✓ Branch 2 taken 23 times.
✓ Branch 3 taken 2 times.
2/2
✓ Decision 'true' taken 23 times.
✓ Decision 'false' taken 23 times.
46 if (mapCamPrevToothAngle < engineConfiguration->mapCamDetectionAnglePosition && toothAngle360 > engineConfiguration->mapCamDetectionAnglePosition) {
659 // we are somewhere close to 'mapCamDetectionAnglePosition'
660
661 // warning: hack hack hack
662 23 float map = engine->outputChannels.instantMAPValue;
663
664 // Compute diff against the last time we were here
665 23 float instantMapDiffBetweenReadoutAngles = map - mapCamPrevCycleValue;
666 23 mapCamPrevCycleValue = map;
667
668
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 21 times.
2/2
✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 21 times.
23 if (instantMapDiffBetweenReadoutAngles > engineConfiguration->mapSyncThreshold) {
669 2 mapVvt_sync_counter++;
670 2 int revolutionCounter = getTriggerCentral()->triggerState.getSynchronizationCounter();
671 2 mapVvt_MAP_AT_CYCLE_COUNT = revolutionCounter - prevChangeAtCycle;
672 2 prevChangeAtCycle = revolutionCounter;
673 2 result = true;
674 } else {
675 21 result = false;
676 }
677
678 23 mapVvt_MAP_AT_SPECIAL_POINT = map;
679 23 mapVvt_MAP_AT_DIFF = instantMapDiffBetweenReadoutAngles;
680 23 } else {
681 23 result = false;
682 }
683
684 46 mapCamPrevToothAngle = toothAngle360;
685 46 return result;
686 }
687
688 #ifdef TEMP_V_TWIN
689
690 float mapAtAngle[200];
691
692 #endif
693
694 33453 void TriggerCentral::decodeMapCam(int toothIndexForListeners, efitick_t timestamp, float currentPhase) {
695 UNUSED(toothIndexForListeners);
696
697
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 33407 times.
33499 isDecodingMapCam = engineConfiguration->vvtMode[0] == VVT_MAP_V_TWIN &&
698
1/2
✓ Branch 1 taken 46 times.
✗ Branch 2 not taken.
46 Sensor::getOrZero(SensorType::Rpm) < engineConfiguration->cranking.rpm;
699
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 33407 times.
2/2
✓ Decision 'true' taken 46 times.
✓ Decision 'false' taken 33407 times.
33453 if (isDecodingMapCam) {
700
701
702 #ifdef TEMP_V_TWIN
703 mapAtAngle[toothIndexForListeners] = engine->outputChannels.instantMAPValue;
704
705 if (toothIndexForListeners > 2) {
706 if (mapAtAngle[toothIndexForListeners - 2] > mapAtAngle[toothIndexForListeners - 1] &&
707 mapAtAngle[toothIndexForListeners - 1] < mapAtAngle[toothIndexForListeners - 0]) {
708 mapVvt_min_point_counter++;
709 }
710
711 }
712 #endif
713
714
715
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 44 times.
2/2
✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 44 times.
46 if (isMapCamSync(timestamp, currentPhase)) {
716 2 hwHandleVvtCamSignal(TriggerValue::RISE, timestamp, /*index*/0);
717 2 hwHandleVvtCamSignal(TriggerValue::FALL, timestamp, /*index*/0);
718 #if EFI_UNIT_TEST
719 // hack? feature? existing unit test relies on VVT phase available right away
720 // but current implementation which is based on periodicFastCallback would only make result available on NEXT tooth
721 2 getLimpManager()->onFastCallback();
722 #endif // EFI_UNIT_TEST
723 }
724 }
725 33453 }
726
727 38711 bool TriggerCentral::isToothExpectedNow(efitick_t timestamp) {
728 // Check that the expected next phase (from the last tooth) is close to the actual current phase:
729 // basically, check that the tooth width is correct
730
1/1
✓ Branch 2 taken 38711 times.
38711 auto estimatedCurrentPhase = getCurrentEnginePhase(timestamp);
731 38711 auto lastToothPhase = m_lastToothPhaseFromSyncPoint;
732
733
6/6
✓ Branch 1 taken 33359 times.
✓ Branch 2 taken 5352 times.
✓ Branch 4 taken 33045 times.
✓ Branch 5 taken 314 times.
✓ Branch 6 taken 33045 times.
✓ Branch 7 taken 5666 times.
2/2
✓ Decision 'true' taken 33045 times.
✓ Decision 'false' taken 5666 times.
38711 if (expectedNextPhase && estimatedCurrentPhase) {
734 33045 float angleError = expectedNextPhase.Value - estimatedCurrentPhase.Value;
735
736 // Wrap around correctly at the end of the cycle
737
1/1
✓ Branch 1 taken 33045 times.
33045 float cycle = getEngineState()->engineCycle;
738
2/2
✓ Branch 0 taken 2398 times.
✓ Branch 1 taken 30647 times.
2/2
✓ Decision 'true' taken 2398 times.
✓ Decision 'false' taken 30647 times.
33045 if (angleError < -cycle / 2) {
739 2398 angleError += cycle;
740 }
741
742 // positive value - tooth received earlier than expected
743 // negative value - tooth received later than expected
744 33045 triggerToothAngleError = angleError;
745
746 // Only perform checks if engine is spinning quickly
747 // All kinds of garbage happens while cranking
748
3/3
✓ Branch 1 taken 33045 times.
✓ Branch 3 taken 16018 times.
✓ Branch 4 taken 17027 times.
2/2
✓ Decision 'true' taken 16018 times.
✓ Decision 'false' taken 17027 times.
33045 if (Sensor::getOrZero(SensorType::Rpm) > 1000) {
749 // Now compute how close we are to the last tooth decoded
750 16018 float angleSinceLastTooth = estimatedCurrentPhase.Value - lastToothPhase;
751
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 16017 times.
2/2
✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 16017 times.
16018 if (angleSinceLastTooth < 0.5f) {
752 // This tooth came impossibly early, ignore it
753 // This rejects things like doubled edges, for example:
754 // |-| |----------------
755 // | | |
756 // ____________| |_|
757 // 1 2
758 // #1 will be decoded
759 // #2 will be ignored
760 // We're not sure which edge was the "real" one, but they were close enough
761 // together that it doesn't really matter.
762
1/1
✓ Branch 1 taken 1 time.
1 warning(ObdCode::CUSTOM_PRIMARY_DOUBLED_EDGE, "doubled trigger edge after %.2f deg at #%d", angleSinceLastTooth, triggerState.currentCycle.current_index);
763
764 1 return false;
765 }
766
767 // Absolute error from last tooth
768 16017 float absError = absF(angleError);
769
2/3
✓ Branch 1 taken 16017 times.
✓ Branch 3 taken 16017 times.
✗ Branch 4 not taken.
16017 float isRpmEnough = Sensor::getOrZero(SensorType::Rpm) > 1000;
770 // TODO: configurable threshold
771
5/6
✓ Branch 0 taken 16017 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 176 times.
✓ Branch 3 taken 15841 times.
✓ Branch 4 taken 171 times.
✓ Branch 5 taken 5 times.
2/2
✓ Decision 'true' taken 171 times.
✓ Decision 'false' taken 15846 times.
16017 if (isRpmEnough && absError > 10 && absError < 180) {
772 // This tooth came at a very unexpected time, ignore it
773
3/3
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 79 times.
✓ Branch 3 taken 171 times.
171 warning((angleError > 0) ? ObdCode::CUSTOM_PRIMARY_BAD_TOOTH_TIMING_EARLY : ObdCode::CUSTOM_PRIMARY_BAD_TOOTH_TIMING_LATE,
774 "tooth #%d error of %.1f", triggerState.currentCycle.current_index, angleError);
775
776 // TODO: this causes issues with some real engine logs, should it?
777 // return false;
778 }
779 }
780 } else {
781 5666 triggerToothAngleError = 0;
782 }
783
784 // We aren't ready to reject unexpected teeth, so accept this tooth
785 38710 return true;
786 }
787
788 38710 PUBLIC_API_WEAK bool boardAllowTriggerActions() { return true; }
789
790 33453 angle_t TriggerCentral::findNextTriggerToothAngle(int p_currentToothIndex) {
791 33453 int currentToothIndex = p_currentToothIndex;
792 // TODO: is this logic to compute next trigger tooth angle correct?
793 33453 angle_t nextToothAngle = 0;
794
795 33453 int loopAllowance = 2 * engineCycleEventCount + 1000;
796 do {
797 // I don't love this.
798 101931 currentToothIndex = (currentToothIndex + 1) % engineCycleEventCount;
799
4/4
✓ Branch 1 taken 101931 times.
✓ Branch 5 taken 101931 times.
✓ Branch 7 taken 910 times.
✓ Branch 8 taken 101021 times.
101931 nextToothAngle = getTriggerCentral()->triggerFormDetails.eventAngles[currentToothIndex] - tdcPosition();
800
1/1
✓ Branch 1 taken 101931 times.
101931 wrapAngle(nextToothAngle, "nextEnginePhase", ObdCode::CUSTOM_ERR_6555);
801
6/6
✓ Branch 0 taken 68518 times.
✓ Branch 1 taken 33413 times.
✓ Branch 2 taken 68478 times.
✓ Branch 3 taken 40 times.
✓ Branch 4 taken 68478 times.
✓ Branch 5 taken 33453 times.
0/1
? Decision couldn't be analyzed.
101931 } while (nextToothAngle == currentEngineDecodedPhase && --loopAllowance > 0); // '==' for float works here since both values come from 'eventAngles' array
802
3/4
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 33413 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 40 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 33453 times.
33453 if (loopAllowance == 0 && nextToothAngle != currentEngineDecodedPhase) {
803 // HW CI fails here, looks like we sometimes change trigger while still handling it?
804 // Note: for single-tooth triggers, all eventAngles map to the same engine phase,
805 // so nextToothAngle == currentEngineDecodedPhase is expected and not an error.
806 // see #9045 for report of this problem
807 firmwareError(ObdCode::CUSTOM_ERR_TRIGGER_ZERO, "handleShaftSignal unexpected loop end %d %d %f %f", p_currentToothIndex, engineCycleEventCount, nextToothAngle, currentEngineDecodedPhase);
808 }
809 33453 return nextToothAngle;
810 }
811
812 /**
813 * This method is NOT invoked for VR falls.
814 */
815 39680 void TriggerCentral::handleShaftSignal(trigger_event_e signal, efitick_t timestamp) {
816
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39680 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 39680 times.
39680 if (triggerShape.shapeDefinitionError) {
817 // trigger is broken, we cannot do anything here
818 warning(ObdCode::CUSTOM_ERR_UNEXPECTED_SHAFT_EVENT, "Shaft event while trigger is mis-configured");
819 return;
820 }
821
822 // This code gathers some statistics on signals and compares accumulated periods to filter interference
823
2/2
✓ Branch 0 taken 1908 times.
✓ Branch 1 taken 37772 times.
2/2
✓ Decision 'true' taken 1908 times.
✓ Decision 'false' taken 37772 times.
39680 if (engineConfiguration->useNoiselessTriggerDecoder) {
824
3/3
✓ Branch 1 taken 1908 times.
✓ Branch 3 taken 44 times.
✓ Branch 4 taken 1864 times.
2/2
✓ Decision 'true' taken 44 times.
✓ Decision 'false' taken 1864 times.
1908 if (!noiseFilter.noiseFilter(timestamp, &triggerState, signal)) {
825 44 return;
826 }
827
3/3
✓ Branch 1 taken 1864 times.
✓ Branch 3 taken 925 times.
✓ Branch 4 taken 939 times.
2/2
✓ Decision 'true' taken 925 times.
✓ Decision 'false' taken 939 times.
1864 if (!isUsefulSignal(signal, triggerShape)) {
828 925 return;
829 }
830 }
831
832
3/3
✓ Branch 1 taken 38711 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 38710 times.
2/2
✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 38710 times.
38711 if (!isToothExpectedNow(timestamp)) {
833 1 triggerIgnoredToothCount++;
834 1 return;
835 }
836
837 38710 isSpinningJustForWatchdog = true;
838
839 #if EFI_HD_ACR
840 bool firstEventInAWhile = m_lastEventTimer.hasElapsedSec(1);
841 if (firstEventInAWhile) {
842 // let's open that valve on first sign of movement
843 engine->module<HarleyAcr>()->updateAcr();
844 }
845 #endif // EFI_HD_ACR
846
847
2/3
✓ Branch 1 taken 38710 times.
✓ Branch 3 taken 38710 times.
✗ Branch 4 not taken.
1/2
✓ Decision 'true' taken 38710 times.
✗ Decision 'false' not taken.
38710 if (boardAllowTriggerActions()) {
848
1/1
✓ Branch 1 taken 38710 times.
38710 m_lastEventTimer.reset(timestamp);
849 }
850
851 38710 int eventIndex = (int) signal;
852
2/5
✓ Branch 0 taken 38710 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 38710 times.
✗ Branch 5 not taken.
38710 efiAssertVoid(ObdCode::CUSTOM_TRIGGER_EVENT_TYPE, eventIndex >= 0 && eventIndex < HW_EVENT_TYPES, "signal type");
853 38710 hwEventCounters[eventIndex]++;
854
855 // Decode the trigger!
856 38710 auto decodeResult = triggerState.decodeTriggerEvent(
857 "trigger",
858
1/1
✓ Branch 1 taken 38710 times.
38710 triggerShape,
859 engine,
860 primaryTriggerConfiguration,
861 signal, timestamp);
862
863 // Don't propagate state if we don't know where we are
864
2/2
✓ Branch 1 taken 33453 times.
✓ Branch 2 taken 5257 times.
2/2
✓ Decision 'true' taken 33453 times.
✓ Decision 'false' taken 5257 times.
38710 if (decodeResult) {
865 33453 ScopePerf perf(PE::ShaftPositionListeners);
866
867 /**
868 * If we only have a crank position sensor with four stroke, here we are extending crank revolutions with a 360 degree
869 * cycle into a four stroke, 720 degrees cycle.
870 */
871
2/2
✓ Branch 1 taken 33453 times.
✓ Branch 4 taken 33453 times.
33453 int crankDivider = getCrankDivider(triggerShape.getWheelOperationMode());
872
1/1
✓ Branch 1 taken 33453 times.
33453 int crankInternalIndex = triggerState.getSynchronizationCounter() % crankDivider;
873
1/1
✓ Branch 1 taken 33453 times.
33453 int triggerIndexForListeners = decodeResult.Value.CurrentIndex + (crankInternalIndex * triggerShape.getSize());
874
875
1/1
✓ Branch 1 taken 33453 times.
33453 reportEventToWaveChart(signal, triggerIndexForListeners, triggerShape.useOnlyRisingEdges);
876
877 // Look up this tooth's angle from the sync point. If this tooth is the sync point, we'll get 0 here.
878
1/1
✓ Branch 1 taken 33453 times.
33453 auto currentPhaseFromSyncPoint = getTriggerCentral()->triggerFormDetails.eventAngles[triggerIndexForListeners];
879
880 // Adjust so currentPhase is in engine-space angle, not trigger-space angle
881
4/4
✓ Branch 1 taken 33453 times.
✓ Branch 3 taken 455 times.
✓ Branch 4 taken 32998 times.
✓ Branch 6 taken 33453 times.
33453 currentEngineDecodedPhase = wrapAngleMethod(currentPhaseFromSyncPoint - tdcPosition(), "currentEnginePhase", ObdCode::CUSTOM_ERR_6555);
882
883 // Record precise time and phase of the engine. This is used for VVT decode, and to check that the
884 // trigger pattern selected matches reality (ie, we check the next tooth is where we think it should be)
885 {
886 // under lock to avoid mismatched tooth phase and time
887 chibios_rt::CriticalSectionLocker csl;
888
889
1/1
✓ Branch 1 taken 33453 times.
33453 m_lastToothTimer.reset(timestamp);
890 33453 m_lastToothPhaseFromSyncPoint = currentPhaseFromSyncPoint;
891 }
892
893 #if TRIGGER_EXTREME_LOGGING
894 efiPrintf("trigger %d %d %d", triggerIndexForListeners, getRevolutionCounter(), time2print(getTimeNowUs()));
895 #endif /* TRIGGER_EXTREME_LOGGING */
896
897 // Update engine RPM
898
1/1
✓ Branch 1 taken 33453 times.
33453 rpmShaftPositionCallback(signal, triggerIndexForListeners, timestamp);
899
900 // Schedule the TDC mark
901
1/1
✓ Branch 1 taken 33453 times.
33453 tdcMarkCallback(triggerIndexForListeners, timestamp);
902
903 #if EFI_LOGIC_ANALYZER
904 waTriggerEventListener(signal, triggerIndexForListeners, timestamp);
905 #endif
906
907
1/1
✓ Branch 1 taken 33453 times.
33453 angle_t nextPhase = findNextTriggerToothAngle(triggerIndexForListeners);
908
909
3/3
✓ Branch 2 taken 33453 times.
✓ Branch 4 taken 455 times.
✓ Branch 5 taken 32998 times.
33453 float expectNextPhase = nextPhase + tdcPosition();
910
1/1
✓ Branch 1 taken 33453 times.
33453 wrapAngle(expectNextPhase, "nextEnginePhase", ObdCode::CUSTOM_ERR_6555);
911 33453 expectedNextPhase = expectNextPhase;
912
913 #if EFI_CDM_INTEGRATION
914 if (trgEventIndex == 0 && isBrainPinValid(engineConfiguration->cdmInputPin)) {
915 int cdmKnockValue = getCurrentCdmValue(getTriggerCentral()->triggerState.getSynchronizationCounter());
916 engine->knockLogic(cdmKnockValue);
917 }
918 #endif /* EFI_CDM_INTEGRATION */
919
920
7/7
✓ Branch 1 taken 33453 times.
✓ Branch 3 taken 33138 times.
✓ Branch 4 taken 315 times.
✓ Branch 5 taken 2450 times.
✓ Branch 6 taken 30688 times.
✓ Branch 7 taken 2450 times.
✓ Branch 8 taken 31003 times.
2/2
✓ Decision 'true' taken 2450 times.
✓ Decision 'false' taken 31003 times.
33453 if (engine->rpmCalculator.getCachedRpm() > 0 && triggerIndexForListeners == 0) {
921
2/2
✓ Branch 1 taken 2450 times.
✓ Branch 5 taken 2450 times.
2450 engine->module<TpsAccelEnrichment>()->onEngineCycleTps();
922 }
923
924 // Handle ignition and injection
925
1/1
✓ Branch 1 taken 33453 times.
33453 mainTriggerCallback(triggerIndexForListeners, timestamp, currentEngineDecodedPhase, nextPhase);
926
927 33453 temp_mapVvt_index = triggerIndexForListeners / 2;
928
929 // Decode the MAP based "cam" sensor
930
1/1
✓ Branch 1 taken 33453 times.
33453 decodeMapCam(temp_mapVvt_index, timestamp, currentEngineDecodedPhase);
931
932
1/1
✓ Branch 1 taken 33453 times.
33453 boardTriggerCallback(timestamp, currentEngineDecodedPhase);
933 } else {
934 // We don't have sync, but report to the wave chart anyway as index 0.
935
1/1
✓ Branch 1 taken 5257 times.
5257 reportEventToWaveChart(signal, 0, triggerShape.useOnlyRisingEdges);
936
937 5257 expectedNextPhase = unexpected;
938 }
939 }
940
941 static void triggerShapeInfo() {
942 #if EFI_PROD_CODE || EFI_SIMULATOR
943 TriggerWaveform *shape = &getTriggerCentral()->triggerShape;
944 TriggerFormDetails *triggerFormDetails = &getTriggerCentral()->triggerFormDetails;
945 efiPrintf("syncEdge=%s", getSyncEdge(TRIGGER_WAVEFORM(syncEdge)));
946 efiPrintf("gap from %.2f to %.2f", TRIGGER_WAVEFORM(synchronizationRatioFrom[0]), TRIGGER_WAVEFORM(synchronizationRatioTo[0]));
947
948 for (size_t i = 0; i < shape->getSize(); i++) {
949 efiPrintf("event %d %.2f", i, triggerFormDetails->eventAngles[i]);
950 }
951 #endif
952 }
953
954 #if EFI_PROD_CODE
955 extern PwmConfig triggerEmulatorSignals[NUM_EMULATOR_CHANNELS];
956 #endif /* #if EFI_PROD_CODE */
957
958 void triggerInfo(void) {
959 #if EFI_PROD_CODE || EFI_SIMULATOR
960
961 TriggerCentral *tc = getTriggerCentral();
962 TriggerWaveform *ts = &tc->triggerShape;
963
964 efiPrintf("Template %s (%d) trigger %s (%d) syncEdge=%s tdcOffset=%.2f",
965 getEngine_type_e(engineConfiguration->engineType),
966 (int)engineConfiguration->engineType,
967 getTrigger_type_e(engineConfiguration->trigger.type),
968 (int)engineConfiguration->trigger.type,
969 getSyncEdge(TRIGGER_WAVEFORM(syncEdge)), TRIGGER_WAVEFORM(tdcPosition));
970
971 if (engineConfiguration->trigger.type == trigger_type_e::TT_TOOTHED_WHEEL) {
972 efiPrintf("total %d/skipped %d", engineConfiguration->trigger.customTotalToothCount,
973 engineConfiguration->trigger.customSkippedToothCount);
974 }
975
976
977 efiPrintf("trigger#1 event counters up=%d/down=%d", tc->getHwEventCounter(0),
978 tc->getHwEventCounter(1));
979
980 if (ts->needSecondTriggerInput) {
981 efiPrintf("trigger#2 event counters up=%d/down=%d", tc->getHwEventCounter(2),
982 tc->getHwEventCounter(3));
983 }
984 efiPrintf("expected cycle events %d/%d",
985 TRIGGER_WAVEFORM(getExpectedEventCount(TriggerWheel::T_PRIMARY)),
986 TRIGGER_WAVEFORM(getExpectedEventCount(TriggerWheel::T_SECONDARY)));
987
988 efiPrintf("trigger type=%d/need2ndChannel=%s", (int)engineConfiguration->trigger.type,
989 boolToString(TRIGGER_WAVEFORM(needSecondTriggerInput)));
990
991
992 efiPrintf("synchronizationNeeded=%s/isError=%s/total errors=%lu ord_err=%lu/total revolutions=%d/self=%s",
993 boolToString(ts->isSynchronizationNeeded),
994 boolToString(tc->isTriggerDecoderError()),
995 tc->triggerState.totalTriggerErrorCounter,
996 tc->triggerState.orderingErrorCounter,
997 tc->triggerState.getSynchronizationCounter(),
998 boolToString(tc->directSelfStimulation));
999
1000 if (TRIGGER_WAVEFORM(isSynchronizationNeeded)) {
1001 efiPrintf("gap from %.2f to %.2f", TRIGGER_WAVEFORM(synchronizationRatioFrom[0]), TRIGGER_WAVEFORM(synchronizationRatioTo[0]));
1002 }
1003
1004 #endif /* EFI_PROD_CODE || EFI_SIMULATOR */
1005
1006 #if EFI_PROD_CODE
1007
1008 efiPrintf("primary trigger input: %s", hwPortname(engineConfiguration->triggerInputPins[0]));
1009 efiPrintf("primary trigger simulator: %s %s freq=%d",
1010 hwPortname(engineConfiguration->triggerSimulatorPins[0]),
1011 getPin_output_mode_e(engineConfiguration->triggerSimulatorPinModes[0]),
1012 engineConfiguration->triggerSimulatorRpm);
1013
1014 if (ts->needSecondTriggerInput) {
1015 efiPrintf("secondary trigger input: %s", hwPortname(engineConfiguration->triggerInputPins[1]));
1016 #if EFI_EMULATE_POSITION_SENSORS
1017 efiPrintf("secondary trigger simulator: %s %s phase=%d",
1018 hwPortname(engineConfiguration->triggerSimulatorPins[1]),
1019 getPin_output_mode_e(engineConfiguration->triggerSimulatorPinModes[1]), triggerEmulatorSignals[0].safe.phaseIndex);
1020 #endif /* EFI_EMULATE_POSITION_SENSORS */
1021 }
1022
1023
1024 for (int camInputIndex = 0; camInputIndex<CAM_INPUTS_COUNT;camInputIndex++) {
1025 if (isBrainPinValid(engineConfiguration->camInputs[camInputIndex])) {
1026 int camLogicalIndex = camInputIndex % CAMS_PER_BANK;
1027 efiPrintf("VVT input: %s mode %s", hwPortname(engineConfiguration->camInputs[camInputIndex]),
1028 getVvt_mode_e(engineConfiguration->vvtMode[camLogicalIndex]));
1029 efiPrintf("VVT %d event counters: %d/%d",
1030 camInputIndex,
1031 tc->vvtEventRiseCounter[camInputIndex], tc->vvtEventFallCounter[camInputIndex]);
1032 }
1033 }
1034
1035 efiPrintf("primary logic input: %s", hwPortname(engineConfiguration->logicAnalyzerPins[0]));
1036 efiPrintf("secondary logic input: %s", hwPortname(engineConfiguration->logicAnalyzerPins[1]));
1037
1038
1039 efiPrintf("totalTriggerHandlerMaxTime=%lu", triggerMaxDuration);
1040
1041 #endif /* EFI_PROD_CODE */
1042
1043 #if EFI_ENGINE_SNIFFER
1044 efiPrintf("engine sniffer current size=%d", waveChart.getSize());
1045 #endif /* EFI_ENGINE_SNIFFER */
1046
1047 }
1048
1049 static void resetRunningTriggerCounters() {
1050 #if !EFI_UNIT_TEST
1051 getTriggerCentral()->resetCounters();
1052 triggerInfo();
1053 #endif
1054 }
1055
1056 223 void onConfigurationChangeTriggerCallback() {
1057 223 bool changed = false;
1058 // todo: how do we static_assert here?
1059
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 223 times.
223 criticalAssertVoid(efi::size(engineConfiguration->camInputs) == efi::size(engineConfiguration->vvtOffsets), "sizes");
1060
1061
2/2
✓ Branch 1 taken 892 times.
✓ Branch 2 taken 223 times.
2/2
✓ Decision 'true' taken 892 times.
✓ Decision 'false' taken 223 times.
1115 for (size_t camIndex = 0; camIndex < efi::size(engineConfiguration->camInputs); camIndex++) {
1062 892 changed |= isConfigurationChanged(camInputs[camIndex]);
1063 892 changed |= isConfigurationChanged(vvtOffsets[camIndex]);
1064 }
1065
1066
2/2
✓ Branch 1 taken 4014 times.
✓ Branch 2 taken 223 times.
2/2
✓ Decision 'true' taken 4014 times.
✓ Decision 'false' taken 223 times.
4237 for (size_t i = 0; i < efi::size(engineConfiguration->triggerGapOverrideFrom); i++) {
1067 4014 changed |= isConfigurationChanged(triggerGapOverrideFrom[i]);
1068 4014 changed |= isConfigurationChanged(triggerGapOverrideTo[i]);
1069 }
1070
1071
2/2
✓ Branch 1 taken 446 times.
✓ Branch 2 taken 223 times.
2/2
✓ Decision 'true' taken 446 times.
✓ Decision 'false' taken 223 times.
669 for (size_t i = 0; i < efi::size(engineConfiguration->triggerInputPins); i++) {
1072 446 changed |= isConfigurationChanged(triggerInputPins[i]);
1073 446 Gpio pin = engineConfiguration->camInputs[i];
1074
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 446 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 446 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 446 times.
446 if (engineConfiguration->vvtMode[0] == VVT_MAP_V_TWIN && isBrainPinValid(pin)) {
1075 criticalError("Please no physical sensors in CAM by MAP mode index=%d %s", i, hwPortname(pin));
1076 }
1077 }
1078
1079
2/2
✓ Branch 1 taken 446 times.
✓ Branch 2 taken 223 times.
2/2
✓ Decision 'true' taken 446 times.
✓ Decision 'false' taken 223 times.
669 for (size_t i = 0; i < efi::size(engineConfiguration->vvtMode); i++) {
1080 446 changed |= isConfigurationChanged(vvtMode[i]);
1081 }
1082
1083 223 changed |= isConfigurationChanged(trigger.type);
1084 223 changed |= isConfigurationChanged(skippedWheelOnCam);
1085 223 changed |= isConfigurationChanged(twoStroke);
1086 223 changed |= isConfigurationChanged(globalTriggerAngleOffset);
1087 223 changed |= isConfigurationChanged(trigger.customTotalToothCount);
1088 223 changed |= isConfigurationChanged(trigger.customSkippedToothCount);
1089 223 changed |= isConfigurationChanged(overrideTriggerGaps);
1090 223 changed |= isConfigurationChanged(gapTrackingLengthOverride);
1091 223 changed |= isConfigurationChanged(overrideVvtTriggerGaps);
1092 223 changed |= isConfigurationChanged(gapVvtTrackingLengthOverride);
1093
1094
2/2
✓ Branch 0 taken 110 times.
✓ Branch 1 taken 113 times.
2/2
✓ Decision 'true' taken 110 times.
✓ Decision 'false' taken 113 times.
223 if (changed) {
1095 #if EFI_ENGINE_CONTROL
1096 110 engine->updateTriggerConfiguration();
1097 110 getTriggerCentral()->noiseFilter.resetAccumSignalData();
1098 #endif
1099 }
1100 #if EFI_DETAILED_LOGGING
1101 efiPrintf("isTriggerConfigChanged=%d", triggerConfigChanged);
1102 #endif /* EFI_DETAILED_LOGGING */
1103
1104 // we do not want to miss two updates in a row
1105
4/4
✓ Branch 1 taken 116 times.
✓ Branch 2 taken 107 times.
✓ Branch 3 taken 109 times.
✓ Branch 4 taken 7 times.
223 getTriggerCentral()->triggerConfigChangedOnLastConfigurationChange = getTriggerCentral()->triggerConfigChangedOnLastConfigurationChange || changed;
1106 }
1107
1108 82 static void initVvtShape(int camIndex, TriggerWaveform& shape, const TriggerConfiguration& p_config, TriggerDecoderBase &initState) {
1109 82 shape.initializeTriggerWaveform(FOUR_STROKE_CAM_SENSOR, p_config.TriggerType, /*isCrank*/ false);
1110
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 4 times.
2/2
✓ Decision 'true' taken 78 times.
✓ Decision 'false' taken 4 times.
82 if (camIndex == 0) {
1111 // at the moment we only support override of first cam
1112 // nasty code: this implicitly adjusts 'shape' parameter
1113 78 getTriggerCentral()->applyCamGapOverride();
1114 }
1115 82 shape.initializeSyncPoint(initState, p_config);
1116 82 }
1117
1118 702 void TriggerCentral::validateCamVvtCounters() {
1119 // micro-optimized 'synchronizationCounter % 256'
1120 702 int camVvtValidationIndex = triggerState.getSynchronizationCounter() & 0xFF;
1121
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 691 times.
2/2
✓ Decision 'true' taken 11 times.
✓ Decision 'false' taken 691 times.
702 if (camVvtValidationIndex == 0) {
1122 11 vvtCamCounter = 0;
1123
4/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 687 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
2/2
✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 689 times.
691 } else if (camVvtValidationIndex == 0xFE && vvtCamCounter < 60) {
1124 // magic logic: we expect at least 60 CAM/VVT events for each 256 trigger cycles, otherwise throw a code
1125 2 warning(ObdCode::OBD_Camshaft_Position_Sensor_Circuit_Range_Performance, "No Camshaft Position Sensor signals");
1126 }
1127 702 }
1128 /**
1129 * Calculate 'shape.triggerShapeSynchPointIndex' value using 'TriggerDecoderBase *state'
1130 */
1131 928 static void calculateTriggerSynchPoint(
1132 const PrimaryTriggerConfiguration &primaryTriggerConfiguration,
1133 TriggerWaveform& shape,
1134 TriggerDecoderBase& initState) {
1135
1136 #if EFI_PROD_CODE
1137 efiAssertVoid(ObdCode::CUSTOM_TRIGGER_STACK, hasLotsOfRemainingStack(), "calc s");
1138 #endif
1139
1140 928 shape.initializeSyncPoint(initState, primaryTriggerConfiguration);
1141
1142
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 928 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 928 times.
928 if (shape.getSize() >= PWM_PHASE_MAX_COUNT) {
1143 // todo: by the time we are here we had already modified a lot of RAM out of bounds!
1144 firmwareError(ObdCode::CUSTOM_ERR_TRIGGER_WAVEFORM_TOO_LONG, "Trigger length above maximum: %d", shape.getSize());
1145 shape.setShapeDefinitionError(true);
1146 return;
1147 }
1148
1149
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 928 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 928 times.
928 if (shape.getSize() == 0) {
1150 firmwareError(ObdCode::CUSTOM_ERR_TRIGGER_ZERO, "triggerShape size is zero");
1151 }
1152 }
1153
1154 TriggerDecoderBase initState("init");
1155
1156 928 void TriggerCentral::applyTriggerGapOverride() {
1157 /**
1158 * this is only useful while troubleshooting a new trigger shape in the field
1159 * in very VERY rare circumstances
1160 */
1161
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 906 times.
2/2
✓ Decision 'true' taken 22 times.
✓ Decision 'false' taken 906 times.
928 if (engineConfiguration->overrideTriggerGaps) {
1162 22 int gapIndex = 0;
1163
1164 22 triggerShape.gapTrackingLength = engineConfiguration->gapTrackingLengthOverride;
1165
1166 // copy however many the user wants
1167
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 22 times.
2/2
✓ Decision 'true' taken 64 times.
✓ Decision 'false' taken 22 times.
86 for (; gapIndex < engineConfiguration->gapTrackingLengthOverride; gapIndex++) {
1168 64 float gapOverrideFrom = engineConfiguration->triggerGapOverrideFrom[gapIndex];
1169 64 float gapOverrideTo = engineConfiguration->triggerGapOverrideTo[gapIndex];
1170 64 triggerShape.setTriggerSynchronizationGap3(/*gapIndex*/gapIndex, gapOverrideFrom, gapOverrideTo);
1171 }
1172
1173 // fill the remainder with the default gaps
1174
2/2
✓ Branch 0 taken 332 times.
✓ Branch 1 taken 22 times.
2/2
✓ Decision 'true' taken 332 times.
✓ Decision 'false' taken 22 times.
354 for (; gapIndex < GAP_TRACKING_LENGTH; gapIndex++) {
1175 332 triggerShape.synchronizationRatioFrom[gapIndex] = NAN;
1176 332 triggerShape.synchronizationRatioTo[gapIndex] = NAN;
1177 }
1178 }
1179 928 }
1180
1181 1006 void TriggerCentral::applyCamGapOverride() {
1182
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1006 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1006 times.
1006 if (engineConfiguration->overrideVvtTriggerGaps) {
1183 int gapIndex = 0;
1184
1185 TriggerWaveform *shape = &vvtShape[0];
1186 shape->gapTrackingLength = engineConfiguration->gapVvtTrackingLengthOverride;
1187
1188 for (; gapIndex < engineConfiguration->gapVvtTrackingLengthOverride; gapIndex++) {
1189 float gapOverrideFrom = engineConfiguration->triggerVVTGapOverrideFrom[gapIndex];
1190 float gapOverrideTo = engineConfiguration->triggerVVTGapOverrideTo[gapIndex];
1191 shape->synchronizationRatioFrom[gapIndex] = gapOverrideFrom;
1192 shape->synchronizationRatioTo[gapIndex] = gapOverrideTo;
1193 }
1194 // fill the remainder with the default gaps
1195 for (; gapIndex < VVT_TRACKING_LENGTH; gapIndex++) {
1196 shape->synchronizationRatioFrom[gapIndex] = NAN;
1197 shape->synchronizationRatioTo[gapIndex] = NAN;
1198 }
1199 }
1200 1006 }
1201
1202 928 void TriggerCentral::applyShapesConfiguration() {
1203 // Re-read config in case it's changed
1204 928 primaryTriggerConfiguration.update();
1205
2/2
✓ Branch 0 taken 1856 times.
✓ Branch 1 taken 928 times.
2/2
✓ Decision 'true' taken 1856 times.
✓ Decision 'false' taken 928 times.
2784 for (int camIndex = 0;camIndex < CAMS_PER_BANK;camIndex++) {
1206 1856 vvtTriggerConfiguration[camIndex].update();
1207 }
1208
1209 928 triggerShape.initializeTriggerWaveform(lookupOperationMode(), primaryTriggerConfiguration.TriggerType);
1210
1211 928 applyTriggerGapOverride();
1212
1213
1/2
✓ Branch 0 taken 928 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 928 times.
✗ Decision 'false' not taken.
928 if (!triggerShape.shapeDefinitionError) {
1214 928 int length = triggerShape.getLength();
1215 928 engineCycleEventCount = length;
1216
1217
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 928 times.
928 efiAssertVoid(ObdCode::CUSTOM_SHAPE_LEN_ZERO, length > 0, "shapeLength=0");
1218
1219 928 triggerErrorDetection.clear();
1220
1221 /**
1222 * 'initState' instance of TriggerDecoderBase is used only to initialize 'this' TriggerWaveform instance
1223 * #192 BUG real hardware trigger events could be coming even while we are initializing trigger
1224 */
1225 928 calculateTriggerSynchPoint(primaryTriggerConfiguration,
1226 928 triggerShape,
1227 initState);
1228 }
1229
1230 928 applyCamGapOverride();
1231
1232
2/2
✓ Branch 0 taken 1856 times.
✓ Branch 1 taken 928 times.
2/2
✓ Decision 'true' taken 1856 times.
✓ Decision 'false' taken 928 times.
2784 for (int camIndex = 0; camIndex < CAMS_PER_BANK; camIndex++) {
1233 // todo: should 'vvtWithRealDecoder' be used here?
1234
2/2
✓ Branch 1 taken 82 times.
✓ Branch 2 taken 1774 times.
2/2
✓ Decision 'true' taken 82 times.
✓ Decision 'false' taken 1774 times.
1856 if (engineConfiguration->vvtMode[camIndex] != VVT_INACTIVE) {
1235 164 initVvtShape(
1236 camIndex,
1237 82 vvtShape[camIndex],
1238 82 vvtTriggerConfiguration[camIndex],
1239 initState
1240 );
1241 }
1242 }
1243
1244 // This is not the right place for this, but further refactoring has to happen before it can get moved.
1245 928 triggerState.setNeedsDisambiguation(engine->triggerCentral.triggerShape.needsDisambiguation());
1246
1247 }
1248
1249 /**
1250 * @returns true if configuration just changed, and if that change has affected trigger
1251 */
1252 2450 bool TriggerCentral::checkIfTriggerConfigChanged() {
1253 // we want to make sure that configuration has changed AND that change has changed trigger specifically
1254
4/4
✓ Branch 2 taken 111 times.
✓ Branch 3 taken 2339 times.
✓ Branch 4 taken 87 times.
✓ Branch 5 taken 24 times.
2450 bool result = triggerVersion.isOld(engine->getGlobalConfigurationVersion()) && triggerConfigChangedOnLastConfigurationChange;
1255 2450 triggerConfigChangedOnLastConfigurationChange = false; // whoever has called the method is supposed to react to changes
1256 2450 return result;
1257 }
1258
1259 #if EFI_UNIT_TEST
1260 125 bool TriggerCentral::isTriggerConfigChanged() {
1261 125 return triggerConfigChangedOnLastConfigurationChange;
1262 }
1263 #endif // EFI_UNIT_TEST
1264
1265 825 void validateTriggerInputs() {
1266
4/6
✓ Branch 1 taken 818 times.
✓ Branch 2 taken 7 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 818 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 825 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 825 times.
825 if (!isBrainPinValid(engineConfiguration->triggerInputPins[0]) && isBrainPinValid(engineConfiguration->triggerInputPins[1])) {
1267 criticalError("First trigger channel not configured while second one is.");
1268 }
1269
1270
4/6
✓ Branch 1 taken 814 times.
✓ Branch 2 taken 11 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 814 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 825 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 825 times.
825 if (!isBrainPinValid(engineConfiguration->camInputs[0]) && isBrainPinValid(engineConfiguration->camInputs[2])) {
1271 criticalError("First bank cam input is required if second bank specified");
1272 }
1273 825 }
1274
1275 void initTriggerCentral() {
1276
1277 #if EFI_ENGINE_SNIFFER
1278 initWaveChart(&waveChart);
1279 #endif /* EFI_ENGINE_SNIFFER */
1280
1281 #if EFI_PROD_CODE || EFI_SIMULATOR
1282 addConsoleAction(CMD_TRIGGERINFO, triggerInfo);
1283 addConsoleAction("trigger_shape_info", triggerShapeInfo);
1284 addConsoleAction("reset_trigger", resetRunningTriggerCounters);
1285 #endif // EFI_PROD_CODE || EFI_SIMULATOR
1286
1287 }
1288
1289 /**
1290 * @return TRUE is something is wrong with trigger decoding
1291 */
1292 bool TriggerCentral::isTriggerDecoderError() {
1293 return triggerErrorDetection.sum(6) > 4;
1294 }
1295
1296 #endif // EFI_SHAFT_POSITION_INPUT
1297