Diferencia entre revisiones de «PROGMEM»

De Proyectos
Ir a la navegación Ir a la búsqueda
Sin resumen de edición
 
Línea 137: Línea 137:


*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.
*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 =
= Funciones de lectura =
Línea 146: Línea 147:
</pre>
</pre>
*A cada una de estas funciones se le pasa una dirección de 16 bits que indican donde reside la variable.
*A cada una de estas funciones se le pasa una dirección de 16 bits que indican donde reside la variable.
== Ejemplos ==
== Ejemplos ==
<pre>
<pre>

Revisión actual - 18:20 21 mar 2013

¿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:

avr/pgmspace.h de AVR-GCC
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);
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