/** * @file engine_controller.cpp * @brief Controllers package entry point code * * * * @date Feb 7, 2013 * @author Andrey Belomutskiy, (c) 2012-2016 * * 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 "sensor_chart.h" #include "main.h" #include "engine_configuration.h" #include "trigger_central.h" #include "engine_controller.h" #include "fsio_core.h" #include "fsio_impl.h" #include "idle_thread.h" #include "rpm_calculator.h" #include "signal_executor.h" #include "main_trigger_callback.h" #include "io_pins.h" #include "flash_main.h" #include "tunerstudio.h" #include "injector_central.h" #include "rfiutil.h" #include "engine_math.h" #include "wave_analyzer.h" #include "allsensors.h" #include "electronic_throttle.h" #include "map_averaging.h" #include "malfunction_central.h" #include "malfunction_indicator.h" #include "engine.h" #include "algo.h" #include "LocalVersionHolder.h" #include "alternatorController.h" #include "fuel_math.h" #include "settings.h" #if HAL_USE_ADC || defined(__DOXYGEN__) #include "AdcConfiguration.h" #endif #if EFI_PROD_CODE || defined(__DOXYGEN__) #include "pwm_generator.h" #include "adc_inputs.h" #include "efilib2.h" #include "PwmTester.h" #include "pwm_generator.h" #include "lcd_controller.h" #include "pin_repository.h" #include "tachometer.h" #endif extern bool hasFirmwareErrorFlag; persistent_config_container_s persistentState CCM_OPTIONAL; persistent_config_s *config = &persistentState.persistentConfiguration; /** * 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 virtual_timer_t periodicSlowTimer; static virtual_timer_t periodicFastTimer; static LoggingWithStorage logger("Engine Controller"); #if EFI_ENGINE_CONTROL || defined(__DOXYGEN__) static engine_configuration2_s ec2 CCM_OPTIONAL; engine_configuration2_s * engineConfiguration2 = &ec2; #endif #if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__) /** * todo: eliminate constructor parameter so that _engine could be moved to CCM_OPTIONAL * todo: this should probably become 'static', i.e. private, and propagated around explicitly? */ Engine _engine(&persistentState.persistentConfiguration); 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 extern OutputPin runningPin; static msg_t csThread(void) { chRegSetThreadName("status"); #if EFI_SHAFT_POSITION_INPUT || defined(__DOXYGEN__) while (true) { int rpm = getRpmE(engine); int is_cranking = isCrankingR(rpm); int is_running = rpm > 0 && !is_cranking; if (is_running) { // blinking while running runningPin.setValue(0); chThdSleepMilliseconds(50); runningPin.setValue(1); chThdSleepMilliseconds(50); } else { // constant on while cranking and off if engine is stopped runningPin.setValue(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(PASS_ENGINE_PARAMETER_F)), OBD_Intake_Air_Temperature_Circuit_Malfunction); setError(!isValidCoolantTemperature(getCoolantTemperature(PASS_ENGINE_PARAMETER_F)), OBD_Engine_Coolant_Temperature_Circuit_Malfunction); } #if EFI_PROD_CODE || defined(__DOXYGEN__) Overflow64Counter halTime; /** * 64-bit result would not overflow, but that's complex stuff for our 32-bit MCU */ //todo: macro to save method invocation efitimeus_t getTimeNowUs(void) { return getTimeNowNt() / (CORE_CLOCK / 1000000); } //todo: macro to save method invocation efitick_t getTimeNowNt(void) { return halTime.get(); } /** * number of SysClock ticks in one ms */ #define TICKS_IN_MS (CH_FREQUENCY / 1000) // todo: this overflows pretty fast! efitimems_t currentTimeMillis(void) { // todo: migrate to getTimeNowUs? or not? return chTimeNow() / TICKS_IN_MS; } // todo: this overflows pretty fast! int getTimeNowSeconds(void) { return currentTimeMillis() / 1000; } #endif /* EFI_PROD_CODE */ static void cylinderCleanupControl(Engine *engine) { #if EFI_ENGINE_CONTROL || defined(__DOXYGEN__) 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)); } #endif } static LocalVersionHolder versionForConfigurationListeners; static void periodicSlowCallback(Engine *engine); static void scheduleNextSlowInvocation(void) { // schedule next invocation int periodMs = boardConfiguration->generalPeriodicThreadPeriod; if (periodMs == 0) periodMs = 50; // this might happen while resetting configuration chVTSetAny(&periodicSlowTimer, MS2ST(periodMs), (vtfunc_t) &periodicSlowCallback, engine); } static void periodicFastCallback(DECLARE_ENGINE_PARAMETER_F) { engine->periodicFastCallback(); chVTSetAny(&periodicFastTimer, MS2ST(20), (vtfunc_t) &periodicFastCallback, engine); } static void resetAccel(void) { engine->engineLoadAccelEnrichment.reset(); engine->tpsAccelEnrichment.reset(); engine->wallFuel.reset(); } static void periodicSlowCallback(Engine *engine) { efiAssertVoid(getRemainingStack(chThdSelf()) > 64, "lowStckOnEv"); #if EFI_PROD_CODE /** * 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(); } #endif if (!engine->rpmCalculator.isRunning()) { #if (EFI_PROD_CODE && EFI_ENGINE_CONTROL && EFI_INTERNAL_FLASH) || defined(__DOXYGEN__) writeToFlashIfPending(); #endif resetAccel(); } if (versionForConfigurationListeners.isOld()) { /** * version change could happen for multiple reason and on different threads * in order to be sure which thread (and which stack) invokes the potentially heavy * listeners we invoke them from here. */ engine->configurationListeners.invokeJustArgCallbacks(); // todo: convert to a callback? updateAccelParameters(); engine->engineState.warmupAfrPid.reset(); } engine->watchdog(); engine->updateSlowSensors(); #if (EFI_PROD_CODE && EFI_FSIO) || defined(__DOXYGEN__) runFsio(); #endif updateErrorCodes(); cylinderCleanupControl(engine); scheduleNextSlowInvocation(); } void initPeriodicEvents(DECLARE_ENGINE_PARAMETER_F) { scheduleNextSlowInvocation(); periodicFastCallback(PASS_ENGINE_PARAMETER_F); } char * getPinNameByAdcChannel(adc_channel_e hwChannel, char *buffer) { #if HAL_USE_ADC || defined(__DOXYGEN__) strcpy((char*) buffer, portname(getAdcChannelPort(hwChannel))); itoa10(&buffer[2], getAdcChannelPin(hwChannel)); #else strcpy(buffer, "NONE"); #endif return (char*) buffer; } static char pinNameBuffer[16]; #if HAL_USE_ADC || defined(__DOXYGEN__) extern AdcDevice fastAdc; #endif static void printAnalogChannelInfoExt(const char *name, adc_channel_e hwChannel, float adcVoltage, float dividerCoeff) { #if HAL_USE_ADC || defined(__DOXYGEN__) if (hwChannel == EFI_ADC_NONE) { scheduleMsg(&logger, "ADC is not assigned for %s", name); return; } if (fastAdc.isHwUsed(hwChannel)) { scheduleMsg(&logger, "fast enabled=%s", boolToString(boardConfiguration->isFastAdcEnabled)); } float voltage = adcVoltage * dividerCoeff; scheduleMsg(&logger, "%s ADC%d %s %s adc=%f/input=%fv/divider=%f", name, hwChannel, getAdcMode(hwChannel), getPinNameByAdcChannel(hwChannel, pinNameBuffer), adcVoltage, voltage, dividerCoeff); #endif } static void printAnalogChannelInfo(const char *name, adc_channel_e hwChannel) { #if HAL_USE_ADC || defined(__DOXYGEN__) printAnalogChannelInfoExt(name, hwChannel, getVoltage("print", hwChannel), engineConfiguration->analogInputDividerCoefficient); #endif } static void printAnalogInfo(void) { scheduleMsg(&logger, "analogInputDividerCoefficient: %f", engineConfiguration->analogInputDividerCoefficient); printAnalogChannelInfo("hip9011", engineConfiguration->hipOutputChannel); printAnalogChannelInfo("fuel gauge", engineConfiguration->fuelLevelSensor); printAnalogChannelInfo("TPS", engineConfiguration->tpsAdcChannel); printAnalogChannelInfo("pPS", engineConfiguration->pedalPositionChannel); printAnalogChannelInfo("CLT", engineConfiguration->clt.adcChannel); if (engineConfiguration->hasIatSensor) { printAnalogChannelInfo("IAT", engineConfiguration->iat.adcChannel); } if (hasMafSensor()) { printAnalogChannelInfo("MAF", engineConfiguration->mafAdcChannel); } for (int i = 0; i < FSIO_ADC_COUNT ; i++) { adc_channel_e ch = engineConfiguration->fsioAdc[i]; if (ch != EFI_ADC_NONE) { printAnalogChannelInfo("fsio", ch); } } printAnalogChannelInfo("AFR", engineConfiguration->afr.hwChannel); if (engineConfiguration->hasMapSensor) { printAnalogChannelInfo("MAP", engineConfiguration->map.sensor.hwChannel); } if (hasBaroSensor()) { printAnalogChannelInfo("BARO", engineConfiguration->baroSensor.hwChannel); } if (engineConfiguration->externalKnockSenseAdc != EFI_ADC_NONE) { printAnalogChannelInfo("extKno", engineConfiguration->externalKnockSenseAdc); } printAnalogChannelInfo("A/C sw", engineConfiguration->acSwitchAdc); printAnalogChannelInfo("HIP9011", engineConfiguration->hipOutputChannel); printAnalogChannelInfoExt("Vbatt", engineConfiguration->vbattAdcChannel, getVoltage("vbatt", engineConfiguration->vbattAdcChannel), engineConfiguration->vbattDividerCoeff); } static THD_WORKING_AREA(csThreadStack, UTILITY_THREAD_STACK_SIZE); // declare thread stack #define isOutOfBounds(offset) ((offset<0) || (offset) >= (int) sizeof(engine_configuration_s)) static void getShort(int offset) { if (isOutOfBounds(offset)) return; uint16_t *ptr = (uint16_t *) (&((char *) engineConfiguration)[offset]); uint16_t value = *ptr; /** * this response is part of dev console API */ scheduleMsg(&logger, "short @%d is %d", offset, value); } static void setBit(const char *offsetStr, const char *bitStr, const char *valueStr) { int offset = atoi(offsetStr); if (absI(offset) == absI(ERROR_CODE)) { scheduleMsg(&logger, "invalid offset [%s]", offsetStr); return; } if (isOutOfBounds(offset)) { return; } int bit = atoi(bitStr); if (absI(bit) == absI(ERROR_CODE)) { scheduleMsg(&logger, "invalid bit [%s]", bitStr); return; } int value = atoi(valueStr); if (absI(value) == absI(ERROR_CODE)) { scheduleMsg(&logger, "invalid value [%s]", valueStr); return; } int *ptr = (int *) (&((char *) engineConfiguration)[offset]); *ptr ^= (-value ^ *ptr) & (1 << bit); /** * this response is part of dev console API */ scheduleMsg(&logger, "bit @%d/%d is %d", offset, bit, value); applyNewConfiguration(); } static void setShort(const int offset, const int value) { if (isOutOfBounds(offset)) return; uint16_t *ptr = (uint16_t *) (&((char *) engineConfiguration)[offset]); *ptr = (uint16_t) value; getShort(offset); applyNewConfiguration(); } static void getBit(int offset, int bit) { if (isOutOfBounds(offset)) return; int *ptr = (int *) (&((char *) engineConfiguration)[offset]); int value = (*ptr >> bit) & 1; /** * this response is part of dev console API */ scheduleMsg(&logger, "bit @%d/%d is %d", offset, bit, value); } static void getInt(int offset) { if (isOutOfBounds(offset)) return; int *ptr = (int *) (&((char *) engineConfiguration)[offset]); int value = *ptr; /** * this response is part of dev console API */ scheduleMsg(&logger, "int @%d is %d", offset, value); } static void setInt(const int offset, const int value) { if (isOutOfBounds(offset)) return; int *ptr = (int *) (&((char *) engineConfiguration)[offset]); *ptr = value; getInt(offset); applyNewConfiguration(); } static void getFloat(int offset) { if (isOutOfBounds(offset)) return; float *ptr = (float *) (&((char *) engineConfiguration)[offset]); float value = *ptr; /** * this response is part of dev console API */ scheduleMsg(&logger, "float @%d is %..100000f", 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; } if (isOutOfBounds(offset)) return; float value = atoff(valueStr); if (cisnan(value)) { scheduleMsg(&logger, "invalid value [%s]", valueStr); return; } float *ptr = (float *) (&((char *) engine->engineConfiguration)[offset]); *ptr = value; getFloat(offset); } #if EFI_ENABLE_MOCK_ADC || EFI_SIMULATOR static void setMockVoltage(int hwChannel, float voltage) { engine->engineState.mockAdcState.setMockVoltage(hwChannel, voltage); } static void setCltVoltage(float voltage) { setMockVoltage(engineConfiguration->clt.adcChannel, voltage); } static void setIatVoltage(float voltage) { setMockVoltage(engineConfiguration->iat.adcChannel, voltage); } static void setMafVoltage(float voltage) { setMockVoltage(engineConfiguration->mafAdcChannel, voltage); } static void setAfrVoltage(float voltage) { setMockVoltage(engineConfiguration->afr.hwChannel, voltage); } static void setTpsVoltage(float voltage) { setMockVoltage(engineConfiguration->tpsAdcChannel, voltage); } static void setMapVoltage(float voltage) { setMockVoltage(engineConfiguration->map.sensor.hwChannel, voltage); } static void setVBattVoltage(float voltage) { setMockVoltage(engineConfiguration->vbattAdcChannel, voltage); } static void initMockVoltage(void) { addConsoleActionF("set_mock_clt_voltage", setCltVoltage); addConsoleActionF("set_mock_iat_voltage", setIatVoltage); addConsoleActionF("set_mock_maf_voltage", setMafVoltage); addConsoleActionF("set_mock_afr_voltage", setAfrVoltage); addConsoleActionF("set_mock_tps_voltage", setTpsVoltage); addConsoleActionF("set_mock_map_voltage", setMapVoltage); addConsoleActionF("set_mock_vbatt_voltage", setVBattVoltage); } #endif /* EFI_ENABLE_MOCK_ADC */ static void initConfigActions(void) { addConsoleActionSS("set_float", (VoidCharPtrCharPtr) setFloat); addConsoleActionII("set_int", (VoidIntInt) setInt); addConsoleActionII("set_short", (VoidIntInt) setShort); addConsoleActionSSS("set_bit", setBit); addConsoleActionI("get_float", getFloat); addConsoleActionI("get_int", getInt); addConsoleActionI("get_short", getShort); addConsoleActionII("get_bit", getBit); } // todo: move this logic somewhere else? static void getKnockInfo(void) { adc_channel_e hwChannel = engineConfiguration->externalKnockSenseAdc; scheduleMsg(&logger, "externalKnockSenseAdc on ADC", getPinNameByAdcChannel(hwChannel, pinNameBuffer)); engine->printKnockState(); } // this method is used by real firmware and simulator void commonInitEngineController(Logging *sharedLogger DECLARE_ENGINE_PARAMETER_S) { initConfigActions(); initMockVoltage(); #if EFI_PROD_CODE || EFI_SIMULATOR initSignalExecutor(); #endif #if EFI_SENSOR_CHART || defined(__DOXYGEN__) initSensorChart(); #endif /* EFI_SENSOR_CHART */ #if EFI_PROD_CODE || EFI_SIMULATOR // todo: this is a mess, remove code duplication with simulator initSettings(engineConfiguration); #endif #if EFI_TUNER_STUDIO || defined(__DOXYGEN__) if (engineConfiguration->isTunerStudioEnabled) { startTunerStudioConnectivity(); } #endif if (hasFirmwareError()) { return; } initSensors(sharedLogger PASS_ENGINE_PARAMETER_F); #if EFI_FSIO || defined(__DOXYGEN__) initFsioImpl(sharedLogger PASS_ENGINE_PARAMETER); #endif initAccelEnrichment(sharedLogger); } void initEngineContoller(Logging *sharedLogger DECLARE_ENGINE_PARAMETER_S) { addConsoleAction("analoginfo", printAnalogInfo); commonInitEngineController(sharedLogger); #if EFI_PROD_CODE || defined(__DOXYGEN__) initPwmGenerator(); #endif initAlgo(sharedLogger, engineConfiguration); #if EFI_WAVE_ANALYZER || defined(__DOXYGEN__) if (engineConfiguration->isWaveAnalyzerEnabled) { initWaveAnalyzer(sharedLogger); } #endif /* EFI_WAVE_ANALYZER */ #if EFI_SHAFT_POSITION_INPUT || defined(__DOXYGEN__) /** * 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(sharedLogger, engine); #endif /* EFI_SHAFT_POSITION_INPUT */ // multiple issues with this initMapAdjusterThread(); initPeriodicEvents(PASS_ENGINE_PARAMETER_F); if (hasFirmwareError()) { return; } chThdCreateStatic(csThreadStack, sizeof(csThreadStack), LOWPRIO, (tfunc_t) csThread, NULL); #if (EFI_PROD_CODE && EFI_ENGINE_CONTROL) || defined(__DOXYGEN__) initInjectorCentral(sharedLogger); /** * This has to go after 'initInjectorCentral' and 'initInjectorCentral' in order to * properly detect un-assigned output pins */ prepareShapes(PASS_ENGINE_PARAMETER_F); #endif #if EFI_PWM_TESTER || defined(__DOXYGEN__) initPwmTester(); #endif initMalfunctionCentral(); #if EFI_ALTERNATOR_CONTROL || defined(__DOXYGEN__) initAlternatorCtrl(sharedLogger); #endif #if EFI_ELECTRONIC_THROTTLE_BODY || defined(__DOXYGEN__) initElectronicThrottle(); #endif /* EFI_ELECTRONIC_THROTTLE_BODY */ #if EFI_MALFUNCTION_INDICATOR || defined(__DOXYGEN__) if (engineConfiguration->isMilEnabled) { initMalfunctionIndicator(); } #endif /* EFI_MALFUNCTION_INDICATOR */ #if EFI_MAP_AVERAGING || defined(__DOXYGEN__) if (engineConfiguration->isMapAveragingEnabled) { initMapAveraging(sharedLogger, engine); } #endif /* EFI_MAP_AVERAGING */ #if EFI_ENGINE_CONTROL || defined(__DOXYGEN__) if (boardConfiguration->isEngineControlEnabled) { /** * This method initialized the main listener which actually runs injectors & ignition */ initMainEventListener(sharedLogger, engine); } #endif /* EFI_ENGINE_CONTROL */ #if EFI_IDLE_CONTROL || defined(__DOXYGEN__) if (engineConfiguration->isIdleThreadEnabled) { startIdleThread(sharedLogger, engine); } #endif if (engineConfiguration->externalKnockSenseAdc != EFI_ADC_NONE) { addConsoleAction("knockinfo", getKnockInfo); } #if EFI_PROD_CODE addConsoleAction("reset_accel", resetAccel); #endif #if EFI_HD44780_LCD || defined(__DOXYGEN__) initLcdController(); #endif #if EFI_PROD_CODE || defined(__DOXYGEN__) initTachometer(); #endif }