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 | 1097 | float KnockControllerBase::getKnockRetard() const { | ||
87 | 1097 | return m_knockRetard; | ||
88 | } | |||
89 | ||||
90 | ✗ | uint32_t KnockControllerBase::getKnockCount() const { | ||
91 | ✗ | return m_knockCount; | ||
92 | } | |||
93 | ||||
94 | 4521 | float KnockControllerBase::getFuelTrimMultiplier() const { | ||
95 | 4521 | return 1.0 + m_knockFuelTrimMultiplier; | ||
96 | } | |||
97 | ||||
98 | 4144 | void KnockControllerBase::onFastCallback() { | ||
99 | 4144 | m_knockThreshold = getKnockThreshold(); | ||
100 | 4144 | m_maximumRetard = getMaximumRetard(); | ||
101 | ||||
102 | 4144 | constexpr auto callbackPeriodSeconds = FAST_CALLBACK_PERIOD_MS / 1000.0f; | ||
103 | ||||
104 | 4144 | auto applyRetardAmount = engineConfiguration->knockRetardReapplyRate * callbackPeriodSeconds; | ||
105 | 4144 | auto applyFuelAmount = engineConfiguration->knockFuelTrimReapplyRate * 0.01f * callbackPeriodSeconds; | ||
106 | ||||
107 | // disable knock suppression then deceleration | |||
108 | 4144 | 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 1121 times.
✓ Branch 2 taken 3023 times.
|
2/2✓ Decision 'true' taken 1121 times.
✓ Decision 'false' taken 3023 times.
|
4144 | if(TPSValue < engineConfiguration->knockSuppressMinTps) { |
116 | 1121 | m_knockRetard = 0.0; | ||
117 | 1121 | m_knockFuelTrimMultiplier = 0.0; | ||
118 | 1121 | 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 | 1120 | float KnockController::getKnockThreshold() const { | ||
144 | 1120 | return interpolate2d( | ||
145 | Sensor::getOrZero(SensorType::Rpm), | |||
146 | 1120 | config->knockNoiseRpmBins, | ||
147 | 1120 | config->knockBaseNoise | ||
148 | 1120 | ); | ||
149 | } | |||
150 | ||||
151 | 1120 | float KnockController::getMaximumRetard() const { | ||
152 | return | |||
153 | 1120 | interpolate3d( | ||
154 | 1120 | config->maxKnockRetardTable, | ||
155 | 1120 | config->maxKnockRetardLoadBins, getIgnitionLoad(), | ||
156 | 1120 | config->maxKnockRetardRpmBins, Sensor::getOrZero(SensorType::Rpm) | ||
157 | 1120 | ); | ||
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 | 6211 | 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 | 6211 | } | ||
200 |