Hace 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.
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 gustaMe gusta
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 gustaMe gusta