mirror of https://github.com/FOME-Tech/fome-fw.git
Compare commits
3 Commits
7593cd61d4
...
51a23364e2
Author | SHA1 | Date |
---|---|---|
Matthew Kennedy | 51a23364e2 | |
Matthew Kennedy | 397e3dcd04 | |
Matthew Kennedy | 3fc42222cc |
|
@ -43,6 +43,7 @@ or
|
||||||
- TunerStudio UI improvements (#436, etc)
|
- TunerStudio UI improvements (#436, etc)
|
||||||
- Dropdown selector for popular gearbox ratios (#358, thank you @alrijleh and @nmschulte!)
|
- Dropdown selector for popular gearbox ratios (#358, thank you @alrijleh and @nmschulte!)
|
||||||
- Add two more aux linear sensors #476
|
- Add two more aux linear sensors #476
|
||||||
|
- Support wasted spark on odd cylinder count 4-stroke engines. Improves startup and allows running without a cam sensor!
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Improve performance with Lua CAN reception of a high volume of frames
|
- Improve performance with Lua CAN reception of a high volume of frames
|
||||||
|
|
|
@ -24,6 +24,8 @@ public:
|
||||||
*/
|
*/
|
||||||
angle_t engineCycle;
|
angle_t engineCycle;
|
||||||
|
|
||||||
|
bool useOddFireWastedSpark = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this is based on sensorChartMode and sensorSnifferRpmThreshold settings
|
* this is based on sensorChartMode and sensorSnifferRpmThreshold settings
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -104,6 +104,14 @@ static void prepareCylinderIgnitionSchedule(angle_t dwellAngleDuration, floatms_
|
||||||
event->sparkAngle = sparkAngle;
|
event->sparkAngle = sparkAngle;
|
||||||
|
|
||||||
auto ignitionMode = getCurrentIgnitionMode();
|
auto ignitionMode = getCurrentIgnitionMode();
|
||||||
|
|
||||||
|
// On an odd cylinder (or odd fire) wasted spark engine, map outputs as if in sequential.
|
||||||
|
// During actual scheduling, the events just get scheduled every 360 deg instead
|
||||||
|
// of every 720 deg.
|
||||||
|
if (ignitionMode == IM_WASTED_SPARK && engine->engineState.useOddFireWastedSpark) {
|
||||||
|
ignitionMode = IM_INDIVIDUAL_COILS;
|
||||||
|
}
|
||||||
|
|
||||||
engine->outputChannels.currentIgnitionMode = static_cast<uint8_t>(ignitionMode);
|
engine->outputChannels.currentIgnitionMode = static_cast<uint8_t>(ignitionMode);
|
||||||
|
|
||||||
const int index = getIgnitionPinForIndex(event->cylinderIndex, ignitionMode);
|
const int index = getIgnitionPinForIndex(event->cylinderIndex, ignitionMode);
|
||||||
|
@ -309,20 +317,9 @@ void turnSparkPinHigh(IgnitionEvent *event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void scheduleSparkEvent(bool limitedSpark, IgnitionEvent *event,
|
static void scheduleSparkEvent(bool limitedSpark, IgnitionEvent *event,
|
||||||
int rpm, efitick_t edgeTimestamp, float currentPhase, float nextPhase) {
|
int rpm, float dwellMs, float dwellAngle, float sparkAngle, efitick_t edgeTimestamp, float currentPhase, float nextPhase) {
|
||||||
|
|
||||||
angle_t sparkAngle = event->sparkAngle;
|
float angleOffset = dwellAngle - currentPhase;
|
||||||
const floatms_t dwellMs = engine->ignitionState.sparkDwell;
|
|
||||||
if (std::isnan(dwellMs) || dwellMs <= 0) {
|
|
||||||
warning(ObdCode::CUSTOM_DWELL, "invalid dwell to handle: %.2f at %d", dwellMs, rpm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (std::isnan(sparkAngle)) {
|
|
||||||
warning(ObdCode::CUSTOM_ADVANCE_SPARK, "NaN advance");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float angleOffset = event->dwellAngle - currentPhase;
|
|
||||||
if (angleOffset < 0) {
|
if (angleOffset < 0) {
|
||||||
angleOffset += engine->engineState.engineCycle;
|
angleOffset += engine->engineState.engineCycle;
|
||||||
}
|
}
|
||||||
|
@ -457,6 +454,12 @@ void onTriggerEventSparkLogic(int rpm, efitick_t edgeTimestamp, float currentPha
|
||||||
engine->outputChannels.sparkCutReason = (int8_t)limitedSparkState.reason;
|
engine->outputChannels.sparkCutReason = (int8_t)limitedSparkState.reason;
|
||||||
bool limitedSpark = !limitedSparkState.value;
|
bool limitedSpark = !limitedSparkState.value;
|
||||||
|
|
||||||
|
const floatms_t dwellMs = engine->ignitionState.sparkDwell;
|
||||||
|
if (std::isnan(dwellMs) || dwellMs <= 0) {
|
||||||
|
warning(ObdCode::CUSTOM_DWELL, "invalid dwell to handle: %.2f at %d", dwellMs, rpm);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!engine->ignitionEvents.isReady) {
|
if (!engine->ignitionEvents.isReady) {
|
||||||
prepareIgnitionSchedule();
|
prepareIgnitionSchedule();
|
||||||
}
|
}
|
||||||
|
@ -467,13 +470,48 @@ void onTriggerEventSparkLogic(int rpm, efitick_t edgeTimestamp, float currentPha
|
||||||
* See initializeIgnitionActions()
|
* See initializeIgnitionActions()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Only apply odd cylinder count wasted logic if:
|
||||||
|
// - odd cyl count
|
||||||
|
// - current mode is wasted spark
|
||||||
|
// - four stroke
|
||||||
|
bool enableOddCylinderWastedSpark =
|
||||||
|
engine->engineState.useOddFireWastedSpark
|
||||||
|
&& getCurrentIgnitionMode() == IM_WASTED_SPARK;
|
||||||
|
|
||||||
// scheduleSimpleMsg(&logger, "eventId spark ", eventIndex);
|
|
||||||
if (engine->ignitionEvents.isReady) {
|
if (engine->ignitionEvents.isReady) {
|
||||||
for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
|
for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
|
||||||
IgnitionEvent *event = &engine->ignitionEvents.elements[i];
|
IgnitionEvent *event = &engine->ignitionEvents.elements[i];
|
||||||
|
|
||||||
if (!isPhaseInRange(event->dwellAngle, currentPhase, nextPhase)) {
|
angle_t dwellAngle = event->dwellAngle;
|
||||||
|
|
||||||
|
angle_t sparkAngle = event->sparkAngle;
|
||||||
|
if (std::isnan(sparkAngle)) {
|
||||||
|
warning(ObdCode::CUSTOM_ADVANCE_SPARK, "NaN advance");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isOddCylWastedEvent = false;
|
||||||
|
if (enableOddCylinderWastedSpark) {
|
||||||
|
auto dwellAngleWastedEvent = dwellAngle + 360;
|
||||||
|
if (dwellAngleWastedEvent > 720) {
|
||||||
|
dwellAngleWastedEvent -= 720;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether this event hits 360 degrees out from now (ie, wasted spark),
|
||||||
|
// and if so, twiddle the dwell and spark angles so it happens now instead
|
||||||
|
isOddCylWastedEvent = isPhaseInRange(dwellAngleWastedEvent, currentPhase, nextPhase);
|
||||||
|
|
||||||
|
if (isOddCylWastedEvent) {
|
||||||
|
dwellAngle = dwellAngleWastedEvent;
|
||||||
|
|
||||||
|
sparkAngle += 360;
|
||||||
|
if (sparkAngle > 720) {
|
||||||
|
sparkAngle -= 720;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOddCylWastedEvent && !isPhaseInRange(dwellAngle, currentPhase, nextPhase)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,7 +536,7 @@ void onTriggerEventSparkLogic(int rpm, efitick_t edgeTimestamp, float currentPha
|
||||||
engine->ALSsoftSparkLimiter.setTargetSkipRatio(ALSSkipRatio);
|
engine->ALSsoftSparkLimiter.setTargetSkipRatio(ALSSkipRatio);
|
||||||
#endif // EFI_ANTILAG_SYSTEM
|
#endif // EFI_ANTILAG_SYSTEM
|
||||||
|
|
||||||
scheduleSparkEvent(limitedSpark, event, rpm, edgeTimestamp, currentPhase, nextPhase);
|
scheduleSparkEvent(limitedSpark, event, rpm, dwellMs, dwellAngle, sparkAngle, edgeTimestamp, currentPhase, nextPhase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,6 @@ static bool noFiringUntilVvtSync(vvt_mode_e vvtMode) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Odd cylinder count engines don't work properly with wasted spark, so wait for full sync (so that sequential works)
|
|
||||||
// See https://github.com/rusefi/rusefi/issues/4195 for the issue to properly support this case
|
|
||||||
if (engineConfiguration->cylindersCount > 1 && engineConfiguration->cylindersCount % 2 == 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Symmetrical crank modes require cam sync before firing
|
// Symmetrical crank modes require cam sync before firing
|
||||||
// non-symmetrical cranks can use faster spin-up mode (firing in wasted/batch before VVT sync)
|
// non-symmetrical cranks can use faster spin-up mode (firing in wasted/batch before VVT sync)
|
||||||
// Examples include Nissan MR/VQ, Miata NB, etc
|
// Examples include Nissan MR/VQ, Miata NB, etc
|
||||||
|
|
|
@ -386,8 +386,7 @@ ignition_mode_e getCurrentIgnitionMode() {
|
||||||
ignition_mode_e ignitionMode = engineConfiguration->ignitionMode;
|
ignition_mode_e ignitionMode = engineConfiguration->ignitionMode;
|
||||||
#if EFI_SHAFT_POSITION_INPUT
|
#if EFI_SHAFT_POSITION_INPUT
|
||||||
// In spin-up cranking mode we don't have full phase sync info yet, so wasted spark mode is better
|
// In spin-up cranking mode we don't have full phase sync info yet, so wasted spark mode is better
|
||||||
// However, only do this on even cylinder count engines: odd cyl count doesn't fire at all
|
if (ignitionMode == IM_INDIVIDUAL_COILS) {
|
||||||
if (ignitionMode == IM_INDIVIDUAL_COILS && (engineConfiguration->cylindersCount % 2 == 0)) {
|
|
||||||
bool missingPhaseInfoForSequential =
|
bool missingPhaseInfoForSequential =
|
||||||
!engine->triggerCentral.triggerState.hasSynchronizedPhase();
|
!engine->triggerCentral.triggerState.hasSynchronizedPhase();
|
||||||
|
|
||||||
|
@ -405,7 +404,20 @@ ignition_mode_e getCurrentIgnitionMode() {
|
||||||
* This heavy method is only invoked in case of a configuration change or initialization.
|
* This heavy method is only invoked in case of a configuration change or initialization.
|
||||||
*/
|
*/
|
||||||
void prepareOutputSignals() {
|
void prepareOutputSignals() {
|
||||||
getEngineState()->engineCycle = getEngineCycle(getEngineRotationState()->getOperationMode());
|
auto operationMode = getEngineRotationState()->getOperationMode();
|
||||||
|
getEngineState()->engineCycle = getEngineCycle(operationMode);
|
||||||
|
|
||||||
|
bool isOddFire = false;
|
||||||
|
for (size_t i = 0; i < engineConfiguration->cylindersCount; i++) {
|
||||||
|
if (engineConfiguration->timing_offset_cylinder[i] != 0) {
|
||||||
|
isOddFire = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use odd fire wasted spark logic if not two stroke, and an odd fire or odd cylinder # engine
|
||||||
|
getEngineState()->useOddFireWastedSpark = operationMode != TWO_STROKE
|
||||||
|
&& (isOddFire | (engineConfiguration->cylindersCount % 2 == 1));
|
||||||
|
|
||||||
#if EFI_UNIT_TEST
|
#if EFI_UNIT_TEST
|
||||||
if (verboseMode) {
|
if (verboseMode) {
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
#include "spark_logic.h"
|
#include "spark_logic.h"
|
||||||
|
|
||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
|
using ::testing::InSequence;
|
||||||
|
using ::testing::StrictMock;
|
||||||
|
|
||||||
TEST(ignition, twoCoils) {
|
TEST(ignition, twoCoils) {
|
||||||
EngineTestHelper eth(engine_type_e::FRANKENSO_BMW_M73_F);
|
EngineTestHelper eth(engine_type_e::FRANKENSO_BMW_M73_F);
|
||||||
|
@ -148,3 +150,55 @@ TEST(ignition, CylinderTimingTrim) {
|
||||||
EXPECT_NEAR(engine->engineState.timingAdvance[2], unadjusted + 2, EPS4D);
|
EXPECT_NEAR(engine->engineState.timingAdvance[2], unadjusted + 2, EPS4D);
|
||||||
EXPECT_NEAR(engine->engineState.timingAdvance[3], unadjusted + 4, EPS4D);
|
EXPECT_NEAR(engine->engineState.timingAdvance[3], unadjusted + 4, EPS4D);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ignition, oddCylinderWastedSpark) {
|
||||||
|
StrictMock<MockExecutor> mockExec;
|
||||||
|
|
||||||
|
EngineTestHelper eth(engine_type_e::TEST_ENGINE);
|
||||||
|
engine->scheduler.setMockExecutor(&mockExec);
|
||||||
|
engineConfiguration->cylindersCount = 1;
|
||||||
|
engineConfiguration->firingOrder = FO_1;
|
||||||
|
engineConfiguration->ignitionMode = IM_WASTED_SPARK;
|
||||||
|
|
||||||
|
efitick_t nowNt1 = 1000000;
|
||||||
|
efitick_t nowNt2 = 2222222;
|
||||||
|
|
||||||
|
|
||||||
|
engine->rpmCalculator.oneDegreeUs = 100;
|
||||||
|
|
||||||
|
{
|
||||||
|
InSequence is;
|
||||||
|
|
||||||
|
// Should schedule one dwell+fire pair:
|
||||||
|
// Dwell 5 deg from now
|
||||||
|
float nt1deg = USF2NT(engine->rpmCalculator.oneDegreeUs);
|
||||||
|
efitick_t startTime = nowNt1 + nt1deg * 5;
|
||||||
|
EXPECT_CALL(mockExec, schedule(testing::NotNull(), _, startTime, _));
|
||||||
|
// Spark 15 deg from now
|
||||||
|
efitick_t endTime = startTime + nt1deg * 10;
|
||||||
|
EXPECT_CALL(mockExec, schedule(testing::NotNull(), _, endTime, _));
|
||||||
|
|
||||||
|
|
||||||
|
// Should schedule second dwell+fire pair, the out of phase copy
|
||||||
|
// Dwell 5 deg from now
|
||||||
|
startTime = nowNt2 + nt1deg * 5;
|
||||||
|
EXPECT_CALL(mockExec, schedule(testing::NotNull(), _, startTime, _));
|
||||||
|
// Spark 15 deg from now
|
||||||
|
endTime = startTime + nt1deg * 10;
|
||||||
|
EXPECT_CALL(mockExec, schedule(testing::NotNull(), _, endTime, _));
|
||||||
|
}
|
||||||
|
|
||||||
|
engine->ignitionState.sparkDwell = 1;
|
||||||
|
|
||||||
|
// dwell should start at 15 degrees ATDC and firing at 25 deg ATDC
|
||||||
|
engine->ignitionState.dwellAngle = 10;
|
||||||
|
engine->engineState.timingAdvance[0] = -25;
|
||||||
|
engine->engineState.useOddFireWastedSpark = true;
|
||||||
|
engineConfiguration->minimumIgnitionTiming = -25;
|
||||||
|
|
||||||
|
// expect to schedule the on-phase dwell and spark (not the wasted spark copy)
|
||||||
|
onTriggerEventSparkLogic(1200, nowNt1, 10, 30);
|
||||||
|
|
||||||
|
// expect to schedule second events, the out-of-phase dwell and spark (the wasted spark copy)
|
||||||
|
onTriggerEventSparkLogic(1200, nowNt2, 360 + 10, 360 + 30);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue