rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
long_term_fuel_trim.cpp
Go to the documentation of this file.
1#include "pch.h"
2
3#if EFI_LTFT_CONTROL
4
5#include "storage.h"
6
8
9#include "board_overrides.h"
10
11// +/-25% maximum
12#define MAX_ADJ (0.25f)
13
14#define SAVE_AFTER_HITS 1000
15
16constexpr float integrator_dt = FAST_CALLBACK_PERIOD_MS * 0.001f;
17
18// TODO: store in backup ram and validate on start
20
21// LTFT to VE table custom apply algo
22std::optional<setup_custom_board_overrides_type> custom_board_LtftTrimToVeApply;
23
25#if EFI_PROD_CODE
26 storageWrite(EFI_LTFT_RECORD_ID, (const uint8_t *)trims, sizeof(trims));
27#endif //EFI_PROD_CODE
28}
29
31#if EFI_PROD_CODE
32 if (storageRead(EFI_LTFT_RECORD_ID, (uint8_t *)trims, sizeof(trims)) != StorageStatus::Ok) {
33#else
34 if (1) {
35#endif
36 //Reset to some defaules
37 reset();
38 }
39}
40
41void LtftState::reset() {
42 for (size_t bank = 0; bank < FT_BANK_COUNT; bank++) {
43 setTable(trims[bank], 0.0f);
44 }
45}
46
48 for (size_t bank = 0; bank < FT_BANK_COUNT; bank++) {
49 for (size_t loadIndex = 0; loadIndex < VE_LOAD_COUNT; loadIndex++) {
50 for (size_t rpmIndex = 0; rpmIndex < VE_RPM_COUNT; rpmIndex++) {
51 trims[bank][loadIndex][rpmIndex] = PERCENT_DIV * (loadIndex + rpmIndex * 0.1);
52 }
53 }
54 }
55}
56
58 // if we have custom implementation
60 return;
61 }
62
63 for (size_t loadIndex = 0; loadIndex < VE_LOAD_COUNT; loadIndex++) {
64 for (size_t rpmIndex = 0; rpmIndex < VE_RPM_COUNT; rpmIndex++) {
65 float k = 0;
66
67 /* We have single VE table, but FT_BANK_COUNT banks of trims */
68 for (size_t bank = 0; bank < FT_BANK_COUNT; bank++) {
69 k += 1.0f + trims[bank][loadIndex][rpmIndex];
70 }
71 k = k / FT_BANK_COUNT;
72
73 config->veTable[loadIndex][rpmIndex] = config->veTable[loadIndex][rpmIndex] * k;
74 }
75 }
76}
77
79 m_state = state;
80
81#if EFI_PROD_CODE
82 ltftLoadPending = storageReqestReadID(EFI_LTFT_RECORD_ID);
83#else
84 ltftLoadPending = false;
85 reset();
86#endif
87}
88
89float LongTermFuelTrim::getIntegratorGain(const ltft_s& cfg, ft_region_e region) const
90{
91 return 1 / clampF(1, cfg.timeConstant[region], 3000);
92}
93
94float LongTermFuelTrim::getMaxAdjustment(const ltft_s& cfg) const {
95 // Don't allow maximum less than 0, or more than maximum add adjustment
96 return clampF(0, PERCENT_DIV * cfg.maxAdd, MAX_ADJ);
97}
98
99float LongTermFuelTrim::getMinAdjustment(const ltft_s& cfg) const {
100 // Don't allow minimum more than 0, or less than maximum remove adjustment
101 return clampF(-MAX_ADJ, -PERCENT_DIV * cfg.maxRemove, 0);
102}
103
104void LongTermFuelTrim::learn(ClosedLoopFuelResult clResult, float rpm, float fuelLoad) {
105 const auto& cfg = engineConfiguration->ltft;
106
107 // LTFT uses STFT output, so if STFT is not correcting for some reason - LTFT also should not learn
108 if ((!cfg.enabled) || (ltftSavePending) || (ltftLoadPending) ||
109 (engine->module<ShortTermFuelTrim>()->stftCorrectionState != stftEnabled)) {
110 ltftLearning = false;
111 return;
112 }
113
114 // TODO: should we swap x and y here to keep aligned to wierd TS table definition?
115 // x - load, y - rpm
116 auto x = priv::getClosestBin(fuelLoad, config->veLoadBins);
117 auto y = priv::getClosestBin(rpm, config->veRpmBins);
118
119 // Skip learning if current load point falls far outside the table
120 if ((abs(x.Frac) > 0.5) ||
121 (abs(y.Frac) > 0.5)) {
122 // we are outside table
123 ltftCntMiss++;
124 ltftLearning = false;
125 return;
126 }
127
128 bool adjusted = false;
129
130 // calculate weight depenting on distance from cell center
131 // Is this too heavy?
132 float weight = 1.0 - hypotf(x.Frac, y.Frac) / hypotf(0.5, 0.5);
133 float k = getIntegratorGain(cfg, clResult.region) * integrator_dt * weight;
134
135 for (size_t bank = 0; bank < FT_BANK_COUNT; bank++) {
136 float lambdaCorrection = clResult.banks[bank] - 1.0;
137
138 // If we're within the deadband, make no adjustment.
139 if (std::abs(lambdaCorrection) < PERCENT_DIV * cfg.deadband) {
140 continue;
141 }
142
143 // get current trim
144 float trim = m_state->trims[bank][x.Idx][y.Idx];
145
146 // Integrate
147 float newTrim = trim + k * (lambdaCorrection - trim);
148
149 // TODO:
150 // rise OBD code if we hit trim limit
151
152 // Clamp to bounds and save
153 newTrim = clampF(getMinAdjustment(cfg), newTrim, getMaxAdjustment(cfg));
154
155 // accumulate
156 ltftAccummulatedCorrection[bank] += newTrim - trim;
157
158 // store
159 m_state->trims[bank][x.Idx][y.Idx] = newTrim;
160
161 adjusted = true;
162 }
163
164 ltftLearning = adjusted;
165 if (adjusted) {
166 ltftCntHit++;
167 showUpdateToUser = true;
168 if ((ltftCntHit % SAVE_AFTER_HITS) == 0) {
169 // request save
170#if EFI_PROD_CODE
172#endif
173 }
174 } else {
176 }
177}
178
179ClosedLoopFuelResult LongTermFuelTrim::getTrims(float rpm, float fuelLoad) {
180 const auto& cfg = engineConfiguration->ltft;
181
182 if ((!cfg.correctionEnabled) || (ltftLoadPending)) {
183 for (size_t bank = 0; bank < FT_BANK_COUNT; bank++) {
184 ltftCorrection[bank] = 1.0f;
185 }
186 ltftCorrecting = false;
187 return { };
188 }
189
190 // Keep calculating/applying correction even load point is far outside table
191#if 0
192 // x - load, y - rpm
193 auto x = priv::getClosestBin(fuelLoad, config->veLoadBins);
194 auto y = priv::getClosestBin(rpm, config->veRpmBins);
195
196 // do not interpolate outside table...
197 if ((abs(x.Frac) > 0.5) ||
198 (abs(y.Frac) > 0.5)) {
199 // we are outside table
200 miss++;
201 return { };
202 }
203#endif
204
205 // Is there any reason we should not apply LTFT?
206
207 for (size_t bank = 0; bank < FT_BANK_COUNT; bank++) {
208 ltftCorrection[bank] = 1.0f + interpolate3d(
209 m_state->trims[bank],
210 config->veLoadBins, fuelLoad,
211 config->veRpmBins, rpm
212 );
213 }
214
216 for (size_t bank = 0; bank < FT_BANK_COUNT; bank++) {
217 result.banks[bank] = ltftCorrection[bank];
218 }
219
220 ltftCorrecting = true;
221 return result;
222}
223
224// Called from storage manager thread when requested ID is ready
226 m_state->load();
227
228 ltftLoadPending = false;
229}
230
232 // TODO: lock to avoid modification while writing
233 ltftSavePending = true;
234
235 if (m_state) {
236 m_state->save();
237 }
238
239 // TODO: unlock
240 ltftSavePending = false;
241}
242
244 m_state->reset();
245
246 ltftCntHit = 0;
247 ltftCntMiss = 0;
248 ltftCntDeadband = 0;
249
250 for (size_t bank = 0; bank < FT_BANK_COUNT; bank++) {
251 ltftAccummulatedCorrection[bank] = 0.0;
252 }
253}
254
256 m_state->applyToVe();
257 m_state->reset();
258
259 veNeedRefresh = true;
260}
261
263 if (veNeedRefresh) {
264 veNeedRefresh = false;
265 return true;
266 }
267 return false;
268}
269
271 // rise refresh flag every second for one TS reading of livedata if we have something new...
272 if (ltftPageRefreshFlag) {
273 ltftPageRefreshFlag = false;
274 showUpdateToUser = false;
275 pageRefreshTimer.reset();
276 } else {
277 // was update to table and timeout
278 ltftPageRefreshFlag = showUpdateToUser && pageRefreshTimer.hasElapsedSec(1);
279 }
280}
281
283 m_state->fillRandom();
284}
285
287 // we can wait some time for LTFT to be loaded from storage...
288 if ((ltftLoadPending) &&
289#if EFI_SHAFT_POSITION_INPUT
291#endif
292 (1)) {
293 efiPrintf("LTFT: failed to load calibrations");
294 m_state->reset();
295 ltftLoadPending = false;
296 ltftLoadError = true;
297 }
298 // Do some magic math here?
299
300 /* ... */
301}
302
304 // TODO: We should delay power off until we store LTFT
305 return false;
306}
307
308void initLtft(void)
309{
311}
312
315}
316
318 engine->module<LongTermFuelTrim>()->applyTrimsToVe();
319}
320
321bool ltftNeedVeRefresh() {
322 return engine->module<LongTermFuelTrim>()->isVeUpdated();
323}
324
327}
328
329void *ltftGetTsPage() {
330 return (void *)ltftState.trims;
331}
332
334 return &ltftState;
335}
336
337size_t ltftGetTsPageSize() {
338 return sizeof(ltftState.trims);
339}
340
341#endif // EFI_LTFT_CONTROL
static bool call_board_override(std::optional< FuncType > board_override, Args &&... args)
RpmCalculator rpmCalculator
Definition engine.h:306
constexpr auto & module()
Definition engine.h:200
void onSlowCallback() override
bool needsDelayedShutoff() override
float getMinAdjustment(const ltft_s &cfg) const
void init(LtftState *state)
float getMaxAdjustment(const ltft_s &cfg) const
void learn(ClosedLoopFuelResult clResult, float rpm, float fuelLoad)
ClosedLoopFuelResult getTrims(float rpm, float fuelLoad)
float getIntegratorGain(const ltft_s &cfg, ft_region_e region) const
float getSecondsSinceEngineStart(efitick_t nowNt) const
efitick_t getTimeNowNt()
Definition efitime.cpp:19
static EngineAccessor engine
Definition engine.h:413
static constexpr persistent_config_s * config
static constexpr engine_configuration_s * engineConfiguration
bool settingsLtftRequestWriteToFlash()
constexpr float integrator_dt
std::optional< setup_custom_board_overrides_type > custom_board_LtftTrimToVeApply
static LtftState ltftState
size_t ltftGetTsPageSize()
void * ltftGetTsPage()
bool ltftNeedVeRefresh()
void initLtft()
void applyLongTermFuelTrimToVe()
void resetLongTermFuelTrim()
void devPokeLongTermFuelTrim()
LtftState * ltftGetState()
ft_region_e
state("state", SensorCategory.SENSOR_INPUTS, FieldType.INT8, 1878, 1.0, -1.0, -1.0, "")
ltftCntDeadband("LTFT learning: in deadband", SensorCategory.SENSOR_INPUTS, FieldType.INT, 2024, 1.0, 0.0, 10000.0, "cnt")
ltftCntMiss("LTFT learning: miss", SensorCategory.SENSOR_INPUTS, FieldType.INT, 2020, 1.0, 0.0, 10000.0, "cnt")
ltftCntHit("LTFT learning: hits", SensorCategory.SENSOR_INPUTS, FieldType.INT, 2016, 1.0, 0.0, 10000.0, "cnt")
trim("ETB: trim", SensorCategory.SENSOR_INPUTS, FieldType.INT, 1840, 1.0, -100.0, 100.0, "%")
bool storageReqestReadID(StorageItemId id)
Definition storage.cpp:190
StorageStatus storageRead(StorageItemId id, uint8_t *ptr, size_t size)
Definition storage.cpp:160
StorageStatus storageWrite(StorageItemId id, const uint8_t *ptr, size_t size)
Definition storage.cpp:142
@ EFI_LTFT_RECORD_ID
Definition storage.h:51
float banks[FT_BANK_COUNT]
void applyToVe()
void fillRandom()
void reset()
float trims[FT_BANK_COUNT][VE_LOAD_COUNT][VE_RPM_COUNT]
scaled_channel< uint8_t, 10, 1 > maxRemove
scaled_channel< uint8_t, 10, 1 > deadband
scaled_channel< uint16_t, 1, 1 > timeConstant[STFT_CELL_COUNT]
scaled_channel< uint8_t, 10, 1 > maxAdd
scaled_channel< uint16_t, 10, 1 > veTable[VE_LOAD_COUNT][VE_RPM_COUNT]
constexpr void setTable(TElement(&dest)[N][M], const VElement value)