EEPROM para datos en los PIC16

¿Requieres que ciertos datos permanezcan almacenados en el microcontrolador después de retirar la energía? Algunos PIC16 cuentan con memoria EEPROM para datos. En esta entrada te comento cómo se usa, y cómo complementar los programadores que hemos desarrollado para que puedan inicializar dicha EEPROM al programar el PIC.

La entrada contiene los ejemplos siguientes:

Probé el contenido de esta entrada con los PIC16F628A y 887. Los diagramas de los circuitos de prueba están en las figuras 1 y 2.

Diagrama del programador para el PIC16F628A.
Figura 1. Diagrama del programador para el PIC16F628A. Durante la programación del microcontrolador JP1 debe estar colocado y JP2 debe conectar las terminales 1 y 2. Para ejecutar el código grabado en el PIC hay que retirar JP1, y hacer que JP2 conecte las terminales 2 y 3. Observa que este circuito implica que el microcontrolador funcionará con su oscilador interno.
Diagrama del programador para el PIC16F887.
Figura 2. Diagrama del programador para el PIC16F887. Durante la programación JP1 debe estar colocado y JP2 debe conectar las terminales 1 y 2. Para ejecutar el código grabado en el PIC hay que retirar JP1, y hacer que JP2 conecte las terminales 2 y 3. Observa que este circuito implica que el uC funcionará con su oscilador interno.

Antes de iniciar con los códigos, recuerda que los microcontroladores tienen diferentes cantidades de memoria:

  • 128 bytes para los PIC16F627A, 628A y 882.
  • 256 bytes para los PIC16F648A, 883, 884, 886 y 887.

EepromDatos0.c. Este programa es un ejemplo de escritura – lectura de la EEPROM. Como puedes observar, la función de escritura eeprom_write requiere dos argumentos: dirección y dato. Ambos argumentos son de tipo unsigned char, por lo que pueden tomar valores de 0 a 255. Sin embargo para un uC que sólo tiene 128 bytes de memoria, por ejemplo el PIC16F628A, NO tendría sentido intentar almacenar algo en la localidad 200 (o cualquiera entre la 128 y la 255). La función para obtener la información almacenada en la EEPROM es eeprom_read, ésta devuelve el dato solicitado en un carácter sin signo y requiere como argumento la dirección de la localidad de memoria que contiene el dato. Para más información puedes consultar la sección 5.5.5 de MPLAB XC8 C Compiler User’s Guide.

A continuación te muestro el código de EepromDatos0.c así como una captura de pantalla que hice durante su simulación.

#include <xc.h>
#pragma config WDTE=OFF
// Usar la directiva siguiente para los PIC16F627A, 628A y 648A
// #pragma config FOSC=INTOSCIO
// Usar la directiva siguiente para los PIC16f882, 883, 884 y 887
#pragma config FOSC=INTRC_NOCLKOUT

void main(void)
{
 unsigned char dir, c;
 
 dir=0;
 for (c='A'; c<='Z'; c++)
 {
  eeprom_write(dir++,c);
  NOP(); // Observa que el contenido de la EEPROM no cambia inmediatamente.
 }
 for (dir=0; dir<26; dir++)
 {
  c=eeprom_read(dir);
  NOP(); // Sólo para facilitar el análisis del programa al correrlo por pasos.
 }
 // Evita que la parte anterior del programa se ejecute indefinidamente.
 while (1);
}
 Captura de pantalla que hice durante la simulación de EepromDatos0.c.
Figura 3. Captura de pantalla que hice durante la simulación de EepromDatos0.c. Observa que usé el PIC16F887. Puedes avanzar paso por paso en la simulación oprimiendo F8. No olvides colocar el punto de paro antes de iniciar la simulación. Considera que si programas el código en un PIC y permites que se ejecute, las letras de la A a la Z quedarán almacenadas en las primeras 26 localidades de la EEPROM para datos. Observa que el contenido de la memoria NO CAMBIA DE FORMA INSTANTÁNEA.

EepromDatosAsm es un código equivalente a EepromDatos0.c, pero hace más notorio que grabar un dato en la EEPROM toma tiempo, además de que hay medidas de precaución para evitar escrituras accidentales.

NOTAS:

Los registros de uso especial involucrados en el funcionamiento de la EEPROM para datos son:

  • EEDATA: Aquí debes almacenar el dato a escribir en la memoria, y también a través de este registro obtendrás la información previamente almacenada.
  • EEADR: Es para almacenar la dirección de la memoria que se utilizará en la siguiente operación de lectura o escritura.
  • EECON1: Sirve para ordenar que inicie una operación y para verificar si ésta ha concluido.
  • EECON2: Se usa para producir una secuencia de seguridad, cuyo fin es evitar escrituras accidentales en la memoria.

Entre otras cosas, observa que en EepromDatosAsm mando grabar letras minúsculas:

     #INCLUDE	"p16f628a.inc"

     __CONFIG	0x3FF8
     
; Variables en el banco 1 para evitar cambios.
     UDATA   0A0
DATO	RES	    1
DIR	 RES	    1	    
     
     ORG	    0
; Registros de control de EEPROM y variables en banco 1.
     BSF	    STATUS,RP0
     CLRF    DIR
     MOVLW   'a'
     MOVWF   DATO
; Habilita la escritura de la EEPROM.
     BSF     EECON1,WREN
ESCRITURA
; Espera a que NO haya proceso de escritura ejecutándose.
     BTFSC   EECON1,WR
     GOTO    ESCRITURA
     MOVF    DATO,W	; Preparar dato.
     MOVWF   EEDATA
     MOVF    DIR,W	; Preparar dirección.
     MOVWF   EEADR
     MOVLW   055		; Ejecuta secuencia de protección
     MOVWF   EECON2
     MOVLW   0AA
     MOVWF   EECON2
     BSF	    EECON1,WR	; Ordena iniciar la escritura.
     INCF    DATO,F	; Prepara dato y
     INCF    DIR,F      ; dirección siguientes.
     MOVF    DIR,W    
     SUBLW   .26
     BTFSS   STATUS,Z
     GOTO    ESCRITURA	; 26 iteraciones.
ULTIMO
; Avanza hasta que se almacena el último dato.
     BTFSC   EECON1,WR
     GOTO    ULTIMO	    
     BCF     EECON1,WREN	; Deshabilita escritura en EEPROM.
     
     CLRF    DIR		; Sección de lectura.
CICLO2	    
     MOVF    DIR,W
     MOVWF   EEADR
     BSF     EECON1,RD
     MOVF    EEDATA,W
     NOP			; Observa en este punto que el registro de 
     NOP			; trabajo contiene la información extraída
     NOP			; de la EEPROM.
     INCF    DIR,F
     MOVF    DIR,w
     SUBLW   .26
     BTFSS   STATUS,Z
     GOTO    CICLO2	; 26 iteraciones.
     NOP

FIN  GOTO    FIN
     END
Registro EECON1.
Figura 4. Registro EECON1. Esta figura pertenece a Microchip y se encuentra en la página 92 de la especificación del PIC16F628A.

ProgEeprom.c es un programa para la PC que graba o quema la EEPROM de datos de los PIC. Antes de escribir, ProgEeprom borra la memoria de datos pero deja intacta la memoria de programa. Llamé BorraEeprom a la función para borrar toda la memoria de datos, y de hecho, su uso es opcional ya que se puede escribir en la EEPROM sin borrarla previamente.

El código de BorraEeprom es:

void BorraEeprom(void)
{
 ModoProg();
 Escribe(0xB); // comando borrar memoria de datos
 g_usleep(6100); // espera el tiempo necesario para borrar
}

ProgEeprom es la función que lee los datos del archivo hex y los graba en el PIC. Considera que los datos se almacenan en el PIC a partir de la dirección 0x2100 (aunque para acceder al primer elemento desde un programa del PIC se usa la dirección 0), y que este número se observa en los archivos hex como un 0x4200. El código de la función ProgEeprom es:

void ProgEeprom(FILE *arch)
{
 char renglon[50], cadena[5]="____";
 int dir, n_datos, m, dato;
 
 ModoProg();
 do
 {
  fgets(renglon,50,arch); // Lee un renglón del archivo hex.
  if (renglon[8]=='0')
  {
   // Obtiene la dirección en la que se almacenará la información.
   cadena[0]=renglon[3]; cadena[1]=renglon[4]; cadena[2]=0;
   dir=(int)strtol(cadena,NULL,16);
   if (dir>=0x42) // La dirección corresponde a la EEPROM para datos.
   {
    // Obtiene el número de datos que contiene el renglón.
    cadena[0]=renglon[1]; cadena[1]=renglon[2]; cadena[2]=0;
    n_datos=(int)strtol(cadena,NULL,16)/2;
    printf("%d %s",n_datos,renglon);
    for (m=0; m<n_datos; m++)
    {
     // Programa n datos.
     cadena[0]=renglon[9+m*4]; cadena[1]=renglon[10+m*4];
     dato=(int)strtol(cadena,NULL,16);
     Escribe2(0x3,dato);
     Escribe(0x8);
     g_usleep(6100);
     Escribe(0x6);
    }
   }
  }
 } while (renglon[8]!='1');
}

La última función que diseñé se llama LeeEeprom, y como su nombre indica, obtiene el contenido de la EEPROM de datos y lo envía a la PC. El código:

void LeeEeprom(void)
{
 int n, m, dato;
 
 ModoProg();
 for (n=0; n<32; n++)
 {
  printf("%04X > ",n*16);
  for (m=0; m<8; m++)
  {
   dato=Lee(0x5);
   printf("%02X ",dato);
   Escribe(0x6);
  }
  printf("\n");
 }
}

Las instrucciones que deberás teclear en una terminal de la PC para compilar ProgEeprom.c y ejecutar ProgEeprom son:

gcc ProgEeprom.c -o ProgEeprom $(pkg-config --cflags --libs gtk+-3.0)
./ProgEeprom /dev/ttyUSB0 prueba.hex

EepromDatos1.c es un programa que:

  • Usa la macro __EEPROM_DATA para indicar los datos que deben ser almacenados en el PIC.
  • Transmite a través de la USART el contenido de una sección de la EEPROM de datos (68 direcciones).
  • Recibe a través de la USART un byte, lo almacena en la dirección 65 de la EEPROM para datos, y regresa a la sección de transmisión.

El código de EepromDatos1.c es:

#include <xc.h>
#pragma config WDTE=OFF
// Usar la directiva siguiente para los PIC16F627A, 628A y 648A
#pragma config FOSC=INTOSCIO
// Usar la directiva siguiente para los PIC16f882, 883, 884 y 887
//#pragma config FOSC=INTRC_NOCLKOUT

// Texto para almacenar en el PIC durante su programación.
// Cada llamada a la macro __EEPROM_DATA debe contener 8 bytes.
__EEPROM_DATA ('P', 'I', 'C', '1', '6', 'F', '8', '8');
__EEPROM_DATA ('7', ' ', 't', 'r', 'a', 'n', 's', 'm');
__EEPROM_DATA ('i', 't', 'i', 'e', 'n', 'd', 'o', ' ');
__EEPROM_DATA ('a', ' ', 'u', 'n', 'a', ' ', 'P', 'C');
__EEPROM_DATA ('.', 10, 13, 'P', 'r', 'u', 'e', 'b');
__EEPROM_DATA ('a', ' ', 'd', 'e', ' ', 'l', 'a', ' ');
__EEPROM_DATA ('E', 'E', 'P', 'O', 'M', ' ', 'd', 'e');
__EEPROM_DATA (' ', 'd', 'a', 't', 'o', 's', '.', 10);
__EEPROM_DATA (13, ' ', 10, 13, 0xFF, 0xFF, 0xFF, 0xFF);

void main(void)
{
 unsigned char dir, c;
 
 // Configura la USART.
 TXSTAbits.TXEN=1;
 TXSTAbits.BRGH=1;
 SPBRG=25; // 9600 bps, con un oscilador de 4 MHz.
 RCSTAbits.CREN=1;
 RCSTAbits.SPEN=1;
 while(PIR1bits.RCIF!=1); // Espera un dato.
 c=RCREG;
 do
 {
  // Transmite los datos almacenados en la EEPROM.
  for (dir=0; dir<68; dir++)
  {
   while(PIR1bits.TXIF!=1);
   TXREG=eeprom_read(dir);
  }
  while(PIR1bits.RCIF!=1); // Recibe un dato.
  c=RCREG;
  // Graba el dato recibido en la dirección 65 de la EEPROM.
  eeprom_write(65,c);
 } while (1);
}

Para probarlo deberás:

  • Incluir EepromDatos1.c en un proyecto del MPLAB X, y compilar.
  • Programar el código resultante empleando Prog88xV2.
  • Programar el contenido de la EEPROM usando ProgEeprom.
  • Permitir que el PIC inicie la ejecución.
  • Abrir GtkTerm en la PC y configurar el puerto.
  • Oprimir una tecla cada vez que quieras que el PIC envíe el contenido de la EEPROM; a partir de la segunda ocasión, el contenido de la localidad 65 variará de acuerdo al carácter enviado.

Ten en mente que es indistinto qué grabes primero, si el programa o los datos de la EEPROM; es decir, no importa el orden en que ejecutes Prog88xV2 y ProgEeprom.

En la figura 5 te muestro la captura de pantalla al probar el código en un PIC16F628A. Después de configurar el puerto oprimí las teclas: espacio, C, I y C.

Figura 5. Captura de pantalla al probar con un PIC16F628A.
Figura 5. Captura de pantalla al probar con un PIC16F628A: el circuito de la figura 1, Prog88xV2, ProgEeprom y EepromDatos1.c. Seguramente habrás notado que NO cambié el texto que dice PIC16F887 al reprogramar para un PIC16F628A.

Finalmente te dejo un ejemplo de la sintaxis en ensamblador para inicializar la EEPROM de datos (más información en la página 96 de MPASM Assembler, MPLINK Object Linker, MPLIB Object Librarian User’s Guide):

ORG	02100
de	'P', 'I', 'C', '1', '6', 'F', '8', '8'
de	'7', ' ', 't', 'r', 'a', 'n', 's', 'm'
de	'i', 't', 'i', 'e', 'n', 'd', 'o', ' '
de	'a', ' ', 'u', 'n', 'a', ' ', 'P', 'C'
de	'.', 10, 13, 'P', 'r', 'u', 'e', 'b'
de	'a', ' ', 'd', 'e', ' ', 'l', 'a', ' '
de	'E', 'E', 'P', 'O', 'M', ' ', 'd', 'e'
de	' ', 'd', 'a', 't', 'o', 's', '.', 10
de	13, ' ', 10, 13

Hasta la próxima.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *