01 · Alcance
Del curso al robot completo
Hasta ahora cada tema resolvió una pieza: sensores, motores, encoders, odometría, seguimiento de línea, evasión de obstáculos, máquinas de estados, lazos de control. Un proyecto final no suma piezas nuevas, las hace trabajar juntas, y eso exige algo distinto: decidir qué se quiere, calcular si alcanza lo que se tiene, y organizar el trabajo para poder comprobar cada cosa.
El proyecto de estos cuatro temas es un robot diferencial (tema 26) que recorre un circuito marcado con una línea negra, se detiene ante un obstáculo, lo rodea y retoma la línea. Se reparte así:
- Tema 45 (este): requisitos, decisiones de hardware, presupuestos y plan de pruebas.
- Tema 46: construcción, drivers de cada dispositivo y firmware en capas.
- Tema 47: integración: la máquina de estados que une el seguimiento de línea, la evasión y el control PID.
- Tema 48: pruebas de aceptación, calibración, métricas y cierre.
Los números de este tema son cálculos de diseño con supuestos explícitos (masa, corrientes, tiempos). No sustituyen a la medición: el tema 46 y el 48 los verifican sobre el robot real. Si tu hardware es otro, cambiás los supuestos y repetís las cuentas; el procedimiento es lo que vale.
02 · Qué debe cumplir
Requisitos medibles
«El robot debe seguir bien la línea» no es un requisito: nadie podría decir si se cumplió. Uno útil tiene un valor, una condición de prueba y un método de medición. Se agrupan en funcionales (qué hace), de seguridad (qué nunca debe pasar) y de calidad (qué tan bien y por cuánto tiempo):
| Id | Requisito | Verificación |
|---|---|---|
| F1 | Seguir una línea negra de 19 mm sobre fondo blanco con un desvío lateral máximo de 15 mm | Registrar la posición de la línea respecto del arreglo de sensores en todo el circuito |
| F2 | Velocidad nominal en recta de 0,30 m/s (±10 %) | Cronometrar 1,5 m marcados: entre 4,5 y 5,5 s |
| F3 | Detectar un obstáculo a 20 cm y detenerse con al menos 10 cm de margen | Colocar una caja frente al robot y medir la distancia final |
| F4 | Rodear un obstáculo de 10 cm × 10 cm y recuperar la línea en 15 s o menos | Cronometrar desde la detección hasta que vuelve a seguir la línea |
| F5 | Completar un circuito de 6 m con un obstáculo en 45 s o menos | Cronometrar 5 vueltas |
| S1 | Con el botón de parada, los motores llegan a cero en 100 ms o menos | Pulsar en movimiento y medir con la telemetría |
| S2 | Si pierde la línea durante más de 1 s, detenerse y avisar con el LED | Levantar el robot de la pista o interrumpir la línea |
| S3 | Con la batería por debajo de 6,4 V, detenerse y avisar | Alimentar con fuente regulable y bajar la tensión |
| S4 | Al encender, los motores permanecen apagados hasta pulsar el botón | Encender con el robot en la pista |
| C1 | Autonomía de al menos 1 h de funcionamiento continuo | Cálculo de energía y prueba de descarga |
| C2 | Repetibilidad: cinco vueltas consecutivas dentro de ±3 s entre sí | Cronometrar y comparar |
| C3 | Telemetría a 10 Hz sin afectar al control (variación del período de control ≤ 2 ms) | Medir el período real con micros() |
Algunas observaciones sobre cómo se llegó a esos números:
- Los valores se eligen mirando la aplicación y los límites físicos, y se pueden discutir. Lo que no se puede es dejarlos sin valor. F5 se obtiene así: 6 m a 0,30 m/s son 20 s, la maniobra de F4 agrega hasta 15 s, y el resto es margen para curvas más lentas: 35 s de cálculo contra 45 s de requisito.
- Los requisitos de seguridad son pocos y absolutos: se verifican con una prueba de pasa o no pasa, y no se negocian por rendimiento. Los temas 4, 38 y 39 son el respaldo: estado seguro, fallas como estados y enclavamientos.
- S4 parece menor, pero es el que evita que el robot salga corriendo de la mesa al conectar la batería.
- Cada requisito debe poder asociarse a una prueba; si no se te ocurre cómo medirlo, hay que reescribirlo.
| Incluye | No incluye |
|---|---|
| Seguimiento de línea con PID sobre cinco sensores | Mapas, planificación de rutas ni localización global |
| Detección frontal con un sensor ultrasónico | Evasión en entornos desconocidos ni obstáculos móviles |
| Control de velocidad de cada rueda con encoder | Control de trayectoria por odometría pura |
| Máquina de estados con fallas y parada segura | Comunicación inalámbrica ni interfaz gráfica |
| Telemetría por el puerto serie | Almacenamiento de datos en el robot |
03 · Elegir con cuentas
Decisiones de hardware
Cada elección tiene alternativas razonables. Conviene dejar por escrito qué se eligió y por qué: más adelante, cuando algo falla, esa lista dice qué suposición se rompió.
| Elemento | Elección | Por qué (y alternativa) |
|---|---|---|
| Chasis | Dos ruedas motrices y una rueda loca, tracción diferencial (tema 26) | Gira sobre su eje y es simple de modelar. Cuatro ruedas patinan al girar; tres con dirección complican la mecánica |
| Motores | Dos motores de continua con reductor y encoder, 200 rpm en la rueda a 6 V | Alcanzan la velocidad con más del doble de margen en vacío y entregan el par necesario (cuentas abajo) |
| Rueda | Radio de 3,25 cm y separación entre ruedas de 15 cm | Valores del tema 27: una vuelta de rueda son 20,42 cm |
| Línea | Cinco sensores reflectivos con paso de 10 mm, 8 cm por delante del eje | El ancho de la línea (19 mm) supera el paso, así que siempre hay algún sensor sobre ella; la posición se estima por promedio ponderado (tema 31) |
| Obstáculos | Un ultrasónico tipo HC-SR04 al frente | Basta para F3 y F4. Tres sensores (tema 32) darían más información pero no hay pines (sección siguiente) |
| Controlador | Arduino UNO R3 | Es la plataforma del curso. Un Mega tendría más pines y memoria, a costa de tamaño y precio |
| Driver | Puente H doble capaz de 1,2 A continuos por canal o más (tema 12) | El bloqueo de un motor pide 1,2 A; con menos capacidad el driver se calienta o se protege |
| Batería | Dos celdas de ión-litio 18650 en serie (7,4 V), 2 Ah, con protección | Energía suficiente y tensión alta para compensar la caída del driver (tema 4) |
Velocidad y par de los motores
Con la rueda de 3,25 cm de radio, una vuelta avanza 2π · 3,25 = 20,42 cm. Para 0,30 m/s hacen falta 30 / 20,42 = 1,47 vueltas por segundo, es decir, 88 rpm en la rueda. Un motor de 200 rpm en vacío a 6 V trabaja al 44 % de su velocidad máxima; como el driver y la carga restan tensión y velocidad, el margen real es algo menor, pero hay holgura para corregir en las curvas sin saturar.
Para el par se parte de una masa de 0,8 kg, que debe llegar a 0,30 m/s en 0,5 s (aceleración de 0,6 m/s²), con un coeficiente de rodadura de 0,03:
- Fuerza para acelerar:
0,8 · 0,6 = 0,48 N. - Fuerza de rodadura:
0,03 · 0,8 · 9,81 = 0,235 N. - Total: 0,715 N, a un radio de 0,0325 m:
0,0233 N·mentre las dos ruedas, o0,0116 N·m(0,12 kgf·cm) por motor. - Con un factor de seguridad 3 (arranques, rampas leves, suciedad, baterías bajas): 0,36 kgf·cm por motor, de forma continua.
Es un par modesto, que un motorreductor pequeño supera. El cálculo importa por el método: si sumás una carga, repetís la cuenta y ves si el motor sigue alcanzando. El factor 3 también cubre algo que la cuenta ignora, que es la tensión que se pierde en el driver.
Encoders y resolución
Con 1440 cuentas por vuelta de rueda (tema 27), cada cuenta equivale a 0,0142 cm. A 30 cm/s cada rueda genera unas 2116 cuentas por segundo (4231 entre las dos). Si cada interrupción cuesta unos 5 µs, las interrupciones consumen 4231 · 5 µs = 2,1 % del tiempo de la CPU: viable. En una ventana de 20 ms, una cuenta equivale a 0,71 cm/s, el 2,4 % de la velocidad nominal: suficiente para un lazo de velocidad (tema 18, donde el encoder de 48 cuentas era inservible por el mismo cálculo).
04 · Que no se apague
Presupuesto de energía
La batería tiene que dar la corriente promedio durante la prueba más larga y la corriente pico en el peor momento. Son dos cuentas distintas, y las dos pueden hacer fallar el robot.
| Consumidor | Promedio | Pico |
|---|---|---|
| Dos motores (0,30 A cada uno en marcha; 1,2 A con el eje bloqueado) | 0,60 A | 2,40 A |
| Arduino UNO | 0,05 A | 0,05 A |
| Cinco sensores de línea (LED infrarrojo de unos 20 mA) | 0,10 A | 0,10 A |
| Ultrasónico | 0,015 A | 0,015 A |
| Dos encoders y LED de estado | 0,025 A | 0,025 A |
| Total | 0,79 A | 2,59 A |
La lógica (todo menos los motores) suma 0,05 + 0,10 + 0,015 + 0,025 = 0,19 A.
- Autonomía. De una batería de 2 Ah se aprovecha el 80 % (descargarla del todo la daña):
2,0 · 0,8 / 0,79 = 2,03 h. El requisito C1 de 1 h se cumple con un factor 2. Con una batería de 1 Ah, la autonomía sería de 1,01 h: el requisito se cumpliría apenas, y con una batería usada, no. - Pico. Con ambos motores trabados, la batería debe entregar 2,59 A y cada canal del driver 1,2 A. Una celda 18650 sin problemas lo da; un driver de 1 A por canal, no.
- Regulador del UNO. Alimentando el UNO desde la entrada VIN con 7,4 V, su regulador lineal disipa
(7,4 − 5) · 0,19 = 0,456 W. Es tolerable, pero sin disipador buena parte del calor se acumula en la placa; con más sensores o con una batería de 12 V se vuelve un problema. Una alternativa es un regulador conmutado de 5 V. - Caída del driver. Un puente H bipolar puede restar 2 V: con 7,4 V, los motores ven unos 5,4 V. El diseño de velocidad (200 rpm a 6 V) ya lo considera.
Vigilar la batería
El requisito S3 exige medir la tensión. La batería llega hasta 8,4 V (4,2 V por celda) y el ADC mide hasta 5 V, así que se usa un divisor de dos resistencias de 10 kΩ, que reparte por la mitad. Consume 0,42 mA y su impedancia de salida (5 kΩ) está dentro de lo que el ADC admite (hasta 10 kΩ). Las lecturas esperables:
| Batería | Tensión en el pin | Lectura |
|---|---|---|
| 8,4 V (llena) | 4,2 V | 859 |
| 7,4 V (nominal) | 3,7 V | 757 |
| 6,4 V (umbral de parada) | 3,2 V | 655 |
Cada cuenta del ADC representa 9,8 mV de la batería. El umbral de 6,4 V (3,2 V por celda) deja margen sobre los 3,0 V por celda a los que la protección corta. Como la tensión baja un poco cuando los motores arrancan, la lectura se promedia en el tiempo y el robot se detiene solo si la tensión se mantiene baja durante un segundo, no por un valle de un instante.
05 · Hacer caber todo
Presupuesto de pines
El UNO R3 tiene 20 pines de entrada y salida, pero D0 y D1 son el puerto serie (telemetría y programación), así que quedan 18 utilizables: D2 a D13 y A0 a A5 (los analógicos también funcionan como digitales). Hay que repartirlos con criterio, porque algunas funciones exigen pines concretos:
- Las interrupciones externas del UNO están sólo en D2 y D3: ahí van los canales A de los encoders.
- El PWM está en D3, D5, D6, D9, D10 y D11. Los motores usan D5 y D6.
- Los sensores de línea necesitan entradas analógicas (A0 a A4) y la batería, otra (A5).
| Pin | Función | Motivo |
|---|---|---|
| D2, D3 | Encoders, canales A (izquierdo, derecho) | Interrupciones externas INT0 e INT1 |
| D4, D9 | Encoders, canales B (izquierdo, derecho) | Entradas digitales comunes: se lee su estado en la interrupción |
| D5, D6 | PWM de los motores (izquierdo, derecho) | Pines con PWM |
| D7, D8 | Sentido de los motores (izquierdo, derecho) | Salidas digitales |
| D10, D11 | TRIG y ECHO del ultrasónico | Salida y entrada digitales |
| D12 | Botón de arranque y parada | Entrada con resistencia interna de pull-up |
| D13 | LED de estado | El LED incorporado de la placa |
| A0 a A4 | Cinco sensores de línea | Entradas analógicas |
| A5 | Tensión de la batería (divisor) | Entrada analógica |
Cuenta total: 4 (motores) + 4 (encoders) + 5 (línea) + 2 (ultrasónico) + 1 (batería) + 1 (botón) + 1 (LED) = 18 de 18. No sobra ninguno. Eso condiciona decisiones futuras:
- Un segundo ultrasónico, o los tres del tema 32, no caben: harían falta 2 a 6 pines más.
- Para ganar pines se puede pasar a un Mega, leer los encoders con un solo canal (se pierde el sentido de giro, que se infiere de la orden al motor), reducir el arreglo de línea a tres sensores o usar un expansor de entradas y salidas por I²C.
- Ninguna de esas opciones es gratis: por eso conviene hacer esta cuenta antes de comprar y cablear.
En el esquema, la batería llega al driver sin pasar por el Arduino: la corriente de los motores nunca debe circular por la placa (tema 4). Las masas de todo se unen en un punto, y cerca de los motores va un capacitor para amortiguar los picos de arranque (tema 12).
06 · Capas, tiempos y estados
Arquitectura de software y de tiempo
El firmware se organiza en capas, cada una con una responsabilidad y una interfaz pequeña, como en el brazo del tema 24. De abajo hacia arriba:
- Drivers de dispositivo: leer la línea, medir distancia, contar pulsos de cada encoder, medir la batería, mover un motor con un valor entre −1 y 1. Nada más.
- Percepción: convierte lecturas en magnitudes útiles: posición de la línea respecto del centro, velocidad de cada rueda, distancia filtrada, pose por odometría (tema 27).
- Control: el lazo externo de línea (el error de posición da una diferencia de velocidades) y los lazos internos de velocidad de cada rueda, con el PID del tema 43.
- Comportamientos: seguir la línea, evitar un obstáculo, buscar la línea; los temas 33 y 38.
- Supervisión y estados: la máquina de estados que decide qué comportamiento actúa y vigila las condiciones de seguridad (tema 39).
Una regla: cada capa sólo usa la de abajo y nunca la de arriba. Con eso, los drivers se prueban sin control, el control se prueba con velocidades simuladas, y la máquina de estados puede probarse en Python con las mismas reglas.
Los estados
Las transiciones cubren los requisitos: S1 a S4 son entradas a FALLA o la condición de salir de ESPERA; F3 y F4 son SEGUIR_LINEA → EVITAR → BUSCAR_LINEA. El tema 47 los desarrolla con subestados (cuándo girar, cuándo avanzar de costado, cuándo mirar la línea).
El presupuesto de tiempo
El microcontrolador tiene un solo núcleo y 16 MHz. Antes de programar conviene sumar cuánto tiempo necesita cada tarea periódica y verificar que no haya sobrecarga. Estos son los supuestos de diseño (a medir en el tema 46):
| Tarea | Período | Costo por ejecución | Carga |
|---|---|---|---|
| Interrupciones de encoders | continuo (4231 por segundo) | 5 µs cada una | 2,1 % |
Control y línea (cinco analogRead de unos 112 µs, cuentas y PWM) | 20 ms | 1,46 ms | 7,3 % |
Ultrasónico (pulseIn con tiempo máximo de 6 ms) | 60 ms | 6,1 ms en el peor caso | 10,2 % |
| Telemetría (40 bytes a 115200 baudios) | 100 ms | 0,4 ms | 0,4 % |
| Medición de la batería | 1 s | 0,2 ms | 0,0 % |
| Total | 20,0 % |
La carga total es de un 20 %: alcanza con holgura. Pero hay un problema escondido en la tabla. La lectura del ultrasónico bloquea durante hasta 6 ms, y el período de control es de 20 ms: si coinciden, el lazo se atrasa un 30 % de su período y la variación de tiempo (jitter) rompe el requisito C3. Dos soluciones: ejecutar el ultrasónico justo después del control y no antes, o medirlo con interrupciones sin bloquear (tema 37). Si en cambio se lo mide cada 30 ms para detectar antes, su carga sube al 20,3 % y el total a casi el 30 %.
La velocidad a la que se actualiza el control también sale de una cuenta. A 0,30 m/s, en 20 ms el robot avanza 6 mm, una fracción del ancho de la línea (19 mm): cada ciclo ve una situación casi igual a la anterior, que es lo que necesita un lazo estable (tema 43).
Cuánto cuesta no ver a tiempo
Del requisito F3 sale un cálculo de seguridad. Si el sensor mide cada 60 ms y el procesamiento tarda 10 ms más, un obstáculo puede aparecer justo después de una medición y detectarse 70 ms tarde: a 0,30 m/s son 0,30 · 0,070 = 2,1 cm. Para detener el robot con una desaceleración de 1,0 m/s² hacen falta 0,30² / (2 · 1,0) = 4,5 cm. En total, 6,6 cm: detectando a 20 cm quedan 13,4 cm de margen contra los 10 cm exigidos. Si el robot fuera al doble de velocidad, el frenado se cuadruplica (18 cm) y no alcanzaría. El laboratorio 2 permite explorar este compromiso.
07 · Cómo se va a comprobar
Plan de pruebas y riesgos
El plan de pruebas se escribe ahora, no al final. Las pruebas se organizan en tres niveles, que se corresponden con los temas siguientes:
| Nivel | Qué se prueba | Cuándo | Ejemplos |
|---|---|---|---|
| Módulo | Cada driver y cada función, aislados | Tema 46 | Sentido y signo de cada encoder; lectura de la batería contra un tester; rango del ultrasónico; el motor gira al ordenar +0,5 y −0,5 |
| Integración | Varias capas juntas, con el robot levantado o en pista corta | Tema 47 | El lazo de velocidad sigue una consigna; el robot sigue una recta y una curva; la máquina de estados pasa por todos los estados |
| Aceptación | Los requisitos F, S y C sobre el robot completo | Tema 48 | Las cinco vueltas del circuito; el botón de parada; la batería baja |
| Prueba | Requisito | Procedimiento | Criterio de aprobación |
|---|---|---|---|
| P1 | F2 | Recta de 1,5 m con marcas; cronometrar 5 pasadas | Todas entre 4,5 y 5,5 s |
| P2 | F1 | Circuito completo con la telemetría de la posición de la línea | Desvío máximo ≤ 15 mm |
| P3 | F3 | Caja de 10 × 10 cm en la recta; tres posiciones distintas | Distancia final ≥ 10 cm en las tres |
| P4 | F4, F5 | Circuito con caja; cronometrar 5 vueltas | Maniobra ≤ 15 s; vuelta ≤ 45 s; dispersión ±3 s |
| P5 | S1 | Pulsar el botón en marcha; medir en la telemetría | Motores en cero en ≤ 100 ms |
| P6 | S2 | Levantar el robot de la línea o cortarla | Detención en ≈ 1 s y LED de falla |
| P7 | S3, C1 | Fuente regulable bajando de 7,4 a 6,0 V; descarga completa en otra prueba | Parada al cruzar 6,4 V; autonomía ≥ 1 h |
| P8 | S4 | Conectar la batería con el robot en la pista | No se mueve hasta pulsar |
| P9 | C3 | Medir con micros() el período de control durante 1 minuto | Variación ≤ 2 ms |
Riesgos del diseño
| Riesgo | Efecto | Prevención |
|---|---|---|
| Caída de tensión al arrancar los motores | El Arduino se reinicia | Capacitor cerca del driver; cables gruesos; batería con corriente de sobra; no alimentar la lógica con la misma pista de los motores |
| Luz ambiente sobre los sensores infrarrojos | La línea parece estar en otro lugar | Calibrar blanco y negro en el lugar de uso (tema 30); proteger los sensores con una carcasa |
| Ecos falsos del ultrasónico (paredes, mesa) | Detenciones sin obstáculo | Filtrar con la mediana de varias lecturas; exigir dos lecturas consecutivas |
| Ruido en los encoders | Velocidades erráticas | Cables cortos y trenzados; verificar con osciloscopio o contador |
| Deslizamiento de ruedas | La odometría y la velocidad no concuerdan con el avance real | Ruedas con buen agarre; aceleraciones suaves; no confiar sólo en la odometría |
| Retardo y ruido en el lazo de línea | Oscilaciones sobre la línea | Ajustar el PID y filtrar (temas 43 y 44) |
| Pines agotados | No se puede agregar un sensor | Revisar el presupuesto antes de comprar; considerar un Mega |
08 · Código para la placa
Autodiagnóstico en Arduino
El primer programa del proyecto no mueve nada: confirma el cableado. Define el mapa de pines de la sección anterior en un solo lugar y muestra por el puerto serie las lecturas de todos los dispositivos. Los motores quedan apagados. Con esto se detectan cruces de cables, sensores invertidos y una batería baja antes de que algo se mueva.
// Autodiagnóstico del robot: lee todos los sensores y los muestra por serie cada 500 ms. Los motores permanecen apagados.
// Mapa de pines (el mismo para todo el proyecto).
const byte PIN_ENC_IZQ_A = 2, PIN_ENC_DER_A = 3, PIN_ENC_IZQ_B = 4, PIN_ENC_DER_B = 9;
const byte PIN_PWM_IZQ = 5, PIN_PWM_DER = 6, PIN_DIR_IZQ = 7, PIN_DIR_DER = 8;
const byte PIN_TRIG = 10, PIN_ECHO = 11, PIN_BOTON = 12, PIN_LED = 13;
const byte PIN_LINEA[5] = {A0, A1, A2, A3, A4};
const byte PIN_BATERIA = A5;
const float VREF = 5.0, DIVISOR = 2.0; // divisor 10 kΩ + 10 kΩ
const float V_BAT_MIN = 6.4; // 3,2 V por celda
volatile long cuentasIzq = 0, cuentasDer = 0;
// Se cuenta en el flanco ascendente del canal A; el canal B indica el sentido.
// Si al empujar la rueda hacia delante la cuenta disminuye, invertí el signo de esa rueda.
void isrIzq() { cuentasIzq += digitalRead(PIN_ENC_IZQ_B) ? -1 : 1; }
void isrDer() { cuentasDer += digitalRead(PIN_ENC_DER_B) ? 1 : -1; }
float leerBateria() {
return analogRead(PIN_BATERIA) * VREF / 1023.0 * DIVISOR;
}
float leerDistanciaCm() {
digitalWrite(PIN_TRIG, LOW); delayMicroseconds(2);
digitalWrite(PIN_TRIG, HIGH); delayMicroseconds(10);
digitalWrite(PIN_TRIG, LOW);
unsigned long us = pulseIn(PIN_ECHO, HIGH, 6000UL); // espera como máximo 6 ms: unos 100 cm
return us == 0 ? -1.0 : us * 0.01715; // 343 m/s, ida y vuelta: 0,01715 cm/µs
}
void setup() {
Serial.begin(115200);
pinMode(PIN_LED, OUTPUT);
pinMode(PIN_BOTON, INPUT_PULLUP);
pinMode(PIN_TRIG, OUTPUT);
pinMode(PIN_ECHO, INPUT);
pinMode(PIN_ENC_IZQ_A, INPUT_PULLUP); pinMode(PIN_ENC_IZQ_B, INPUT_PULLUP);
pinMode(PIN_ENC_DER_A, INPUT_PULLUP); pinMode(PIN_ENC_DER_B, INPUT_PULLUP);
pinMode(PIN_PWM_IZQ, OUTPUT); pinMode(PIN_DIR_IZQ, OUTPUT);
pinMode(PIN_PWM_DER, OUTPUT); pinMode(PIN_DIR_DER, OUTPUT);
digitalWrite(PIN_PWM_IZQ, LOW); digitalWrite(PIN_PWM_DER, LOW); // motores apagados (requisito S4)
attachInterrupt(digitalPinToInterrupt(PIN_ENC_IZQ_A), isrIzq, RISING);
attachInterrupt(digitalPinToInterrupt(PIN_ENC_DER_A), isrDer, RISING);
}
void loop() {
static unsigned long ultimo = 0;
if (millis() - ultimo >= 500) {
ultimo += 500;
noInterrupts(); // copia atómica de los contadores
long ci = cuentasIzq, cd = cuentasDer;
interrupts();
float vbat = leerBateria();
Serial.print("bat="); Serial.print(vbat, 2); Serial.print(" V linea=[");
for (byte i = 0; i < 5; i++) {
Serial.print(analogRead(PIN_LINEA[i]));
Serial.print(i < 4 ? " " : "]");
}
Serial.print(" dist="); Serial.print(leerDistanciaCm(), 1); Serial.print(" cm");
Serial.print(" enc=("); Serial.print(ci); Serial.print(", "); Serial.print(cd); Serial.print(")");
Serial.print(" boton="); Serial.println(digitalRead(PIN_BOTON) == LOW ? 1 : 0);
digitalWrite(PIN_LED, vbat >= V_BAT_MIN ? HIGH : LOW); // LED fijo si la batería está bien
}
}
Un ejemplo de salida (los valores dependen del robot y de la luz):
bat=7.41 V linea=[812 790 120 805 820] dist=23.4 cm enc=(0, 0) boton=0
Cómo se lee. La batería debe coincidir con la de un tester; cuando baja de 6,4 V, el LED se apaga. Con la línea de 19 mm centrada debe verse el valor bajo en el sensor del medio (el que está sobre lo negro, aquí el tercero) y altos en el resto; si los números van al revés, el sensor ve el negro como alto. El ultrasónico debe responder entre unos 2 y 100 cm, con −1,0 si no hay eco. Al empujar cada rueda hacia delante, sus cuentas deben aumentar; si disminuyen, se invierte el signo. Y mientras se haga esto, PWM está en 0 y el robot no se mueve.
Dos notas de diseño. El mapa de pines está en una sola sección: si hay que cambiar un pin, se toca un lugar. Y la lectura de los contadores se hace con las interrupciones desactivadas un instante, porque un long de 4 bytes se escribe en más de una instrucción y podría leerse a medias. El tema 46 convierte estas funciones en los drivers del robot.
09 · Exploración interactiva
Laboratorios de diseño
1. Energía: autonomía, pico y regulador
Cambiá la capacidad de la batería y los consumos. La batería es de 7,4 V; se aprovecha el 80 % de su capacidad, y el pico supone los dos motores bloqueados con 1,2 A cada uno.
Mové los controles.
Probá con los valores de la tabla: 2,0 Ah, 0,30 A y 0,19 A dan 2,03 h. Después poné 1,0 Ah (1,01 h) y mirá cómo cambia el veredicto. Subí la corriente de cada motor a 0,60 A, como si el robot cargara más peso o las ruedas rozaran: la autonomía cae por debajo de 1,3 h con la misma batería.
2. Detección y frenado
Del requisito F3: el robot detecta un obstáculo a la distancia elegida y debe quedar detenido con 10 cm o más de margen. El tiempo de procesamiento es de 10 ms. El tiempo de reacción supone que el obstáculo aparece justo después de una medición.
Mové los controles.
Con los valores iniciales el recorrido es de 6,6 cm y el margen de 13,4 cm. Subí la velocidad a 0,60 m/s: el recorrido es de 22,2 cm y el robot choca, porque el frenado se cuadruplica. Para que vuelva a cumplir, compará las tres salidas: detectar a 40 cm, frenar a 3 m/s², o bajar la velocidad. Cada una cuesta algo distinto (alcance del sensor, tracción de las ruedas, tiempo de vuelta).
10 · Código ejecutable
Las cuentas del diseño en Python
Todas las cuentas del tema reunidas en un programa: tracción, encoders, energía, frenado, presupuesto de CPU, pines y divisor de batería. Cuando cambies un supuesto, sirve para recalcular el diseño completo y ver qué requisito deja de cumplirse.
Probalo en el navegador. Python se descarga con Pyodide la primera vez y requiere conexión. El código es editable y no controla ningún equipo real.
from math import pi
# Supuestos del robot (ajustalos a los tuyos)
R, B, CPR = 3.25, 15.0, 1440 # radio de rueda y separación entre ruedas (cm); cuentas por vuelta de rueda
MASA, C_ROD, G = 0.8, 0.03, 9.81 # kg, coeficiente de rodadura, m/s²
V_OBJ, T_ACEL = 0.30, 0.5 # velocidad objetivo (m/s) y tiempo para alcanzarla (s)
KGF_CM = 0.0980665 # N·m por kgf·cm
def traccion():
"""Vueltas por segundo de la rueda y par por motor (kgf·cm) para la velocidad objetivo."""
rps = V_OBJ / (2 * pi * R / 100)
fuerza = MASA * V_OBJ / T_ACEL + C_ROD * MASA * G # acelerar + rodadura (N)
par = fuerza * (R / 100) / 2 # N·m por motor
return rps, par / KGF_CM
def encoders(rps):
"""Cuentas por segundo por rueda, carga de CPU de las interrupciones y resolución de velocidad en 20 ms."""
cuentas = rps * CPR
carga = 2 * cuentas * 5e-6 * 100 # 5 µs por interrupción, dos ruedas
resolucion = 2 * pi * R / (CPR * 0.02) # cm/s que vale una cuenta en 20 ms
return cuentas, carga, resolucion
def autonomia(cap_ah, i_motor, i_logica, usable=0.8):
media = 2 * i_motor + i_logica
return cap_ah * usable / media, media
def frenado(v, a, t_sensor, t_proc, d_det):
"""Distancias en cm: reacción, frenado, total y margen restante."""
reaccion = v * (t_sensor + t_proc) * 100
freno = v * v / (2 * a) * 100
return reaccion, freno, reaccion + freno, d_det - (reaccion + freno)
if __name__ == "__main__":
rps, par = traccion()
print(f"Tracción a {V_OBJ * 100:.0f} cm/s: {rps:.2f} vueltas/s ({rps * 60:.1f} rpm), "
f"par por motor {par:.2f} kgf·cm, con factor 3: {par * 3:.2f} kgf·cm")
cuentas, carga, res = encoders(rps)
print(f"Encoders: {cuentas:.0f} cuentas/s por rueda, interrupciones al {carga:.1f} % de CPU, "
f"resolución de velocidad {res:.2f} cm/s ({res / (V_OBJ * 100) * 100:.1f} % de la velocidad)")
print("\nEnergía (motores 0,30 A cada uno, lógica 0,19 A):")
for cap in (2.0, 1.0):
horas, media = autonomia(cap, 0.30, 0.19)
print(f" batería {cap:.1f} Ah: corriente media {media:.2f} A, autonomía {horas:.2f} h")
print(f" pico con motores bloqueados: {2 * 1.2 + 0.19:.2f} A; el regulador del UNO disipa {(7.4 - 5) * 0.19:.3f} W")
print("\nFrenado (a = 1,0 m/s², sensor cada 60 ms, procesamiento de 10 ms, detección a 20 cm):")
for v in (0.30, 0.60):
r, f, t, margen = frenado(v, 1.0, 0.060, 0.010, 20.0)
print(f" {v:.2f} m/s: reacción {r:.1f} cm + freno {f:.1f} cm = {t:.1f} cm; "
f"margen {margen:.1f} cm {'(cumple)' if margen >= 10 else '(NO cumple)'}")
print("\nPresupuesto de CPU:")
tareas = [("interrupciones de encoders", 2 * cuentas * 0.005, 1000.0), # ms de CPU por segundo
("control y línea", 1.46, 20.0), ("ultrasonido (peor caso)", 6.1, 60.0),
("telemetría", 0.4, 100.0), ("batería", 0.2, 1000.0)]
total = 0.0
for nombre, costo, periodo in tareas:
porcentaje = costo / periodo * 100
total += porcentaje
print(f" {nombre:<28} {porcentaje:5.1f} %")
print(f" {'total':<28} {total:5.1f} %")
pines = {"motores": 4, "encoders": 4, "línea": 5, "ultrasonido": 2, "batería": 1, "botón": 1, "LED": 1}
print(f"\nPines usados: {sum(pines.values())} de 18 disponibles")
print("Lectura del ADC con el divisor 10k + 10k:")
for vb in (8.4, 7.4, 6.4):
print(f" {vb:.1f} V -> {round(vb * 0.5 / 5 * 1023)}")La salida repite las cuentas del texto: 88,1 rpm y 0,36 kgf·cm con factor 3, 2116 cuentas por segundo, 2,03 h de autonomía con 2 Ah, un margen de frenado de 13,4 cm a 0,30 m/s y de −2,2 cm a 0,60 m/s, un 20,0 % de CPU y 18 pines de 18.
Probá cambiar MASA a 1,2 y ver el par (0,53 kgf·cm con el factor 3), o poner 0,45 en la lista de velocidades del frenado para ver cuándo el margen deja de cumplir.
11 · Antes de construir
Errores frecuentes de diseño
- Empezar a cablear sin haber escrito los requisitos: es imposible saber cuándo se terminó ni qué probar.
- Requisitos sin número («rápido», «suave», «seguro»). Se reescriben con una medida y un método.
- Alimentar los motores y la lógica de la misma fuente sin desacoplo: el primer arranque reinicia la placa.
- No contar los pines antes de comprar sensores; el último dispositivo no entra.
- Confundir la corriente media con la pico: la batería y el driver se dimensionan por el pico; la autonomía, por la media.
- Medir el ultrasónico con un bloqueo largo en medio del lazo de control: el período de control deja de ser constante.
- Dejar la seguridad para el final: los estados de falla y el botón de parada se diseñan junto con el movimiento.
«Los requisitos se escriben después, cuando el robot ya funciona»
Después sólo se pueden escribir los que ya se cumplen. Los requisitos sirven para decidir, y eso se hace antes de construir.
«Con un motor más potente me quedo tranquilo»
Más potencia significa más corriente pico, más peso y más consumo, y puede dejar al driver o a la batería fuera de rango. Se elige el que el cálculo justifica, con un margen.
«La autonomía es capacidad dividida corriente»
Falta el aprovechable (cerca del 80 %), el envejecimiento de la batería y que la corriente real supera a la estimada. Se deja un factor de 2.
«El presupuesto de CPU del 20 % me deja tranquilo»
El promedio no dice nada de los bloqueos: una tarea de 6 ms dentro de un lazo de 20 ms destruye la regularidad aunque el total sea bajo.
«Un obstáculo detectado a 20 cm es seguro»
Depende de la velocidad, la desaceleración y el período de medición. La seguridad se verifica con el recorrido total, no con la distancia de detección sola.
«Las pruebas son lo último»
El plan se escribe ahora. Cada módulo se prueba al terminarlo; el robot completo se prueba contra requisitos que ya estaban escritos.
12 · Práctica guiada
Predecí antes de comprobar
1. Una batería más chica
Se dispone de una batería de 1,5 Ah. Con las corrientes del tema, ¿cuánto dura el robot? ¿Cumple C1?
Ver solución
1,5 · 0,8 / 0,79 = 1,52 h. Cumple el requisito de 1 h, con un margen del 52 %. Con menos del 25 % de margen convendría pensar en más capacidad, porque el consumo real suele superar al estimado.
2. Velocidad mayor
¿Cumple F3 si el robot va a 0,45 m/s, con el resto igual (1,0 m/s², sensor cada 60 ms, detección a 20 cm)? ¿Qué cambiarías?
Ver solución
Reacción: 0,45 · 0,070 = 3,15 cm. Frenado: 0,45² / 2 = 10,1 cm. Total: 13,3 cm, margen de 6,7 cm: no cumple. Con detección a 30 cm el margen sería de 16,7 cm; con una desaceleración de 2 m/s², el frenado baja a 5,1 cm y el margen a 11,8 cm; reducir el período del sensor a 30 ms no alcanza (margen de 8,1 cm).
3. Un segundo ultrasónico
Se quiere agregar un ultrasónico a la izquierda. ¿Cuántos pines faltan y qué alternativas hay?
Ver solución
Se necesitan 2 pines (TRIG y ECHO) y no queda ninguno: se pasaría a 20 de 18. Alternativas: usar un Mega, compartir el TRIG entre los dos sensores (se mide uno por vez) y ahorrar un pin, leer los encoders con un solo canal (ahorra 2) o reducir la línea a tres sensores (ahorra 2).
4. Umbral de la batería
¿Qué lectura del ADC corresponde a 6,4 V con el divisor de 10 kΩ + 10 kΩ? ¿Qué cambio de tensión representa una cuenta?
Ver solución
El pin recibe 3,2 V: 3,2 / 5 · 1023 = 654,7, es decir, 655. Una cuenta son 5 / 1023 · 2 = 9,8 mV de la batería.
5. Ultrasónico más frecuente
Se mide el ultrasónico cada 30 ms para detectar antes. ¿Cómo queda el presupuesto de CPU?
Ver solución
Su carga pasa de 10,2 % a 6,1 / 30 = 20,3 %; el total sube de 20,0 % a unos 30 %. Todavía hay holgura de CPU, pero un bloqueo de 6 ms cada 30 ms perjudica más a un lazo de 20 ms: conviene medirlo con interrupciones.
6. Más masa
El robot termina pesando 1,2 kg. ¿Qué par necesita cada motor con el factor 3?
Ver solución
Fuerza: 1,2 · 0,6 + 0,03 · 1,2 · 9,81 = 0,72 + 0,353 = 1,073 N. Par por motor: 1,073 · 0,0325 / 2 = 0,0174 N·m = 0,18 kgf·cm. Con el factor 3: 0,53 kgf·cm.
7. Reescribir un requisito
«El robot debe esquivar bien el obstáculo.» Reescribilo como un requisito verificable.
Ver solución
Por ejemplo: «El robot rodea una caja de 10 × 10 cm ubicada sobre la línea, sin tocarla, y vuelve a seguir la línea en 15 s o menos contado desde la detección». Se verifica con un cronómetro en cinco intentos. Es el requisito F4.
13 · Síntesis
Ideas para recordar
- Un proyecto se define por sus requisitos: cada uno con un valor, una condición de prueba y un método de medición.
- Los de seguridad son pocos y absolutos, y se diseñan junto con el movimiento: botón de parada, pérdida de línea, batería baja y arranque seguro.
- Las decisiones de hardware se justifican con cuentas: velocidad de la rueda, par con factor de seguridad, resolución del encoder y carga de las interrupciones.
- La batería se dimensiona por dos corrientes: la media determina la autonomía y la pico debe soportarla la batería y el driver.
- Los pines son un recurso finito: 18 de 18 en el UNO. Contar antes de comprar evita rediseñar después.
- El software se organiza en capas (drivers, percepción, control, comportamientos, estados) donde cada una usa solo la de abajo.
- El presupuesto de CPU debe considerar los bloqueos, no sólo el promedio; una tarea bloqueante rompe la regularidad del lazo de control.
- La distancia de seguridad es reacción más frenado, y el frenado crece con el cuadrado de la velocidad.
- El plan de pruebas se escribe al diseñar: módulos, integración y aceptación, cada requisito con su prueba.
¿Listo para el tema 46?
Antes de avanzar, reemplazá los supuestos de este tema por los de tu robot (masa, radio de rueda, baterías, motores) y rehacé las cuentas con el programa de Python. Anotá qué requisito está más ajustado y qué medirías primero para confirmarlo. Armá el robot sin conectar los motores y ejecutá el autodiagnóstico.
En el próximo tema construimos el robot y su firmware modular: montaje, drivers de sensores y motores, y las capas de código que acabamos de definir.