PROGMEM
Ir a la navegación
Ir a la búsqueda
¿Para que sirve?
[editar]- PROGMEM hace que una variable constante a la que se le aplica resida en la memoria flash en vez de la RAM. Asi podemos ahorrar RAM para las demás variables.
- Si tenemos muchos strings que no se tienen que modificar (por ejemplo strings de menus) vale la pena guardarlos en la flash para que no ocupen RAM
- Las variables PROGMEM tienen espacio asignado en la memoria que no cambia durante la ejecución del programa.
Declaración de variables
[editar]Para alojar una variable constante en la memoria Flash se hace asi:
PROGMEM const tipoVariable nombreVariable = valoresIniciales;
Por ejemplo:
PROGMEM const int a = 5;
PROGMEM const int a[] = {0,3,4,2};
También puede hacerse asi:
const int a[] PROGMEM = {0,4,3,2};
O de un modo más abreviado:
const prog_uint16_t a[] = {0,4,3,2);//Esto equivale al ejemplo de encima
-----
'----> se mantiene const
Esta tabla muestra las equivalencias:
| escritura normal | escritura abrebiada | stdint.h |
|---|---|---|
| char PROGMEM | prog_char | |
| unsigned char PROGMEM | prog_uchar | |
| int8_t PROGMEM | prog_int8_t | signed char |
| uint8_t PROGMEM | prog_uint8_t | unsigned char |
| int16_t PROGMEM | prog_int16_t | signed int |
| uint16_t PROGMEM | prog_uint16_t | unsigned int |
| int32_t PROGMEM | prog_int32_t | signed long int |
| uint32_t PROGMEM | prog_uint32_t | unsigned long int |
| int64_t PROGMEM | prog_int64_t | signed long long int |
| uint64_t PROGMEM | prog_uint64_t | unsigned long long int |
Comportamiento extraño(en Arduino IDE)
[editar]Primer caso
[editar]const uint8_t a[] PROGMEM = {1,23,34,45};
void buffer(const uint8_t b[] PROGMEM){
uint8_t g;
for(int i=0; i<4;i++){
g = pgm_read_byte(b++);
Serial.print(g);Serial.print(" - ");
}
}
void setup(){
Serial.begin(9600);
buffer(a);
}
void loop(){}
- En este caso la salida por serial será: 1 - 23 - 34 - 45
const prog_uint8_t a[] = {1,23,34,45};
void buffer(const uint8_t b[] PROGMEM){
uint8_t g;
for(int i=0; i<4;i++){
g = pgm_read_byte(b++);
Serial.print(g);Serial.print(" - ");
}
}
void setup(){
Serial.begin(9600);
buffer(a);
}
void loop(){}
- En este caso la salida por serial no será correcta
- No entiendo porque pasa si se supone que prog_uint8_t es equivalente a uint8_t PROGMEM
Segundo caso
[editar]const uint8_t a[] PROGMEM = {1,23,42,3};
uint8_t b[4];
void setup(){
Serial.begin(9600);
for(int i=0; i<4;i++){
b[i] = pgm_read_byte(a++);
Serial.print(b[i]);
}
}
void loop(){}
- Aqui recibo un error: increment of read-only location '(const uint8_t*)(&a)'. Cuando en los ejemplos de arriba no pasa al poner una función de por medio.
Funciones de lectura
[editar]pgm_read_byte() //Lee un byte de la memoria Flash pgm_read_word() //Lee 2 bytes de la memoria Flash pgm_read_dword() //Lee 4 bytes de la memoria Flash pgm_read_float() //Lee un número de coma flotante de 4 bytes
- A cada una de estas funciones se le pasa una dirección de 16 bits que indican donde reside la variable.
Ejemplos
[editar]const prog_uint8_t a[]={1,2,3,32,55,12};
uint8_t b;
b = pgm_read_byte(&a[0]);
- b es igual a 1. Lo que le paso a pgm_read_byte es la dirección de la primera posición del array a
const prog_uint8_t a[]={1,2,3,32,55,12};
uint8_t b;
b = pgm_read_byte(a+1);
- a+1 es equivalente a a[1], como vimos en Operaciones con Vectores
const prog_uint8_t a[]={1,2,3,32,55,12};
uint8_t b[6];
for(int i=0;i<6;i++){
b[i] = pgm_read_byte(a+i);
}
- En este ejemplo he copiado todo el contenido de la variable a en la variable b
Funciones
[editar]Globales y Locales
[editar]- Cuando declaramos una variable PROGMEM globalmente se hace como hemos dicho antes
const prog_uint8_t a = 1;
- Cuando es una variable local de una función en los compiladores AVR IAR C y AVR GCC se tiene que poner static.La función setup y loop de Arduino son funciones, por tanto si declaramos variables PROGMEM tenemos que hacerlo como sigue
static const porg_uint8_t a =1;
Punteros a variables en flash
[editar]PGM_P
[editar]- PGM_P define un tipo puntero que apunta a variables de la memoria flash de 8bits
- Su definición en pgmspace.h de avr-gcc es
#define PGM_P const PROGMEM char *
Ejemplo
[editar]#include "avr_compiler.h"
PROGMEM const unsigned int a[] = {1,2,3,4,5};
PROGMEM const unsigned char b[] = {23,42,54,2};
int main(void)
{
PGM_P p;
unsigned char b1;
unsigned int a1;
p = (PGM_P)b; //Se esta haciendo un casting para que no haya problemas con el compilador
c = pgm_read_byte(p + 1); //En el ejemplo de arriba p apunta al primer elemento de b. Con pgm_read_byte(p+1) leemos b[1]
//Esto es lo mismo que hago en la sección Ejemplos de Funciones de Lectura, solo que en este caso no le paso &p+1, porque p ya contiene dircciones
p = (PGM_P)a; //Aqui vuelvo ha hacer lo mismo en la primera linea que en el array b
n = pgm_read_word((PROGMEM const unsigned int *)p + 3); //Como pgm_read_word() necesita que le pasen una dirección de variable de 2bytes (word) se hace la conversión de p.
//También se puede poner (PROGMEM int *) porque lo que cuenta es que se convierta a un tipo de 2 bytes
}
- En el ejemplo anterior estamos utilizando el puntero PGM_P para acceder a varaibles de 2 bytes. Para ello estamos utilizando conversiones y realmente lo mejor seria utilizar punteros neutros, PGM_P void
PGM_P void
[editar]- Son punteros neutros
- Los punteros PGM_P void se definen:
#define PGM_VOID_P const void PROGMEM *
Ejemplo
[editar]#include "avr_compiler.h"
PROGMEM const unsigned int a[] = {1,2,3,4,5};
PROGMEM const unsigned char b[] = {23,42,54,2};
int main(void)
{
PGM_VOID_P p;
unsigned char c;
unsigned int n;
p = (PGM_VOID_P)b; //Realmente no haria falta convertir el array b a PGM_VOID_P, pero es mejor hacer las cosas bien hechas
c = pgm_read_byte((PROGMEM char*)p + 1);
p = (PGM_VOID_P)a; //Lo mismo aquí
n = pgm_read_word((PROGMEM int*)p + 3);
}
- Aqui lo que hacemos es convertir la variable a y b a puntero neutro, asi podemos asignar a p la dirección de memoria de estas variables.
- Luego para poder acceder a la variable que apunta p tenemos que convertirla al tipo que la función necesita. En el primer caso a una variable de 8 bits(char) y en el segundo caso a una variable de 2 bytes(int)
PROGMEM const unsigned int a = 5;
int main(void){
PGM_VOID_P p;
unsigned int i;
p = (PGM_VOID_P)&a;
i = pgm_read_word((PROGMEM int *)p);
}
- Si nos fijamos para asignar al puntero p la dirección de a lo hemos hecho de forma purista, convirtiendo antes la variable a al tipo que tiene p. Esto realmente no es necesario en Arduino IDE, pero es mejor hacerlo bien para que quede todo claro
- Luego hacemos casting al puntero p porque la funcion pgm_read_word() necesita recibir una dirección de 2 bytes (tipo int) y p es de tipo PGM_VOID_P