Programador para los PIC16F627A, 628A y 648A

En el capítulo 10 del libro abordé el tema de los dispositivos para programar los PIC y la característica ICSP; también incluí algunas guías para hacer un programador propio. Sin embargo, una fuerte limitante para hablar de como hacer un programador, fue el compilador a usar para obtener los programas que se ejecutarían en la PC (no todas las bibliotecas necesarias son de uso estándar, y no resultaba razonable recomendar la compra de alguno en particular).

Ahora, al trabajar en Ubuntu, codificaremos los programas para la PC en lenguaje C, y usaremos el compilador gcc que se instala de forma automática con el sistema operativo. Para medir los tiempos que requiere la programación de los microcontroladores usaremos la función g_usleep, que forma parte de la biblioteca gtk. Para establecer la comunicación con la PC, usaremos un módulo convertidor USB-RS232 que sea capás de manejar señales de control, y trabajar con una tensión de 5 V (en mi caso, usé el que está etiquetado con el número 4 en la entrada Comunicación RS232 a través del USB). Probé todo el contenido de esta entrada con un PIC16F628A.

Si deseas hacer una prueba rápida antes de continuar leyendo y estudiando este material, el código LeeId.c  junto con el circuito de las figuras siguientes, permiten leer el identificador de los dispositivos PIC16F627A, PIC16F628A y PIC16F647A. MUY IMPORTANTE: Para que todo esto funcione, los microcontroladores deberán estar configurados para permitir el uso de MCLR y la programación a bajo voltaje (así vienen de fábrica).

Nota: Las partes sin aislante de los alambres permiten enganchar fácilmente las puntas del osciloscopio.
Programador para los PIC16F627A, 628A y 648A. La energía para trabajar se obtiene del USB a través del adaptador. Para colocar el microcontrolador se puede usar una base de fuerza cero, o una hilera de pines torneados cortada al tamaño.

Para leer y editar el archivo LeeId.c puedes usar un editor de textos “sencillo” como gedit (viene con el Ubuntu; yo prefiero usar Kate, que se puede instalar de forma gratuita).

Si no tienes la biblioteca gtk, puedes instalarla con los dos comandos siguientes (se requiere conexión a Internet y permisos de administrador):

sudo apt update
sudo apt install libgtk-3-dev

Para compilar LeeId.c y obtener el programa ejecutable LeeId, (asumo que el archivo con el código en C está en el directorio de trabajo) usa el comando:

gcc LeeId.c -o LeeId $(pkg-config --cflags --libs gtk+-3.0)

Para que LeeId pueda conectarse con el adaptador USB-RS232, la cuenta de usuario desde donde trabajas debe pertenecer al grupo dialout. Si no sabes como incorporar un usuario a un grupo, puedes consultar el material en esta liga.

Para ejecutar el programa, debes teclear:

./LeeId /dev/ttyUSB0

Si todo sale bien obtendrás el mensaje siguiente, pero recuerda que los 5 bits menos significativos pueden cambiar:

Identificador leído: 0x1068

Los documentos que usé para diseñar el circuito y los programas fueron:

Programar los PIC involucra:

  • Poner al microcontrolador en modo programación / verificación.
  • Establecer comunicación entre la PC y el PIC de forma secuencial, sincronizada y bidireccional.
  • Enviar comandos desde la PC al PIC.
  • El envío de datos desde la PC al PIC durante la programación, y la recepción por parte de la PC durante la verificación.
Diagrama de tiempos de las señales que ponen a los PIC16F627A, 628A y 648A en modo programación / verificación; modalidad de bajo voltaje. Al iniciar todas las terminales están en cero; se pone a 5 V VDD; después de por lo menos 5 us se sube PGM; 5 us después es MCLR la que se levanta; y 5 us después se puede iniciar el envío de comandos y datos para programar al PIC (variaciones en PGD y PGC).

La siguiente tabla muestra los comandos para programación:

Comando                MSb-LSb  Datos
Cargar configuración   XX 0000  0,datos(14),0
Cargar mem programa    XX 0010  0,datos(14),0
Cargar mem datos       XX 0011  0,datos(8),ceros(6),0    
Incrementar dirección  XX 0110
Leer mem programa      XX 0100  0,datos/14),0
Leer mem datos         XX 0101  0,datos(8),ceros(6),0
Programar              X0 1000
Borrar mem programa    XX 1001
Borrar mem datos       XX 1011
Pantalla de osciloscopio que muestra las señales ICSP durante la lectura del identificador de dispositivo de un PIC16F628A. MCLR/VPP en el trazo 1 (terminal TXD del módulo), PGC en el trazo 2 (terminal RTS), PGD en el trazo 3, terminal DTR en el trazo 4. La diferencia entre los trazos 3 y 4 se bebe a que tanto el módulo como el PIC escriben en PGD (sí, una simple resistencia evita el cortocircuito y mantiene la integridad de la información).
Similar a la imagen anterior, pero se cambió la base de tiempo para apreciar el momento en que sale el identificador (se pierde de vista la mayor parte de los comandos que ordenan la salida). Primero aparece el bit menos significativo.

En este punto los lectores con más experiencia ya habrán notado una incongruencia, y hasta una explicación al uso “extraño” de las líneas de control: el RS232 usa comunicación serie asíncrona, y el modo ICSP requiere comunicación serie sincronizada. Así que en lugar de usar las líneas de transmisión y recepción de datos de la PC, usaremos las líneas que originalmente eran para el control de equipos de comunicación (RTS, DTR y CTS). La PC produce y envía el reloj a través de RTS; la PC envía información (comandos e instrucciones a grabar) a través de DTR y el PIC la recibe a través de PGD; el PIC envía información a través de PGD y la PC la recibe por CTS.

Observa que, en principio, no hay suficientes líneas de salida en el RS232 para poner al microcontrolador en modo programación (subir primero VDD, luego PGM y al final MCLR). Así que probé mantener fijas VDD y PGM a 5 V; y después bajar y subir MCLR, funcionó (mas detalles al final de la publicación). Así, la orden para poner al PIC en modo programación sale por la terminal TXD y, después de configurar el puerto (líneas 39 y de la 46 a la 59 en LeeId.c), el código es tan simple como el que se muestra a continuación:

void ModoProg(void)
{
 unsigned char modoProgamacion=0;
 
 write(puerto,&modoProgamacion,1);
 g_usleep(600);
}

Cuando la PC no transmite información TXD se mantiene en 1 (5 v en la salida del modulo); cuando la instrucción write envía un dato por el puerto, TXD baja y sube de acuerdo a la información a transmitir; en este caso sólo produce una transición de bajada y otra de subida; g_usleep produce un retardo de 600 ms (un poco más que la duración del puso que se produce, e inicia, al ejecutarse la función write).

Para enviar comandos sin datos (de la tabla anterior: incrementar dirección, programar, borrar memoria de programa y borrar memoria de datos) diseñé la función Escribe:

void Escribe(int comando)
{
 int n, control=255;
 
 for (n=0; n<6; n++)
 {
  if (comando&0x1) control&=~TIOCM_DTR; else control|=TIOCM_DTR;
  comando=comando>>1;
  control&=~TIOCM_RTS; ioctl(puerto,TIOCMSET,&control); // RTS a 1
  control|=TIOCM_RTS;  ioctl(puerto,TIOCMSET,&control); // RTS a 0
 }
}

La función recibe como argumento el comando que debe enviar. La sentencia for controla el envío de 6 bits; if analiza el valor de cada bit en el comando a procesar y pone a DTR en el valor apropiado; las líneas 81 y 82 ponen a 1 y 0 la señal RTS para producir el reloj que requiere la comunicación sincronizada del ICSP.

La función Escribe2 envía comandos con datos:

void Escribe2(int comando, int dato)
{
 int n, control=255;
 
 for (n=0; n<6; n++)
 {
  if (comando&0x1) control&=~TIOCM_DTR; else control|=TIOCM_DTR;
  comando=comando>>1;
  control&=~TIOCM_RTS; ioctl(puerto,TIOCMSET,&control); // RTS a 1
  control|=TIOCM_RTS;  ioctl(puerto,TIOCMSET,&control); // RTS a 0
 }
 dato=dato<<1;
 for (n=0; n<16; n++)
 {
  if (dato&0x1) control&=~TIOCM_DTR; else control|=TIOCM_DTR;
  dato=dato>>1;
  control&=~TIOCM_RTS; ioctl(puerto,TIOCMSET,&control); // RTS a 1
  control|=TIOCM_RTS;  ioctl(puerto,TIOCMSET,&control); // RTS a 0
 }
}

Hasta el primer for esta función es prácticamente igual a Escribe. El segundo for procesa el segundo argumento, es decir: recibe un dato de 14 u 8 bits, le agrega un cero inicial, y transmite 16 bits. Observa que al iniciar la transmisión desde el bit menos significativo, los ceros adicionales (a veces 1 y otras veces 7) en la parte más significativa se obtienen automáticamente.

Las funciones para leer las memorias de programa y datos requieren del envío de un comando (XX 0100 ó XX 0101 – por cierto, la X significa que en ese bit puedes usar 0 ó 1, yo usé ceros), y luego de la recepción de información. La función Lee efectúa la tarea:

unsigned short int Lee(int comando)
{
 int n, dato=0, control=255;
 
 for (n=0; n<6; n++)
 {
  if (comando&0x1) control&=~TIOCM_DTR; else control|=TIOCM_DTR;
  comando=comando>>1;
  control&=~TIOCM_RTS; ioctl(puerto,TIOCMSET,&control); // RTS a 1
  control|=TIOCM_RTS;  ioctl(puerto,TIOCMSET,&control); // RTS a 0
 }
 for (n=0; n<16; n++)
 {
  dato>>=1;
  control&=~TIOCM_RTS; ioctl(puerto,TIOCMSET,&control); // RTS a 1
  control|=TIOCM_RTS;  ioctl(puerto,TIOCMSET,&control); // RTS a 0
  ioctl(puerto,TIOCMGET,&control);
  if (!(control&TIOCM_CTS)) dato|=0x8000;
 }
 dato>>=1;
 return dato;
}

El primer for realiza 6 iteraciones para enviar el comando; el segundo, permite efectuar la lectura de 16 bits. La sentencia if hace que, dependiendo del valor en CTS, se modifique la variable dato. Observa que al hacer la operación or con la constante 0x8000, cada 1 recibido se asigna al bit más significativo; pero las iteraciones incluyen un corrimiento a la derecha, por lo que cada bit termina en el lugar correcto.

Así, leer el identificador de los dispositivos involucra: poner al PIC en modo programación; apuntar a la dirección de memoria donde se almacena la configuración de los procesadores (comando cargar configuración); incrementar la dirección 6 veces para llegar a donde se almacena el identificador; y finalmente, leer el identificador. La función LeeId hace la tarea y se muestra a continuación:

unsigned short int LeeId(void)
{
 int n;
 
 ModoProg();
 Escribe2(0x0,0x0);
 for (n=0; n<6; n++) Escribe(0x06);
 return Lee(0x04);
}

El ejecutable que se obtiene a partir de Prog.c permite programar los microcontroladores con el contenido de un archivo HEX (formato Intel de 32 bits). El archivo HEX se pasa como segundo argumento a prog, y es el resultado de dar la orden Build Main Project en el MPLAB (si por ejemplo, un proyecto y su código fuente se llaman prueba, se obtendrá prueba.X.production.hex en el subdirectorio del proyecto dist/default/production). La orden para compilar Prog.c y un ejemplo para ejecutar prog son:

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

El siguiente programa en ensamblador sirve para producir una oscilación cuadrada en la terminal RB5 de un PIC16F628A. Este código usa el oscilador interno del procesador, por lo que sólo se requiere: conectar la alimentación del procesador, conectar MCLR a VDD (para evitar que el procesador se reinicie accidentalmente) y PGM a VSS (para evitar la entrada accidental al modo de programación LVP). Si usas este programa para probar el programador, podrás ver que un led parpadea rápidamente si lo conectas a RB5 (terminal 11, no olvides agregar una resistencia de unos 220 ohms); si usas un multímetro, podrás leer una tensión de aproximadamente 2.5 V; con un frecuencímetro u osciloscópio, podrás medir una frecuencia de aproximadamente 7.6 Hz.

            #INCLUDE    "p16f628a.inc"

            __CONFIG    0X3FB0
    
            ORG    0
            BSF    STATUS,RP0
            BCF    TRISB,5
            MOVLW  0D7
            MOVWF  OPTION_REG
            BCF    STATUS,RP0
            MOVLW  07
            MOVWF  CMCON
CICLO        
            BTFSS  INTCON,T0IF
            GOTO   CICLO
            BCF    INTCON,T0IF
            BSF    PORTB,5
ESPERA    
            BTFSS  INTCON,T0IF
            GOTO   ESPERA
            BCF    INTCON,T0IF
            BCF    PORTB,5
            GOTO   CICLO
            END

Por comodidad el archivo de salida se renombró  prueba.hex, su contenido es:

:020000040000FA
:1000000083168612D7308100831207309F000B1DA4
:1000100007280B1186160B1D0B280B1186120728BB
:040020000034003474
:02400E00B03FC1
:00000001FF

Los siguientes segmentos de código pertenecen a Prog.c y sirven para: leer el identificador de dispositivo y los bits de configuración, borrar la memoria de programa, leer la memoria de programa y escribir en la memoria de programa.

void LeeIdConfig(void)
{
 int n;
 
 ModoProg();
 Escribe2(0x0,0x0); // comando cargar configuración
 for (n=0; n<6; n++) Escribe(0x6); // comando incrementar dirección
 printf("identificador: %X\n",Lee(0x4)); // imprime lo que lee de la memoria de programa
 Escribe(0x6);
 printf("bits de configuración: %X\n",Lee(0x4));
}
void BorraMemProg(void)
{
 ModoProg();
 Escribe(0x9); // comando borrar memoria de programa
 g_usleep(6100); // espera el tiempo necesario para borrar
}
void LeeMemProg(void)
{
 int n, m, dato;
 
 ModoProg();
 for (n=0; n<10; n++)	// Para leer toda la memoria reemplazar el 10 por 256
 {
  printf("%04X > ",n*16);
  for (m=0; m<8; m++)
  {
   dato=Lee(0x4);
   dato=((dato&0xFF00)>>8)|((dato&0x00FF)<<8);
   printf("%04X ",dato);
   Escribe(0x6);
  }
  printf("\n");
 }
}
void ProgMemProg(FILE *arch)
{
 char renglon[50], cadena[5]="____";
 int n, m, dir, dato, n_datos;
 
 ModoProg();
 do
 {
  fgets(renglon,50,arch);
  printf("%s",renglon);
  if (renglon[8]=='0')
  {
   dir=(renglon[3]-'0')*16+(renglon[4]-'0');
   cadena[2]=0;
   if (dir<0x40)
   {
    cadena[0]=renglon[1]; cadena[1]=renglon[2];
    n_datos=(int)(strtol(cadena,NULL,16)/2);
    for (m=0; m<n_datos; m++)
    {
     cadena[0]=renglon[11+m*4]; cadena[1]=renglon[12+m*4];
     cadena[2]=renglon[9+m*4]; cadena[3]=renglon[10+m*4];
     dato=(int)strtol(cadena,NULL,16);
     Escribe2(0x2,dato);
     Escribe(0x8);
     g_usleep(6100);
     Escribe(0x6);
    }
   }
   if (dir==0x40) // Programa los bits de configuración
   {
    cadena[0]=renglon[11]; cadena[1]=renglon[12];
    cadena[2]=renglon[9]; cadena[3]=renglon[10];
    dato=(int)strtol(cadena,NULL,16);
    ModoProg();
    Escribe2(0x0,0x0);
    for (n=0; n<7; n++) Escribe(0x6);
    Escribe2(0x2,dato);
    Escribe(0x8);
    g_usleep(6100);
   }
  }
 } while(renglon[8]!='1'); // Detecta la última línea del archivo 
}

CONSIDERACIONES FINALES:

  • Los programas presentados constituyen versiones mínimas, a partir de las cuales podrás desarrollar códigos de acuerdo a tus necesidades.
  • La función LeeMemProg puede servirte para hacer una rutina que verifique la programación correcta de los procesadores. Observa que su ciclo principal se limitó para leer sólo una fracción de la memoria, y que leer la memoria completa sólo implica cambiar una constante en el código.
  • Las pequeñas operaciones con vectores en ProgMemProg, y otras a nivel de bits en la mayoría de las funciones, se deben a los diferentes formatos que toman las instrucciones (información a programar) en el archivo HEX, en los comandos, en la comunicación serie, etc.
  • La entrada forzada al modo programación que hicimos: nos limita a trabajar siempre en modo de programación de bajo voltaje; y NO nos permite usar la terminal MCLR como una entrada digital. Si cometemos un error con los bits de configuración y cambiamos MCLRE o LVP, no podremos reprogramar con este método ese PIC.
  • La forma de obtener una comunicación sincronizada, hace relativamente lento a este programador; por lo que te recomiendo usarlo sólo para programar un primer PIC que funcione como circuito programador e interfaz serie asíncrona con la PC (circuito y programas descritos en el capítulo 10 del libro).
  • Recuerda que Ubuntu es gratuito, se obtiene y actualiza a través de Internet, se puede instalar en computadoras que ya tienen otro sistema operativo, y que cada vez que arranques la PC, podrás decidir que sistema emplear.

2 comentarios en “Programador para los PIC16F627A, 628A y 648A”

Deja un comentario

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