Está en la página 1de 29

Suscríbete a DeepL Pro para poder editar este documento.

Entra en www.DeepL.com/pro para más información.

UAR CAPÍTULO 11

El receptor/transmisor asíncrono universal (UART) permite que dos dispositivos se


comuniquen entre sí. Anteriormente ubicuo como el hardware que alimentaba a los puertos
serie, el UART ha sido casi completamente reemplazado por el bus serie universal (USB).
Aunque son obsoletos para el usuario medio de computadoras, los UART siguen siendo
importantes en los sistemas integrados debido a su relativa simplicidad. Un UART puede ser
usado con un dispositivo transceptor externo para implementar comunicación RS-232,
comunicación multipunto RS-485, comunicación inalámbrica infrarroja IrDA, u otros tipos
de comunicación inalámbrica como el estándar IEEE 802.15.4.

11.1 Visión general


El PIC32 tiene seis UARTs, cada una de las cuales le permite comunicarse con otro
dispositivo. Cada UART usa al menos dos pines, uno para recibir datos (RX) y otro para
transmitir
datos (TX). Además, los dispositivos comparten una línea de tierra común (GND). Una
UART puede enviar y recibir datos simultáneamente, una característica conocida como
comunicación full duplex. Para la comunicación unidireccional, sólo se requiere un cable
(además de la GND). Para distinguir
su PIC32 del dispositivo con el que se está comunicando, que puede ser su ordenador u otro
PIC32, llamamos el otro dispositivo equipo terminal de datos (DTE). La línea RX
para el PIC32 es la línea TX para el DTE, y la línea TX para el PIC32 es la línea RX
para el DTE.
Has usado las UARTs del PIC32 para comunicarte con tu ordenador. El software controlador
de FTDI de tu ordenador envía los datos a través de un cable USB, donde un chip del NU32
(el FTDI FT231X) recibe los datos del USB y los convierte en señales apropiadas para una de
las UARTs de la PIC32. Los datos enviados por la UART de la PIC32 son convertidos por el
chip de FTDI en señales USB para enviarlas a tu ordenador, donde el software controlador de
FTDI las interpreta como si fueran recibidas por una UART de tu ordenador.
Los parámetros importantes para la comunicación UART son el baudio, la longitud de los
datos, la paridad y el número de bits de parada. Los dos dispositivos utilizan los mismos
parámetros para una comunicación exitosa. El baudio se refiere al número de bits enviados
por segundo. La UART del PIC32 envía y recibe datos en grupos de 8 o 9 bits. Para
longitudes de datos de 8 bits, el PIC32 puede

Computación y mecatrónica incorporadas con el microcontrolador PIC32. http://dx.doi.org/10.1016/B978-0-12-420165-1.00011-1

Copyright © 2016 Elsevier Inc. Todos los derechos reservados.


159
160Capítulo 11

opcionalmente transmitir un bit de paridad adicional como una simple medida de detección de
errores de transmisión. Por ejemplo, si la paridad es "par", el número de bits enviados que son
uno debe ser un número par; el bit de paridad se elige para cumplir con esta restricción. Si el
receptor ve un número impar de unos en la transmisión, entonces sabe que se ha producido un
error de transmisión. Finalmente, el UART del PIC32 puede ser ajustado a uno o dos bits de
parada, que son los que se envían al final de una transmisión.
La biblioteca NU32 usa un baudio de 230.400, ocho bits de datos, ningún bit de paridad
y un bit de parada. Escrito en taquigrafía, esto es 230.400/8N1. La paridad puede ser
impar, par o ninguna.
Por razones históricas, las opciones de baudios comunes incluyen 1200, 2400, 4800, 9600,
19.200, 38.400, 57.600, 115.200 y 230.400, pero cualquier opción es posible, siempre que
ambos dispositivos estén de acuerdo.
Según el Manual de Referencia, la UART del PIC32 es teóricamente capaz de baudios de
hasta 20 M; sin embargo, en la práctica, el máximo baudios alcanzable es mucho menor.
La comunicación UART es asincrónica, lo que significa que no hay una línea de reloj para
mantener los dos dispositivos sincronizados. Debido a las diferencias en las frecuencias de reloj
de los dos dispositivos, los baudios de cada uno de ellos pueden ser ligeramente diferentes, y los
dispositivos UART pueden manejar pequeñas diferencias resincronizando sus relojes de baudios
en cada transmisión.
La figura 11.1 muestra una transmisión UART típica. Cuando no se transmite, la línea TX
es alta. Para iniciar una transmisión, la UART baja la TX por un período de un baudio. Este
bit de inicio le dice al receptor que una transmisión ha comenzado para que el receptor pueda
iniciar su reloj de baudios y comenzar a recibir bits. A continuación, se envían los bits de
datos. Cada bit se mantiene en la línea durante un período de baudios. Los bits se envían
primero el bit menos significativo (por ejemplo, el primer bit enviado para 0b11001000
será un cero). Después de los bits de datos, se puede enviar opcionalmente un bit de paridad.
Finalmente, el transmisor mantiene la línea en alto, transmitiendo uno o dos bits de parada.
Después de que se hayan transmitido los bits de parada, puede iniciarse otra transmisión; de
este modo, el uso de dos bits de parada proporciona a los dispositivos un tiempo de
procesamiento adicional entre las transmisiones. El bit de inicio, el bit de paridad y los bits
de parada son bits de control: no contienen datos. Por lo tanto, el baudio no corresponde
directamente a la velocidad de los datos.
A medida que la UART recibe datos, el hardware cambia cada bit en un registro. Cuando se ha
recibido un byte completo, ese byte se transfiere a la cola de primeras entradas y primeras
salidas (FIFO) de la UART. Al transmitir los datos, el software carga los bytes en el TX FIFO.
El hardware entonces carga los bytes del FIFO en un registro de desplazamiento, que los envía
por el cable. Si cualquiera de los FIFO está lleno y
Id Co Bi Bi Bi Bi Bi Bi Bi Bi D
le mi t t t t3 t4 t5 t6 t7 et
en 0 1 2 én
za ga
se

Figura 11.1
Transmisión UART de 0b10110010 con 8 bits de datos, sin paridad, y un bit de parada.
UART 161

es necesario añadir otro byte, se produce una condición de desbordamiento y se pierden los
datos. Para evitar una sobrecarga de TX FIFO, el software no debería intentar escribir en la
UART a menos que el TX FIFO tenga espacio. Para evitar un desbordamiento de RX FIFO, el
software debe leer el RX FIFO lo suficientemente rápido para que siempre tenga espacio
cuando lleguen los datos. El hardware mantiene banderas que indican el estado de los FIFO y
también puede interrumpir en función del número de elementos en los FIFO.
Una característica opcional llamada control de flujo de hardware puede ayudar al software a
prevenir los excesos. El control de flujo por hardware requiere dos cables adicionales:
solicitud de envío (RTS) y autorización de envío (CTS). 1 Cuando el RX FIFO está lleno, el
hardware de UART desactiva (impulsa en alto) el RTS, que le dice al DTE que no envíe
datos. Cuando el RX FIFO tiene espacio disponible, el hardware afirma (unidades bajas)
RTS, lo que permite al DTE enviar datos. El DTE controla el CTS. Cuando el DTE
des-asegura (conduce alto) CTS, el PIC32 no transmitirá datos. Para que el control de flujo
por hardware funcione, tanto el DTE como el PIC32 deben respetar las señales de control de
flujo. Por defecto, cuando usas make screen o make putty, esos emuladores de terminal
configuran tu DTE para usar el control de flujo por hardware.
Estos son los fundamentos de la operación de la UART. Existen muchas otras opciones,
demasiadas para cubrir aquí. El principio rector de toda operación UART, sin embargo, sigue
siendo el mismo: ambos extremos de la comunicación deben estar de acuerdo en todas las
opciones. Al interactuar con un dispositivo específico, lea su hoja de datos y seleccione las
opciones apropiadas.

11.2 Detalles
A continuación se describe el registro de la UART. La "x" en los nombres SFR significa
UART número 1 a 6. Todos los bits están por defecto a cero excepto dos bits de sólo
lectura en UxSTA.
UxMODE Activa o desactiva la UART. Determina la paridad, el número de bits de datos, el
número de bits de parada y el método de control de flujo.
UxMODE×15 )o UxMODEbits.ON: cuando se ajusta a uno, activa la UART.
UxMODE×9:8 )o UxMODEbits.UEN: Determina qué pines usa la UART.
Las opciones más comunes son
0b00Sólo se utilizan el UxTX y el UxRX (el mínimo requerido para
la comunicación UART).
0b10UxTX , UxRX, UxCTS, y UxRTS se utilizan. Esto permite el control
de flujo por hardware.
UxMODE×3 )o UxMODEbits.BRGH: Se llama "bit generador de alta tasa de baudios" y
controla el valor de un divisor M usado en el cálculo de la tasa de baudios (ver el
SFR UxBRG). Si este bit es 1, M = 4, y si es 0, M = 16.
1
Algunas UART del PIC32 no tienen líneas de control de flujo por hardware, y los pines de control de flujo de una
UART pueden coincidir con las líneas RX y TX de otra UART. Por ejemplo, el uso de UART3 con control de
flujo impide el uso de UART6.
162Capítulo 11

UxMODE×2:1 )o UxMODEbits.PDSEL: Determina la paridad y el número de bits


de datos.
0b119 bits de datos, sin
paridad. 0b108 bits de datos ,
paridad impar. 0b018 bits de
datos , paridad par. 0b008
bits de datos, sin
paridad.
UxMODE×0 )o UxMODEbits.STSEL: El número de bits de parada. 0 = 1 bit de
parada, 1 = 2 bits de parada.
UxSTA Contiene el estado de la UART: banderas de error y estado de ocupado. Controla las
condiciones en las que se producen las interrupciones. También permite al usuario
encender y apagar el transmisor o el receptor.
UxSTA×15:14)o UxSTAbits.UTXISEL: Determina cuando generar una interrupción TX.
El PIC32 puede contener ocho bytes en su TX FIFO. Las interrupciones continuarán
sucediendo hasta que la condición que causa la interrupción termine.
0b10Interrumpir mientras TX FIFO está vacío.
0b01Interrumpir después de que todo en el TX FIFO haya sido
transmitido. 0b00Interrumpir cuando el TX FIFO no esté lleno.
UxSTA×12)o UxSTAbits. Cuando está configurado, activa el pin RX de la UART.
UxSTA×10)o UxSTAbits.UTXEN: Cuando está configurado, habilita el pin TX de
la UART.
UxSTA×9)o UxSTAbits.UTXBF: Cuando está configurado, indica que el buffer de
transmisión está lleno. Si intentas escribir en la UART cuando el buffer está lleno, los
datos serán ignorados.
UxSTA×8)o UxSTAbits.TRMT: Cuando está claro, indica que no hay ninguna
transmisión o datos pendientes en el buffer de TX.
UxSTA×7:6)o UxSTAbits. Determina cuando se generan las interrupciones de recepción
de UART. El PIC32 puede contener ocho bytes en su RX FIFO. La interrupción
continuará sucediendo hasta que la condición que causa la interrupción se elimine.
0b10Interrumpir siempre que el RX FIFO contenga seis o más
caracteres. 0b01Interrumpir siempre que el RX FIFO contenga cuatro o
más caracteres. 0b00Interrumpir cuando el FIFO de RX contenga al
menos un carácter.
UxSTA×3 )o UxSTAbits.PERR: Se establece cuando la paridad de los datos recibidos es
incorrecta. Para la paridad par (impar) la UART espera que el número total de los
recibidos (incluyendo el bit de paridad) sea par (impar). Si no se utiliza un bit de
paridad, entonces no puede haber error de paridad, pero también se pierde la
comprobación de la integridad de los datos que proporciona la paridad.
UxSTA×2)o UxSTAbits.FERR: Establecido cuando ocurre un error de encuadre. Un
error de encuadre ocurre cuando la UART no detecta el bit de parada. Esto ocurre a
menudo si hay un desajuste de baudios.
UxSTA×1 )o UxSTAbits.OERR: Se establece cuando el buffer de recepción está lleno
pero a la UART se le envía otro byte. Cuando este bit está establecido, la UART no
puede recibir datos; por lo tanto, si se produce una sobrecarga, debe borrar
manualmente este bit para seguir recibiendo datos. Borrado
UART 163

este bit elimina los datos en el buffer de recepción, por lo que es posible que desee
leer los bytes en el buffer de recepción antes de limpiarlo.
UxSTA×0)o UxSTAbits.URXDA: Cuando está configurado, indica que la memoria
intermedia de recepción contiene datos.
UxTXREG Usar este SFR para transmitir datos. Escribir en UxTXREG coloca los
datos en un FIFO de hardware de ocho bytes de largo. El transmisor elimina los
datos del FIFO y los carga en un registro de desplazamiento interno, UxTSR, donde
los datos se desplazan hacia la línea de TX, bit a bit. Una vez terminado el
desplazamiento, el hardware elimina el siguiente byte y comienza a transmitirlo.
UxRXREG Usar este SFR para recibir datos. El hardware cambia los datos recibidos bit a
bit en un registro de cambio de RX interno. Después de recibir un byte completo, el
hardware lo transfiere del registro de desplazamiento al RX FIFO. La lectura de
UxRXREG elimina un byte del RX FIFO. Si no lee de UxRXREG con suficiente
frecuencia, el FIFO de RX puede sobrecargarse. Si el FIFO está lleno, los bytes recibidos
posteriormente se descartan y se fija un indicador de estado de error de sobrecarga.
UxBRG controla el baudio. El valor de este registro debe ser fijado para lograr el baudio B
deseado de acuerdo con la siguiente ecuación:

FPB
UxBRG 1 (11.1)
=
M×B

donde FPB es la frecuencia del bus periférico, y o bien M = 4 si UxMODE.BRGH = 1 o


M = 16 si UxMODE.BRGH = 0.
Los números de los vectores de interrupción para las UARTs se llaman _UART_x_VECTOR, donde
x es de 1 a 6. Los bits de estado de bandera de interrupción para UART1 son IFS0bits.U1EIF
(interrupción de error generada por un error de paridad, error de encuadre o error de
sobrecarga), IFS0bits.U1RXIF (interrupción de RX), e IFS0bits.U1TXIF (interrupción de
TX). Los bits de control de habilitación de interrupción para UART1 son IEC0bits.U1EIE
(error de habilitación de interrupción), IEC0bits.U1RXIE (habilitación de interrupción de
RX), y IEC0bits.U1TXIE (habilitación de interrupción de TX). Los bits de prioridad y
subprioridad son IPC6bits.U1IP y IPC6bits.U1IS. Los bits de estado de bandera de
interrupción, los bits de control de habilitación y los bits de prioridad para UART2 se
nombran de manera similar (reemplazando "U1" por "U2") y están en IFS1, IEC1, e IPC8;
para UART3 están en IFS1, IEC1, e IPC7; y para UART4 a UART6 están en IFS2, IEC2, e
IPC12.

11.3 Código de muestra


11.3.1 Loopback

En nuestro primer ejemplo, el PIC32 usa la UART1 para hablar consigo mismo. Conecta
U1RX (RD2) a U1TX (RD3). El programa usa la biblioteca NU32 y UART3 para pedir al
usuario un solo byte,
164Capítulo 11

lo envía dos veces de U1TX a U1RX, y reporta el byte que fue leído en U1RX. Configuramos
el baudio a una tasa extremadamente baja (100) para que pueda ver fácilmente la transmisión
en un osciloscopio. Si se pone el osciloscopio en modo de captura única y se dispara en el
flanco descendente, el osciloscopio capturará la señal desde el principio de la transmisión,
cuando se envíe el primer bit de inicio. El envío del byte dos veces permite verificar los bits
de parada.

Muestra de código 11.1 uart_loop.c. Código de UART que habla por sí mismo.
#incluyen constantes "NU32.h" //, funciones para el arranque y UART

// Configuraremos UART1 a una velocidad de baudios lenta para que puedas examinar la señal en un
telescopio.
// Conecta los pines de RX y TX de la UART1 para que la UART pueda comunicarse consigo misma.

int main(void) {
char msg[100] = {};
NU32_Startup(); // caché encendido, interrupciones encendidas, LED/botón encendido, UART encendido

// Inicializar UART1: 100 baudios, paridad impar, 1 bit de parada.


U1MODEbits.PDSEL = 0x2; // paridad impar (bit de paridad configurado para hacer el
número de 1's impares) U1STAbits.UTXEN = 1; // habilitar la transmisión
U1STAbits.URXEN = 1; // habilitar recepción

// U1BRG = Fpb/(M * baud) - 1 (nota U1MODEbits.BRGH = 0 por defecto, por lo que M = 16)
// Preparado para 100 baudios. Esto significa 100 bits /seg o 1
bit/ 1/10ms U1BRG = 49999;// 80 M/(16*100) - 1 = 49,999
U1MODEbits.ON = 1; // enciende el uart

// instrucciones de alcance: 10 ms/div, disparador en el borde descendente,


captura única mientras(1) {
datos de caracteres sin firmar = 0;
NU32_WriteUART3("Introduzca el byte hexagonal (en minúsculas) para enviar a UART1 (es decir,
0xa1): "); NU32_ReadUART3(msg, tamaño(msg));
sscanf(msg,"%2x",&datos); sprintf(msg,"0x
%02x\r\n",datos);
NU32_WriteUART3(msg); //echo atrás

mientras que (U1STAbits.UTXBF) { // espera a que UART esté listo para transmitir
;
}
U1TXREG = datos; // escribir dos veces para poder ver el
bit de parada U1TXREG = datos;
while(!U1STAbits.URXDA) { // encuesta para ver si hay datos para leer en RX FIFO
;
}
datos = U1RXREG; // los datos han llegado; lee el byte while(!
U1STAbits.URXDA) { // espera hasta que haya más datos para leer en RX FIFO
;
}
Datos = U1RXREG; // sobrescribiendo los datos de la lectura anterior!
podría comprobar si el mismo sprintf(msg,"Read 0x%x from UART1\r\n",data);
NU32_WriteUART3(msg);
}
...devuelve 0;
}
UART 165

11.3.2 Interrupción basada en


El siguiente ejemplo demuestra el uso de las interrupciones. Las interrupciones pueden
generarse en base al número de elementos en los buffers RX o TX, o cuando se ha producido
un error. Por ejemplo, se puede interrumpir cuando el búfer RX está medio lleno o cuando el
búfer TX está vacío. Las IRQ de estas interrupciones comparten el mismo vector; por lo tanto,
se debe verificar dentro de la ISR para ver qué evento la desencadenó. También debe eliminar
la condición que disparó la interrupción o se disparará de nuevo después de salir de la ISR.
El código de abajo lee los datos de su emulador de terminal y los envía de vuelta. Utiliza
UART3, al igual que la biblioteca NU32, pero no utiliza los comandos NU32 UART. Se
dispara una interrupción cuando el búfer RX contiene al menos un carácter, y el ISR envía
inmediatamente los datos de vuelta al emulador de terminales.
El uso de interrupciones para E/S en serie permite al PIC32 recibir datos del puerto en serie
sin perder tiempo en buscarlos.

Muestra de código 11.2 uart_int.c. Código UART que utiliza interrupciones para recibir
datos.
#incluyen constantes "NU32.h" //, funciones para el arranque y UART

void ISR(_UART_3_VECTOR, IPL1SOFT) IntUart1Handler(void) {


si (IFS1bits.U3RXIF) {// compruebe si la interrupción generada por un
evento de RX U3TXREG = U3RXREG; // envíe los datos recibidos fuera
IFS1bits.U3RXIF = 0; // borre la bandera de interrupción de RX
} más si(IFS1bits.U3TXIF) { // si es una interrupción de TX
} más si(IFS1bits.U3EIF) { // si es una interrupción de error. compruebe U3STA por la razón
}
}

int main(void) {
NU32_Startup(); // caché encendida, interrupciones encendidas,
LED/botón encendido, UART encendido NU32_LED1 = 1;
NU32_LED2 = 1;
builtin_disable_interrupts();

// Ponga el baudio a 230400, para que coincida con el emulador de terminal; use el
valor por defecto 8N1 de UART U3MODEbits.BRGH = 0;
U3BRG = ((NU32_SYS_FREQ / 230400) / 16) - 1;

// Configurar los pines


de TX y RX
U3STAbits.UTXEN = 1;
U3STAbits.URXEN = 1;

// configurar usando RTS y CTS


U3MODEbits.UEN = 2;

// configurar las interrupciones de UART


U3STAbits.URXISEL = 0x0; // Interrupción de RX cuando el buffer de
recepción no está vacío IFS1bits.U3RXIF = 0; // Borrar la bandera de
interrupción de RX. para
// tx o interrupciones de error que también necesitaría para limpiar
// las respectivas banderas
166Capítulo 11

IPC7bits.U3IP = 1; // prioridad de
interrupción IEC1bits.U3RXIE = 1; // habilitar la
interrupción de RX

// enciende UART1
U3MODEbits.ON = 1;

builtin_enable_interrupts();
while(1) {
;
}
...devuelve 0;
}

11.3.3 Biblioteca NU32

La biblioteca NU32 contiene tres funciones que acceden a la UART: NU32_Setup,


NU32_ReadUART3 y NU32_WriteUART3. El código de configuración configura el UART3 para un
baudio de 230400, un bit de parada, 8 bits de datos, sin bit de paridad, y control de flujo por
hardware. No se utilizan interrupciones UART. Noten que el NU32_ReadUART3 sigue leyendo de
la UART hasta que recibe un cierto carácter de control ( '\n' o '\r'); por lo tanto, esperará
indefinidamente por la entrada antes de proceder.
La función NU32_WriteUART3 espera a que el TX FIFO tenga espacio disponible antes de
intentar añadirle más datos. Además, como el control de flujo por hardware está habilitado en
la UART del PIC32, no se enviarán datos por el PIC32 a menos que el DTE (su computadora)
mantenga la línea CTS baja. El emulador de terminal debe tener el control de flujo por
hardware habilitado para asegurar su correcto funcionamiento.

Muestra de código 11.3 NU32.c. La implementación de la biblioteca NU32.


#incluir "NU32.h"

// Registros de configuración del dispositivo


// Estos sólo tienen un efecto para los programas autónomos pero no dañan los programas cargados de
arranque.
// los ajustes aquí son los mismos que los utilizados por el cargador de arranque
#pra con DEBUG = OFF / El depurador de fondo está desactivado
gma fig /
#pra con FWDTEN = OFF / Temporizador WD: OFF
gma fig /
#pra con WDTPS = PS4096 / Período WD: 4.096 seg.
gma fig /
#pra con POSCMOD = HS / Modo oscilador primario: Cristal de alta
gma fig / velocidad
#pra con FNOSC = PRIPLL / Selección del oscilador: Oscilador primario
gma fig / con PLL
#pra con FPLLMUL = MUL_20 / Multiplicador PLL: Multiplicar por 20
gma fig /
#pra con FPLLIDIV = DIV_2 / Divisor de entrada PLL: Dividir por 2
gma fig /
#pra con FPLLODIV = DIV_1 / Divisor de salida PLL: Dividir por 1
gma fig /
#pra con FPBDIV = DIV_1 / Reloj de bus periférico: Dividir por 1
gma fig /
#pra con UPLLEN = ON / El reloj USB usa PLL
gma fig /
#pra con UPLLIDIV = DIV_2 / Dividir la entrada de 8 MHz por 2, mult por 12
gma fig / para 48 MHz
#pra con FUSBIDIO = ON / El USBID controlado por el periférico USB
gma fig / cuando está encendido
#pra con FVBUSONIO = ON / VBUSON controlado por un periférico USB cuando
gma fig / está encendido
#pra con FSOSCEN = OFF / Deshabilitar la segunda oscuridad para
gma fig / recuperar los alfileres
#pra con BWP = ON / Boot flash write protect: EN
gma fig /
#pra con ICESEL = / Las clavijas de ICE configuradas en PGx2
gma fig ICS_PGx2 /
#pra con FCANIO = OFF / Usar pines CAN alternativos
gma fig /
#pra con FMIIEN = OFF / Usar RMII (no MII) para el ethernet
gma fig /
UART 167

#pragma config FSRSSEL = PRIORITY_6 // Shadow Register Establecido para la

prioridad de interrupción 6 #define NU32_DESIRED_BAUD 230400/// Velocidad de

transmisión para RS232

// Realizar rutinas de inicio:


// Hacer que los pines NU32_LED1 y NU32_LED2 sean salidas (NU32_USER es por defecto una entrada)
// Inicializar el puerto serie - UART3 (sin interrupción)
// Habilitar
interrumpe el vacío
NU32_Startup() {
// Desactivar las interrupciones
builtin_disable_interrupts();

// habilitar la caché
// Este comando establece el registro CP0 CONFIG
// los 4 bits inferiores pueden ser 0b0011 (0x3) o 0b0010 (0x2)
// para indicar que kseg0 es cacheable (0x3) o no cacheable (0x2)
// ver Capítulo 2 "CPU para dispositivos con núcleo M4K" del manual de referencia del PIC32
// la mayoría de los otros bits tienen valores prescritos
// el microchip no proporciona una macro _CP0_SET_CONFIG, así que usamos directamente
// el comando incorporado del compilador _mtc0
// para desactivar la caché, use 0xa4210582
builtin_mtc0(_CP0_CONFIG, _CP0_CONFIG_SELECT, 0xa4210583);

// establecer el estado de espera de la memoria caché del prefecto a 2, según el


// hoja de datos de características
eléctricas CHECONbits.PFMWS = 0x2;

//habilitar prefetch para memoria cacheable y no cacheable


CHECONbits.PREFEN = 0x3;

// 0 datos de acceso a la RAM


estados de espera
BMXCONbits.BMXWSDRM = 0x0;

// Habilitar interrupciones
multivectoriales INTCONbits.MVEC
= 0x1;

// deshabilitar JTAG para obtener B10, B11, B12 y B13 de


vuelta DDPCONbits.JTAGEN = 0;

TRISFCLR = 0x0003; // Hacer que las salidas F0 y F1 (LED1 y


LED2) NU32_LED1 = 1; // El LED1 está apagado
NU32_LED2 = 0; // El LED2 está encendido

// enciende UART3 sin una interrupción


U3MODEbits.BRGH = 0; // establece baudios a
NU32_DESIRED_BAUD
U3BRG = ((NU32_SYS_FREQ / NU32_DESIRED_BAUD) / 16) - 1;

// 8 bit, sin bit de paridad, y 1 bit de parada


(configuración 8N1) U3MODEbits.PDSEL = 0;
U3MODEbits.STSEL = 0;

// configurar los pines de TX y RX como pines de salida y


entrada U3STAbits.UTXEN = 1;
U3STAbits.URXEN = 1;
// configurar el control de flujo de hardware usando RTS y
CTS U3MODEbits.UEN = 2;

// Activar el Uart
U3MODEbits.ON = 1;
builtin_enable_interrupts();
168Capítulo 11

// Leído de UART3
// Bloquear otras funciones hasta que se obtenga un '\r' o '\N'.
// envía el puntero a tu matriz de caracteres y el número de elementos en la
matriz vacía NU32_ReadUART3(char * mensaje, int maxLength) {
Datos de caracteres = 0;
int complete = 0, num_bytes = 0;
// ...hasta que consigas un "r" o
"n" mientras que...
si (U3STAbits.URXDA) { // si hay datos
disponibles datos = U3RXREG; // leer los
datos
si ((data == '\n') || (data == '\r'))
{ complete = 1;
} más {
message[num_bytes] = data;
++número_de_bites;
// pasar el ratón por encima si la
matriz es demasiado pequeña si
(num_bytes >= maxLength) {
num_bytes = 0;
}
}
}
}
// termina la cadena de
mensajes[num_bytes] =
'\0';
}

// Escribir una matriz de caracteres


usando UART3 void NU32_WriteUART3(const
char * string) {
while (*string!= '\0')
{ while (U3STAbits.UTXBF)
{
Espera hasta que el buffer TX no esté lleno.
}
U3TXREG = *cadena;
++cadena;
}
}

11.3.4 Envío de datos de un ISR

A menudo es deseable transmitir a la computadora los datos recogidos por el PIC32. Por
ejemplo, un ISR de frecuencia fija podría tomar muestras de datos de un sensor y luego
enviarlas a su computadora para que las grafique. Sin embargo, el ISR puede recoger
muestras a una velocidad mucho más alta de la que pueden enviarse a través de la UART. En
este caso, algunos de los datos tienen que ser descartados. El
− proceso de mantener sólo un
dato por cada N recolectado (desechando N 1) se llama diezmo. 2 El diezmo es
común en el procesamiento de señales.
Además del diezmo, otro concepto que permite la transmisión de datos de un ISR es el de un
un búfer circular. Un buffer circular (o de anillo) es una implementación de un FIFO, como el de la
UART
2
El origen de este término es una práctica disciplinaria del ejército romano, por la cual uno de cada diez soldados
que se había desempeñado de manera vergonzosa fue asesinado.
UART 169

70
6 1 Lea

Escriba 5 2
43

Figura 11.2
Un búfer circular de ocho elementos (FIFO), donde el índice de escritura apunta actualmente al elemento 5 y
al
El índice de lectura apunta actualmente al elemento 1.

TX y RX FIFO. Se implementa un búfer circular como una matriz y dos variables de índice:
escritura y lectura (véase la figura 11.2). Los datos se añaden a la matriz en el lugar de
escritura y se leen desde el lugar de lectura, tras lo cual se incrementan los índices. Cuando
los índices llegan al final de la matriz, se envuelven hasta el principio. Si los índices de
lectura y escritura son iguales, el búfer está vacío. Si el índice de escritura está una
ranura detrás del índice de lectura, el búfer está lleno.
Los buffers circulares son útiles para compartir datos entre las interrupciones y el código de la
línea principal. La ISR puede escribir datos en el buffer mientras que el código de línea
principal lee del buffer y envía los datos a través de la UART.
La Muestra de Código 11.4 demuestra el concepto de diezmar y la Muestra de Código 11.5
demuestra el concepto de un buffer circular. Ambos programas envían 5000 muestras de datos
del PIC32 al ordenador central. La Muestra de Código 11.4 se titula batch.c porque todos los
datos diezmados se almacenan primero en una matriz en la RAM, y luego se envían a través
de la UART en un lote. La Muestra de Código 11.5 se llama circ_buf.c porque los datos son
enviados usando un buffer circular. El uso de un búfer circular
(a) permite que la memoria intermedia utilice menos RAM que la matriz en batch.c y b)
permite que los datos se envíen inmediatamente, no sólo en un lote después de que se hayan
recogido todos.

Muestra de código 11.4 batch.c. Almacenamiento de datos en un ISR y envío en


un lote sobre la UART.
#incluyen constantes "NU32.h" //, funciones para el arranque y UART

#define DECIMATE 3// sólo envía cada 4 muestras (el conteo comienza en
cero) #define NSAMPLES 5000// almacena 5000 muestras

volatile int data_buf[NSAMPLES];// almacena las muestras


volátil int curr = 0; // el índice de corriente en el buffer

void ISR(_TIMER_1_VECTOR, IPL5SOFT) Timer1ISR(void) { // El Timer1 ISR opera a 5 kHz


estático int count = 0; // contador usado para diezmar
static int i = 0; // los datos devueltos desde el isr
++i; // generar los datos (sólo los incrementamos por
ahora) si(count == DECIMATE) {/// saltar algunos datos
cuenta = 0;
si(curr < NSAMPLES) {
data_buf[curr] = i; // poner en cola un número para enviar la UART
170Capítulo 11

++curr;
}
}
++cuenta;
IFS0bits.T1IF = 0; // borrar la bandera de interrupción
}

int main(void) {
int i = 0;
Char Buffer[100] = {};
NU32_Startup(); // caché encendido, interrupciones encendidas, LED/botón encendido,
UART encendido

builtin_disable_interrupts();// INT paso 2: deshabilitar


interrupciones en la CPU T1CONbits.TCKPS = 0b01; // PBCLK valor del
prescalador de 1:8
PR1 = 1999; // La frecuencia es de 80 MHz / (8 * (1999 + 1)) = 5 kHz
TMR1 = 0;
IPC1bits.T1IP = 5; // Interrumpir prioridad 5
IFS0bits.T1IF = 0; // borrar la bandera de interrupción
IEC0bits.T1IE = 1; // habilitar la interrupción
T1CONbits.ON = 1; // enciende el temporizador
builtin_enable_interrupts(); // INT paso 7: habilitar las interrupciones en la CPU

NU32_ReadUART3(buffer, sizeof(buffer)); // esperar a que el usuario pulse


enter mientras(curr !=NSAMPLES) { ; }// esperar a que se recojan los
datos

sprintf(buffer,"%d\r\n",NSAMPLES); // enviar el número de muestras que se


enviarán NU32_WriteUART3(buffer);

para(i = 0; i < NSAMPLES; ++i) {


sprintf(buffer,"%d\r\n",data_buf[i]); // enviar los datos a la terminal
NU32_WriteUART3(buffer);
}
...devuelve 0;
}

Muestra de código 11.5 circ_buf.c. Transmisión de datos de un ISR sobre la UART,


usando un buffer circular.
#incluir "NU32.h" // constantes, funciones para el inicio y UART
// utiliza un búfer circular para transmitir datos de un ISR sobre la UART
// noten que el buffer puede ser mucho más pequeño que el número total de muestras enviadas y
// que los datos empiezan a fluir inmediatamente a diferencia de con batch.c

#definir BUFLEN1024// longitud del buffer


#Definir NSAMPLES 5000// número de muestras a recoger

static volatile int data_buf[BUFLEN]; // matriz que almacena los


datos static volatile unsigned int read = 0, write = 0; // circular buf
indexes static volatile int start = 0; // set to start recording

int buffer_empty() {// return true if the buffer is empty (read = write)
return read == write;
}

int buffer_full() {// return true if the buffer is full.


return (write + 1) % BUFLEN == read;
}
UART 171

int buffer_read() {// lee de la ubicación actual del buffer; asume que el buffer no
está vacío int val = data_buf[read];
++lectura; // incrementos índice de lectura
if(read >= BUFLEN) { // envuelve el índice de lectura si es
necesario leer = 0;
}
Devuélvelo a Val;
}

void buffer_write(int data) { // añadir un elemento al buffer. if(!


buffer_full()) {/// si el buffer está lleno los datos se
pierden
data_buf[write] = data;
++escribir; // incrementar el índice de escritura y envolver si es
necesario si(write >= BUFLEN) {
escribe = 0;
}
}
}

void ISR(_TIMER_1_VECTOR, IPL5SOFT) Timer1ISR(void) { // el temporizador 1 isr opera a


5 kHz static int i = 0; // los datos devueltos desde el isr
si(inicio) {
buffer_write(i); // añadir los datos al buffer
++i; // modificar los datos (aquí sólo lo incrementamos como ejemplo)
}
IFS0bits.T1IF = 0; // borrar la bandera de interrupción
}

int main(void) {
int sent = 0;
char msg[100] = {};
NU32_Startup(); // caché encendido, interrupciones encendidas, LED/botón
encendido, UART encendido

builtin_disable_interrupts(); // INT paso 2: deshabilitar


interrupciones en la CPU T1CONbits.TCKPS = 0b01; // PBCLK valor del
prescalador de 1:8
PR1 = 1999; // La frecuencia es de 80 MHz / (8 * (1999 + 1)) = 5 kHz
TMR1 = 0;
IPC1bits.T1IP = 5; // Interrumpir prioridad 5
IFS0bits.T1IF = 0; // borrar la bandera de interrupción
IEC0bits.T1IE = 1; // habilitar la interrupción
T1CONbits.ON = 1; // enciende el temporizador
builtin_enable_interrupts(); // INT paso 7: habilitar las interrupciones en la CPU

NU32_ReadUART3(msg,sizeof(msg)); // esperar a que el usuario pulse enter antes de continuar


sprintf(msg, "%d\r\n", NSAMPLES); // decirle al cliente cuántas muestras debe esperar
NU32_WriteUART3(msg);
Inicio = 1;
for(sent = 0; sent < NSAMPLES; ++sent) { // enviar las muestras al cliente
mientras(buffer_empty()) { ; }// esperar a que los datos
estén en la cola
sprintf(msg,"%d\r\n", buffer_read()); // leer del buffer, enviar datos a través de uart
NU32_WriteUART3(msg);
}

mientras que(1) {
;
}
...devuelve 0;
}
172Capítulo 11

En circ_buf.c, si el búfer circular está lleno, se pierden datos. Mientras que circ_buf .c está
escrito para enviar un número fijo de muestras, puede ser fácilmente modificado para
transmitir muestras indefinidamente. Si el baudio de la UART es suficientemente superior a la
velocidad a la que se generan los bits de datos en la ISR, la transmisión de datos sin pérdidas
puede realizarse indefinidamente. El búfer circular simplemente proporciona cierto
amortiguamiento en los casos en que la comunicación se retrasa o se interrumpe
temporalmente.

11.3.5 Comunicación con MATLAB

Hasta ahora, cuando hemos usado la UART para comunicarnos con un ordenador, hemos
abierto el puerto serie en un emulador de terminal. MATLAB también puede abrir puertos
serie, permitiendo la comunicación y el trazado de datos desde el PIC32. Como primer
ejemplo, nos comunicaremos con talkingPIC.c de MATLAB.
Primero, carga talkingPIC.c en el PIC32 (ver capítulo 1 para el código). Luego, abre
MATLAB y edita el talkingPIC.m. Necesitarás editar la primera línea y establecer el puerto
como el valor PORT de tu Makefile.

Muestra de código 11.6 talkingPIC.m. Código MATLAB simple para hablar con
talkingPIC en el PIC32.
port='COM3'; % Edita esto con el nombre correcto de tu PUERTO.

Asegúrate de que el puerto


esté cerrado si
˜isempty(instrfind)
fclose(instrfind);
delete(instrfind);
fin
fprintf ('Opening port %s.. \N - port',port);

Definiendo la variable serial


mySerial = serial(puerto, 'BaudRate', 230400, 'FlowControl', 'hardware');

Abriendo la conexión en
serie abierta;

Escribiendo algunos datos en el puerto


serie fprintf(miSerie,'%f %d %d\n',
[1.0,1,2])

Lectura del eco del PIC32 para verificar la correcta comunicación


data_read = fscanf(miSerie,'%f %d %d')

Cerrando la conexión en
serie fclose(mySerial)

El código talkingPIC.m abre un puerto serial, envía tres valores numéricos al PIC32,
recibe los valores y cierra el puerto. Ejecuta talkingPIC .c en tu PIC32, y luego ejecuta
talkingPIC.m en MATLAB.
UART 173

También podemos combinar MATLAB con batch.c o circ_buf.c, permitiéndonos trazar los
datos recibidos de un ISR. El siguiente ejemplo lee los datos producidos por batch. c o
circ_buf.c y los traza en MATLAB. Una vez más, cambia la variable del puerto para que
coincida con el puerto serie que utiliza tu PIC32.
Muestra de código 11.7 uart_plot.m. Código MATLAB para trazar los datos
recibidos de la UART.
% de datos de streaming en el
puerto del laboratorio
='/dev/ttyUSB0'.

si ˜isempty(instrfind) % cierra el puerto si estaba abierto


fclose(instrfind);
borrar(instrfind);
terminar

mySerial = serial(puerto, 'BaudRate', 230400, 'FlowControl','hardware');


fopen(mySerial);

fprintf(mySerial,'%s','\n'); %envía una nueva línea para decirle al PIC32 que

enviara datos len = fscanf(mySerial,'%d'); % obtenía la longitud de la matriz

datos = ceros(len,1);

para i = 1:len
datos(i) = fscanf(miSerie,'%d'); % leer cada
elemento final

trazar (1:len,datos); % trazar los datos

11.3.6 Comunicación con Python

También puedes comunicarte con el PIC32 desde el lenguaje de programación Python. Este
lenguaje de programación de libre acceso tiene muchas librerías disponibles que ayudan a
que sea usado como una alternativa a MATLAB. Para comunicarse a través del puerto serie
se necesita la biblioteca pyserial. Para el trazado, usamos las bibliotecas matplotlib y numpy.
El siguiente código lee los datos del PIC32 y los traza. Al igual que con el código MATLAB,
necesitas especificar tu propio puerto donde se define la variable del puerto. Este código
trazará los datos generados por batch.c o
circ_buf.c.

Muestra de código 11.8 uart_plot.py. Código Python para graficar los datos
recibidos de la UART.
#!/usr/bin/python
# Datos de la gráfica del PIC32 en la pitón
# requiere la importación de series de
pyseries, matplotlib y numpy
importar matplotlib.pyplot como plt
174Capítulo 11

importar numpy como np

port = '/dev/ttyUSB0' # el nombre del puerto serie con

serial.Serial(port,230400,rtscts=1) como ser:


ser.write("\n".encode()) #Dile a la imagen que envíe datos. La codificación se
convierte en una línea de matriz de bytes = ser.readline()
nsamples = int(line)
x = np.arange(0,nmuestras) # x es [1,2,3,... nmuestras]
y = np.ceros(nmuestras)# x es 1 x nmuestras un conjunto de ceros y almacenará los datos

para i en el rango (muestras): # leer cada muestra


line = ser.readline() # leer una línea del puerto serie
y[i] = int(line) # parse la línea (en este caso es sólo un entero)

plt.plot(x,y)
plt.show()

11.4 Comunicación inalámbrica con una radio XBee


Los radios XBee son pequeños transmisores de radio de baja potencia que permiten la
comunicación inalámbrica a lo largo de decenas de metros, según el estándar IEEE 802.15.4
(Figura 11.3). Cada uno de los dos dispositivos de comunicación se conecta a un XBee a
través de una UART, y entonces pueden comunicarse de forma inalámbrica como si sus
UART estuvieran conectadas por cable. Por ejemplo, dos PIC32 podrían hablar entre sí
usando sus UART3 usando la biblioteca NU32.
Las radios XBee tienen numerosos ajustes de firmware que deben ser configurados antes de
usarlas. La configuración principal es el canal inalámbrico; dos XBees no pueden
comunicarse a menos que utilicen el mismo canal. Otro ajuste es el baudio, que puede ser
configurado hasta 115.200. La manera más fácil de configurar un XBee es comprar una
tarjeta de desarrollo, conectarla a su computadora y usar el programa X-CTU proporcionado
por el fabricante. Alternativamente, puedes programar XBees directamente usando el modo
API a través de un puerto serie (ya sea desde tu ordenador o el PIC32).
Después de configurar los XBees para usar el baudio y el canal de comunicación deseados,
puedes usarlos como sustituto, uno en cada UART, para los cables que normalmente los
conectan. 3

3
Una advertencia se produce a tasas de baudios más altas. El XBee genera su baudio dividiendo una señal de reloj
interno. Este reloj no alcanza realmente un baudio de 115.200, sin embargo, cuando se ajusta a 115.200 el
baudio es en realidad de 111.000. Tales desajustes en la tasa de baudios son un problema común cuando se usan
UARTs. Debido a las tolerancias de la sincronización de la UART, el XBee puede funcionar durante un tiempo
pero ocasionalmente experimenta fallos. La solución es ajustar su baudio a 111.000 para que coincida con el
baudio real del XBee.
UART 175

Figura 11.3
Una radio XBee 802.15.4. (Imagen cortesía de Digi International, digi.com.)

11.5 Resumen del capítulo


• Una UART es el motor de bajo nivel que subyace a la comunicación en serie. Una vez
ubicuos, los puertos serie han sido reemplazados en gran parte por el USB en los
productos de consumo.
• La placa NU32 utiliza una UART para comunicarse con su PC. El software de tu
ordenador emula un puerto serie, que transfiere los datos a través de USB a un chip de la
placa NU32. Este chip convierte los datos del USB en un formato adecuado para la
UART. Ni tu terminal ni el PIC32 saben que los datos se envían realmente a través del
USB, sólo ven una UART.
• El PIC32 mantiene dos FIFOs de hardware de ocho bytes, uno para recibir, otro para
enviar. Estos FIFOs almacenan datos en un buffer en el hardware, permitiendo al
software atender temporalmente a otras tareas mientras los buffers se transmiten o se
llenan con los datos recibidos.
• Tanto el PIC32 como el DTE deben acordar una velocidad de comunicación (baudios)
y un formato de datos comunes; de lo contrario, los datos no se interpretarán
correctamente.
• El control de flujo de hardware proporciona un método para señalar que su dispositivo no
está listo para recibir más datos. Aunque el hardware maneja el control de flujo
automáticamente, el software debe asegurar que los buffers de RX y TX no se desborden.

11.6 Ejercicios
1. Trace la forma de onda para una UART enviando el byte 0b11011001, asumiendo
9600 baudios, sin paridad, 8 bits de datos, y un bit de parada.
176Capítulo 11

2. Escribe un programa que lea los caracteres que escribes en tu emulador de


terminal (a través de UART3), los ponga en mayúsculas y los devuelva a tu
ordenador. En lugar de procesar cada carácter una línea a la vez, usted quiere un
resultado después de que cada carácter sea presionado; por lo tanto, no puede usar
NU32_WriteUART3 o NU32_ReadUART3. Por ejemplo, si tecleas 'x' en el emulador de
terminal, deberías ver 'X'. Puede utilizar la función de la biblioteca estándar de C
toupper, definida en ctype.h, para convertir los caracteres a mayúsculas.

Más lecturas
Manual de referencia de la familia PIC32. Sección 21: UART. (2012). Microchip Technology Inc.
Módulos de RF XBee/XBee-PRO (v1.xEx). (2009). Digi International

También podría gustarte