domingo, 13 de enero de 2019

Leer y escribir en una memoria SD

Después de mucho tiempo voy a dedicarle un post a las memorias SD. Las memorias SD nos van a permitir escribir y leer datos de forma no volátil y que perduren a través del tiempo sin necesidad de utilizar la memoria EEPROM. Esto tiene muchas ventajas como la gran capacidad de almacenamiento, el reemplazo rápido de la memoria, el uso de un sistema de archivos nos permite exportar e importar datos con una computadora, etc.

Para esto vamos a utilizar una librería que viene incluida con el software de arduino y algún módulo que nos facilite el cableado entre el microcontrolador y la tarjeta de memoria, en mi caso voy a utilizar alguna de las pantallas que tengo que ya vienen con lectores de memorias SD, aunque no vamos a sacar imágenes de la memoria para mostrar en la pantalla, si vamos a cargar una página web y a almacenar datos en la tarjeta.

Primero vamos a ver el ejemplo del almacenamiento de datos, utilizando el siguiente código:

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

const int selectorChip = 4;

void setup() {
  
  Serial.begin(9600);
  while(!Serial)
  {
    ;
  }

  Serial.println("inicializando tarjeta SD");

  //intentamos inicializar la memoria
  if(!SD.begin(selectorChip))
  {
    Serial.println("Fallo el inicio de la tarjeta.");
    //no hace nada más
    return;
  }
  Serial.println("Tarjeta iniciada.");
}

void loop()
{
  //genero los datos
  String datos = "milisegundos desde encendido: " + String(millis());
  //abro el archivo en modo escritura
  File archivo = SD.open("datos.txt", FILE_WRITE);
  //si se abrió correctamente guardo los datos.
  if(archivo)
  {
    archivo.println(datos);
    archivo.close();
  }
  else
  {
    Serial.println("error al intentar abrir el archivo datos.txt");
  }
}


Para el siguiente ejemplo vamos a utilizar un ESP8266 y el código ya utilizado en esta entrada


#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>

#include <SPI.h>
#include <SD.h>
File archivo;

ESP8266WebServer servidorweb(80);

const char* red = "red"/*ingrese aquí el nombre de id*/;
const char* contra = "contra"/*ingrese aquí la contraseña*/;
String HTMLpage = "";

void setup() {

  Serial.begin(115200);
  pinMode(2, OUTPUT);

  Serial.print("Inicializando tarjeta SD");

  if (!SD.begin(4)) {
    Serial.println("Fallo al incializar");
    return;
  }
  Serial.println("Tarjeta iniciada con exito");

  WiFi.begin(red, contra);

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  
  Serial.println("");
  Serial.print("Conectado a");
  Serial.println(red);
  Serial.print("Dirección IP:");
  Serial.println(WiFi.localIP());
  
  servidorweb.on("/", []()
  {
    String estado = servidorweb.arg("estado");
    if(estado == "ON")
    {
      digitalWrite(2, LOW);
    }
    else if(estado == "OFF")
    {
      digitalWrite(2, HIGH);
    }
    servidorweb.send(200, "text/html", paginaWeb());
  });

  servidorweb.begin();
}

void loop() {
  
  servidorweb.handleClient();
  
}

String paginaWeb()
{
  String pagina = "";
  archivo = SD.open("pagina.txt");
  if(archivo)
  {
    //leo el archivo mientras esté disponible
    while(archivo.available())
    {
      //leo cada byte del archivo mientras esté disponible
      pagina +=archivo.read();
    }

    //cierro el archivo
    archivo.close();
    return pagina;
  }
}


Si queremos cargar otras páginas lo único que tenemos que hacer es "llamar" distintos archivos y modificar la función para que recupere otras cosas.

Hay que aclarar que la librería "SD" para el ESP8266 no es la misma que usamos normalmente para los arduinos, por lo que hay que especificar la ruta de la librería de la siguiente forma:

#include "c:\SD.h"

También lo que se puede hacer es poner la librería en la misma carpeta que el programa y de ahí "llamarla" colocando:

#include "SD.h"

En lugar de:

#include <SD.h>

De esta forma lo que conseguimos es que el preprocesador busque dentro de la carpeta del programa en lugar de la carpeta en donde se guardan las librerías normalmente.

De todas formas al ser tantos archivos que comparten el mismo nombre lo mejor va a ser modificar lo lo que diga SD por SD-esp de los siguientes ítem:
  • La carpeta (SD) 
  • El archivo SD.h (SD/src/SD.h)
  • El archivo SD.cpp (SD/src/SD.cpp)
  • La línea 53 del archivo SD.cpp (SD/src/SD.cpp)
  • La línea 15 del archivo File.cpp (SD/src/File.cpp)
Entonces en lugar de utilizar "#include <SD.h>" usaremos "#include <SD-esp.h>" (hay que modificar eso del programa).

Otro punto que hay que aclarar es la extensión del archivo, dado que la librería trabaja (por lo que tengo entendido) con nombres de archivo del estilo 8.3, es decir que el nombre puede tener un máximo de 8 caracteres y la extensión de 3, es por eso que no podemos utilizar un archivo ".html", pero como al final de cuentas a página es sólo texto, podemos utilizar un archivo de texto normal.

domingo, 6 de enero de 2019

Arduino con JSON

Hoy les traigo una librería capaz de integrar Json a arduino, es decir, la librería se encarga de decodificar texto en variables y viceversa. Con esta librería, por ejemplo, vamos a ser capaces de adquirir datos desde la red y utilizarlos fácilmente en nuestro arduino (sea cual sea). La idea de esta librería es facilitar la adquisición de datos de la red desde nuestro arduino.

Json tiene la siguiente sintaxis:

{"nombreDeLaVariable":"valor"}

Para más de un dato va a figurar de la siguiente forma:

{"nombre1":"valor1", "nombre2":"valor2", "nombre3":"valor3"}

Y un arreglo de datos se vería de la siguiente manera:

{"nombreGeneral":[{"nombre1":"valor1", "nombre2":"valor2"}, {"nombre1":"valor3", "nombre2":"valor4"}, {"nombre1":"valor5", "nombre2":"valor6"}]}

Aunque por lo general el Json de arriba se declara de la siguiente forma:

{
   "nombreGeneral":[
      {"nombre1":"valor1", "nombre2":"valor2"},
      {"nombre1":"valor3", "nombre2":"valor4"},
      {"nombre1":"valor5", "nombre2":"valor6"}
   ]
}

Esto es para que sea más legible dentro del programa. Por lo general le asignamos estas cadenas a alguna variable, quedando de la forma:

variable = {
   "nombreGeneral":[
      {"nombre1":"valor1", "nombre2":"valor2"},
      {"nombre1":"valor3", "nombre2":"valor4"},
      {"nombre1":"valor5", "nombre2":"valor6"}
   ]
}

Por lo tanto si queremos acceder a "valor6" utilizaríamos la siguiente cadena:

variable.nombreGeneral[2].nombre2

En donde vamos "accediendo" a los distintos niveles del objeto hasta llegar a la variable deseada. Esto puede parecer un poco confuso para el ejemplo que dí, por lo que acá les dejo otro, en donde vamos a englobar los vegetales y los vamos a separar entre frutas y verduras, y a cada vegetal le vamos a dar un nombre y un sabor:

vegetales = {
   "verduras":[
      {"nombre":"lechuga", "sabor":"semi amargo"},
      {"nombre":"zanahoria", "sabor":"semi dulce"},
      {"nombre":"radicheta", "sabor":"amargo"}
   ],
   "frutas":[
      {"nombre":"limón", "sabor":"ácido"},
      {"nombre":"mandarina", "sabor":"dulce"},
      {"nombre":"pomelo", "sabor":"amargo"}
   ]
}

Entonces si queremos obtener el sabor de la mandarina siempre vamos a comenzar escribiendo el nombre de nuestra variable, que en este caso es "vegetales", luego vamos por el siguiente identificador, que es una fruta, y sabemos que la mandarina se encuentra en la posición 1 (porque se empieza contando desde el 0), entonces nos dirigimos a esa posición, y por último vamos al sabor, entonces nos quedaría de la forma: "vegetales.frutas[1].sabor".
La estructura que yo definí no es única, por lo que no hay límites de identificadores que coloquemos, a las verduras podríamos agregarle el color o la textura, mientras que a las frutas podríamos ponerle la cantidad de azúcar en gramos o la forma.
El Json es muy versátil y tiene muchas aplicaciones más sobre todo en javaScript, dónde podemos poner hasta funciones como valor.

Yendo a arduino, acá les dejo un código que utiliza los vegetales de arriba y muestra todos los datos por puerto serie.


#include <ArduinoJson.h>

void setup() {
  // Inicialiazmos el puerto serie
  Serial.begin(9600);
  while(!Serial){}

  /*
   * Acá le asignamos una memoria para que la librería
   * trabaje.
   * 
   * Dentro de los símbolos de mayor y menos le vamos
   * a pasar la cantidad de bytes que le queremos asig-
   * nar, dependiendo de la longitud de los datos con
   * los que vamos a trabajar tendremos que asignar más
   * o menos memora
   */
  StaticJsonBuffer<600> jsonBuffer;

  char json[] = 
  
  "{" 
    "\"verduras\":["
      "{\"nombre\":\"lechuga\",\"sabor\":\"semi amargo\"},"
      "{\"nombre\":\"zanahoria\",\"sabor\":\"semi dulce\"},"
      "{\"nombre\":\"radicheta\",\"sabor\":\"amargo\"}"
    "],"
    "\"frutas\":["
      "{\"nombre\":\"limon\",\"sabor\":\"acido\"},"
      "{\"nombre\":\"mandarina\",\"sabor\":\"dulce\"},"
      "{\"nombre\":\"pomelo\",\"sabor\":\"amargo\"}"
    "]"
  "}";

  //Creamos el objeto bajo el nombre vegetales
  JsonObject& vegetales = jsonBuffer.parseObject(json);
  
  //Verificamos que todo se generó de forma correcta
  if (!vegetales.success()) {
    Serial.println("fallo: parseObject()");
    return;
  }


  /* Mostramos los valores.
   * 
   * Como dato, antes de trabajar con los datos hay
   * que pasarlos a una variable del tipo "const char*"
   * para que funcione correctamente.
   */

  Serial.println("");
  for(int i = 0; i < 3; i++)
  {
    const char* nombre = vegetales["verduras"][i]["nombre"];
    const char* sabor = vegetales["verduras"][i]["sabor"];
    Serial.print("verdura: ");
    Serial.print(nombre);
    Serial.print(", sabor: ");
    Serial.println(sabor);
  }
  for(int i = 0; i < 3; i++)
  {
    const char* nombre = vegetales["frutas"][i]["nombre"];
    const char* sabor = vegetales["frutas"][i]["sabor"];
    Serial.print("fruta: ");
    Serial.print(nombre);
    Serial.print(", sabor: ");
    Serial.println(sabor);
  }
}

void loop() {
  //no utilizado
}


Si necesitamos saber cuánta memoria asignarle al buffer de los datos Json, está el  sitio arduinojson.org/v5/assistant en donde podremos ingresar el texto que se va a utilizar como Json y nos da el tamaño a asignar. Además, más abajo, nos va a dar una porción de código que ya nos genera una variable por cada elemento del texto Json.

Además esta librería tiene un generador de Json que con lo que expliqué arriba y el ejemplo de la librería debería bastar para que se entienda.