calculate per-cylinder ignition timing (#3652)

* simplify cylinder phasing

* per cylinder timing

* s

* s

* s

* why was there a divide by 2?
This commit is contained in:
Matthew Kennedy 2021-12-06 18:19:37 -08:00 committed by GitHub
parent 17debda6b6
commit 3a95e86112
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 36 additions and 32 deletions

View File

@ -624,7 +624,7 @@ static void updateFuelInfo() {
} }
static void updateIgnition(int rpm) { static void updateIgnition(int rpm) {
float timing = engine->engineState.timingAdvance; float timing = engine->engineState.timingAdvance[0];
// that's weird logic. also seems broken for two stroke? // that's weird logic. also seems broken for two stroke?
tsOutputChannels.ignitionAdvance = timing > FOUR_STROKE_CYCLE_DURATION / 2 ? timing - FOUR_STROKE_CYCLE_DURATION : timing; tsOutputChannels.ignitionAdvance = timing > FOUR_STROKE_CYCLE_DURATION / 2 ? timing - FOUR_STROKE_CYCLE_DURATION : timing;
// 60 // 60

View File

@ -165,7 +165,11 @@ void EngineState::periodicFastCallback() {
injectionOffset = getInjectionOffset(rpm, fuelLoad); injectionOffset = getInjectionOffset(rpm, fuelLoad);
float ignitionLoad = getIgnitionLoad(); float ignitionLoad = getIgnitionLoad();
timingAdvance = getAdvance(rpm, ignitionLoad) * luaAdjustments.ignitionTimingMult + luaAdjustments.ignitionTimingAdd; float advance = getAdvance(rpm, ignitionLoad) * luaAdjustments.ignitionTimingMult + luaAdjustments.ignitionTimingAdd;
for (size_t i = 0; i < engineConfiguration->specs.cylindersCount; i++) {
timingAdvance[i] = advance;
}
// TODO: calculate me from a table! // TODO: calculate me from a table!
trailingSparkAngle = engineConfiguration->trailingSparkAngle; trailingSparkAngle = engineConfiguration->trailingSparkAngle;

View File

@ -54,7 +54,7 @@ public:
/** /**
* timing advance is angle distance before Top Dead Center (TDP), i.e. "10 degree timing advance" means "happens 10 degrees before TDC" * timing advance is angle distance before Top Dead Center (TDP), i.e. "10 degree timing advance" means "happens 10 degrees before TDC"
*/ */
angle_t timingAdvance = 0; angle_t timingAdvance[MAX_CYLINDER_COUNT] = {0};
// Angle between firing the main (primary) spark and the secondary (trailing) spark // Angle between firing the main (primary) spark and the secondary (trailing) spark
angle_t trailingSparkAngle = 0; angle_t trailingSparkAngle = 0;

View File

@ -600,7 +600,7 @@ void canDashboardHaltech(CanCycle cycle) {
msg[2] = 0x00; msg[2] = 0x00;
msg[3] = 0x00; msg[3] = 0x00;
/* Ignition Angle (Leading) - y = x/10 */ /* Ignition Angle (Leading) - y = x/10 */
float timing = engine->engineState.timingAdvance; float timing = engine->engineState.timingAdvance[0];
int16_t ignAngle = ((timing > 360 ? timing - 720 : timing) * 10); int16_t ignAngle = ((timing > 360 ? timing - 720 : timing) * 10);
msg[4] = (ignAngle >> 8); msg[4] = (ignAngle >> 8);
msg[5] = (ignAngle & 0x00ff); msg[5] = (ignAngle & 0x00ff);

View File

@ -56,7 +56,7 @@ static void populateFrame(Speeds& msg) {
auto rpm = GET_RPM(); auto rpm = GET_RPM();
msg.rpm = rpm; msg.rpm = rpm;
auto timing = engine->engineState.timingAdvance; auto timing = engine->engineState.timingAdvance[0];
msg.timing = timing > 360 ? timing - 720 : timing; msg.timing = timing > 360 ? timing - 720 : timing;
msg.injDuty = getInjectorDutyCycle(rpm); msg.injDuty = getInjectorDutyCycle(rpm);

View File

@ -138,7 +138,7 @@ static void handleGetDataRequest(const CANRxFrame& rx) {
obdSendValue(_1_MODE, pid, 1, Sensor::getOrZero(SensorType::VehicleSpeed)); obdSendValue(_1_MODE, pid, 1, Sensor::getOrZero(SensorType::VehicleSpeed));
break; break;
case PID_TIMING_ADVANCE: { case PID_TIMING_ADVANCE: {
float timing = engine->engineState.timingAdvance; float timing = engine->engineState.timingAdvance[0];
timing = (timing > 360.0f) ? (timing - 720.0f) : timing; timing = (timing > 360.0f) ? (timing - 720.0f) : timing;
obdSendValue(_1_MODE, pid, 1, (timing + 64.0f) * 2.0f); // angle before TDC. (A/2)-64 obdSendValue(_1_MODE, pid, 1, (timing + 64.0f) * 2.0f); // angle before TDC. (A/2)-64
break; break;

View File

@ -11,9 +11,9 @@
#include "hip9011.h" #include "hip9011.h"
int getCylinderKnockBank(uint8_t cylinderIndex) { int getCylinderKnockBank(uint8_t cylinderNumber) {
// C/C++ can't index in to bit fields, we have to provide lookup ourselves // C/C++ can't index in to bit fields, we have to provide lookup ourselves
switch (cylinderIndex) { switch (cylinderNumber) {
#if EFI_PROD_CODE #if EFI_PROD_CODE
case 0: case 0:
return engineConfiguration->knockBankCyl1; return engineConfiguration->knockBankCyl1;
@ -45,13 +45,13 @@ int getCylinderKnockBank(uint8_t cylinderIndex) {
} }
} }
bool KnockController::onKnockSenseCompleted(uint8_t cylinderIndex, float dbv, efitick_t lastKnockTime) { bool KnockController::onKnockSenseCompleted(uint8_t cylinderNumber, float dbv, efitick_t lastKnockTime) {
bool isKnock = dbv > engine->engineState.knockThreshold; bool isKnock = dbv > engine->engineState.knockThreshold;
#if EFI_TUNER_STUDIO #if EFI_TUNER_STUDIO
// Pass through per-cylinder peak detector // Pass through per-cylinder peak detector
float cylPeak = peakDetectors[cylinderIndex].detect(dbv, lastKnockTime); float cylPeak = peakDetectors[cylinderNumber].detect(dbv, lastKnockTime);
tsOutputChannels.knock[cylinderIndex] = roundf(cylPeak); tsOutputChannels.knock[cylinderNumber] = roundf(cylPeak);
// Pass through all-cylinders peak detector // Pass through all-cylinders peak detector
tsOutputChannels.knockLevel = allCylinderPeakDetector.detect(dbv, lastKnockTime); tsOutputChannels.knockLevel = allCylinderPeakDetector.detect(dbv, lastKnockTime);
@ -64,7 +64,7 @@ bool KnockController::onKnockSenseCompleted(uint8_t cylinderIndex, float dbv, ef
// TODO: retard timing, then put it back! // TODO: retard timing, then put it back!
if (isKnock) { if (isKnock) {
auto baseTiming = engine->engineState.timingAdvance; auto baseTiming = engine->engineState.timingAdvance[cylinderNumber];
// TODO: 20 configurable? Better explanation why 20? // TODO: 20 configurable? Better explanation why 20?
auto distToMinimum = baseTiming - (-20); auto distToMinimum = baseTiming - (-20);
@ -107,13 +107,13 @@ void KnockController::periodicFastCallback() {
} }
// This callback is to be implemented by the knock sense driver // This callback is to be implemented by the knock sense driver
__attribute__((weak)) void onStartKnockSampling(uint8_t cylinderIndex, float samplingTimeSeconds, uint8_t channelIdx) { __attribute__((weak)) void onStartKnockSampling(uint8_t cylinderNumber, float samplingTimeSeconds, uint8_t channelIdx) {
UNUSED(cylinderIndex); UNUSED(cylinderNumber);
UNUSED(samplingTimeSeconds); UNUSED(samplingTimeSeconds);
UNUSED(channelIdx); UNUSED(channelIdx);
} }
static uint8_t cylinderIndexCopy; static uint8_t cylinderNumberCopy;
// Called when its time to start listening for knock // Called when its time to start listening for knock
// Does some math, then hands off to the driver to start any sampling hardware // Does some math, then hands off to the driver to start any sampling hardware
@ -126,16 +126,16 @@ static void startKnockSampling(Engine* engine) {
float samplingSeconds = engine->rpmCalculator.oneDegreeUs * engineConfiguration->knockSamplingDuration / US_PER_SECOND_F; float samplingSeconds = engine->rpmCalculator.oneDegreeUs * engineConfiguration->knockSamplingDuration / US_PER_SECOND_F;
// Look up which channel this cylinder uses // Look up which channel this cylinder uses
auto channel = getCylinderKnockBank(cylinderIndexCopy); auto channel = getCylinderKnockBank(cylinderNumberCopy);
// Call the driver to begin sampling // Call the driver to begin sampling
onStartKnockSampling(cylinderIndexCopy, samplingSeconds, channel); onStartKnockSampling(cylinderNumberCopy, samplingSeconds, channel);
} }
static scheduling_s startSampling; static scheduling_s startSampling;
void Engine::onSparkFireKnockSense(uint8_t cylinderIndex, efitick_t nowNt) { void Engine::onSparkFireKnockSense(uint8_t cylinderNumber, efitick_t nowNt) {
cylinderIndexCopy = cylinderIndex; cylinderNumberCopy = cylinderNumber;
#if EFI_HIP_9011 || EFI_SOFTWARE_KNOCK #if EFI_HIP_9011 || EFI_SOFTWARE_KNOCK
scheduleByAngle(&startSampling, nowNt, scheduleByAngle(&startSampling, nowNt,
@ -143,6 +143,6 @@ void Engine::onSparkFireKnockSense(uint8_t cylinderIndex, efitick_t nowNt) {
#endif #endif
#if EFI_HIP_9011 #if EFI_HIP_9011
hip9011_onFireEvent(cylinderIndex, nowNt); hip9011_onFireEvent(cylinderNumber, nowNt);
#endif #endif
} }

View File

@ -9,12 +9,12 @@
#include "peak_detect.h" #include "peak_detect.h"
int getCylinderKnockBank(uint8_t cylinderIndex); int getCylinderKnockBank(uint8_t cylinderNumber);
class KnockController { class KnockController {
public: public:
// onKnockSenseCompleted is the callback from the knock sense driver to report a sensed knock level // onKnockSenseCompleted is the callback from the knock sense driver to report a sensed knock level
bool onKnockSenseCompleted(uint8_t cylinderIndex, float dbv, efitick_t lastKnockTime); bool onKnockSenseCompleted(uint8_t cylinderNumber, float dbv, efitick_t lastKnockTime);
void periodicFastCallback(); void periodicFastCallback();
float getKnockRetard() const; float getKnockRetard() const;

View File

@ -503,7 +503,7 @@ static void showMainInfo(Engine *engine) {
int rpm = GET_RPM(); int rpm = GET_RPM();
float el = getFuelingLoad(); float el = getFuelingLoad();
efiPrintf("rpm %d engine_load %.2f", rpm, el); efiPrintf("rpm %d engine_load %.2f", rpm, el);
efiPrintf("fuel %.2fms timing %.2f", engine->injectionDuration, engine->engineState.timingAdvance); efiPrintf("fuel %.2fms timing %.2f", engine->injectionDuration, engine->engineState.timingAdvance[0]);
#endif /* EFI_PROD_CODE */ #endif /* EFI_PROD_CODE */
} }

View File

@ -75,7 +75,7 @@ static void prepareCylinderIgnitionSchedule(angle_t dwellAngleDuration, floatms_
const angle_t sparkAngle = const angle_t sparkAngle =
// Negate because timing *before* TDC, and we schedule *after* TDC // Negate because timing *before* TDC, and we schedule *after* TDC
- engine->engineState.timingAdvance - engine->engineState.timingAdvance[event->cylinderNumber]
// Offset by this cylinder's position in the cycle // Offset by this cylinder's position in the cycle
+ getCylinderAngle(event->cylinderIndex, event->cylinderNumber) + getCylinderAngle(event->cylinderIndex, event->cylinderNumber)
// Pull any extra timing for knock retard // Pull any extra timing for knock retard
@ -373,7 +373,7 @@ void initializeIgnitionActions() {
IgnitionEventList *list = &engine->ignitionEvents; IgnitionEventList *list = &engine->ignitionEvents;
angle_t dwellAngle = engine->engineState.dwellAngle; angle_t dwellAngle = engine->engineState.dwellAngle;
floatms_t sparkDwell = engine->engineState.sparkDwell; floatms_t sparkDwell = engine->engineState.sparkDwell;
if (cisnan(engine->engineState.timingAdvance) || cisnan(dwellAngle)) { if (cisnan(engine->engineState.timingAdvance[0]) || cisnan(dwellAngle)) {
// error should already be reported // error should already be reported
// need to invalidate previous ignition schedule // need to invalidate previous ignition schedule
list->isReady = false; list->isReady = false;

View File

@ -11,7 +11,7 @@
#include "ch.hpp" #include "ch.hpp"
static NO_CACHE adcsample_t sampleBuffer[2000]; static NO_CACHE adcsample_t sampleBuffer[2000];
static int8_t currentCylinderIndex = 0; static int8_t currentCylinderNumber = 0;
static efitick_t lastKnockSampleTime = 0; static efitick_t lastKnockSampleTime = 0;
static Biquad knockFilter; static Biquad knockFilter;
@ -104,7 +104,7 @@ const ADCConversionGroup* getConversionGroup(uint8_t channelIdx) {
return &adcConvGroupCh1; return &adcConvGroupCh1;
} }
void onStartKnockSampling(uint8_t cylinderIndex, float samplingSeconds, uint8_t channelIdx) { void onStartKnockSampling(uint8_t cylinderNumber, float samplingSeconds, uint8_t channelIdx) {
if (!engineConfiguration->enableSoftwareKnock) { if (!engineConfiguration->enableSoftwareKnock) {
return; return;
} }
@ -128,8 +128,8 @@ void onStartKnockSampling(uint8_t cylinderIndex, float samplingSeconds, uint8_t
// Select the appropriate conversion group - it will differ depending on which sensor this cylinder should listen on // Select the appropriate conversion group - it will differ depending on which sensor this cylinder should listen on
auto conversionGroup = getConversionGroup(channelIdx); auto conversionGroup = getConversionGroup(channelIdx);
// Stash the current cylinder's index so we can store the result appropriately // Stash the current cylinder's number so we can store the result appropriately
currentCylinderIndex = cylinderIndex; currentCylinderNumber = cylinderNumber;
adcStartConversionI(&KNOCK_ADC, conversionGroup, sampleBuffer, sampleCount); adcStartConversionI(&KNOCK_ADC, conversionGroup, sampleBuffer, sampleCount);
lastKnockSampleTime = getTimeNowNt(); lastKnockSampleTime = getTimeNowNt();
@ -201,7 +201,7 @@ void processLastKnockEvent() {
// clamp to reasonable range // clamp to reasonable range
db = clampF(-100, db, 100); db = clampF(-100, db, 100);
engine->knockController.onKnockSenseCompleted(currentCylinderIndex, db, lastKnockTime); engine->knockController.onKnockSenseCompleted(currentCylinderNumber, db, lastKnockTime);
} }
void KnockThread::ThreadTask() { void KnockThread::ThreadTask() {

View File

@ -23,7 +23,7 @@ TEST(ignition, twoCoils) {
ASSERT_EQ(engine->ignitionPin[ID2INDEX(12)], 1); ASSERT_EQ(engine->ignitionPin[ID2INDEX(12)], 1);
// let's recalculate with zero timing so that we can focus on relation advance between cylinders // let's recalculate with zero timing so that we can focus on relation advance between cylinders
engine->engineState.timingAdvance = 0; setArrayValues(engine->engineState.timingAdvance, 0.0f);
initializeIgnitionActions(); initializeIgnitionActions();
ASSERT_EQ(engine->ignitionEvents.elements[0].sparkAngle, 0); ASSERT_EQ(engine->ignitionEvents.elements[0].sparkAngle, 0);

View File

@ -345,7 +345,7 @@ TEST(misc, testRpmCalculator) {
eth.engine.periodicFastCallback(); eth.engine.periodicFastCallback();
ASSERT_NEAR(engine->engineState.timingAdvance, 707, 0.1f); ASSERT_NEAR(engine->engineState.timingAdvance[0], 707, 0.1f);
assertEqualsM("fuel #1", 4.5450, engine->injectionDuration); assertEqualsM("fuel #1", 4.5450, engine->injectionDuration);
InjectionEvent *ie0 = &engine->injectionEvents.elements[0]; InjectionEvent *ie0 = &engine->injectionEvents.elements[0];