Contenido detallado
📏 Qué medir y con qué frecuencia
Telemetría es el robot informando sobre su propio estado mientras trabaja. Regla práctica: registra la entrada (lo que leyó el sensor), la decisión (error, salida del PID, estado de la máquina) y la salida (PWM de cada motor), siempre con el tiempo en milisegundos. Con estas tres columnas descubres si el problema está en percibir, pensar o actuar.
🌱 ¿Eres nuevo aquí?
Frecuencia de muestreo es cuántas mediciones por segundo guardas, en hercios (Hz): 50 Hz = una fila cada 20 ms. Baud es la velocidad del puerto serial en bits por segundo; a 115200 baud caben cerca de 11 500 caracteres por segundo, porque cada carácter ocupa 10 bits.
| Qué | Ejemplo | Ritmo sugerido |
|---|---|---|
| Bucle de control | error, corrección del PID, PWM | 50 a 200 Hz (el ritmo del PID) |
| Movimiento | velocidad de las ruedas, ángulo de la IMU | 20 a 100 Hz |
| Estado | voltaje de la batería, señal Wi-Fi (RSSI) | 1 a 2 Hz |
| Eventos | cambio de estado, línea perdida | cuando ocurren |
| Panel para las personas | resumen de los elementos anteriores | 5 a 10 Hz (el ojo ya no distingue más) |
Cómo leer: el recuadro punteado de arriba representa la capacidad del puerto serial. Cada barra es una línea de CSV de unos 40 caracteres, enviada a distintos ritmos. Cuando la barra supera el límite (rojo), el envío empieza a retrasar el propio bucle de control. Graba rápido en la tarjeta; envía despacio por la red.
✓ Hazlo
- ✓Una columna de tiempo (t_ms) en cada fila.
- ✓Nombres de columna cortos e iguales en todos los registros.
- ✓Una línea de encabezado con los parámetros de la prueba (Kp, Kd, velocidad).
✗ Evita
- ✗Frases largas en cada ciclo ("El valor del sensor ahora es...").
- ✗Imprimir dentro del bucle de control más rápido de lo que el puerto soporta.
- ✗Registro sin tiempo: no sabes qué ocurrió primero.
Por qué aprender
En el seguidor de línea del 2.6 ajustarás Kp y Kd mirando gráficos del antes y el después. Si el registro no tiene las columnas correctas con el ritmo adecuado, el gráfico no muestra la oscilación que quieres corregir.
Conceptos clave
📈 Gráfico en vivo con Serial Plotter
Arduino IDE 2.x tiene un Serial Plotter (menú Herramientas o el ícono de gráfico en la esquina superior derecha). Lee cada línea de Serial y dibuja una curva por valor. El formato es una línea por instante, con pares nombre:valor separados por comas. Solo una ventana puede usar el puerto: cierra Serial Monitor antes de abrir Plotter.
Cómo leer: el eje horizontal es el tiempo (las líneas que van llegando) y el vertical es el valor. La curva azul muestra lo que entrega el sensor; la cian es la misma lectura después del filtro, más suave y con un poco de retraso; la línea punteada es el objetivo fijo. Comparar las curvas lado a lado es la forma más rápida de ver el ruido, el retraso y el error.
Objetivo: dibujar tres curvas en vivo: la lectura cruda de un potenciómetro (o sensor IR) en GPIO 34, la versión filtrada y un objetivo fijo. Funciona en Wokwi, que también tiene Serial Plotter.
// Tres curvas en Serial Plotter de Arduino IDE 2.x (ESP32, paquete esp32 3.x)
const int PINO_SENSOR = 34; // potenciómetro o sensor IR analógico (ADC1)
const int ALVO = 2000;
float filtrado = 0;
unsigned long tPlot = 0;
void setup() { Serial.begin(115200); }
void loop() {
int cru = analogRead(PINO_SENSOR); // 0 a 4095
filtrado = 0.9 * filtrado + 0.1 * cru; // filtro exponencial: 10% del valor nuevo, 90% del anterior
if (millis() - tPlot >= 20) { // 50 líneas por segundo: Plotter puede seguir el ritmo
tPlot = millis();
Serial.printf("pot:%d,filtrado:%.0f,alvo:%d\n", cru, filtrado, ALVO);
}
}
Cómo verificar
- ✓Plotter muestra tres curvas con las leyendas pot, filtrado y objetivo.
- ✓Al girar rápido el potenciómetro, la curva filtrada llega después, sin los picos.
- ✗¿Solo aparece una curva, llamada "value 1"? Revisa el separador: dos puntos entre el nombre y el valor, y comas entre los pares. Si el formato cambia en tu versión del IDE, la ayuda de Plotter muestra el formato actual.
💡 El mismo formato sirve para el PID
Cambia las variables por erro:%d,corr:%d,esq:%d,dir:%d y verás el PID del 2.3 en acción: la corrección aumenta cuando aparece el error y las ruedas se diferencian. Con el robot sobre la mesa, sujeto al cable, esto ya revela si la señal está invertida o la ganancia es excesiva.
Por qué aprender
Es telemetría sin costo: sin cables adicionales ni programas extra. Para pruebas de banco (sensor, filtro, señal del PID), resuelve la mayoría de las dudas en minutos.
Conceptos clave
💾 Grabar en tarjeta microSD (CSV)
Un robot en movimiento no puede arrastrar un cable USB. La solución es llevar una bitácora: un módulo de tarjeta microSD que graba un archivo CSV con 50 o 100 líneas por segundo. Después de la prueba, sacas la tarjeta y abres el archivo en la computadora, en una hoja de cálculo o en Python. La tarjeta se comunica con el ESP32 mediante el bus SPI.
🌱 ¿Eres nuevo aquí?
CSV (valores separados por comas) es un texto en el que cada línea es un registro y cada coma separa una columna; la primera línea indica los nombres. SPI es un bus de cuatro cables (reloj SCK, datos MOSI y MISO, selección CS), más rápido que I2C, que usan tarjetas, pantallas y algunos sensores.
Cómo leer: cada cable conecta el pin del ESP32 (izquierda) con el pin de la misma función en el módulo (derecha). Los cuatro de arriba son los pines SPI estándar del ESP32 (VSPI). El rojo es la alimentación: hay módulos de 3,3 V y módulos con regulador que requieren 5 V en VCC; lee las etiquetas antes de conectar.
Objetivo: crear un archivo nuevo en cada encendido (/log001.csv, /log002.csv...), grabar 50 líneas por segundo y cerrar de forma segura al presionar BOOT. Tarjeta formateada en FAT32.
// Registro CSV en tarjeta microSD (ESP32, paquete esp32 3.x, módulo microSD por SPI)
#include <SPI.h>
#include <SD.h>
const int PINO_CS = 5, PINO_SENSOR = 34, BOTAO = 0; // SCK 18, MISO 19, MOSI 23 (VSPI estándar)
File arq;
char nome[16];
unsigned long tLog = 0, linhas = 0;
float filtrado = 0;
bool gravando = true;
void setup() {
Serial.begin(115200);
pinMode(BOTAO, INPUT_PULLUP);
if (!SD.begin(PINO_CS)) { Serial.println("SD no encontrado: comprueba los cables y FAT32"); while (true) delay(1000); }
for (int i = 1; i < 1000; i++) { // primer nombre libre
snprintf(nome, sizeof nome, "/log%03d.csv", i); // la barra inicial es obligatoria
if (!SD.exists(nome)) break;
}
arq = SD.open(nome, FILE_WRITE);
arq.println("t_ms,sensor,filtrado"); // encabezado, una vez
Serial.printf("Grabando en %s (BOOT finaliza)\n", nome);
}
void loop() {
if (!gravando) return;
if (millis() - tLog >= 20) { // 50 líneas por segundo
tLog = millis();
int cru = analogRead(PINO_SENSOR);
filtrado = 0.9 * filtrado + 0.1 * cru;
arq.printf("%lu,%d,%.1f\n", tLog, cru, filtrado);
if (++linhas % 25 == 0) arq.flush(); // realmente escribe cada 0,5 s
}
if (digitalRead(BOTAO) == LOW) { // finalizar antes de retirar la tarjeta
arq.close(); gravando = false;
Serial.printf("Archivo cerrado: %lu líneas. Puedes retirar la tarjeta.\n", linhas);
}
}
Cómo verificar
- ✓El Monitor Serie muestra "Grabando en /log001.csv"; después de presionar BOOT, el total de líneas (unas 50 por segundo grabado).
- ✓En la computadora, el archivo se abre en la hoja de cálculo con las columnas t_ms, sensor y filtrado.
- ✗"SD no encontrado": tarjeta de más de 32 GB en exFAT, cable de CS cambiado o módulo sin la alimentación adecuada.
💡 Por qué importa flush()
El ESP32 junta las líneas en la memoria y solo las escribe en la tarjeta cada cierto tiempo. Si la batería se agota antes, se pierde el final de la prueba y el archivo puede dañarse. flush() cada medio segundo limita la pérdida a 0,5 s; close() antes de retirar la tarjeta evita que el archivo se dañe.
Por qué aprender
La tarjeta graba rápido, sin red ni cable, y guarda pruebas completas para compararlas después. Es el registro que vas a usar para ajustar el seguidor de línea.
Conceptos clave
📡 MQTT: publicar telemetría en la red
Para ver el robot en vivo, sin cable, usa MQTT: un protocolo ligero de mensajes, diseñado para dispositivos pequeños. El robot publica en un tópico (robo/telemetria) y no necesita saber quién está escuchando. Un servidor llamado broker entrega el mensaje a todos los que se suscribieron a ese tópico: el panel en el navegador, Node-RED, un script de Python. Los comandos siguen el camino inverso, por otro tópico.
🌱 ¿Eres nuevo aquí?
O broker es el correo central (el más usado es Mosquitto, gratuito). Un tópico es la dirección del mensaje, con niveles separados por barras; robo/# se suscribe a todo lo que está debajo de robo/. Publicar es enviar; suscribirse es pedir recibir.
Cómo leer: las flechas continuas representan la telemetría que sale del robot y el broker copia para cada suscriptor. Las discontinuas representan el comando: el panel publica en robo/comando y el broker se lo entrega al robot. Nadie se comunica directamente con nadie; todos se comunican con el broker.
Paso a paso: broker Mosquitto en tu PC o Raspberry Pi
Instala
Linux y Raspberry Pi OS mediante apt (abajo). En Windows y macOS, usa el instalador de mosquitto.org/download.
Abre el acceso a la red local
Mosquitto 2.x solo acepta conexiones de la propia máquina hasta que declares un listener. El archivo robo.conf abre el puerto 1883 (ESP32) y el 9001 (navegador, WebSocket).
Averigua la IP y escucha
Anota la IP del PC en la red (hostname -I en Linux, ipconfig en Windows) y deja una terminal con mosquitto_sub abierta.
# Broker para la red de casa o de la escuela (Mosquitto 2.x)
listener 1883
listener 9001
protocol websockets
# sin contraseña: solo en una red local de confianza; si está expuesto a Internet, configura usuario y contraseña
allow_anonymous true
sudo apt install mosquitto mosquitto-clients
sudo nano /etc/mosquitto/conf.d/robo.conf # pega el bloque de arriba y guarda
sudo systemctl restart mosquitto
mosquitto_sub -h localhost -t 'robo/#' -v # muestra todo lo que publica el robot
mosquitto_pub -h localhost -t robo/comando -m parar # en otra terminal: envía un comando
Objetivo: el ESP32 publica un JSON breve 5 veces por segundo y obedece "parar" y "andar" (el LED del GPIO 2 indica que está detenido). Instala la biblioteca PubSubClient (Nick O'Leary) desde el Administrador de Bibliotecas. Cambia los campos entre < >.
// Telemetría por MQTT (ESP32, paquete esp32 3.x + biblioteca PubSubClient)
#include <WiFi.h>
#include <PubSubClient.h>
const char *WIFI_NOME = "<seu_wifi>"; // en Wokwi: "Wokwi-GUEST" con contraseña ""
const char *WIFI_SENHA = "<sua_senha>";
const char *BROKER = "<ip_do_seu_pc>"; // prueba rápida: "test.mosquitto.org" (público: cualquiera puede leer)
const int PINO_SENSOR = 34, PINO_LED = 2;
WiFiClient wifi;
PubSubClient mqtt(wifi);
unsigned long tPub = 0, tTentativa = 0;
bool parado = false;
void aoReceber(char *topico, byte *dados, unsigned int n) { // los comandos llegan aquí
String msg; for (unsigned int i = 0; i < n; i++) msg += (char)dados[i];
if (msg == "parar") parado = true;
if (msg == "andar") parado = false;
digitalWrite(PINO_LED, parado);
Serial.printf("comando: %s\n", msg.c_str());
}
void setup() {
Serial.begin(115200);
pinMode(PINO_LED, OUTPUT);
WiFi.begin(WIFI_NOME, WIFI_SENHA);
while (WiFi.status() != WL_CONNECTED) { delay(300); Serial.print("."); } // espera solo en setup
Serial.printf("\nWi-Fi listo: %s\n", WiFi.localIP().toString().c_str());
mqtt.setServer(BROKER, 1883);
mqtt.setCallback(aoReceber);
}
void loop() {
if (!mqtt.connected() && millis() - tTentativa > 2000) { // se reconecta sin bloquear el robot
tTentativa = millis();
if (mqtt.connect("robo-inema-01")) { mqtt.subscribe("robo/comando"); Serial.println("MQTT conectado"); }
}
mqtt.loop(); // envía y recibe; llama en cada vuelta
if (mqtt.connected() && millis() - tPub >= 200) { // 5 mensajes por segundo
tPub = millis();
char json[96]; // breve: el búfer predeterminado es de 256 bytes
snprintf(json, sizeof json, "{\"t\":%lu,\"sensor\":%d,\"rssi\":%d,\"parado\":%d}", tPub, analogRead(PINO_SENSOR), WiFi.RSSI(), parado);
mqtt.publish("robo/telemetria", json);
}
}
Cómo verificar
- ✓La terminal de mosquitto_sub muestra robo/telemetria {"t":12345,"sensor":2048,...} 5 veces por segundo.
- ✓O mosquitto_pub ... -m parar enciende el LED; "andar" lo apaga.
- ✗¿Nunca se conecta? El firewall del PC bloquea el puerto 1883, la IP es incorrecta o el PC y el ESP32 están en redes diferentes (la red de invitados aísla los dispositivos). En un broker público, usa un tópico solo tuyo, como inema/seunombre/telemetria.
Por qué aprender
MQTT es el idioma común de Internet de las cosas: el mismo robot se comunica con un panel, una hoja de cálculo, Home Assistant u otro robot sin cambiar una sola línea. Y es la misma idea de publicar y suscribirse a tópicos que volverás a encontrar en ROS 2, en la Trilha 4.
Conceptos clave
🖥️ Panel en el navegador o en Node-RED/Grafana
Con la telemetría en el broker, cualquier programa puede dibujar el panel. Hay tres opciones, de la más simple a la más completa. Empieza por la primera y pasa a la siguiente solo cuando la necesites.
Un solo archivo, se abre en el navegador y se comunica con el broker por el puerto WebSocket (9001). Es útil para la mesa de trabajo y para mostrarlo en clase.
Arma flujos arrastrando bloques: mqtt in → json → gráfico. Con el paquete de dashboard de FlowFuse, se convierte en un panel con medidores y botones.
Gráficos históricos y de largo plazo. En general, guarda los datos en una base de datos de series temporales (por ejemplo, InfluxDB), alimentada por Node-RED o Telegraf.
Objetivo: una página que muestra el sensor y la señal Wi-Fi en tiempo real, dibuja el gráfico de los últimos 120 valores y tiene botones Parar y Avanzar. Guárdala como painel.html y ábrela en el navegador de la PC donde se ejecuta el broker (o cambia la dirección).
<!doctype html>
<html lang="pt-BR"><head><meta charset="utf-8"><title>Panel del robot</title>
<script src="https://unpkg.com/mqtt@5.10.1/dist/mqtt.min.js"></script></head>
<body style="font-family:sans-serif;background:#111;color:#eee;padding:16px">
<h1>Robot: <span id="estado">desconectado</span></h1>
<p>sensor: <b id="sensor">-</b> · Wi-Fi: <b id="rssi">-</b> dBm · detenido: <b id="parado">-</b></p>
<canvas id="g" width="600" height="200" style="background:#000;max-width:100%"></canvas>
<p><button onclick="cmd('parar')">Detener</button> <button onclick="cmd('andar')">Avanzar</button></p>
<script>
const cli = mqtt.connect('ws://localhost:9001'); // en otro dispositivo: ws://<ip_do_seu_pc>:9001
const pts = [], g = document.getElementById('g').getContext('2d');
const $ = id => document.getElementById(id);
cli.on('connect', () => { $('estado').textContent = 'conectado'; cli.subscribe('robo/telemetria'); });
cli.on('message', (topico, m) => {
const d = JSON.parse(m.toString());
$('sensor').textContent = d.sensor; $('rssi').textContent = d.rssi; $('parado').textContent = d.parado ? 'sí' : 'no';
pts.push(d.sensor); if (pts.length > 120) pts.shift();
g.clearRect(0, 0, 600, 200); g.strokeStyle = '#60a5fa'; g.lineWidth = 2; g.beginPath();
pts.forEach((v, i) => g.lineTo(i * 5, 200 - v / 4095 * 200)); g.stroke();
});
function cmd(c) { cli.publish('robo/comando', c); }
</script></body></html>
Cómo verificar
- ✓El título cambia a «Robot: conectado» y la línea azul se mueve cuando giras el sensor.
- ✓«Parar» enciende el LED del ESP32 y el campo «parado» cambia a «sí».
- ✗¿Sigue «desconectado»? Falta el puerto 9001 con protocol websockets en robo.conf. Abre la consola del navegador (F12) para ver el error.
Paso a paso: el mismo panel en Node-RED
Instala y abre
Con Node.js instalado: npm install -g --unsafe-perm node-red, después node-red. El editor se abre en http://localhost:1880.
Agrega el dashboard
Menu → Manage palette → Install → busca @flowfuse/node-red-dashboard.
Conecta los bloques
mqtt in (servidor localhost:1883, tópico robo/telemetria) → json → change (msg.payload = msg.payload.sensor) → ui-chart. Un ui-button con el payload «parar» conectado a un mqtt out en robo/comando. Haz clic en Deploy y abre /dashboard.
Por qué aprender
Un panel convierte al robot en una caja transparente para quienes lo rodean: todo el grupo ve el error y la batería mientras se mueve. En el aula, esto permite analizar el comportamiento del robot con datos, no con impresiones.
Conceptos clave
🐍 Analizar el registro en Python (pandas + gráfico)
La hoja de cálculo abre el CSV, pero se bloquea con 50 000 filas y no permite comparar dos pruebas con facilidad. pandas lee el archivo en una tabla con una línea de código, calcula promedios e intervalos, y matplotlib dibuja los gráficos. El siguiente script sirve para cualquier registro de este curso: tú indicas el archivo y las columnas.
Cómo leer: sigue los cuadros de izquierda a derecha. El paso del medio es el que más enseña: la diferencia entre un t_ms y el siguiente debería ser siempre de 20 ms; un pico ahí revela cuándo se retrasó el bucle (tarjeta lenta, Wi-Fi, un delay olvidado).
Objetivo: leer el CSV del tema 3, imprimir un resumen y guardar un PNG con las curvas y el intervalo entre filas. Prepara el entorno una vez: python3 -m venv .venv, source .venv/bin/activate (en Windows, .venv\Scripts\activate) e pip install pandas matplotlib.
# analisa_log.py: resumen y gráfico de un registro CSV del robot
# Uso: python analisa_log.py log001.csv [columna1 columna2 ...]
import sys
import pandas as pd
import matplotlib.pyplot as plt
arquivo = sys.argv[1] if len(sys.argv) > 1 else "log001.csv"
df = pd.read_csv(arquivo, comment="#") # las filas con # son anotaciones
colunas = sys.argv[2:] or [c for c in df.columns if c != "t_ms"]
df["t_s"] = (df["t_ms"] - df["t_ms"].iloc[0]) / 1000 # segundos desde el inicio
dt = df["t_ms"].diff() # intervalo entre filas
print(f"{len(df)} filas, {df['t_s'].iloc[-1]:.1f} s registrados")
print(f"intervalo: promedio {dt.mean():.1f} ms, máximo {dt.max():.0f} ms")
print(df[colunas].describe().round(1))
fig, (a1, a2) = plt.subplots(2, 1, sharex=True, figsize=(10, 6))
df.plot(x="t_s", y=colunas, ax=a1, title=arquivo)
a1.set_ylabel("valor"); a1.grid(alpha=0.3)
a2.plot(df["t_s"], dt, color="tab:red")
a2.set_ylabel("intervalo (ms)"); a2.set_xlabel("tiempo (s)"); a2.grid(alpha=0.3)
plt.tight_layout()
plt.savefig(arquivo.replace(".csv", ".png"), dpi=120)
plt.show()
Cómo verificar
- ✓python analisa_log.py log001.csv imprime algo como «1500 filas, 30.0 s registrados» e «intervalo: promedio 20.0 ms».
- ✓Aparece una ventana con dos gráficos y el archivo log001.png queda guardado junto al CSV.
- ✗¿Aparece «externally-managed-environment» en pip? El entorno virtual no está activo: ejecuta activate de nuevo.
✓ Buenas preguntas para el registro
- ✓¿El intervalo se mantuvo constante o tuvo picos?
- ✓¿El error oscila? ¿Cada cuánto tiempo?
- ✓¿El PWM llegó a 255 (se saturó) en algún tramo?
✗ Conclusiones apresuradas
- ✗Comparar dos pruebas con la batería en distintos niveles de carga.
- ✗Cambiar tres parámetros a la vez y no saber cuál ayudó.
- ✗Mirar solo el promedio: oculta el pico que hizo caer al robot.
Por qué aprender
Este script es la herramienta de ajuste del proyecto 2.6: graba una prueba, ejecuta el script, cambia un parámetro, graba de nuevo y compara los PNG. También es la puerta a la Trilha 3, donde Python es el lenguaje principal.
Conceptos clave
✅ Resumen del módulo
Para profundizar
- Serial Plotter en Arduino IDE 2 — formato y opciones
- Biblioteca SD de Arduino-ESP32 — ejemplos oficiales
- PubSubClient — API del cliente MQTT
- Mosquitto — broker, listeners y seguridad
- Node-RED e FlowFuse Dashboard
- Grafana — paneles de series temporales
- pandas: primeros pasos
Siguiente módulo
2.6 · Proyecto: seguidor de línea con PID, ajustado con la telemetría que acabas de preparar.