Утро показа логгера заказчику началось с «initialization failed» на чистом столе — хотя вечером накануне всё писало исправно. За пятнадцать минут я успел перепрошить плату, переткнуть все восемь проводов и начать подозревать ноутбук. Помогло то, что в итоге не тронуло ни код, ни питание: карта выехала из разъёма на миллиметр — push-push не был дожат до щелчка, контакт плавал. Переткнул до двойного щелчка — всё заработало. Полтора часа стоило отсутствие одной привычки: перетыкать карту до конца, а не до «как-то стоит».
SD.begin() — самая отказная строчка любого проекта с картой. Не потому, что функция плохая, а потому, что она проверяет сразу три слоя: жива ли шина SPI, отвечает ли карта и монтируется ли файловая система. Отказ любого из трёх выглядит одинаково — false без единого слова объяснений. Задача этого урока — научиться читать этот false как диагноз со списком вероятных болезней, а не как приговор всему проекту.
План такой: заглянем внутрь инициализации и поймём, зачем ей сотни миллисекунд; соберём таблицу «симптом — причина — лечение» из реальных поломок, включая мою коллекцию; напишем скетч с тремя попытками и честным чек-листом; и потренируем всё это на модели в симуляторе, где кнопка изображает переформатирование карты.
Что происходит внутри SD.begin()
Шаг первый — настройка SPI и переход на пониженную скорость. Протокол карт требует знакомиться медленно: свежевключённая карта ещё не сказала, на какой частоте способна жить, поэтому первые команды гоняют на частотах порядка 400 кГц. Отсюда и время: вся инициализация занимает от 100 до 500 мс на Uno. Это заметная пауза, и по двум причинам из неё следствия: во-первых, SD.begin() не вызывают в каждом обороте loop() просто «для проверки» — повесите на него весь скетч; во-вторых, блокировка на полсекунды в setup() — нормальная цена, а в рабочем цикле — роскошь, которую логгер не может себе позволить.
Шаг второй — диалог с картой. Библиотека посылает сброс (CMD0), спрашивает про допустимый диапазон питания (CMD8), запускает карту командой ACMD41 и по ответам понимает поколение карты и её готовность. Если на CMD0 ответа нет вообще — «карта не отвечает» — это почти всегда контакт или мёртвая карта. В расширенной диагностике из примера CardInfo такой отказ печатается кодом 0X1: карта не прошла даже сброс. Коды выглядят сухо, но когда чек-лист не помогает, именно они сужают круг подозреваемых.
Шаг третий — монтирование файловой системы: библиотека читает загрузочный сектор, ищет таблицу FAT и корневой каталог. Вот здесь ложится exFAT: у него другая структура, и вместо вежливого признания «формат не знаю» вы получаете тот же безликий false. true возвращается только когда все три слоя сказали да — поэтому и проверять результат нужно всегда: без карты каждый последующий SD.open() молча вернёт «неудачу», и вы будете ловить призрака в коде, которого не существует. Это правило мы завели ещё в первом уроке раздела.
Теперь галерея причин — в порядке частоты в моей практике. Первая — формат. Карты на 64 ГБ и больше почти наверняка размечены в exFAT из коробки; форматирование в телефоне тоже любит exFAT. Лечение: FAT32 на компьютере, не на телефоне. Объём — берите 2–16 ГБ: для текстового лога это миллионы строк, а старые карты предсказуемее скоростных новинок. SDXC даже после принудительного FAT32 иногда капризничает — я такие в проекты не ставлю.
Вторая — питание. Классика жанра: в покое всё отлично, begin проходит, а при первой записи сыплются ошибки — импульс в 150 мА проваливает вход модуля ниже 4,4 В, и стабилизатор проседает. Короткие провода и конденсатор 100 мкФ у модуля, как мы считали в уроке про устройство модуля, закрывают вопрос в девяти случаях из десяти.
Третья — провода и CS: false при исправной карте чаще всего означает перепутанные MOSI/MISO или несовпадение номера CS с реальным проводом. Распиновка и общая шина — прошлая тема: урок про SPI-подключение. Четвёртая — контакт в разъёме: моя утренняя история с картой, выехавшей на миллиметр. Пятая — само собой, карта вообще не вставлена: звучит как анекдот, случалось со мной дважды за зиму.
Метод здесь тот же, что при диагностике NAN у датчиков температуры: печатать этапы и смотреть, на каком стоп — ровно этому учит урок про диагностику DHT. Отказ SD.begin() ничем не экзотичнее: это тоже не «карта сломалась», а конкретный слой, который сказал нет.
#include <SPI.h>#include <SD.h> const int chipSelect = 10; void setup() { Serial.begin(9600); bool ok = false; for (int attempt = 1; attempt <= 3; attempt++) { Serial.print("try="); Serial.print(attempt); Serial.print(" ok="); ok = SD.begin(chipSelect); Serial.println(ok); if (ok) break; delay(2000); // передышка: карты иногда просыпаются не сразу } if (!ok) { Serial.println("Chek-list:"); Serial.println("1) FAT32 na karte? ne exFAT?"); Serial.println("2) Vhod modulya 4.4-5.5 V vo vremya zapisi?"); Serial.println("3) MOSI->11, MISO->12, SCK->13, CS->10?"); Serial.println("4) Karta vstavlena do dvoinogo shchelchka?"); while (true) { } } Serial.println("Karta primontirovana, rabotaem");} void loop() { }| Симптом | Вероятная причина | Лечение |
|---|---|---|
| false всегда, с первого раза | Карта в exFAT или это SDXC 64+ ГБ | Переформатировать в FAT32 на компьютере; взять карту 2–16 ГБ |
| begin=true, но ошибки при записи | Просадка питания на импульсах записи | Короткие провода питания, конденсатор 100 мкФ у модуля |
| false, светодиод L не мерцает вовсе | Перепутаны MOSI/MISO или не тот CS | Прозвонить линии, сверить номер в SD.begin() с проводом |
| false через раз, то да, то нет | Плавающий контакт в разъёме push-push | Переткнуть карту до двойного щелчка, почистить контакты |
| true в setup, отказы спустя часы | Длинные провода, помехи, изношенная карта | Сократить шину, заменить карту на новую |
| false после перетыкания на горячую | Библиотека монтируется один раз за запуск | Повторный SD.begin() по таймеру — логика ниже в тексте |
Логика попыток в setup — минимум гигиены. Но у логгеров есть сценарий пожёстче: карту вставляют на горячую, когда плата уже работает на объекте. Тогда SD.begin() повторяют из loop() — по таймеру, раз в пять секунд, без единого delay: приёмы неблокирующих таймеров разобраны в уроке про millis вместо delay. Осторожность одна: каждый begin — блокировка на сотни миллисекунд, и в этом окне скетч не читает датчики. Поэтому моё правило — повторная инициализация не чаще раза в пять секунд и только пока флаг cardOk ложный.
Флаг-состояние делает скетч взрослым: bool cardOk; при успехе печатаем состояние и работаем; если запись вдруг перестала удаваться — переводим логгер в режим «нет карты» и снова включаем таймер попыток. Так устроены все мои автономные устройства, и печать состояний в Serial — тот самый метод из урока про отладку логики: строка try=1 ok=0 в журнале честнее любых догадок.
Когда чек-лист и три попытки не помогли, открывайте пример CardInfo из библиотеки: он печатает коды card.init() — например, 0X1,0X0 означает, что карта не ответила даже на сброс, и дело почти наверняка в контакте или её смерти. Коды сухие, но по ним видно, на каком слое сломалось, — та же логика, что у таблицы выше, только глубже.
- 1. Сколько слоёв проверяет один вызов SD.begin()?
- 2. Карта на 64 ГБ вставлена, провода идеальны — SD.begin() возвращает false. Первая гипотеза?
- 3. Почему инициализация занимает сотни миллисекунд?
- 4. Пример CardInfo напечатал код 0X1,0X0. О чём он?
- 5. Логгер должен ловить вставку карты «на горячую». Как это устроить?
- SD.begin() проверяет три слоя — шину, карту, файловую систему; false всегда одинаковый, причины ищите таблицей «симптом — причина — лечение»
- Инициализация занимает 100–500 мс из-за медленного старта протокола; поэтому begin не дёргают в каждом обороте loop()
- Первая гипотеза при false — формат: карты 64+ ГБ и телефонное форматирование дают exFAT, нужен FAT32 и карты 2–16 ГБ
- Три попытки с паузой 2 с ловят случайные сбои; три подряд false — разбирайте железо по чек-листу
- Горячую вставку карты ловит повторный SD.begin() по таймеру millis — не чаще раза в пять секунд, пока флаг cardOk ложный
- Коды из примера CardInfo (0X1 — нет ответа на сброс) сужают круг подозреваемых, когда чек-лист не помог
Когда begin отрабатывает стабильно, начинается самое интересное: данные. Урок про CSV и конфиги покажет, как оформить лог в таблицу, которую открывает Excel, и как менять пороги устройства файлом config.txt с той же карты. А если в журнале нужны настоящие даты и часы, а не счётчик секунд — их даст отдельная пара уроков про часы реального времени.
Домашнее задание: запустите пример CardInfo из библиотеки SD на своей сборке и запишите коды, которые он печатает: тип карты, объём тома, коды card.init(). Потом намеренно устроите сбой — вставьте карту не до щелчка — и сравните, как изменится вывод. Два лога рядом учат читать false быстрее любого учебника.
Частые вопросы по теме урока
- Сколько слоёв проверяет один вызов SD.begin()?
- Шина, диалог с картой, монтирование FAT. Отказ любого слоя даёт один и тот же безликий false. Три: шину SPI, карту и файловую систему
- Карта на 64 ГБ вставлена, провода идеальны — SD.begin() возвращает false. Первая гипотеза?
- Карты от 64 ГБ из коробки exFAT — самая частая причина у новичков. Переформатирование в FAT32 на компьютере лечит. Карта размечена в exFAT — нужен FAT32
- Почему инициализация занимает сотни миллисекунд?
- Свежевключённая карта сначала знакомится на низких частотах, потом идёт диалог CMD0/CMD8/ACMD41 — и только потом монтирование FAT. Протокол требует медленного старта около 400 кГц и диалога из нескольких команд
- Пример CardInfo напечатал код 0X1,0X0. О чём он?
- Код 0X1 значит «карта не прошла сброс»: контакт, мёртвая карта или сломанные провода. Диалог даже не начался. Карта не ответила даже на сброс
- Логгер должен ловить вставку карты «на горячую». Как это устроить?
- Каждый begin блокирует скетч на сотни миллисекунд, поэтому попытки редкие, по неблокирующему таймеру, и только пока карты нет. Повторять SD.begin() из loop() по таймеру millis, не чаще раза в несколько секунд