La comunicación entre dispositivos es vital para diseñar sistemas más complejos. En este tema, uno de los primeros pasos cuando se trabaja con el PIC es la comunicación con el PC usando el puerto serie.
A continuación veremos las posibilidades disponibles y una implementación de una UART en ensamblador para el PIC.

Escenario

Por supuesto, existen muchos microcontroladores que integran entre sus periféricos una UART. En estos casos es mucho más sencilla la comunicación con el PC o con otros periféricos, pues la UART se encarga de casi todo. El problema surge cuando el micro que usamos no tiene el hardware adecuado (por problemas de espacio, de costo, etc.). En esos casos, es posible utilizar UART's hardware externas, pero que presentan el problema de tener que usar más hardware (encarecimiento, volumen...), por lo que quizá tampoco sea una solución. Para estas situaciones (u otras que se puedan presentar) tenemos la posibilidad de diseñar nosotros mismos la UART e implementarla en software. Veamos un ejemplo de esta implementación.

Ingredientes

Lo que vamos a hacer es conectar un PIC al PC para enviar información entre ambos dispositivos. Usaremos el puerto serie del PC, que emplea la norma RS232. Para hacer esto, tenemos que convertir los niveles de señal entre los que usa el PIC (TTL) y los del puerto serie (RS232). Para ello, necesitaremos un conversor. En este caso, usaremos el DS275. Para el conexionado y las señales, te remito a: La consola de la fonera: DS275. Si no quieres leer mucho, estos son los pines a conectar (usando un DB-9 para el RS232):
 RS232      DS275      PIC
--------------------------
 RxD(2)     TXout(5)
 TxD(3)     RXin (7)

            RXout(1)   RxD
            TXin (3)   TxD
En cuanto a hardware, sólo eso. Necesitarás también una protoboard, pines... lo normal en estos casos. El software lo haremos nosotros ;-) .

Comunicación serie

Antes de seguir, nos sería útil saber cómo funciona una comunicación serie, en este caso la que se usa en el puerto serie de nuestro PC (o Fonera...). Serie nos indica uno detrás de otro, y en efecto, cuando transmitimos algo en serie, lo hacemos bit a bit. En el caso del PIC, si queremos transmitir algo, hemos de usar uno de los pines configurado como salida. Para transmitir un '1' lógico, ponemos el pin en nivel alto (+5V por ejemplo) y si queremos enviar un '0', ponemos el pin en nivel bajo (cerca del nivel GND). Así, variando en el tiempo el estado del pin, podemos enviar todos los datos que queramos. La pregunta que surge es: ¿cuánto tiempo mantenemos el nivel? La respuesta es obvia: dependiendo de la velocidad con que estemos transmitiendo. En el RS232 mediremos la velocidad en bps (bits por segundo). Así pues, tenemos diferentes velocidades 'estándar': 600, 1200, 2400... En nuestro ejemplo usaremos 9600 bps, pero si es necesario podríamos llegar a velocidades de casi 1 Mbps. Luego veremos cuanto ha de durar cada bit para enviar a esa velocidad. Vemos que si cada bit tiene una duración fija, constante, no es necesario un reloj que marque los cambios de bit. Cierto, no es necesario, es implícito. Pero esto implica que si se comete un error en la transmisión de un bit, el resto de la comunicación sería basura. Existen por tanto ciertos métodos y bits bandera para que esto no ocurra. En primer lugar, el estado del pin de envio mientras no se está enviando es fijo (deber ser '1'). Así, cuando se empieza la transmisión, enviamos un bit de inicio (que es un '0'). Después, se acuerda entre emisor y receptor cuantos bits van a conformar los datos: 5, 6, 7 u 8. Lo normal es 8 (un byte). Se envían respetando los tiempos de cada bit. Una vez enviados los datos, el siguiente bit es opcional y se usa como mecanismo para evitar errores: la paridad. Consiste en un bit que indicar si el número de unos o ceros en los datos es par o impar. Aquí no lo usaremos. Para terminar, enviamos uno o dos bits de parada, de forma que el pin de transmisión se quede en el nivel lógico '1'. Así, se termina la transmisión de nuestro dato. Nos quedamos con la velocidad (9600 bps), los bits de datos (8bits), la paridad (N) y el bit de stop (1): 9600 8N1 Podemos verlo todo un poco más claro en el siguiente cronograma:
Cronograma rs232

UART software

Ahora lo que haremos será implementar unas rutinas que nos simplifiquen el envio de datos por el puerto serie. Antes de nada, convenir que pines usaremos. El PORTA,0 será el pin TxD mientras que el PORTA,1 será el RxD. Usaremos el PIC16F84, así lo configuramos:
	list	p=16f84		; Procesador a usar.
	include	<p16f84.inc>

;;; Configuración
;;;  Oscilador:	cristal de cuarzo
;;;  WatchDogTimer: apagado
;;;  CodeProtection: desactivado
;;;  PoWeR up Timer activado
	__CONFIG _XT_OSC & _WDT_OFF & _CP_OFF & _PWRTE_ON
También podemos definir las constantes que usarán las rutinas:
;;; Constantes
tx_port equ	PORTA 		; Puerto de tranmisión
tx_pin	equ	0x0		; Pin de transmisión
rx_port equ	PORTA 		; Puerto de recepción
rx_pin	equ	0x1		; Pin de recepción

data_tx	equ	0xd		; Datos a enviar
data_rx	equ	0xf		; Datos recibidos

count_d equ	0xc		; Contador para el retardo
count_p	equ	0x10		; Contador para el número de bits
Ahora podemos definir la rutina de retardo, que será la que marque el tiempo que consideraremos de un bit. Como hemos dicho, la velocidad de transmisión será 9600bps. También hay un comentario que indica que el reloj que usaremos será de 4 MHz, lo que indica que tendremos un ciclo de reloj de 1 us. Si dividimos 9600/1s = 104 us. Luego la duración de cada bit ha de ser de 104 us. Nuestro bucle será así:
rs232_d:
	movlw	D'32'		; 1 us (algo menos de 33)
	movwf	count_d		; 1 us
	decfsz	count_d, F	; 1 us (+ 1 en caso de skip)
	goto	$-1		; 2 us

	return			; 2 us
Y como vemos, se ejecuta una media de 3 veces más 5 ciclos de inicialización y retorno. Luego haciendo un cálculo rápido vemos que con justo 33 veces conseguimos que rs232_d dure 104 us. En la práctica, no nos es útil acercarnos tanto al límite, por lo que si usamos 32, funciona mejor. Ahora, cada vez que enviamos un bit, hemos de esperar el tiempo marcado por rs232_d. Veamos la rutina de envio:
send_b:	               
        movwf   data_tx        	; Guargamos el dato a enviar
        movlw   d'8'          	; Enviaremos 8 bits 
        movwf   count_p
	
        bcf     tx_port,tx_pin	; enviamos el bit de inicio
        call    rs232_d         
	
s_loop:
	btfss	data_tx,0	; comprobamos el primer bit
        bcf     tx_port,tx_pin  ; si es un 0 enviamos un 0	
        btfsc   data_tx,0       
        bsf     tx_port,tx_pin  ; si es un 1 enviamos un 1

        call    rs232_d         ; mantenemos el bit el tiempo necesario
	
        rrf     data_tx,F	; movemos los bits a la derecha	

        decfsz  count_p,F       ; si no se han enviado 8 bits...
        goto    s_loop       	; seguimos
	
        bsf     tx_port,tx_pin 	; si se han enviado, enviamos el bit de stop
        call    rs232_d
	return
NOTA: enviamos el LSB (least significative bit) primero, el bit 0. Antes de llamar a esta rutina, debes poner en W el byte que quieras enviar. Este byte se guarda en un registro, así como el número de bits de datos. Se envia el bit de inicio y después el resto de bits. Una vez terminado, nos queda poner el bit de stop y retornar. Sencillo, ¿verdad? Pues la de recepción no es mucho más complicada:
recv_b:
	movlw	d'8'
	movwf	count_p
	clrf	data_rx		; daremos por hecho que son todo 0's
	btfsc	rx_port, rx_pin ; comprobamos bit de inicio
	return			; datos erróneos	

r_loop:
	rrf	data_rx,F	; rotamos a la derecha (primero LSB)
	call	rs232_d		; esperamos el tiempo adecuado
	
	btfsc	rx_port, rx_pin	; solo cambiamos si es un 1
	bsf	data_rx,7	
	decfsz	count_p, F
	goto	r_loop
	
	movf	data_rx, W	; retornamos el dato leido, en W
	return	
Antes de llamar a esta rutina, es necesario que compruebes el pin RxD en espera del bit de inicio. Inmediatamente después de cambiar de estado el pin RxD, se ha de llamar a esta rutina. Es posible retardarse un lapso de tiempo máximo de rs232_d/2, pero no mucho mayor, si se quiere leer algo. Lo más aconsejable es usar como pin RxD uno que sea sensible a cambios de estado mediante interrupciones, y llamar a la rutina de lectura desde el vector de interrupción. Así se evita el hacer un polling al pin RxD.

Ejemplo de uso

Como ejemplo de uso, veremos un simple servicio de echo en el puerto serie: cada byte que se envía desde el PC al PIC es retornado de nuevo al PC. Es algo muy sencillo:
;;; -*- coding:	utf-8 -*-
;;; Prueba de comunicación con el puerto RS232 de
;;; un pc. Se establece la comunicación a
;;; 9600bps, 8N1, con un cristal de 4MHz

;;; Author: Oscar Acena, (c) 2006, 2007
;;; License: GPL

	list	p=16f84		; Procesador a usar.
	include	<p16f84.inc>

;;; Configuración
;;;  Oscilador:	cristal de cuarzo
;;;  WatchDogTimer: apagado
;;;  CodeProtection: desactivado
;;;  PoWeR up Timer activado
	__CONFIG _XT_OSC & _WDT_OFF & _CP_OFF & _PWRTE_ON

;;; Constantes
tx_port equ	PORTA 		; Puerto de tranmisión
tx_pin	equ	0x0		; Pin de transmisión
rx_port equ	PORTA 		; Puerto de recepción
rx_pin	equ	0x1		; Pin de recepción

data_tx	equ	0xd		; Datos a enviar
data_rx	equ	0xf		; Datos recibidos
	
count_d equ	0xc		; Contador para el retardo
count_p	equ	0x10		; Contador para el numero de bits

	org	0		; Punto de inicio
	goto	main

	org	4		; Rutina de interrupcion
	retfie

;;; -------------------------------------------------------------
;;; Tenemos que transmitir a 9600bps, lo que implica 1 bit
;;;  cada 1/9600 s -> 104 us. Con un reloj de 4MHz, tenemos
;;;  un ciclo de 1us. El siguiente codigo es un bucle que usa una
;;;  media de 3 ciclos, por lo que si lo ejecutamos unas 33 veces,
;;;  tendremos el ratio deseado: (2+1)*33 + 1+1+1+2 = 104 us 		 
rs232_d:
	movlw	D'32'		; 1 us (algo menos de 33)
	movwf	count_d		; 1 us
	decfsz	count_d, F	; 1 us (+ 1 en caso de skip)
	goto	$-1		; 2 us

	return			; 2 us

;;; -------------------------------------------------------------
;;; Recibimos un byte, que dejamos en w cuando retornamos. Usamos la
;;; configuración 8N1. El bit de start será el primero que leeremos.
recv_b:
	movlw	d'8'
	movwf	count_p
	clrf	data_rx		; daremos por hecho que son todo 0's
	btfsc	rx_port, rx_pin ; comprobamos bit de inicio
	return			; datos erróneos	

r_loop:
	rrf	data_rx,F	; rotamos a la derecha (primero LSB)
	call	rs232_d		; esperamos el tiempo adecuado
	
	btfsc	rx_port, rx_pin	; solo cambiamos si es un 1
	bsf	data_rx,7	
	decfsz	count_p, F
	goto	r_loop
	
	movf	data_rx, W	; retornamos el dato leido, en W
	return
	
;;; -------------------------------------------------------------
;;; Enviamos un byte, que debe estar en W antes de llamar a esta rutina
;;; con la cofiguración 8N1, a la velocidad estipulada por rs232_d.
send_b:	               
        movwf   data_tx        	; Guargamos el dato a enviar
        movlw   d'8'          	; Enviaremos 8 bits 
        movwf   count_p
	
        bcf     tx_port,tx_pin	; enviamos el bit de inicio
        call    rs232_d         
	
s_loop:
	btfss	data_tx,0	; comprobamos el primer bit
        bcf     tx_port,tx_pin  ; si es un 0 enviamos un 0	
        btfsc   data_tx,0       
        bsf     tx_port,tx_pin  ; si es un 1 enviamos un 1

        call    rs232_d         ; mantenemos el bit el tiempo necesario
	
        rrf     data_tx,F	; movemos los bits a la derecha	

        decfsz  count_p,F       ; si no se han enviado 8 bits...
        goto    s_loop       	; seguimos
	
        bsf     tx_port,tx_pin 	; si se han enviado, enviamos el bit de stop
        call    rs232_d
	return


;;; Rutina principal. Configura el pic.
main:
	bsf	STATUS, RP0	
	bcf	tx_port, tx_pin ; Pin TxD como salida
	bcf	TRISB, 0x3      ; y pin PORTB,3 como salida también
	bcf	STATUS, RP0

loop:	
 	btfsc	rx_port, rx_pin ; Esperamos al bit de inicio
 	goto	$-1
	
	bsf	PORTB, 0x3
	call	recv_b		; recibimos 
	call	send_b		; enviamos
	bcf	PORTB, 0x3
	
	goto	loop
	end
En este caso, tengo puesto un LED en el PORTB,3, que se enciende cuando hay comunicación. Lo puedes probar con el minicom, o mejor con algo hexadecimal, como el cutecom.

Disclaimer

Esta seguro no es la mejor UART que existe, desde luego. Acepto comentarios que ayuden a mejorarla. Este es mi pequeño aporte, quizá entre todos salga algo decente que podamos usar. Es lo genial del software libre. Por otro lado, todo esto es software prototipo. No esta completamente depurado y NO ASEGURO QUE FUNCIONE EN NINGÚN CASO. NO ME HAGO RESPONSABLE DE LOS DAÑOS CAUSADOS AL HARDWARE, A LA COMPUTADORA O A SU PERSONA. Puedes modificarlo o usarlo a tu antojo. Queda bajo la licencia GPL v2.0 o posterior.

Referencias

  1. X-Robotics
  2. Mucha info útil (alemán)


blog comments powered by Disqus