| Line | Branch | Decision | Exec | Source |
|---|---|---|---|---|
| 1 | /* | |||
| 2 | * @file knock_logic.c | |||
| 3 | * | |||
| 4 | * @date Apr 04, 2021 | |||
| 5 | * @author Andrey Gusakov | |||
| 6 | */ | |||
| 7 | ||||
| 8 | #include "pch.h" | |||
| 9 | #include "knock_logic.h" | |||
| 10 | ||||
| 11 | ✗ | int getCylinderKnockBank(uint8_t cylinderNumber) { | ||
| 12 | // C/C++ can't index in to bit fields, we have to provide lookup ourselves | |||
| 13 | ✗ | switch (cylinderNumber) { | ||
| 14 | ✗ | case 0: | ||
| 15 | ✗ | return engineConfiguration->knockBankCyl1; | ||
| 16 | ✗ | case 1: | ||
| 17 | ✗ | return engineConfiguration->knockBankCyl2; | ||
| 18 | ✗ | case 2: | ||
| 19 | ✗ | return engineConfiguration->knockBankCyl3; | ||
| 20 | ✗ | case 3: | ||
| 21 | ✗ | return engineConfiguration->knockBankCyl4; | ||
| 22 | ✗ | case 4: | ||
| 23 | ✗ | return engineConfiguration->knockBankCyl5; | ||
| 24 | ✗ | case 5: | ||
| 25 | ✗ | return engineConfiguration->knockBankCyl6; | ||
| 26 | ✗ | case 6: | ||
| 27 | ✗ | return engineConfiguration->knockBankCyl7; | ||
| 28 | ✗ | case 7: | ||
| 29 | ✗ | return engineConfiguration->knockBankCyl8; | ||
| 30 | ✗ | case 8: | ||
| 31 | ✗ | return engineConfiguration->knockBankCyl9; | ||
| 32 | ✗ | case 9: | ||
| 33 | ✗ | return engineConfiguration->knockBankCyl10; | ||
| 34 | ✗ | case 10: | ||
| 35 | ✗ | return engineConfiguration->knockBankCyl11; | ||
| 36 | ✗ | case 11: | ||
| 37 | ✗ | return engineConfiguration->knockBankCyl12; | ||
| 38 | ✗ | default: | ||
| 39 | ✗ | return 0; | ||
| 40 | } | |||
| 41 | } | |||
| 42 | ||||
| 43 | 224 | void KnockControllerBase::onKnockSenseCompleted(uint8_t cylinderNumber, float dbv, efitick_t lastKnockTime) { | ||
| 44 | 224 | bool isKnock = dbv > m_knockThreshold; | ||
| 45 | ||||
| 46 | // Per-cylinder peak detector | |||
| 47 | 224 | float cylPeak = peakDetectors[cylinderNumber].detect(dbv, lastKnockTime); | ||
| 48 | 224 | m_knockCyl[cylinderNumber] = roundf(cylPeak); | ||
| 49 | ||||
| 50 | // All-cylinders peak detector | |||
| 51 | 224 | m_knockLevel = allCylinderPeakDetector.detect(dbv, lastKnockTime); | ||
| 52 | ||||
| 53 |
2/2✓ Branch 0 taken 204 times.
✓ Branch 1 taken 20 times.
|
2/2✓ Decision 'true' taken 204 times.
✓ Decision 'false' taken 20 times.
|
224 | if (isKnock) { |
| 54 | 204 | m_knockCount++; | ||
| 55 | ||||
| 56 | 204 | auto baseTiming = engine->engineState.timingAdvance[cylinderNumber]; | ||
| 57 | ||||
| 58 | // TODO: 20 configurable? Better explanation why 20? | |||
| 59 | 204 | auto distToMinimum = baseTiming - (-20); | ||
| 60 | ||||
| 61 | // percent -> ratio = divide by 100 | |||
| 62 | 204 | auto retardFraction = engineConfiguration->knockRetardAggression * 0.01f; | ||
| 63 | 204 | auto retardAmount = distToMinimum * retardFraction; | ||
| 64 | ||||
| 65 | // TODO: remove magic 30% m_maximumFuelTrim? | |||
| 66 | 204 | auto maximumFuelTrim = 0.3f; | ||
| 67 | ||||
| 68 | 204 | auto trimFuelFraction = engineConfiguration->knockFuelTrimAggression * 0.01f; | ||
| 69 | 204 | float trimFuelPercent = clampF(0.f, (float)engineConfiguration->knockFuelTrim, maximumFuelTrim * 100.f); | ||
| 70 | 204 | float trimFuelAmountPercent = trimFuelPercent * trimFuelFraction; | ||
| 71 | 204 | float trimFuelAmount = trimFuelAmountPercent / 100.f; | ||
| 72 | ||||
| 73 | { | |||
| 74 | // Adjust knock retard under lock | |||
| 75 | chibios_rt::CriticalSectionLocker csl; | |||
| 76 | ||||
| 77 | 204 | auto newRetard = m_knockRetard + retardAmount; | ||
| 78 | 204 | m_knockRetard = clampF(0.f, newRetard, m_maximumRetard); | ||
| 79 | ||||
| 80 | 204 | auto newFuelTrim = m_knockFuelTrimMultiplier + trimFuelAmount; | ||
| 81 | 204 | m_knockFuelTrimMultiplier = clampF(0.f, newFuelTrim, maximumFuelTrim); | ||
| 82 | } | |||
| 83 | } | |||
| 84 | 224 | } | ||
| 85 | ||||
| 86 | 1078 | float KnockControllerBase::getKnockRetard() const { | ||
| 87 | 1078 | return m_knockRetard; | ||
| 88 | } | |||
| 89 | ||||
| 90 | ✗ | uint32_t KnockControllerBase::getKnockCount() const { | ||
| 91 | ✗ | return m_knockCount; | ||
| 92 | } | |||
| 93 | ||||
| 94 | 4445 | float KnockControllerBase::getFuelTrimMultiplier() const { | ||
| 95 | 4445 | return 1.0 + m_knockFuelTrimMultiplier; | ||
| 96 | } | |||
| 97 | ||||
| 98 | 4125 | void KnockControllerBase::onFastCallback() { | ||
| 99 | 4125 | m_knockThreshold = getKnockThreshold(); | ||
| 100 | 4125 | m_maximumRetard = getMaximumRetard(); | ||
| 101 | ||||
| 102 | 4125 | constexpr auto callbackPeriodSeconds = FAST_CALLBACK_PERIOD_MS / 1000.0f; | ||
| 103 | ||||
| 104 | 4125 | auto applyRetardAmount = engineConfiguration->knockRetardReapplyRate * callbackPeriodSeconds; | ||
| 105 | 4125 | auto applyFuelAmount = engineConfiguration->knockFuelTrimReapplyRate * 0.01f * callbackPeriodSeconds; | ||
| 106 | ||||
| 107 | // disable knock suppression then deceleration | |||
| 108 | 4125 | auto TPSValue = Sensor::getOrZero(SensorType::Tps1); | ||
| 109 | ||||
| 110 | { | |||
| 111 | // Adjust knock retard under lock | |||
| 112 | chibios_rt::CriticalSectionLocker csl; | |||
| 113 | ||||
| 114 | ||||
| 115 |
2/2✓ Branch 1 taken 1102 times.
✓ Branch 2 taken 3023 times.
|
2/2✓ Decision 'true' taken 1102 times.
✓ Decision 'false' taken 3023 times.
|
4125 | if(TPSValue < engineConfiguration->knockSuppressMinTps) { |
| 116 | 1102 | m_knockRetard = 0.0; | ||
| 117 | 1102 | m_knockFuelTrimMultiplier = 0.0; | ||
| 118 | 1102 | return; | ||
| 119 | } | |||
| 120 | ||||
| 121 | // Reduce knock retard at the requested rate | |||
| 122 | 3023 | float newRetard = m_knockRetard - applyRetardAmount; | ||
| 123 | ||||
| 124 | // don't allow retard to go negative | |||
| 125 |
2/2✓ Branch 0 taken 2357 times.
✓ Branch 1 taken 666 times.
|
2/2✓ Decision 'true' taken 2357 times.
✓ Decision 'false' taken 666 times.
|
3023 | if (newRetard < 0) { |
| 126 | 2357 | m_knockRetard = 0; | ||
| 127 | } else { | |||
| 128 | 666 | m_knockRetard = newRetard; | ||
| 129 | } | |||
| 130 | ||||
| 131 | // Reduce fuel trim at the requested rate | |||
| 132 | 3023 | float newTrim = m_knockFuelTrimMultiplier - applyFuelAmount; | ||
| 133 | ||||
| 134 | // don't allow trim to go negative | |||
| 135 |
2/2✓ Branch 0 taken 1411 times.
✓ Branch 1 taken 1612 times.
|
2/2✓ Decision 'true' taken 1411 times.
✓ Decision 'false' taken 1612 times.
|
3023 | if (newTrim < 0) { |
| 136 | 1411 | m_knockFuelTrimMultiplier = 0; | ||
| 137 | } else { | |||
| 138 | 1612 | m_knockFuelTrimMultiplier = newTrim; | ||
| 139 | } | |||
| 140 | } | |||
| 141 | } | |||
| 142 | ||||
| 143 | 1101 | float KnockController::getKnockThreshold() const { | ||
| 144 | 1101 | return interpolate2d( | ||
| 145 | Sensor::getOrZero(SensorType::Rpm), | |||
| 146 | 1101 | config->knockNoiseRpmBins, | ||
| 147 | 1101 | config->knockBaseNoise | ||
| 148 | 1101 | ); | ||
| 149 | } | |||
| 150 | ||||
| 151 | 1101 | float KnockController::getMaximumRetard() const { | ||
| 152 | return | |||
| 153 | 1101 | interpolate3d( | ||
| 154 | 1101 | config->maxKnockRetardTable, | ||
| 155 | 1101 | config->maxKnockRetardLoadBins, getIgnitionLoad(), | ||
| 156 | 1101 | config->maxKnockRetardRpmBins, Sensor::getOrZero(SensorType::Rpm) | ||
| 157 | 1101 | ); | ||
| 158 | } | |||
| 159 | ||||
| 160 | // This callback is to be implemented by the knock sense driver | |||
| 161 | ✗ | __attribute__((weak)) void onStartKnockSampling(uint8_t cylinderNumber, float samplingTimeSeconds, uint8_t channelIdx) { | ||
| 162 | UNUSED(cylinderNumber); | |||
| 163 | UNUSED(samplingTimeSeconds); | |||
| 164 | UNUSED(channelIdx); | |||
| 165 | ✗ | } | ||
| 166 | ||||
| 167 | #if EFI_SOFTWARE_KNOCK | |||
| 168 | static uint8_t cylinderNumberCopy; | |||
| 169 | ||||
| 170 | // Called when its time to start listening for knock | |||
| 171 | // Does some math, then hands off to the driver to start any sampling hardware | |||
| 172 | static void startKnockSampling(Engine* p_engine) { | |||
| 173 | // todo: why do we pass engine as parameter? is that for unit tests? | |||
| 174 | if (!p_engine->rpmCalculator.isRunning()) { | |||
| 175 | return; | |||
| 176 | } | |||
| 177 | ||||
| 178 | // Convert sampling angle to time | |||
| 179 | float samplingSeconds = engine->rpmCalculator.oneDegreeUs * engineConfiguration->knockSamplingDuration / US_PER_SECOND_F; | |||
| 180 | ||||
| 181 | // Look up which channel this cylinder uses | |||
| 182 | auto channel = getCylinderKnockBank(cylinderNumberCopy); | |||
| 183 | ||||
| 184 | // Call the driver to begin sampling | |||
| 185 | onStartKnockSampling(cylinderNumberCopy, samplingSeconds, channel); | |||
| 186 | } | |||
| 187 | ||||
| 188 | #endif // EFI_SOFTWARE_KNOCK | |||
| 189 | ||||
| 190 | 6867 | void Engine::onSparkFireKnockSense(uint8_t cylinderNumber, efitick_t nowNt) { | ||
| 191 | #if EFI_SOFTWARE_KNOCK | |||
| 192 | cylinderNumberCopy = cylinderNumber; | |||
| 193 | scheduleByAngle(nullptr, nowNt, | |||
| 194 | /*angle*/engineConfiguration->knockDetectionWindowStart, action_s::make<startKnockSampling>(static_cast<Engine*>(engine))); | |||
| 195 | #else | |||
| 196 | UNUSED(cylinderNumber); | |||
| 197 | UNUSED(nowNt); | |||
| 198 | #endif | |||
| 199 | 6867 | } | ||
| 200 |