Comunicación serie
UART
Trama asíncrona, velocidad con DLM, DLL y el divisor fraccionario, e interrupciones de Tx y Rx
Idea clave. La UART manda bytes de uno en uno por un solo hilo (TxD) y los recibe por otro (RxD), sin reloj compartido. Por eso los dos extremos tienen que acordar de antemano el formato y la velocidad. El LPC1768 tiene cuatro (UART0–UART3). En la Mini-DK2, la UART0 (P0.2 y P0.3) va a un conversor USB-serie: es la que se usa para hablar con el PC y la que usa el bootloader para grabar la flash.
La trama
En reposo la línea está a 1. Cada byte va así:
- Start: un bit a 0. Su flanco de bajada avisa al receptor de que empieza un byte.
- Datos: 8 bits (pueden ser 5–8), primero el de menos peso.
- Paridad (opcional): par o impar, para detectar un bit erróneo.
- Stop: 1 o 2 bits a 1.
El formato más usado es 8N1 (8 datos, sin paridad, 1 stop): 10 bits por byte. Cada bit dura , y la velocidad se da en bit/s (baudios): 9600, 19 200, 115 200…
Para enlazar dos equipos, el TxD de uno va al RxD del otro (cruzados) y las masas unidas. Así se conectan un GPS, un módulo GSM o Bluetooth, un LiDAR…
La configuración
- Pines con PINSEL: P0.2 → TXD0 y P0.3 → RXD0 (función 01).
- Alimentación con PCONP (UART0 y UART1 ya están encendidas tras el reset; UART2 y UART3 no).
- Formato en LCR.
- Velocidad con DLL, DLM y FDR (con DLAB = 1).
- Interrupciones en IER y en el NVIC.
| Bits | Campo | Qué hace |
|---|---|---|
| 1:0 | Word Length Select | 00 = 5 bits … 11 = 8 bits |
| 2 | Stop Bit Select | 0 = 1 bit de stop; 1 = 2 bits |
| 3 | Parity Enable | 1 = con paridad |
| 5:4 | Parity Select | 00 = impar, 01 = par, 10 = forzada a 1, 11 = forzada a 0 |
| 7 | DLAB | 1 = acceso a DLL y DLM; 0 = acceso a RBR, THR e IER |
La velocidad
El reloj de la UART se divide entre 16 (cada bit se muestrea 16 veces) y entre el divisor (16 bits). El divisor fraccionario (FDR) afina aún más:
En la mayoría de los casos basta con DL (tras el reset, DivAddVal = 0 y el factor vale 1):
Por ejemplo, con MHz y 19 200 bit/s: y la velocidad real es 19 290 bit/s (0,47 % de error, sin problema). Con 115 200 bit/s el redondeo da y un error del 3,1 %, ya demasiado; ahí entra el fraccionario.
| Bits | Campo | Qué hace |
|---|---|---|
| 3:0 | DIVADDVAL | 0 a 14. Con 0, el divisor fraccionario no actúa (valor tras el reset) |
| 7:4 | MULVAL | 1 a 15, y mayor que DIVADDVAL (vale 1 tras el reset) |
La calculadora prueba todas las combinaciones válidas de MulVal y DivAddVal y se queda con la de menor error:
Transmitir y recibir
Hay dos registros de datos de 8 bits en la misma dirección: THR (lo que se escribe se envía) y RBR (lo que se lee es lo recibido).
- Tx: empieza al escribir en THR. Cuando el byte pasa al registro de desplazamiento, THR queda vacío y salta la interrupción THRE: ya cabe el siguiente.
- Rx: el flanco de bajada del start arranca la recepción, se muestrean los bits y, con el stop, el byte queda en RBR y salta la interrupción RDA (Receive Data Available).
| Bits | Campo | Qué hace |
|---|---|---|
| 0 | RBR Interrupt Enable | 1 = interrumpir al recibir un dato (RDA) |
| 1 | THRE Interrupt Enable | 1 = interrumpir cuando THR queda vacío |
| 2 | RX Line Status | 1 = interrumpir con errores de recepción |
| Bits | Campo | Qué hace |
|---|---|---|
| 0 | IntStatus | 0 = hay una interrupción pendiente (activo a nivel bajo) |
| 3:1 | IntId | 011 = errores de línea, 010 = RDA, 110 = time-out de la FIFO, 001 = THRE |
void uart0_init(void) {
LPC_PINCON->PINSEL0 |= (1 << 4) | (1 << 6); // P0.2 = TXD0, P0.3 = RXD0
LPC_UART0->LCR = (1 << 7) | 0x03; // DLAB = 1; 8 bits, sin paridad, 1 stop
LPC_UART0->DLL = 81; // 19 200 bit/s con F_PCLK = 25 MHz
LPC_UART0->DLM = 0;
LPC_UART0->LCR = 0x03; // DLAB = 0
LPC_UART0->IER = (1 << 0) | (1 << 1); // RDA y THRE
NVIC_EnableIRQ(UART0_IRQn);
}
Mandar una cadena con interrupciones
El primer carácter se escribe fuera de la ISR; cada interrupción THRE manda el siguiente, hasta el '\0'. Mientras tanto, el programa principal sigue con lo suyo.
char *ptr_tx; // siguiente carácter que hay que mandar
char tx_completa;
char buffer[32]; // línea recibida (RDA); 32 es un tamaño de ejemplo
char *ptr_rx = buffer; // dónde se guarda el siguiente byte recibido
volatile char rx_completa; // la ISR lo pone a 1 cuando llega '\r'
void tx_cadena_UART0(char *cadena) {
ptr_tx = cadena;
tx_completa = 0;
LPC_UART0->THR = *ptr_tx++; // el primero, a mano
}
void UART0_IRQHandler(void) {
switch ((LPC_UART0->IIR >> 1) & 0x07) { // leer IIR borra la THRE
case 2: // RDA: ha llegado un byte
*ptr_rx = LPC_UART0->RBR; // leer RBR borra la RDA
if (*ptr_rx == '\r') { // fin de línea: cadena completa
*ptr_rx = '\0';
rx_completa = 1;
ptr_rx = buffer;
} else {
ptr_rx++;
}
break;
case 1: // THRE: cabe el siguiente
if (*ptr_tx) LPC_UART0->THR = *ptr_tx++;
else tx_completa = 1;
break;
}
}
La cadena tiene que seguir existiendo hasta que acabe de enviarse (una variable global, no un array local de una función que ya ha terminado). Y en la recepción conviene comprobar que ptr_rx no se sale de buffer.
Práctica 4 (UART: monitorización remota) · conviene dominarlo
Cuando lo que llega no son líneas de texto sino tramas con cabecera, longitud y checksum, la ISR de recepción se convierte en una máquina de estados: cada byte hace avanzar un estado. Se ve en el tema de máquinas de estados.
Práctica 5 (UART: lectura del LiDAR) · conviene dominarlo
Preguntas de examen
Configura la UART0 a 9600 bit/s, 8N1, con F_PCLK = 25 MHz y sin divisor fraccionario. ¿Qué error tiene?
: DLM = 0, DLL = 163. bit/s, un −0,15 %. LCR = 0x83, DLL, DLM, LCR = 0x03. (Respuesta redactada a partir de los apuntes.)
¿Por qué se manda el primer carácter fuera de la ISR?
Porque la interrupción THRE salta cuando THR pasa a estar vacío. La interrupción THRE solo se produce después de que THR haya recibido algún dato y lo haya pasado al registro de desplazamiento: mientras no se escriba en THR, no hay nada que se vacíe y no salta ninguna. Por eso hay que escribir el primer carácter a mano. Una vez hecho, cada THRE posterior encadena el siguiente. (Respuesta redactada a partir de los apuntes.)
¿Qué error máximo de velocidad aguanta una UART con 8N1?
El receptor se sincroniza en el flanco del start y muestrea cada bit hacia su centro. Hasta el bit de stop pasan unos 9,5 bits, y el error acumulado no puede llegar a medio bit: en total, menos de un 5 % entre los dos extremos (en la práctica se busca menos del 2 %). (Respuesta redactada: es la regla general de cualquier UART.)
Material del curso usado: Apuntes de Miguel · Resumen de interrupciones del LPC1768 (diapositivas de clase) · UM10360 (manual del LPC17xx), cap. 14.