GCC Code Coverage Report


Directory: ./
File: firmware/controllers/engine_cycle/knock_controller.cpp
Date: 2025-10-03 00:57:22
Coverage Exec Excl Total
Lines: 64.0% 57 0 89
Functions: 70.0% 7 0 10
Branches: 38.1% 8 0 21
Decisions: 38.1% 8 - 21

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