2015-07-10 06:01:56 -07:00
|
|
|
/**
|
|
|
|
* @file rpm_calculator.cpp
|
|
|
|
* @brief RPM calculator
|
|
|
|
*
|
|
|
|
* Here we listen to position sensor events in order to figure our if engine is currently running or not.
|
|
|
|
* Actual getRpm() is calculated once per crankshaft revolution, based on the amount of time passed
|
|
|
|
* since the start of previous shaft revolution.
|
|
|
|
*
|
|
|
|
* @date Jan 1, 2013
|
|
|
|
* @author Andrey Belomutskiy, (c) 2012-2015
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "main.h"
|
|
|
|
#include "rpm_calculator.h"
|
|
|
|
|
|
|
|
#if EFI_SHAFT_POSITION_INPUT || defined(__DOXYGEN__)
|
|
|
|
|
|
|
|
#include "trigger_central.h"
|
|
|
|
#include "engine_configuration.h"
|
|
|
|
#include "engine_math.h"
|
|
|
|
|
|
|
|
#if EFI_PROD_CODE
|
|
|
|
#include "rfiutil.h"
|
|
|
|
#include "engine.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if ! EFI_UNIT_TEST
|
|
|
|
#include <sensor_chart.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "efilib2.h"
|
|
|
|
|
2015-07-15 18:01:45 -07:00
|
|
|
#if EFI_ENGINE_SNIFFER
|
|
|
|
#include "engine_sniffer.h"
|
2015-07-10 06:01:56 -07:00
|
|
|
extern WaveChart waveChart;
|
2015-07-15 18:01:45 -07:00
|
|
|
#endif /* EFI_ENGINE_SNIFFER */
|
2015-07-10 06:01:56 -07:00
|
|
|
|
|
|
|
EXTERN_ENGINE
|
|
|
|
;
|
|
|
|
|
|
|
|
efitime_t notRunnintNow;
|
|
|
|
efitime_t notRunningPrev;
|
|
|
|
|
|
|
|
static Logging * logger;
|
|
|
|
|
|
|
|
RpmCalculator::RpmCalculator() {
|
|
|
|
#if !EFI_PROD_CODE
|
|
|
|
mockRpm = MOCK_UNDEFINED;
|
|
|
|
#endif
|
|
|
|
rpmValue = 0;
|
|
|
|
setRpmValue(0);
|
|
|
|
|
|
|
|
// we need this initial to have not_running at first invocation
|
|
|
|
lastRpmEventTimeNt = (efitime_t) -10 * US2NT(US_PER_SECOND_LL);
|
|
|
|
revolutionCounterSinceStart = 0;
|
|
|
|
revolutionCounterSinceBoot = 0;
|
|
|
|
|
|
|
|
lastRpmEventTimeNt = 0;
|
|
|
|
oneDegreeUs = NAN;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return true if there was a full shaft revolution within the last second
|
|
|
|
*/
|
|
|
|
bool RpmCalculator::isRunning(DECLARE_ENGINE_PARAMETER_F) {
|
|
|
|
efitick_t nowNt = getTimeNowNt();
|
|
|
|
if (engine->stopEngineRequestTimeNt != 0) {
|
|
|
|
if (nowNt
|
|
|
|
- engine->stopEngineRequestTimeNt< 3 * US2NT(US_PER_SECOND_LL)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* note that the result of this subtraction could be negative, that would happen if
|
|
|
|
* we have a trigger event between the time we've invoked 'getTimeNow' and here
|
|
|
|
*/
|
|
|
|
bool_t result = nowNt - lastRpmEventTimeNt < US2NT(US_PER_SECOND_LL);
|
|
|
|
if (!result) {
|
|
|
|
notRunnintNow = nowNt;
|
|
|
|
notRunningPrev = lastRpmEventTimeNt;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void RpmCalculator::setRpmValue(int value) {
|
|
|
|
previousRpmValue = rpmValue;
|
|
|
|
rpmValue = value;
|
|
|
|
if (rpmValue <= 0) {
|
|
|
|
oneDegreeUs = NAN;
|
|
|
|
} else {
|
|
|
|
oneDegreeUs = getOneDegreeTimeUs(rpmValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RpmCalculator::onNewEngineCycle() {
|
|
|
|
revolutionCounterSinceBoot++;
|
|
|
|
revolutionCounterSinceStart++;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t RpmCalculator::getRevolutionCounter(void) {
|
|
|
|
return revolutionCounterSinceBoot;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t RpmCalculator::getRevolutionCounterSinceStart(void) {
|
|
|
|
return revolutionCounterSinceStart;
|
|
|
|
}
|
|
|
|
|
|
|
|
float RpmCalculator::getRpmAcceleration() {
|
|
|
|
return 1.0 * previousRpmValue / rpmValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* WARNING: this is a heavy method because 'getRpm()' is relatively heavy
|
|
|
|
*
|
|
|
|
* @return -1 in case of isNoisySignal(), current RPM otherwise
|
|
|
|
*/
|
|
|
|
// todo: migrate to float return result or add a float version? this would have with calculations
|
|
|
|
// todo: add a version which does not check time & saves time? need to profile
|
|
|
|
int RpmCalculator::rpm(DECLARE_ENGINE_PARAMETER_F) {
|
|
|
|
#if !EFI_PROD_CODE
|
|
|
|
if (mockRpm != MOCK_UNDEFINED)
|
|
|
|
return mockRpm;
|
|
|
|
#endif
|
|
|
|
if (!isRunning(PASS_ENGINE_PARAMETER_F)) {
|
|
|
|
revolutionCounterSinceStart = 0;
|
|
|
|
if (rpmValue != 0) {
|
|
|
|
rpmValue = 0;
|
|
|
|
scheduleMsg(logger,
|
|
|
|
"templog rpm=0 since not running [%x][%x] [%x][%x]",
|
|
|
|
(int) (notRunnintNow >> 32), (int) notRunnintNow,
|
|
|
|
(int) (notRunningPrev >> 32), (int) notRunningPrev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return rpmValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__)
|
|
|
|
bool isCrankingE(Engine *engine) {
|
|
|
|
int rpm = getRpmE(engine);
|
|
|
|
return isCrankingR(rpm);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* WARNING: this is a heavy method because 'getRpm()' is relatively heavy
|
|
|
|
*/
|
|
|
|
bool isCranking(void) {
|
|
|
|
return isCrankingE(engine);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Shaft position callback used by RPM calculation logic.
|
|
|
|
*
|
|
|
|
* This callback should always be the first of trigger callbacks because other callbacks depend of values
|
|
|
|
* updated here.
|
|
|
|
* This callback is invoked on interrupt thread.
|
|
|
|
*/
|
|
|
|
void rpmShaftPositionCallback(trigger_event_e ckpSignalType,
|
|
|
|
uint32_t index DECLARE_ENGINE_PARAMETER_S) {
|
|
|
|
RpmCalculator *rpmState = &engine->rpmCalculator;
|
|
|
|
efitick_t nowNt = getTimeNowNt();
|
|
|
|
engine->m.beforeRpmCb = GET_TIMESTAMP();
|
|
|
|
#if EFI_PROD_CODE
|
|
|
|
efiAssertVoid(getRemainingStack(chThdSelf()) > 256, "lowstck#2z");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (index != 0) {
|
|
|
|
#if EFI_ANALOG_CHART || defined(__DOXYGEN__)
|
|
|
|
if (boardConfiguration->sensorChartMode == SC_TRIGGER)
|
|
|
|
scAddData(getCrankshaftAngleNt(nowNt PASS_ENGINE_PARAMETER),
|
|
|
|
1000 * ckpSignalType + index);
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool hadRpmRecently = rpmState->isRunning(PASS_ENGINE_PARAMETER_F);
|
|
|
|
|
|
|
|
if (hadRpmRecently) {
|
|
|
|
efitime_t diffNt = nowNt - rpmState->lastRpmEventTimeNt;
|
|
|
|
/**
|
|
|
|
* Four stroke cycle is two crankshaft revolutions
|
|
|
|
*
|
|
|
|
* We always do '* 2' because the event signal is already adjusted to 'per engine cycle'
|
|
|
|
* and each revolution of crankshaft consists of two engine cycles revolutions
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
if (diffNt == 0) {
|
|
|
|
rpmState->setRpmValue(NOISY_RPM);
|
|
|
|
} else {
|
|
|
|
int mult = engineConfiguration->engineCycle / 360;
|
|
|
|
int rpm = (int) (60 * US2NT(US_PER_SECOND_LL) * mult / diffNt);
|
|
|
|
rpmState->setRpmValue(rpm > UNREALISTIC_RPM ? NOISY_RPM : rpm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rpmState->onNewEngineCycle();
|
|
|
|
rpmState->lastRpmEventTimeNt = nowNt;
|
|
|
|
#if EFI_ANALOG_CHART || defined(__DOXYGEN__)
|
|
|
|
if (boardConfiguration->sensorChartMode == SC_TRIGGER)
|
|
|
|
scAddData(getCrankshaftAngleNt(nowNt PASS_ENGINE_PARAMETER), index);
|
|
|
|
#endif
|
|
|
|
engine->m.rpmCbTime = GET_TIMESTAMP() - engine->m.beforeRpmCb;
|
|
|
|
}
|
|
|
|
|
|
|
|
static scheduling_s tdcScheduler[2];
|
|
|
|
|
|
|
|
static char rpmBuffer[10];
|
|
|
|
|
|
|
|
#if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__)
|
|
|
|
/**
|
|
|
|
* This callback has nothing to do with actual engine control, it just sends a Top Dead Center mark to the dev console
|
|
|
|
* digital sniffer.
|
|
|
|
*/
|
|
|
|
static void onTdcCallback(void) {
|
|
|
|
itoa10(rpmBuffer, getRpm());
|
|
|
|
addWaveChartEvent(TOP_DEAD_CENTER_MESSAGE, (char* ) rpmBuffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This trigger callback schedules the actual physical TDC callback in relation to trigger synchronization point.
|
|
|
|
*/
|
|
|
|
static void tdcMarkCallback(trigger_event_e ckpSignalType,
|
|
|
|
uint32_t index0 DECLARE_ENGINE_PARAMETER_S) {
|
|
|
|
(void) ckpSignalType;
|
|
|
|
bool isTriggerSynchronizationPoint = index0 == 0;
|
|
|
|
if (isTriggerSynchronizationPoint
|
|
|
|
&& engineConfiguration->isEngineChartEnabled) {
|
|
|
|
int revIndex2 = engine->rpmCalculator.getRevolutionCounter() % 2;
|
|
|
|
int rpm = getRpm();
|
|
|
|
// todo: use event-based scheduling, not just time-based scheduling
|
|
|
|
if (isValidRpm(rpm)) {
|
|
|
|
scheduleByAngle(rpm, &tdcScheduler[revIndex2], tdcPosition(),
|
|
|
|
(schfunc_t) onTdcCallback, NULL, &engine->rpmCalculator);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if EFI_PROD_CODE || EFI_SIMULATOR
|
|
|
|
int getRevolutionCounter() {
|
|
|
|
return engine->rpmCalculator.getRevolutionCounter();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Current crankshaft angle, 0 to 720 for four-stroke
|
|
|
|
*/
|
|
|
|
float getCrankshaftAngleNt(efitime_t timeNt DECLARE_ENGINE_PARAMETER_S) {
|
|
|
|
efitime_t timeSinceZeroAngleNt = timeNt
|
|
|
|
- engine->rpmCalculator.lastRpmEventTimeNt;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* even if we use 'getOneDegreeTimeUs' macros here, it looks like the
|
|
|
|
* compiler is not smart enough to figure out that "A / ( B / C)" could be optimized into
|
|
|
|
* "A * C / B" in order to replace a slower division with a faster multiplication.
|
|
|
|
*/
|
|
|
|
int rpm = engine->rpmCalculator.rpm(PASS_ENGINE_PARAMETER_F);
|
|
|
|
return rpm == 0 ? NAN : timeSinceZeroAngleNt / getOneDegreeTimeNt(rpm);
|
|
|
|
}
|
|
|
|
|
|
|
|
void initRpmCalculator(Logging *sharedLogger, Engine *engine) {
|
|
|
|
logger = sharedLogger;
|
|
|
|
#if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__)
|
|
|
|
|
|
|
|
// tdcScheduler[0].name = "tdc0";
|
|
|
|
// tdcScheduler[1].name = "tdc1";
|
|
|
|
addTriggerEventListener(tdcMarkCallback, "chart TDC mark", engine);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
addTriggerEventListener(rpmShaftPositionCallback, "rpm reporter", engine);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if (EFI_PROD_CODE || EFI_SIMULATOR) || defined(__DOXYGEN__)
|
|
|
|
/**
|
|
|
|
* Schedules a callback 'angle' degree of crankshaft from now.
|
|
|
|
* The callback would be executed once after the duration of time which
|
|
|
|
* it takes the crankshaft to rotate to the specified angle.
|
|
|
|
*/
|
|
|
|
void scheduleByAngle(int rpm, scheduling_s *timer, angle_t angle,
|
|
|
|
schfunc_t callback, void *param, RpmCalculator *calc) {
|
|
|
|
efiAssertVoid(isValidRpm(rpm), "RPM check expected");
|
|
|
|
float delayUs = calc->oneDegreeUs * angle;
|
|
|
|
efiAssertVoid(!cisnan(delayUs), "NaN delay?");
|
|
|
|
scheduleTask("by angle", timer, (int) delayUs, callback, param);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#else
|
|
|
|
RpmCalculator::RpmCalculator() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* EFI_SHAFT_POSITION_INPUT */
|
|
|
|
|