Программирование · статья
Память Arduino: что значит «Глобальные переменные используют … байт»
После каждой сборки среда пишет две строки про байты — «Скетч использует … байт» и «Глобальные переменные используют … байт динамической памяти, оставляя … для локальных переменных» — и почти никто их не читает, пока плата не начнёт перезагружаться сама по себе. Разбираем, что это за числа, чем Flash отличается от SRAM и EEPROM, почему «скетч использует 0 байт», что делать, когда программа не помещается, и как освободить память — с измерениями на настоящей Arduino Uno.
Две строки после сборки, которые все пролистывают
Нажмите «Проверить» — и после компиляции в консоли появится отчёт о памяти. В Arduino IDE это две фразы по-русски, в Trema IDE — две строки с полосками. Говорят они об одном и том же, только разными словами:
Первая строка — про Flash, память программ. «Скетч использует 1556 байт памяти устройства» значит: прошивка, которую сейчас запишут в плату, весит 1556 байт. «Всего доступно 32256 байт» — предел для этой платы. Проценты среда округляет вниз, поэтому 4,8 % показаны как 4 %.
Вторая строка — про SRAM, оперативную память. «Глобальные переменные используют 270 байт динамической памяти» — столько занято ещё до того, как выполнится первая команда setup(). «Оставляя 1778 байт для локальных переменных» — столько остаётся на саму работу программы. «Максимум: 2048 байт» — весь объём оперативной памяти Uno.
Trema IDE пишет то же самое короче: RAM: used 270 bytes from 2048 и Flash: used 1556 bytes from 32256, а полоска [= ] показывает заполненность десятками процентов — по ней видно проблему, не читая цифр.
Откуда берутся числа: после компоновки прошивки запускается утилита avr-size, которая измеряет секции готового файла .elf. Именно поэтому строки появляются в самом конце и только при успешной сборке.
Три памяти в одной плате: Flash, SRAM и EEPROM
У микроконтроллера Arduino не одна память, а три, и у каждой своя работа. Путаница начинается, когда «32 КБ памяти» из характеристик платы принимают за место под переменные — а это Flash, и к переменным она отношения не имеет.
Сюда записывается прошивка: ваш код, ядро Arduino, библиотеки и то, что вы спрятали в PROGMEM или в F("…").
- Переживает выключениеда
- Пишетсятолько при загрузке
- Ресурс перезаписи≈10 000 раз
- В отчёте сборки«Скетч использует…»
Здесь живут переменные, пока плата работает: глобальные, локальные, буферы Serial и дисплеев, объекты String.
- Переживает выключениенет
- Пишетсяпостоянно
- Ресурс перезаписибез ограничений
- В отчёте сборки«Глобальные переменные…»
Маленький «сейф» для настроек и счётчиков: то, что должно пережить выключение, но меняется во время работы.
- Переживает выключениеда
- Пишетсяиз программы, EEPROM.put()
- Ресурс перезаписи≈100 000 раз
- В отчёте сборкине показывается
Проще всего представить это как рабочее место. Flash — инструкция на стене: написана один раз, при загрузке прошивки, и читается всё время работы. SRAM — стол: на нём лежит всё, с чем программа работает прямо сейчас, и стол этот у Uno совсем маленький — 2048 байт, в шестнадцать раз меньше Flash. EEPROM — ящик стола, куда убирают то, что должно сохраниться до завтра: настройки, счётчик включений, калибровку.
Отчёт сборки говорит только про первые две. Про EEPROM среда молчит, потому что её никто не «занимает» при компиляции — только ваша программа во время работы, командами EEPROM.put() и EEPROM.get().
«Скетч использует N байт памяти устройства» — это Flash
Во Flash попадает всё, что не меняется: машинный код вашего скетча, ядро Arduino (запуск платы, millis(), работа с пинами), код библиотек и константы, которые вы явно попросили оставить во Flash — через PROGMEM или макрос F().
- Загрузчик — 512 байт. Маленькая программа, которая принимает прошивку по USB. Она занята всегда, поэтому «всего доступно 32256 байт», а не 32 768. У Nano со старым загрузчиком под него уходит 2048 байт — остаётся 30 720.
- Ваша прошивка — 1556 байт. Из них 444 байт — ядро Arduino (запуск платы,
millis(), таблица прерываний: столько весит даже пустой скетч), около 994 байт —Serial, и лишь 118 — собственно строка с текстом и вызовprintln(). - Свободно — 30 700 байт. Сюда поместится ещё очень много кода: дисплей, датчики и логика проекта средней руки укладываются в 10–15 КБ.
Отсюда два частых вопроса. Почему у Uno «32 КБ», а доступно 32 256 байт? Потому что 512 байт занимает загрузчик — маленькая программа, которая принимает прошивку по USB и стирается только программатором. У Nano с загрузчиком старого образца под него уходит 2 КБ, поэтому там доступно 30 720 байт; у Leonardo и Pro Micro — 4 КБ, остаётся 28 672. Почему пустой скетч весит 444 байта? Это ядро: таблица прерываний, настройка таймера для millis() и delay(), инициализация портов. Меньше не бывает.
Flash расходуется предсказуемо: чем больше кода и библиотек, тем больше. Неожиданно дорого стоят только вещи, которые тянут за собой целые куски математики. Одна строчка Serial.println(t) с переменной float вместо int прибавляет к прошивке около 1,1 КБ — компилятор подключает библиотеку операций с плавающей точкой и перевода дробей в текст.
«Глобальные переменные используют N байт динамической памяти» — это SRAM
Слово «динамической» здесь сбивает с толку: имеется в виду просто оперативная память, а не «динамическое выделение». Число, которое показывает среда, — это статическая часть SRAM: то, что занято с первой миллисекунды и не освобождается никогда.
Что туда входит:
- Глобальные и статические переменные — всё, что объявлено вне функций:
int counter;, массивы, структуры. - Объекты библиотек, объявленные глобально:
LiquidCrystal lcd(...),Servo motor,Adafruit_SSD1306 display(...). Последний держит буфер экрана 128×64 точки — 1024 байта, половину всей памяти Uno. - Serial: один вызов
Serial.begin()приносит с собой два буфера по 64 байта и сам объект — 175 байт сверху пустого скетча. - Тексты в кавычках. Каждый
Serial.print("...")иlcd.print("...")безF()копируется из Flash в SRAM при старте, по байту на символ, а кириллица — по два.
Пустой скетч (setup и loop без кода)+ Serial.begin(9600)два буфера по 64 байта и объект Serial+ Serial.println("Датчик температуры…")текст в кавычках копируется в SRAMто же, но Serial.println(F("…"))текст остался во Flash: −82 байтаint values[100]100 × 2 байтаbyte values[100]100 × 1 байт: −100 байтString text + сложение строкплюс куча во время работы, которую отчёт не видитLiquidCrystal lcd(12, 11, 5, 4, 3, 2)без Serial; объект экрана 1602Сколько занимает переменная, зависит от типа. На восьмибитных платах (Uno, Nano, Mega) размеры такие:
| Тип | Размер | Для чего |
|---|---|---|
bool, byte, char, uint8_t | 1 байт | флаги, пины, яркость 0–255, символы |
int, short, word, uint16_t | 2 байта | показания analogRead (0–1023), счётчики до 32 767 |
long, unsigned long, float | 4 байта | millis(), большие числа, дроби |
double | 4 байта на AVR (то же, что float) | на ESP32 и Pico — 8 байт |
указатель, адрес | 2 байта на AVR | 4 байта на 32-битных платах |
текст в кавычках "Привет" | по байту на символ + 1 | кириллица в UTF-8 — 2 байта на букву; F() убирает во Flash |
String | 6 байт объекта + сам текст в куче | плюс дыры после каждого изменения |
«…оставляя N байт для локальных переменных» — самое важное число
Всё, что не занято статически, делят между собой два соседа: стек и куча. Стек растёт сверху вниз при каждом вызове функции — там лежат локальные переменные, аргументы, адреса возврата, данные прерываний. Куча растёт снизу вверх, когда программа просит память на ходу: String, new, malloc(). Между ними — то самое «оставляя 1778 байт».
- Глобальные переменные используют 270 байт — это место занято с первой миллисекунды и не освобождается никогда: глобальные и статические переменные, буферы Serial, тексты в кавычках.
- Куча растёт снизу вверх, когда программа просит память на ходу:
String,new,malloc(). - Стек растёт сверху вниз при каждом вызове функции: локальные переменные, адреса возврата, прерывания.
- «…оставляя 1778 байт для локальных переменных» — это всё, что стек и куча делят между собой. Столкнутся — программа сломается без единого сообщения.
Опасность в том, что никакой защиты между ними нет. Когда стек и куча встречаются, микроконтроллер не выдаёт ошибку — он просто начинает работать с чужими данными. Снаружи это выглядит как мистика: программа зависает через десять минут, плата перезагружается сама, в мониторе порта появляется мусор, экран показывает случайные символы, а после добавления одной безобидной строчки всё ломается. Если такое происходит, первым делом смотрите на строку про глобальные переменные.
Arduino IDE предупреждает заранее: когда глобальные переменные заняли больше 75 % SRAM, под отчётом появляется оранжевая строка «Мало доступной памяти, могут возникнуть проблемы со стабильностью». Прошивка при этом загрузится и даже, возможно, поработает — до первого глубокого вызова функции или длинной строки. Практическое правило для Uno и Nano: держите свободными хотя бы 400–500 байт, а если в программе есть String — больше.
«Скетч использует 0 байт памяти» — откуда ноль
Ноль здесь — не размер прошивки, а сбой измерения. Как мы выяснили, даже пустой скетч для Uno занимает 444 байта, поэтому «0 байт» означает, что среда не смогла посчитать: утилита размера не запустилась, отчиталась не в том формате или в пакете платы её вовсе нет. Обычные причины — повреждённая или неполная установка поддержки платы, сторонние ядра со своими настройками сборки, редко — антивирус, который заблокировал одну из утилит.
Прошивка при этом, как правило, собрана и загрузится. Но пользоваться средой, которая не видит размер программы, неудобно: вы не узнаете о приближении к пределу. Переустановите поддержку платы через менеджер плат или соберите тот же проект в Trema IDE — для Uno, Nano и Mega размеры считает движок сборки, и строки RAM: и Flash: появляются всегда.
Программа не помещается в память платы: «Sketch too big», «region overflowed», «Недостаточно памяти»
Когда прошивка не влезает, сообщения выглядят по-разному в зависимости от среды и платы, но их всегда можно свести к вопросу «какая память кончилась»:
| Что пишет среда | Где встречается | Какая память |
|---|---|---|
Скетч слишком большой; см. … для советов по его уменьшению (Sketch too big) | Arduino IDE | Flash |
region 'text' overflowed by 1284 bytes / section '.text' will not fit in region 'text' | компоновщик avr-ld | Flash |
Error: The program size (49712 bytes) is greater than maximum allowed (32256 bytes) | Trema IDE (Uno, Nano, Mega) | Flash |
Недостаточно памяти; см. … для советов по его уменьшению (Not enough memory) | Arduino IDE | SRAM |
Мало доступной памяти, могут возникнуть проблемы со стабильностью (Low memory available) | Arduino IDE, предупреждение | SRAM > 75 % |
Warning! The data size (2388 bytes) is greater than maximum allowed (2048 bytes) | Trema IDE (Uno, Nano, Mega), предупреждение | SRAM |
region 'data' overflowed / section '.bss' is not within region 'data' | компоновщик, платы ARM и ESP | SRAM |
Строки об ошибке Trema IDE переводит: над консолью появляется русская карточка, а исходное сообщение остаётся ниже.
Одна тонкость, про которую стоит знать. На Uno, Nano и Mega движок сборки считает переполнение оперативной памяти предупреждением, а не ошибкой: строка Warning! The data size … is greater than maximum allowed печатается, а сборка помечается успешной. Прошивка при этом работать не будет. Если плата после загрузки ведёт себя странно, посмотрите на строку RAM: — там будет больше 100 %.
Как освободить память: приёмы по убыванию пользы
Порядок не случайный: первые два приёма дают больше всего и почти ничего не стоят, последние — на случай, когда всё остальное уже сделано.
1. Тексты — во Flash макросом F()
Самая частая утечка оперативной памяти — обычные строки в кавычках. Оберните их в F(), и текст останется во Flash, откуда будет читаться по мере печати:
// Было: текст лежит в оперативной памяти — 270 байт SRAM.
Serial.println("Датчик температуры запущен, ждём показаний...");
// Стало: текст остался во Flash — 188 байт SRAM. Экономия 82 байта на одной строке.
Serial.println(F("Датчик температуры запущен, ждём показаний..."));
// F() работает везде, где печатают текст: lcd.print, display.print, client.print.
lcd.print(F("Температура:"));
В проекте с меню на экране и десятком отладочных сообщений это легко возвращает 300–500 байт — четверть всей памяти Uno.
2. Правильные типы вместо int по привычке
int ledPin = 13; // 2 байта, а хватило бы одного
byte ledPin = 13; // 1 байт: значения 0–255 — пины, яркость, проценты
int values[100]; // 200 байт SRAM
byte values[100]; // 100 байт SRAM — если хватает 0–255
long counter = 0; // 4 байта; если счёт до 32 767 — берите int
float t = 23.5; // 4 байта и ~1 КБ Flash на математику с плавающей точкой
int t10 = 235; // «десятые доли» в целом числе: 23,5 → 235
Замена int на byte в массиве из ста элементов — минус 100 байт (мы измерили: 388 против 288). Отказ от float экономит не столько SRAM, сколько Flash — около килобайта на первом же вычислении.
3. Не String, а массив char
String удобен, но на плате с 2 КБ памяти опасен: каждое сложение строк выделяет новый кусок кучи, старый освобождается — и между ними остаются дыры, в которые потом ничего не помещается. Отчёт сборки этого не покажет: он видит только 6 байт самого объекта. Программа со String в loop() может исправно работать час, а потом зависнуть.
// Дорого: String собирает строку в куче, кусочки остаются дырами.
String line = "T=" + String(analogRead(A0)) + " C";
Serial.println(line);
// Дёшево: буфер фиксированного размера на стеке, без кучи.
char line[16];
snprintf(line, sizeof(line), "T=%d C", analogRead(A0));
Serial.println(line);
// Дробное число в текст без float-версии printf (её на AVR нет):
char text[10];
dtostrf(23.5, 4, 1, text); // "23.5"
4. Таблицы, шрифты и картинки — в PROGMEM
Всё, что не меняется во время работы — мелодии, таблицы значений, знакогенераторы, картинки для дисплея, — можно оставить во Flash целиком:
#include <avr/pgmspace.h>
// Таблица лежит во Flash и не занимает ни байта оперативной памяти.
const byte melody[] PROGMEM = { 60, 62, 64, 65, 67, 69, 71, 72 };
// Читать из Flash нужно специальной функцией — обычное melody[i] не сработает.
byte note = pgm_read_byte(&melody[i]);
// Для строк — pgm_read_word и strcpy_P, для чисел покрупнее — pgm_read_word / pgm_read_dword.
5. Массивы по размеру, а не «с запасом»
char buffer[200] «на всякий случай» — это десятая часть памяти Uno под одну переменную. Посчитайте реальную длину: строка для экрана 1602 — 17 байт, команда из монитора порта — редко больше 32.
6. Не заводить лишние объекты
Каждый объект библиотеки, объявленный глобально, занимает место, даже если вы вызвали его один раз. Два экземпляра SoftwareSerial, забытый Servo из прошлого варианта, дисплей с буфером кадра — всё это статика. И Serial.begin() в готовом устройстве без отладки можно убрать: это вернёт 175 байт.
7. Уменьшить Flash: без float, без printf, без лишних библиотек
Когда не помещается прошивка, а не переменные, работают другие приёмы: целочисленная математика вместо float, Serial.print вместо sprintf, одна библиотека для дисплея вместо двух, и — честно — удалить код, который уже не используется. Библиотеки Adafruit для экранов на Uno занимают 10–15 КБ; лёгкие альтернативы вроде U8x8 из U8g2 или текстовых режимов заметно меньше.
8. Взять плату с большей памятью
Иногда проще признать, что проект перерос Uno. Arduino Mega 2560 даёт 8 КБ SRAM и 256 КБ Flash при том же коде и напряжении; ESP32 — 520 КБ оперативной, мегабайты Flash, Wi-Fi и Bluetooth в придачу. Обе ставятся в Trema IDE без ручной настройки: Mega встроена, ESP32 приходит пакетом с зеркала packages.trema.ru.
Как узнать свободную память прямо во время работы
Отчёт сборки показывает статику, но не стек и не кучу. Чтобы увидеть, сколько остаётся в реальной работе, добавьте в скетч функцию, которая вычитает адрес вершины кучи из адреса стека, и печатайте её в монитор порта — в самых «тяжёлых» местах программы:
// Сколько оперативной памяти свободно прямо сейчас (Uno, Nano, Mega).
int freeRam() {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.print(F("Свободно: "));
Serial.println(freeRam()); // печатает, например, 1791
delay(1000);
}
// ESP32 и ESP8266: Serial.println(ESP.getFreeHeap());
// Raspberry Pi Pico: Serial.println(rp2040.getFreeHeap());
Если число уменьшается от вызова к вызову и не восстанавливается — у вас утечка (чаще всего это String). Если проваливается ниже 200 — вы на грани. Функция сама почти ничего не стоит: 8 байт SRAM и полторы сотни байт Flash.
Сколько памяти у разных плат Arduino
Цифры из спецификаций микроконтроллеров. Столбец «доступно» во Flash — за вычетом загрузчика, именно это число среда показывает как «всего доступно». Для плат ESP оперативная память делится между вашей программой и радиочастью, поэтому программе достаётся меньше, чем написано на чипе.
| Плата | Микроконтроллер | Flash | SRAM | EEPROM | В Trema IDE |
|---|---|---|---|---|---|
| Arduino Uno | ATmega328P | 32 КБ (доступно 32 256 Б) | 2 048 Б | 1 024 Б | встроена |
| Arduino Nano | ATmega328P | 32 КБ (30 720 Б со старым загрузчиком) | 2 048 Б | 1 024 Б | встроена |
| Arduino Mega 2560 | ATmega2560 | 256 КБ (253 952 Б) | 8 192 Б | 4 096 Б | встроена |
| Leonardo, Micro, Pro Micro | ATmega32U4 | 32 КБ (28 672 Б) | 2 560 Б | 1 024 Б | пакет Arduino AVR с зеркала |
| Arduino UNO R4 Minima / WiFi | Renesas RA4M1 | 256 КБ | 32 КБ | 8 КБ (эмуляция во Flash данных) | с зеркала |
| ESP8266 (NodeMCU, Wemos D1 mini) | ESP8266EX | обычно 4 МБ на модуле | ≈80 КБ (программе ≈50 КБ) | нет, эмуляция во Flash | с зеркала |
| ESP32 (DevKit, WROOM-32) | ESP32 | обычно 4 МБ на модуле | 520 КБ (программе ≈320 КБ) | нет, эмуляция или Preferences | с зеркала |
| Raspberry Pi Pico | RP2040 | 2 МБ | 264 КБ | нет, эмуляция во Flash | с зеркала |
| STM32F103C8 «Blue Pill» | STM32F103 | 64 КБ | 20 КБ | нет, эмуляция во Flash | с зеркала |
У плат на ESP32, ESP8266, RP2040 и STM32 отчёт выглядит иначе: для них Trema IDE использует движок Arduino CLI, и строки приходят в оригинале — «Sketch uses … bytes of program storage space» и «Global variables use … bytes of dynamic memory». Смысл тот же, что описан выше; проценты для ESP считаются от статической части, а куча там на порядок больше, чем у Uno.
EEPROM: память, которая переживает выключение
Третья память — для настроек. Если пользователь крутит энкодером порог срабатывания, а после отключения питания порог сбрасывается — вам сюда. У Uno и Nano это 1024 байта, у Mega — 4096, у Leonardo — 1024. Пишут в неё командами EEPROM.put() и читают EEPROM.get() — они работают с любыми типами и структурами:
#include <EEPROM.h>
int threshold = 500; // порог, который настраивают кнопками
void saveSettings() {
EEPROM.put(0, threshold); // запишет 2 байта по адресу 0 (только если изменились)
}
void loadSettings() {
EEPROM.get(0, threshold); // прочитает обратно при следующем включении
}
Два правила. Не писать в EEPROM из loop() каждый проход: ресурс ячейки — около 100 000 записей, а loop() выполняется тысячи раз в секунду; сохраняйте только когда значение действительно изменилось (put() и update() сами пропускают запись, если байт тот же). Не полагаться на EEPROM у ESP: у ESP32 и ESP8266 настоящей EEPROM нет, библиотека эмулирует её во Flash и требует EEPROM.begin(size) перед работой и EEPROM.commit() после записи; на ESP32 удобнее библиотека Preferences.
Как это выглядит в Trema IDE
Trema IDE — это Arduino-совместимая среда по-русски, и с памятью она обращается так, чтобы проблему было видно раньше, чем она случится. После каждой сборки в консоли стоят строки RAM: и Flash: с полосками заполненности — они не тонут в логе: служебную болтовню инструментов программа сворачивает под кнопку «Подробности сборки», а размер прошивки оставляет на виду. Когда прошивка не помещается, вместо английской строки компоновщика над консолью появляется карточка «Программа не помещается в память платы» с советом, что сокращать. Ошибки компиляции тоже объясняются по-русски — их разбор есть в статье про ошибки компиляции.

Платы Uno, Nano и Mega собираются встроенным движком без установки чего-либо ещё; ESP32, ESP8266, UNO R4, Raspberry Pi Pico и STM32 ставятся из менеджера плат с зеркала packages.trema.ru, которое работает из России без обходных путей. Библиотеки для датчиков и экранов ищутся по имени файла из строки #include — об этом статья про библиотеки. Для дома программа бесплатна.
Что значит «Глобальные переменные используют 270 байт (13%) динамической памяти, оставляя 1778 байт для локальных переменных»?
Это отчёт об оперативной памяти (SRAM). 270 байт заняты навсегда: глобальными переменными, буферами Serial и текстами в кавычках. 1778 байт остаются на работу программы: локальные переменные, вызовы функций и String. У Arduino Uno и Nano всего 2048 байт, у Mega — 8192.
Что значит «Скетч использует 1556 байт (4%) памяти устройства. Всего доступно 32256 байт»?
Это про Flash — память программ. Столько занимает прошивка: ваш код, ядро Arduino и библиотеки. Она записывается при загрузке и не тратится во время работы. Из 32 768 байт Uno доступны 32 256, потому что 512 байт занимает загрузчик.
Почему «Скетч использует 0 байт»?
Ноль — не про размер, а про сбой измерения: размер прошивки считает отдельная утилита (avr-size) уже после сборки. Если она не запустилась или пакет платы неполный, среда пишет 0. Даже пустой скетч для Uno занимает 444 байта. Переустановите поддержку платы или соберите проект в Trema IDE — там размеры считает движок сборки.
Что означает предупреждение «Мало доступной памяти, могут возникнуть проблемы со стабильностью»?
Глобальные переменные заняли больше 75 % оперативной памяти. Прошивка загрузится, но стеку и куче почти не осталось места: программа может зависать, перезагружаться или печатать мусор. Уберите тексты во Flash макросом F(), замените int на byte, откажитесь от String.
Сколько оперативной памяти у Arduino Uno, Nano, Mega и ESP32?
Uno и Nano — 2 КБ SRAM и 32 КБ Flash, Mega 2560 — 8 КБ и 256 КБ, Leonardo и Pro Micro — 2,5 КБ и 32 КБ, UNO R4 — 32 КБ и 256 КБ, ESP8266 — около 80 КБ, ESP32 — 520 КБ оперативной и обычно 4 МБ Flash, Raspberry Pi Pico — 264 КБ и 2 МБ.
Как уменьшить использование памяти Arduino?
Самое действенное по порядку: тексты в Serial.print и lcd.print завернуть в F(), таблицы и картинки положить в PROGMEM, заменить int на byte там, где хватает 0–255, вместо String взять массив char, не заводить лишние объекты библиотек и убрать float, если хватает целых чисел. На Uno одна строка с F() экономит десятки байт.
Динамическая память Arduino — это то же самое, что SRAM?
Да. В сообщении Arduino IDE «динамической памятью» названа оперативная память SRAM: в ней живут переменные, пока плата включена. Не путайте с «динамическим выделением» — так называют запрос памяти на ходу через String, new или malloc(); он берёт место из той же SRAM.
Как узнать, сколько памяти свободно прямо во время работы?
На платах AVR (Uno, Nano, Mega) добавьте в скетч функцию freeRam(), которая вычитает адрес вершины кучи из адреса стека, и печатайте её в монитор порта. На ESP32 и ESP8266 есть готовая ESP.getFreeHeap(), на Raspberry Pi Pico — rp2040.getFreeHeap(). Готовый код — в статье.
Проверьте память своего проекта
Скачайте Trema IDE, откройте скетч и нажмите «Проверить» — строки RAM и Flash покажут, сколько осталось. Другие статьи — в базе знаний.
