rusefi/firmware/controllers/bench_test.cpp

685 lines
18 KiB
C++

/**
* @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 <http://www.gnu.org/licenses/>.
*/
#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<UTILITY_THREAD_STACK_SIZE> {
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 tunerstudio.template.ini 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 EFI_PROD_CODE && (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_PROD_CODE && 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 */