Логгер в гараже отработал зиму без единой претензии, а весной я принёс карту домой — и ноутбук предложил её отформатировать. Двухмесячный архив показаний, судя по занятому месту, лежал где-то внутри, но файлы не открывались. Это не старость носителя: за зиму свет в гараже отключали десятки раз, каждый обрыв приходился на чью-то запись, и один из них наконец разорвал файловую систему в самый неподходящий момент структуры. Карту я тогда спас посекторной копией и проверкой диска на большом винте: вернул всё, кроме последних минут, записанных без close.
Разбираю по слоям, что именно ломается: где у FAT живёт размер файла, что такое потерянные кластеры и почему их не видно глазами, чем flush отличается от close и как дать логгеру секунду на прощание — диодом Шоттки и суперконденсатором, которые стоят дешевле самого модуля SD.
Анатомия записи: четыре шага, которые рвёт обрыв
У FAT32 файл на карте — не сплошной кусок, а цепочка кластеров. Кластер — порция от 4 до 32 килобайт в зависимости от объёма карты и разметки. Порядок кластеров записан в таблице FAT, а в каталоге лежит запись с именем, датой и текущим размером. Пишете строку — библиотека ищет свободный кластер, вписывает его в цепочку таблицы FAT, кладёт байты в сектор и рано или поздно обновляет размер в каталоге. Четыре шага, и каждый заканчивается записью сектора во флеш-память.
Карта — не магнитофон: сектор в 512 байт уезжает целиком за один флеш-цикл, внутри контроллера есть свой буфер, а сам цикл занимает единицы миллисекунд. Если вольты пропали ровно в этот момент, контроллер либо дотягивает сектор от внутренних конденсаторов, либо оставляет его недописанным. Структура остаётся наполовину готовой, и таблица начинает врать о содержимом. Десятки миллисекунд на сектор — вот и вся разница между «файл жив» и «файл разорван»: у обрыва нет ни расписания, ни жалости.
Отсюда правило, которое все открывают на своей шкуре: вызов print у файла — не запись на карту. Между print и флеш-памятью сидит буфер на 512 байт, и всё, что меньше сектора, живёт только в оперативке платы. Выдернул шнур — выдернул вместе с ним и неотправленные строки, хоть скетч их уже «написал». Библиотека держит ровно один сектор, и это не опция, а устройство потока: print кладёт байты в RAM, flush гонит сектор целиком.
Половинчатая структура даёт характерные симптомы. Оборвалась цепочка в FAT — кластеры остаются помеченными занятыми, но не принадлежат ни одному файлу. Это и есть потерянные кластеры: Windows их не показывает, зато честно отражает занятое место, и карта «толстеет» без причины. Утилита проверки диска предлагает вернуть их в строй как FILE0000.CHK, а с флагом автоисправления может просто срезать. На ПК вас выручает chkdsk или fsck; на логгере, который живёт в поле без компьютера, лечения нет — библиотека SD чинить разметку не умеет, мусор копится, и однажды карта перестаёт монтироваться вовсе. Косвенный признак можно увидеть прямо на плате: после обрыва файл открывается, но readString возвращает только то, что числилось в каталоге на момент последнего close.
Стратегия на плате одна: не доводить до обрыва структуры. Закрывать файл аккуратно, копить журнал в оперативке и писать пакетами — приёмы из урока про пакетную запись. А какой разметке верит библиотека SD и чем FAT16 отличается от FAT32 на картах до 4 ГБ и выше — в разборе форматов: карты exFAT в этой истории не участвуют никак.
flush, close и секунда на прощание
flush() выталкивает буфер на карту: сектор уезжает, цепочка в FAT обновляется. close() делает то же самое плюс закрывает запись в каталоге — вписывает финальный размер. Разница видна именно после обрыва: с flush, но без close файл существует, но каталог уверяет, что он короче, чем есть, — свежие строки числятся призраками, которые покажет только проверка диска. Механику обоих вызовов с таймингами я разбирал в уроке про flush и sync, базовое «записал — закрыл» — в уроке про println и close. Здесь следующий слой: что делать, когда питание умирает не по расписанию, а в произвольную миллисекунду.
Ответ железный: дать плате секунды после пропажи сети. Диод Шоттки от блока питания к плате плюс суперконденсатор 1 Ф на 5,5 В — копеечная связка. Розетку выключили, сеть мертва, а Uno с логгером без экранов и реле доживает на накопленной ёмкости несколько секунд. Скетч в это время следит за входным напряжением через делитель на аналоговом входе: просадка ниже порога — немедленно flush и close. На эти два вызова уходят миллисекунды, запас остаётся столетним. Суперконденсатор питает плату целиком, поэтому счёт бюджета ведите честно: Wi-Fi-модуль или реле на той же ёмкости съедают его за доли секунды. Делитель под порог считайте под свою сеть: резисторы 10 и 10 кОм делят 5 В пополам, для 12-вольтового ввода — 30 и 10 кОм, и порог пересчитывается пропорционально.
Граница метода: если обрыв случился в середине многосекторной записи, контроллеру карты нужно время дотянуть транзакцию — сотни миллисекунд на блок. Секунды суперкапа закрывают это с запасом, но не бесконечность: не вешайте на ту же ёмкость дисплей, свисток и сервопривод, иначе скетч успеет закрыть файл на плате, а карта — не успеть дописать сектор.
#include <SPI.h>#include <SD.h> const int PIN_VIN = A0; // делитель 1:1 на входном питанииconst int CS_PIN = 10;const int POROG = 4200; // ниже 4200 мВ сеть пропала: прощаемся File logFile;unsigned long lastRow = 0;int rowsSinceFlush = 0;bool saved = false; void setup() { Serial.begin(9600); if (!SD.begin(CS_PIN)) { while (true) delay(500); } logFile = SD.open("log.csv", FILE_WRITE);} void loop() { unsigned long ms = millis(); int mv = analogRead(PIN_VIN) * 5000L / 1023; if (mv < POROG && logFile && !saved) { logFile.flush(); logFile.close(); // каталог получит финальный размер saved = true; Serial.println("Просадка: файл закрыт, можно гаснуть"); while (true) delay(500); } if (ms - lastRow >= 2000 && logFile && !saved) { lastRow = ms; logFile.print(ms / 1000); logFile.print(","); logFile.println(analogRead(A1)); if (++rowsSinceFlush >= 10) { // пакет: раз в 10 строк logFile.flush(); rowsSinceFlush = 0; } }}В модели видно то, чего без осциллографа на железе не увидишь: сколько байт стояло в буфере в момент обрыва. Включите тумблер и повторите отключение — потеря сожмётся в разы. На реальной плате частый flush означает лишние операции записи, а у флеш-памяти ресурс по стиранию конечен: для лога раз в две секунды это несущественно, для журнала по миллисекундам — уже бюджет, который надо считать.
Таймер flush соберите на millis без delay, как показано в уроке про millis вместо delay. Логгер с delay внутри пропустит момент просадки на целую паузу — а это ровно те секунды, ради которых в схему ставился суперконденсатор. Порядок всегда один: увидел просадку, flush, close, и только потом плата вправе гаснуть.
Единственное, что не заменит суперконденсатор, — дисциплина сессий записи. Если файл держится открытым сутками, а close зовётся только перед сном, любой обрыв между flush-ами оставит на карте призраков и оборванные цепочки. Держите сессию короткой — открыл, дописал, закрыл — так, как разобрано в уроке про сессию open-write-close: тогда окно уязвимости измеряется миллисекундами, а не часами работы логгера.
| Момент обрыва | Что видит ПК | Чем спасаться |
|---|---|---|
| Середина записи сектора | файл открылся, хвост обрезан или мусор в последней строке | потерянные кластеры покажет проверка диска; сначала копия карты |
| Между FAT и каталогом | размер файла старый, свежие строки числятся призраками | close вместо голого flush: он вписывает финальный размер |
| Месяцы без close при живой записи | файл пустой при занятом месте на карте | порядок: flush, затем close, затем сон или пауза |
| Суперкап и контроль входа | только то, что не успело до команды close | диод Шоттки плюс 1 Ф дают секунды на аккуратное закрытие |
- 1. Что физически делает flush() у файла на SD-карте?
- 2. Питание пропало между обновлением FAT и записью размера в каталог. Что на карте?
- 3. Кластер помечен занятым в FAT, но не принадлежит ни одному файлу. Это...
- 4. Сколько держит Uno с логгером на суперконденсаторе 1 Ф после пропажи сети?
- 5. Почему delay() в логгере опасен для схемы с суперкапом?
- Запись строки — четыре шага: кластер, цепочка в FAT, сектор, размер в каталоге; обрыв рвёт любой из них.
- Потерянные кластеры помечены занятыми и бесхозны: место тает, файлов не видно, лечит только проверка диска на ПК.
- flush выталкивает буфер 512 байт на карту; close делает это плюс вписывает финальный размер в каталог.
- Диод Шоттки плюс суперконденсатор 1 Ф дают Uno 3-4 секунды на закрытие файла — миллисекунды, которые решают судьбу архива.
- Контроль входного питания через делитель и millis-таймеры без delay: просадка замечена — flush, close, гаснуть.
Частые вопросы по теме урока
- Что физически делает flush() у файла на SD-карте?
- flush гонит буфер из оперативки платы на флеш-память карты и обновляет цепочку в FAT. Финальный размер в каталог вписывает close — именно его нехватка оставляет призрачные строки. Выталкивает буфер 512 байт из RAM платы на карту
- Питание пропало между обновлением FAT и записью размера в каталог. Что на карте?
- Записанные секторы лежат на карте, но запись в каталоге не обновлена, поэтому файл выглядит короче. Строки числятся призраками до проверки диска — вот зачем нужен close. Содержимое доехало, но каталог показывает старый размер
- Кластер помечен занятым в FAT, но не принадлежит ни одному файлу. Это...
- Обрыв между шагами записи оставляет кластер занятым и бесхозным. Место съедается, файлов не видно; проверка диска предлагает FILE0000.CHK — соглашайтесь только после копии карты. Потерянный кластер — след оборванной цепочки
- Сколько держит Uno с логгером на суперконденсаторе 1 Ф после пропажи сети?
- 1 Ф на входе 5 В даёт Uno с одним логгером 3-4 секунды. Радиомодули и реле с их пиками в сотни миллиампер съедают запас за доли секунды — бюджет считайте по своей схеме. Несколько секунд без экранов и реле
- Почему delay() в логгере опасен для схемы с суперкапом?
- Пока скетч висит в delay, условие просадки не проверяется, и секунды прощания горят впустую. Таймеры собирают на millis — как в уроке про millis вместо delay. В симуляторе разницу между flush и без него видно руками — на карте её видят только через месяц. Пауза прячет момент просадки: проверка питания пропустит секунды