Transmisión y Recepción Serie con PIC 16F84A

ProgramaciónHace tiempo que escribí esta rutina para transmisión-recepción serie RS-232 con el PIC 16F84, y aunque puede resultar simple, creo que sirve como una buena base para aprender a programar en ensamblador un PIC, saber utilizar las interrupciones del mismo, conocer como funciona una transmisión asíncrona, y de paso reutilizar el 16F84 (un PIC sin USART) para aplicaciones que requieran comunicaciones en serie. La rutina debería de ser fácil de adaptar para crear una nueva aplicación para el PIC.

Lo que hace la rutina en sí es que el PIC haga eco del carácter (byte) que recibe del PC. En el PC utilizaremos para ello cualquier programa de terminal, que nos permita enviar caracteres por un puerto RS-232.

La rutina en la que me basé es original de Mike McLaren, la cual está hecha para el PIC 12F683. Esta es una adaptación de aquella para el PIC 16F84A. En el enlace que he puesto para acceder a la rutina de Mike McLaren, existe una buena colección de rutinas para PICs sin USART, que pueden resultar muy útiles, igual que esta. Las hay que utilizan o no las interrupciones, y diversos tipos de mecanismos para trabajar con bits o con bytes directamente a la hora de recibir o transmitir.

La rutina del sr. McLaren, trabaja con las interrupciones de manera que para cada bit se genera una interrupción para el tratamiento del mismo, ya sea para transmitir o recibir. Estas interrupciones se generan por un timer del PIC, que tendrá una duración exacta del tiempo de un bit. Bueno, para todos los bits, menos para el primero, el bit de inicio, que va justo previamente a los bits de cada carácter (byte). Para este bit de inicio se utiliza otra interrupción, que es la de cambio de estado del pin. Esta interrupción solo se habilita para la patilla que se conecte a la linea de recepción de datos. La velocidad a la que trabaja la rutina original es a 9600 Baudios, con 1 bit de inicio, 8 bit de datos, 1 bit de stop, y sin paridad.

A la hora de programar el PIC 16F84A para trabajar de este modo, tenemos que tener en cuenta los recursos del mismo. Los cuales son ligeramente distintos del 12F683. Por tanto hay algunas diferencias entre uno y otro que tuve que tener en cuenta para crear esta rutina:

  • El reloj de los PICs. Para simplificar la circuitería la de el sr. McLaren utiliza el reloj interno del 12F683 que trabaja a 8 MHz, lo cual significa que tenemos disponible un tiempo de instrucción de 0,5 microsegundos. Para trabajar con el 16F84A, no tenemos un reloj interno tan rápido, así que es mejor utilizar un reloj externo para generar una velocidad de reloj lo suficientemente adecuada para trabajar a 9600 Baudios. Yo elegí directamente la versión de 4 Mhz, aunque con la de 20 Mhz hubiese sido más fácil hacerlo.
  • La forma de tratar las interrupciones son distintas en cada uno de los PICs:
    • Respecto a los timers: En el 12F683, utilizamos el TIMER2, y para usarlo cargamos el valor de la cuenta que queremos que haga (el tiempo de duración de un bit) en un registro llamado PR2, con el cual cada vez que se incrementa el timer se compara, y si el valor es igual, se produce una interrupción. Sin embargo, en el 16F84A no tenemos tantos timers. De hecho solo tenemos el TIMER0, y en él tenemos que cargar una cantidad igual al valor 255 menos el valor de la cuenta que queremos hacer(el tiempo de duración de un bit, que a 9600 Baudios resulta ser 104 microsegundos de duración cada bit). Así cuando se complete la cuenta se producirá desbordamiento del TIMER0 y por tanto la interrupción.
    • Respecto a los cambios de estados en los pines: En el 12F683, podemos configurar cualquier pin como entrada y a la vez habilitar que su cambio de estado produzca una interrupción. En el 16F84A no. Solo tenemos el pin RB0/INT, el cual permite decir en que flanco del cambio de estado queremos que genere la interrupción, o podemos utilizar cualquier otro del conjunto RB4 a RB7, que no funcionan exactamente igual que RB0/INT. De modo que en esta rutina he optado por utilizar el RB0/INT, que producirá una interrupción cuando se genere un flanco de bajada. (NOTA: Cuando ponemos la etapa adaptadora entre RS-232 y TTL, en TTL la linea siempre estará en reposo a +5V. El primer bit que llega, el bit de inicio (START), modifica el estado de la linea. Ver este otro artículo).

Para probar la rutina hay que conectar el PIC 16F84A al puerto Serie RS-232 del PC.
En el PC tenemos que abrir un programa de terminal, y configurar el puerto donde conectemos el PIC a 9600 Baudios, 8 bits de datos, 1 bit de STOP y sin paridad.
Este es el esquema de conectividad:

Los valores de los componentes que he utilizado para este caso son: 100nF para el condensador entre VSS y VDD, 15pF para los condensadores del cristal y un cristal de 4 Mhz.
Para la etapa adaptadora de niveles RS-232 TTL, se puede utilizar el descrito anteriormente en este otro artículo.

Y este es el código, que está auto-comentado en castellano, con lo cual es muy fácil de entender su funcionamiento.

list p=16F84A, b=8, c= 102, n=71, t=on, st=off, f=inhx32
;******************************************************************
;*                                                                *
;*  Fichero: Serie_16F84.asm                                      *
;*    Autor: Jesus Carmona begin_of_the_skype_highlighting     end_of_the_skype_highlighting                                        *
;*    Fecha: 21/02/2009                                           *
;*                                                                *
;*   TX y RX Serie a 9600 baudios con 16F84A, utilizando          *
;*   interrupciones para el procesado de cada bit individual.     *
;*                                                                *
;******************************************************************

  #include  <p16f84a.inc>
  errorlevel  -302
  __config  _XT_OSC & _WDT_OFF

;
; Variables de los procesos
;
PROC232 equ     0x0C    ;Registro de proceso RS-232
TXCNT   equ     0x0D    ;Contador de bits de TX-232
TXVAR   equ     0x0E    ;Byte de dato de TX-232
RXCNT   equ     0x0F    ;Contador de bits de RX-232
RXVAR   equ     0x10    ;Byte de dato de RX-232
;
; PROC232 flag bits
;
RXFLAG  equ     0x00    ;1=RX en progreso
RXCHAR  equ     0x01    ;1=Caracter recibido después de RX.
TXFLAG  equ     0x02    ;1=TX en progreso.
RXSTART equ     0x03    ;1=START bit recibido.

; Otras constantes

RXPIN   equ     0x00    ;RS-232 RX - PORTB.0/INT
TXPIN   equ     0x01    ;RS-232 TX . PORTB.1
BAUDX   equ     d'104'  ;104 microsegundos por bit para 9600 baud
;
; Registros para salvar y restaurar la pila cuando se entra a la ISR
;
W_ISR   equ     0x11    ;ISR 'W'
S_ISR   equ     0x12    ;ISR 'STATUS'
P_ISR   equ     0x13    ;ISR 'PCLATH'
;
PTRL    equ     0x14    ;byte bajo de GREETING
PTRH    equ     0x15    ;byte alto de GREETING

; Notas sobre el Hardware:
;
; <1> Utilizamos xtal de 4 Mhz
; <2> Usamos PORTB.0 para entrada serie a 9600 baud.
; <3> Usamos PORTB.1 para salida serie a 9600 buad.
; <4> Utilizamos una etapa adaptadora RS232-TTL con transistores
; <5> Las señales RS-232 son invertidas por la etapa adaptadora RS232-TTL

;  Este programa simplemente imprime un texto al principio en el
;  Hyperterminal (o software de TXRX serie desde el PC), y luego
;  hace eco de los caracteres pulsados desde el teclado.
;
;  Configura tu programa terminal con 9600 Baud, 8, 1 stop, sin paridad.
;

;******************************************************************
;*                                                                *
;*                                                                *
;*                                                                *
;*                                                                *
;*                                                                *
;******************************************************************

        org     0x0000

START   goto    MAIN            ;                                 |B0

;******************************************************************
;*                                                                *
;*    Rutina de Servicio a la Interrupción.                       *
;*    La rutina procesa los bits individualmente.                 *
;*                                                                *
;*    Las interrupciones se generan cada 104-us por TMR0 para     *
;*    conseguir los 9600 baudios.                                 *
;*                                                                *
;*    Para detectar el comienzo de una recepción, se espera un    *
;*    flaco de bajada en RB0/INT. Se procesa el bit de START para *
;*    asegurarse de que es una entrada válida de datos y después  *
;*    se procesa la recepción del dato y el bit de STOP.          *
;*    RXSTART indica que se ha detectado un bit de inicio.        *
;*    RXFLAG indica que se ha comenzado a recibir un caracter     *
;*    RXCNT lleva la cuenta de 8 bits de datos +1 de STOP.        *
;*                                                                *
;*    Dado que cuando recibimos un flanco de bajada en RB0 como   *
;*    START bit, avanzamos el contador 52-us (medio bit),         *
;*    podríamos tener problemas si enviamos y recibimos a la vez. *
;*                                                                *
;******************************************************************

        org 0x0004
;
; Salvado de los registros W y STATUS en la entrada
;
ISR     MOVWF   W_ISR           ;Copia W a registro temporal de W |B0
        SWAPF   STATUS, W       ;salva STATUS en W                |B0
        MOVWF   S_ISR           ;Salva W en el temporal de STATUS |B0
        clrf    STATUS          ;retorna al banco 0               |B0
;
        btfsc   PROC232,RXFLAG  ;Estamos recibiendo ?             |B0
        goto    ISR_RX          ;Si. Procesar RX                  |B0
        btfsc   PROC232,TXFLAG  ;Estamos transmitiendo ?          |B0
        goto    ISR_TX          ;Si, Procesar TX                  |B0
        bcf     INTCON,INTF     ;ponemos a 0 el flag INTF         |B0
        bcf     INTCON,INTE     ;deshabilitamos la interrupción   |B0
        bsf     PROC232,RXSTART ;Hemos recibido un START bit      |B0
        bsf     PROC232,RXFLAG  ;indicamos que hay RX en marcha   |B0
        movlw   d'9'            ;9 bits (8 data + stop), porque...|B0
                                ;...el bit de START lo procesamos aparte
        movwf   RXCNT           ;initializamos el contador de bits|B0
        movlw   0xFF-(BAUDX/2)+d'18'  ;avanzamos TMR0...          |B0
        movwf   TMR0            ;...justo 1/2 bit(52-usec)        |B0
        goto    ISR_XIT         ;salimos                          |B0
;
; Rutina de proceso de una Transmision.
;
ISR_TX  movf    TXCNT,W         ;si, cargamos la cuenta de bits   |B0
        xorlw   b'00001010'     ;comparamos con 10 (total de bits)|B0
        btfsc   STATUS,Z        ;es el bit de START? no, salta 1  |B0
        goto    ISR_TX1         ;si, pues envia START bit         |B0
        rrf     TXVAR,f         ;desplaza el bit 0(menor peso) a C|B0
        bsf     TXVAR,7         ;pone el bit 7 a 0 como bit STOP  |B0
        btfsc   STATUS,C        ;si lo que hay en C es 0 salta 1  |B0
        bsf     PORTB,TXPIN     ;no. Pues pone TX a 1             |B0
        btfss   STATUS,C        ;si lo que hay en C es 1 salta 1  |B0
ISR_TX1 bcf     PORTB,TXPIN     ;no. Pues pone TX a 0             |B0
        decf    TXCNT,f         ;decrementa el contador de bits   |B0
        btfsc   STATUS,Z        ;era el último bit?               |B0
        bcf     PROC232,TXFLAG  ;si, pues indica final de TX      |B0
        movlw   0xFF-BAUDX+d'26';Carga TMR0 para de nuevo ...     |B0
        movwf   TMR0            ;... 104 microsegundos            |B0
        goto    ISR_XIT
;
; Rutina de proceso de una Recepción.
; Lo primero que procesamos es el START bit y luego los datos.
;
ISR_RX  btfss   PROC232,RXSTART ;Estamos en START bit ?           |B0
        goto    ISR_RXD         ;no, entonces procesamos DATOS    |B0
        bcf     PROC232,RXSTART ;Si. Quitamos la marca START bit  |B0
        btfsc   PORTB,RXPIN     ;Sigue a 0 la linea ?             |B0
        goto    ISR_X2          ;no, entonces falso START bit.Fin.|B0
        movlw   0xFF-BAUDX+d'15';Carga TMR0 para nuevos           |B0
        movwf   TMR0            ; 104 microsegundos               |B0
        goto    ISR_XIT         ;a la salida                      |B0
ISR_RXD bsf     STATUS,C        ;asume bit=0 (logica inversa)     |B0
        btfss   PORTB,RXPIN     ;es un 0?                         |B0
        bcf     STATUS,C        ;No, pues bit=1 (logica inversa)  |B0
        rrf     RXVAR,f         ;lo desplaza a RXVAR              |B0
        movlw   0xFF-BAUDX+d'18';Carga TMR0 para nuevos           |B0
        movwf   TMR0            ; 104 microsegundos               |B0
        decfsz  RXCNT,f         ;se han recibido 9 bits?          |B0
        goto    ISR_XIT         ;no, pues a la salida             |B0
        rlf     RXVAR,f         ;Retrocede justo el el STOP bit   |B0
ISR_X   bsf     PROC232,RXCHAR  ;indica que hay un caracter       |B0
ISR_X2  bcf     PROC232,RXFLAG  ;fin del proceso de RX            |B0
        bsf     INTCON,INTE     ;Habilitamos INT por flaco en RB0 |B0
        bcf     INTCON,INTF     ;Quitamos flag de INT RB0         |B0
;
; Salimos de la Rutina de Servicio de Interrupción (ISR, en ingles).
;
ISR_XIT bcf     INTCON,T0IF     ; quita flag de irq en TMR0       |B0
        SWAPF   S_ISR, W        ; Intercambia los nibbles de S_ISR|B0
                                ; y pone el resultado en W
        MOVWF   STATUS          ; Pone W en el registro STATUS    |B0
                                ; (pone el banco a su estado original)
        SWAPF   W_ISR, F        ; Intercambia los nibbles en W_ISR|B?
                                ; y pone el resultado en W_ISR
        SWAPF   W_ISR, W        ; Intercambia los nibbles en W_ISR|B?
                                ; y poner el resultado en W
                                ; Esto se hace así para no alterar
                                ; el registro STATUS en modo alguno
        retfie                  ; retorna de la interrupción      |B?
;
;******************************************************************

;******************************************************************
;
;  Bloque principal del programa
;
;******************************************************************
; Inicialización
;
MAIN    bsf     STATUS,RP0       ;banco 1                         |B1
        clrf    TRISB            ;Todo el PORTB como salida       |B1
        bsf     TRISB,RXPIN      ;Ponemos solo RXPIN como entrada |B1
;
; Configuramos el OPTION_REG con los siguientes parametros:
;   bit 7 (RPBU)= 1 : Habilitamos las weak pull-ups para las entradas.
;   bit 6 (INTEDG)= 0 : 0=flanco de bajada para el cambio de RB0/INT.
;   bit 5 (T0CS)=0 : Ponemos la clock source de TMR0 = 1/4 Fosc
;   bit 4 (T0SE)=x : Da igual
;   bit 3 (PSA)=1 : Se lo asignamos al WDT
;   bit 2-0 : Los ignoramos.
;
        movlw   b'10001000'     ;                                 |B1
        movwf   OPTION_REG      ;                                 |B1
;
        bcf     STATUS,RP0      ;banco 0                          |B0
        clrf    PORTB           ;Todos a 0                        |B0
        bsf     PORTB,TXPIN     ;ponemos TXPIN a 1 (STOP)         |B0
;
; Limpiamos los flags de nuestro registro para RS232 antes de
; habilitar todas las interrupciones.
;
        clrf    PROC232         ;                                 |B0
;
; Continuamos configurando el registro de control de interrupciones.
;
        clrf    INTCON          ;Deshabilitamos todo              |B0
        bsf     INTCON,T0IE     ;Habilita int. por overflow de TMR0|B0
        bsf     INTCON,INTE     ;Habilita int. por cambio en RXPIN|B0
        bsf     INTCON,GIE      ;Habilitamos todas las interrupt  |B0
;
;******************************************************************
;
; Imprime la cadena inicial de saludo
;
        movlw   low GREET       ;Direccion de la cadena(byte bajo)|B0
        movwf   PTRL            ;                                 |B0
        movlw   high GREET      ;Direccion de la cadena(byte alto)|B0
        movwf   PTRH            ;                                 |B0
        call    PRTSTR          ;Imprime el saludo inicial        |B0
;
; Rutina de eco de caracteres pulsados en el hyperterminal
;
TEST    call    RX232           ;recibe caracter en W             |B0
        call    TX232           ;envia el caracter de W           |B0
        goto    TEST            ;                                 |B0

;******************************************************************
;******************************************************************
;
; TX232 - Se entra cone el caracter que se va a enviar en W.
;
TX232   btfsc   PROC232,TXFLAG  ;TX in progress?                  |B0
        goto    TX232           ;yes, branch and wait             |B0
        movwf   TXVAR           ;stuff character                  |B0
        movlw   d'10'           ;10 bits (start + 8 data + stop)  |B0
        movwf   TXCNT           ;set TX bit count                 |B0
        bsf     PROC232,TXFLAG  ;initiate TX                      |B0
        return                  ;                                 |B0

;******************************************************************
;
; RX232 - Termina con el caracter recibido en W.
;
RX232   btfss   PROC232,RXCHAR  ;character available flag?        |B0
        goto    RX232           ;no, loop                         |B0
        movf    RXVAR,W         ;yes, get character               |B0
        bcf     PROC232,RXCHAR  ;clear flag                       |B0
        return                  ;                                 |B0

;******************************************************************
;
PRTSTR  call    GETSTR          ;Cogemos un caracter de la cadena |B0
        andlw   b'11111111'     ;Comprobamos si el caracter= 0x00 |B0
        btfsc   STATUS,Z        ;si caracter=0x00 es el último    |B0
        return                  ;Si, pues retorna                 |B0
        call    TX232           ;No, pues envia caracter          |B0
        incfsz  PTRL,F          ;Incrementa el puntero            |B0
        goto    PRTSTR          ;                                 |B0
        incf    PTRH,F          ;                                 |B0
        goto    PRTSTR          ;                                 |B0

GETSTR  movf    PTRH,W          ;                                 |B0
        movwf   PCLATH          ;                                 |B0
        movf    PTRL,W          ;                                 |B0
        movwf   PCL             ;                                 |B0

GREET   dt      0x1b, 0x5b, a'H'    ;home cursor
        dt      0x1b, 0x5b, a'J'    ;clear screen
        dt      "JCE Codigo transmision serie con 16F84A v1.0"
        dt      0x0d, 0x0a, 0x0a, 0x00

        end

NOTAS sobre el código: En las lineas 117, 136, 148 y 155, se suman a BAUDX cantidades aparentemente inexplicables. Estas cantidades son en realidad microsegundos consumidos por el propio código, que el timer debe de excluir, para que el conteo de los 104 microsegundos por bit se haga lo más preciso posible. Es decir, como mientras que se está procesando el código de la interrupción el contador no está contando, cada interrupción que se ejecuta es 1 microsegundo(o 2 en el caso de instrucciones de salto. Ver hoja técnica del PIC) que pasa dentro del tiempo de procesado del bit actual, y que deberemos de reajustar cuando cargamos el valor de la cuenta en el timer, para que la siguiente interrupción se produzca en el momento preciso. De otro modo, tendremos un desfase en la sincronización con el tiempo de bit, y podemos no leer el dato correcto.

Anuncio publicitario

Autor: Sphinx

Robotics enthusiast

3 opiniones en “Transmisión y Recepción Serie con PIC 16F84A”

  1. buen aporte. Tengo entendido que si uno hace comunicacion serie con un PIC que no tenga los pines tx y rx, entonces las interrupciones ser pierden. Voy a estudiar este codigo para hacerlo con otro pic y con leguaje c++

    Me gusta

    1. Hola rolandl337,
      Creo entender tu pregunta, pero no estoy seguro. De hecho, creo esa afirmación sobre las interrupciones y los pines puede confundir. Intento dar una explicación por si te ayuda a esclarecer alguna idea, y para el resto que nos lean:

      Más que tener pines de Rx y Tx, imagino que te refieres a que un PIC puede tener USART (como por ejemplo los PIC 18F2455/2550/4455/4550) y por tener USART no necesitan dedicar ninguna interrupción del PIC para la transmisión/recepción, mientras que el 16F84 y el 12F683 (que no tienen USART), necesitan ingeniárselas con las interrupciones que poseen para conseguir funcionar como una USART.
      Esto no quiere decir que para realizar tal función se vayan a tener que utilizar todas las interrupciones que posee el PIC. Y por supuesto tampoco que las que queden sin utilizar, no las podamos utilizar para otras tareas.

      En el caso de este artículo, el PIC 16F84 tiene 4 fuentes de interrupción: pin RB0/INT, del TIMER0, del conjunto de pines del puerto B del pin 4 al pin 7, y por último, la interrupción de «Data EEPROM write complete» .
      Las interrupciones que utiliza esta rutina son 2:
      – La del TIMER0 para conseguir el timing perfecto para cada bit, esos 104 microsegundos.
      – La interrupción del pin RB0/INT para detectar los bits que nos van a llegar (Rx).
      De este modo, aún nos quedan otras interrupciones para utilizar en el PIC.

      Si queremos que nuestro PIC haga muchas cosas, y además, que las haga a la vez, es posible que unas tareas interrumpan o afecten a otras. Pero para evitar esto hay que hacer una buena planificación de los recursos del PIC que queremos utilizar. Y si nuestro PIC no tiene recursos suficientes para llevarlas todas, tal y como queremos, es cuestión de buscar un PIC un poco más potente.

      Respecto a rutinas de Rx/Tx Serie para PIC que no utilizan interrupciones, en esta página del señor McLaren (que comento también en mi artículo), tiene una sección con algunas de ellas que quizá te sean de utilidad. Aquí te la dejo: http://www.piclist.com/techref/microchip/rs232.htm

      Me he paseado por tu blog, y me ha gustado mucho la estética. Además eres aficionado a la psicología y la pintura como yo! 😉
      Ánimo con él.

      Un saludo,
      Jesús.

      Me gusta

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

A %d blogueros les gusta esto: