01 · Punto de partida
Hablar en texto
En el tema anterior vimos cómo recibir bytes sin perderlos. Ahora toca darles sentido. Hay dos grandes estilos: los protocolos binarios, compactos pero ilegibles, y los protocolos de texto, donde cada mensaje es una línea de caracteres imprimibles. Los de texto cuestan más bytes, pero tienen una ventaja enorme para quien programa: se pueden probar a mano con el monitor serie, se leen en una captura sin decodificar nada y un error salta a la vista.
Por eso casi todos los módulos que se conectan por UART hablan en texto: los módems y módulos celulares, Bluetooth y Wi-Fi se configuran con comandos AT, y los receptores GPS emiten sentencias NMEA una vez por segundo. Si aprendés a manejar esos dos, aprendés el patrón de decenas de dispositivos más.
02 · La forma común
Anatomía de un protocolo de texto
Aunque cada protocolo tiene sus particularidades, casi todos los de texto comparten una estructura:
- Un mensaje por línea. El fin de línea marca dónde termina el mensaje; es el delimitador que los protocolos binarios resuelven de otra manera (tema 41).
- Campos separados. Con una coma (NMEA), un espacio, dos puntos o un signo igual (AT).
- Estilo pregunta–respuesta, en el que cada comando recibe una respuesta, o emisión espontánea, en la que el dispositivo transmite solo (el GPS).
- Mensajes no solicitados. Algunos módulos emiten avisos en cualquier momento (un SMS recibido, una conexión que se cae); el programa debe saber reconocerlos y no confundirlos con la respuesta a su comando.
El detalle que más problemas causa es el terminador de línea, porque hay tres convenciones y cada dispositivo usa la suya:
| Terminador | Bytes | Dónde se usa |
|---|---|---|
CR (retorno de carro, \r) | 0x0D | Muchos módems y módulos AT esperan un CR al final de cada comando; Macintosh antiguos |
LF (nueva línea, \n) | 0x0A | Unix, Linux; muchos protocolos propios |
CR + LF (\r\n) | 0x0D 0x0A | NMEA, HTTP, respuestas de módulos AT, Windows |
La regla de oro: al enviar, usá exactamente lo que pide el dispositivo; al recibir, tolerá cualquiera. El lector de líneas del tema 9 ignora el CR y termina en el LF, y por eso admite los tres casos. Y como Serial Monitor de Arduino puede agregar «nueva línea», «retorno de carro» o ambos, la causa de muchos módulos que «no responden» es simplemente un terminador equivocado.
03 · Módems y módulos
Comandos AT
El conjunto de comandos AT nació en los años ochenta para controlar módems telefónicos (el «Hayes») y se convirtió en el estándar de hecho para configurar módulos de comunicación: celulares (SIM800, SIM7600), Bluetooth (HC-05), Wi-Fi (ESP8266 con firmware AT) y LoRa, entre otros. Cada comando empieza con las letras AT (de attention), sigue con el nombre y termina con el terminador de línea.
| Forma | Significado | Ejemplo |
|---|---|---|
AT | Prueba de vida: ¿estás ahí? | AT → OK |
AT+CMD | Ejecutar una acción | AT+RST |
AT+CMD? | Consultar el valor actual | AT+CPIN? → +CPIN: READY |
AT+CMD=valor | Establecer un valor | AT+NAME=Sensor1 |
AT+CMD=? | Consultar los valores admitidos | AT+CWMODE=? |
Las respuestas siguen su propia convención. Cuando el comando termina bien, el módulo devuelve una línea OK; si falla, ERROR o una variante con el motivo (+CME ERROR: 10). Antes del OK puede haber líneas de datos, que empiezan con el nombre del comando. Una conversación típica de un módulo celular, con los terminadores representados:
| Enviás | Recibís | Qué significa |
|---|---|---|
AT\r\n | \r\nOK\r\n | El módulo responde |
AT+CPIN?\r\n | \r\n+CPIN: READY\r\n\r\nOK\r\n | La tarjeta SIM está lista |
AT+CSQ\r\n | \r\n+CSQ: 18,0\r\n\r\nOK\r\n | Calidad de señal 18 → −113 + 2 × 18 = −77 dBm |
AT+FOO\r\n | \r\nERROR\r\n | Comando desconocido |
En el valor de AT+CSQ, la escala va de 0 (−113 dBm o menos) a 31 (−51 dBm o más), con 99 para «desconocido». La fórmula para el rango intermedio es dBm = −113 + 2 × n.
Reglas prácticas
- El eco. Muchos módulos repiten lo que reciben (
ATE0lo desactiva). Si el eco está activo, el primer renglón de la respuesta es tu propio comando: el programa debe saltearlo. - Los tiempos. Una respuesta puede tardar milisegundos (
AT) o decenas de segundos (conectarse a una red conAT+CWJAP). Cada comando necesita su propio timeout, que se toma de la hoja de datos. - La velocidad. Algunos módulos fijan su velocidad (el HC-05 en modo AT usa 38 400 baudios) y otros la detectan solos (el SIM800 con autobaud, como vimos en el tema 8): hay que enviar
ATvarias veces hasta que contestenOK. - Los avisos espontáneos (URC). Líneas como
RINGo+CMTI: "SM",1pueden aparecer en cualquier momento, incluso entre un comando y su respuesta. Un buen manejador de AT reconoce elOKo elERRORque corresponde y trata el resto como datos o avisos. - Un comando a la vez. No se envía el siguiente hasta recibir
OK,ERRORo agotar el tiempo.
04 · El idioma del GPS
NMEA 0183: el idioma del GPS
Los receptores GPS más comunes (u-blox, los módulos NEO-6M y similares) salen de fábrica enviando por la UART, a 9600 baudios y una vez por segundo, un grupo de sentencias NMEA 0183, un estándar de texto de la industria naval. Cada sentencia es una línea con una estructura rígida:
El checksum es el XOR bit a bit de todos los caracteres que hay entre el $ y el *, escrito con dos dígitos hexadecimales. Es un chequeo simple, parecido a la paridad pero por mensaje: detecta casi cualquier alteración de un carácter, y lo calcula el receptor para comparar con el que llegó. Una sentencia con checksum incorrecto se descarta.
Los tipos de sentencia más usados son:
| Tipo | Qué informa |
|---|---|
GGA | Datos de posición: hora, latitud, longitud, calidad de la señal, satélites, altitud |
RMC | Mínimo recomendado: hora, estado, latitud, longitud, velocidad en nudos, rumbo, fecha |
GSV | Satélites a la vista (varias sentencias por ciclo) |
GSA | Satélites activos y dilución de la precisión |
GLL | Latitud y longitud con la hora |
Una sentencia GGA real tiene estos campos, numerados desde cero (el campo 0 es el tipo):
| Nº | Campo | Valor del ejemplo | Interpretación |
|---|---|---|---|
| 1 | Hora UTC | 123519 | 12:35:19 UTC |
| 2–3 | Latitud | 4807.038,N | 48° 07,038′ Norte = 48,1173° |
| 4–5 | Longitud | 01131.000,E | 11° 31,000′ Este = 11,5167° |
| 6 | Calidad de la solución | 1 | 0 = sin posición, 1 = GPS, 2 = GPS diferencial |
| 7 | Satélites en uso | 08 | 8 |
| 8 | HDOP | 0.9 | Dilución horizontal de la precisión (menor es mejor) |
| 9–10 | Altitud | 545.4,M | 545,4 metros sobre el nivel del mar |
| 11–12 | Separación del geoide | 46.9,M | 46,9 metros |
Las coordenadas no son grados decimales
La trampa más conocida de NMEA es el formato de las coordenadas: ddmm.mmmm para la latitud y dddmm.mmmm para la longitud, es decir, los grados enteros seguidos de los minutos con decimales, sin separador entre ellos. Para pasar a grados decimales:
Otros detalles: la velocidad de la sentencia RMC está en nudos (1 nudo = 1,852 km/h: 22,4 nudos son 41,5 km/h), la hora es UTC y la fecha viene como ddmmaa. Un GPS sin señal sigue emitiendo sentencias, pero con los campos de posición vacíos y la calidad en 0: hay que comprobarlo antes de usar los números.
05 · Convertir texto en datos
Analizar líneas sin tropezar
Con una línea completa en un arreglo (tema 9), el análisis tiene cuatro pasos: validar (largo, inicio, checksum), separar los campos, convertir los que se necesitan y verificar rangos. En cada uno hay trampas típicas:
| Paso | Trampa | Cómo evitarla |
|---|---|---|
| Validar | Usar datos de una línea con checksum incorrecto o cortada | Verificar el checksum y el largo antes de mirar los campos |
| Separar | strtok trata varias comas seguidas como un solo separador y saltea los campos vacíos: los índices se corren | Separar a mano o con strsep, conservando los campos vacíos |
| Convertir | atoi y atof devuelven 0 con un campo vacío, que se confunde con un valor real | Comprobar que el campo no esté vacío antes de convertirlo |
| Coordenadas | Tomar 4807.038 como grados decimales | Separar grados y minutos y convertir |
| Precisión | Un float de un AVR tiene unos 7 dígitos: se pierden metros en la posición | Usar enteros (minutos × 10 000) para precisión de metros |
| Rangos | Aceptar valores imposibles por un error de transmisión que pasó el checksum | Verificar rangos (latitud entre −90 y 90, satélites entre 0 y 24…) |
El caso de los campos vacíos merece un ejemplo. Una sentencia GGA sin señal tiene 15 campos, pero la mayoría están vacíos: $GPGGA,123519,,,,,0,00,99.99,,,,,,*45. Un strtok con la coma como separador devuelve solo 5 elementos (GPGGA, 123519, 0, 00 y 99.99), y el programa que busca la calidad en la posición 6 leerá algo que no corresponde. El análisis por posición exige que cada coma cuente.
06 · Frenar al emisor
Control de flujo: RTS/CTS y XON/XOFF
En el tema 9 vimos que un buffer grande ayuda, pero que si el emisor es más rápido que el receptor siempre termina desbordándolo. La solución de fondo es que el receptor pueda pedirle al emisor que se detenga. Eso es el control de flujo. Hay dos variantes.
Por hardware: RTS/CTS
Se agregan dos cables. En el uso moderno, el receptor controla una línea RTS («listo para recibir») conectada a la entrada CTS del emisor («vía libre»). Mientras el receptor tiene lugar, mantiene RTS activa y el emisor transmite; cuando su buffer se acerca a llenarse, desactiva RTS y el emisor se detiene después del byte en curso. Cuando el buffer se vacía, la reactiva.
Hay que dejar un margen. Cuando el receptor baja RTS, todavía pueden llegar bytes: el que se estaba enviando y los que el emisor ya tenía en su FIFO de hardware (que puede ser de 16 bytes o más). Por eso el umbral alto no puede ser el 100 % del buffer: tiene que cumplir umbral alto + bytes en vuelo ≤ tamaño del buffer. Un umbral bajo bastante menor (por ejemplo, el 25 %) da histéresis y evita que la línea oscile.
Por software: XON/XOFF
Sin cables extra, el receptor envía por la línea de retorno un carácter de control: XOFF (0x13) para pedir que el emisor pare y XON (0x11) para que reanude. Es simple, pero tiene dos defectos: no sirve para datos binarios (porque 0x11 y 0x13 pueden aparecer como datos) y la orden viaja con la misma demora que cualquier byte, lo que obliga a un margen mayor.
| Estrategia | Cables | Datos binarios | Reacción | Cuándo usarla |
|---|---|---|---|---|
| Ninguna | TX, RX, GND | Sí | — | Flujos pequeños o receptor siempre más rápido |
| XON/XOFF | TX, RX, GND | No (reserva dos códigos) | Lenta (viaja como un byte más) | Texto entre equipos sin cables adicionales |
| RTS/CTS | TX, RX, GND, RTS, CTS | Sí | Rápida (una señal eléctrica) | Velocidades altas, módulos Wi-Fi y Bluetooth, transferencias grandes |
Los ESP32 y muchos STM32 implementan RTS/CTS por hardware: la UART sola vigila el nivel del buffer y mueve la línea. En un AVR sin esa función se hace por software, activando un pin desde la interrupción de recepción, como se muestra en el código del tema.
07 · Con el microcontrolador
Un manejador de comandos AT y un lector de NMEA
El primer programa envía un comando AT a un módulo conectado a Serial1 (dejando Serial para depurar) y espera OK, ERROR o el vencimiento del tiempo. Si se le indica un prefijo (por ejemplo +CSQ:), guarda la línea de datos que lo lleva. Descarta lo que haya antes, así que el eco y los avisos viejos no confunden:
// Devuelve true si el módulo respondió OK. Si 'prefijo' no es NULL, copia a 'dato' la línea que empieza con él.
bool comandoAT(const char *cmd, unsigned long timeoutMs, const char *prefijo = NULL,
char *dato = NULL, size_t tam = 0) {
while (Serial1.available()) Serial1.read(); // descartar avisos y restos anteriores
Serial1.print(cmd);
Serial1.print("\r\n"); // la mayoría de los módulos piden CR LF
char linea[64];
byte n = 0;
unsigned long t0 = millis();
while (millis() - t0 < timeoutMs) {
while (Serial1.available()) {
char c = Serial1.read();
if (c == '\r') continue;
if (c != '\n') {
if (n < sizeof(linea) - 1) linea[n++] = c;
continue;
}
linea[n] = '\0'; // línea completa
n = 0;
if (strcmp(linea, "OK") == 0) return true;
if (strncmp(linea, "ERROR", 5) == 0 || strncmp(linea, "+CME ERROR", 10) == 0) return false;
if (prefijo && dato && strncmp(linea, prefijo, strlen(prefijo)) == 0) {
strncpy(dato, linea, tam - 1);
dato[tam - 1] = '\0';
} // el eco y otras líneas se ignoran
}
}
return false; // se agotó el tiempo
}
void setup() {
Serial.begin(115200);
Serial1.begin(9600);
for (byte i = 0; i < 5 && !comandoAT("AT", 500); i++) {} // autobaud: insistir hasta que conteste
char resp[32];
if (comandoAT("AT+CSQ", 1000, "+CSQ:", resp, sizeof(resp))) {
int n = atoi(resp + 6); // "+CSQ: 18,0" -> 18
Serial.print("Señal: ");
Serial.print(-113 + 2 * n);
Serial.println(" dBm");
}
}
void loop() {}
Esta versión bloquea mientras espera, y es adecuada para la inicialización. Con un programa que debe atender otras tareas, la misma lógica se convierte en una máquina de estados que se avanza en cada vuelta de loop() (estado «esperando respuesta» con su hora de inicio), como en el tema 41.
El segundo programa valida una sentencia NMEA, separa los campos conservando los vacíos y convierte la posición a grados:
// 's' = "$GPGGA,...*47" ya sin CR ni LF
bool nmeaValida(const char *s) {
if (s[0] != '$') return false;
uint8_t suma = 0;
const char *p = s + 1;
while (*p && *p != '*') suma ^= *p++; // XOR de todo lo que está entre '$' y '*'
if (*p != '*') return false;
return suma == (uint8_t)strtol(p + 1, NULL, 16); // comparar con los dos dígitos hexadecimales
}
// Reemplaza cada coma por '\0' y guarda dónde empieza cada campo. A diferencia de strtok, conserva los vacíos.
byte separar(char *s, char *campo[], byte max) {
byte n = 0;
campo[n++] = s;
for (; *s && n < max; s++) {
if (*s == ',') { *s = '\0'; campo[n++] = s + 1; }
}
return n;
}
float grados(const char *campo, char hemisferio) { // "4807.038", 'N' -> 48.1173
float v = atof(campo);
int g = (int)(v / 100); // los grados son lo que está antes de los minutos
float res = g + (v - g * 100) / 60.0;
return (hemisferio == 'S' || hemisferio == 'W') ? -res : res;
}
void procesarGGA(char *linea) {
if (!nmeaValida(linea)) return; // checksum incorrecto: descartar
char *estrella = strchr(linea, '*');
*estrella = '\0'; // dejar solo el cuerpo para separar
char *c[16];
byte n = separar(linea, c, 16);
if (n < 10 || c[6][0] == '0' || c[2][0] == '\0') return; // sin posición válida: no usar
float lat = grados(c[2], c[3][0]);
float lon = grados(c[4], c[5][0]);
Serial.print(lat, 4); Serial.print(", "); Serial.println(lon, 4);
}
Dos comentarios finales. Primero, como el float de un AVR tiene unos siete dígitos significativos, la latitud 48,1173° llega con una resolución de unos 10 m; para más precisión conviene guardar los minutos como entero. Segundo, para implementar RTS/CTS por software en un AVR, la rutina de recepción del tema 9 debe, después de guardar cada byte, comparar la ocupación con el umbral alto y bajar un pin de salida (digitalWrite(PIN_RTS, HIGH) en un esquema de lógica activa baja), y la función de lectura debe volver a subirlo al bajar del umbral bajo.
08 · Analogía con programación
Validar, separar y convertir en Python
El mismo análisis en Python cabe en pocas líneas, porque split(",") conserva los campos vacíos. El programa calcula el checksum, valida cuatro sentencias (una de ellas con un dígito alterado y otra sin señal), convierte la posición, transforma nudos en km/h y traduce la calidad de señal de un módulo AT a dBm.
Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.
def checksum_nmea(frase):
cuerpo = frase[1:frase.index("*")] # lo que está entre el '$' y el '*'
suma = 0
for c in cuerpo:
suma ^= ord(c)
return f"{suma:02X}"
def es_valida(frase):
if not frase.startswith("$") or "*" not in frase:
return False
return checksum_nmea(frase) == frase.split("*")[1].strip().upper()
def grados(campo, hemisferio):
valor = float(campo)
g = int(valor // 100) # los grados son lo que está antes de los minutos
decimal = g + (valor - g * 100) / 60
return -decimal if hemisferio in "SW" else decimal
def leer_gga(frase):
campos = frase.split("*")[0].split(",") # split conserva los campos vacíos
if campos[6] == "0":
return "sin posición (calidad = 0)"
lat = grados(campos[2], campos[3])
lon = grados(campos[4], campos[5])
return f"lat {lat:.4f}, lon {lon:.4f}, altitud {campos[9]} m, {int(campos[7])} satélites"
def dbm_de_csq(n):
return None if n == 99 else -113 + 2 * n
frases = [
"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47",
"$GPGGA,123519,4807.039,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47", # un dígito alterado
"$GPGGA,123519,,,,,0,00,99.99,,,,,,*45", # sin señal
"$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A",
]
for f in frases:
valida = es_valida(f)
print(f[:30] + "...", "checksum calculado", checksum_nmea(f), "->", "válida" if valida else "RECHAZADA")
if valida and f[1:6] == "GPGGA":
print(" ", leer_gga(f))
campos = frases[3].split("*")[0].split(",")
nudos = float(campos[7])
print(f"velocidad: {nudos} nudos = {nudos * 1.852:.1f} km/h")
for n in (18, 5, 99):
print(f"AT+CSQ: {n} -> {dbm_de_csq(n)} dBm")
Resultado: la primera sentencia tiene checksum 47 y es válida: latitud 48,1173°, longitud 11,5167°, altitud 545,4 m y 8 satélites. En la segunda, el dígito alterado cambia el XOR a 46 y la sentencia se rechaza. La tercera es válida (checksum 45) pero no tiene posición, y la cuarta (RMC, checksum 6A) da 22,4 nudos = 41,5 km/h. La señal AT+CSQ de 18 son −77 dBm; la de 5, −103 dBm; y 99 significa «desconocido». Alterá un carácter de la primera frase y mirá qué checksum calcula.
Una sentencia puede tener un checksum correcto y datos absurdos (un error doble que se compensa en el XOR, o un GPS defectuoso). Siempre conviene comprobar rangos antes de usar los valores.
09 · Exploración interactiva
NMEA y control de flujo
El laboratorio tiene dos herramientas. La primera es un analizador de sentencias NMEA: elegí una de muestra o escribí la tuya, y verás el checksum calculado, el recibido, la validez, los campos con su significado y la posición en grados decimales. La segunda simula una transferencia de 2000 bytes hacia un receptor lento, con y sin control de flujo.
1. Analizador de sentencias NMEA
| Nº | Significado | Valor |
|---|
Elegí una sentencia o editá el texto.
2. Control de flujo con un receptor lento (2000 bytes)
Elegí los parámetros de la transferencia.
En la primera herramienta, el «dato adicional» muestra la altitud (GGA) o la velocidad en km/h (RMC). En la segunda, el emisor manda sin pausa un byte por cada tiempo de byte de la línea; el receptor consume a su ritmo, desactiva RTS al llegar al umbral alto y la reactiva al bajar al 25 % del buffer. Los «bytes en vuelo» son los que todavía llegan después de la orden (el que se estaba enviando y los de la FIFO del emisor).
Recorrido guiado
- Con la sentencia GGA de muestra, el checksum calculado y el recibido son 47: es válida. La latitud es 48,1173° y la longitud 11,5167°; la altitud, 545,4 m. Fijate en la tabla que los campos 11 y 12 (separación del geoide) y los dos últimos, vacíos, mantienen su lugar.
- Pasá a RMC: el checksum es 6A, la velocidad 22,4 nudos (41,5 km/h) y la fecha 230394.
- Elegí GGA con un dígito alterado: el XOR calculado es 46 y el recibido 47: la sentencia se rechaza. Un solo carácter cambiado alcanza para detectarlo.
- Con esa misma sentencia, presioná Corregir el checksum: se reescribe el 47 por el 46 y pasa a ser válida, aunque la posición sea otra. Un checksum correcto no significa que los datos sean los originales: solo que son consistentes consigo mismos.
- Probá GGA sin señal: el checksum es válido, pero la calidad vale 0 y los campos de posición están vacíos. El analizador lo señala; un programa debe comprobarlo antes de usar la latitud.
- Editá la sentencia a mano: cambiá un carácter del cuerpo y mirá cómo cambia el checksum calculado; borrá el asterisco y verás que no es una sentencia NMEA.
- En la segunda herramienta, con los valores iniciales (115 200 baudios, receptor de 2000 bytes/s, buffer de 64, RTS/CTS al 75 % y 2 bytes en vuelo) no se pierde ningún byte; el máximo es 50 y la transferencia tarda 1 s, el tiempo que necesita el receptor.
- Cambiá a Ninguno: el emisor termina en 0,2 s pero el receptor solo alcanza a guardar 411 bytes; se pierden 1589 (el 79 %). El control de flujo cambió velocidad por integridad.
- Volvé a RTS/CTS y subí los bytes en vuelo a 32: el umbral del 75 % (48 bytes) más 32 en vuelo supera los 64 del buffer y se pierden 280 bytes. Bajá el umbral a 50 % y vuelve a funcionar.
Desafío: calcular el umbral máximo
Con un buffer de 128 bytes, 16 bytes en vuelo y la regla umbral + bytes en vuelo ≤ buffer, ¿cuál es el umbral alto máximo? 128 − 16 = 112 bytes, es decir, el 87,5 %. En la práctica se deja margen para la reacción del programa, y por eso se usa el 75 %. Con un FIFO de 64 bytes en el emisor y un buffer de 64 no hay umbral posible: el control de flujo no alcanza si el emisor ya tiene en cola más de lo que cabe en el receptor. Verificá el primer caso eligiendo 256 bytes de buffer y 32 en vuelo.
10 · Método de trabajo
Cómo integrar un módulo serie
- Leé la hoja de datos del módulo: velocidad inicial, formato, terminador de línea, niveles de tensión y lista de comandos o sentencias.
- Probá a mano antes de programar. Conectá el módulo a la computadora con un adaptador USB–serie y hablale con un terminal. Si no entendés la conversación a mano, el código no te va a salvar.
- Usá el terminador exacto al enviar y tolerá cualquiera al recibir.
- Esperá siempre con un tiempo límite. Cada comando tiene su plazo; un módulo colgado no puede congelar el programa.
- Descartá lo viejo antes de enviar un comando (restos del eco, avisos anteriores) y ordená el manejo de los avisos espontáneos.
- Validá antes de usar: checksum, largo, inicio de línea y rangos de los valores.
- Separá campos conservando los vacíos y comprobá que cada uno exista antes de convertirlo.
- Dimensioná buffers y flujo. A 9600 baudios un GPS genera unos 500 bytes por segundo; con velocidades altas o módulos que envían ráfagas (Wi-Fi, celular), usá buffers grandes o RTS/CTS.
- Registrá los fallos: contadores de sentencias descartadas, de comandos sin respuesta y de buffers desbordados, para poder diagnosticar el enlace en campo.
Una última idea: un protocolo de texto es ideal para empezar, y el mismo diseño (línea, campos, checksum, comando y respuesta) se traslada a un protocolo propio. Eso es lo que haremos en el tema 41.
11 · Comprensión
Confusiones frecuentes
«El módulo no responde a los comandos AT, así que está roto»
Lo más frecuente es un terminador de línea equivocado (falta CR o LF), una velocidad distinta o TX y RX sin cruzar. Probalo con un terminal que permita elegir el fin de línea, y empezá por 9600 y 115 200 baudios.
«Si el checksum coincide, los datos son correctos»
Significa que son consistentes con su checksum: puede pasar una alteración que se compense en el XOR, o un emisor defectuoso. Un receptor prudente también verifica rangos y coherencia (por ejemplo, la calidad de posición y los satélites).
«4807.038 es la latitud en grados»
Son 48 grados y 7,038 minutos. Hay que dividir los minutos por 60: 48 + 7,038 / 60 = 48,1173°. Si se usa el número tal cual, la posición queda a decenas de kilómetros de la real.
«strtok sirve para separar cualquier línea con comas»
Salta los campos vacíos y corrompe los índices de formatos como NMEA, donde la posición de cada campo es parte del significado. Además modifica la cadena y no es reentrante. Es mejor separar a mano.
«El control de flujo evita todas las pérdidas»
Solo si se respeta el margen: umbral alto más bytes en vuelo no puede superar el tamaño del buffer. Con un umbral demasiado alto o un emisor con una FIFO grande, el flujo se detiene demasiado tarde y los datos igual se pierden.
«XON/XOFF sirve para cualquier dato»
Reserva dos códigos de control, así que no sirve para binarios, donde 0x11 y 0x13 aparecen como datos. Para transferencias binarias hace falta RTS/CTS o un protocolo propio con confirmaciones.
12 · Práctica guiada
Predecí antes de comprobar
1. Costo de un comando AT
Enviás AT+CSQ con terminador CR LF a 9600 baudios en 8N1. ¿Cuántos bytes son y cuánto tarda en salir? ¿Y la respuesta \r\n+CSQ: 18,0\r\n\r\nOK\r\n?
Ver solución
El comando tiene 6 caracteres más 2 del terminador: 8 bytes → 8 × 10 / 9600 ≈ 8,3 ms. La respuesta: 2 + 10 («+CSQ: 18,0») + 2 + 2 + 2 + 2 = 20 bytes → ≈ 20,8 ms. Entre ambas, el módulo tarda además su propio tiempo de procesamiento.
2. Calidad de señal
Un módulo responde +CSQ: 5,0, otro +CSQ: 25,0 y un tercero +CSQ: 99,99. Pasá cada valor a dBm.
Ver solución
dBm = −113 + 2 × n. Con 5: −103 dBm (señal débil). Con 25: −63 dBm (buena). El 99 significa desconocido: no hay valor de señal disponible y no debe convertirse con la fórmula.
3. Checksum a mano
Calculá el checksum NMEA de la sentencia de cuerpo ABC, es decir $ABC*??. Los códigos ASCII son A = 0x41, B = 0x42 y C = 0x43.
Ver solución
0x41 XOR 0x42 = 0x03; 0x03 XOR 0x43 = 0x40. La sentencia completa es $ABC*40. Cambiando cualquier letra el resultado cambia, y por eso se detecta la alteración de un carácter.
4. Coordenadas
Una sentencia trae la latitud 3748.000,S y la longitud 14457.000,E. Pasalas a grados decimales.
Ver solución
Latitud: 37 + 48,000 / 60 = 37,8°, y como es Sur, −37,8°. Longitud: 144 + 57,000 / 60 = 144,95°, Este: +144,95°. (La longitud usa tres dígitos de grados, 144, y la latitud solo dos.)
5. Campos vacíos
La sentencia sin señal $GPGGA,123519,,,,,0,00,99.99,,,,,,*45 se separa por comas. ¿Cuántos campos hay en realidad y cuántos devolvería strtok? ¿Qué problema trae?
Ver solución
Hay 15 campos (14 comas), la mayoría vacíos. strtok devuelve solo 5 (GPGGA, 123519, 0, 00 y 99.99) porque trata las comas seguidas como un solo separador. El programa que lee la calidad de señal en la posición 6 encontraría algo equivocado, y cualquier campo posterior (como la altitud, en la posición 9) estaría desplazado. Por eso se separa a mano.
6. Umbral de control de flujo
Un receptor tiene un buffer de 128 bytes, necesita 3 bytes de reacción y el emisor tiene una FIFO de 16 bytes ya cargada. ¿Cuál es el umbral alto máximo para no perder datos? ¿Qué umbral práctico elegirías?
Ver solución
Bytes en vuelo = 3 + 16 = 19 (más el byte que se está transmitiendo: 20). Umbral máximo ≈ 128 − 20 = 108 bytes. En la práctica se elige un valor con más margen, como el 75 % (96 bytes), y se reactiva RTS al 25 % (32 bytes) para tener histéresis.
13 · Síntesis
Ideas para recordar
- Los protocolos de texto usan una línea por mensaje, campos separados y un terminador; se prueban a mano y se depuran a simple vista, a cambio de más bytes.
- El terminador (CR, LF o CR LF) es una causa frecuente de fallas: enviá el que pide el dispositivo y tolerá cualquiera al recibir.
- Los comandos AT tienen las formas
AT+CMD,?,=valory=?; la respuesta termina enOKoERROR, y hay que usar un tiempo límite por comando y descartar el eco y los avisos espontáneos. - Una sentencia NMEA es
$tipo,campos*checksum; el checksum es el XOR de todo lo que hay entre el$y el*, y las sentencias con checksum incorrecto se descartan. - Las coordenadas NMEA son ddmm.mmmm: grados enteros más minutos; se convierten con grados = dd + mm.mmmm / 60, y Sur y Oeste son negativos.
- Al separar campos hay que conservar los vacíos (nunca
strtok) y comprobar que existan antes de convertirlos; además se verifican los rangos. - El control de flujo hace que el receptor detenga al emisor: RTS/CTS por hardware (rápido, sirve para binarios) o XON/XOFF por software (sin cables, solo texto).
- Con RTS/CTS debe cumplirse umbral alto + bytes en vuelo ≤ tamaño del buffer; el umbral bajo da histéresis.
¿Listo para el tema 11?
Antes de avanzar, calculá a mano el checksum de una sentencia de tu invención, convertí la longitud 01131.000,E a grados decimales y explicá por qué un umbral de RTS demasiado alto puede perder datos. Si podés justificar las tres respuestas, manejás los protocolos de texto sobre UART.
En el próximo tema armamos un taller con dos placas conversando por UART: un protocolo de comandos y respuestas, tiempos de espera y comunicación entre microcontroladores.