Логгер в котельной писал строку раз в десять секунд и работал так девять месяцев. Когда заказчик попросил график за зиму, я поставил карту в компьютер и завис: log.csv весил больше гигабайта. Excel открыл первые 1 048 576 строк — свой жёсткий предел — и вежливо сообщил, что остальное грузить не будет. Notepad++ честно попытался и ушёл в раздумья на минуту. Файл цел, данные все, а работать с ними нельзя: один файл на девять месяцев — не архив, а чемодан без ручки.
Интересно, что до FAT32-предела в 4 ГБ было ещё далеко. И это не случайность, а правило: текстовый лог с разумным интервалом до четырёх гигабайт не доживает — раньше становится неудобным. Но правило держится, только пока интервал разумный: одна забытая пауза в цикле — и та же карта наберёт лимит за считанные дни. Поэтому объём файла надо мерить прямо из скетча — это делает myFile.size(), и сегодня встроим его в логгер, который завели ещё в уроке про первое логирование.
План такой: посчитаем, сколько байт в сутки приносит типовая строка, познакомимся с size() и соберём ротацию — автоматический переход на новый файл по порогу. А в конце объясню, зачем вообще помнить про 4 ГБ, если до них, казалось бы, никто не доживает. Порог поставим в сотнях килобайт и заодно поймём, почему неудобство наступает задолго до гигабайтов.
Сколько весит строка и сколько набежит за сутки
Строка лога — не абстракция, а конкретные байты. Возьмём каноничную запись с датой и временем: дата, часы-минуты-секунды, температура, влажность, разделители — 2025-11-30 21:45:10,24.7,68. Это 28 символов, плюс перевод строки, который println добавляет двумя байтами. Итого около 30–40 байт на строку; я считаю по сорок, чтобы был запас. Сам формат CSV мы собирали по всем правилам в уроке про CSV-лог, здесь важен только вес.
Арифметика суток. Запись раз в 10 секунд — это 8640 строк в сутки: 86 400 секунд делим на 10. Умножаем на 40 байт — 345 600 байт, округлённо треть мегабайта в сутки. Месяц — около 10 МБ, год — около 126 МБ. Четыре гигабайта при таком темпе наберутся года за тридцать четыре: карта износится или потеряется раньше, чем кончится файл. Отсюда практический вывод: даже скромная карта на 2 ГБ вмещает пятнадцать лет такого лога, и гнаться за объёмом незачем — тем более что большие карты приезжают в exFAT, а этот разбор у нас уже был. Отсюда практический вывод: даже скромная карта на 2 ГБ вмещает пятнадцать лет такого лога, и гнаться за объёмом незачем — тем более что большие карты приезжают в exFAT, а этот разбор у нас уже был.
Теперь сдвигаем интервал — и картина меняется радикально, таблица ниже это показывает. Раз в секунду — 3,5 МБ в сутки, лимит через три с половиной года. Раз в 100 миллисекунд — 35 МБ в сутки, четыре гигабайта за четыре месяца. А цикл записи без паузы, по строке каждые 10 мс, приносит 350 МБ в сутки и добивает лимит за двенадцать дней. Именно так появляются форумные темы «карта перестала писать»: не износ, а переполнение. Заметьте: пропорции ломает не скорость библиотеки, а ваш интервал. Библиотека честно кладёт свои сорок байт; вопрос лишь в том, как часто вы это ей разрешаете. Заметьте: пропорции ломает не скорость библиотеки, а ваш интервал. Библиотека честно кладёт свои сорок байт; вопрос лишь в том, как часто вы это ей разрешаете.
| Интервал записи | Строк в сутки | МБ в сутки | 4 ГБ заполнятся за |
|---|---|---|---|
| 10 секунд | 8 640 | 0,35 | около 34 лет |
| 1 секунда | 86 400 | 3,5 | около 3,4 лет |
| 100 миллисекунд | 864 000 | 35 | около 4 месяцев |
| без паузы (10 мс) | 8 640 000 | 350 | около 12 дней |
size(): объём, не выходя из скетча
size() — метод объекта File: возвращает размер файла в байтах как unsigned long. Хитрость в том, когда его спрашивать. В режиме FILE_WRITE курсор встаёт в конец — проходили это в уроке про режимы открытия, — поэтому сразу после открытия size() показывает текущий объём на карте. Спрашивать по ходу записи тоже можно: всё, что вы напечатали, попадает в размер ещё до close, число растёт с каждой строкой. Стоит проверка ничего — хоть на каждой итерации цикла. Стоит проверка ничего — хоть на каждой итерации цикла.
Проверка стоит ноль: размер лежит в служебной записи файловой системы, а не считается перебором байтов. Гигабайтный файл ответит за те же микросекунды, что и новорождённый. Поэтому порог проверяют перед каждой записью: открыли файл, спросили size, сравнили с лимитом — и либо пишем дальше в этот файл, либо закрываем и переходим на новый. Одна строка if вместо целой категории сюрпризов. Мой добавочный приём — печатать размер в Serial раз в сотую запись: рост на глазах успокаивает, а размер, вставший на месте, кричит о беде раньше, чем кончится файл. Почему Serial удобен для таких проверок, я разбирал в уроке про отладку логики.
Собираем ротацию. Паузу между записями держу на millis, а не на delay — приём разобран в уроке про millis вместо delay, здесь он вдобавок держит интервал ровным. Порог ставлю в 100 КБ, около 2500 строк: такой файл открывается в любом редакторе за секунду, а за месяц логгер кладёт на карту полтора-два десятка кусков. Имена строю как log1.csv, log2.csv: у библиотеки короткие имена формата 8.3, до восьми символов до точки, — мы обсуждали это в уроке про формат карты, нумерация влезает с запасом. Порог — компромисс: чем мельче куски, тем быстрее искать нужный день, зато больше служебных записей в таблице FAT. От сотни килобайт ни то, ни другое не мешает.
#include <SPI.h>#include <SD.h> const int chipSelect = 10;const unsigned long LIMIT = 100000UL; // порог ротации: около 2500 строк по 40 байт String name = "log1.csv";int part = 1;unsigned long lastWrite = 0; void setup() { Serial.begin(9600); if (!SD.begin(chipSelect)) { Serial.println("карта не видна: формат, питание, провода"); while (true) {} } Serial.print("стартую с "); Serial.println(name);} void loop() { if (millis() - lastWrite < 10000UL) return; // запись раз в 10 секунд lastWrite = millis(); File f = SD.open(name.c_str(), FILE_WRITE); if (!f) { Serial.println("файл не открылся"); return; } if (f.size() > LIMIT) { // файл дорос до порога f.close(); part = part + 1; name = "log" + String(part) + ".csv"; Serial.print("ротация: перехожу на "); Serial.println(name); return; } // строка с датой, секундами и показанием - около 40 байт f.print("2025-11-30 "); f.print(millis() / 1000UL); f.print(",24.7,"); f.println(analogRead(A0) / 10); Serial.print(name); Serial.print(" size="); Serial.println(f.size()); // объём ещё до close f.close();}Предел 4 ГБ и ротация, которая реально нужна
Теперь сам предел. FAT32 хранит размер файла в 32-битном поле, максимум — 4 294 967 295 байт, четыре гигабайта минус один. Дальше файл не растёт: print молча вернёт ноль записанных байт, и лог кончится, хотя карта полупустая. Заметьте: предел касается одного файла, а не карты — соседние файлы пишут себе спокойно. Проверять результат можно без гаданий: print и println возвращают число записанных байт, и ноль в ответе — ровно тот момент, когда файл упёрся.
Почему же до лимита не доживают реальные логи? Первое: разумный интервал приносит сотню мегабайт в год, мы посчитали выше. Второе: карту регулярно забирают — лог уехал к заказчику, и файл оборвался на месяцах работы. Третье: длинный файл становится бесполезным раньше, чем полным, — Excel останавливается на 1 048 576 строках, это примерно сорок мегабайт нашего лога, и дальше отказывается идти.
Но два случая дотягиваются до четырёх гигабайт честно. Первый — цикл записи без паузы: строка каждые 10 мс — это 350 МБ в сутки и переполнение за двенадцать дней. Второй — бинарная запись сырых выборок АЦП: сотни байт за опрос ломают все наши пропорции разом. В обоих случаях спасает один и тот же if с size() из скетча выше — или, на худой конец, ротация, добавленная постфактум за вечер. Свой двенадцатидневный лог я потом резал скриптом на месяцы и клялся вставить ротацию сразу, в следующий раз. Вставил: порог проверяет размер до каждой записи, и ручной работы в этом больше нет.
- 1. Что вернёт myFile.size() сразу после SD.open(name, FILE_WRITE) существующего файла?
- 2. Строка лога весит около 40 байт, запись раз в 10 секунд. Сколько набежит за сутки?
- 3. Чему равен максимальный размер одного файла в FAT32?
- 4. Логгер пишет строку каждые 10 мс без паузы. Что случится раньше?
- 5. Зачем ротация на 100 КБ, если лимит FAT32 — 4 ГБ?
- size() возвращает размер файла в байтах; сразу после FILE_WRITE-открытия это текущий объём на карте
- Строка лога — около 40 байт; раз в 10 секунд это 0,35 МБ в сутки и примерно 126 МБ в год
- Предел FAT32 — 4 ГБ минус один байт на файл; текстовые логи до него не доживают: мешают Excel и снятие карты
- Ротация: порог 100 КБ, имена log2.csv и log3.csv, проверка size() перед каждой записью
- Цикл без паузы набивает лимит за 12 дней — если карта перестала писать, первым делом проверьте интервал
Частые вопросы по теме урока
- Что вернёт myFile.size() сразу после SD.open(name, FILE_WRITE) существующего файла?
- FILE_WRITE позиционирует курсор в конец, и size() сразу показывает объём файла на карте. Ноль вы увидите только у нового файла. Текущий размер в байтах: FILE_WRITE ставит курсор в конец
- Строка лога весит около 40 байт, запись раз в 10 секунд. Сколько набежит за сутки?
- 8640 строк в сутки по 40 байт — это 345 600 байт, примерно треть мегабайта. 3,5 МБ в сутки даёт запись раз в секунду. Около 0,35 МБ
- Чему равен максимальный размер одного файла в FAT32?
- 32 бита на размер файла — это 4 294 967 295 байт максимум. Предел раздела в 32 ГБ — отдельное ограничение, к размеру файла отношения не имеет. 4 ГБ минус один байт — размер хранится в 32-битном поле
- Логгер пишет строку каждые 10 мс без паузы. Что случится раньше?
- 350 МБ в сутки — четыре гигабайта за двенадцать дней. Износ при таких объёмах тоже ускоряется, но лимит файла наступает первым. Предел файла в 4 ГБ — примерно через 12 дней
- Зачем ротация на 100 КБ, если лимит FAT32 — 4 ГБ?
- До 4 ГБ текстовый лог не доживает — неудобство наступает раньше. Куски по 100 КБ открываются мгновенно, а месяцы лога раскладываются по файлам сами. Чтобы файлы оставались удобными: Excel останавливается на 1 048 576 строках