A menudo es necesario almacenar una gran cantidad de datos en la memoria del microcontrolador que no cambiar谩n durante el funcionamiento, por ejemplo:
- Matriz de calibraci贸n
- Texto del nombre de elementos del men煤
- Algo de texto
- Trigonometr铆a calculada (seno, coseno)
- Im谩genes para visualizaci贸n (mapa de bits)
- Y mucho m谩s
Almacenar dichos datos en RAM (como una variable ordinaria) no es la mejor idea, porque no cambiar谩n, 隆pero ocupar谩n espacio! Perm铆tanme recordarles que la RAM es siempre mucho menor que la memoria del programa (Flash): en el mismo ATmega328 (Arduino UNO / Nano / Pro mini) – 32 KB Flash y 2 KB SRAM, 隆SRAM es 16 veces menor! Por lo tanto, es mucho m谩s eficiente almacenar dichos datos en Flash, tambi茅n conocida como memoria del programa (tambi茅n conocida como PROGMEM). 驴Pero c贸mo?
Estamos acostumbrados al hecho de que podemos cambiar variables durante la ejecuci贸n del programa, por eso son variables, por eso la memoria se llama din谩mica. Pero con la memoria Flash, todo no es tan simple: solo un programador puede escribir en 茅l, con el que se carga el c贸digo del programa, o un cargador de arranque, que pr谩cticamente realiza la funci贸n de un programador. Por cierto, existe un cargador modificado que permite acceder a la memoria Flash directamente desde el programa, pero en estas lecciones consideramos herramientas est谩ndar, en este caso, la utilidad PROGMEM. Para trabajar con PROGMEM, se usa la biblioteca incorporada avr / pgmspace.h , no necesita conectarla, se conectar谩 sola (en versiones Arduino IDE superiores a 1.0).
Contenido
Grabaci贸n.
La palabra clave PROGMEM (modificador de variable) permite escribir datos en la memoria Flash. La sintaxis es:
const tipo de datos data[] PROGMEM = {}; const PROGMEM tipo de datos data[] = {};
隆Todos! Los datos, en el caso mostrado, los tipos de datos de las matrices se colocar谩n en la memoria Flash. PROGMEM puede trabajar con todos los tipos de enteros (8, 16, 32, 64 bits), float y char.
隆Un punto importante! El modificador PROGMEM solo se puede aplicar a global (definido fuera de funciones) o variables est谩tic (global o local, pero con la palabra est谩tic)! Lea la lecci贸n sobre tipos de datos en Arduino si lo olvid贸.
Lectura.
Si, todo es m谩s simple que con la escritura (se agrega UNA palabra clave), entonces con la lectura es mucho m谩s interesante: se lleva a cabo usando una funci贸n especial. La funci贸n principal de la lectura de progmem espgm_read_type ( direcci贸n ). Podemos usar estos 4 tipos:
- pgm_read_byte ( datos ) ; – para el primer byte (char, byte, int8_t, uint8_t)
- pgm_read_word ( datos ) ; – para 2 bytes (int, word, unsigned int, int16_t, int16_t)
- pgm_read_dword ( datos ) ; – para 4 bytes (long, unsigned long, int32_t, int32_t)
- pgm_read_float ( datos ) ; – para n煤meros en coma flotante
隆Donde datos es la direcci贸n (o puntero) del bloque de datos almacenado! Recuerde la lecci贸n sobre punteros para entender de qu茅 se trata.
La lista completa de caracter铆sticas de pgmspace se puede encontrar en la documentaci贸n.
N煤meros 煤nicos
Consideremos un ejemplo simple: escribir y leer n煤meros individuales:
const uint16_t data PROGMEM = 125; const int16_t signed_data PROGMEM = -654; const float float_data PROGMEM = 3.14; void setup() { Serial.begin(9600); Serial.println(pgm_read_word(&data)); // imprime 125 uint16_t *dataPtr = &data; //prueba con un puntero Serial.println(pgm_read_word(dataPtr)); // imprime 125 Serial.println(pgm_read_word(&signed_data)); // imprime 64882 Serial.println((int16_t)pgm_read_word(&signed_data)); // imprime -654 Serial.println(pgm_read_float(&float_data)); // imprime 3.14 } void loop() {}
Lo que es importante recordar aqu铆: al leer n煤meros negativos (por ejemplo, tipos int y long) se deben convertir, porque PROGMEM almacena n煤meros en representaci贸n sin signo. Presta atenci贸n a la lectura de signed_data del ejemplo anterior, sin convertir a int 隆el n煤mero se mostr贸 incorrectamente!
Matrices unidimensionales
Con matrices de n煤meros, todo es bastante l贸gico:
const uint8_t data[] PROGMEM = {10, 20, 30, 40}; void setup() { Serial.begin(9600); for (byte i = 0; i < 4; i++) { Serial.println(pgm_read_byte(&data[i])); } // imprime 10 20 30 40 } void loop() {}
Matrices 2D
Al crear una matriz bidimensional, aseg煤rese de especificar el tama帽o de al menos una de las dimensiones.
const uint16_t data[][5] PROGMEM = { {10, 20, 30, 40, 50}, {60, 70, 80, 90, 100}, {110, 120, 130, 140, 150}, }; void setup() { Serial.begin(9600); // imprime 70, segunda fila, segunda columna Serial.println(pgm_read_word(&data[1][1])); // imprime 150, tercera fila quinta columna Serial.println(pgm_read_word(&data[2][4])); } void loop() {}
Matriz de matrices
Puede almacenar varias matrices en una, la llamada, tabla de matrices.
// matrices const uint16_t data0[] PROGMEM = {10, 20, 30, 40, 50}; const uint16_t data1[] PROGMEM = {60, 70, 80, 90, 100}; const uint16_t data2[] PROGMEM = {110, 120, 130, 140, 150}; const uint16_t data3[] PROGMEM = {160, 170, 180, 190, 200}; // tabla de matrices const uint16_t* const data_array[] PROGMEM = {data0, data1, data2, data3}; void setup() { Serial.begin(9600); // muestra 170, el segundo elemento de la cuarta matriz Serial.println(pgm_read_word(&data_array[3][1])); } void loop() {}
Cadenas de caracteres
PROGMEM permite guardar cadenas como matrices de caracteres, char:
const char data_message[] PROGMEM = {"Hello!"}; void setup() { Serial.begin(9600); for (byte i = 0; i < strlen_P(data_message); i++) { Serial.print((char)pgm_read_byte(&data_message[i])); } //imprime 隆Hola! } void loop() {}
La lectura se realiza car谩cter a car谩cter; al leer, debemos convertir el tipo char. Tambi茅n puede haber notado que para calcular la longitud de la matriz de caracteres, usamos la funci贸n strlen_P (), esto es an谩logo a strlen () (vea la lecci贸n sobre cadenas), pero se usa especialmente para cadenas PROGMEM. En la documentaci贸n se puede encontrar un conjunto de herramientas para trabajar con cadenas en PROGMEM, hay muchas de ellas.
Matrices de cadenas
A veces es conveniente almacenar varias l铆neas con el mismo nombre, por ejemplo, para los elementos del men煤 Arduino. En este caso, puede usar una matriz de cadenas (una matriz de matrices de caracteres), hablamos de ello en la lecci贸n sobre cadenas. El mecanismo es el siguiente: creamos cadenas, las ponemos en PROGMEM. Creamos una 芦tabla de enlaces禄 para estas l铆neas. 隆Leemos cualquier fila seleccionada de la tabla!
//declaramos nuestras "cadenas" const char array_1[] PROGMEM = "Period"; const char array_2[] PROGMEM = "Work"; const char array_3[] PROGMEM = "Stop"; // declara la tabla de enlaces const char* const names[] PROGMEM = { array_1, array_2, array_3, }; void setup() { Serial.begin(9600); // muestra la l铆nea # 1 (texto "Work") // strlen_P (names [1]) - la longitud de esta cadena for (byte i = 0; i < strlen_P(names[1]); i++) { // 隆acceder al elemento ser谩 como una matriz bidimensional! // nombres [1] [0] - letra W Serial.print((char)pgm_read_byte(&(names[1][i]))); //mostrar谩 Work } } void loop() {}
La tarea se vuelve m谩s complicada, 驴no? =) Puedes ir al rev茅s: un b煤fer char en el que copiar la l铆nea completa de PROGMEM usando la funci贸n strcpy_P () que copia los datos especificados de PROGMEM en una matriz regular. Obtenemos una matriz regular de caracteres, que incluso se pueden enviar directamente al puerto:
// declaramos nuestras "cadenas" const char array_1[] PROGMEM = "Period"; const char array_2[] PROGMEM = "Work"; const char array_3[] PROGMEM = "Stop"; // declara la tabla de enlaces const char* const names[] PROGMEM = { array_1, array_2, array_3, }; void setup() { Serial.begin(9600); char arrayBuf[10]; // crea un b煤fer // copiar a arrayBuf usando strcpy_P strcpy_P(arrayBuf, pgm_read_word(&(names[1]))); Serial.println(arrayBuf); // mostrar谩 Work strcpy_P(arrayBuf, pgm_read_word(&(names[2]))); Serial.println(arrayBuf); // mostrar谩 Stop } void loop() {}
Consideremos otro ejemplo en el que sacaremos una cadena de la memoria sin funciones pesadas adicionales. Adem谩s, este ejemplo funciona correctamente cuando se genera a trav茅s de un bucle (a diferencia de los ejemplos anteriores).
// declaramos nuestras "cadenas" const char array_1[] PROGMEM = "Period"; const char array_2[] PROGMEM = "Work"; const char array_3[] PROGMEM = "Stop"; // declara la tabla de enlaces const char* const names[] PROGMEM = { array_1, array_2, array_3, }; void setup() { Serial.begin(9600); for (int i = 0; i < 3; i++) { // bucle uint16_t ptr = pgm_read_word(&(names[i]));// obt茅n la direcci贸n de la tabla de enlaces while (pgm_read_byte(ptr) != NULL) { // cadena completa hasta cero caracteres Serial.print(char(pgm_read_byte(ptr))); // imprimir en el monitor o donde necesitemos ptr++; // siguiente caracter } Serial.println(); } } void loop() {}
La funci贸n para imprimir l铆neas al serial o al display, de PROGMEM se puede hacer en una funci贸n separada. Ejemplo final:
// declaramos nuestras "cadenas" const char array_1[] PROGMEM = "Period"; const char array_2[] PROGMEM = "Work"; const char array_3[] PROGMEM = "Stop"; // declara la tabla de enlaces const char* const names[] PROGMEM = { array_1, array_2, array_3, }; void setup() { Serial.begin(9600); for (int i = 0; i < 3; i++) { printFromPGM(&names[i]); Serial.println(); } } void loop() { } // funci贸n para imprimir desde PROGMEM void printFromPGM(int charMap) { char buffer[10]; // b煤fer para almacenar la cadena uint16_t ptr = pgm_read_word(charMap); // obtener la direcci贸n de la tabla de enlaces uint8_t i = 0; // variable - 铆ndice de la matriz de b煤fer do { buffer[i] = (char)(pgm_read_byte(ptr++)); //lee un car谩cter del PGM en una ubicaci贸n de b煤fer, mueve el puntero } while (buffer[i++] != NULL); // repetir hasta que el car谩cter le铆do no sea cero, mover el 铆ndice del b煤fer Serial.print(buffer); // imprimir la l铆nea terminada }
Macro F().
La llamada 芦macro F ()禄 hace que sea muy f谩cil almacenar cadenas (matrices de caracteres) en la memoria Flash sin tener que recurrir al uso de PROGMEM:
// esta salida (l铆nea, texto) ocupa 18 bytes en RAM Serial.println("Hello <username>!"); //esta salida no ocupa nada en RAM, gracias a F () Serial.println(F("Type /help to help"));
隆Versatil! Pero PROGMEM le brinda m谩s opciones, especialmente con una tabla de referencias, donde se accede a m煤ltiples filas usando un solo nombre y n煤mero com煤n. La macro F () funciona muy bien en los casos en que hay que mostrar texto sin formato ( lcd.print(F(芦Hello禄)) ) y para programas con control de consola.