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 |