GCC Code Coverage Report


Directory: ./
File: firmware/controllers/trigger/trigger_emulator_algo.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 47.4% 27 0 57
Functions: 66.7% 6 0 9
Branches: 38.5% 15 0 39
Decisions: 39.1% 9 - 23

Line Branch Decision Exec Source
1 /**
2 * @file trigger_emulator_algo.cpp
3 *
4 * This file is about producing real electrical signals which emulate trigger signal based on
5 * a known TriggerWaveform.
6 *
7 * Historically this implementation was implemented based on PwmConfig which is maybe not the
8 * best way to implement it. (todo: why is not the best way?)
9 *
10 * A newer implementation of pretty much the same thing is TriggerStimulatorHelper
11 * todo: one emulator should be enough! another one should be eliminated
12 *
13 * @date Mar 3, 2014
14 * @author Andrey Belomutskiy, (c) 2012-2020
15 */
16
17 #include "pch.h"
18
19 560402 int getPreviousIndex(const int currentIndex, const int size) {
20 560402 return (currentIndex + size - 1) % size;
21 }
22
23 373650 bool needEvent(const int currentIndex, const MultiChannelStateSequence & mcss, int channelIndex) {
24 373650 int prevIndex = getPreviousIndex(currentIndex, mcss.phaseCount);
25 373650 pin_state_t previousValue = mcss.getChannelState(channelIndex, /*phaseIndex*/prevIndex);
26 373650 pin_state_t currentValue = mcss.getChannelState(channelIndex, /*phaseIndex*/currentIndex);
27
28 373650 return previousValue != currentValue;
29 }
30
31 #if EFI_EMULATE_POSITION_SENSORS
32
33 #if !EFI_SHAFT_POSITION_INPUT
34 fail("EFI_SHAFT_POSITION_INPUT required to have EFI_EMULATE_POSITION_SENSORS")
35 #endif
36
37 #include "trigger_emulator_algo.h"
38 #include "trigger_central.h"
39 #include "trigger_simulator.h"
40
41 2 TriggerEmulatorHelper::TriggerEmulatorHelper() {
42 2 }
43
44 static OutputPin emulatorOutputs[NUM_EMULATOR_CHANNELS][PWM_PHASE_MAX_WAVE_PER_PWM];
45
46 73 void TriggerEmulatorHelper::handleEmulatorCallback(int channel, const MultiChannelStateSequence& multiChannelStateSequence, int stateIndex) {
47 73 efitick_t stamp = getTimeNowNt();
48
49 // todo: code duplication with TriggerStimulatorHelper::feedSimulatedEvent?
50 #if EFI_SHAFT_POSITION_INPUT
51
2/2
✓ Branch 0 taken 146 times.
✓ Branch 1 taken 73 times.
2/2
✓ Decision 'true' taken 146 times.
✓ Decision 'false' taken 73 times.
219 for (size_t i = 0; i < PWM_PHASE_MAX_WAVE_PER_PWM; i++) {
52
2/2
✓ Branch 1 taken 73 times.
✓ Branch 2 taken 73 times.
2/2
✓ Decision 'true' taken 73 times.
✓ Decision 'false' taken 73 times.
146 if (needEvent(stateIndex, multiChannelStateSequence, i)) {
53 73 bool isRise = TriggerValue::RISE == multiChannelStateSequence.getChannelState(/*phaseIndex*/i, stateIndex);
54
55
3/4
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 24 times.
73 isRise ^= (i == 0 && engineConfiguration->invertPrimaryTriggerSignal);
56
3/4
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 49 times.
73 isRise ^= (i == 1 && engineConfiguration->invertSecondaryTriggerSignal);
57
58
1/2
✓ Branch 0 taken 73 times.
✗ Branch 1 not taken.
1/2
✓ Decision 'true' taken 73 times.
✗ Decision 'false' not taken.
73 if (channel == 0) {
59 73 handleShaftSignal(i, isRise, stamp);
60 } else {
61 handleVvtCamSignal(isRise ? TriggerValue::RISE : TriggerValue::FALL, stamp, INDEX_BY_BANK_CAM(channel - 1, i));
62 }
63 }
64 }
65 #endif // EFI_SHAFT_POSITION_INPUT
66 73 }
67
68 // same is used for either self or external trigger simulation
69 PwmConfig triggerEmulatorSignals[NUM_EMULATOR_CHANNELS];
70 TriggerWaveform *triggerEmulatorWaveforms[NUM_EMULATOR_CHANNELS];
71
72 static int atTriggerVersions[NUM_EMULATOR_CHANNELS] = { 0 };
73
74 /**
75 * todo: why is this method NOT reciprocal to getCrankDivider?!
76 * todo: oh this method has only one usage? there must me another very similar method!
77 */
78 static float getRpmMultiplier(operation_mode_e mode) {
79 switch (mode) {
80 case FOUR_STROKE_SYMMETRICAL_CRANK_SENSOR:
81 case FOUR_STROKE_THREE_TIMES_CRANK_SENSOR:
82 case FOUR_STROKE_SIX_TIMES_CRANK_SENSOR:
83 case FOUR_STROKE_TWELVE_TIMES_CRANK_SENSOR:
84 case FOUR_STROKE_CRANK_SENSOR:
85 case FOUR_STROKE_CAM_SENSOR:
86 case OM_NONE:
87 return getCrankDivider(mode) / 2.0;
88 case TWO_STROKE:
89 // unit test coverage still runs if the value below is changed to '2' not a great sign!
90 // but HW CI insists that we have '1' here
91 return 1;
92 };
93 criticalError("We should not have reach this line");
94 return 1;
95 }
96
97 void setTriggerEmulatorRPM(int rpm) {
98 criticalAssertVoid(rpm >= 0 && rpm <= 30000, "emulator RPM out of range");
99
100 engineConfiguration->triggerSimulatorRpm = rpm;
101 /**
102 * All we need to do here is to change the periodMs
103 * togglePwmState() would see that the periodMs has changed and act accordingly
104 */
105 for (int channel = 0; channel < NUM_EMULATOR_CHANNELS; channel++) {
106 float rPerSecond = NAN;
107 if (rpm != 0) {
108 // use 0.5 multiplier for cam
109 float rpmM = (channel == 0) ? getRpmMultiplier(getEngineRotationState()->getOperationMode()) : 0.5f;
110 rPerSecond = rpm * rpmM / 60.0; // per minute converted to per second
111 }
112 triggerEmulatorSignals[channel].setFrequency(rPerSecond);
113 }
114
115 engine->resetEngineSnifferIfInTestMode();
116
117 efiPrintf("Emulating position sensor(s). RPM=%d", rpm);
118 }
119
120 static void updateTriggerWaveformIfNeeded(PwmConfig *state) {
121 for (int channel = 0; channel < NUM_EMULATOR_CHANNELS; channel++) {
122 if (state != &triggerEmulatorSignals[channel])
123 continue;
124
125 if (atTriggerVersions[channel] < triggerEmulatorWaveforms[channel]->version) {
126 atTriggerVersions[channel] = triggerEmulatorWaveforms[channel]->version;
127 efiPrintf("Stimulator: updating trigger shape for ch%d: %d/%d %ld", channel, atTriggerVersions[channel],
128 engine->getGlobalConfigurationVersion(), getTimeNowMs());
129
130 copyPwmParameters(state, &triggerEmulatorWaveforms[channel]->wave);
131 state->safe.periodNt = -1; // this would cause loop re-initialization
132 }
133 }
134 }
135
136 static TriggerEmulatorHelper helper;
137 static bool hasStimPins = false;
138
139 static bool hasInitTriggerEmulator = false;
140
141 #if EFI_PROD_CODE
142 PUBLIC_API_WEAK void onTriggerEmulatorPinState(int, int) { }
143 #endif /* EFI_PROD_CODE */
144
145 # if !EFI_UNIT_TEST
146
147 static void emulatorApplyPinState(int stateIndex, PwmConfig *state) /* pwm_gen_callback */ {
148 assertStackVoid("emulator", ObdCode::STACK_USAGE_MISC, EXPECTED_REMAINING_STACK);
149 if (engine->triggerCentral.directSelfStimulation) {
150 /**
151 * this callback would invoke the input signal handlers directly
152 */
153 for (int channel = 0; channel < NUM_EMULATOR_CHANNELS; channel++) {
154 if (state != &triggerEmulatorSignals[channel])
155 continue;
156 helper.handleEmulatorCallback(channel,
157 *state->multiChannelStateSequence,
158 stateIndex);
159 }
160 }
161
162 #if EFI_PROD_CODE
163 // Only set pins if they're configured - no need to waste the cycles otherwise
164 else if (hasStimPins) {
165 applyPinState(stateIndex, state);
166
167 // this allows any arbitrary code to synchronize with the trigger emulator
168 for (int channel = 0; channel < NUM_EMULATOR_CHANNELS; channel++) {
169 if (state != &triggerEmulatorSignals[channel])
170 continue;
171 onTriggerEmulatorPinState(stateIndex, channel);
172 }
173 }
174 #endif /* EFI_PROD_CODE */
175 }
176
177 static void startSimulatedTriggerSignal() {
178 // No need to start more than once
179 if (hasInitTriggerEmulator) {
180 return;
181 }
182
183 // store the crank+cam waveforms
184 triggerEmulatorWaveforms[0] = &engine->triggerCentral.triggerShape;
185 for (int cami = 0; cami < CAMS_PER_BANK; cami++) {
186 triggerEmulatorWaveforms[1 + cami] = &engine->triggerCentral.vvtShape[cami];
187 }
188
189 setTriggerEmulatorRPM(engineConfiguration->triggerSimulatorRpm);
190
191 for (int channel = 0; channel < NUM_EMULATOR_CHANNELS; channel++) {
192 TriggerWaveform *s = triggerEmulatorWaveforms[channel];
193 if (s->getSize() == 0)
194 continue;
195 triggerEmulatorSignals[channel].weComplexInit(
196 &engine->scheduler,
197 &s->wave,
198 updateTriggerWaveformIfNeeded, emulatorApplyPinState);
199 }
200 hasInitTriggerEmulator = true;
201 }
202
203 // self-stimulation
204 // see below for trigger output generator
205 void enableTriggerStimulator(bool incGlobalConfiguration) {
206 startSimulatedTriggerSignal();
207 engine->triggerCentral.directSelfStimulation = true;
208 engine->rpmCalculator.Register();
209 if (incGlobalConfiguration) {
210 incrementGlobalConfigurationVersion("trgSim");
211 }
212 }
213
214 // start generating trigger signal on physical outputs
215 // similar but different from self-stimulation
216 void enableExternalTriggerStimulator() {
217 startSimulatedTriggerSignal();
218 engine->triggerCentral.directSelfStimulation = false;
219 incrementGlobalConfigurationVersion("extTrg");
220 }
221
222 void disableTriggerStimulator() {
223 engine->triggerCentral.directSelfStimulation = false;
224 for (int channel = 0; channel < NUM_EMULATOR_CHANNELS; channel++) {
225 triggerEmulatorSignals[channel].stop();
226 }
227 hasInitTriggerEmulator = false;
228 incrementGlobalConfigurationVersion("disTrg");
229 }
230
231 void onConfigurationChangeRpmEmulatorCallback(engine_configuration_s *previousConfiguration) {
232 if (engineConfiguration->triggerSimulatorRpm ==
233 previousConfiguration->triggerSimulatorRpm) {
234 return;
235 }
236 setTriggerEmulatorRPM(engineConfiguration->triggerSimulatorRpm);
237 }
238
239 void initTriggerEmulator() {
240 efiPrintf("Emulating %s", getEngine_type_e(engineConfiguration->engineType));
241
242 startTriggerEmulatorPins();
243
244 addConsoleActionI(CMD_RPM, setTriggerEmulatorRPM);
245 }
246
247 #endif /* EFI_UNIT_TEST */
248
249 219 void startTriggerEmulatorPins() {
250 219 hasStimPins = false;
251
2/2
✓ Branch 0 taken 657 times.
✓ Branch 1 taken 219 times.
2/2
✓ Decision 'true' taken 657 times.
✓ Decision 'false' taken 219 times.
876 for (int channel = 0; channel < NUM_EMULATOR_CHANNELS; channel++) {
252
2/2
✓ Branch 2 taken 1314 times.
✓ Branch 3 taken 657 times.
2/2
✓ Decision 'true' taken 1314 times.
✓ Decision 'false' taken 657 times.
1971 for (size_t i = 0; i < efi::size(emulatorOutputs[channel]); i++) {
253 1314 triggerEmulatorSignals[channel].outputPins[i] = &emulatorOutputs[channel][i];
254
255 #if EFI_PROD_CODE
256 brain_pin_e pin;
257
258 pin_output_mode_e outputMode;
259 if (channel == 0) {
260 pin = engineConfiguration->triggerSimulatorPins[i];
261 outputMode = engineConfiguration->triggerSimulatorPinModes[i];
262 } else if (channel == 1 && i == 0) {
263 pin = engineConfiguration->camSimulatorPin;
264 outputMode = engineConfiguration->camSimulatorPinMode;
265 } else {
266 // todo: add pin configs for cam simulator channels
267 continue;
268 }
269
270 // Only bother trying to set output pins if they're configured
271 if (isBrainPinValid(pin)) {
272 hasStimPins = true;
273 }
274
275 if (isConfigurationChanged(triggerSimulatorPins[i])) {
276 triggerEmulatorSignals[channel].outputPins[i]->initPin("Trigger emulator", pin,
277 outputMode);
278 }
279 #endif // EFI_PROD_CODE
280 }
281 }
282 219 }
283
284 219 void stopTriggerEmulatorPins() {
285 #if EFI_PROD_CODE
286 for (int channel = 0; channel < NUM_EMULATOR_CHANNELS; channel++) {
287 // todo: add pin configs for cam simulator channels
288 if (channel != 0)
289 continue;
290 for (size_t i = 0; i < efi::size(emulatorOutputs[channel]); i++) {
291 if (isConfigurationChanged(triggerSimulatorPins[i])) {
292 triggerEmulatorSignals[channel].outputPins[i]->deInit();
293 }
294 }
295 }
296 #endif // EFI_PROD_CODE
297 219 }
298
299 #endif /* EFI_EMULATE_POSITION_SENSORS */
300