Utilización de la memoria EEPROM en el AVR-GCC
Dean Camera
15 febrero 2016
**********
Texto ➞ cámara Dean, 2013. Todos los derechos reservados.
Este documento puede ser distribuido libremente sin pagar al
autor, a condición de no ser vendido, y la información del autor
original se conserva.
Para más información del proyecto, tutoriales, información de la
donación y la información de contacto del autor,
visite www.fourwalledcubicle.com.
Contenido
1 Introduction
1.1 ¿Qué es la memoria EEPROM y por qué usarlo?
1.2 ¿Cómo se se accede?
2 Las funciones de EEPROM avr-libc
2.1 La inclusión de la cabecera de EEPROM avr-libc
2.2 La actualización y escribir funciones
3 La lectura de datos desde la EEPROM
4 escritura de datos en la EEPROM
5 EEPROM Block acceso
6 Uso del atributo EEMEM
7 Establecimiento de valores iniciales
Capítulo 1
Introducción
1.1 ¿Qué es la memoria EEPROM y por qué iba a utilizar
¿eso?
La mayoría de los AVR de 8 bits en la línea de productos de
Atmel contienen al menos parte de la memoria EEPROM
interna.
EEPROM, abreviatura de electrónicamente borrable memoria
de sólo lectura, es un tipo de memoria no volátil con un tiempo
razonablemente largo tiempo de vida.
Debido a que es no volátil, que conservará su información
durante períodos sin alimentacion y por lo tanto es un lugar
ideal para el almacenamiento de datos, tales como los
parámetros del dispositivo y con sistema de configuración en
tiempo de ejecución de modo que puede persistir entre
restablecimiento del procesador de aplicaciones
Un hecho importante a destacar es que la memoria EEPROM
interna del AVR tiene una vida útil limitada de 100.000
escrituras por página EEPROM | lee son ilimitadas. Tenga esto
en cuenta en su aplicación;
tratar de mantener las escrituras al mínimo, por lo que sólo se
escribe la menor cantidad de información requerida para su
aplicación cada vez que se actualiza la EEPROM.
1.2 ¿Cómo se se accede?
EEPROM interna del AVR se accede a través de registros
especiales en el interior del AVR, que controlan la dirección
que debe escribirse en (EEPROM utiliza direccionamiento de
byte), los datos que se escriben (o los datos que se ha leído),
así como la AGS encargar al controlador de EEPROM para
realizar la solicitada leer o escribir operación.
El lenguaje C no tiene ningún normas que obligan a la forma
en memoria que no sea una sola en el modelo (SRAM en
RAV) se accede o dirigida. Debido a esto, al igual que el
almacenamiento de datos arbitrarios en Memoria de borrado a
través de su programa,
la biblioteca estándar de cada compilador tiene una aplicación
única en base a lo que el autor creía era el sistema más
lógico.
Este tutorial se centrará espec camente en el compilador
AVR-GCC; otros compiladores pueden tener alternativo
sintaxis y APIs.
Chapter 2
Las funciones de EEPROM avr-libc
2.1 La inclusión de la cabecera de EEPROM avr-libc
La biblioteca avr-libc, incluido con WinAVR y (más
recientemente) el Atmel AVR Toolchain, contiene rutinas de
biblioteca creados previamente para el acceso y la
manipulación de EEPROM para simplificar su uso en el
usuario
aplicaciones. Antes de poder hacer uso de esas rutinas,
necesitamos incluir la librería EEPROM
Cabecera : <avr / eeprom.h> por el preprocesador directiva
#include <avr / eeprom.h>.
Una vez añadido a su aplicación, usted ahora tiene acceso a la
memoria EEPROM, a través de las rutinas Ahora proporcionada
por el módulo <avr / eeprom.h> de avr-libc. Hay cinco tipos
principales de EEPROM Acceso: byte, palabra, DWORD,
avena y manzana. Cada tipo tiene tres tipos de funciones; una
escritura, una actualización, y una variante de lectura. Los
nombres de las rutinas expuestos por nuestras nuevas
cabeceras son:
uint8_t eeprom_read_byte ( const uint8_t * addr )
void eeprom_write_byte ( uint8_t * addr , uint8_t value )
void eeprom_update_byte ( uint8_t * addr , uint8_t value )
uint16_t eeprom_read_word ( const uint16_t * addr )
void eeprom_write_word ( uint16_t * addr , uint16_t value )
void eeprom_update_word ( uint16_t * addr , uint16_t value )
uint32_t eeprom_read_dword ( const uint32_t * addr )
void eeprom_write_dword ( uint32_t * addr , uint32_t value )
void eeprom_update_dword ( uint32_t * addr , uint32_t value )
m_read_float ( const float * addr )
void eeprom_write_float ( float * addr , float value )
void eeprom_update_float ( float * addr , float value
void eeprom_read_block ( void * pointer_ram , const void *
pointer_eeprom , size_t n)
void eeprom_write_block ( const void * pointer_ram , void *
pointer_eeprom , size_t n)
void eeprom_update_block ( const void * pointer_ram , void *
pointer_eeprom , size_t n)
En AVR-GCC, una palabra es de dos bytes de longitud y una
palabra doble es el doble que la (4 bytes), mientras que un
bloque es un número arbitrario de bytes que se piensa
alimentación (bu cuerda? res, o tipos de datos de clientes
crear) y una de avena también es de cuatro bytes de longitud
(pero puede contener valores fraccionarios).
2.2 La actualización y escribir funciones
CAPÍTULO 2. FUNCIONES DE LA EEPROM AVR-LIBC
Históricamente, sólo había dos tipos de funciones de
EEPROM por tipo de datos; una función de escritura, y
una función de lectura. En el caso de las funciones de
escritura de EEPROM, estas funciones simplemente
escribieron los datos solicitados a la EEPROM y sin ningún
tipo de comprobación realizado;
Esto resultó en una reducción EEPROM vida útil si los datos
que se escriben ya coincide con el contenido actual de la
memoria EEPROM celda.
Para reducir el desgaste de la EEPROM vida útil limitada del
AVR, las nuevas funciones de actualización eran agregado
que sólo realizar una escritura de EEPROM si los datos a
partir del contenido de la celda actual.
Las funciones de escritura todavía se mantienen en torno a la
compatibilidad con aplicaciones antiguas, sin embargo
las nuevas funciones de actualización deben utilizarse en
lugar de las antiguas funciones de escritura en todas las
aplicaciones nuevas.
Capítulo 3
La lectura de datos desde la EEPROM
Para empezar, vamos a tratar un ejemplo simple y tratar de
leer un solo byte de la memoria EEPROM, digamos en la 46.
ubicación Nuestro código podría tener este aspecto:
# include < avr / eeprom .h >
void main ( void )
{
uint8_t ByteOfData ;
ByteOfData = eeprom_read_byte (( uint8_t *) 46) ;
}
Esto dará lectura a la ubicación 46 de la EEPROM y ponerlo en
nuestra nueva variable denominada byte de datos.
¿Como funciona? En primer lugar, declaramos nuestra variable
de bytes, que estoy seguro de que está familiarizado con:
uint8_t ByteOfData ;
Ahora, entonces llamamos a nuestra (de rutina
eeprom_read_byte), que espera un puntero a un byte en la
EEPROM espacio.
Estamos trabajando con un valor de dirección constante y
conocida de 46, por lo que añadiremos en el encasillado
para transformar ese número 46 en un puntero para la
eeprom_read_byte () para hacer funcionar el compilador
contento.
palabras EEPROM (dos bytes de tamaño) se pueden escribir
y leer de la misma manera, excepto que requiere un puntero
a un int:
# include < avr / eeprom .h >
void main ( void )
{
uint16_t WordOfData ;
WordOfData = eeprom_read_word (( uint16_t *) 46) ;
}
Y las palabras dobles y flotantes pueden leerse mediante el
eeprom_read_dword() y eeprom_read_float() funciones y un
puntero a una uint32_t o flotante variable.
Capítulo 4
La escritura de datos en la EEPROM
Muy bien, por lo que ahora sabemos cómo leer bytes y
palabras de la EEPROM, pero ¿cómo se escribe
¿datos? Los mismos principios que se aplican los datos de
EEPROM de lectura, excepto que ahora tenemos que
suministrar los datos
escribir como segundo argumento a las funciones de
EEPROM. Como se explicó anteriormente, vamos a utilizar el
las funciones de actualización de EEPROM en lugar de las
antiguas funciones de escritura para preservar la vida útil de
la EEPROM cuando sea posible.
# include < avr / eeprom .h >
void main ( void )
{
uint8_t ByteOfData ;
ByteOfData = 0 x12 ;
eeprom_update_byte (( uint8_t *) 46 , ByteOfData );
}
Como era de esperar, el proceso para la escritura de palabras
de datos en la EEPROM sigue el mismo patrón:
# include < avr / eeprom .h >
void main ( void )
{
uint16_t WordOfData ;
WordOfData = 0 x1232 ;
eeprom_update_word (( uint16_t *) 46 , WordOfData ) ;
}
Y palabras dobles y flotantes se pueden escribir con el
eeprom_update_dword() y eeprom_update_float()
funciones y una uint32_t or float variable.
Capítulo 5
El acceso EEPROM Bloque
Ahora podemos leer y escribir bytes y palabras a la
EEPROM, pero qué pasa con los grandes tipos de datos, o
cadenas? Aquí es donde los comandos de bloques vienen
en.
Los comandos de bloque de la avr-libc <avr / eeprom.h>
cabecera diferir de las funciones de palabras y bytes que se
muestran arriba. A diferencia de sus primos más pequeños,
los comandos de bloque son dos rutinas que devuelven
nada, y por lo tanto operar sobre una variable que usted
suministra como un parámetro. Veamos la declaración de
función para el comando de bloque de lectura.
void eeprom_read_block ( void * pointer_ram , const void *
pointer_eeprom , size_t n)
¡Guauu! Parece duro, pero en la práctica no lo es. Puede ser
el uso de un concepto que no está familiarizado con el sin
embargo void-type pointers.
punteros normales especifican el tamaño del tipo de datos en
su declaración (o encasillados), por ejemplo, uint8_t* es un
puntero a un byte sin signo y int16_t* es un puntero a un int
firmado. Pero void no es un tipo de variable que podemos
crear, así, ¿qué significa?
Un puntero de tipo void es útil cuando el tipo exacto de los
datos que se apunta que no se sabe por una función, o no es
importante. Un void es utilizado un puntero a una ubicación
de memoria, con los datos almacenados
en ese lugar de ser importante, pero el tipo de datos
almacenados en ese lugar no ser importante. ¿Por qué se
utiliza un puntero nulo?
El uso de un puntero nulo significa que el bloque de funciones
de lectura / escritura puede hacer frente a cualquier tipo de
datos que te gusta, ya que toda la función que hace es copiar
datos de un espacio de direcciones a otro. La función no hace
Importacia lo que copia los datos, solo que copia el número
correcto de bytes
Ok, eso es suficiente de un descarrilamiento | de nuevo a
nuestra función. Los comandos de bloque esperan un
puntero void * a una ubicación de la memoria RAM, un void *
puntero a una ubicación EEPROM y un valor que especifica
el número de bytes a ser escritos o leídos de las memorias
intermedias. En nuestro primer ejemplo vamos a tratar de
leer 10 bytes de memoria a partir de dirección de la EEPROM
12 en una cadena.
# include < avr / eeprom .h >
void main ( void )
{
uint8_t StringOfData [10];
eeprom_read_block (( void *) StringOfData , ( const void *) 12 , 10) ;
}
Una vez más, parece difícil no lo hace! Pero no lo es; Vamos a
romper los argumentos que estamos enviando a la
eeprom_read_block() funcion.
( void *) StringOfData
El primer argumento es el puntero de memoria RAM.
El eeprom_read_block () modifica la rutina de nuestro buffer
RAM y por lo que el tipo de puntero que está esperando es un
puntero void *.
Nuestra memoria intermedia es del tipo uint8_t, por lo que
necesitará su dirección encasillado al tipo void necesario.
( const void *) 12
La lectura de la EEPROM no va a cambiar, así que el segundo
parámetro es un puntero del tipo const void . Actualmente
estamos utilizando una constante fija como una dirección, por
lo que necesitamos que encasillado constante a un puntero al
igual que con los ejemplos de lectura de bytes y de palabra.
10
Obviamente, esto el número de bytes a leer. Estamos
utilizando una constante, sino una variable podría ser
suministrada en su lugar.
La función de actualización bloque se utiliza de la misma
manera, excepto por la rutina de actualización del puntero
RAM es del tipo const void y la EEPROM es sólo un puntero
void normal, ya que los datos se obtienen de la ubicación
RAM y se escribe en la ubicación EEPROM :
# include < avr / eeprom .h >
void main ( void )
{
uint8_t StringOfData [10] = " TEST ";
eeprom_update_block (( const void *) StringOfData , ( void *) 12 ,
10) ;
}
Capítulo 6
Usando el atributo EEMENM
Ahora debe saber cómo leer y escribir en la EEPROM
usando direcciones fijas. Pero eso no es muy práctico! En
una aplicación de gran tamaño, manteniendo todas las
direcciones fijas es una pesadilla absoluta en el mejor. Pero
hay una solución para impulsar este trabajo en el compilador
el atributo EEMEM.
Definido por el mismo <avr / eeprom.h de cabecera .que nos
da nuestras funciones de acceso a la EEPROM, el atributo
EEMEM indica al compilador GCC para asignar la variable
en el espacio de direcciones de memoria EEPROM, en lugar
del espacio de direcciones de memoria SRAM como una
variable normal. Por ejemplo, se podría añadir el siguiente
código a nuestra aplicación
# include < avr / eeprom .h >
uint8_t EEMEM NonVolatileChar ;
uint16_t EEMEM NonVolatileInt ;
uint8_t EEMEM NonVolatileString [10];
Y el compilador asignará automáticamente las direcciones
para cada una de las variables EEPROM dentro del espacio
de direcciones de memoria EEPROM, a partir de la posición
0. Aunque el compilador asigna direcciones de las variables
se declaran EEMEM, no se puede acceder directamente a
ellos. Esto significa que el código siguiente no funcionará
como se pretende:
# include < avr / eeprom .h >
uint8_t EEMEM NonVolatileChar ;
uint16_t EEMEM NonVolatileInt ;
uint8_t EEMEM NonVolatileString [10];
int main ( void )
{
uint8_t SRAMchar ;
SRAMchar = NonVolatileChar ;
}
Ese código se asigna la variable en lugar de RAM \ SRAMchar
"a lo que se encuentra en la SRAM en la dirección de \
NonVolatileChar", por lo que el resultado será incorrecto.
Como variables almacenadas en memoria del programa,
tenemos que seguir utilizando la lectura de EEPROM y escribir
rutinas descritas anteriormente para acceder al espacio de
memoria EEPROM independiente.
Así que vamos a tratar de arreglar nuestro código roto.
Estamos tratando de leer un solo byte de memoria. Vamos a
tratar de utilizar la rutina eeprom_read_byte ().
# include < avr / eeprom .h >
uint8_t EEMEM NonVolatileChar ;
uint16_t EEMEM NonVolatileInt ;
uint8_t EEMEM NonVolatileString [10];
int main ( void )
{
uint8_t SRAMchar ;
SRAMchar = eeprom_read_byte (& NonVolatileChar );
}
¡Funciona! ¿Por qué no intentamos dar lectura a las otras
variables ya que estamos en él. La segunda variable es un
entero, por lo que necesitamos la rutina pgm_read_word ():
# include < avr / eeprom .h >
uint8_t EEMEM NonVolatileChar ;
uint16_t EEMEM NonVolatileInt ;
uint8_t EEMEM NonVolatileString [10];
int main ( void )
{
uint8_t SRAMchar ;
uint16_t SRAMint ;
SRAMchar = eeprom_read_byte (& NonVolatileChar );
SRAMint = eeprom_read_word (& NonVolatileInt );
}
Y nuestra última es una cadena, por lo que tendremos que
utilizar el bloque de comando de lectura
# include < avr / eeprom .h >
uint8_t EEMEM NonVolatileChar ;
uint16_t EEMEM NonVolatileInt ;
uint8_t EEMEM NonVolatileString [10];
int main ( void )
{
uint8_t SRAMchar ;
uint16_t SRAMint ;
uint8_t SRAMstring [10];
SRAMchar = eeprom_read_byte (& NonVolatileChar );
SRAMint = eeprom_read_word (& NonVolatileInt );
eeprom_read_block (( void *) SRAMstring , ( const void *)
NonVolatileString , 10) ;
}
Capítulo 7
Establecimiento de valores iniciales
Por último, voy a discutir la cuestión de establecer un valor
inicial a las variables de EEPROM.
Tras la compilación de su programa con el makefile
predeterminado, GCC es la salida de dos archivos de formato
Intel-HEX. Una de ellas será su .HEX que contiene sus datos
de programa (y que se carga en la memoria del AVR), y el
otro será un archivo .EEP. El archivo contiene los valores
.EEP EEPROM por defecto, que se puede cargar en su AVR
a través de las funciones de programación EEPROM de su
programador.
To set a default EEPROM value in GCC, simply assign a value
to your EEMEM variable, like thus:
uint8_t EEMEM SomeVariable = 12;
Puede entonces el programa en el archivo .EEP resultante en
la EEPROM del destino mediante el software de programación
elegido. Esta es una operación independiente a la
programación en la aplicación de la hex generado si se le olvida
esto, la EEPROM del AVR no tendrá los valores iniciales
correctas! Usted debe encontrar una manera en la aplicación
para comprobar esta posibilidad de garantizar que no hay
problemas surgirá si los valores iniciales no se cargan
correctamente, a través de algún tipo de esquema de
protección (por ejemplo, la carga en varios valores y los
comparen con valores iniciales conocidos).