rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
closed_loop_idle.cpp
Go to the documentation of this file.
1#include "pch.h"
2#include "closed_loop_idle.h"
3#include "engine_math.h"
4#include "efitime.h"
5#include "engine.h"
6#include <rusefi/rusefi_math.h>
7
8// LTIT_TABLE_SIZE is defined in the header file
9
10#if EFI_IDLE_CONTROL
11
14 emaError = 0.0f; //TODO: unused?
16 m_pendingSave = false;
17}
18
20 // Initialize with 100% (1.0 multiplier) as default
21 for (int i = 0; i < LTIT_TABLE_SIZE; i++) {
22 ltitTableHelper[i] = 100.0f;
23 }
24}
25
26//TODO: move? add? to validateConfigOnStartUpOrBurn
28 // More robust validation - check for reasonable range and distribution
29 int validCount = 0;
30 float totalValue = 0.0f;
31
32 for (int i = 0; i < LTIT_TABLE_SIZE; i++) {
33 float value = static_cast<float>(config->ltitTable[i]);
34
35 // Check if value is in reasonable range (50% to 150%)
36 if (value >= 50.0f && value <= 150.0f) {
37 validCount++;
38 totalValue += value;
39 }
40 }
41
42 // Require at least half the table to be valid and reasonable average
43 if (validCount < (LTIT_TABLE_SIZE / 2)) {
44 return false;
45 }
46
47 float avgValue = totalValue / validCount;
48 return (avgValue >= 80.0f && avgValue <= 120.0f); // Reasonable average range
49}
50
52 if (hasValidData()) {
53 // Convert autoscaled uint16_t to float
54 for (int i = 0; i < LTIT_TABLE_SIZE; i++) {
55 ltitTableHelper[i] = static_cast<float>(config->ltitTable[i]);
56 }
57
59 } else {
60 //TODO: this is part of setDefaultEngineConfiguration?
61 // Initialize with defaults if no valid data
64 }
65}
66
67float LongTermIdleTrim::getLtitFactor(float rpm, float clt) const {
68 //TODO: rpm unused?
69 UNUSED(rpm);
70
72 return 1.0f; // No correction if not initialized
73 }
74
75 // Use 2D interpolation based only on CLT (temperature)
76 return interpolate2d(clt, config->cltIdleCorrBins, ltitTableHelper) * 0.01f;
77}
78
79bool LongTermIdleTrim::isValidConditionsForLearning(float idleIntegral) const {
80 float minThreshold = engineConfiguration->ltitIntegratorThreshold;
81 if (std::abs(idleIntegral) < minThreshold) {
82 return false; // Integrator too low - PID not working hard enough
83 }
84
85 // Upper limit to avoid extreme conditions
86 if (std::abs(idleIntegral) > 25.0f) {
87 return false; // Integrator too high - unstable conditions
88 }
89
90 // Check if enough time has passed since ignition on
92 return false;
93 }
94
95 // Must be in stable idle
96 if (!isStableIdle) {
97 return false;
98 }
99
100 return true;
101}
102
103void LongTermIdleTrim::update(float rpm, float clt, bool acActive, bool fan1Active, bool fan2Active, float idleIntegral) {
104 //TODO: acActive unused, fan1Active, fan2Active, check comment about isValidConditionsForLearning
105 UNUSED(acActive); UNUSED(fan1Active); UNUSED(fan2Active);
106
108 return;
109 }
110
111 // Try to load data periodically until successful
114 return;
115 }
116
117 // Check ignition delay
119 m_stableIdleTimer.reset();
120 isStableIdle = false;
121 return;
122 }
123
124 auto& idleController = engine->engineModules.get<IdleController>();
125 auto currentPhase = idleController->getCurrentPhase();
126
127 // LTIT should only learn during Phase::Idling
128 if (currentPhase != IIdleController::Phase::Idling) {
129 m_stableIdleTimer.reset();
130 isStableIdle = false;
131 return;
132 }
133
134 // Check if we're in idle RPM range
135 float targetRpm = idleController->getTargetRpm(clt).ClosedLoopTarget;
136 float rpmDelta = std::abs(rpm - targetRpm);
137 bool isIdleRpm = rpmDelta < engineConfiguration->ltitStableRpmThreshold;
138
139 // Check stability
140 if (!isIdleRpm) {
141 m_stableIdleTimer.reset();
142 isStableIdle = false;
143 return;
144 }
145
146 // Check if stable for minimum time
147 // TODO: set ltitStableTime default value? also move to autoscale 0.1 like other configs with seconds?
149 isStableIdle = true;
150 }
151
152 if (!isStableIdle) {
153 return;
154 }
155
156 if (!idleController->isIdleClosedLoop) {
157 return;
158 }
159
160 // Main table learning (now allowed even with AC/Fan active)
161 // Validate conditions
162 if (!isValidConditionsForLearning(idleIntegral)) {
163 return;
164 }
165
166 // Check minimum update interval (fixed slowCallback period: 50ms)
167 if (!m_updateTimer.hasElapsedSec(1.0f)) {
168 return;
169 }
170 m_updateTimer.reset();
171
172 //TODO: docs?, weird use of non-public interpolation.h, we are trying to the get bin index for X temp?
173 // also ltitTableHelper scale will depend on clt idle bins?
174 // Use proper bin finding with getBin function for CLT only
175 auto cltBin = priv::getBin(clt, config->cltIdleCorrBins);
176
177 // Apply correction rate in %/s (percentage per second)
178 // Using fixed slowCallback delta time (50ms = 0.05s) for consistent behavior
179 const float deltaTime = 0.05f; // SLOW_CALLBACK_PERIOD_MS / 1000.0f
180 float correctionPerSecond = idleIntegral * engineConfiguration->ltitCorrectionRate * 0.01f; // Convert % to decimal
181 float correction = correctionPerSecond * deltaTime; // Apply time-based correction
182 float alpha = engineConfiguration->ltitEmaAlpha / 255.0f;
183
184 // Primary cell (largest weight)
185 float newValue = ltitTableHelper[cltBin.Idx] * (1.0f + correction);
186 newValue = alpha * newValue + (1.0f - alpha) * ltitTableHelper[cltBin.Idx];
187
188 // Apply clamping
190 float clampMax = engineConfiguration->ltitClampMax > 0 ? engineConfiguration->ltitClampMax : 250.0f;
191 ltitTableHelper[cltBin.Idx] = clampF(clampMin, newValue, clampMax);
192
193 // Apply to adjacent cells with reduced weight (for better interpolation)
194 float adjWeight = 0.3f; // 30% weight for adjacent cells
195 for (int di = -1; di <= 1; di++) {
196 if (di == 0) continue; // Skip primary cell
197
198 int adjI = cltBin.Idx + di;
199
200 if (adjI >= 0 && adjI < LTIT_TABLE_SIZE) {
201 float adjCorrection = correction * adjWeight;
202 float adjNewValue = ltitTableHelper[adjI] * (1.0f + adjCorrection);
203 adjNewValue = alpha * adjNewValue + (1.0f - alpha) * ltitTableHelper[adjI];
204 ltitTableHelper[adjI] = clampF(clampMin, adjNewValue, clampMax);
205 }
206 }
207
208 updatedLtit = true;
209}
210
212 m_ignitionState = ignitionOn;
213
214 if (ignitionOn) {
215 // Reset timers when ignition turns on
216 m_updateTimer.reset();
217 m_stableIdleTimer.reset();
218 isStableIdle = false;
219 m_pendingSave = false;
220 } else if (updatedLtit) {
221 // Schedule save after ignition off
222 // TODO: maybe move all this to EngineModule & use needsDelayedShutoff?
223 m_pendingSave = true;
224 m_ignitionOffTimer.reset();
225 updatedLtit = false;
226 }
227}
228
230 // Handle delayed save after ignition off
232 float saveDelaySeconds = engineConfiguration->ltitIgnitionOffSaveDelay;
233 if (saveDelaySeconds <= 0) {
234 //TODO: this is part of setDefaultEngineConfiguration?
235 saveDelaySeconds = 5.0f; // Default 5 seconds
236 }
237
238 if (m_ignitionOffTimer.hasElapsedSec(saveDelaySeconds)) {
239 // TODO: copyArray?
240 // Save to flash memory
241 for (int i = 0; i < LTIT_TABLE_SIZE; i++) {
242 // Convert float to autoscaled uint16_t
243 config->ltitTable[i] = static_cast<uint16_t>(ltitTableHelper[i]);
244 }
245
246#if EFI_PROD_CODE
247 //TODO: we need to use requestBurn here?
249#endif // EFI_PROD_CODE
250 m_pendingSave = false;
251 }
252 }
253}
254
255//TODO: unused?
257 if (!engineConfiguration->ltitEnabled || intensity <= 0.0f || intensity > 100.0f) {
258 return; // Invalid intensity or LTIT disabled
259 }
260
261 // Normalize intensity to 0.0-1.0 range
262 float normalizedIntensity = intensity / 100.0f;
263
264 // Apply 1D smoothing for the temperature-based curve
265 float temp[LTIT_TABLE_SIZE];
266
267 for (int i = 0; i < LTIT_TABLE_SIZE; i++) {
268 float sum = ltitTableHelper[i];
269 int count = 1;
270
271 // Add values from adjacent cells if they exist
272 if (i > 0) {
273 sum += ltitTableHelper[i-1];
274 count++;
275 }
276 if (i < LTIT_TABLE_SIZE-1) {
277 sum += ltitTableHelper[i+1];
278 count++;
279 }
280
281 // Calculate the average of the cell and its neighbors
282 float avg = sum / count;
283
284 // Apply weighted average based on intensity
285 temp[i] = ltitTableHelper[i] * (1.0f - normalizedIntensity) + avg * normalizedIntensity;
286 }
287
288 // Copy back the smoothed values
289 for (int i = 0; i < LTIT_TABLE_SIZE; i++) {
290 ltitTableHelper[i] = temp[i];
291 }
292
293 // Mark for saving
294 m_pendingSave = true;
295}
296
297#endif // EFI_IDLE_CONTROL
type_list< Mockable< InjectorModelPrimary >, Mockable< InjectorModelSecondary >,#if EFI_IDLE_CONTROL Mockable< IdleController >,#endif TriggerScheduler,#if EFI_HPFP &&EFI_ENGINE_CONTROL Mockable< HpfpController >,#endif #if EFI_ENGINE_CONTROL Mockable< ThrottleModel >,#endif #if EFI_ALTERNATOR_CONTROL AlternatorController,#endif MainRelayController, Mockable< IgnitionController >, Mockable< AcController >, PrimeController, DfcoController,#if EFI_HD_ACR HarleyAcr,#endif Mockable< WallFuelController >, KnockController, SensorChecker,#if EFI_ENGINE_CONTROL Mockable< LimpManager >,#endif #if EFI_VVT_PID VvtController1, VvtController2, VvtController3, VvtController4,#endif #if EFI_BOOST_CONTROL BoostController,#endif TpsAccelEnrichment,#if EFI_LAUNCH_CONTROL NitrousController,#endif #if EFI_LTFT_CONTROL LongTermFuelTrim,#endif ShortTermFuelTrim,#include "modules_list_generated.h" EngineModule > engineModules
Definition engine.h:194
Phase getCurrentPhase() const override
Definition idle_thread.h:96
float ltitTableHelper[LTIT_TABLE_SIZE]
float getLtitFactor(float rpm, float clt) const
virtual void loadLtitFromConfig()
bool hasValidData() const
void onIgnitionStateChanged(bool ignitionOn)
void update(float rpm, float clt, bool acActive, bool fan1Active, bool fan2Active, float idleIntegral)
void smoothLtitTable(float intensity)
bool isValidConditionsForLearning(float idleIntegral) const
constexpr int LTIT_TABLE_SIZE
static EngineAccessor engine
Definition engine.h:413
static constexpr persistent_config_s * config
static constexpr engine_configuration_s * engineConfiguration
void setNeedToWriteConfiguration()
static CCM_OPTIONAL FunctionalSensor clt(SensorType::Clt, MS2NT(10))
UNUSED(samplingTimeSeconds)
scaled_channel< uint16_t, 10, 1 > ltitTable[CLT_IDLE_TABLE_CLT_SIZE]
uint16_t count
Definition tunerstudio.h:1