36- Trabajar con memoria EEPROM Arduino

As铆 que llegamos al tercer tipo de memoria disponible en Arduino: EEPROM (Memoria de solo lectura programable y borrable el茅ctricamente – (EEPROM)), tambi茅n es memoria no vol谩til. Recordemos los otros tipos de memoria, Flash y SRAM, y sus capacidades de almacenamiento de datos:


La Eeprom de Arduino.

TipoLeer desde programaGrabaci贸n por programaBorrada al reiniciar
FLASHS铆, PROGMEMEs posible, pero dif铆cilNo
SRAMSiSiSi
EEPROMSiSiNo
Caracter铆sticas de la memoria Eeprom de Arduino.

En palabras simples: EEPROM – memoria a la que tenemos acceso completo desde un programa en ejecuci贸n, es decir podemos leer y escribir datos all铆 en tiempo de ejecuci贸n, y estos datos no se borran cuando se reinicia Arduino. 

Usabilidad

  • Almacenamiento de configuraciones que cambian 芦desde el men煤禄 del dispositivo;
  • Calibraci贸n, almacenamiento de datos de calibraci贸n de Arduino;
  • Utilizar como memoria SRAM adicional en caso de escasez;
  • 芦Caja negra禄: registro permanente de las lecturas de los sensores para una posterior decodificaci贸n de fallas;
  • Registrar el estado de un flujo de trabajo para recuperarse de un reinicio repentino.

Recurso

El 煤nico punto importante: EEPROM tiene un recurso en el n煤mero de reescritura de c茅lulas. El fabricante garantiza 100.000 ciclos de escritura para cada celda, de hecho, este n煤mero depende de las condiciones espec铆ficas del chip y la temperatura, las pruebas independientes mostraron 3-6 millones de ciclos de reescritura a temperatura ambiente antes de que ocurriera el primer error, es decir, los 100.000 declarados se toman con un margen muy amplio. Pero hay una peque帽a aclaraci贸n: con los 100.000 ciclos de reescritura declarados, la seguridad de los datos grabados est谩 garantizada durante 100 a帽os a una temperatura de 24 掳 C; si sobrescribe un mill贸n, los datos se deteriorar谩n m谩s r谩pido. Al mismo tiempo, el n煤mero de lecturas de cada celda es ilimitado. 

Volumen

EEPROM es un 谩rea de memoria formada por celdas unitarias de un byte (como SRAM). El tama帽o de EEPROM es diferente para diferentes modelos de Arduino:

  • ATmega328 (Arduino UNO, Nano, Pro Mini): 1 KB
  • ATmega2560 (Arduino Mega): 4 KB
  • ATtiny85 (Digispark): 512 B

Direccionamiento

La tarea principal cuando se trabaja con EEPROM es no confundirla con direcciones, porque cada byte tiene su propia direcci贸n. Si escribe datos de doble byte, se necesitar谩n dos bytes, y los siguientes datos deber谩n escribirse en la direcci贸n de al menos +2 a la anterior, de lo contrario ser谩n 芦reescritos禄. Considere un ejemplo de almacenamiento de un conjunto de datos de diferentes tipos, ubicados en la memoria secuencialmente uno tras otro (entre par茅ntesis escribo el tama帽o del tipo de datos actual, cuyo tama帽o aumentar谩 la direcci贸n para el siguiente 芦bloque禄):

  • byte – direcci贸n 0 (+1)
  • byte – direcci贸n 1 (+1)
  • int – direcci贸n 2 (+2)
  • byte – direcci贸n 4 (+1)
  • float – direcci贸n 5 (+4)
  • int – direcci贸n 9 (+2)
  • etc

Un punto importante: todas las celdas tienen un valor predeterminado (para un nuevo chip) 255 .

Velocidad

Velocidad EEPROM (el tiempo de respuesta no depende de la frecuencia del reloj del sistema Arduino):

  • Escribir un byte toma ~ 3.3ms (milisegundos)
  • La lectura de un byte tarda ~ 0,4 渭s (microsegundos)

Voltaje

Puede haber distorsiones al escribir datos en EEPROM con un VCC (voltaje de suministro) demasiado bajo; se recomienda encarecidamente utilizar BOD  o controlar manualmente el voltaje antes de escribir.

Frecuencia

Cuando se utiliza Arduino con un generador de reloj interno de 8 MHz, su desviaci贸n no debe exceder el 10% (7.2-8.8 MHz), de lo contrario, en la escritura en EEPROM o FLASH probablemente se cometer谩 con errores. En consecuencia, todo overclocking del reloj interno es inaceptable cuando se escribe EEPROM o FLASH.

Bibliotecas

Para trabajar con EEPROM en el entorno Arduino, tenemos dos bibliotecas completas, la segunda es un 芦shell禄 m谩s conveniente para la primera. Consideremos ambas, porque cualquier cosa se puede encontrar en el boceto de otra persona, y la combinaci贸n de estas dos bibliotecas hace que trabajar con EEPROM sea incre铆blemente adecuado.


Biblioteca avr / eeprom.h

La biblioteca est谩ndar eeprom.h viene con el compilador avr-gcc, que compila nuestros bocetos del IDE de Arduino. Puedes leer la documentaci贸n completa aqu铆. Para conectar la biblioteca al boceto, escriba #include <avr / eeprom.h>

La biblioteca tiene un conjunto de funciones para trabajar con tipos de datos enteros (byte – 1 byte, palabra – 2 bytes, dword – 4 bytes), float y blocks 鈥淏loques鈥: conjuntos de datos de cualquier formato (estructuras, matrices, etc.). Trabajar significa escribir, leer y actualizar. La actualizaci贸n es una herramienta extremadamente importante para evitar la sobrescritura innecesaria de las celdas de memoria en Arduino. La actualizaci贸n escribe si el valor que se escribe difiere del actual en esta celda.

Leer:

  • eeprom_read_byte ( direcci贸n ) – devolver谩 el valor
  • eeprom_read_word ( direcci贸n ) – devolver谩 el valor
  • eeprom_read_dword ( direcci贸n ) – devolver谩 el valor
  • eeprom_read_float ( direcci贸n ) – devolver谩 el valor
  • eeprom_read_block ( direcci贸n SRAM, direcci贸n EEPROM, tama帽o ) – lee contenido por Direcci贸n EEPROM y lo coloca en direcci贸n en SRAM

Gravar:

  • eeprom_write_byte ( direcci贸n, valor )
  • eeprom_write_word ( direcci贸n, valor )
  • eeprom_write_dword ( direcci贸n, valor )
  • eeprom_write_float ( direcci贸n, valor )
  • eeprom_write_block ( direcci贸n SRAM, direcci贸n EEPROM, tama帽o ) – escribir谩 el contenido por direcci贸n en SRAM en Direcci贸n EEPROM

Actualizar:

  • eeprom_update_byte ( direcci贸n, valor )
  • eeprom_update_word ( direcci贸n, valor )
  • eeprom_update_dword ( direcci贸n, valor )
  • eeprom_update_float ( direcci贸n, valor )
  • eeprom_update_block ( direcci贸n SRAM, direcci贸n EEPROM, tama帽o ) – actualizar谩 el contenido por direcci贸n en SRAM en Direcci贸n EEPROM

Macros:

  • _EEPUT (addr, val)– escribe (escribe) un byte val en la direcci贸n addr. No se requiere encasillamiento (es una macro)
  • _EEGET ( val, addr)– lee un byte en una direcci贸n addr y lo escribe en una variable val. No se requiere encasillamiento (hecho en una macro)

Consideremos un ejemplo simple en el que hay una escritura y lectura de tipos de datos individuales en diferentes celdas:

#include <avr / eeprom.h>
void setup () {  
  Serial.begin(9600);
  
  // declarar datos de diferentes tipos
  byte dataB = 120;
  float dataF = 3.14;
  int16_t dataI = -634;  
  // escribe uno tras otro
  eeprom_write_byte ( 0, dataB ) ; // 1 byte
  eeprom_write_float ( 1, dataF ) ; // 4 bytes
  // "actualizar" para variar
  eeprom_update_word ( 5, dataI ) ;
  // declaramos variables donde leeremos
  byte dataB_read = 0;
  float dataF_read = 0;
  int16_t dataI_read = 0;
  // leer
  dataB_read = eeprom_read_byte ( 0 ) ;
  dataF_read = eeprom_read_float ( 1 ) ;
  dataI_read = eeprom_read_word ( 5 ) ;
  // imprimir谩 120 3,14 -634
  Serial.println ( dataB_read ) ;
  Serial.println ( dataF_read ) ;
  Serial.println ( dataI_read ) ;
}
void loop() {}  

No es muy conveniente almacenar datos de esta manera, porque la administraci贸n de direcciones debe hacerse manualmente, contar el n煤mero de bytes en cada tipo y 芦cambiar禄 la direcci贸n en la cantidad requerida. Es mucho m谩s conveniente almacenar diversos datos en estructuras, hablamos de ellos con m谩s detalle en la lecci贸n sobre tipos de datos de Arduino.

Debemos pasar a la funci贸n la direcci贸n de los datos en memoria (operador &) es esencialmente un puntero, y tambi茅n lo convierte a tipo void* porque la funci贸n de lectura / escritura del bloque toma exactamente ese tipo. Hablamos sobre los punteros en Arduino con m谩s detalle en una lecci贸n separada. Adem谩s, a la funci贸n de lectura / escritura del bloque se le debe pasar el tama帽o del bloque de datos en bytes. Esto se puede hacer manualmente (por n煤mero), pero es mejor usar sizeof (), que calcular谩 este tama帽o y lo pasar谩 a la funci贸n.

#include <avr/eeprom.h>
void setup() {
  Serial.begin(9600);
  //declara la estructura
  struct MyStruct {
    byte a;
    int b;
    float c;
  };
  // crea y llena la estructura
  MyStruct myStruct;
  myStruct.a = 10;
  myStruct.b = 1000;
  myStruct.c = 3.14;
  // escribir en la direcci贸n 10, especificando el tama帽o de la estructura y convertirlo en void *
  eeprom_write_block((void*) &myStruct, 10, sizeof(myStruct));
  //  crea una nueva estructura vac铆a
  MyStruct newStruct;
  //leer de la direcci贸n 10
  eeprom_read_block((void*) &newStruct, 10, sizeof(newStruct));
  // controlar
  // imprime 10 1000 3,14
  Serial.println(newStruct.a);
  Serial.println(newStruct.b);
  Serial.println(newStruct.c);
}
void loop() {}

Las matrices se pueden almacenar de la misma manera:

#include <avr / eeprom.h>
void setup() {
 Serial.begin(9600);
  // crea una matriz
  float dataF [] = { 3,14 , 60,25 , 9132,5 , -654,3 } ;
  // escribe en la direcci贸n 20, especificando el tama帽o
  eeprom_write_block (( void * ) & dataF, 20, sizeof ( dataF )) ;
  // 隆crea una nueva matriz vac铆a del mismo tipo y tama帽o!
  float dataF_read [ 4 ] ;
  // leer de la direcci贸n 20
  eeprom_read_block (( void * ) & dataF_read, 20, sizeof ( dataF_read )) ;
  // controlar
  // dar谩 como resultado 3.14 60.25 9132.5 -654.3
  for ( byte i = 0; i < 4; i ++ ) 
   Serial.println(dataF_read[i]);
}
void loop() {}

Existe otra herramienta muy 煤til en la biblioteca Arduino avr / eeprom.h – EEMEM, que te permite realizar direccionamiento autom谩tico de datos creando punteros a los que el compilador asignar谩 un valor.

Considere un ejemplo en el que escribimos varias variables, una estructura y una matriz en la EEPROM, d谩ndoles direcciones autom谩ticamente.

 隆Un punto importante! Las direcciones se establecen de abajo hacia arriba en el orden de la declaraci贸n EEMEM:

#include <avr / eeprom.h>
struct MyStruct {
  byte val1;
  int val2;
  float int3;
} ;
uint8_t EEMEM byteAddr;    // 27
uint16_t EEMEM intAddr;    // 25
uint32_t EEMEM longAddr;   // 21
MyStruct EEMEM myStructAddr ; // 14
int EEMEM intArrayAddr [ 5 ] ; // cuatro
float EEMEM floatAddr;     // 0

El propio EEMEM distribuye las direcciones seg煤n el tama帽o de los datos. Un punto importante: este enfoque no ocupa espacio de memoria adicional, es decir Numerar las direcciones manualmente con n煤meros, sin crear 芦variables禄 EEMEM, 隆no ahorramos memoria!

Volvamos a nuestro primer ejemplo y lo reescribamos con EEMEM. Al especificar una direcci贸n a trav茅s de EEMEM, debe utilizar el operador de toma de direcci贸n &.

#include <avr/eeprom.h>
byte EEMEM dataB_addr;
float EEMEM dataF_addr;
int16_t EEMEM dataI_addr;
void setup() {
  Serial.begin(9600);
  // declarar datos de diferentes tipos
  byte dataB = 120;
  float dataF = 3.14;
  int16_t dataI = -634;
  // escribe uno tras otro
  eeprom_write_byte(&dataB_addr, dataB);
  eeprom_write_float(&dataF_addr, dataF);
  //"actualizar" para variar
  eeprom_update_word(&dataI_addr, dataI);
  // declaramos variables donde leeremos
  byte dataB_read = 0;
  float dataF_read = 0;
  int16_t dataI_read = 0;
  // leer
  dataB_read = eeprom_read_byte(&dataB_addr);
  dataF_read = eeprom_read_float(&dataF_addr);
  dataI_read = eeprom_read_word(&dataI_addr);
  //imprimir谩 120 3,14 -634
  Serial.println(dataB_read);
  Serial.println(dataF_read);
  Serial.println(dataI_read);
}
void loop() {}

Y finalmente, escribir y leer un bloque a trav茅s de EEMEM. La direcci贸n deber谩 convertirse a (const void*) a mano:

#include <avr / eeprom.h>
// obtener la direcci贸n (habr谩 0)
int EEMEM intArrayAddr [ 5 ] ;
void setup() {
  Serial.begin(9600);
  // crea una matriz
  int intArrayWrite [ 5 ] = { 10, 20, 30, 40, 50 } ;
  // escribir en intArrayAddr
  eeprom_write_block (( void * ) & intArrayWrite, ( const void * ) & intArrayAddr, sizeof ( intArrayWrite )) ; 
  // crea una nueva matriz para leer
  int intArrayRead [ 5 ] ;
  // leer en intArrayAddr
  eeprom_read_block (( void * ) & intArrayRead, ( const void * ) & intArrayAddr, sizeof ( intArrayRead )) ; 
  // controlar
  for (byte i = 0; i < 5; i++)
    Serial.println(intArrayRead[i]);
}
void loop() {}

Por lo tanto, puede agregar 芦datos禄 para su almacenamiento en la EEPROM durante el desarrollo del programa, sin pensar en las direcciones. Recomiendo agregar nuevos datos secuencialmente sobre los 煤ltimos para que el direccionamiento no se pierda (recuerde, el direccionamiento va de abajo hacia arriba, comenzando desde cero).


Biblioteca EEPROM.h de Arduino.

La biblioteca EEPROM.h viene con el n煤cleo Arduino y es una biblioteca est谩ndar. De hecho, EEPROM.h es un shell preparado para avr / eeprom.h, que ampl铆a ligeramente sus capacidades y simplifica su uso. Un punto importante: al conectar EEPROM.h al sketch, autom谩ticamente conectamos avr / eeprom.h y podemos usar sus comandos, como EEMEM. Considere las herramientas que nos ofrece la biblioteca:

  • EEPROM.write ( direcci贸n, datos )– escribe datos (隆 solo byte! ) en la direcci贸n
  • EEPROM.update ( direcci贸n, datos )– actualiza (el mismo registro, pero mejor) el byte de datos ubicado en la direcci贸n
  • EEPROM.read ( direcci贸n )– lee y devuelve el byte de datos ubicado en la direcci贸n
  • EEPROM.put ( direcci贸n, datos )– escribe (de hecho – update, actualiza) datos de cualquier tipo (el tipo de la variable pasada) en la direcci贸n
  • EEPROM.get ( direcci贸n, datos )– lee datos en la direcci贸n y los escribe en la variable especificada
  • EEPROM [] – la biblioteca le permite trabajar con la memoria EEPROM como con una matriz de bytes ordinaria (uint8_t)

A diferencia de avr/eeprom.h, no tenemos herramientas separadas para trabajar con tipos de datos espec铆ficos que no sean bytes, y no podemos escribir / actualizar / leer un float / long / int. Pero luego tenemos un put / get omn铆voro, 隆que es muy conveniente de usar! Tambi茅n podemos usar lo que nos da avr / eeprom.h, que se conecta autom谩ticamente desde EEPROM.h. Consideremos un ejemplo con bytes de lectura / escritura:

#include <EEPROM.h>
void setup() {
  Serial.begin(9600);
  
  // escribe 200 en la direcci贸n 10
  EEPROM.update(10, 200);  
  Serial.println(EEPROM.read(10));  //muestra 200
  Serial.println(EEPROM[10]);       // muestra 200
}
void loop() {}

隆La l贸gica de trabajar con direcciones es la misma que en el p谩rrafo anterior de la lecci贸n! Preste atenci贸n a trabajar con la EEPROM como una matriz, puede leer, escribir, comparar e incluso usar operadores compuestos, por ejemplo EEPROM [ 0 ] + = 10 pero esto solo funciona para celdas at贸micas, bytes.

Ahora veamos c贸mo funciona put / get:

#include <EEPROM.h>
void setup() {
  Serial.begin(9600);
  // declaramos las variables que escribiremos
  float dataF = 3.14;
  int16_t dataI = -634;
  byte dataArray[] = {10, 20, 30, 40};
  EEPROM.put(0, dataF);
  EEPROM.put(4, dataI);
  EEPROM.put(6, dataArray);
  // declaramos variables donde leeremos
  float dataF_read = 0;
  int16_t dataI_read = 0;
  byte dataArray_read[4];
  // leer exactamente como lo escribimos
  EEPROM.get(0, dataF_read);
  EEPROM.get(4, dataI_read);
  EEPROM.get(6, dataArray_read);
  // controlar
  Serial.println(dataF_read);
  Serial.println(dataI_read);
  Serial.println(dataArray_read[0]);
  Serial.println(dataArray_read[1]);
  Serial.println(dataArray_read[2]);
  Serial.println(dataArray_read[3]);
}
void loop() {}

Mucho m谩s conveniente que write_block y read_block, 驴no? Poner y leer tipos y calcular el tama帽o del bloque de datos por s铆 mismos, es muy conveniente. Trabajan tanto con matrices como con estructuras.


EEPROM.h + avr / eeprom.h

Y, por supuesto, puede utilizar todas las ventajas de ambas bibliotecas de arduino al mismo tiempo, por ejemplo, el direccionamiento autom谩tico de EEMEM y put / get. Considere el ejemplo anterior, en lugar de configurar direcciones manualmente, usamos EEMEM, pero el valor tendr谩 que convertirse a un tipo entero, primero tomando la direcci贸n de 茅l, es decir ( int ) y eem_address.

#include <EEPROM.h>
float EEMEM dataF_addr;
int16_t EEMEM dataI_addr;
byte EEMEM dataArray_addr[5];
void setup() {
  Serial.begin(9600);
  // declaramos las variables que escribiremos
  float dataF = 3.14;
  int16_t dataI = -634;
  byte dataArray[] = {10, 20, 30, 40};
  EEPROM.put((int)&dataF_addr, dataF);
  EEPROM.put((int)&dataI_addr, dataI);
  EEPROM.put((int)&dataArray_addr, dataArray);
  //declaramos variables donde leeremos
  float dataF_read = 0;
  int16_t dataI_read = 0;
  byte dataArray_read[4];
  // leer exactamente como lo escribimos
  EEPROM.get((int)&dataF_addr, dataF_read);
  EEPROM.get((int)&dataI_addr, dataI_read);
  EEPROM.get((int)&dataArray_addr, dataArray_read);
  EEPROM[0] += 10;
  // control
  Serial.println(dataF_read);
  Serial.println(dataI_read);
  Serial.println(dataArray_read[0]);
  Serial.println(dataArray_read[1]);
  Serial.println(dataArray_read[2]);
  Serial.println(dataArray_read[3]);
}
void loop() {}

Habiendo descubierto las capacidades de las bibliotecas, pasemos a la pr谩ctica.


Ejemplo real en Arduino

Considere un ejemplo en el que sucede lo siguiente: dos botones controlan el brillo del LED conectado al pin PWM. El brillo ajustado se guarda en la EEPROM, es decir cuando el dispositivo se reinicia, se encender谩 el 煤ltimo brillo configurado. La biblioteca GyverButton se usa para sondear botones.

Primero, mire el programa original, donde no se guarda el brillo establecido. El programa se puede optimizar ligeramente, pero este no es el prop贸sito de esta lecci贸n.

Cambia el brillo con botones

#define BTN_UP_PIN 3    //  pin del bot贸n arriba
#define BTN_DOWN_PIN 4  // pin del bot贸n abajo
#define LED_PIN 5       //Pin LED
#include <GyverButton.h>
GButton btnUP(BTN_UP_PIN); // bot贸n "aumentar el brillo"
GButton btnDOWN(BTN_DOWN_PIN); // bot贸n "bajar brillo"
int LEDbright = 0;
void setup() {
  pinMode(LED_PIN, OUTPUT); // Pin LED como salida
}
void loop() {
  //  botones de sondeo
  btnUP.tick();
  btnDOWN.tick();
  if (btnUP.isClick()) {
    //aumentar al hacer clic
    LEDbright += 5;
    setBright();
  }
  if (btnDOWN.isClick()) {
    //  bajar al hacer clic
    LEDbright -= 5;
    setBright();
  }
}
void setBright() {
  LEDbright = constrain(LEDbright, 0, 255); //limitado
  analogWrite(LED_PIN, LEDbright);    // cambi贸 el brillo
}

Preservaci贸n del brillo

#define BTN_UP_PIN 3 // pin del bot贸n arriba
#define BTN_DOWN_PIN 4 // pin del bot贸n abajo
#define LED_PIN 5 // Pin LED
#include <EEPROM.h>
#include <GyverButton.h>
GButton btnUP(BTN_UP_PIN); //bot贸n "aumentar el brillo"
GButton btnDOWN(BTN_DOWN_PIN); / bot贸n bajar brillo
int LEDbright = 0;
void setup() {
  pinMode(LED_PIN, OUTPUT); //  Pin LED como salida
  EEPROM.get(0, LEDbright); // lee el brillo de la direcci贸n 0
  analogWrite(LED_PIN, LEDbright);  // incluido
}
void loop() {
  //botones de sondeo
  btnUP.tick();
  btnDOWN.tick();
  if (btnUP.isClick()) {
    // aumentar al hacer clic
    LEDbright += 5;
    setBright();
  }
  if (btnDOWN.isClick()) {
    // bajar al hacer clic
    LEDbright -= 5;
    setBright();
  }
}
void setBright() {
  LEDbright = constrain(LEDbright, 0, 255); // limitado
  EEPROM.put(0, LEDbright);           //escrito en la direcci贸n 0
  analogWrite(LED_PIN, LEDbright);    // cambi贸 el brillo
}

Entonces, ahora al inicio, se restaura el 煤ltimo brillo configurado y cuando se cambia, se registra. Perm铆tame recordarle que la EEPROM se desgasta por sobrescribir. Por supuesto, 芦hacer clic禄 en el brillo varios millones de veces y matar una celda, le llevar谩 mucho tiempo, pero el proceso de escribir un nuevo valor puede y debe optimizarse, especialmente en proyectos m谩s serios, lo haremos, hablo de esto con m谩s detalle a continuaci贸n.

Tambi茅n en nuestro c贸digo hay un momento m谩s desagradable: en el primer inicio despu茅s de reiniciar, la EEPROM no se inicializa, cada celda almacena el n煤mero 255, y este es el valor que tomar谩 la variable LEDbright despu茅s del primer inicio de arduino, durante el tiempo -llamada 芦primera lectura禄. No importa aqu铆, pero en un dispositivo m谩s serio, deber谩 establecer los valores predeterminados deseados en la EEPROM para el primer inicio, tambi茅n hablaremos de esto a continuaci贸n. De lo contrario, 隆imag铆nese qu茅 en las 鈥渃onfiguraciones predeterminadas Arduino鈥 recibir铆a su dispositivo, para brillo / velocidad / volumen / n煤mero de modo / etc… un valor no deseado o incluso peligroso!


Trucos 煤tiles.

Inicializaci贸n

Por inicializaci贸n, me refiero a configurar los valores de las celdas en la EEPROM 芦por defecto禄 durante el primer inicio del dispositivo. En el ejemplo anterior, actuamos en este orden:

  1. Lectura de EEPROM a variable
  2. Usar una variable para su prop贸sito previsto

En la primera ejecuci贸n del c贸digo (y para todas las posteriores, en las que no se escribe nada nuevo en la celda), la variable recibir谩 el valor que estaba en la EEPROM por defecto. En la mayor铆a de los casos, este valor no es adecuado para el dispositivo, por ejemplo, la celda almacena el n煤mero de modo, de acuerdo con la idea del desarrollador, de 0 a 5, y de la EEPROM leemos 255. 隆Fuera de servicio! En el primer inicio, debe inicializar la EEPROM para que el Arduino funcione correctamente, para ello debe definir este primer inicio.

Puede hacer esto manualmente al flashear un programa que llenar谩 la EEPROM con los datos necesarios. A continuaci贸n, flashee el programa que ya est谩 funcionando con datos actualizados.

Al desarrollar un programa, esto no es muy conveniente, porque la cantidad de datos guardados puede cambiar durante el desarrollo, por lo que puede utilizar el siguiente algoritmo:

  1. Reservamos alguna celda (por ejemplo, la 煤ltima) para almacenar la 芦clave禄 del primer lanzamiento
  2. Leemos la celda y si su contenido no coincide con la clave: 隆este es el primer lanzamiento!
  3. En el controlador del primer lanzamiento, escriba la clave requerida en la celda
  4. Escribimos los valores predeterminados requeridos en las celdas restantes
  5. Y despu茅s de eso, ya leemos los datos en todas las variables necesarias.

Echemos un vistazo al mismo ejemplo con un LED y botones:

Preservaci贸n del brillo.

#define INIT_ADDR 1023  // n煤mero de celda de respaldo
#define INIT_KEY 50     //clave del primer lanzamiento. 0-254, a elecci贸n
#define BTN_UP_PIN 3    // pin del bot贸n arriba
#define BTN_DOWN_PIN 4  // pin del bot贸n abajo
#define LED_PIN 5       //Pin LED
#include <EEPROM.h>
#include <GyverButton.h>
GButton btnUP(BTN_UP_PIN); //bot贸n "aumentar el brillo"
GButton btnDOWN(BTN_DOWN_PIN); // bot贸n "bajar brillo"
int LEDbright = 0;
void setup() {
  pinMode(LED_PIN, OUTPUT); //  Pin LED como salida
  if (EEPROM.read(INIT_ADDR) != INIT_KEY) { //  primer inicio
    EEPROM.write(INIT_ADDR, INIT_KEY);    // anota la clave
  // escribi贸 el valor de brillo predeterminado
  // en este caso, este es el valor de la variable declarada arriba
    EEPROM.put(0, LEDbright);
  }
  EEPROM.get(0, LEDbright); //lee el brillo
  analogWrite(LED_PIN, LEDbright);  // incluido
}
void loop() {
  //  botones de sondeo
  btnUP.tick();
  btnDOWN.tick();
  if (btnUP.isClick()) {
    //aumentar al hacer clic
    LEDbright += 5;
    setBright();
  }
  if (btnDOWN.isClick()) {
    // bajar al hacer clic
    LEDbright -= 5;
    setBright();
  }
}
void setBright() {
  LEDbright = constrain(LEDbright, 0, 255); // limitado
  EEPROM.put(0, LEDbright);           //escribi贸
  analogWrite(LED_PIN, LEDbright);    // cambi贸 el brillo
}

Ahora, en la primera ejecuci贸n, obtendremos la inicializaci贸n de las celdas requeridas. Si necesita reinicializar la EEPROM, por ejemplo, si se agregan nuevos datos, es suficiente cambiar nuestra clave a cualquier otro valor dentro de el byte (0-254). Me refiero exactamente hasta 254 porque 255 es el valor de celda predeterminado de f谩brica y nuestro truco no funcionar谩.

Velocidad

Como escrib铆 anteriormente, la velocidad de trabajo con EEPROM es:

  • Escribir / actualizar un byte tarda ~ 3.3ms (milisegundos)
  • La lectura de un byte tarda ~ 0,4 渭s (microsegundos)

Si realmente lo desea, puede usar una celda en lugar de una variable, es decir, arriba consideramos un ejemplo en el que la EEPROM se ley贸 en una variable en el programa, y 鈥嬧媦a se estaba trabajando con ella. Con una grave falta de RAM, puede leer el valor directamente desde la EEPROM, porque lleva un tiempo insignificante. Pero con la grabaci贸n, todo es mucho peor, tarda hasta 3,3 ms. Por ejemplo as铆: analogWrite(LED_PIN, EEPROM.read(0));

Para cambiar el valor, debe leer la celda, realizar las operaciones necesarias y escribir en ella nuevamente.

Otro truco: puede ingresar macros para leer y escribir valores espec铆ficos, por ejemplo:

#define GET_MODE EEPROM.read (0) // obt茅n el n煤mero de modo
#define GET_BRIGHT EEPROM.read (1) // obt茅n brillo
#define SET_MODE (x) EEPROM.write (0, (x)) // recuerda el modo
#define SET_BRIGHT (x) EEPROM.put (1, (x)) // recuerda el brillo

Obtendremos macros convenientes con las que ser谩 un poco m谩s r谩pido y factible escribir c贸digo, es decir la l铆nea SET_MODE (3) escribir谩 3 en la celda 0.

Desgaste reducido

Un tema importante: reducir el desgaste de las celdas mediante sobreescrituras frecuentes. Puede haber muchas situaciones y hay soluciones interesantes para ellas tambi茅n. Consideremos el ejemplo m谩s simple: el mismo c贸digo con un LED y un bot贸n en Arduino. Haremos lo siguiente: escribiremos el nuevo valor solo si ha pasado alg煤n tiempo desde la 煤ltima pulsaci贸n del bot贸n. Es decir, necesitamos un temporizador (usaremos el temporizador milis(), cuando se presione el bot贸n, el temporizador se reiniciar谩, y cuando se active el temporizador, escribiremos el valor real en la EEPROM. Tambi茅n necesitar谩 una bandera que se帽alar谩 la grabaci贸n y le permitir谩 grabar exactamente una vez. El algoritmo es como sigue:

  • Pulsando el bot贸n:
    • Si se omite la bandera, fija la bandera
    • Restablecer temporizador
  • Si se activa el temporizador y se levanta la bandera:
    • Baja la bandera
    • Escribir valores en EEPROM

Veamos el mismo ejemplo:

Preservaci贸n del brillo.

#define INIT_ADDR 1023 // n煤mero de celda de respaldo
#define INIT_KEY 50 // clave del primer inicio. 0-254, a elecci贸n
#define BTN_UP_PIN 3 // pin del bot贸n arriba
#define BTN_DOWN_PIN 4 // pin del bot贸n abajo
#define LED_PIN 5 // Pin LED
#include <EEPROM.h>
#include <GyverButton.h>
GButton btnUP(BTN_UP_PIN); // bot贸n "aumentar el brillo"
GButton btnDOWN(BTN_DOWN_PIN); // bot贸n bajar brillo
int LEDbright = 0;
uint32_t eepromTimer = 0;
boolean eepromFlag = false;
void setup() {
  pinMode(LED_PIN, OUTPUT); // Pin LED como salida
  if (EEPROM.read(INIT_ADDR) != INIT_KEY) { // primer inicio  
    EEPROM.write(INIT_ADDR, INIT_KEY);    // anot贸 la clave
   // escribi贸 el valor de brillo predeterminado
    // en este caso, este es el valor de la variable declarada arriba
    EEPROM.put(0, LEDbright);
  }
  EEPROM.get(0, LEDbright); //  lee el brillo
  analogWrite(LED_PIN, LEDbright);  //incluido
}
void loop() {
  // comprobar EEPROM
  checkEEPROM();
  // botones de sondeo
  btnUP.tick();
  btnDOWN.tick();
  if (btnUP.isClick()) {
    // aumentar al hacer clic
    LEDbright += 5;
    setBright();
  }
  if (btnDOWN.isClick()) {
    //  bajar al hacer clic
    LEDbright -= 5;
    setBright();
  }
}
void setBright() {
  LEDbright = constrain(LEDbright, 0, 255); // limitado
  analogWrite(LED_PIN, LEDbright);          // cambi贸 el brillo
  eepromFlag = true;                        //levanta la bandera
  eepromTimer = millis();                   // reiniciar el temporizador
}
void checkEEPROM() {
   // si la bandera est谩 levantada y han pasado 10 segundos desde el 煤ltimo clic (10,000 ms)
  if (eepromFlag && (millis() - eepromTimer >= 10000) ) {
    eepromFlag = false;           // dej贸 caer la bandera
    EEPROM.put(0, LEDbright);     // a la EEPROM
  }
}

De una manera tan simple, hemos reducido significativamente el desgaste de la EEPROM, muy a menudo uso este 芦algoritmo禄 para trabajar con las configuraciones en mis Arduinos.

Hay otras tareas en las que los datos se escriben en la EEPROM no cuando el usuario cambia algo, sino constantemente, es decir, la memoria opera en modo de caja negra y registra valores continuamente. Este puede ser, por ejemplo, un controlador de horno que mantiene el r茅gimen de temperatura de acuerdo con un algoritmo especial, y despu茅s de un reinicio repentino debe regresar al lugar en el proceso donde se interrumpi贸. Hay dos opciones a nivel general:

  • Un capacitor de gran capacidad para alimentar el microcontrolador, que le permite guardar el funcionamiento del Arduino despu茅s de apagar la alimentaci贸n durante un tiempo suficiente para escribir en la EEPROM (~ 3.3 ms). Adem谩s, el Arduino debe tener en cuenta que la alimentaci贸n general se ha apagado: si es un voltaje alto (por encima de 5 voltios), entonces puede ser un divisor de voltaje por un pin anal贸gico. Si es de 5 Voltios, puede medir el voltaje del Mc, y tambi茅n se puede capturar el momento de apagado (descarga del capacitor) y se pueden registrar los datos necesarios. Se puede definir una interrupci贸n que se activar谩 cuando la tensi贸n de alimentaci贸n caiga por debajo de un nivel peligroso. Puede llevar 5 voltios directamente al pin digital y alimentar el Arduino a trav茅s de un diodo y poner un condensador; luego, el voltaje en el pin de medici贸n desaparecer谩 antes de que el Arduino se apague, y funcionar谩 un poco m谩s desde el condensador. Aqu铆 hay un diagrama: 
Alimentaci贸n de arduino con condensador y diodo
Alimentaci贸n de arduino con condensador y diodo
  • Puede escribir datos (no necesariamente un byte, puede tener una estructura completa) inteligentemente, extendi茅ndolos por toda la EEPROM. Hay dos opciones a nivel general:
    • Escriba datos cada vez en la siguiente celda y repita la transici贸n a la primera. Tambi茅n necesitar谩 almacenar un contador en alg煤n lugar, apuntando a la direcci贸n de la celda actual, y este contador tambi茅n deber谩 almacenarse inteligentemente para que no desgaste la celda. Por ejemplo, un contador es una estructura que consta de un contador de reescritura para esa estructura y un contador de direcciones para una estructura grande.
    • Escriba datos hasta que se alcance el l铆mite en el n煤mero de reescrituras; almacene el n煤mero de reescrituras actuales, por ejemplo, en la misma estructura. Digamos que la estructura ocupa 30 bytes, es decir, en el futuro podemos encontrar esta estructura en una direcci贸n que sea m煤ltiplo de 30. El programa se ejecuta, el contador cuenta el n煤mero de reescrituras, cuando se alcanza una cantidad peligrosa, todo la estructura se 鈥渕ueve鈥 a las siguientes 30 direcciones.

Puede encontrar muchas opciones para reducir el desgaste de las celdas EEPROM, 煤nicamente para su situaci贸n. Incluso hay bibliotecas listas para usar, por ejemplo EEPROMWearLevel


Deja un comentario