GCC Code Coverage Report


Directory: ./
File: firmware/controllers/trigger/trigger_central.h
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 87.0% 20 0 23
Functions: 100.0% 5 0 5
Branches: 53.3% 8 0 15
Decisions: 75.0% 6 - 8

Line Branch Decision Exec Source
1 /*
2 * @file trigger_central.h
3 *
4 * @date Feb 23, 2014
5 * @author Andrey Belomutskiy, (c) 2012-2020
6 */
7
8 #pragma once
9
10 #include "rusefi_enums.h"
11 #include "listener_array.h"
12 #include "trigger_decoder.h"
13 #include "instant_rpm_calculator.h"
14 #include "trigger_central_generated.h"
15 #include <rusefi/timer.h>
16 #include "pin_repository.h"
17 #include "local_version_holder.h"
18 #include "cyclic_buffer.h"
19
20 #define MAP_CAM_BUFFER 64
21
22 #ifndef RPM_LOW_THRESHOLD
23 // no idea what is the best value, 25 is as good as any other guess
24 #define RPM_LOW_THRESHOLD 25
25 #endif
26
27 class Engine;
28 typedef void (*ShaftPositionListener)(trigger_event_e signal, uint32_t index, efitick_t edgeTimestamp);
29
30 #define HAVE_CAM_INPUT() (isBrainPinValid(engineConfiguration->camInputs[0]))
31
32 class TriggerNoiseFilter {
33 public:
34 void resetAccumSignalData();
35 bool noiseFilter(efitick_t nowNt,
36 TriggerDecoderBase* triggerState,
37 trigger_event_e signal);
38
39 efitick_t lastSignalTimes[HW_EVENT_TYPES];
40 efitick_t accumSignalPeriods[HW_EVENT_TYPES];
41 efitick_t accumSignalPrevPeriods[HW_EVENT_TYPES];
42 };
43
44 /**
45 * Maybe merge TriggerCentral and TriggerState classes into one class?
46 * Probably not: we have an instance of TriggerState which is used for trigger initialization,
47 * also composition probably better than inheritance here
48 */
49 class TriggerCentral final : public trigger_central_s {
50 public:
51 TriggerCentral();
52 /**
53 * we have two kinds of sync:
54 * this method is about detecting of exact engine phase with 720 degree precision usually based on cam wheel decoding
55 * not to be confused with a totally different trigger _wheel_ sync which could be either crank wheel sync or cam wheel sync
56 */
57 angle_t syncEnginePhaseAndReport(int divider, int remainder);
58 void handleShaftSignal(trigger_event_e signal, efitick_t timestamp);
59 int getHwEventCounter(int index) const;
60 void resetCounters();
61 void validateCamVvtCounters();
62 void applyShapesConfiguration();
63
64 angle_t findNextTriggerToothAngle(int nextToothIndex);
65
66 InstantRpmCalculator instantRpm;
67
68 1490 void prepareTriggerShape() {
69 #if EFI_ENGINE_CONTROL && EFI_SHAFT_POSITION_INPUT
70
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1490 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 1490 times.
1490 if (triggerShape.shapeDefinitionError) {
71 // Nothing to do here if there's a problem with the trigger shape
72 return;
73 }
74
75 1490 triggerFormDetails.prepareEventAngles(&triggerShape);
76 #endif
77 }
78
79 // this is useful at least for real hardware integration testing - maybe a proper solution would be to simply
80 // GND input pins instead of leaving them floating
81 bool hwTriggerInputEnabled = true;
82
83 cyclic_buffer<int> triggerErrorDetection;
84
85 /**
86 * See also triggerSimulatorRpm
87 */
88 bool directSelfStimulation = false;
89
90 PrimaryTriggerConfiguration primaryTriggerConfiguration;
91 #if CAMS_PER_BANK == 1
92 VvtTriggerConfiguration vvtTriggerConfiguration[CAMS_PER_BANK] = {{"VVT1 ", 0}};
93 #else
94 VvtTriggerConfiguration vvtTriggerConfiguration[CAMS_PER_BANK] = {{"VVT1 ", 0}, {"VVT2 ", 1}};
95 #endif
96
97 LocalVersionHolder triggerVersion;
98
99 /**
100 * By the way:
101 * 'cranking' means engine is not stopped and the rpm are below crankingRpm
102 * 'running' means RPM are above crankingRpm
103 * 'spinning' means the engine is not stopped
104 */
105 // todo: combine with other RpmCalculator fields?
106 /**
107 * this is set to true each time we register a trigger tooth signal
108 */
109 bool isSpinningJustForWatchdog = false;
110
111 float mapCamPrevCycleValue = 0;
112 int prevChangeAtCycle = 0;
113
114 /**
115 * value of 'triggerShape.getLength()'
116 * pre-calculating this value is a performance optimization
117 */
118 uint32_t engineCycleEventCount = 0;
119 /**
120 * true if a recent configuration change has changed any of the trigger settings which
121 * we have not adjusted for yet
122 */
123 bool triggerConfigChangedOnLastConfigurationChange = false;
124
125 bool checkIfTriggerConfigChanged();
126 #if EFI_UNIT_TEST
127 bool isTriggerConfigChanged();
128 #endif // EFI_UNIT_TEST
129
130 bool isTriggerDecoderError();
131
132 expected<float> getCurrentEnginePhase(efitick_t nowNt) const;
133
134 5630 float getSecondsSinceTriggerEvent(efitick_t nowNt) const {
135 5630 return m_lastEventTimer.getElapsedSeconds(nowNt);
136 }
137
138 5630 bool engineMovedRecently(efitick_t nowNt) const {
139 // todo: this user-defined property is a quick solution, proper fix https://github.com/rusefi/rusefi/issues/6593 is needed
140
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 5630 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 5630 times.
1/2
✗ Decision 'true' not taken.
✓ Decision 'false' taken 5630 times.
5630 if (engineConfiguration->triggerEventsTimeoutMs != 0 && m_lastEventTimer.hasElapsedMs(engineConfiguration->triggerEventsTimeoutMs)) {
141 return false;
142 }
143
144 5630 constexpr float oneRevolutionLimitInSeconds = 60.0 / RPM_LOW_THRESHOLD;
145 5630 auto maxAverageToothTime = oneRevolutionLimitInSeconds / triggerShape.getSize();
146
147 // Some triggers may have long gaps (with many teeth), don't count that as stopped!
148 5630 auto maxAllowedGap = maxAverageToothTime * 10;
149
150 // Clamp between 0.1 seconds ("instant" for a human) and worst case of one engine cycle on low tooth count wheel
151 5630 maxAllowedGap = clampF(0.1f, maxAllowedGap, oneRevolutionLimitInSeconds);
152
153
3/4
✓ Branch 1 taken 2857 times.
✓ Branch 2 taken 2773 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2857 times.
5630 return (getSecondsSinceTriggerEvent(nowNt) < maxAllowedGap) || directSelfStimulation;
154 }
155
156 3295 bool engineMovedRecently() const {
157 3295 return engineMovedRecently(getTimeNowNt());
158 }
159
160 TriggerNoiseFilter noiseFilter;
161
162 angle_t getVVTPosition(uint8_t bankIndex, uint8_t camIndex);
163
164 #if EFI_UNIT_TEST
165 // latest VVT event position (could be not synchronization event)
166 angle_t currentVVTEventPosition[BANKS_COUNT][CAMS_PER_BANK];
167 #endif // EFI_UNIT_TEST
168
169 // synchronization event position
170 angle_t vvtPosition[BANKS_COUNT][CAMS_PER_BANK];
171
172 #if EFI_SHAFT_POSITION_INPUT
173 PrimaryTriggerDecoder triggerState;
174 #endif //EFI_SHAFT_POSITION_INPUT
175
176 TriggerWaveform triggerShape;
177
178 VvtTriggerDecoder vvtState[BANKS_COUNT][CAMS_PER_BANK] = {
179 {
180 "VVT B1 Int",
181 #if CAMS_PER_BANK >= 2
182 "VVT B1 Exh"
183 #endif
184 },
185 #if BANKS_COUNT >= 2
186 {
187 "VVT B2 Int",
188 #if CAMS_PER_BANK >= 2
189 "VVT B1 Exh"
190 #endif
191 }
192 #endif
193 };
194
195 TriggerWaveform vvtShape[CAMS_PER_BANK];
196
197 TriggerFormDetails triggerFormDetails;
198
199 // Keep track of the last time we got a valid trigger event
200 Timer m_lastEventTimer;
201
202 /**
203 * this is based on engineSnifferRpmThreshold settings and current RPM
204 */
205 bool isEngineSnifferEnabled = false;
206
207 void applyCamGapOverride();
208 bool isMapCamSync(efitick_t nowNt, float currentPhase);
209 private:
210 void decodeMapCam(int triggerIndexForListeners, efitick_t nowNt, float currentPhase);
211 void applyTriggerGapOverride();
212
213 bool isToothExpectedNow(efitick_t timestamp);
214
215 // Time since the last tooth
216 Timer m_lastToothTimer;
217 // Phase of the last tooth relative to the sync point
218 float m_lastToothPhaseFromSyncPoint;
219
220 // At what engine phase do we expect the next tooth to arrive?
221 // Used for checking whether your trigger pattern is correct.
222 expected<float> expectedNextPhase = unexpected;
223 };
224
225 void triggerInfo(void);
226 void hwHandleShaftSignal(int signalIndex, bool isRising, efitick_t timestamp);
227 void handleShaftSignal(int signalIndex, bool isRising, efitick_t timestamp);
228 void hwHandleVvtCamSignal(TriggerValue front, efitick_t timestamp, int index);
229 void hwHandleVvtCamSignal(bool isRising, efitick_t timestamp, int index);
230 void handleVvtCamSignal(TriggerValue front, efitick_t timestamp, int index);
231
232 void validateTriggerInputs();
233
234 void initTriggerCentral();
235
236 int isSignalDecoderError(void);
237
238 void onConfigurationChangeTriggerCallback();
239
240 #define SYMMETRICAL_CRANK_SENSOR_DIVIDER (2 * 2)
241 #define SYMMETRICAL_THREE_TIMES_CRANK_SENSOR_DIVIDER (3 * 2)
242 #define SYMMETRICAL_SIX_TIMES_CRANK_SENSOR_DIVIDER (6 * 2)
243 #define SYMMETRICAL_TWELVE_TIMES_CRANK_SENSOR_DIVIDER (12 * 2)
244
245 TriggerCentral * getTriggerCentral();
246 int getCrankDivider(operation_mode_e operationMode);
247
248 174987 constexpr bool isTriggerUpEvent(trigger_event_e event) {
249
2/3
✓ Branch 0 taken 71918 times.
✓ Branch 1 taken 103069 times.
✗ Branch 2 not taken.
174987 switch (event) {
250
1/1
✓ Decision 'true' taken 71918 times.
71918 case SHAFT_PRIMARY_FALLING:
251 case SHAFT_SECONDARY_FALLING:
252
1/1
✓ Decision 'true' taken 71918 times.
71918 return false;
253
1/1
✓ Decision 'true' taken 103069 times.
103069 case SHAFT_PRIMARY_RISING:
254 case SHAFT_SECONDARY_RISING:
255
1/1
✓ Decision 'true' taken 103069 times.
103069 return true;
256 }
257
258 return false;
259 }
260