rusefi/firmware/controllers/engine_controller.cpp

537 lines
16 KiB
C++

/**
* @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 <http://www.gnu.org/licenses/>.
*/
#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] CCM_OPTIONAL;
static SimplePwm fsioPwm[LE_COMMAND_COUNT] CCM_OPTIONAL;
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);
}