/** * @file engine_controller.cpp * @brief Controllers package entry point code * * * * @date Feb 7, 2013 * @author Andrey Belomutskiy, (c) 2012-2014 * * 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 "main.h" #include "trigger_central.h" #include "engine_controller.h" #include "idle_thread.h" #include "rpm_calculator.h" #include "signal_executor.h" #include "main_trigger_callback.h" #include "map_multiplier_thread.h" #include "io_pins.h" #include "flash_main.h" #include "tunerstudio.h" #include "injector_central.h" #include "ignition_central.h" #include "rfiutil.h" #include "engine_configuration.h" #include "engine_math.h" #include "wave_analyzer.h" #include "allsensors.h" #include "analog_chart.h" #include "electronic_throttle.h" #include "malfunction_indicator.h" #include "map_averaging.h" #include "malfunction_central.h" #include "pin_repository.h" #include "pwm_generator.h" #include "adc_inputs.h" #include "algo.h" #include "efilib2.h" #include "ec2.h" #include "PwmTester.h" #include "engine.h" #include "logic_expression.h" #include "le_functions.h" #include "pin_repository.h" #include "pwm_generator.h" #define LE_ELEMENT_POOL_SIZE 256 static LECalculator calc; static LEElement mainPool[LE_ELEMENT_POOL_SIZE]; static LEElementPool lePool(mainPool, LE_ELEMENT_POOL_SIZE); static LEElement * acRelayLogic; static LEElement * fuelPumpLogic; static LEElement * radiatorFanLogic; static LEElement * alternatorLogic; extern OutputPin outputs[IO_PIN_COUNT]; extern pin_output_mode_e *pinDefaultState[IO_PIN_COUNT]; extern bool hasFirmwareErrorFlag; static LEElement * fsioLogics[LE_COMMAND_COUNT]; static SimplePwm fsioPwm[LE_COMMAND_COUNT]; persistent_config_container_s persistentState CCM_OPTIONAL; /** * todo: it really looks like these fields should become 'static', i.e. private * the whole 'extern ...' pattern is less then perfect, I guess the 'God object' Engine * would be a smaller evil. Whatever is needed should be passed into methods/modules/files as an explicit parameter. */ engine_configuration_s *engineConfiguration = &persistentState.persistentConfiguration.engineConfiguration; board_configuration_s *boardConfiguration = &persistentState.persistentConfiguration.engineConfiguration.bc; /** * CH_FREQUENCY is the number of system ticks in a second */ static VirtualTimer everyMsTimer; static Logging logger; static engine_configuration2_s ec2 CCM_OPTIONAL; engine_configuration2_s * engineConfiguration2 = &ec2; #if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__) /** * todo: this should probably become 'static', i.e. private, and propagated around explicitly? */ Engine _engine; Engine * engine = &_engine; #endif /** * I am not sure if this needs to be configurable. * * Also technically the whole feature might be implemented as cranking fuel coefficient curve by TPS. */ #define CLEANUP_MODE_TPS 95 static msg_t csThread(void) { chRegSetThreadName("status"); #if EFI_SHAFT_POSITION_INPUT while (true) { int rpm = getRpm(); int is_cranking = isCrankingR(rpm); int is_running = rpm > 0 && !is_cranking; if (is_running) { // blinking while running setOutputPinValue(LED_RUNNING, 0); chThdSleepMilliseconds(50); setOutputPinValue(LED_RUNNING, 1); chThdSleepMilliseconds(50); } else { // constant on while cranking and off if engine is stopped setOutputPinValue(LED_RUNNING, is_cranking); chThdSleepMilliseconds(100); } } #endif /* EFI_SHAFT_POSITION_INPUT */ return -1; } static void updateErrorCodes(void) { /** * technically we can set error codes right inside the getMethods, but I a bit on a fence about it */ setError(isValidIntakeAirTemperature(getIntakeAirTemperature(engine)), OBD_Intake_Air_Temperature_Circuit_Malfunction); setError(isValidCoolantTemperature(getCoolantTemperature(engine)), OBD_Engine_Coolant_Temperature_Circuit_Malfunction); } static void fanRelayControl(void) { if (boardConfiguration->fanPin == GPIO_UNASSIGNED) return; int isCurrentlyOn = getOutputPinValue(FAN_RELAY); int newValue; if (isCurrentlyOn) { // if the fan is already on, we keep it on till the 'fanOff' temperature newValue = getCoolantTemperature(engine) > engineConfiguration->fanOffTemperature; } else { newValue = getCoolantTemperature(engine) > engineConfiguration->fanOnTemperature; } if (isCurrentlyOn != newValue) { if (isRunningBenchTest()) return; // let's not mess with bench testing scheduleMsg(&logger, "FAN relay: %s", newValue ? "ON" : "OFF"); setOutputPinValue(FAN_RELAY, newValue); } } Overflow64Counter halTime; uint64_t getTimeNowUs(void) { return getTimeNowNt() / (CORE_CLOCK / 1000000); } uint64_t getTimeNowNt(void) { return halTime.get(); } efitimems_t currentTimeMillis(void) { // todo: migrate to getTimeNowUs? or not? return chTimeNow() / TICKS_IN_MS; } int getTimeNowSeconds(void) { return chTimeNow() / CH_FREQUENCY; } static void cylinderCleanupControl(Engine *engine) { bool newValue; if (engineConfiguration->isCylinderCleanupEnabled) { newValue = isCrankingE(engine) && getTPS(PASS_ENGINE_PARAMETER_F) > CLEANUP_MODE_TPS; } else { newValue = false; } if (newValue != engine->isCylinderCleanupMode) { engine->isCylinderCleanupMode = newValue; scheduleMsg(&logger, "isCylinderCleanupMode %s", boolToString(newValue)); } } static void handleGpio(Engine *engine, int index) { if (boardConfiguration->gpioPins[index] == GPIO_UNASSIGNED) return; bool_t isPwmMode = boardConfiguration->fsioFrequency[index] != 0; io_pin_e pin = (io_pin_e) ((int) GPIO_0 + index); float fvalue = calc.getValue2(fsioLogics[index], engine); engine->engineConfiguration2->fsioLastValue[index] = fvalue; if (isPwmMode) { fsioPwm[index].setSimplePwmDutyCycle(fvalue); } else { int value = (int) fvalue; if (value != getOutputPinValue(pin)) { // scheduleMsg(&logger, "setting %s %s", getIo_pin_e(pin), boolToString(value)); setOutputPinValue(pin, value); } } } static void setPinState(io_pin_e ioPin, LEElement *element, Engine *engine) { if (element == NULL) { warning(OBD_PCM_Processor_Fault, "invalid expression for %s", getIo_pin_e(ioPin)); } else { int value = calc.getValue2(element, engine); if (value != getOutputPinValue(ioPin)) { if (isRunningBenchTest()) return; // let's not mess with bench testing scheduleMsg(&logger, "setting %s %s", getIo_pin_e(ioPin), boolToString(value)); setOutputPinValue(ioPin, value); } } } static void onEvenyGeneralMilliseconds(Engine *engine) { /** * We need to push current value into the 64 bit counter often enough so that we do not miss an overflow */ bool alreadyLocked = lockAnyContext(); updateAndSet(&halTime.state, hal_lld_get_counter_value()); if (!alreadyLocked) { unlockAnyContext(); } if (!engine->rpmCalculator.isRunning()) writeToFlashIfPending(); engine->watchdog(); engine->updateSlowSensors(); for (int i = 0; i < LE_COMMAND_COUNT; i++) { handleGpio(engine, i); } #if EFI_FUEL_PUMP if (boardConfiguration->fuelPumpPin != GPIO_UNASSIGNED && engineConfiguration->isFuelPumpEnabled) { setPinState(FUEL_PUMP_RELAY, fuelPumpLogic, engine); } #endif if (boardConfiguration->acRelayPin != GPIO_UNASSIGNED) { setPinState(AC_RELAY, acRelayLogic, engine); } if (boardConfiguration->alternatorControlPin != GPIO_UNASSIGNED) { setPinState(ALTERNATOR_SWITCH, alternatorLogic, engine); } updateErrorCodes(); // todo: migrate this to flex logic fanRelayControl(); cylinderCleanupControl(engine); setOutputPinValue(O2_HEATER, engine->rpmCalculator.isRunning()); // schedule next invocation chVTSetAny(&everyMsTimer, boardConfiguration->generalPeriodicThreadPeriod * TICKS_IN_MS, (vtfunc_t) &onEvenyGeneralMilliseconds, engine); } static void initPeriodicEvents(Engine *engine) { // schedule first invocation chVTSetAny(&everyMsTimer, boardConfiguration->generalPeriodicThreadPeriod * TICKS_IN_MS, (vtfunc_t) &onEvenyGeneralMilliseconds, engine); } char * getPinNameByAdcChannel(adc_channel_e hwChannel, char *buffer) { strcpy((char*) buffer, portname(getAdcChannelPort(hwChannel))); itoa10(&buffer[2], getAdcChannelPin(hwChannel)); return (char*) buffer; } static char pinNameBuffer[16]; static void printAnalogChannelInfoExt(const char *name, adc_channel_e hwChannel, float adcVoltage) { float voltage = adcVoltage * engineConfiguration->analogInputDividerCoefficient; scheduleMsg(&logger, "%s ADC%d %s %s rawValue=%f/divided=%fv", name, hwChannel, getAdcMode(hwChannel), getPinNameByAdcChannel(hwChannel, pinNameBuffer), adcVoltage, voltage); } static void printAnalogChannelInfo(const char *name, adc_channel_e hwChannel) { if (hwChannel != EFI_ADC_NONE) { printAnalogChannelInfoExt(name, hwChannel, getVoltage(hwChannel)); } } static void printAnalogInfo(void) { printAnalogChannelInfo("TPS", engineConfiguration->tpsAdcChannel); printAnalogChannelInfo("CLT", engineConfiguration->cltAdcChannel); if (engineConfiguration->hasIatSensor) { printAnalogChannelInfo("IAT", engineConfiguration->iatAdcChannel); } printAnalogChannelInfo("MAF", engineConfiguration->mafAdcChannel); printAnalogChannelInfo("AFR", engineConfiguration->afrSensor.afrAdcChannel); printAnalogChannelInfo("MAP", engineConfiguration->map.sensor.hwChannel); if (engineConfiguration->hasBaroSensor) { printAnalogChannelInfo("BARO", engineConfiguration->baroSensor.hwChannel); } printAnalogChannelInfo("A/C sw", engineConfiguration->acSwitchAdc); printAnalogChannelInfoExt("Vbatt", engineConfiguration->vbattAdcChannel, getVBatt(engineConfiguration)); } static THD_WORKING_AREA(csThreadStack, UTILITY_THREAD_STACK_SIZE); // declare thread stack static void setFsioFrequency(int index, int frequency) { index--; if (index < 0 || index > LE_COMMAND_COUNT) { scheduleMsg(&logger, "invalid index %d", index); return; } boardConfiguration->fsioFrequency[index] = frequency; scheduleMsg(&logger, "Setting FSIO frequency %d on #%d", frequency, index + 1); } static void setFsioPin(const char *indexStr, const char *pinName) { int index = atoi(indexStr) - 1; if (index < 0 || index > LE_COMMAND_COUNT) { scheduleMsg(&logger, "invalid index %d", index); return; } brain_pin_e pin = parseBrainPin(pinName); // todo: extract method - code duplication with other 'set_xxx_pin' methods? if (pin == GPIO_INVALID) { scheduleMsg(&logger, "invalid pin name [%s]", pinName); return; } boardConfiguration->gpioPins[index] = pin; scheduleMsg(&logger, "FSIO pin #%d [%s]", (index + 1), hwPortname(pin)); } static void setUserOutput(const char *indexStr, const char *quotedLine, Engine *engine) { int index = atoi(indexStr) - 1; if (index < 0 || index > LE_COMMAND_COUNT) { scheduleMsg(&logger, "invalid index %d", index); return; } char * l = unquote((char*) quotedLine); if (strlen(l) > LE_COMMAND_LENGTH - 1) { scheduleMsg(&logger, "Too long %d", strlen(l)); return; } scheduleMsg(&logger, "setting user out #%d to [%s]", index + 1, l); strcpy(engine->engineConfiguration->bc.le_formulas[index], l); } static void setInt(const char *offsetStr, const char *valueStr) { } static void getInt(int offset) { int *ptr = (int *) (&((char *) engine->engineConfiguration)[offset]); int value = *ptr; scheduleMsg(&logger, "int @%d is %d", offset, value); } static void getFloat(int offset) { float *ptr = (float *) (&((char *) engine->engineConfiguration)[offset]); float value = *ptr; scheduleMsg(&logger, "float @%d is %f", offset, value); } static void setFloat(const char *offsetStr, const char *valueStr) { int offset = atoi(offsetStr); if (absI(offset) == absI(ERROR_CODE)) { scheduleMsg(&logger, "invalid offset [%s]", offsetStr); return; } float value = atoff(valueStr); if (cisnan(value)) { scheduleMsg(&logger, "invalid value [%s]", valueStr); return; } float *ptr = (float *) (&((char *) engine->engineConfiguration)[offset]); *ptr = value; scheduleMsg(&logger, "setting float @%d to %f", offset, value); } static pin_output_mode_e d = OM_DEFAULT; void initEngineContoller(Engine *engine) { if (hasFirmwareError()) { return; } initLogging(&logger, "Engine Controller"); initSensors(engine); initPwmGenerator(); #if EFI_ANALOG_CHART initAnalogChart(); #endif /* EFI_ANALOG_CHART */ initAlgo(engineConfiguration); #if EFI_WAVE_ANALYZER if (engineConfiguration->isWaveAnalyzerEnabled) { initWaveAnalyzer(); } #endif /* EFI_WAVE_ANALYZER */ #if EFI_SHAFT_POSITION_INPUT /** * there is an implicit dependency on the fact that 'tachometer' listener is the 1st listener - this case * other listeners can access current RPM value */ initRpmCalculator(engine); #endif /* EFI_SHAFT_POSITION_INPUT */ #if EFI_TUNER_STUDIO if (engineConfiguration->isTunerStudioEnabled) { startTunerStudioConnectivity(); } #endif // multiple issues with this initMapAdjusterThread(); initPeriodicEvents(engine); chThdCreateStatic(csThreadStack, sizeof(csThreadStack), LOWPRIO, (tfunc_t) csThread, NULL); initInjectorCentral(engine); initPwmTester(); initIgnitionCentral(); /** * This has to go after 'initInjectorCentral' and 'initInjectorCentral' in order to * properly detect un-assigned output pins */ prepareShapes(engine); initMalfunctionCentral(); #if EFI_ELECTRONIC_THROTTLE_BODY initElectronicThrottle(); #endif /* EFI_ELECTRONIC_THROTTLE_BODY */ #if EFI_MALFUNCTION_INDICATOR if (engineConfiguration->isMilEnabled) { initMalfunctionIndicator(); } #endif /* EFI_MALFUNCTION_INDICATOR */ #if EFI_MAP_AVERAGING if (engineConfiguration->isMapAveragingEnabled) { initMapAveraging(); } #endif /* EFI_MAP_AVERAGING */ #if EFI_ENGINE_CONTROL if (boardConfiguration->isEngineControlEnabled) { /** * This method initialized the main listener which actually runs injectors & ignition */ initMainEventListener(engine, engineConfiguration2); } #endif /* EFI_ENGINE_CONTROL */ #if EFI_IDLE_CONTROL if (engineConfiguration->isIdleThreadEnabled) { startIdleThread(engine); } #else scheduleMsg(&logger, "no idle control"); #endif #if EFI_FUEL_PUMP fuelPumpLogic = lePool.parseExpression(FUEL_PUMP_LOGIC); #endif acRelayLogic = lePool.parseExpression(AC_RELAY_LOGIC); alternatorLogic = lePool.parseExpression(ALTERNATOR_LOGIC); addConsoleAction("analoginfo", printAnalogInfo); for (int i = 0; i < LE_COMMAND_COUNT; i++) { brain_pin_e brainPin = boardConfiguration->gpioPins[i]; if (brainPin != GPIO_UNASSIGNED) { const char *formula = boardConfiguration->le_formulas[i]; LEElement *logic = lePool.parseExpression(formula); if (logic == NULL) { warning(OBD_PCM_Processor_Fault, "parsing [%s]", formula); } fsioLogics[i] = logic; //mySetPadMode2("user-defined", boardConfiguration->gpioPins[i], PAL_STM32_MODE_OUTPUT); io_pin_e pin = (io_pin_e) ((int) GPIO_0 + i); int frequency = boardConfiguration->fsioFrequency[i]; if (frequency == 0) { outputPinRegisterExt2(getPinName(pin), pin, boardConfiguration->gpioPins[i], &d); } else { startSimplePwmExt(&fsioPwm[i], "FSIO", brainPin, pin, frequency, 0.5f, applyPinState); } } } addConsoleActionSSP("set_fsio", (VoidCharPtrCharPtrVoidPtr) setUserOutput, engine); addConsoleActionSS("set_fsio_pin", (VoidCharPtrCharPtr) setFsioPin); addConsoleActionII("set_fsio_frequency", (VoidIntInt) setFsioFrequency); addConsoleActionSS("set_float", (VoidCharPtrCharPtr) setFloat); addConsoleActionSS("set_int", (VoidCharPtrCharPtr) setInt); addConsoleActionI("get_float", getFloat); addConsoleActionI("get_int", getInt); initEval(engine); }