SD

De ArduWiki
Saltar a: navegación, buscar

Descripción

La librería SD.h de SparkFun habilita un Arduino para la interacción con tarjetas SD (Secure Digital) mediante el protocolo Serial Peripheral Interface (SPI). A pesar de que existen tres tamaños distintos, todos operan exactamente de la misma manera (a determinada capacidad); por lo tanto, esta librería funciona con todos los tamaños.

Debido a que usa SPI, la conexión debe realizarse hacia los respectivos pines del Arduino; salvo SS/CS, este puede conectarse en cualquier pin digital libre.

Nota: la librería soporta tarjetas SD del estándar SC y HC; por esta razón, la capacidad máxima soportada es de 32 GB.


Tip: la librería está preinstalada junto con la IDE de Arduino, no es necesario descargarla.


Nota: debido a limitaciones de la librería y sistema de archivos compatibles, el tamaño máximo por archivo es de 4 GB o 4294967295 bytes.


Una mejor opción sera la libreria SdFat de Bill Greiman que soporta FAT16 y FAT32.

La clase SD

Es un objeto preinstanciado que se utiliza para iniciar la tarjeta y realizar operaciones generales sobre el sistema de archivos (FAT en sus versiones de 16 y 32 bits; exFAT no es compatible).

Sintaxis

#include <SPI.h>
#include <SD.h>
SD.begin(CS);

Parametros

CS
Puerto 4 si es Aduino Shield.

Metodos

Metodos de la clase SD
Método Descripción
SD.begin() Intenta inicializar la tarjeta SD al modo SPI, para así obtener de esta la información necesaria para empezar a usarla.
SD.end() Devuelve la librería al estado inicial; útil para remover, "en caliente" y de forma segura, la tarjeta.
SD.open() Abre un archivo o una carpeta.
SD.exists() Verifica si determinado archivo o carpeta realmente existe en la tarjeta.
SD.remove() Elimina un archivo.
SD.mkdir() Crea un directorio (carpeta).
SD.rmdir() Elimina un directorio (carpeta).

Nota: prácticamente ningún otro método funcionará si no se inicializa de antemano la tarjeta SD.


La clase File

Es un objeto que representa la instancia de un fichero (archivo) o directorio (carpeta) dentro de la tarjeta SD; con este se interactúa directamente sobre ellos. Se crea mediante las funciones SD.open() o File.openNextFile().

Sintaxis

File variable1 = SD.open(nombre, modo);
File variable2 = variable1.openNextFile();

Parámetros

variable
Nombre de variable a instanciar.
nombre
Nombre de archivo SFN (8.3). Ejemplo log12345.txt.
modo
Puede ser FILE_WRITE o FILE_READ

Metodos

Metrodos de la clase File
Método Descripción
File.read() Lee un byte/caracter o varios del archivo.
File.peek() Lee un byte/caracter del archivo sin avanzar al siguiente.
File.available() Retorna la cantidad de bytes/caracteres disponibles para leer (antes de llegar al final del archivo).
File.write() Escribe un byte/caracter o varios al archivo. Suele usarse para archivos binarios.
File.print() Escribe una representación textual de un dato (o una cadena de caracteres) al archivo. Suele usarse para archivos de texto plano.
File.println() Igual al anterior, excepto que agrega una nueva línea de texto.
File.flush() Fuerza la actualización de los datos en la tarjeta SD.
File.seek() Posiciona el "cursor" del archivo hacia una posición dada.
File.position() Retorna la posición actual del "cursor" del archivo.
File.size() Retorna el tamaño del archivo (en bytes).
File.name() Retorna el nombre del archivo/carpeta.
File.isDirectory() Verifica si esta instancia realmente representa un directorio (carpeta) y no un archivo regular.
File.openNextFile() Abre el siguiente archivo de la lista (válido si esta instancia representa un directorio/carpeta).
File.rewindDirectory() Devuelve la iteración de archivos al comienzo (válido si esta instancia representa un directorio/carpeta).
File.close() Cierra el archivo (o carpeta) para liberar correctamente cualquier recurso que esté siendo utilizado por esta instancia.

Nota: Prácticamente ningún método funcionará si la instancia es "vacía" o representa un archivo/carpeta sin abrir.


Formato de la ruta de acceso

Esta librería utiliza el formato de rutas de UNIX/Linux: el nombre de las carpetas involucradas se separa con / (barra inclinada o slash), siendo el destino final el último nombre de la secuencia. Por ejemplo:

datos.txt
ruta/hacia/el/archivo/que/buscamos.txt

Esto hará que el archivo datos.txt sea creado o buscado en el directorio raíz; en otras palabras, fuera de toda carpeta.

Nota: Para efectos del sistema de archivos, los nombres NO SON sensibles a las mayúsculas; por lo tanto, datos.txt, Datos.txt y DATOS.TXT hacen referencia al mismo archivo. Lo mismo aplica para directorios/carpetas.


Siendo ruta, hacia, el, archivo y que las carpetas que nos llevan al archivo buscamos.txt

Ese destino no tiene que ser solamente un archivo regular, también podría ser otra carpeta.

Si es algo que queremos colocar o ubicar en la "raíz", basta con simplemente colocar el nombre del elemento. Por ejemplo:

Nota: ya que el directorio de trabajo de la librería siempre es la raíz, da igual que la ruta sea absoluta (con / al comienzo) o relativa (sin / al comienzo).


Nombres SFN (8.3)

Parte de la escritura de rutas también involucra respetar el formato SFN (Short File Name o nombre corto de archivo), el cuál sigue estas reglas:

  • El nombre del elemento debe tener como mínimo 1 carácter, y como máximo 8 caracteres.
  • Si es un archivo regular, debe haber un único punto (.) justo después del nombre.
  • Si es un archivo regular, deben haber de 1 a 3 caracteres después del antes mencionado punto. Esto es lo que se conoce como la "extensión", una forma burda (pero muy usada en Windows) de etiquetar archivos por su contenido (ej.: txt para texto plano, jpg para imágenes codificadas en JPEG, mp3 para audio codificado en MPEG capa 3, etc.).

Nota: deduciendo de las reglas anteriores, los directorios/carpetas solo deben cumplir la primera; no debe haber punto (.) ni extensión en el nombre.


Caracteres permitidos

En el nombre puede venir cualquier carácter, excepto los siguientes:

  • " * + , / : ; < = > ? \ [ ] |
  • Los que tienen un valor decimal del 0 al 31
  • El carácter de valor decimal 127
  • El punto (.) sólo si no es antecedido por al menos un caracter permitido.

Técnicamente se permite el carácter espaciador y los del ASCII extendido (valor decimal del 128 al 255), sin embargo el primero no se recomienda porque puede hacer más complicado programar la generación de rutas; y lo segundo tampoco ya que la codificación no es estándar entre regiones geográficas (lo que puede llevar a una interpretación errónea del nombre en cuestión), eso sin contar que si el primer carácter es de valor 0xE5 (229 decimal), la librería podría después mal interpretarlo como un archivo eliminado (que no existe).

Curiosamente, las letras minúsculas están prohibidas; sin embargo esto no es cierto al crear e ingresar la ruta, ya que la librería automáticamente usa, en su lugar, las respectivas contrapartes mayúsculas al procesarla.

Comentarios

Hereda de la clase Stream, lo que quiere decir que prácticamente comparte la mayoría de métodos (y funcionalidad) de incluso la clase Serial. Que algunos no sean mencionados en esta página, no quiere decir que no se puedan usar.

Nota: a partir de la versión 1.0 del IDE de Arduino, se permite tener múltiples archivos abiertos a la vez.


Nota: El flujo de entrada y salida de esta clase se categorizan como por bloques.


Nota: Este objeto se puede introducir directamente en un contexto booleano (ej.: como condición de un if... else); se evaluará como verdadero o true si el archivo/carpeta que representa está abierto/a en dicha instancia, falso o false en el caso contrario.


Cómo agregar fecha y hora a los archivos

Todo archivo/fichero y directorio/carpeta tiene por atributos, además del nombre, la fecha y hora de creación, último acceso y última modificación (datos que se pueden mirar en las "propiedades" o "detalles" del elemento, en el explorador de archivos de tu PC). La librería por defecto no tiene definido cómo obtener dicha información; pero cuando es requerido, por defecto coloca el 01/01/2000 1:00:00 am aunque haya transcurrido un tiempo desde que el sistema ha arrancado.

Para definir la obtención de esos datos, primero debes crear una función con la siguiente "firma" (declaración):

void nombreFuncion(uint16_t* fecha, uint16_t* hora)

Quiere decir que dicha función DEBE retornar void y DEBE tener dos parámetros de tipo word o uint16_t*. El nombre no es importante.

En dicha función, programarás la obtención de la fecha y hora actualizados; que pueden provenir de un reloj generado por software, un reloj NTP o un RTC.

Luego, en el setup(), colocas esta línea para así otorgarle dicha definición a la librería:

SdFile::dateTimeCallback(nombreFuncion);

Advertencias

  • Para efectos del sistema de archivos, los nombres NO SON sensibles a las mayúsculas; por lo tanto, datos.txt, Datos.txt y DATOS.TXT hacen referencia al mismo archivo. Lo mismo aplica para directorios/carpetas.
  • Atento al uso de la SRAM, esta librería suele ser intensiva en ese recurso. Inicialmente consume alrededor de 700 bytes de este tipo de memoria.
  • La tarjeta debe estar formateada en FAT16 o FAT32 con SDFormatter (NUNCA CON LOS UTILITARIOS DEL SISTEMA OPERATIVO), de lo contrario no funcionará con la librería o podría tener un bajo rendimiento.

Ejemplo 1

void obtenerFechaHora(uint16_t* fecha, uint16_t* hora) {
  // Aquí llamas al reloj para que actualice los datos

  *fecha = FAT_DATE(anio, mes, dia);
  *hora = FAT_TIME(hora, minuto, segundo);
}

void setup() {
  // Todas las demás inicializaciones
  SdFile::dateTimeCallback(obtenerFechaHora);
}

void loop() {
  // El resto del programa
}

Siendo anio una variable de tipo unsigned int, mientras que el resto de componentes son de tipo byte. El año se agrega con un valor que tenga los cuatro dígitos.

El rango de fecha y hora válido es del 01/01/1980 12:00:00 am al 31/12/2107 11:59:59 pm.

Una vez "adjuntada", esta función se utiliza de manera similar a como ocurre en las interrupciones: implícitamente cuando la librería la necesite.

La fecha/hora de creación se aplica únicamente al momento de crear un archivo/carpeta que antes no existía; la de acceso con el simple hecho de lograr abrirla; y la de modificación cuando se cierra un archivo regular que fue abierto para escritura, o cuando una subcarpeta adquiere o pierde contenido.

Ejemplo 2

Este ejemplo muestra como conectar SD mediante SPI, con MOSI, MISO, CLK, CS (11,12,13,4). CS puede variar segun si es Arduino Shield (4), Adafuit (19, Sparkfun(8) o MKRZero. Es el mismo ejemplo de CardInfo, solo que los mensajes se han traducido al español.

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

Sd2Card card;
SdVolume volume;
SdFile root;

const int chipSelect = 4;   //Arduino Ethernet shield, o cambiar según corresponda.

void setup() {
   Serial.begin(9600);
   Serial.print("Inicializando SD...");
   if (!card.init(SPI_HALF_SPEED, chipSelect)) {
      Serial.println("Error con SD:");
      Serial.println("* la trajeta SD esta puesta ?");
      Serial.println("* los cables estan correctos ?");
      Serial.println("* pin de chipSelect es correcto segun modelo ?");
      while (1);
   } else {
      Serial.println("Conexiones correctas y tarjeta SD en su lugar.");
   }

   Serial.println();
   Serial.print("Tipo de tarjeta:         ");
   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("Desconocido");
   }

   if (!volume.init(card)) {
      Serial.println("No encuentro particion FAT16/FAT32. La tarjeta esta formateada ?");
      while (1);
   }

   Serial.print("Clusteres:          ");
   Serial.println(volume.clusterCount());
   Serial.print("Sectores por Cluster:  ");
   Serial.println(volume.blocksPerCluster());
   Serial.print("Total de sectores:      ");
   Serial.println(volume.blocksPerCluster() * volume.clusterCount());
   Serial.println();
   uint32_t volumesize;
   Serial.print("El tipo de volumen es:    FAT");
   Serial.println(volume.fatType(), DEC);

   volumesize = volume.blocksPerCluster();
   volumesize *= volume.clusterCount();
   volumesize /= 2;                       // Bloques (sectores) de una SD son de 512 bytes (2 bloques abarcan 1 KB o 1024 bytes)
   Serial.print("Tamanio del volumen (KB):  ");
   Serial.println(volumesize);
   Serial.print("Tamanio del volumen (MB):  ");
   volumesize /= 1024;
   Serial.println(volumesize);
   Serial.print("Tamanio del volumen (GB):  ");
   Serial.println((float)volumesize / 1024.0);

   Serial.println("\r\nArchivos en SD (nombre, fecha y tamanio en bytes): ");
   root.openRoot(volume);

   // Mostrar contenido
   root.ls(LS_R | LS_DATE | LS_SIZE);
}

void loop(void) {
   // Nada
}

Ejemplo 3

Data logger.

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

const int chipSelect = 4;

void setup() {
   Serial.begin(9600);
   Serial.print("Inicializando SD...");
   if (!SD.begin(chipSelect)) {
      Serial.println("SD fallo, esta puesta ?");
      while (1);
   }
   Serial.println("SD lista.");
}

void loop() {
   String data = "";
   for (byte analogPin=0; analogPin<3; analogPin++) {
      int sensor = analogRead(analogPin);
      data += String(sensor);
      if (analogPin < 2) {
         data += ",";
      }
   }
   File File = SD.open("datalog.txt", FILE_WRITE);
   if (File) {
      File.println(data);
      File.close();
      Serial.println(data);
   }else{
      Serial.println("Error abriendo datalog.txt");
   }
}

Ejemplo 4

Vea también


Referencias externas