rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
engine.cpp
Go to the documentation of this file.
1/**
2 * @file engine.cpp
3 *
4 *
5 * This might be a http://en.wikipedia.org/wiki/God_object but that's best way I can
6 * express myself in C/C++. I am open for suggestions :)
7 *
8 * @date May 21, 2014
9 * @author Andrey Belomutskiy, (c) 2012-2020
10 */
11
12#include "pch.h"
13
14#include "trigger_central.h"
15#include "fuel_math.h"
16#include "advance_map.h"
17#include "speed_density.h"
18#include "advance_map.h"
19#include "init.h"
20
21#include "rusefi_wideband.h"
22#include "aux_valves.h"
23#include "map_averaging.h"
24#include "perf_trace.h"
25#include "backup_ram.h"
26#include "idle_thread.h"
27#include "idle_hardware.h"
28#include "gppwm.h"
29#include "tachometer.h"
30#include "speedometer.h"
31#include "dynoview.h"
32#include "boost_control.h"
33#include "fan_control.h"
34#include "ac_control.h"
35#include "vr_pwm.h"
36#include "max3185x.h"
37#if EFI_MC33816
38 #include "mc33816.h"
39#endif // EFI_MC33816
40
41#include "bench_test.h"
42
43#if EFI_PROD_CODE
45#endif /* EFI_PROD_CODE */
46
47#if (BOARD_TLE8888_COUNT > 0)
48#include "gpio/tle8888.h"
49#endif
50
51#if EFI_ENGINE_SNIFFER
52#include "engine_sniffer.h"
53extern int waveChartUsedSize;
54extern WaveChart waveChart;
55#endif /* EFI_ENGINE_SNIFFER */
56
58#if EFI_ENGINE_SNIFFER
60 // TODO: what is the exact reasoning for the exact engine sniffer pause time I wonder
63 }
64#endif /* EFI_ENGINE_SNIFFER */
65}
66
67/**
68 * VVT decoding delegates to universal trigger decoder. Here we map vvt_mode_e into corresponding trigger_type_e
69 */
71 switch (vvtMode) {
72 case VVT_INACTIVE:
74 case VVT_TOYOTA_3_TOOTH:
76 case VVT_MIATA_NB:
78 case VVT_BOSCH_QUICK_START:
80 case VVT_HONDA_K_EXHAUST:
82 case VVT_HONDA_K_INTAKE:
83 case VVT_SINGLE_TOOTH:
84 case VVT_MAP_V_TWIN:
86 case VVT_FORD_ST170:
88 case VVT_BARRA_3_PLUS_1:
90 case VVT_FORD_COYOTE:
92 case VVT_DEV:
94 case VVT_MAZDA_SKYACTIV:
96 case VVT_MAZDA_L:
98 case VVT_NISSAN_VQ:
100 case VVT_TOYOTA_4_1:
102 case VVT_MITSUBISHI_4G69:
104 case VVT_MITSUBISHI_3A92:
106 case VVT_MITSUBISHI_6G72:
108 case VVT_HONDA_CBR_600:
110 case VVT_MITSUBISHI_6G75:
111 case VVT_NISSAN_MR:
113 case VVT_MITSUBISHI_4G9x:
115 case VVT_MITSUBISHI_4G63:
117 case VVT_HR12DDR_IN:
119 default:
120 criticalError("Broken VVT mode maybe corrupted calibration %d: %s", vvtMode, getVvt_mode_e(vvtMode));
121 return trigger_type_e::TT_HALF_MOON; // we have to return something for the sake of -Werror=return-type
122 }
123}
124
126
127
128#if EFI_ENGINE_CONTROL && EFI_SHAFT_POSITION_INPUT
129 // we have a confusing threading model so some synchronization would not hurt
130 chibios_rt::CriticalSectionLocker csl;
131
133
134
137 }
138#endif /* EFI_ENGINE_CONTROL && EFI_SHAFT_POSITION_INPUT */
139}
140
143
144#if EFI_CAN_SUPPORT
146 static Timer canBusWboSetIndex;
147 if (canBusWboSetIndex.getElapsedSeconds() > 1) {
148 canBusWboSetIndex.reset();
150 }
151 }
152#endif // EFI_CAN_SUPPORT
153
154#if EFI_SHAFT_POSITION_INPUT
155 // Re-read config in case it's changed
157 for (int camIndex = 0;camIndex < CAMS_PER_BANK;camIndex++) {
159 }
160
162 enginePins.o2heater.setValue(getEngineState()->heaterControlEnabled);
164#endif // EFI_SHAFT_POSITION_INPUT
165
166 efiWatchdog();
169
170 module<TpsAccelEnrichment>()->onNewValue(Sensor::getOrZero(SensorType::Tps1));
171
173
174 updateGppwm();
175
176 engine->engineModules.apply_all([](auto & m) { m.onSlowCallback(); });
177
178#if (BOARD_TLE8888_COUNT > 0)
180#endif
181
182#if EFI_DYNO_VIEW
184#endif
185
187
188#if EFI_PROD_CODE
189 void baroLps25Update();
191#endif // EFI_PROD_CODE
192}
193
194/**
195 * We are executing these heavy (logarithm) methods from outside the trigger callbacks for performance reasons.
196 * See also periodicFastCallback
197 */
207
209#if EFI_GPIO_HARDWARE
212 }
213#endif // EFI_GPIO_HARDWARE
214 // todo: boolean sensors should leverage sensor framework #6342
216}
217
218static bool getClutchUpState() {
219#if EFI_GPIO_HARDWARE
222 }
223#endif // EFI_GPIO_HARDWARE
224 // todo: boolean sensors should leverage sensor framework #6342
226}
227
229#if EFI_GPIO_HARDWARE
232 }
233#endif // EFI_GPIO_HARDWARE
234 // todo: boolean sensors should leverage sensor framework #6342
236}
237
238
240 // this value is not used yet
244#if EFI_GPIO_HARDWARE
245 {
246 bool currentState;
247 if (hasAcToggle()) {
248 currentState = getAcToggle();
249#ifdef EFI_KLINE
250 } else if (engineConfiguration->hondaK) {
251extern bool kAcRequestState;
252 currentState = kAcRequestState;
253#endif // EFI_KLINE
254 } else {
255 currentState = engine->engineState.lua.acRequestState;
256 }
257 AcController & acController = engine->module<AcController>().unmock();
258 if (engine->acButtonSwitchedState.update(currentState)) {
259 acController.timeSinceStateChange.reset();
260 }
261 }
262
264
265#endif // EFI_GPIO_HARDWARE
266}
267
269 : clutchUpSwitchedState(&engineState.clutchUpState),
270 brakePedalSwitchedState(&engineState.brakePedalState),
271 acButtonSwitchedState(&module<AcController>().unmock().acButtonState)
272
273#if EFI_LAUNCH_CONTROL
274
275 , softSparkLimiter(false), hardSparkLimiter(true)
276
277#if EFI_ANTILAG_SYSTEM
278// , ALSsoftSparkLimiter(false)
279#endif /* EFI_ANTILAG_SYSTEM */
280
281#endif // EFI_LAUNCH_CONTROL
282{
283 reset();
284}
285
289
291 /**
292 * it's important for wrapAngle() that engineCycle field never has zero
293 */
295 resetLua();
296}
297
299 // todo: https://github.com/rusefi/rusefi/issues/4308
300 engineState.lua = {};
304 engineState.lua.luaIgnCut = false;
305 engineState.lua.luaFuelCut = false;
307#if EFI_BOOST_CONTROL
308 module<BoostController>().unmock().resetLua();
309#endif // EFI_BOOST_CONTROL
312#if EFI_IDLE_CONTROL
313 module<IdleController>().unmock().luaAdd = 0;
314#endif // EFI_IDLE_CONTROL
315}
316
317/**
318 * Here we have a bunch of stuff which should invoked after configuration change
319 * so that we can prepare some helper structures
320 */
322#if EFI_TUNER_STUDIO
323 // we take 2 bytes of crc32, no idea if it's right to call it crc16 or not
324 // we have a hack here - we rely on the fact that engineMake is the first of three relevant fields
326
328#endif /* EFI_TUNER_STUDIO */
329}
330
331#if EFI_SHAFT_POSITION_INPUT
334}
335
337 // Needed for early instant-RPM detection
339
342
343 for (size_t i = 0; i < efi::size(triggerCentral.vvtState); i++) {
344 for (size_t j = 0; j < efi::size(triggerCentral.vvtState[0]); j++) {
346 }
347 }
348}
349
350void Engine::OnTriggerSynchronization(bool wasSynchronized, bool isDecodingError) {
351 // TODO: this logic probably shouldn't be part of engine.cpp
352
353 // We only care about trigger shape once we have synchronized trigger. Anything could happen
354 // during first revolution and it's fine
355 if (wasSynchronized) {
357
358 // 'triggerStateListener is not null' means we are running a real engine and now just preparing trigger shape
359 // that's a bit of a hack, a sweet OOP solution would be a real callback or at least 'needDecodingErrorLogic' method?
360 if (isDecodingError) {
361#if EFI_PROD_CODE
363 efiPrintf("error: synchronizationPoint @ index %lu expected %d/%d got %d/%d",
369 }
370#endif /* EFI_PROD_CODE */
371 }
372
373 engine->triggerCentral.triggerErrorDetection.add(isDecodingError);
374 }
375
376}
377#endif
378
380#if EFI_SHAFT_POSITION_INPUT
382 for (int camIndex = 0;camIndex < CAMS_PER_BANK;camIndex++) {
384 }
385#endif // EFI_SHAFT_POSITION_INPUT
386}
387
389#if !EFI_UNIT_TEST
390// huh should this be happy? static_assert(config != nullptr);
391#endif
392 efi::clear(config);
393
395}
396
397/**
398 * This code asserts that we do not have unexpected gaps in time flow with the exception of internal flash burn.
399 */
400static void assertTimeIsLinear() {
401#if ! EFI_UNIT_TEST
402 static efitimems_t mostRecentMs = 0;
403 efitimems_t msNow = getTimeNowMs();
405 if (mostRecentMs != 0) {
406 efitimems_t gapInMs = msNow - mostRecentMs;
407 // todo: lower gapInMs threshold?
408 if (gapInMs > 200) {
409 firmwareError(ObdCode::RUNTIME_CRITICAL_WATCH_DOG_SECONDS, "gap in time: mostRecentMs %lumS, now=%lumS, gap=%lumS",
410 mostRecentMs, msNow, gapInMs);
411 }
412 }
413 }
414 mostRecentMs = msNow;
415#endif
416}
417
421 return;
422
423#if EFI_ENGINE_CONTROL && EFI_SHAFT_POSITION_INPUT
424 if (module<PrimeController>()->isPriming() || triggerCentral.engineMovedRecently()) {
425 // do not invoke check in priming or if engine moved recently, no need to assert safe pin state.
426 return;
427 }
428
431 // todo: make this a firmwareError assuming functional tests would run
432 warning(ObdCode::CUSTOM_ERR_2ND_WATCHDOG, "Some pins were turned off by 2nd pass watchdog");
433 }
434 return;
435 }
436
437 /**
438 * todo: better watch dog implementation should be implemented - see
439 * http://sourceforge.net/p/rusefi/tickets/96/
440 */
443#endif // EFI_ENGINE_CONTROL && EFI_SHAFT_POSITION_INPUT
444}
445
447#if EFI_ENGINE_CONTROL
448 ignitionEvents.isReady = false;
449#endif // EFI_ENGINE_CONTROL
450
451#if EFI_PROD_CODE || EFI_SIMULATOR
452 efiPrintf("Engine has stopped spinning.");
453#endif
454
455 // this invocation should be the last layer of defence in terms of making sure injectors/coils are not active
457}
458
460#if EFI_MAIN_RELAY_CONTROL
461 // if we are already in the "ignition_on" mode, then do nothing
462/* this logic is not alive
463 if (ignitionOnTimeNt > 0) {
464 return;
465 }
466todo: move to shutdown_controller.cpp
467*/
468
469 // here we are in the shutdown (the ignition is off) or initial mode (after the firmware fresh start)
470/* this needs work or tests
471 const efitick_t engineStopWaitTimeoutUs = 500000LL; // 0.5 sec
472 // in shutdown mode, we need a small cooldown time between the ignition off and on
473todo: move to shutdown_controller.cpp
474 if (stopEngineRequestTimeNt == 0 || (getTimeNowNt() - stopEngineRequestTimeNt) > US2NT(engineStopWaitTimeoutUs)) {
475 // if the ignition key is turned on again,
476 // we cancel the shutdown mode, but only if all shutdown procedures are complete
477 const float vBattThresholdOn = 8.0f;
478 // we fallback into zero instead of VBAT_FALLBACK_VALUE because it's not safe to false-trigger the "ignition on" event,
479 // and we want to turn on the main relay only when 100% sure.
480 if ((Sensor::getOrZero(SensorType::BatteryVoltage) > vBattThresholdOn) && !isInShutdownMode()) {
481 ignitionOnTimeNt = getTimeNowNt();
482 efiPrintf("Ignition voltage detected!");
483 if (stopEngineRequestTimeNt != 0) {
484 efiPrintf("Cancel the engine shutdown!");
485 stopEngineRequestTimeNt = 0;
486 }
487 }
488 }
489*/
490#endif /* EFI_MAIN_RELAY_CONTROL */
491}
492
494 // TODO: this logic is currently broken
495#if 0 && EFI_MAIN_RELAY_CONTROL && EFI_PROD_CODE
496 // if we are in "ignition_on" mode and not in shutdown mode
497 if (stopEngineRequestTimeNt == 0 && ignitionOnTimeNt > 0) {
498 const float vBattThresholdOff = 5.0f;
499 // start the shutdown process if the ignition voltage dropped low
500 if (Sensor::get(SensorType::BatteryVoltage).value_or(VBAT_FALLBACK_VALUE) <= vBattThresholdOff) {
502 }
503 }
504
505 // we are not in the shutdown mode?
506 if (stopEngineRequestTimeNt == 0) {
507 return false;
508 }
509
510 const efitick_t turnOffWaitTimeoutNt = NT_PER_SECOND;
511 // We don't want any transients to step in, so we wait at least 1 second whatever happens.
512 // Also it's good to give the stepper motor some time to start moving to the initial position (or parking)
513 if ((getTimeNowNt() - stopEngineRequestTimeNt) < turnOffWaitTimeoutNt)
514 return true;
515
516 const efitick_t engineSpinningWaitTimeoutNt = 5 * NT_PER_SECOND;
517 // The engine is still spinning! Give it some time to stop (but wait no more than 5 secs)
518 if (isSpinning && (getTimeNowNt() - stopEngineRequestTimeNt) < engineSpinningWaitTimeoutNt)
519 return true;
520
521 // The idle motor valve is still moving! Give it some time to park (but wait no more than 10 secs)
522 // Usually it can move to the initial 'cranking' position or zero 'parking' position.
523 const efitick_t idleMotorWaitTimeoutNt = 10 * NT_PER_SECOND;
524 if (isIdleMotorBusy() && (getTimeNowNt() - stopEngineRequestTimeNt) < idleMotorWaitTimeoutNt)
525 return true;
526#endif /* EFI_MAIN_RELAY_CONTROL */
527 return false;
528}
529
531#if EFI_MAIN_RELAY_CONTROL
533#else
534 // if no main relay control, we assume it's always turned on
535 return true;
536#endif /* EFI_MAIN_RELAY_CONTROL */
537}
538
542
543/**
544 * The idea of this method is to execute all heavy calculations in a lower-priority thread,
545 * so that trigger event handler/IO scheduler tasks are faster.
546 */
549
550#if EFI_MAP_AVERAGING
552#endif
553
555
556 tachUpdate();
557 speedoUpdate();
558
559 engineModules.apply_all([](auto & m) { m.onFastCallback(); });
560}
561
565
569
573
575 return &engine->scheduler;
576}
577
578#if EFI_SHAFT_POSITION_INPUT
582#endif // EFI_SHAFT_POSITION_INPUT
583
584#if EFI_ENGINE_CONTROL
586 return &engine->module<LimpManager>().unmock();
587}
588
592
596#endif // EFI_ENGINE_CONTROL
bool getAcToggle()
bool hasAcToggle()
const char * getVvt_mode_e(vvt_mode_e value)
Non-volatile backup-RAM registers support.
bool isRunningBenchTest()
Utility methods related to bench testing.
Timer timeSinceStateChange
Definition ac_control.h:16
TriggerCentral triggerCentral
Definition engine.h:299
void resetLua()
Definition engine.cpp:298
void updateSlowSensors()
Definition engine.cpp:198
bool isFunctionalTestMode
Definition engine.h:321
FuelSchedule injectionEvents
Definition engine.h:269
IgnitionEventList ignitionEvents
Definition engine.h:270
void onEngineHasStopped()
Definition engine.cpp:446
IgnitionState ignitionState
Definition engine.h:221
void periodicFastCallback()
Definition engine.cpp:547
type_list< Mockable< InjectorModelPrimary >, Mockable< InjectorModelSecondary >,#if EFI_IDLE_CONTROL Mockable< IdleController >,#endif TriggerScheduler,#if EFI_HPFP &&EFI_ENGINE_CONTROL HpfpController,#endif #if EFI_ENGINE_CONTROL Mockable< ThrottleModel >,#endif #if EFI_ALTERNATOR_CONTROL AlternatorController,#endif FuelPumpController, MainRelayController, Mockable< IgnitionController >, Mockable< AcController >, FanControl1, FanControl2, PrimeController, DfcoController,#if EFI_HD_ACR HarleyAcr,#endif Mockable< WallFuelController >,#if EFI_VEHICLE_SPEED GearDetector, TripOdometer,#endif KnockController, SensorChecker,#if EFI_ENGINE_CONTROL LimpManager,#endif #if EFI_VVT_PID VvtController1, VvtController2, VvtController3, VvtController4,#endif #if EFI_BOOST_CONTROL BoostController,#endif TpsAccelEnrichment,#if EFI_LAUNCH_CONTROL NitrousController,#endif #if EFI_LTFT_CONTROL LongTermFuelTrim,#endif EngineModule > engineModules
Definition engine.h:181
void checkShutdown()
Definition engine.cpp:459
void preCalculate()
Definition engine.cpp:321
int getGlobalConfigurationVersion(void) const
Definition engine.cpp:286
bool slowCallBackWasInvoked
Definition engine.h:285
SingleTimerExecutor scheduler
Definition engine.h:252
bool isInShutdownMode() const
Definition engine.cpp:493
void efiWatchdog()
Definition engine.cpp:418
bool isMainRelayEnabled() const
Definition engine.cpp:530
EngineState engineState
Definition engine.h:325
void periodicSlowCallback()
Definition engine.cpp:141
SwitchedState brakePedalSwitchedState
Definition engine.h:197
void OnTriggerStateProperState(efitick_t nowNt) override
Definition engine.cpp:332
RpmCalculator rpmCalculator
Definition engine.h:287
void resetEngineSnifferIfInTestMode()
Definition engine.cpp:57
SwitchedState clutchUpSwitchedState
Definition engine.h:196
Engine()
Definition engine.cpp:268
int globalConfigurationVersion
Definition engine.h:296
SwitchedState acButtonSwitchedState
Definition engine.h:198
void setConfig()
Definition engine.cpp:388
void OnTriggerSynchronization(bool wasSynchronized, bool isDecodingError) override
Definition engine.cpp:350
void injectEngineReferences()
Definition engine.cpp:379
TunerStudioOutputChannels outputChannels
Definition engine.h:102
void updateTriggerConfiguration()
Definition engine.cpp:125
void updateSwitchInputs()
Definition engine.cpp:239
constexpr auto & module()
Definition engine.h:187
void reset()
Definition engine.cpp:290
void OnTriggerSynchronizationLost() override
Definition engine.cpp:336
Timer configBurnTimer
Definition engine.h:289
bool isRunningPwmTest
Definition engine.h:315
RegisteredOutputPin mainRelay
Definition efi_gpio.h:76
OutputPin o2heater
Definition efi_gpio.h:98
RegisteredOutputPin triggerDecoderErrorPin
Definition efi_gpio.h:120
RegisteredOutputPin starterRelayDisable
Definition efi_gpio.h:84
bool stopPins()
Definition efi_gpio.cpp:217
virtual bool isCranking() const =0
angle_t engineCycle
void periodicFastCallback()
Definition engine2.cpp:101
sensor_chart_e sensorChartMode
bool getLogicValue() const
Definition efi_gpio.cpp:653
void setValue(const char *msg, int logicValue, bool isForce=false)
Definition efi_gpio.cpp:590
void setSpinningUp(efitick_t nowNt)
bool isRunning() const
virtual SensorResult get() const =0
static float getOrZero(SensorType type)
Definition sensor.h:83
bool update(bool newState)
VvtTriggerDecoder vvtState[BANKS_COUNT][CAMS_PER_BANK]
InstantRpmCalculator instantRpm
PrimaryTriggerDecoder triggerState
bool engineMovedRecently(efitick_t nowNt) const
TriggerWaveform triggerShape
cyclic_buffer< int > triggerErrorDetection
VvtTriggerConfiguration vvtTriggerConfiguration[CAMS_PER_BANK]
PrimaryTriggerConfiguration primaryTriggerConfiguration
virtual void resetState()
current_cycle_state_s currentCycle
bool someSortOfTriggerError() const
size_t getExpectedEventCount(TriggerWheel channelIndex) const
rusEfi console sniffer data buffer
efitick_t pauseEngineSnifferUntilNt
void updateDynoView()
Definition dynoview.cpp:147
EnginePins enginePins
Definition efi_gpio.cpp:24
efitick_t getTimeNowNt()
Definition efitime.cpp:19
efitimems_t getTimeNowMs()
Returns the 32 bit number of milliseconds since the board initialization.
Definition efitime.cpp:34
int waveChartUsedSize
bool getBrakePedalState()
Definition engine.cpp:228
LimpManager * getLimpManager()
Definition engine.cpp:585
Scheduler * getScheduler()
Definition engine.cpp:574
static void assertTimeIsLinear()
Definition engine.cpp:400
TriggerCentral * getTriggerCentral()
Definition engine.cpp:579
static bool getClutchUpState()
Definition engine.cpp:218
injection_mode_e getCurrentInjectionMode()
Definition engine.cpp:539
FuelSchedule * getFuelSchedule()
Definition engine.cpp:589
trigger_type_e getVvtTriggerType(vvt_mode_e vvtMode)
Definition engine.cpp:70
EngineRotationState * getEngineRotationState()
Definition engine.cpp:562
EngineState * getEngineState()
Definition engine.cpp:566
bool getClutchDownState()
Definition engine.cpp:208
IgnitionEventList * getIgnitionEvents()
Definition engine.cpp:593
TunerStudioOutputChannels * getTunerStudioOutputChannels()
Definition engine.cpp:570
WaveChart waveChart
static Engine *const engine
Definition engine.h:389
void prepareOutputSignals()
static constexpr persistent_config_s * config
static constexpr engine_configuration_s * engineConfiguration
rusEfi console wave sniffer
trigger_type_e
bool warning(ObdCode code, const char *fmt,...)
void firmwareError(ObdCode code, const char *fmt,...)
void updateGppwm()
Definition gppwm.cpp:58
Idle Air Control valve hardware.
bool isIdleMotorBusy()
Idle Valve Control thread.
void pokeAuxDigital()
void baroLps25Update()
Definition init_baro.cpp:19
bool efiReadPin(brain_pin_e pin)
Definition io_pins.cpp:89
bool kAcRequestState
Definition kline.cpp:31
void refreshMapAveragingPreCalc()
@ RUNTIME_CRITICAL_WATCH_DOG_SECONDS
@ CUSTOM_ERR_2ND_WATCHDOG
@ EnginePeriodicSlowCallback
@ EnginePeriodicFastCallback
bool isBrainPinValid(brain_pin_e brainPin)
vvt_mode_e
@ FOUR_STROKE_CRANK_SENSOR
injection_mode_e
uint32_t efitimems_t
void setWidebandOffset(uint8_t index)
brakePedalState("Brake switch", SensorCategory.SENSOR_INPUTS, FieldType.INT8, 1263, 1.0, -1.0, -1.0, "")
acButtonState("AC switch", SensorCategory.SENSOR_INPUTS, FieldType.INT8, 1184, 1.0, -1.0, -1.0, "")
clutchUpState("Clutch: up", SensorCategory.SENSOR_INPUTS, FieldType.INT8, 1262, 1.0, -1.0, -1.0, "")
void scheduleStopEngine()
Definition settings.cpp:458
void tle8888startup()
void speedoUpdate()
WaveChart waveChart
size_t eventCount[PWM_PHASE_MAX_WAVE_PER_PWM]
void apply_all(func_t const &f)
Definition type_list.h:43
void tachUpdate()
angle_t getEngineCycle(operation_mode_e operationMode)
void updateVrThresholdPwm()
Definition vr_pwm.cpp:31