Guardar y cargar variables en la EEPROM

Wemos D1 R1 ESP8266 eeprom

La utilización de la EEPROM en los proyectos de Arduino solía ser un tema relativamente escabroso:

Guardar y cargar variables en la EEPROMEn mi caso, recuerdo terminar desarmando cada variable en bytes y guardando así byte a byte su contenido.

Luego debía repetir el proceso a la inversa, leyendo uno a uno los bytes almacenados en la EEPROM y recuperando los valores tipo integer, double, o char[] que en su momento había almacenado allí.

No obstante hay un mecanismo tremendamente sencillo (y útil) que una vez lo integras a tu código, seguramente te acompañará en cada proyecto que realices.

Hoy, este es mi mecanismo favorito para almacenar parámetros de configuración y valores que requieren ser preservados ante un reset de mis ESP8266 (o básicamente cualquier otro  microcontrolador compatible con Arduino, quizás con alguna pequeñísima modificación).

Como primer paso, al inicio de nuestro código no debemos olvidarnos de incluir la librería EEPROM.h:

#include <EEPROM.h> // Manejo de la EEPROM

Una struct encapsulando las variables en EEPROM

En nuestro código vamos a definir una estructura que contenga las variables necesarias por ejemplo para la configuración de nuestro proyecto.

/* Estructura que carga y guarda en eeprom */
struct config_t {
	int puerto,retries=0;
	char ssid[12];
	byte valor1,valor2,valor3 = 0;
	IPAddress sts;
} config;

Y luego agrego estas dos funciones a mi código. Bastará llamarlas cada vez que sea necesario guardar o cargar el contenido de ese struct en la EEPROM.

//Guardo en la eeprom el struct config
void eepromsave() {
	EEPROM.begin(sizeof(config));
	EEPROM.put(0, config); //Guardo la config
	EEPROM.end();
}

//Cargo de la eeprom el struct config
void eepromload() {
	EEPROM.begin(sizeof(config));
	EEPROM.get(0, config); //Cargo config
	EEPROM.end();
}

Por si no estás ducho en el tema de estructuras, puedes utilizar las variables como si fuera cualquier variable, pero dentro de la estructura.

Ejemplo: para guardar un valor en la variable INT llamada puerto, haces

config.puerto = valor;

¿ Cómo funciona una struct en este contexto ?

En la interna, nuestra estructura se almacena en memoria con su contenido ordenado en forma contigua, en bloque.

El nombre de la estructura ( tal como ocurre con el nombre de cualquier variable) actúa como un simple puntero donde se inicia su espacio de almacenamiento en memoria.

El otro dato necesario es la cantidad de bytes que ocupa la estructura, y eso se obtiene haciendo un simple sizeof(config).

Esos dos datos son los únicos necesarios para identificar ese bloque de memoria que contiene nuestra estructura.

Por eso es tan simple, guardar todo de una sola vez en la EEPROM: Simplemente nos limitamos a guardar o cargar el paquete de bytes definido por la estructura, con su inicio y tamaño.

Lo de adentro, se resuelve solo, porque las variables están declaradas explícitamente en su interior. Si necesitas agregar más variables en tu proyecto, simplemente los agregas dentro de tu estructura config.

Consejo a considerar al inicio de tu proyecto

Debes considerar que la EEPROM puede contener cualquier valor arbitrario (basura) tal como viene de fábrica.

Por este motivo te conviene inicializar previamente tu estructura con valores conocidos, «por defecto» y guardarlos en la EEPROM, antes de leer nada desde allí, la primera vez que corre tu código

Para lograr esto puedes agregar en tu código una función como la siguiente:

//Inicializo valores para eeprom
void eeprominit() {
        config.eepromok=49834;
	config.puerto=2828;
        config.retries=3;
	strncpy(config.ssid, "Miproyecto01", 12);
        config.valor1=10;
        config.valor2=127;
        config.valor3=255;
	config.sts[0] = 0;
	config.sts[1] = 0;
	config.sts[2] = 0;
	config.sts[3] = 0;
    //Guardo valores por defecto en la eeprom 
	eepromsave(); 
}

 

Al iniciar la ejecución de tu código, cargas la config desde la eeprom.

A continuación,  verificas que una variable INT que has puesto dentro de la estructura config para este propósito tenga un valor arbitrario conocido:

Como ejemplo, digamos el valor 49834 en la variable config.eepromok

Si la variable tiene ese valor, quiere decir que está todo bien. Pero si hay cualquier otro valor, llamas a la función eeprominit() porque eso significa que la eeprom aún no fue inicializada.

Impresora 3D: Trucos y Consejos

prototipar con impresora 3D

Hace unas semanas tuve la oportunidad de adquirir una impresora 3D en un remate de decomiso de Aduanas.

Impresora 3D en partes HESINE M-505La impresora 3D se encontraba desarmada, sin documentación alguna.

El paquete incluía dos rollos de plástico como insumo de impresión: Uno plástico PLA y el otro ABS.

Contando solamente con esos detalles, me llevó unas horas ubicar marca y modelo, comparando contra imágenes en Internet.

impresora 3D HESINE M-505 desarmadaResultó ser una impresora 3D HESINE M-505 de origen chino y bajo costo, pero buenas prestaciones.

Con un poco más de búsqueda, encontré finalmente algunos manuales online, que me ayudaron a concretar el armado, tarea que me llevó unas 8 – 10 horas.

Al finalizar el armado, conecté la fuente de 12v de la impresora 3D a la red eléctrica y con gran alivio observé que la pantalla LCD retroiluminada mostraba el proceso de inicialización y el estado de la impresora en forma correcta.

Controlador impresora 3DA continuación, conecté la impresora 3D a la computadora y me puse a investigar el software llamado «Repetier» que recomendaban en los diversos foros y sitios web para controlar estos aparatos.

Continuando con la buena racha, observé que todo funcionaba correctamente.

Luego de la impresión de diversos modelos 3D he aprendido algunos trucos y consejos que ahora quiero compartirles.

Antes de continuar, el comentario obligado: Parece mentira para alguien que ya tiene unos años encima, tener hoy la posibilidad de diseñar e imprimir un objeto 3D desde la comodidad de su propio taller o escritorio de trabajo.

La realidad de una impresora 3D: ¡Demora!

Lo primero que surge con el uso de una impresora 3D es su lentitud.

La extrusión de plástico a través de la boquilla de su cabezal de impresión es definitiva y literalmente un cuello de botella.

Esto se traduce en esperas de horas (2, 3, 4 horas) para hacer una impresión de una cajita o un objeto de 4 o 5 centímetros de altura.

El resultado de la impresora 3D no es profesional

El segundo comentario corresponde a la forma en que imprime: El cabezal va depositando el plástico bajo la forma de hilos (en mi caso, 0.4 mm es el diámetro del agujero extrusor):

Una capa arriba de la otra, que si bien tiene buena precisión, es inevitable observar sobre todo en las verticales los «estratos» en forma de líneas.

El resultado final no tiene el «look and feel» de una pieza de plástico profesional hecha por molde, sino más bien a algo «casero».

Pero, también es cierto es que la pieza resultante se puede lijar e incluso pintar, como paliativo a este eventual problema.

Limitantes en el diseño de un objeto 3D para imprimir

objetos-impresora-3d-fusion-360A la hora de diseñar un modelo 3D «hay que pensar como cabeza de impresión 3D»:

Hay cosas que no se pueden hacer, directamente, y otras que ameritan concesiones y rediseños.

Ejemplo: Si debes generar una abertura, digamos un rectángulo de 4cm de lado x 3cm de alto, en una pared vertical de tu diseño, se complica:

Cuando el hilo de plástico deba cerrar ese espacio en la parte superior, el hilo de plástico simplemente irá de una punta a la otra, intentando unir el cierre. Como la distancia es grande, el hilo extruido, aún caliente y maleable, quedará colgado entre una punta y la otra, haciendo «comba» hacia abajo.

Luego de varias pasadas, el problema se autocorrige: Finalmente los hilos de plástico -que se van enfriando- van logrando sostener cada vez más a los de arriba, hasta que luego de 4 o 5 mm de «pared» superior sobre la abertura, queda finalmente recto el cierre.

Se desprende de esta situación que una herramienta muy útil a tener es un pequeño alicate para cortar rebarbas, combas, y defectos del diseño una vez terminada la impresión.

Aplicando un poco de trabajo es posible paliar y así resolver este tipo de problemas a la hora de diseñar el objeto 3D:

curva tipo boveda para facilitar cerrar el espacioLa parte superior de esa apertura rectangular puede ser terminada en «bóveda»: una leve curvatura con su punto más alto al medio de la apertura.

Esto redunda en trazos que quedan cerrando la parte superior de la apertura en forma gradual, escalonada, siendo mucho más amable la situación para que se autosoporte cada trazo sobre el trazo anterior.

El plástico se enfría y se contrae

Base objeto 3D pegada a la cama caliente impresora 3DOtro problema con la impresora 3D -y el motivo por el cual la «cama» o base donde se apoya el objeto impreso es caliente- es:

Por un lado, la base del modelo que estamos imprimiendo debe estar firmemente «pegada» a la «cama» de la impresora.

Si el objeto estuviera suelto, simplemente quedaría agarrado a la boquilla extrusora, yéndose de paseo con ella, mientras esta se mueve para imprimir. En un caso así, el resultado será una gran bola de hilo plástico, y un modelo arruinado.

Esto se resuelve agregando algún material a la superficie de la «cama» que asegure un agarre fuerte de la base del objeto que estamos imprimiendo.

  • Algunos colocan cinta de papel «para pintor». Así vino originalmente preparada «cama» de mi kit de impresora 3D.
  • Otros -yo estoy haciendo eso ahora- aplican pegamento en barra sobre la superficie de la cama -me refiero a esos tubos de pegamento, similares a los lápices de labio en forma y funcionamiento- usualmente amarillos de marca UHU, o rojos de FABER CASTELL, que son los que se consiguen en mi región.
  • Y otros, colocan un rectángulo de plástico LPE (finito, tiene un grosor de 0.5mm) y argumentan que es la mejor solución para el agarre del modelo 3D a la cama de impresión.

Por otro lado, por más que la impresión 3D se inicie con la base del modelo bien pegado a la cama de la impresora, si el modelo demora mucho en imprimirse, el plástico va a enfriarse y contraerse, haciendo aún más fuerza sobre todo en la base, al punto de que puede terminar arqueando todo el modelo.

Conclusión sobre las impresoras 3D

En resúmen: El uso de la impresora 3D es «todo un arte» en sí.

Permite, por ejemplo, prototipar gabinetes para nuestros proyectos de electrónica, a sabiendas de que seguramente harás varias impresiones hasta llegar al gabinete que quieres.

Debes tener paciencia con la impresora 3D. En próximos artículos hablaremos de los distintos tipos de plástico, programas para diseño 3D, cuidado de la impresora 3D, armado de una impresora 3D, y más.

Reloj DS3231 de alta precisión y eficiencia

Reloj-DS3231-i2c

El reloj DS3231 es un integrado bastante versátil capaz de llevar cuenta del tiempo y fecha con una precisión de +/- 2 minutos por año.

Reloj-DS3231-i2c-bateria-arduinoPuedes conseguirlo por un costo de 5 a 10 USD en la tienda de Amazon.

El integrado DS3231 es presentado usualmente en un pequeño circuito que incluye, entre otros componentes, una pila de litio CR2032 (en la parte de abajo del circuito, por eso no se ve en la foto) que le brinda autonomía de hasta unos 5 años, aún sin otra fuente de alimentación.

El DS3231 cuenta con un interfaz i2c desde el cual podremos ajustar y consultar su información.

Puede ser alimentado tanto con 3.3v como 5v lo que da buena comodidad en nuestros proyectos Arduino independientemente de que controladora y voltajes utilicemos.

Manipulando el DS3231 desde Arduino

Hay numerosas librerías disponibles para controlar al DS3231 en nuestros proyectos Arduino.

En mi caso, me destilé por la librería SODAQ DS3231 que está disponible para su integración desde el propio gestor de librerías del IDE oficial de Arduino.

NOTA: La librería SODAQ no compila (al menos la versión que tengo en este momento) correctamente para el ESP8266. Tuve que buscar el archivo Sodaq_DS3231.cpp y al principio donde se hace el #include avr\pgmspace.h cambiarlo por:

#if (defined(__AVR__))
#include <avr\pgmspace.h>
#else
#include <pgmspace.h>
#endif

Para mi actual proyecto, conecté el reloj DS3231 a una controladora ESP8266 para así obtener buena precisión horaria en  el registro histórico de datos.

Cómo vuelta de tuerca, hago que el ESP8266 consulte via Internet con un servidor de NTP al ser encendido y así ajuste el reloj del DS3231.

Ajustando el reloj DS3231 con un servidor NTP via Internet

Antes de presentar el código que utilizo para ajustar el DS3231 via Internet con un servidor NTP, necesito explicar algunas cosas que verán allí:

Primero, el objeto ssd es simplemente una pantalla OLED que tengo conectada -también vía i2c a mi ESP8266.

Segundo, la función que presento aquí abajo es llamada dentro de mi código general luego de confirmar que el ESP8266 se ha conectado a mi red WIFI.  No estoy poniendo toda esa parte del software porque serían cientos de líneas de código que no considero «centrales» al tema de este artículo.

Por último, hay una serie de variables y objetos que se inicializan para que puedan ser consumidos por esta función, que voy a intentar detallar aquí debajo e iría en los inicios de tu proyecto de software para Arduino:

/* ==== Includes ==== */
#include <WiFiUdp.h> //Se necesita para consultar el NTP y ajustar el reloj
#include <Sodaq_DS3231.h> //Librería para el DS3231

/*==== Objetos-libs ====*/
Sodaq_DS3231 clock;  //RTC DS3231
WiFiUDP udp;	//Instancia UDP para enviar y recibir paquetes al Servidor NTP

/* Variables para el RTC y acceso NTP */
unsigned int localPort = 2390;      //puerto local para recibir paquete UDP desde el NTP server
IPAddress timeServerIP; // donde voy a guardar la IP del servidor NTP
const char* ntpServerName = "0.pool.ntp.org"; //el servidor NTP
const int NTP_PACKET_SIZE = 48; // tamaño del paquete NTP que nos interesa
byte packetBuffer[NTP_PACKET_SIZE]; //buffer para paquetes entrantes y salientes al NTP
const double timezone = -3.0;

/* ==== Setup ==== */
void setup() {
        //aquí va todo el código de tu Setup, y al final, activas el reloj:
	clock.begin();
}

Esta es la función que hace la magia de consultar al servidor NTP y así ajustar el DS3231 con los datos precisos del tiempo y fecha:

//Actualizar reloj RTC con datos del NTP
void actualizareloj()
{
	ssd.clearDisplay();
	ssd.setCursor(0, 0);
	ssd.println("Actualizando RTC");
	ssd.println(ntpServerName);
	ssd.display();
	if (udp.begin(localPort) == 0) //activo puerto udp 2390 para los paquetes del servidor NTP
	{
		ssd.print("\nERROR PUERTO UDP");
	}
	else {
		while (udp.parsePacket() > 0); // discard any previously received packets
		WiFi.hostByName(ntpServerName, timeServerIP); //Busco IP del servidor NTP
		if (timeServerIP[0] == 0)
		{
			ssd.println("\nERROR NO IP");
		}
		else
		{
			ssd.println("IP " + (String)timeServerIP[0] + '.' + (String)timeServerIP[1] + '.' + (String)timeServerIP[2] + '.' + (String)timeServerIP[3]);
			ssd.display();
			yield();
			// Envio paquete NTP para pedir hora
			memset(packetBuffer, 0, NTP_PACKET_SIZE);
			packetBuffer[0] = 0b11100011;   // LI, Version, Mode
			packetBuffer[1] = 0;     // Stratum, or type of clock
			packetBuffer[2] = 6;     // Polling Interval
			packetBuffer[3] = 0xEC;  // Peer Clock Precision
									 // 8 bytes of zero for Root Delay & Root Dispersion
			packetBuffer[12] = 49;
			packetBuffer[13] = 0x4E;
			packetBuffer[14] = 49;
			packetBuffer[15] = 52;
			udp.beginPacket(timeServerIP, 123); //NTP requests are to port 123
			udp.write(packetBuffer, NTP_PACKET_SIZE);
			udp.endPacket();
			yield();
			//Ahora a esperar la respuesta del servidor NTP:
			int cb = 0;
			unsigned long mi = millis();
			while (millis() - mi < 4000 && !cb)
			{
				cb = udp.parsePacket();
				yield();
			}
			if (cb != 0)
			{
				//Vino paquete con hora
				udp.read(packetBuffer, NTP_PACKET_SIZE); // Levantamos paquete al buffer
				unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
				unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
				// combine the four bytes (two words) into a long integer
				// this is NTP time (seconds since Jan 1 1900):
				unsigned long secsSince1900 = highWord << 16 | lowWord;
				// now convert NTP time into everyday time:
				// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
				const unsigned long seventyYears = 2208988800UL;
				// subtract seventy years:
				unsigned long epoch = secsSince1900 - seventyYears;
				// adjust to user timezone
				epoch += timezone * 3600;
				ssd.println("\nOK");
				clock.setEpoch(epoch);
			}
			else {
				ssd.print("\nERROR NO RESPONDE");
			}
		}
		udp.stopAll();
	}
	ssd.display();
	delay(3000);
}

Como siempre, por favor espero dudas y comentarios acerca de este código para gestionar el DS3231, o simplemente para que cuenten en que andan con Arduino.