/** * @file bench_test.cpp * @brief Utility methods related to bench testing. * * * @date Sep 8, 2013 * @author Andrey Belomutskiy, (c) 2012-2020 * * This file is part of rusEfi - see http://rusefi.com * * rusEfi is free software; you can redistribute it and/or modify it under the terms of * the GNU General Public License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. * If not, see . */ #include "pch.h" static bool isRunningBench = false; static OutputPin *outputOnTheBenchTest = nullptr; bool isRunningBenchTest() { return isRunningBench; } const OutputPin *getOutputOnTheBenchTest() { return outputOnTheBenchTest; } #if !EFI_UNIT_TEST #include "flash_main.h" #include "bench_test.h" #include "main_trigger_callback.h" #include "periodic_thread_controller.h" #include "electronic_throttle.h" #include "electronic_throttle_impl.h" #include "malfunction_central.h" #include "trigger_emulator_algo.h" #include "vvt.h" #include "microsecond_timer.h" #if EFI_WIDEBAND_FIRMWARE_UPDATE #include "rusefi_wideband.h" #endif // EFI_WIDEBAND_FIRMWARE_UPDATE #if EFI_PROD_CODE #include "rusefi.h" #include "mpu_util.h" #endif /* EFI_PROD_CODE */ #if (BOARD_TLE8888_COUNT > 0) #include "gpio/tle8888.h" #endif // BOARD_TLE8888_COUNT static scheduling_s benchSchedStart; static scheduling_s benchSchedEnd; #if EFI_SIMULATOR static int savedPinToggleCounter = 0; static uint32_t savedDurationsInStateMs[2] = { 0, 0 }; #endif // EFI_SIMULATOR #define BENCH_MSG "bench" static void benchOn(OutputPin* output) { output->setValue(BENCH_MSG, true, /*isForce*/ true); } static void benchOff(OutputPin* output) { #if EFI_PROD_CODE && (BOARD_EXT_GPIOCHIPS > 0) static char pin_error[64]; brain_pin_diag_e diag = output->getDiag(); if (diag == PIN_UNKNOWN) { efiPrintf("No Diag on this pin"); } else { pinDiag2string(pin_error, sizeof(pin_error), diag); efiPrintf("Diag says %s", pin_error); } #endif // EFI_PROD_CODE output->setValue(BENCH_MSG, false, /*isForce*/ true); } static void runBench(OutputPin *output, float onTimeMs, float offTimeMs, int count, bool swapOnOff) { int onTimeUs = MS2US(maxF(0.1, onTimeMs)); int offTimeUs = MS2US(maxF(0.1, offTimeMs)); if (onTimeUs > TOO_FAR_INTO_FUTURE_US) { firmwareError(ObdCode::CUSTOM_ERR_BENCH_PARAM, "onTime above limit %dus", TOO_FAR_INTO_FUTURE_US); return; } efiPrintf("Running bench: ON_TIME=%d us OFF_TIME=%d us Counter=%d", onTimeUs, offTimeUs, count); efiPrintf("output on %s", hwPortname(output->brainPin)); isRunningBench = true; outputOnTheBenchTest = output; for (int i = 0; isRunningBench && i < count; i++) { engine->outputChannels.testBenchIter = i; efitick_t nowNt = getTimeNowNt(); // start in a short time so the scheduler can precisely schedule the start event efitick_t startTime = nowNt + US2NT(50); efitick_t endTime = startTime + US2NT(onTimeUs); // Schedule both events engine->executor.scheduleByTimestampNt("bstart", &benchSchedStart, startTime, {(swapOnOff ? benchOff : benchOn), output}); engine->executor.scheduleByTimestampNt("bend", &benchSchedEnd, endTime, {(swapOnOff ? benchOn : benchOff), output}); // Wait one full cycle time for the event + delay to happen chThdSleepMicroseconds(onTimeUs + offTimeUs); } /* last */ engine->outputChannels.testBenchIter++; #if EFI_SIMULATOR // save the current counters and durations after the test while the pin is still controlled savedPinToggleCounter = output->pinToggleCounter; savedDurationsInStateMs[0] = output->durationsInStateMs[0]; savedDurationsInStateMs[1] = output->durationsInStateMs[1]; #endif // EFI_SIMULATOR efiPrintf("Done!"); outputOnTheBenchTest = nullptr; isRunningBench = false; } // todo: migrate to smarter getOutputOnTheBenchTest() approach? static volatile bool isBenchTestPending = false; static bool widebandUpdatePending = false; static float globalOnTimeMs; static float globalOffTimeMs; static int globalCount; static OutputPin* pinX; static bool swapOnOff = false; static chibios_rt::CounterSemaphore benchSemaphore(0); static void pinbench(float ontimeMs, float offtimeMs, int iterations, OutputPin* pinParam, bool p_swapOnOff = false) { globalOnTimeMs = ontimeMs; globalOffTimeMs = offtimeMs; #if EFI_SIMULATOR globalCount = maxI(2, iterations); #else globalCount = iterations; #endif // EFI_SIMULATOR pinX = pinParam; swapOnOff = p_swapOnOff; // let's signal bench thread to wake up isBenchTestPending = true; benchSemaphore.signal(); } static void cancelBenchTest() { isRunningBench = false; } /*==========================================================================*/ static void doRunFuelInjBench(size_t humanIndex, float onTimeMs, float offTimeMs, int count) { if (humanIndex < 1 || humanIndex > engineConfiguration->cylindersCount) { efiPrintf("Invalid index: %d", humanIndex); return; } pinbench(onTimeMs, offTimeMs, count, &enginePins.injectors[humanIndex - 1]); } static void doRunSparkBench(size_t humanIndex, float onTime, float offTime, int count) { if (humanIndex < 1 || humanIndex > engineConfiguration->cylindersCount) { efiPrintf("Invalid index: %d", humanIndex); return; } pinbench(onTime, offTime, count, &enginePins.coils[humanIndex - 1]); } static void doRunSolenoidBench(size_t humanIndex, float onTime, float offTime, int count) { if (humanIndex < 1 || humanIndex > TCU_SOLENOID_COUNT) { efiPrintf("Invalid index: %d", humanIndex); return; } pinbench(onTime, offTime, count, &enginePins.tcuSolenoids[humanIndex - 1]); } static void doRunBenchTestLuaOutput(size_t humanIndex, float onTimeMs, float offTimeMs, int count) { if (humanIndex < 1 || humanIndex > LUA_PWM_COUNT) { efiPrintf("Invalid index: %d", humanIndex); return; } pinbench(onTimeMs, offTimeMs, count, &enginePins.luaOutputPins[humanIndex - 1]); } /** * cylinder #2, 5ms ON, 1000ms OFF, repeat 3 times * fuelInjBenchExt 2 5 1000 3 */ static void fuelInjBenchExt(float humanIndex, float onTimeMs, float offTimeMs, float count) { doRunFuelInjBench((int)humanIndex, onTimeMs, offTimeMs, (int)count); } /** * fuelbench 5 1000 2 */ static void fuelInjBench(float onTimeMs, float offTimeMs, float count) { fuelInjBenchExt(1, onTimeMs, offTimeMs, count); } /** * sparkbench2 1 5 1000 2 */ static void sparkBenchExt(float humanIndex, float onTime, float offTimeMs, float count) { doRunSparkBench((int)humanIndex, onTime, offTimeMs, (int)count); } /** * sparkbench 5 400 2 * 5 ms ON, 400 ms OFF, two times */ static void sparkBench(float onTime, float offTimeMs, float count) { sparkBenchExt(1, onTime, offTimeMs, count); } /** * solenoid #2, 1000ms ON, 1000ms OFF, repeat 3 times * tcusolbench 2 1000 1000 3 */ static void tcuSolenoidBench(float humanIndex, float onTime, float offTimeMs, float count) { doRunSolenoidBench((int)humanIndex, onTime, offTimeMs, (int)count); } /** * channel #1, 5ms ON, 1000ms OFF, repeat 3 times * fsiobench2 1 5 1000 3 */ static void luaOutBench2(float humanIndex, float onTime, float offTimeMs, float count) { doRunBenchTestLuaOutput((int)humanIndex, onTime, offTimeMs, (int)count); } static void fanBenchExt(float onTimeMs) { pinbench(onTimeMs, 100.0, 1, &enginePins.fanRelay); } void fanBench() { fanBenchExt(BENCH_FAN_DURATION); } void fan2Bench() { pinbench(3000.0, 100.0, 1, &enginePins.fanRelay2); } /** * we are blinking for 16 seconds so that one can click the button and walk around to see the light blinking */ void milBench() { pinbench(500.0, 500.0, 16, &enginePins.checkEnginePin); } void starterRelayBench() { pinbench(BENCH_STARTER_DURATION, 100.0, 1, &enginePins.starterControl); } static void fuelPumpBenchExt(float durationMs) { pinbench(durationMs, 100.0, 1, &enginePins.fuelPumpRelay); } void acRelayBench() { pinbench(BENCH_AC_RELAY_DURATION, 100.0, 1, &enginePins.acRelay); } static void mainRelayBench() { // main relay is usually "ON" via FSIO thus bench testing that one is pretty unusual pinbench(BENCH_MAIN_RELAY_DURATION, 100.0, 1, &enginePins.mainRelay, true); } static void hpfpValveBench() { pinbench(engineConfiguration->benchTestOnTime, engineConfiguration->benchTestOffTime, engineConfiguration->benchTestCount, &enginePins.hpfpValve); } void fuelPumpBench() { fuelPumpBenchExt(BENCH_FUEL_PUMP_DURATION); } static void vvtValveBench(int vvtIndex) { #if EFI_VVT_PID pinbench(BENCH_VVT_DURATION, 100.0, 1, getVvtOutputPin(vvtIndex)); #endif // EFI_VVT_PID } class BenchController : public ThreadController { public: BenchController() : ThreadController("BenchTest", PRIO_BENCH_TEST) { } private: void ThreadTask() override { while (true) { benchSemaphore.wait(); assertStackVoid("Bench", ObdCode::STACK_USAGE_MISC, EXPECTED_REMAINING_STACK); if (isBenchTestPending) { isBenchTestPending = false; runBench(pinX, globalOnTimeMs, globalOffTimeMs, globalCount, swapOnOff); } if (widebandUpdatePending) { #if EFI_WIDEBAND_FIRMWARE_UPDATE && EFI_CAN_SUPPORT updateWidebandFirmware(); #endif widebandUpdatePending = false; } } } }; static BenchController instance; static void auxOutBench(int index) { // todo! } #if EFI_HD_ACR static void hdAcrBench(int index) { OutputPin* pin = index == 0 ? &enginePins.harleyAcr : &enginePins.harleyAcr2; pinbench(BENCH_AC_RELAY_DURATION, 100.0, 1, pin); } #endif // EFI_HD_ACR void handleBenchCategory(uint16_t index) { switch(index) { case BENCH_VVT0_VALVE: vvtValveBench(0); return; case BENCH_VVT1_VALVE: vvtValveBench(1); return; case BENCH_VVT2_VALVE: vvtValveBench(2); return; case BENCH_VVT3_VALVE: vvtValveBench(3); return; case BENCH_AUXOUT0: auxOutBench(0); return; case BENCH_AUXOUT1: auxOutBench(1); return; case BENCH_AUXOUT2: auxOutBench(2); return; case BENCH_AUXOUT3: auxOutBench(3); return; case BENCH_AUXOUT4: auxOutBench(4); return; case BENCH_AUXOUT5: auxOutBench(5); return; case BENCH_AUXOUT6: auxOutBench(6); return; case BENCH_AUXOUT7: auxOutBench(7); return; #if EFI_HD_ACR case HD_ACR: hdAcrBench(0); return; case HD_ACR2: hdAcrBench(1); return; #endif // EFI_HD_ACR case BENCH_HPFP_VALVE: hpfpValveBench(); return; case BENCH_FUEL_PUMP: // cmd_test_fuel_pump fuelPumpBench(); return; case BENCH_MAIN_RELAY: mainRelayBench(); return; case BENCH_STARTER_ENABLE_RELAY: starterRelayBench(); return; case BENCH_CHECK_ENGINE_LIGHT: // cmd_test_check_engine_light milBench(); return; case BENCH_AC_COMPRESSOR_RELAY: acRelayBench(); return; case BENCH_FAN_RELAY: fanBench(); return; case BENCH_IDLE_VALVE: // cmd_test_idle_valve #if EFI_IDLE_CONTROL startIdleBench(); #endif /* EFI_IDLE_CONTROL */ return; case BENCH_FAN_RELAY_2: fan2Bench(); return; case BENCH_CANCEL: cancelBenchTest(); return; default: criticalError("Unexpected bench function %d", index); } } int getSavedBenchTestPinStates(uint32_t durationsInStateMs[2]) { #if EFI_SIMULATOR durationsInStateMs[0] = savedDurationsInStateMs[0]; durationsInStateMs[1] = savedDurationsInStateMs[1]; return savedPinToggleCounter; #else UNUSED(durationsInStateMs); return 0; #endif // EFI_SIMULATOR } static void handleCommandX14(uint16_t index) { // todo: define ts_14_command magic constants and use those in rusefi.input file! switch (index) { case TS_GRAB_TPS_CLOSED: grabTPSIsClosed(); return; case TS_GRAB_TPS_WOT: grabTPSIsWideOpen(); return; // case 4: tps2_closed // case 5: tps2_wot case TS_GRAB_PEDAL_UP: grabPedalIsUp(); return; case TS_GRAB_PEDAL_WOT: grabPedalIsWideOpen(); return; case TS_RESET_TLE8888: #if (BOARD_TLE8888_COUNT > 0) tle8888_req_init(); #endif return; case TS_RESET_MC33810: #if (BOARD_MC33810_COUNT > 0) mc33810_req_init(); #endif return; case TS_WRITE_FLASH: // cmd_write_config #if (EFI_STORAGE_INT_FLASH == TRUE) || (EFI_STORAGE_MFS == TRUE) writeToFlashNow(); #endif /* (EFI_STORAGE_INT_FLASH == TRUE) || (EFI_STORAGE_MFS == TRUE) */ return; case TS_TRIGGER_STIMULATOR_ENABLE: #if EFI_EMULATE_POSITION_SENSORS == TRUE enableTriggerStimulator(); #endif /* EFI_EMULATE_POSITION_SENSORS == TRUE */ return; case TS_TRIGGER_STIMULATOR_DISABLE: #if EFI_EMULATE_POSITION_SENSORS == TRUE disableTriggerStimulator(); #endif /* EFI_EMULATE_POSITION_SENSORS == TRUE */ return; case TS_EXTERNAL_TRIGGER_STIMULATOR_ENABLE: #if EFI_EMULATE_POSITION_SENSORS == TRUE enableExternalTriggerStimulator(); #endif /* EFI_EMULATE_POSITION_SENSORS == TRUE */ return; case TS_ETB_RESET: #if EFI_ELECTRONIC_THROTTLE_BODY == TRUE #if EFI_PROD_CODE etbPidReset(); #endif #endif /* EFI_ELECTRONIC_THROTTLE_BODY == TRUE */ return; case TS_ETB_AUTOCAL_0: #if EFI_ELECTRONIC_THROTTLE_BODY == TRUE etbAutocal(0); #endif /* EFI_ELECTRONIC_THROTTLE_BODY == TRUE */ return; case TS_ETB_AUTOCAL_1: #if EFI_ELECTRONIC_THROTTLE_BODY == TRUE etbAutocal(1); #endif /* EFI_ELECTRONIC_THROTTLE_BODY == TRUE */ return; case TS_ETB_START_AUTOTUNE: #if EFI_ELECTRONIC_THROTTLE_BODY == TRUE engine->etbAutoTune = true; #endif /* EFI_ELECTRONIC_THROTTLE_BODY == TRUE */ return; case TS_ETB_STOP_AUTOTUNE: #if EFI_ELECTRONIC_THROTTLE_BODY == TRUE engine->etbAutoTune = false; #if EFI_TUNER_STUDIO engine->outputChannels.calibrationMode = (uint8_t)TsCalMode::None; #endif // EFI_TUNER_STUDIO #endif /* EFI_ELECTRONIC_THROTTLE_BODY == TRUE */ return; case TS_WIDEBAND_UPDATE: widebandUpdatePending = true; benchSemaphore.signal(); return; case TS_BURN_WITHOUT_FLASH: #if EFI_PROD_CODE #if (EFI_STORAGE_INT_FLASH == TRUE) || (EFI_STORAGE_MFS == TRUE) extern bool burnWithoutFlash; burnWithoutFlash = true; #endif /* (EFI_STORAGE_INT_FLASH == TRUE) || (EFI_STORAGE_MFS == TRUE) */ #endif // EFI_PROD_CODE return; default: criticalError("Unexpected bench x14 %d", index); } } extern bool rebootForPresetPending; static void fatalErrorForPresetApply() { rebootForPresetPending = true; firmwareError(ObdCode::OBD_PCM_Processor_Fault, "\n\nTo complete preset apply:\n" " 1. Close TunerStudio\n" " 2. Power cycle ECU\n" " 3. Open TunerStudio and reconnect\n\n"); } void executeTSCommand(uint16_t subsystem, uint16_t index) { efiPrintf("IO test subsystem=%d index=%d", subsystem, index); bool running = !engine->rpmCalculator.isStopped(); switch (subsystem) { case TS_CLEAR_WARNINGS: clearWarnings(); break; case TS_DEBUG_MODE: engineConfiguration->debugMode = (debug_mode_e)index; break; case TS_IGNITION_CATEGORY: if (!running) { doRunSparkBench(index, engineConfiguration->benchTestOnTime, engineConfiguration->benchTestOffTime, engineConfiguration->benchTestCount); } break; case TS_INJECTOR_CATEGORY: if (!running) { doRunFuelInjBench(index, engineConfiguration->benchTestOnTime, engineConfiguration->benchTestOffTime, engineConfiguration->benchTestCount); } break; case TS_SOLENOID_CATEGORY: if (!running) { doRunSolenoidBench(index, 1000.0, 1000.0, engineConfiguration->benchTestCount); } break; case TS_LUA_OUTPUT_CATEGORY: if (!running) { doRunBenchTestLuaOutput(index, 4.0, engineConfiguration->benchTestOffTime, engineConfiguration->benchTestCount); } break; case TS_X14: handleCommandX14(index); break; #if defined(EFI_WIDEBAND_FIRMWARE_UPDATE) && EFI_CAN_SUPPORT case TS_WIDEBAND: setWidebandOffset(index); break; #endif // EFI_WIDEBAND_FIRMWARE_UPDATE && EFI_CAN_SUPPORT case TS_BENCH_CATEGORY: handleBenchCategory(index); break; case TS_SET_ENGINE_TYPE: fatalErrorForPresetApply(); setEngineType(index); break; case TS_SET_DEFAULT_ENGINE: fatalErrorForPresetApply(); setEngineType((int)DEFAULT_ENGINE_TYPE); break; case 0x79: scheduleStopEngine(); break; case 0xba: #if EFI_DFU_JUMP jump_to_bootloader(); #endif /* EFI_DFU_JUMP */ break; case 0xbb: #if EFI_PROD_CODE rebootNow(); #endif /* EFI_PROD_CODE */ break; #if EFI_USE_OPENBLT case 0xbc: /* Jump to OpenBLT if present */ jump_to_openblt(); break; #endif default: criticalError("Unexpected bench subsystem %d %d", subsystem, index); } } void onConfigurationChangeBenchTest() { // default values if configuration was not specified if (engineConfiguration->benchTestOnTime == 0) { engineConfiguration->benchTestOnTime = 4; } if (engineConfiguration->benchTestOffTime < 5) { engineConfiguration->benchTestOffTime = 500; } if (engineConfiguration->benchTestCount < 1) { engineConfiguration->benchTestCount = 3; } } void initBenchTest() { addConsoleAction("fuelpumpbench", fuelPumpBench); addConsoleActionF("fuelpumpbench2", fuelPumpBenchExt); addConsoleActionFFF(CMD_FUEL_BENCH, fuelInjBench); addConsoleActionFFFF("fuelbench2", fuelInjBenchExt); addConsoleActionFFF(CMD_SPARK_BENCH, sparkBench); addConsoleActionFFFF("sparkbench2", sparkBenchExt); addConsoleActionFFFF("tcusolbench", tcuSolenoidBench); addConsoleAction(CMD_AC_RELAY_BENCH, acRelayBench); addConsoleAction(CMD_FAN_BENCH, fanBench); addConsoleAction(CMD_FAN2_BENCH, fan2Bench); addConsoleActionF("fanbench2", fanBenchExt); addConsoleAction("mainrelaybench", mainRelayBench); #if EFI_WIDEBAND_FIRMWARE_UPDATE && EFI_CAN_SUPPORT addConsoleAction("update_wideband", []() { widebandUpdatePending = true; }); addConsoleActionI("set_wideband_index", [](int index) { setWidebandOffset(index); }); #endif // EFI_WIDEBAND_FIRMWARE_UPDATE && EFI_CAN_SUPPORT addConsoleAction(CMD_STARTER_BENCH, starterRelayBench); addConsoleAction(CMD_MIL_BENCH, milBench); addConsoleAction(CMD_HPFP_BENCH, hpfpValveBench); addConsoleActionFFFF("luabench2", luaOutBench2); instance.start(); onConfigurationChangeBenchTest(); } #endif /* EFI_UNIT_TEST */