[161] Karta SD cz. 1

O kartach SD pisałem niedawno, ale temat ograniczyłem do konkretnego zastosowania takiej karty – była nośnikiem dźwięków, które Arduino odtwarzało. Czas wrócić do tematu i przyjrzeć się tej pamięci dokładniej. W maleństwie widocznym na zdjęciu, o wymiarach centymetr na półtora, mieszczą się prawie 2 biliony dwieście miliardów bajtów. Nawet przy upakowaniu 4 bitów w jednej komórce, jest ich tam ponad 4 biliony. Można tam zmieścić na przykład wszystkie istotne dane wszystkich mieszkańców ziemi. Albo 20 milionów książek. Największa biblioteka świata ma ich półtora razy więcej. Dla mnie jest to… co najmniej intrygujące. Ale nie od razu tak było, pierwsze karty pamięci sięgały pojedynczych megabajtów, nie mówiąc o ich protoplastach, które po prostu były pamięciami statycznymi z pokładową baterią.


Czasy różnorodności kart szczęśliwie są już za nami. Wygrał format, można chyba rzec: najlepszy. Ewoluował, kilka razy malał, ale zachował kompatybilność w dół, mechaniczną w pełni, a funkcjonalną w pewnym, wystarczającym jednak praktycznie zakresie. I choć karty SD już pojawiały się w kilku artykułach, dziś będę chciał się nimi zająć stricte.

Jak chyba każdy wie, karty SD występują w kilku kształtach, z czego szerzej znane są dwa: historyczny – duży i współczesny: micro. Do ostatnich zwykle dodawano przejściówkę, z której dawno temu można było zbudować interfejsy, bezkarnie lutując przewody do styków. Ale to było w czasach, gdy jeszcze fabrycznych interfejsów nie sprzedawano i dziś to trochę sensu już nie ma. Chcąc używać kart z Arduino, należy za kilka złotych kupić sobie taką płytkę.

Zwykle siedzi tam także konwerter napięć. Otóż małą niedogodnością jest praca w logice trzywoltowej. Ośmiobitowe Arduino pracują zwykle w pięciu woltach i by spiąć ze sobą urządzenia, potrzebujemy dopasować poziomy. Można to zrobić w prymitywny sposób, za pomocą kilku rezystorów. Będzie to działać dobrze, ale profesjonalne rozwiązania wykorzystują specjalizowane układy konwerterów. Szczegóły połączeń opisałem w tym artykule, więc nie będę się powtarzać.

Karty SD porozumiewają się ze światem kilkoma narzeczami, z których nowoczesne nie będą dostępne dla ośmiobitowych Arduino. Na szczęście kompatybilność oferuje najstarszy interfejs, SPI. Arduino dysponuje nim sprzętowo, a jedyna niedogodność polega na braku możliwości przeorganizowania trzech z czterech linii: piny 11, 12 i 13 będą na stałe przyporządkowane do karty. Skoro wiemy już wszystko co nam będzie potrzebne, podłączmy kartę zgodnie ze sztuką i przetestujmy podstawowe operacje z nią związane. Czwarta linia – dostępu – może być podłączona gdziekolwiek, użyję pinu czwartego. Interfejs widoczny na zdjęciu do pracy potrzebuje pięciu woltów.

Ze względu na zawiły sposób komunikacji z kartami wykorzystuje się tutaj biblioteki. Jest ich kilka, oferują różną liczbę opcji kosztem zasobów. Znajdziemy także ograniczone rozwiązania specjalizowane, na przykład współpracujące z kartami tylko w ramach pobierania danych audio i taki sposób już przedstawiałem. Domyślna biblioteka obsługi kart SD jest już osadzona w środowisku i tej się przyjrzymy, modyfikując na nasze potrzeby przykładowe pliki.

#include <SD.h>  // Biblioteka obsługi karty SD
Sd2Card card;
SdVolume volume;
SdFile root;
const byte cardCS = 4;  // Deklaracja adresu linii CS4

void setup() {
  Serial.begin(115200);                                      // Inicjuj port monitora.
  Serial.println(F("\
Czekam na sygnał z karty SD..."));     // Wyślij komunikat.
  if (!card.init(SPI_HALF_SPEED, cardCS)) {                  // Inicjuj kartę w trybie SPI z połową szybkości, określając port CS
    Serial.println(F("Niepowodzenie, nie wykryto karty."));  // Karta nie została wykryta.
    while (1)                                                // Koniec programu.
      ;
  } else {
    Serial.println(F("Powodzenie, wykryto kartę."));  // Karta została wykryta.
  }
  Serial.print(F("\
Typ karty: "));
  switch (card.type()) {    // Określ typ karty.
    case SD_CARD_TYPE_SD1:  // SD wersja 1
      Serial.println(F("SD v1 (FAT, < 2 GB)"));
      break;
    case SD_CARD_TYPE_SD2:  // SD wersja 2
      Serial.println(F("SD v2 (FAT32, < 32 GB)"));
      break;
    case SD_CARD_TYPE_SDHC:  // SDHC
      Serial.println(F("SDHC (FAT32, < 32 GB)"));
      break;
    default:
      Serial.println(F("Nieznany."));  // Typ karty nierozpoznany.
  }
  if (!volume.init(card)) {  // Inicjacja systemu plików na karcie.
    Serial.println(F("Nie wykryto systemu FAT lub FAT32"));
    while (1)  // Koniec programu.
      ;
  }
  unsigned long blocksPerClusterVolume = volume.blocksPerCluster();  // Liczba bloków na klaster.
  Serial.print(F("\
Rozmiar klastra: "));
  Serial.print(blocksPerClusterVolume * 512);  // Rozmiar klastra.
  Serial.print(F(" B\
Liczba klastrów: "));
  Serial.println(volume.clusterCount());  // Liczba klastrów.
  Serial.print(F("Liczba bloków na klaster: "));
  Serial.println(blocksPerClusterVolume);  // Liczba bloków na klaster.
  Serial.print(F("Liczba wszystkich bloków: "));
  Serial.println(blocksPerClusterVolume * volume.clusterCount());  // Liczba wszystkich bloków.

  Serial.print(F("\
System plików: FAT"));
  Serial.println(volume.fatType(), DEC);                                             // Typ systemu plików.
  unsigned long cardSize = (volume.blocksPerCluster() * volume.clusterCount()) / 2;  // Pojemność karty w kB
  Serial.print(F("\
Rozmiar karty (kB): "));
  Serial.println(cardSize);
  Serial.print(F("Rozmiar karty (MB): "));
  Serial.println(cardSize / 1024);
  Serial.print(F("Rozmiar karty (GB): "));
  Serial.println((float)(cardSize / 1024) / 1024.0, 2);

  Serial.println(F("\
Zawartość karty:"));
  Serial.println(F("----Nazwa----|---Data---|--Czas--|-Rozmiar"));
  root.openRoot(volume);              // Otwarcie głównego katalogu.
  root.ls(LS_R | LS_DATE | LS_SIZE);  // Listing: z podkatalogami, z atrybutem czasu, z atrybutem rozmiaru.
  root.close();                       // Zamknięcie głównego katalogu.
}
void loop(void) {}

Zadaniem pierwszego szkicu jest test karty. Zwróci on nam istotne informacje na jej temat, pozwalając w razie potrzeby sformatować ją w trybie kompatybilnym z biblioteką. Trzeba pamiętać, że generalnie biblioteki wspierają tylko formaty FAT i FAT32, pojemności do 32 GB i system nazw pochodny z DOS-u, a więc składających się z ośmioznakowej nazwy i trzech znaków rozszerzenia. Unikajmy także polskich liter i innych, dziwnych znaczków. Biblioteka SD.h wspiera katalogi, ale niekoniecznie będą to robić biblioteki lekkie. Przejdźmy więc do analizy.

#include <SD.h>  // Biblioteka obsługi karty SD
Sd2Card card;
SdVolume volume;
SdFile root;
const byte cardCS = 4;  // Deklaracja adresu linii CS4

void setup() {
  Serial.begin(115200);                                      // Inicjuj port monitora.
  Serial.println(F("\
Czekam na sygnał z karty SD..."));     // Wyślij komunikat.
  if (!card.init(SPI_HALF_SPEED, cardCS)) {                  // Inicjuj kartę w trybie SPI z połową szybkości, określając port CS
    Serial.println(F("Niepowodzenie, nie wykryto karty."));  // Karta nie została wykryta.
    while (1)                                                // Koniec programu.
      ;
  } else {
    Serial.println(F("Powodzenie, wykryto kartę."));  // Karta została wykryta.
  }

Na początku oczywiście zaczytujemy bibliotekę SD.h obsługującą karty SD i deklarujemy nazwy obiektów z nią związanych. Kolejno deklarujemy port dostępu, czyli dla linii CS. Jak mówiłem, w przeciwieństwie do pozostałych trzech, może on być obsługiwany dowolnym wolnym pinem. Ponieważ wszystkie testy będziemy przeprowadzać korzystając z monitora, należy go zainicjować, po czym można będzie wysłać komunikat oczekiwania na sygnał z karty. Następnie inicjujemy kartę do pracy z interfejsem SPI, z szybkością połowy maksymalnej, co jest zalecane przy współpracy z ośmiobitowym Arduino. W razie potrzeby mamy jeszcze 1/4 i 1/8 szybkości, ale obecnie tak kiepskich kart się nie spotyka. Pełną szybkość można wybrać po weryfikacji konkretnego przypadku. Drugim argumentem funkcji jest adres pinu dostępu, czyli linii CS. W razie niepowodzenia inicjacji pojawi się stosowny komunikat i program pozostanie w tym miejscu. Jeśli karta dogada się z Arduino, program powiadomi nas o tym i przejdzie do kolejnych testów.

Serial.print(F("\
Typ karty: "));
switch (card.type()) {    // Określ typ karty.
  case SD_CARD_TYPE_SD1:  // SD wersja 1
    Serial.println(F("SD v1 (FAT, < 2 GB)"));
    break;
  case SD_CARD_TYPE_SD2:  // SD wersja 2
    Serial.println(F("SD v2 (FAT32, < 32 GB)"));
    break;
  case SD_CARD_TYPE_SDHC:  // SDHC
    Serial.println(F("SDHC (FAT32, < 32 GB)"));
    break;
  default:
    Serial.println(F("Nieznany."));  // Typ karty nierozpoznany.
}
if (!volume.init(card)) {  // Inicjacja systemu plików na karcie.
  Serial.println(F("Nie wykryto systemu FAT lub FAT32"));
  while (1)  // Koniec programu.
    ;
}

Teraz określimy typ karty i system plików. Interesują nas pierwsze trzy generacje kart, oznaczane jako SD wersja pierwsza, druga i SDHC. Pierwszy przypadek jest najstarszy i wspiera karty do 2 GB. Domyślnym systemem plików jest FAT. Drugi typ daje dostęp do 32 GB, więc wymaga systemu FAT32. SDHC wspiera pojemności od 4 do 32 GB, także wymaga systemu FAT32 i tak naprawdę różni się wewnętrzną architekturą struktury plików, co dla nas będzie nieistotne. Inne systemy plików, w szczególności exFAT nie są wspierane i jeśli osadzimy tak sformatowaną kartę, dostaniemy stosowny komunikat.

unsigned long blocksPerClusterVolume = volume.blocksPerCluster();  // Liczba bloków na klaster.
Serial.print(F("\
Rozmiar klastra: "));
Serial.print(blocksPerClusterVolume * 512);  // Rozmiar klastra.
Serial.print(F(" B\
Liczba klastrów: "));
Serial.println(volume.clusterCount());  // Liczba klastrów.
Serial.print(F("Liczba bloków na klaster: "));
Serial.println(blocksPerClusterVolume);  // Liczba bloków na klaster.
Serial.print(F("Liczba wszystkich bloków: "));
Serial.println(blocksPerClusterVolume * volume.clusterCount());  // Liczba wszystkich bloków.

Kolejny blok oblicza nam: rozmiar klastra, ich liczbę, liczbę bloków na klaster i sumę wszystkich bloków. Klaster jest jak gdyby kartką papieru do zapisania. Możemy zapisać tylko jedną literę – jeden bajt – a i tak musimy użyć całej kartki. Jeśli liczba liter – czyli u nas rozmiar pliku – przekroczy pojemność klastra, będziemy musieli sięgnąć do kolejnego i tak dalej, nawet gdy braknie miejsca tylko dla jednego nadmiarowego bajtu.

Liczba bloków na klaster określa jego rozmiar. Klaster może składać się minimalnie z jednego bloku, a maksymalnie z 64. Warunkowo ze 128. Blok zawsze zawiera 512 bajtów, więc klaster może przyjmować rozmiar od tej wartości do 32 kB (warunkowo do 64). Im większy klaster, tym więcej miejsca zmarnujemy – pamiętajmy, że nawet jeden bajt zapisu zaokrąglany jest do pełnej pojemności klastra. Z drugiej strony deklarowanie zbyt małego klastra zwolni pracę z kartą. Deklaracje rozmiaru klastra możemy przeprowadzić podczas formatowania karty. W końcu program podaje nam liczbę wszystkich bloków, czyli obszarów po 512 bajtów.

Serial.print(F("\
System plików: FAT"));
Serial.println(volume.fatType(), DEC);                                             // Typ systemu plików.
unsigned long cardSize = (volume.blocksPerCluster() * volume.clusterCount()) / 2;  // Pojemność karty w kB
Serial.print(F("\
Rozmiar karty (kB): "));
Serial.println(cardSize);
Serial.print(F("Rozmiar karty (MB): "));
Serial.println(cardSize / 1024);
Serial.print(F("Rozmiar karty (GB): "));
Serial.println((float)(cardSize / 1024) / 1024.0, 2);

Kolejny blok szkicu określa rodzaj systemu plików oraz pojemność karty w kB, MB i GB. System plików zwraca zmienna fatType, a rozmiary liczymy ze zmiennych poznanych przed chwilą, przy czym widoczny wzór oblicza pojemność w kilobajtach. Pozostałe jednostki otrzymujemy z dzieleń, a dla gigabajtów używamy wartości ułamkowych z dwoma miejscami po przecinku. I to już wszystko jeśli chodzi o strukturę karty.

  Serial.println(F("\
Zawartość karty:"));
  Serial.println(F("----Nazwa----|---Data---|--Czas--|-Rozmiar"));
  root.openRoot(volume);              // Otwarcie głównego katalogu.
  root.ls(LS_R | LS_DATE | LS_SIZE);  // Listing: z podkatalogami, z atrybutem czasu, z atrybutem rozmiaru.
  root.close();                       // Zamknięcie głównego katalogu.

Teraz program wylistuje jej zawartość. Zacząć należy od otwarcia głównego katalogu karty – po to, by móc w nim przeprowadzać operacje, w naszym przypadku – odczytu. Następnie wysyłamy rozkaz wyświetlenia zawartości, przy czym pierwszy argument każe analizować także wszystkie podkatalogi, drugi dodatkowo wyświetla czas zmiany zawartości plików, trzeci – ich rozmiary. Zawsze po użyciu karty należy zamknąć dostęp tą komendą.

Przetestujmy sobie teraz kilka kart. Na początek – taka prastara, o pojemności 64 MB, wyciągnięta z jakieś dwudziestowiecznej Nokii.

Czekam na sygnał z karty SD...
Powodzenie, wykryto kartę.
Typ karty: SD v1 (FAT, < 2 GB)
Rozmiar klastra: 1024 B
Liczba klastrów: 61056
Liczba bloków na klaster: 2
Liczba wszystkich bloków: 122112
System plików: FAT16
Rozmiar karty (kB): 61056
Rozmiar karty (MB): 59
Rozmiar karty (GB): 0.06
Zawartość karty:
----Nazwa----|---Data---|--Czas--|-Rozmiar
KATALOG/ 2026-03-29 20:06:04
PLIK_3.TXT 2026-03-29 20:07:08 12
PLIK_1.TXT 2026-03-29 20:04:20 11
PLIK_2.TXT 2026-03-29 20:05:38 10

Jak widać, tak małe pojemności pracują w systemie FAT. Na tej i na kolejnej karcie umieściłem trzy pliki, w tym jeden w katalogu, po to, byśmy mogli przetestować je także czysto praktycznie. Listing ukazuje się w postaci typowego drzewka, co niestety przesuwa formatowanie.

Następna karta ma pojemność 2 GB, co było granicą w pierwszych latach ich używania.

Czekam na sygnał z karty SD...
Powodzenie, wykryto kartę.
Typ karty: SD v2 (FAT32, < 32 GB)
Rozmiar klastra: 32768 B
Liczba klastrów: 61109
Liczba bloków na klaster: 64
Liczba wszystkich bloków: 3910976
System plików: FAT16
Rozmiar karty (kB): 1955488
Rozmiar karty (MB): 1909
Rozmiar karty (GB): 1.86
Zawartość karty:
----Nazwa----|---Data---|--Czas--|-Rozmiar
KATALOG/ 2026-03-29 20:06:04
PLIK_3.TXT 2026-03-29 20:07:08 12
PLIK_1.TXT 2026-03-29 20:04:20 11
PLIK_2.TXT 2026-03-29 20:05:38 10

O ile w poprzednim przypadku rozmiar klastra wynosił kilobajt, tutaj osiągnął największą wartość: 32 kB. To duża strata przy zapisie małych plików, jakie w świecie Arduino mają zwykle miejsce, na przykład podczas zrzucania jakichś wartości mierzonych cyklicznie. Dlatego warto takie karty formatować w systemie FAT32. Spójrzmy na kolejny przykład.

Czekam na sygnał z karty SD...
Powodzenie, wykryto kartę.
Typ karty: SDHC (FAT32, < 32 GB)
Rozmiar klastra: 4096 B
Liczba klastrów: 964992
Liczba bloków na klaster: 8
Liczba wszystkich bloków: 7719936
System plików: FAT32
Rozmiar karty (kB): 3859968
Rozmiar karty (MB): 3769
Rozmiar karty (GB): 3.68
Zawartość karty:
----Nazwa----|---Data---|--Czas--|-Rozmiar
KATALOG/ 2026-03-29 20:06:04
PLIK_3.TXT 2026-03-29 20:07:08 12
PLIK_1.TXT 2026-03-29 20:04:20 11
PLIK_2.TXT 2026-03-29 20:05:38 10

Ta karta jest dwukrotnie większa, a klaster zmalał ośmiokrotnie. Taka wartość – 4 kB – jest optymalna ze względu na zużycie miejsca i szybkość dostępu do danych.

Maksymalna pojemność, jaką obsługuje ta biblioteka to 32 GB. Być może dało by się pracować z kartami o dwukrotnie większej pojemności, ale nie mogę tego potwierdzić, a karty o takiej pojemności nie posiadam. W takiej sytuacji należy po prostu użyć innej biblioteki, jak na przykład SdFat. Spójrzmy na raport po wstawieniu karty 32 GB

Czekam na sygnał z karty SD...
Powodzenie, wykryto kartę.
Typ karty: SDHC (FAT32, < 32 GB)
Rozmiar klastra: 16384 B
Liczba klastrów: 1946656
Liczba bloków na klaster: 32
Liczba wszystkich bloków: 62292992
System plików: FAT32
Rozmiar karty (kB): 31146496
Rozmiar karty (MB): 30416
Rozmiar karty (GB): 29.70
Zawartość karty:
----Nazwa----|---Data---|--Czas--|-Rozmiar
KATALOG/ 2026-03-29 20:06:04
PLIK_3.TXT 2026-03-29 20:07:08 12
PLIK_1.TXT 2026-03-29 20:04:20 11
PLIK_2.TXT 2026-03-29 20:05:38 10

Klaster urósł czterokrotnie, ale możemy podczas formatowania wymusić zmniejszenie jego rozmiaru do 8 kB. A co będzie, gdy wstawimy kartę sformatowaną w systemie exFAT, NTFS bądź zbyt dużą – w tym wypadku 256 GB?

Czekam na sygnał z karty SD...
Powodzenie, wykryto kartę.
Typ karty: SDHC (FAT32, < 32 GB)
Nie wykryto systemu FAT lub FAT32

Dostaniemy lakoniczny komunikat, choć może nieco mylić opis. Dokładniejszych danych trzeba szukać, podłączając kartę do komputera. Na dziś to wszystko, w kolejnym artykule spróbujemy kartę wykorzystać praktycznie.

Płytka edukacyjna TME-EDU-ARD-2Płytka edukacyjna TME-EDU-ARD-2

Inne artykuły z tej kategorii

Nasi partnerzy

TMETech Master EventTME EducationPoweredby
Copyright © 2026 arduino.pl