RS232 en el PIC: UART por software

embedded

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)

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.

En el codigo no se deberia

En el codigo no se deberia poner en el pin de trasnmision a 1 inicialmente??

Imagen de oscarah

Pues si, debería

La verdad es que esta es la primera versión que hice (o una de las primeras) y he hecho tantos cambios desde entonces que ya poco se parecen. Llevas toda la razón, el bit de stop debería ser 1.

A ver cuando saco un rato y actualizo la receta un poco. De todos modos, se admiten correcciones. Gracias.
________________________________________________
La “S” de “CRySoL” es de “Software” no de “Salsa-rosa”.
La “L” de “CRySoL” es de “Libre” no de “Linux”.

"aviso: la dereferencia de punteros de tipo castigado romperá las reglas de alias estricto" --GCC 4.3.1

problemas

Hola, realice un circuito para este proyecto, basicamente se trata de una entrada de rs232 a un max232 y de ahi a los pines indicados del pic. en lugar de usar el 16f84, utilice el 16f84a lo cual creo que en este caso es lo mismo y en lugar de usar el ds275 utilice un max232.

el problema es que no puedo lograr que funcione... respete todo lo necesario (creo) pero no logro que me devuelva ninguno de los caracteres que envio. Lo maximo que logre es a una velocidad mas baja (2400 bps), al enviar un caracter un led conectado a portb, 3 se apagaba y volvia a encenderse, como indicando que recibio el envio que hice, pero nunca me hizo el echo esperado.

quisiera saber si puedes enviarme el esquema del circuito para hacerlo funcionar, o bien guiarme si hay algo mas que deba tener en cuenta.

por favor espero puedas ayudarme, ya que quisiera lograr hacer funcionar algo como esto... llevo meses de fracasos.

muchas gracias de antemano,

Jonathan

Imagen de oscarah

Mira por aquí...

En este enlace alguien que tenía tu problema parece que lo solucionó

http://netandtech.wordpress.com/hardware/rs323-y-el-pic-uart-por-software/
________________________________________________
La "S" de "CRySoL" es de "Software" no de "Salsa-rosa".
La "L" de "CRySoL" es de "Libre" no de "Linux".

"aviso: la dereferencia de punteros de tipo castigado romperá las reglas de alias estricto" --GCC 4.3.1

Mu chulo....

No lo he leído de cabo a rabo, solo un poco por encima, pero tiene buena pinta Eye-wink
De todos modos creo recordar que paco hizo algo por el estilo ¿me equivoco?

Salu2 a tod@s

The cause of the problem is:
The vendor put the bug there.
-- Meta amigo informático --

Imagen de oscarah

seguro,

Y mejor, con mecanismos para identificar el rate, y más cosas.
Esto me ha servido para aprender a hacerlo cuando no tenga la de Paco a mano... Sticking out tongue
________________________________________________
La "S" de "CRySoL" es de "Software" no de "Salsa-rosa".
La "L" de "CRySoL" es de "Libre" no de "Linux".

"aviso: la dereferencia de punteros de tipo castigado romperá las reglas de alias estricto" --GCC 4.3.1

jejeje

jejeje

The cause of the problem is:
The vendor put the bug there.
-- Meta amigo informático --