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] = 0.01 * (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 two 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
90{
91 const auto& cfg = engineConfiguration->ltft;
92
93 return 1 / clampF(30, cfg.timeConstant, 3000);
94}
95
97 const auto& cfg = engineConfiguration->ltft;
98
99 float raw = 0.01 * cfg.maxAdd;
100 // Don't allow maximum less than 0, or more than maximum add adjustment
101 return clampF(0, raw, MAX_ADJ);
102}
103
105 const auto& cfg = engineConfiguration->ltft;
106
107 float raw = -0.01f * cfg.maxRemove;
108 // Don't allow minimum more than 0, or less than maximum remove adjustment
109 return clampF(-MAX_ADJ, raw, 0);
110}
111
112void LongTermFuelTrim::learn(ClosedLoopFuelResult clResult, float rpm, float fuelLoad) {
113 const auto& cfg = engineConfiguration->ltft;
114
115 if ((!cfg.enabled) || (ltftSavePending) || (ltftLoadPending)) {
116 ltftLearning = false;
117 return;
118 }
119
120 // TODO: should we swap x and y here to keep aligned to wierd TS table definition?
121 // x - load, y - rpm
122 auto x = priv::getClosestBin(fuelLoad, config->veLoadBins);
123 auto y = priv::getClosestBin(rpm, config->veRpmBins);
124
125 // Skip learning if current load point falls far outside the table
126 if ((abs(x.Frac) > 0.5) ||
127 (abs(y.Frac) > 0.5)) {
128 // we are outside table
129 ltftCntMiss++;
130 ltftLearning = false;
131 return;
132 }
133
134 bool adjusted = false;
135
136 // calculate weight depenting on distance from cell center
137 // Is this too heavy?
138 float weight = 1.0 - hypotf(x.Frac, y.Frac) / hypotf(0.5, 0.5);
139 float k = getIntegratorGain() * integrator_dt * weight;
140
141 for (size_t bank = 0; bank < FT_BANK_COUNT; bank++) {
142 float lambdaCorrection = clResult.banks[bank] - 1.0;
143
144 // If we're within the deadband, make no adjustment.
145 if (std::abs(lambdaCorrection) < 0.01f * cfg.deadband) {
146 continue;
147 }
148
149 // get current trim
150 float trim = m_state->trims[bank][x.Idx][y.Idx];
151
152 // Integrate
153 float newTrim = trim + k * (lambdaCorrection - trim);
154
155 // TODO:
156 // rise OBD code if we hit trim limit
157
158 // Clamp to bounds and save
159 newTrim = clampF(getMinAdjustment(), newTrim, getMaxAdjustment());
160
161 // accumulate
162 ltftAccummulatedCorrection[bank] += newTrim - trim;
163
164 // store
165 m_state->trims[bank][x.Idx][y.Idx] = newTrim;
166
167 adjusted = true;
168 }
169
170 ltftLearning = adjusted;
171 if (adjusted) {
172 ltftCntHit++;
173 showUpdateToUser = true;
174 if ((ltftCntHit % SAVE_AFTER_HITS) == 0) {
175 // request save
176#if EFI_PROD_CODE
178#endif
179 }
180 } else {
182 }
183}
184
185ClosedLoopFuelResult LongTermFuelTrim::getTrims(float rpm, float fuelLoad) {
186 const auto& cfg = engineConfiguration->ltft;
187
188 if ((!cfg.correctionEnabled) || (ltftLoadPending)) {
189 for (size_t bank = 0; bank < FT_BANK_COUNT; bank++) {
190 ltftCorrection[bank] = 1.0f;
191 }
192 ltftCorrecting = false;
193 return { };
194 }
195
196 // Keep calculating/applying correction even load point is far outside table
197#if 0
198 // x - load, y - rpm
199 auto x = priv::getClosestBin(fuelLoad, config->veLoadBins);
200 auto y = priv::getClosestBin(rpm, config->veRpmBins);
201
202 // do not interpolate outside table...
203 if ((abs(x.Frac) > 0.5) ||
204 (abs(y.Frac) > 0.5)) {
205 // we are outside table
206 miss++;
207 return { };
208 }
209#endif
210
211 // Is there any reason we should not apply LTFT?
212
213 for (size_t bank = 0; bank < FT_BANK_COUNT; bank++) {
214 ltftCorrection[bank] = 1.0f + interpolate3d(
215 m_state->trims[bank],
216 config->veLoadBins, fuelLoad,
217 config->veRpmBins, rpm
218 );
219 }
220
222 for (size_t bank = 0; bank < FT_BANK_COUNT; bank++) {
223 result.banks[bank] = ltftCorrection[bank];
224 }
225
226 ltftCorrecting = true;
227 return result;
228}
229
230// Called from storage manager thread when requested ID is ready
232 m_state->load();
233
234 ltftLoadPending = false;
235}
236
238 // TODO: lock to avoid modification while writing
239 ltftSavePending = true;
240
241 if (m_state) {
242 m_state->save();
243 }
244
245 // TODO: unlock
246 ltftSavePending = false;
247}
248
250 m_state->reset();
251
252 ltftCntHit = 0;
253 ltftCntMiss = 0;
254 ltftCntDeadband = 0;
255
256 for (size_t bank = 0; bank < FT_BANK_COUNT; bank++) {
257 ltftAccummulatedCorrection[bank] = 0.0;
258 }
259}
260
262 m_state->applyToVe();
263 m_state->reset();
264
265 veNeedRefresh = true;
266}
267
269 if (veNeedRefresh) {
270 veNeedRefresh = false;
271 return true;
272 }
273 return false;
274}
275
277 // rise refresh flag every second for one TS reading of livedata if we have something new...
278 if (ltftPageRefreshFlag) {
279 ltftPageRefreshFlag = false;
280 showUpdateToUser = false;
281 pageRefreshTimer.reset();
282 } else {
283 // was update to table and timeout
284 ltftPageRefreshFlag = showUpdateToUser && pageRefreshTimer.hasElapsedSec(1);
285 }
286}
287
289 m_state->fillRandom();
290}
291
293 // we can wait some time for LTFT to be loaded from storage...
294 if ((ltftLoadPending) &&
295#if EFI_SHAFT_POSITION_INPUT
297#endif
298 (1)) {
299 efiPrintf("LTFT: failed to load calibrations");
300 m_state->reset();
301 ltftLoadPending = false;
302 ltftLoadError = true;
303 }
304 // Do some magic math here?
305
306 /* ... */
307}
308
310 // TODO: We should delay power off until we store LTFT
311 return false;
312}
313
314void initLtft(void)
315{
317}
318
321}
322
324 engine->module<LongTermFuelTrim>()->applyTrimsToVe();
325}
326
327bool ltftNeedVeRefresh() {
328 return engine->module<LongTermFuelTrim>()->isVeUpdated();
329}
330
333}
334
335void *ltftGetTsPage() {
336 return (void *)ltftState.trims;
337}
338
340 return &ltftState;
341}
342
343size_t ltftGetTsPageSize() {
344 return sizeof(ltftState.trims);
345}
346
347#endif // EFI_LTFT_CONTROL
static bool call_board_override(std::optional< setup_custom_board_overrides_type > board_override)
RpmCalculator rpmCalculator
Definition engine.h:306
constexpr auto & module()
Definition engine.h:200
void onSlowCallback() override
bool needsDelayedShutoff() override
void init(LtftState *state)
float getMinAdjustment() const
void learn(ClosedLoopFuelResult clResult, float rpm, float fuelLoad)
float getIntegratorGain() const
ClosedLoopFuelResult getTrims(float rpm, float fuelLoad)
float getMaxAdjustment() 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()
trim("ETB: trim", SensorCategory.SENSOR_INPUTS, FieldType.INT, 1820, 1.0, -1.0, -1.0, "")
ltftCntDeadband("LTFT learning: in deadband", SensorCategory.SENSOR_INPUTS, FieldType.INT, 2016, 1.0, 0.0, 10000.0, "cnt")
ltftCntMiss("LTFT learning: miss", SensorCategory.SENSOR_INPUTS, FieldType.INT, 2012, 1.0, 0.0, 10000.0, "cnt")
ltftCntHit("LTFT learning: hits", SensorCategory.SENSOR_INPUTS, FieldType.INT, 2008, 1.0, 0.0, 10000.0, "cnt")
state("state", SensorCategory.SENSOR_INPUTS, FieldType.INT8, 1871, 1.0, -1.0, -1.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 > maxAdd
scaled_channel< uint16_t, 10, 1 > veTable[VE_LOAD_COUNT][VE_RPM_COUNT]
constexpr void setTable(TElement(&dest)[N][M], const VElement value)