Главная chevron_right Уроки chevron_right Программирование chevron_right Руководство по работе с SD-картами на Arduino

Руководство по работе с SD-картами на Arduino

Подробный разбор примеров чтения и записи данных на SD-карту с помощью Arduino и библиотеки SD.

Статья обновлена 18.11.2021 автором Karl Söderby.

Это руководство собирает совместимое оборудование и практические примеры кода для тех, кто хочет начать работать с картами Secure Digital (SD) на Arduino.

Все примеры взяты из SD Library, которая изначально основана на SdFat Уильяма Гримана (William Greiman).

Необходимое оборудование и ПО

  • Плата Arduino со слотом для SD-карты*
  • Arduino IDE (online или offline)
  • Отформатированная SD-карта

*Платы и шилды со встроенным слотом для SD-карты:

Практические замечания перед началом

  • SD-карту нужно предварительно отформатировать в FAT16 или FAT32. Большинство карт из магазина уже отформатированы, но если возникают проблемы — переформатируйте вручную.
  • Имена файлов должны соответствовать формату 8.3 (не более 8 символов до точки и 3 — после). Длинные имена вроде mylogfile_2024.txt работать не будут — используйте log.txt или data001.txt.
  • Обязательно указывайте правильный пин Chip Select (CS). По умолчанию это пин 4, но для MKR Zero используйте 28 или константу SDCARD_SS_PIN.
  • Всегда закрывайте файл после записи с помощью myFile.close() — иначе данные могут не сохраниться.

Схема подключения

Ниже показано, как вставить SD-карту в слот платы MKR Zero. Ни один из примеров не требует дополнительных компонентов или проводов.

Insert the SD card.

Вставьте microSD-карту в слот на плате Arduino MKR Zero. Убедитесь, что карта вошла до щелчка.

Примеры

Чтение и запись (Read and Write)

Этот пример демонстрирует базовую операцию: запись строки на карту, а затем её чтение и вывод в Serial Monitor.

Как это работает:

  1. В setup() создаётся новый файл через SD.open() с именем "test.txt". Флаг FILE_WRITE открывает файл для чтения и записи, позиционируя курсор в конец. Если файл "test.txt" уже существует на карте — он будет открыт, а не перезаписан с нуля.
  2. Экземпляр открытого файла называется myFile.
  3. После открытия используйте myFile.println(), чтобы записать строку с переносом строки. Затем закройте файл.
  4. Снова откройте файл через SD.open(), прочитайте его содержимое с помощью SD.read() и отправьте в Serial Monitor. По завершении чтения закройте файл командой SD.close().

Важно: пин 4 — это Chip Select (CS) по умолчанию для большинства плат. Для MKR Zero используйте 28 или константу SDCARD_SS_PIN.

/*
  Чтение/запись SD-карты

  Пример показывает, как читать и записывать данные в файл на SD-карте.
  Схема подключения:
   SD-карта подключена к шине SPI следующим образом:
 ** MOSI - pin 11
 ** MISO - pin 12
 ** CLK - pin 13
 ** CS - pin 4 (для MKRZero SD: SDCARD_SS_PIN)

  создан   ноябрь 2010
  автор David A. Mellis
  изменён 9 апр 2012
  автор Tom Igoe

  Код примера находится в открытом доступе.

*/

#include <SPI.h>
#include <SD.h>

File myFile;

void setup() {
  // Открываем последовательный порт и ждём его готовности:
  Serial.begin(9600);
  while (!Serial) {
    ; // ожидание подключения к последовательному порту. Нужно только для плат с native USB
  }


  Serial.print("Initializing SD card...");

  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");

  // открываем файл. Учтите, что одновременно может быть открыт только один файл,
  // поэтому перед открытием другого нужно закрыть этот.
  myFile = SD.open("test.txt", FILE_WRITE);

  // если файл открылся успешно — записываем в него:
  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.println("testing 1, 2, 3.");
    // закрываем файл:
    myFile.close();
    Serial.println("done.");
  } else {
    // если файл не открылся — выводим ошибку:
    Serial.println("error opening test.txt");
  }

  // открываем файл повторно для чтения:
  myFile = SD.open("test.txt");
  if (myFile) {
    Serial.println("test.txt:");

    // читаем файл до конца:
    while (myFile.available()) {
      Serial.write(myFile.read());
    }
    // закрываем файл:
    myFile.close();
  } else {
    // если файл не открылся — выводим ошибку:
    Serial.println("error opening test.txt");
  }
}

void loop() {
  // после setup ничего не происходит
}

Типичные ошибки:

  • Если SD.open() возвращает 0 (false) — проверьте номер CS-пина и правильность форматирования карты.
  • Если в Serial Monitor появляются «кракозябры» — убедитесь, что скорость порта в мониторе совпадает со скоростью, заданной в коде.

---

Информация о карте (Card Information)

Этот пример считывает и выводит в Serial Monitor служебную информацию о SD-карте: тип файловой системы, объём свободного пространства и другие параметры.

/*

  Тест SD-карты

  Пример показывает, как использовать вспомогательные библиотеки, на которых основана

  библиотека SD, чтобы получить информацию о SD-карте.

  Очень полезно для проверки карты, когда не уверен в её работоспособности.

  Номера пинов соответствуют пинам SPI по умолчанию для моделей Uno и Nano.

  Схема подключения:

    SD-карта подключена к шине SPI следующим образом:

 ** SDO - pin 11 на Arduino Uno/Duemilanove/Diecimila

 ** SDI - pin 12 на Arduino Uno/Duemilanove/Diecimila

 ** CLK - pin 13 на Arduino Uno/Duemilanove/Diecimila

 ** CS - зависит от вашего шилда или модуля SD-карты.

        Здесь используется pin 10 для совместимости с другими примерами Arduino

  создан  28 мар 2011

  автор Limor Fried

  изменён 24 июля 2020

  автор Tom Igoe

*/
// подключаем библиотеку SD:
#include <SPI.h>
#include <SD.h>

// объявляем переменные с помощью функций вспомогательной библиотеки SD:

Sd2Card card;

SdVolume volume;

SdFile root;

// измените под свой шилд или модуль SD-карты;
// SPI по умолчанию на Uno и Nano: пин 10
// Arduino Ethernet Shield: пин 4
// Adafruit® SD shields и модули: пин 10
// SparkFun SD Shield: пин 8
// MKRZero SD: SDCARD_SS_PIN

const int chipSelect = 10;

void setup() {

  // Открываем последовательный порт и ждём его готовности:

  Serial.begin(9600);

  while (!Serial) {

    ; // ожидание подключения к последовательному порту. Нужно только для плат с native USB

  }

  Serial.print("\nInitializing SD card...");

  // используем код инициализации из вспомогательных библиотек,

  // так как просто проверяем работоспособность карты!

  if (!card.init(SPI_HALF_SPEED, chipSelect)) {

    Serial.println("initialization failed. Things to check:");

    Serial.println("* is a card inserted?");

    Serial.println("* is your wiring correct?");

    Serial.println("* did you change the chipSelect pin to match your shield or module?");

    while (1);

  } else {

    Serial.println("Wiring is correct and a card is present.");

  }

  // выводим тип карты

  Serial.println();

  Serial.print("Card type:         ");

  switch (card.type()) {

    case SD_CARD_TYPE_SD1:

      Serial.println("SD1");

      break;

    case SD_CARD_TYPE_SD2:

      Serial.println("SD2");

      break;

    case SD_CARD_TYPE_SDHC:

      Serial.println("SDHC");

      break;

    default:

      Serial.println("Unknown");

  }

  // пробуем открыть раздел — он должен быть FAT16 или FAT32

  if (!volume.init(card)) {

    Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");

    while (1);

  }

  Serial.print("Clusters:          ");

  Serial.println(volume.clusterCount());

  Serial.print("Blocks x Cluster:  ");

  Serial.println(volume.blocksPerCluster());

  Serial.print("Total Blocks:      ");

  Serial.println(volume.blocksPerCluster() * volume.clusterCount());

  Serial.println();

  // выводим тип и размер первого FAT-раздела

  uint32_t volumesize;

  Serial.print("Volume type is:    FAT");

  Serial.println(volume.fatType(), DEC);

  volumesize = volume.blocksPerCluster();    // кластеры — это группы блоков

  volumesize *= volume.clusterCount();       // кластеров будет много

  volumesize /= 2;                           // блоки SD-карты всегда 512 байт (2 блока = 1 КБ)

  Serial.print("Volume size (Kb):  ");

  Serial.println(volumesize);

  Serial.print("Volume size (Mb):  ");

  volumesize /= 1024;

  Serial.println(volumesize);

  Serial.print("Volume size (Gb):  ");

  Serial.println((float)volumesize / 1024.0);

  Serial.println("\nFiles found on the card (name, date and size in bytes): ");

  root.openRoot(volume);

  // выводим список всех файлов на карте с датой и размером

  root.ls(LS_R | LS_DATE | LS_SIZE);

  root.close();
}

void loop(void) {
}

Обратите внимание: размер кластера задаётся при форматировании и может отличаться от стандартного. В примере используется стандартный размер блока — 512 байт. Это не размер кластера: размер кластера вычисляется как количество блоков на кластер. Подробнее — in this article.

---

Вывод содержимого файла (Dump File)

Этот пример показывает, как прочитать файл с SD-карты и передать его содержимое через Serial Monitor на компьютер.

На карте должен находиться файл "datalog.txt". В loop() файл открывается вызовом SD.open(). Для последовательной передачи данных используется Serial.print(), которая читает байты файла через SD.read().

Где пригодится: этот подход удобен для отладки логгеров данных — вы можете скачать накопленный лог прямо через Serial Monitor, не вынимая карту из устройства.

/*

  Дамп файла с SD-карты

  Пример показывает, как прочитать файл с SD-карты с помощью

  библиотеки SD и передать его содержимое через последовательный порт.

  Номера пинов соответствуют пинам SPI по умолчанию для моделей Uno и Nano.

  Схема подключения:

   SD-карта подключена к шине SPI следующим образом:

 ** SDO - pin 11

 ** SDI - pin 12

 ** CLK - pin 13

 ** CS - зависит от вашего шилда или модуля SD-карты.

        Здесь используется pin 10 для совместимости с другими примерами Arduino

   (для MKRZero SD: SDCARD_SS_PIN)

  создан  22 декабря 2010

  автор Limor Fried

  изменён 9 апр 2012

  автор Tom Igoe

  Код примера находится в открытом доступе.

*/
#include <SD.h>

const int chipSelect = 10;

void setup() {

  // Открываем последовательный порт и ждём его готовности:

  Serial.begin(9600);

  // ожидание подключения Serial Monitor. Нужно только для плат с native USB:

  while (!Serial);

  Serial.print("Initializing SD card...");

  if (!SD.begin(chipSelect)) {

    Serial.println("initialization failed. Things to check:");

    Serial.println("1. is a card inserted?");

    Serial.println("2. is your wiring correct?");

    Serial.println("3. did you change the chipSelect pin to match your shield or module?");

    Serial.println("Note: press reset or reopen this serial monitor after fixing your issue!");

    while (true);

  }

  Serial.println("initialization done.");

  // открываем файл. Учтите, что одновременно может быть открыт только один файл,

  // поэтому перед открытием другого нужно закрыть этот.

  File dataFile = SD.open("datalog.txt");

  // если файл доступен — читаем из него:

  if (dataFile) {

    while (dataFile.available()) {

      Serial.write(dataFile.read());

    }

    dataFile.close();

  }

  // если файл не открылся — выводим ошибку:

  else {

    Serial.println("error opening datalog.txt");

  }
}

void loop() {
}

---

Управление файлами (File Management)

Этот пример показывает, как создать файл на SD-карте, убедиться в его существовании и затем удалить его.

В setup() создаётся новый файл через SD.open() с именем "example.txt". Флаг FILE_WRITE открывает файл для чтения и записи с позицией в конце. В этом примере файл сразу закрывается командой myFile.close() — нам важен сам факт его создания.

Далее наличие файла проверяется с помощью SD.exists(), после чего файл удаляется с карты командой SD.remove.

Практическое применение: такой подход полезен, когда нужно периодически очищать старые лог-файлы или управлять конфигурационными файлами прямо из скетча.

/*

  Базовый пример работы с файлом на SD-карте

  Пример показывает, как создать и удалить файл на SD-карте.

  Номера пинов соответствуют пинам SPI по умолчанию

  для моделей Uno и Nano:

   SD-карта подключена к шине SPI следующим образом:

 ** SDO - pin 11

 ** SDI - pin 12

 ** CLK - pin 13

 ** CS - зависит от вашего шилда или модуля SD-карты.

        Здесь используется pin 10 для совместимости с другими примерами Arduino

    (для MKRZero SD: SDCARD_SS_PIN)

  создан   ноябрь 2010

  автор David A. Mellis

  изменён 24 июля 2020

  автор Tom Igoe

  Код примера находится в открытом доступе.

*/
#include <SD.h>

const int chipSelect = 10;

File myFile;

void setup() {

  // Открываем последовательный порт и ждём его готовности:

  Serial.begin(9600);

  // ожидание подключения Serial Monitor. Нужно только для плат с native USB:
while (!Serial);

  Serial.print("Initializing SD card...");

  if (!SD.begin(10)) {

    Serial.println("initialization failed!");

    while (1);

  }

  Serial.println("initialization done.");

  if (SD.exists("example.txt")) {

    Serial.println("example.txt exists.");

  } else {

    Serial.println("example.txt doesn't exist.");

  }

  // создаём новый файл и сразу закрываем его:

  Serial.println("Creating example.txt...");

  myFile = SD.open("example.txt", FILE_WRITE);

  myFile.close();

  // проверяем, существует ли файл:

  if (SD.exists("example.txt")) {

    Serial.println("example.txt exists.");

  } else {

    Serial.println("example.txt doesn't exist.");

  }

  // удаляем файл:

  Serial.println("Removing example.txt...");

  SD.remove("example.txt");

  if (SD.exists("example.txt")) {

    Serial.println("example.txt exists.");

  } else {

    Serial.println("example.txt doesn't exist.");

  }
}

void loop() {

  // после завершения setup ничего не происходит.
}

---

Список файлов (List Files)

Этот пример выводит список всех файлов и директорий, находящихся на SD-карте.

Основной loop() ничего не делает в цикле, потому что функция вывода содержимого директории "/" вызывается один раз — из setup(). Это сделано намеренно: список файлов нужно посмотреть один раз при старте, а не повторять бесконечно.

Функция printDirectory рекурсивно обходит все записи и выводит в Serial Monitor имя каждого файла и директории. Для файлов дополнительно отображается их размер в байтах.

Как проверить результат: откройте Serial Monitor сразу после загрузки скетча. Если карта подключена правильно и содержит файлы — вы увидите их список. Пустой вывод или сообщение об ошибке инициализации говорит о проблеме с CS-пином или форматом карты.

/*

  Listfiles

  Пример показывает, как вывести список файлов в директории

  на SD-карте. Номера пинов соответствуют пинам SPI по умолчанию

  для моделей Uno и Nano.

  Схема подключения:

   SD-карта подключена к шине SPI следующим образом:

 ** SDO - pin 11

 ** SDI - pin 12

 ** CLK - pin 13

 ** CS - зависит от вашего шилда или модуля SD-карты.

        Здесь используется pin 10 для совместимости с другими примерами Arduino

    (для MKRZero SD: SDCARD_SS_PIN)

  создан   ноябрь 2010

  автор David A. Mellis

  изменён 9 апр 2012

  автор Tom Igoe

  изменён 2 фев 2014

  автор Scott Fitzgerald

  изменён 24 июля 2020

  автор Tom Igoe



  Код примера находится в открытом доступе.

*/
#include <SD.h>

const int chipSelect = 10;

File root;

void setup() {

 // Открываем последовательный порт и ждём его готовности:

  Serial.begin(9600);

  // ожидание подключения Serial Monitor. Нужно только для плат с native USB:

  while (!Serial);

  Serial.print("Initializing SD card...");

  if (!SD.begin(chipSelect)) {

    Serial.println("initialization failed. Things to check:");

    Serial.println("1. is a card inserted?");

    Serial.println("2. is your wiring correct?");

    Serial.println("3. did you change the chipSelect pin to match your shield or module?");

    Serial.println("Note: press reset or reopen this serial monitor after fixing your issue!");

    while (true);

  }

  Serial.println("initialization done.");

  root = SD.open("/");

  printDirectory(root, 0);

  Serial.println("done!");
}

void loop() {

  // после завершения setup ничего не происходит.
}

void printDirectory(File dir, int numTabs) {

  while (true) {

    File entry =  dir.openNextFile();

    if (! entry) {

      // файлов больше нет

      break;

    }

    for (uint8_t i = 0; i < numTabs; i++) {

      Serial.print('\t');

    }

    Serial.print(entry.name());

    if (entry.isDirectory()) {

      Serial.println("/");

      printDirectory(entry, numTabs + 1);

    } else {

      // у файлов есть размер, у директорий — нет

      Serial.print("\t\t");

      Serial.println(entry.size(), DEC);

    }

    entry.close();

  }
}