#include "fuel_learning.h" #include FuelLearningTable::FuelLearningTable() : learningRate(0.01), maxCorrection(1.2), minCorrection(0.8) { // Инициализация точек нагрузки (кПа) for (int i = 0; i < LOAD_POINTS; i++) { loadPoints[i] = 20.0 + (i * 6.0); // 20-110 кПа } // Инициализация точек оборотов for (int i = 0; i < RPM_POINTS; i++) { rpmPoints[i] = 800.0 + (i * 400.0); // 800-6800 RPM } // Инициализация ячеек коррекции reset(); } void FuelLearningTable::begin() { load(); // Загрузка сохраненных данных } void FuelLearningTable::reset() { for (int i = 0; i < LOAD_POINTS; i++) { for (int j = 0; j < RPM_POINTS; j++) { cells[i][j] = 1.0; // Нейтральная коррекция } } } int FuelLearningTable::findNearestIndex(float value, const float* points, int size) { if (value <= points[0]) return 0; if (value >= points[size-1]) return size - 2; for (int i = 0; i < size - 1; i++) { if (value >= points[i] && value < points[i+1]) { return i; } } return 0; } float FuelLearningTable::interpolate2D(float load, float rpm) { // Находим ближайшие индексы int loadIdx = findNearestIndex(load, loadPoints, LOAD_POINTS); int rpmIdx = findNearestIndex(rpm, rpmPoints, RPM_POINTS); // Находим веса для интерполяции float loadWeight = (load - loadPoints[loadIdx]) / (loadPoints[loadIdx+1] - loadPoints[loadIdx]); float rpmWeight = (rpm - rpmPoints[rpmIdx]) / (rpmPoints[rpmIdx+1] - rpmPoints[rpmIdx]); // Билинейная интерполяция float c00 = cells[loadIdx][rpmIdx]; float c10 = cells[loadIdx+1][rpmIdx]; float c01 = cells[loadIdx][rpmIdx+1]; float c11 = cells[loadIdx+1][rpmIdx+1]; float c0 = c00 * (1 - loadWeight) + c10 * loadWeight; float c1 = c01 * (1 - loadWeight) + c11 * loadWeight; return c0 * (1 - rpmWeight) + c1 * rpmWeight; } void FuelLearningTable::updateCell(int loadIdx, int rpmIdx, float correction) { // Применяем коррекцию с учетом скорости обучения cells[loadIdx][rpmIdx] += correction * learningRate; // Ограничиваем значение коррекции cells[loadIdx][rpmIdx] = constrain(cells[loadIdx][rpmIdx], minCorrection, maxCorrection); } float FuelLearningTable::getCorrection(float load, float rpm) { return interpolate2D(load, rpm); } void FuelLearningTable::learn(float load, float rpm, float lambdaError) { // Находим ближайшие ячейки int loadIdx = findNearestIndex(load, loadPoints, LOAD_POINTS); int rpmIdx = findNearestIndex(rpm, rpmPoints, RPM_POINTS); // Обновляем основную ячейку updateCell(loadIdx, rpmIdx, lambdaError); // Обновляем соседние ячейки с меньшим весом if (loadIdx > 0) { updateCell(loadIdx-1, rpmIdx, lambdaError * 0.5); } if (loadIdx < LOAD_POINTS-1) { updateCell(loadIdx+1, rpmIdx, lambdaError * 0.5); } if (rpmIdx > 0) { updateCell(loadIdx, rpmIdx-1, lambdaError * 0.5); } if (rpmIdx < RPM_POINTS-1) { updateCell(loadIdx, rpmIdx+1, lambdaError * 0.5); } } bool FuelLearningTable::save() { Preferences prefs; prefs.begin("fuel_learn", false); // Сохраняем таблицу коррекции size_t written = prefs.putBytes("cells", cells, sizeof(float) * LOAD_POINTS * RPM_POINTS); prefs.end(); return written == sizeof(float) * LOAD_POINTS * RPM_POINTS; } bool FuelLearningTable::load() { Preferences prefs; prefs.begin("fuel_learn", true); // Загружаем таблицу коррекции size_t readSize = prefs.getBytes("cells", cells, sizeof(float) * LOAD_POINTS * RPM_POINTS); prefs.end(); return readSize == sizeof(float) * LOAD_POINTS * RPM_POINTS; }