Логгер на даче писал в корень карты три года: log.csv, затем log2.csv, рядом конфиг, бэкапы настроек, пара служебных файлов. К осени корень напоминал ящик со скрепками: сорок с лишним файлов вперемешку, и каждый тестовый запуск листал всю свалку openNextFile-ом. Плюс мелкие неприятности на поверхности: старый конфиг рядом с новым, две версии архива с одним именем, и глазами не отличить, какой файл за май, а какой за март. Параллельно я захотел имя с датой — log-25-05.csv — и библиотека отказалась его создавать, хотя карта была исправна и место оставалось. Разбор показал: обе проблемы живут в одном месте, в устройстве каталогов FAT, и лечатся одной перестройкой привычек.
Начался разбор с факта, который в официальной документации SD-библиотеки записан прямо: она работает с короткими именами 8.3 — до восьми символов имени, точка, до трёх символов расширения. Всё, что длиннее, живёт на карте отдельной длинной записью, а для Arduino виден лишь короткий двойник вида LOG250~1.CSV, который создаёт Windows, но не библиотека. Мой 250512.csv влезал — шесть цифр, — а имя с дефисами и лишними символами уже нет: регламент есть регламент.
В этом уроке раскладываем всё по полочкам: как SD.open видит папки через слеш в пути, почему mkdir создаёт по одному уровню за вызов, сколько файлов физически помещается в корень и что меняет раскладка по папкам. Механику создания папок и готовую схему архива по дням я уже разбирал в уроке про mkdir и структуру архива; здесь — то, о чём тот урок не говорил: ограничения, счёт записей и имена.
Путь со слешем: как SD.open находит файл в папке
Папки в SD-библиотеке адресуются частью пути: SD.open("data/log.csv", FILE_WRITE) открывает файл log.csv внутри папки data. Слеш понимают одинаково все файловые операции: exists("data/log.csv"), remove и rename работают с тем же путём, отдельного синтаксиса для каждой операции не заводится. Понятия «текущая папка» в библиотеке нет: любой путь отсчитывается от корня, chdir-а здесь не существует, и это осознанное упрощение. Хотите жить в data — пишите префикс перед каждым именем; я завожу константу с путём и приклеиваю её к имени файла — одна строка, а пути не расползаются по скетчу.
Правило, которое экономит вечер: папка должна существовать до открытия файла в ней. SD.open("data/log.csv", FILE_WRITE) при отсутствии папки вернёт false — молча, без намёка на причину, и новичок обычно грешит на карту, питание или SPI, а виновата отсутствующая папка. Поэтому мой порядок всегда один: сначала SD.mkdir("data"), потом уже open. Мелочь, которую стоит знать заранее: mkdir на существующей папке вернёт false — это не провал, а норма, и проверять надо не результат mkdir, а существование после него.
Вложенность создаётся по одному уровню за вызов: сначала mkdir("data"), потом mkdir("data/2025"). Вызов mkdir("data/2025/05") одной строкой на классической библиотеке не сработает — родители должны уже существовать. Проверить, что объект в корне — папка, умеет entry.isDirectory(): этим флагом я отделяю папки от файлов при обходе списка, механику которого в деталях разбирает урок про openNextFile. А стирание и переименование папок целиком — в уроке про remove и rename.
| Объект на карте | Записи каталога | Лимит |
|---|---|---|
| Файл с коротким именем в корне | одна запись на файл | сколько записей отведено корню при форматировании, типично 512 |
| Файл с длинным именем | короткая запись плюс по одной на каждые 13 символов имени | тот же корень, но лимит наступает в 2-3 раза раньше |
| Папка | одна запись в родителе | содержимое папки растёт по кластерам почти свободно |
Имена 8.3: восемь символов, точка, три на расширение
Регламент 8.3 — наследие DOS, и в FAT он до сих пор базовый: короткое имя хранится прямо в записи каталога, фиксированными одиннадцатью байтами, без переменных длин. Библиотека SD этим регламентом и живёт. Восемь символов звучат скупо, но хватает их плотнее, чем кажется: log.csv, cfg.txt, dht01.csv, dht02.csv — нумерация в имени закрывает сразу и уникальность, и сортировку. Расширение я держу всегда: оно занимает свои три законных символа и помогает проводнику открывать файлы правильной программой.
Чего в восьми символах не влезает: дата целиком, слово temperature, серийный номер. Отсюда мой стандарт: дата сжимается до четырёх цифр — 0206.csv — второе июня, а двусмысленность дня и месяца снимает папка по году. Важная оговорка про обратную сторону: файл с длинным именем, записанный на карту компьютером, на карте существует — у него просто есть короткий двойник вроде LONGFI~1.TXT, и SD.h работает именно с двойником. Сама библиотека длинные имена не создаёт, и закладываться на них в скетче я не советую: поведение с чужими длинными именами зависит от версии библиотеки — ещё одна причина держать имена короткими.
Если длинные имена всё-таки нужны — путь в сторону библиотеки SdFat: у неё поддержка длинных имён включается настройкой, а плата за неё — десятки килобайтов скетча, и на Uno с его 32 КБ флеша это не бесплатно: я подключаю SdFat только когда без длинных имён правда нельзя. Моё мнение с границей применимости: логгеру, который пишет сам и читает сам, 8.3 хватает всегда, а совместимость с компьютером обеспечивают папки и нумерация. Длинные имена — это про то, когда файлы на карту кладёт человек и потом ищет их глазами.
Лимит корня: считаем записи каталога
Сколько файлов помещается в корень — вопрос не объёма карты, а устройства каталога. У FAT16 корень — фиксированная таблица, число записей в которой задаётся при форматировании; у карт, размеченных Windows, типичный размер — 512 записей, и расширяться таблица не умеет. Каждая запись — 32 байта: имя, атрибуты, даты, указатель на первый кластер. Короткоимённый файл занимает одну запись; файл с длинным именем — короткую запись плюс по одной на каждые тринадцать символов длинного имени, то есть имя на сорок символов съедает четыре записи.
Папки устроены иначе: в родителе папка занимает одну запись, а её содержимое лежит в собственной таблице, которая растёт по кластерам — почти без ограничений. Отсюда практический вывод: свалка в корне упирается в лимит, свалка в папке — нет. Двести файлов с длинными именами в корне — это в худшем случае ноль свободных записей; те же двести файлов по папкам на год — двенадцать записей в корне и запас на годы вперёд. И второй плюс, уже не про лимит: openNextFile в узкой папке за месяц просматривает десятки записей, а не сотни, и обход оживает на глазах. Формат карты, FAT16 против FAT32 и выбор объёма — тема отдельного урока про форматы; здесь важно одно: корень — дефицитная площадь.
На FAT32 формальный лимит корня мягче — там и корень умеет расти кластерами. Но и на FAT32 я держу в корне только папки и три-четыре файла: листинг корня-свалки медленный, путаница гарантирована, а порядок по папкам даёт быстрый обход и понятные границы резервных копий. Предел на размер самого файла — четыре гигабайта — своя история, ей посвящён урок про size и предел 4 ГБ.
#include <SPI.h>#include <SD.h> const int chipSelect = 10; void setup() { Serial.begin(9600); if (!SD.begin(chipSelect)) { Serial.println("SD error"); while (true) { } } // Папка создаётся по одному уровню за вызов if (!SD.exists("data")) { if (SD.mkdir("data")) { Serial.println("mkdir data: ok"); } else { Serial.println("mkdir data: false"); } } else { Serial.println("data uze est - norm"); } // Файл внутри папки - путь со слешем File f = SD.open("data/log.csv", FILE_WRITE); if (!f) { Serial.println("open data/log.csv: false"); // чаще всего папки нет return; } f.println("start"); f.close(); Serial.println("zapisano v data/log.csv"); // Обход корня: кто там живёт File root = SD.open("/"); int files = 0; int dirs = 0; File entry = root.openNextFile(); while (entry) { if (entry.isDirectory()) { dirs = dirs + 1; } else { files = files + 1; } Serial.print(entry.name()); if (entry.isDirectory()) { Serial.println("/"); } else { Serial.println(""); } entry.close(); entry = root.openNextFile(); } root.close(); Serial.print("v korne: "); Serial.print(files); Serial.print(" fajlov, "); Serial.print(dirs); Serial.println(" papok");} void loop() { }Что смотреть в симуляторе. Создайте папку командой mkdir data и откройте файл командой new data/log.csv — маршрут найдёт папку и отработает. Теперь new logs/log.txt до создания logs: получите отказ с тем же смыслом, что у молчаливого false на плате — только здесь причина напечатана, а на карте её приходится додумывать. Команда new log.csv без папки списывает запись корня: сделайте это пару десятков раз и гляньте ls — счётчик тает, и лимит 512 из абстрактного числа становится конечным ресурсом.
На настоящей плате модель заменяется тремя вызовами: SD.mkdir по правилу одного уровня, SD.exists перед открытием и константа с префиксом папки. Список корня печатает скетч выше — сравните его вывод со своим представлением о содержимом карты: расхождение обычно означает чужие короткие двойники длинных имён. А если open возвращает false при живой карте и существующей папке, маршрут проверки шаг за шагом разложен в уроке про диагностику SD.
- 1. Сколько символов отведено имени и расширению в коротком имени 8.3?
- 2. SD.open("data/log.csv", FILE_WRITE) вернул false, карта исправна. Первая проверка?
- 3. Почему двести файлов с длинными именами в корне FAT16 опаснее той же свалки в папке?
- 4. Как создать data/2025/05 одним скетчем на классической SD.h?
- 5. Что вернёт SD.mkdir("data") на карте, где папка data уже есть?
- Путь от корня: SD.open("data/log.csv"); текущей папки в SD.h нет — префикс держите в константе.
- Сначала mkdir, потом файл: open в несуществующую папку молча возвращает false.
- Вложенность — по одному уровню за вызов mkdir; false на существующей папке — норма.
- Имена 8.3: до восьми символов плюс до трёх расширения; дата в имени сжимается до четырёх цифр.
- Корень FAT16 — фиксированные ~512 записей, длинное имя ест несколько; содержимое папки растёт по кластерам свободно.
Частые вопросы по теме урока
- Сколько символов отведено имени и расширению в коротком имени 8.3?
- Восемь на имя, три на расширение, точка между ними. Ограничение мягкое в обе стороны: меньше можно, больше — уже длинное имя. до восьми символов имени и до трёх расширения
- SD.open("data/log.csv", FILE_WRITE) вернул false, карта исправна. Первая проверка?
- Файл не может появиться в несуществующей папке: open молча отдаёт false. Порядок всегда один — mkdir, проверка exists, потом open. существует ли папка data: без неё open с путём внутрь не пройдёт
- Почему двести файлов с длинными именами в корне FAT16 опаснее той же свалки в папке?
- Корень FAT16 — таблица на типичные 512 записей, размер которой зашит при форматировании. Длинное имя стоит короткой записи плюс по одной на каждые 13 символов. корень FAT16 — фиксированная таблица, а длинное имя ест несколько записей на файл
- Как создать data/2025/05 одним скетчем на классической SD.h?
- Библиотека не строит родителей за один вызов: каждый уровень — отдельный mkdir, и предыдущий уровень должен уже существовать. лестницей: mkdir("data"), затем mkdir("data/2025"), затем mkdir("data/2025/05")
- Что вернёт SD.mkdir("data") на карте, где папка data уже есть?
- mkdir на существующем пути отдаёт false, и это не провал. Поэтому проверяют не результат mkdir, а SD.exists после вызова. false — это норма; о наличии папки спрашивают через SD.exists