Перейти к содержанию

Memory Map

Три типа энергонезависимой памяти: внутренний Flash ESP32, NVS-раздел внутри Flash, и внешний I2C-EEPROM M24C16.


1. Flash (ESP32, 4 МБ)

Раскладка определяется partitions.csv в корне проекта:

# Name,   Type, SubType,  Offset,    Size,      Flags
nvs,      data, nvs,      0x09000,   0x05000,           #  20 КБ — Preferences
otadata,  data, ota,      0x0E000,   0x02000,           #   8 КБ — OTA selector
app0,     app,  ota_0,    0x10000,   0x1C0000,          # 1.75 МБ — slot A
app1,     app,  ota_1,    —,         0x1C0000,          # 1.75 МБ — slot B
spiffs,   data, spiffs,   —,         0x02000,           #   8 КБ — резерв
coredump, data, coredump, —,         0x10000,           #  64 КБ — дамп при panic
Раздел Назначение
nvs Все настройки и состояние (см. nvs-reference.md)
otadata Селектор активного app-раздела для bootloader
app0 OTA slot A. Используется на чётных обновлениях
app1 OTA slot B. Используется на нечётных обновлениях
spiffs Не используется на запись. Web-UI зашит в PROGMEM (app)
coredump Core dump при panic/TWDT — backtrace, стеки задач. Читается через /api/diag/coredump

Изменение партиционной таблицы (добавление coredump) применяется только при прошивке по кабелю — OTA таблицу не переписывает. Offset'ы app0/app1/nvs не сдвинуты — данные при этом не теряются.

Web-UI (HTML/CSS/JS) встроен напрямую в текст прошивки через PROGMEM-заголовки include/*_gz.h, а не лежит в spiffs. Это упрощает OTA: один бинарь обновляет и код, и UI атомарно.

ОТА-pattern: 1. Загружен app0app1 свободен. 2. POST /api/ota/upload пишет байты в app1. 3. POST /api/ota/apply ставит otadata на app1 и esp_restart(). 4. Если новый образ не загрузился (boot-validation) — rollback на app0.

См. admin/ota-update.md.


2. NVS (20 КБ внутри Flash)

NVS — key-value store ESP-IDF, разбит на namespaces (≈ "таблицы"). Размер раздела 0x5000 = 20480 байт включает оверхеды.

Использование на устройстве можно прочитать через:

GET /api/diag/system  →  поле "nvs": { used_entries, free_entries, total }

Все ключи каждого namespace и дефолты — в nvs-reference.md.

Размер записи (грубо): - Ключ ≤ 15 символов - Запись = 32 байта overhead + значение (uint = 4 байта, string округляется до 32 байт) - 20 КБ ≈ 500 записей — с большим запасом для текущей схемы (~60 ключей)

Износ: NVS использует wear-leveling внутри своего раздела. Частые записи (например, OTA-прогресс в namespace ota) распределяются по флешу. Расчётный ресурс — ≥10⁵ циклов на ячейку, реально >10⁶.


3. EEPROM M24C16 (внешний, I2C)

Параметр Значение
Чип M24C16 / AT24C16
Объём 2048 байт (2 КБ)
Размер страницы 16 байт
Количество страниц 128
I2C-адрес 0x50
ID-страница (ST) 0x58 (только оригиналы; клоны не отвечают)
Шина SDA/SCL (см. include/config.h)
Ресурс на страницу ≥1 млн циклов записи

См. ADR-007 для деталей определения чипа и self-test'а.

Раскладка наработки дозаторов (схема v3)

Чип делится на две зоны. EXPECTED_EEPROM_SCHEMA = 3.

0x000  ┌──────────────────────────────┐
       │  Zone A — кольцо абсолютной   │  60 слотов × 32 байта
       │  наработки (1920 байт)        │  адрес слота i = i × 32
0x780  ├──────────────────────────────┤
       │  Zone B — baseline + дата ТО  │  2 слота × 64 байта (пинг-понг)
0x800  └──────────────────────────────┘  (end)

Абсолютная наработка хранится в Zone A, относительная (с последнего ТО) вычисляется как absolute − baseline, где baseline — снимок из Zone B.

Zone A — кольцо абсолютных счётчиков (32 байта/слот):

struct __attribute__((packed)) EepromSlotA {
    uint8_t  seq;            // [0]      монотонный счётчик 0→255→0 (RFC1982)
    uint8_t  version;        // [1]      = EXPECTED_EEPROM_SCHEMA (=3)
    uint32_t abs_runtime[3]; // [2..13]  RUN-наработка каналов 0..2, сек, LE
    uint32_t abs_pump[3];    // [14..25] время насоса каналов 0..2, сек, LE
    uint8_t  reserved[4];    // [26..29] резерв
    uint16_t crc16;          // [30..31] CRC16-IBM байт [0..29], LE
};

Zone B — baseline последнего ТО (64 байта/слот):

struct __attribute__((packed)) EepromSlotB {
    uint8_t  seq;             // [0]      монотонный счётчик
    uint8_t  version;         // [1]      = EXPECTED_EEPROM_SCHEMA (=3)
    uint32_t base_runtime[3]; // [2..13]  снимок abs_runtime на момент ТО
    uint32_t base_pump[3];    // [14..25] снимок abs_pump на момент ТО
    uint32_t reset_ts[3];     // [26..37] local epoch последнего ТО, 0=неизв.
    uint8_t  reserved[24];    // [38..61] резерв
    uint16_t crc16;           // [62..63] CRC16-IBM байт [0..61], LE
};

Boot scan: eepromCountersInit() сканирует Zone A (head = валидный CRC и максимальный seq по RFC 1982) и Zone B (пинг-понг из 2 слотов). Если в Zone A нет слотов version==3, но есть version==2 — выполняется миграция: старый счётчик становится абсолютным, baseline инициализируется нулём.

Запись Zone A: новый слот = (head + 1) % 60, flush раз в 60 с при активной работе и при DOSING→IDLE. При ресурсе 1 млн циклов и записи раз в минуту каждая страница перезаписывается раз в час → ресурс — десятилетия.

Запись Zone B: только по команде «Сброс на ТО» (POST /api/statistics/ reset). base ← текущий абсолют, reset_ts ← local epoch. Запись редкая, износ неактуален; 2 слота дают защиту от рваной записи при сбое питания.

Сброс абсолютной наработки возможен только полным форматированием чипа (POST /api/diag/eeprom/format, защищён паролём администратора).

Текущая сессия живёт в RAM (DosatorCounters.session_s / session_pump_s) и прибавляется к абсолюту при DOSING→IDLE.

См. src/eeprom_counters.cpp и include/eeprom_counters.h.


4. RAM (внутренняя SRAM ESP32, ≈ 320 КБ)

Использование статической RAM известно после сборки: pio run -e esp32dev показывает RAM: 19.6% (~62 КБ).

Динамическая heap'a в норме 100…200 КБ свободно. Тонкие места:

  • mbedTLS handshake (telegram/IMAP) — пик до ~40 КБ на коннект.
  • AsyncTCP очередь — буферы по 1.5 КБ на соединение.
  • AsyncWebServer response для больших JSON (например, /api/diag/system) выделяет копию строки целиком в heap.

Текущее свободное heap можно посмотреть в GET /api/diag/system:

{ "heap": { "free": 142000, "min_free": 95000, "largest_block": 90000, "total": 327680 } }