Vamos a dedicar un par de entradas a examinar con cierto
detalle el funcionamiento de las comunicaciones asíncronas (UART, o lo que habitualmente
se conoce como el puerto serie) de la familia PIC18.
Nos centraremos en el software, dejando de lado el aspecto
hardware, sobre todo la conversión de niveles si la comunicación es entre un
PIC y el PC o entre dos microcontroladores con diferentes voltajes (p.e. 3.3V y
5V). En mis pruebas he usado el conversor de niveles integrado en la placa
EasyPIC6. En general habrá que usar un conversor de niveles basado en el Max232
o similar. Una búsqueda de RS232 MAX232 TTL en ebay nos mostrará varias
posibilidades a partir de unos $5.
En primer lugar presentaremos las funciones básicas que la
mayoría de los compiladores tienen para abrir el puerto serie, enviar/recibir,
etc. Luego escribiremos nuestras propias rutinas. El objetivo no es prescindir
de las rutinas del compilador, sino entender los fundamentos para ser capaces
(cuando sea necesario) de saltarnos las limitaciones de un compilador en
particular.
También escribiremos una función que nos permita
configurar el puerto serie a partir de la velocidad en baudios, sin tener que
calcular y programar los registros asociados.
------------------------------------------------------------------------------------------------------------
Funciones básicas de manejo de
puerto serie: (uart0.c)
En primer lugar vamos a ver un sencillo código en C que
establece una comunicación serie entre el PIC y p.e. un PC usando las rutinas
del compilador. Dos compiladores que uso habitualmente son el C18 de Microchip
y el MikroC Pro de Mikroelectronica. No importa que estéis usando algún otro
compilador porque la idea es profundizar un poco más sobre que hace un compilador
al manejar el puerto serie de un PIC.
Las
funciones básicas de ambos compiladores en relación al puerto serie son:
FUNCION
|
MikroC Pro
|
C18
|
Inicialización
|
UART1_init
|
Open_USART
|
Data ?
|
UART1_Data_ready
|
DataRdyUSART
|
Leer
|
UART1_read
|
ReadUSART /
getcUSART
|
Escribir
|
UART1_write
|
WriteUSART / putcUSART
|
Usando las
funciones anteriores, un sencillo programa en C18 que abre el puerto serie a
9600 bauds, lo monitoriza el puerto serie y rebota lo que recibe sería el
siguiente (notad que es necesario incluir usart.h):
#include <usart.h>
void main()
{
char ch;
TRISB=0; PORTB=0;
OpenUSART(USART_ASYNCH_MODE &
USART_EIGHT_BIT & USART_BRGH_HIGH,129);
while(1)
{
if (DataRdyUSART())
{
ch = get_byte(); send_byte(ch+1);
send_byte(0x0D); send_byte(0x0A);
PORTB++;
}
Delay10KTCYx(5);
}
}
El mismo
programa en MikroC Pro:
void main() {
char ch;
TRISB=0; PORTB=0;
UART1_Init(9600);
while(1)
{
if (UART1_Data_Ready())
{
ch = UART1_Read(); UART1_Write(ch+1); UART1_Write(0x0D);
UART1_Write(0x0A);
PORTB++;
}
delay_ms(10);
}
}
Si
conectamos PIC y PC, cualquier cosa que tecleemos en un terminal nos es
devuelta como el carácter siguiente en la tabla ASCII, debido a que enviamos
(ch+1) donde ch es el carácter recibido. Además se manda un retorno de carro y
salto de línea. Según la configuración de nuestro terminal es posible que
veamos tanto el carácter que hemos tecleado como el que envía el PIC. Además
PORTB se incrementa con cada carácter recibido de forma que podemos ver un
feedback visual de los bytes recibidos.
Lo que
vamos a hacer ahora es volver a escribir las funciones anteriores pero usando
nuestro propio código, lo que implicará manejar los SFR (Special Function
Register) del PIC asociados a la UART. El efecto colateral será que entenderemos como funcionan
a "bajo nivel" dichas comunicaciones.
Hay 5
registros que tendremos que conocer y manejar:
TXSTA -> Status de TX, con el indicamos las
opciones de transmisión
RXSTA -> Status de RX, opciones de recepción.
SPBRG -> determina la velocidad de las
comunicaciones (bauds) junto con el bit BRGH de TXSTA
RCREG y TXREG -> registros donde se guarda el
byte recibido y donde ponemos el byte a transmitir.
La más
complicada es la inicialización de la UART. Afecta a los distintos bits de
TXTSTA, RXSTA y SPBRG.
void setup_UART(uint8 brgh,uint8 spbrg)
{
TRISCbits.TRISC6=0; TRISCbits.TRISC7=1; //
RC6 out (tx), RC7 in (rx)
TXSTA =
0b00100110;
/*TXSTA = 0x00; // Clear TX status
TXSTA.SYNC=0;
// Async mode
TXSTA.TXEN=1;
// Enable TX
TXSTA.TRMT=1;
// TRS empty (we start with an
empty TX register)*/
RCSTA = 0b10010000;
/*RCSTA =0x00; // Clear RC status
RCSTA.SPEN=1; // Enable Serial Port
RCSTA.CREN=1; // Enable Receiver*/
// BRGH=0
-> Baud Rate = Fosc / (64 * (SPBRG+1))
// BRGH=1
-> Baud Rate = Fosc / (16 * (SPBRG+1))
TXSTAbits.BRGH=brgh; SPBRG = spbrg;
}
La rutina
setup_UART inicializa la UART para una comunicación asíncrona con 8 bits. La
velocidad (bauds) se calcula en función de los parámetros BRGH y SPBRG, junto
con la frecuencia del oscilador Fosc:
BRGH=0 -> baud
= F_osc / (64 x(SPBRG+1))
BRGH=1 -> baud = F_osc / (16x(SPBRG+1))
Con un
oscilador de 20 MHz, para una velocidad de 9600 baudios usaríamos BRGH=1 y
SPBRG=129, obteniendo
baud = 20000000/(16 x 130) = 9615
suficientemente
próxima al valor correcto.
En la
rutina también se determina la dirección (out) del pin TX y la del pin RX (in).
En este ejemplo (familia PIC18F2520/4520) son RC6 y RC7 respectivamente. No
estoy seguro de que esto sea necesario (tal vez la dirección de RC6/RC7 se
establezca automáticamente al habilitar el receptor y el emisor) pero por si
acaso...
Enviar un
byte es una simple cuestión de esperar a que el buffer de salida esté vacío
(consultando flag TXSTA.TRMT) y cuando lo esté, poner el carácter a transmitir
en el buffer de TX. Automáticamente el bit TRMT se pondrá a 0 (buffer lleno),
por lo que una siguiente llamada a la función
tendría que esperar a terminar de mandar el carácter.
void send_byte(unsigned char ch)
{
while (TXSTAbits.TRMT==0);
// wait until TX buffer is empty
TXREG=ch;
}
Para ver
si se ha recibido algo se consulta la bandera de la interrupción de recepción
del puerto serie (definida como RX_flag como ya vimos):
char rx_ready(void) { return RX_flag; }
Finalmente,
si se ha detectado (rx_ready) una recepción, una llamada a get_byte recupera el
byte recibido (que está esperándonos en RCREG). También se quita la bandera
RX_flag para indicar que estamos de nuevo a la espera.
char get_byte(void)
{
RX_flag=0; // Clear RX flag
return RCREG;
// Devuelve byte recibido
}
El
siguiente código usa estas funciones para hacer exactamente lo mismo que antes:
void main()
{
char ch;
TRISB=0; PORTB=0;
setup_UART(1,129);
while(1)
{
if (rx_ready())
{
ch = get_byte(); send_byte(ch+1);
send_byte(0x0D); send_byte(0x0A);
PORTB++;
}
Delay10KTCYx(5);
}
}
¿Hemos
ganado algo? Obviamente nada si sólo
queríamos hacer lo que hacíamos en el programa inicial. Para ello nos bastaban
las rutinas que ya existían en nuestro compilador. Las ventajas aparecen cuando necesitamos apurar un poco más:
- Las rutinas standard en muchos compiladores solo soportan el modo de comunicación más usual (8 bits sin paridad). Si por ejemplo queremos usar paridad no nos sirven. Sin embargo ahora que sabemos lo fácil que es enviar o recibir bytes será muy sencillo cambiar algunos bits en TXSTA y/o RXSTA para establecer una comunicación usando p.e. paridad o 9 bits.
- Las rutinas standard (y las que hemos escrito, ya que eran intercambiables) son bloqueantes. Antes de escribir debemos esperar a que el buffer de transmision (1 byte) se libere. Antes de recibir debemos estar continuamente preguntando (rx_ready) si se ha recibido algo.
Imaginad
que el PIC está conectado a un sensor que genera unos 1000 bytes/segundo que
deben ser enviados al PC). Cada byte supone como mínimo 10 bits (start + 8 bits
+ stop) , por lo que para poder enviar 1000 bytes/sec precisaremos un enlace de
aproximadamentelo unos 1000 x 10 (podría valernos 9600). Si en estas circunstancias
usamos las rutinas originales, todos los recursos del PIC estarían prácticamente
dedicados a la transmisión de los datos.
Esta
situación puede implementarse mucho más eficientemente usando interrupciones y
esperando que la UART nos avise de que ha recibido un dato o que está libre
para enviar otro. El resto del tiempo el PIC puede estar haciendo otras cosas.
La diferencia puede ser grande. Si esperamos a que se complete la TX, enviar un
byte puede costarnos del orden de 1 ms ( @ 9600 bauds). Usando interrupciones el
único trabajo del microcontrolador será poner un byte en TXREG, lo que
esencialmente nos llevará una instrucción. Transmitir dicho byte seguirá
llevando 1 ms, pero el que estará ocupado durante ese tiempo será el periférico
(UART).
Habiendo
aprendido como funciona por debajo la UART será muy sencillo implementar tanto
la recepción como la transmisión usando interrupciones.
Determinación de SPBRG y BRGH en
función de la velocidad en baudios
Vimos que
aunque la función de MikroC Pro era menos versátil en la configuración del puerto
serie tiene la ventaja de que bastaba con darle la velocidad y ella se
encargaba de determinar los parámetros SPBRG y el bit BRGH. Esto es posible
porque en un proyecto de MikroC Pro se especifica la velocidad del oscilador.
Por la misma razón en MikroC Pro podemos especificar delays en unidades absolutas (msec o usec) en vez de limitarnos
a ciclos como en C18.
Es muy
sencillo escribir una función que calcule los valores correctos de SPBRG y BRGH
y nos lo devuelva para usarlos en una llamada a nuestra rutina setup_UART o a
la OpenUSART de C18:
uint8 get_usart_speed(uint32
baud, uint32 Fosc, uint8* spbrg)
// baud rate
1200,2400,4800,9600,19200, ..., 115200
// Fosc = Frequency
osc in KHz -> 20MHz = 20000
// Returns 0
(USART_BRGH_LOW), 1 (USART_BRGH_HIGH), or 2 (cannot get speed requested)
// It places in spbrg
the value to be used as argument spbrg in OpenUSART
{
uint16 FF;
uint8 brgh;
uint8 shift;
Fosc=Fosc*1000;
baud>>=1; Fosc=Fosc/baud+1;
Fosc>>=1; // computes round(Fosc/baud)
FF=(uint16)Fosc;
if ((FF>16320)||(FF<16)) return 2;
// Baud rate too low or too high
brgh = (FF>4096)? 0:1; //
Decides between BRGH=0 o BRGH=1
shift= (brgh==1)? 3:5;
FF>>=shift; FF--; FF>>=1; // Computes round(FF/64 -1) or round(FF/16-1)
*spbrg = (uint8)FF;
return brgh;
}
La forma
de usar esta rutina para inicializar la UART en conjunción con setup_UART()
sería:
void main()
{
uint8 brgh,sp;
brgh=get_usart_speed(38400,20000,&sp);
setup_UART(brgh,sp);
}
Si
preferimos seguir usando la rutina de C18 (OpenUSART) para inicializar la
USART:
void main()
{
uint8 brgh,brgh_config,sp;
brgh=get_usart_speed(38400,20000,&sp);
brgh_config=(brgh)?
USART_BRGH_HIGH:USART_BRGH_LOW;
OpenUSART(USART_ASYNCH_MODE &
USART_EIGHT_BIT & brgh_config, sp);
}
Si solo
vamos a inicializar el puerto es obviamente más eficiente calcular nosotros los
valores a usar y programarlos directamente. De esta forma nos ahorraríamos los
30-40 palabras de memoria de programa que ocupa la rutina anterior. Este
enfoque es más útil si queremos dinámicamente cambiar la velocidad del puerto
serie durante el programa.
Este comentario ha sido eliminado por un administrador del blog.
ResponderEliminarSaludos. Te tengo una pregunta que para mi es un poco desconcertante. Si estoy usando un PIC 18f45k22 con dos módulos de comunicación serial, ¿Cómo hago para comunicarme?
ResponderEliminarEl PIC deberá leer un protocolo de comunicación a partir de la PC, procesarlo y enviar una acción respectiva a una tarjeta SPARTAN3. Mi duda va relacionada a como dirigir la escritura y/o lectura de cada módulo de comunicación. Si pudieras contestarme estaría eternamente agradecido.
No debería ser un problema, aunque la forma concreta depende del compilador usado. Por ejemplo si usas C18 verás que para los PIC con múltiples USARTS tienes las funciones:
EliminarRead1USART, Read2USART, Write1USART, Write2USART, etc.
Basta usar una u otra para leer de un puerto o del otro.
Si vas a usar los registros directamente como hacemos en esta entrada consulta el datasheet del PIC a usar. Veras que en vez de un registro TXREG tendrás TXREG1 y TXREG2. Usaras uno u otro dependiendo de que puerto quieras usar. Lo mismo sucede con los registros de status TXSTA y RXSTA que se encuentran duplicados.
Antonio.
En la misma tarjeta Spartan del FPGA, puedes tu generar tantos modulos UART como tu quieras sin necesidad de utilizar otro dispositivo como el PIC. Esa es precisamente una de las virtudes de la lógica programable, no te compliques es sencillo implementar tantos RX's como TX's requiereas, configurando los parámetros de bauds que tu requieras, incluso por ejemplo si manejas baudajes diferentes, en uno 9600 y en el otro 115200.
EliminarHola, disculpa estoy intentando controlar un pic a través de otro pic(ambos 16f886) por infrarrojos, con ayuda del pwm el pic emisor envía caracteres por el UART que determinan acciones para el receptor, el receptor atiende los datos con una interrupción y los recibe bien. Pero mi problema es el emisor ya que trato de hacerlo funcionar como un control remoto cuando hay un 1 en ciertos pines este debería enviar un carácter pero algo ocurre y manda de mas, algunos no funcionan y los que funcionan luego como que traban el pic emisor. Lo e intentado con las librerías buton, con revisar los pines en un while infinito de tal manera que no se tomen como dobles 1 pero sigue sin funcionar adecuadamente. ¿Podrías aconsejarme algo?
ResponderEliminarYo en tu lugar intentaría depurar los subsistemas por separado.
EliminarPor lo que entiendo en el emisor detectas 1's en ciertos pines (con pulsadores p.e.) y dependiendo del pin puesto a 1, haces una acción.
Por lo que cuentas, no está claro que esa parte esté funcionando correctamente. Yo me aseguraría de que la detección de la entradas es a prueba de fallos antes de preocuparme de la parte de comunicaciones.
Al mismo tiempo podrías poner a prueba el emisor haciendo que mande una secuencia de ordenes, pero sin usar los "botones", simplemente programando el código.
Sólo si los dos subsistemas están OK pasaría a intentar depurar su "interacción".
Un saludo,
Antonio
Hola.
ResponderEliminarPregunta:
Tengo una tarjeta que he comprado en el que la única forma de comunicación es via seria.
Requiero comunicarla con un PIC16F84A (no hay discusión en cuanto al cambio), es necesario poner un MAX232 como interfase o lo puedo hacer directo.
Gracias!
Dependerá de las especificaciones de la tarjeta.
EliminarEl protocolo serie es una descripcición "digital" (0/1) de lo que debe estar pasando en la línea (por ejemplo, 1 mientras se espera, 0 indica el bit de start, etc.), pero no especifica que voltajes representan el 0/1 lógicos.
Por ejemplo en los PIC se usa la convención (lógica) que el 0 lógico son 0V y el 1 lógico (línea alta) son 5V.
En el puerto serie de un PC (standard RS232) la relación lógica/voltajes está invertida. Un 0 lógico se representa con un voltaje entre 3 y 15V (12 es típico) y un 1 lógico por un voltaje negativo (entre -3 y -15V). Es por esto por lo que para comunicar PIC-PC necesitamos un Max232 o equivalente.
Las especificaciones de tu tarjeta te dirán cuales son los voltajes asociados a su puerto serie. Si dice algo de RS232 tendrás que usar seguramente un buffer tipo MAX232.
Si dice algo asi como lógica TTL podrás conectarla la PIC directamente.
Podrías tener otras posibilidades. Tu tarjeta podría ir con 3.3V y entonces necesitarías un conversor de niveles (5V <-> 3.3V), aunque en ese caso no tengas "inversión" de la lógica.
Un saludo, Antonio
Que tal Antonio, eh checado tu blog, me parece muy interesante y me ha servido de mucho, ya entrando en materia estuve checando este post y queria preguntarte a cerca de la variable RX_flag, al momento de compilar me marca un error ya que dice que no esta definida, yo quiero pensar que no es un registro si no una variable, a menos que me puedas decir que es verdaderamente, esa es mi duda, quiero decirte que estoy usando SDCC, pero no debe de haber ningún problema ya estoy utilizando los registros, salu2.
ResponderEliminarRX_flag es simplemente un define que yo hago de la bandera de la interrupción de recepción en el puerto serie. Si miras el código de uart0.c/uart1.c verás que al principio hay un #include:
Eliminar#include "..\int_defs_C18.h"
En ese fichero hay un monton de definiciones para no tener que acordarme de donde están las diferentes flags de las interrupciones, bits de enable, etc. Ese fichero puedes encontrarlo en la entrada dedicada a las interrupciones:
http://picfernalia.blogspot.com.es/2012/06/interrupciones-conceptos-basicos.html
En particular contiene las líneas:
#define RX_flag PIR1bits.RCIF
#define enable_RX_int PIE1bits.RCIE=1
#define disable_RX_int PIE1bits.RCIE=0
#define set_RX_high IPR1bits.RCIP=1
#define set_RX_low IPR1bits.RCIP=0
donde se definen diversos bits relacionados con la interrupción RX de la UART.
UN saludo,
Antonio
Disculpa diego orozco me gustaría saber si pudiste lograr la comunicación por los dos puertos y como lo realizaste
ResponderEliminartengo el mismo problema quiero comunicarme con un solo puerto físico con pic18f452 y no lo e logrado e realizado con conmutación del puerto con hardware y no lo e logrado si alguien me pudiera ayudar se los agradecería mucho
ResponderEliminarMuchas gracias, en serio, he querido hacerlo hace tiempo pero no tenia idea, y esto me brinda una luz, gracias.
ResponderEliminarEste comentario ha sido eliminado por el autor.
ResponderEliminarbuen aporte
ResponderEliminarPor si os interesa maneras de emplear uart mikroc.
controlxic.blogspot.com/
Hola, estoy tratando de implementar una comunicación básica por medio del USART (PIC 16F628A, sin cristal oscilador, a 9600 bauds) y bluetooth (HC-05), para prender 4 leds, es decir elegir cual prender mediante el hyperterminal, tengo mi programa y lo hace, muy lento, pero lo hace, además que en el hyperterminal me muestra una serie de signos raros, sin sentido, y en algunas ocasiones algunos trozos de los mensajes de mi programa, Mi pregunta es, ¿Será que esto pasa porque el pic no esta usando un oscilador externo? ¿O algo más que yo no este viendo? Alguna ayuda será beinvenida
ResponderEliminarSaludos
También lo vi alguna vez. Luego de eso, concluí que USART es muy sensible a la frecuencia, así que el oscilador interno no es una opción. Usando un cristal se corrige el problema.
EliminarHats off ;)
ResponderEliminarmuchas gracias, con tus explicaciones he conseguido hacer funcionar la USART!!! ya no tendré que hacer troubleshooting encendiendo y apagando leds haha
Salutacions!!
Saludos Antonio. He estado haciendo pruebas con un PIC18F4685 y la asignación de TRIS
ResponderEliminarTRISCbits.TRISC6=0; TRISCbits.TRISC7=1; // RC6 out (tx), RC7 in (rx)
antes de inicializar la UART no sólo no es necesario según el datasheet sino que además es contraproducente. Me explico. Yo tengo los pin RC6/RC7 al aire y cuando quiero abrir una consola conecto mi adaptador USB/USART TTL para monitorizarlo (no lo dejo como parte intrínseca del circuito para ahorrar espacio y costes, ya que su uso es ocasional, para mi uso personal como debugger). Pues bien, el asignar los tris sin tener el adaptador USB/USART conectado provoca que el sistema se vuelva inestable (a no se que se deje un bucle hecho), cosa que no ocurre eliminando estas lineas de código. Saludos!
hola antonio disculpa estoy tratando de hacer un programa en el cual envie una letra a atraves y esta me de una salida en un lcd que me diga señal activada y si envio b señal desactivada y c como sistema apagado.
ResponderEliminarHola
ResponderEliminarEstaba mirando el codigo pero hace cosas raras cuando se usa el modo de 16-bit, porque ese codigo es para 8-bit. Mayormente selecciona un spbrg pero que no tiene porque ser el de menor error, y a veces es demasiado error.
La clave esta aqui:
if ((FF>16320)||(FF<16)) return 2; // Baud rate too low or too high
brgh = (FF>4096)? 0:1; // Decides between BRGH=0 o BRGH=1
Pero no entiendo el porque se han elegido estos valores. ¿Podrias arrojar algo de luz sobre el porque de la eleccion de estos valores (16320 y 4096)?
Gracias de antemano.
saludos, estoy usando un pic18f45k22 y deseo resibir una secuencia ce caracteres por
ResponderEliminarcompilador microbasic for pic
ejemplo
envio: casa y la quiero poner en la variable comida
dentro del programa osea el pic
if comida = (un valor) them
.........
end if
como puedo recibir los datos y luego optener los resultados
gracas
Hola.
ResponderEliminar¿Es posible enviar por el puerto serie palabras de más de 8 bits, por ejemplo palabras de 32 bits?
31 bits de datos + 1 bit de paridad
Gracias
Se puede pero solo si implementas el protocolo por software. Mediante hardware embebido, los PICs solo permiten 8 bits.
Eliminar