← Índice del curso

Robótica y Automatización · Tema 45

Proyecto: requisitos y diseño del sistema robótico

Empieza la etapa final del curso: un robot completo que sigue una línea, evita un obstáculo y vuelve al recorrido. Antes de armar nada se decide qué tiene que cumplir, se hacen las cuentas de energía, pines y tiempo, y se define cómo se va a probar. Un buen diseño en papel evita la mayoría de los problemas que aparecen después en la mesa.

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):

Requisitos del robot: funcionales (F), de seguridad (S) y de calidad (C).
IdRequisitoVerificación
F1Seguir una línea negra de 19 mm sobre fondo blanco con un desvío lateral máximo de 15 mmRegistrar la posición de la línea respecto del arreglo de sensores en todo el circuito
F2Velocidad nominal en recta de 0,30 m/s (±10 %)Cronometrar 1,5 m marcados: entre 4,5 y 5,5 s
F3Detectar un obstáculo a 20 cm y detenerse con al menos 10 cm de margenColocar una caja frente al robot y medir la distancia final
F4Rodear un obstáculo de 10 cm × 10 cm y recuperar la línea en 15 s o menosCronometrar desde la detección hasta que vuelve a seguir la línea
F5Completar un circuito de 6 m con un obstáculo en 45 s o menosCronometrar 5 vueltas
S1Con el botón de parada, los motores llegan a cero en 100 ms o menosPulsar en movimiento y medir con la telemetría
S2Si pierde la línea durante más de 1 s, detenerse y avisar con el LEDLevantar el robot de la pista o interrumpir la línea
S3Con la batería por debajo de 6,4 V, detenerse y avisarAlimentar con fuente regulable y bajar la tensión
S4Al encender, los motores permanecen apagados hasta pulsar el botónEncender con el robot en la pista
C1Autonomía de al menos 1 h de funcionamiento continuoCálculo de energía y prueba de descarga
C2Repetibilidad: cinco vueltas consecutivas dentro de ±3 s entre síCronometrar y comparar
C3Telemetrí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.
Qué incluye el proyecto y qué no.
IncluyeNo incluye
Seguimiento de línea con PID sobre cinco sensoresMapas, planificación de rutas ni localización global
Detección frontal con un sensor ultrasónicoEvasión en entornos desconocidos ni obstáculos móviles
Control de velocidad de cada rueda con encoderControl de trayectoria por odometría pura
Máquina de estados con fallas y parada seguraComunicación inalámbrica ni interfaz gráfica
Telemetría por el puerto serieAlmacenamiento 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ó.

Decisiones de diseño y su justificación.
ElementoElecciónPor qué (y alternativa)
ChasisDos 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
MotoresDos motores de continua con reductor y encoder, 200 rpm en la rueda a 6 VAlcanzan la velocidad con más del doble de margen en vacío y entregan el par necesario (cuentas abajo)
RuedaRadio de 3,25 cm y separación entre ruedas de 15 cmValores del tema 27: una vuelta de rueda son 20,42 cm
LíneaCinco sensores reflectivos con paso de 10 mm, 8 cm por delante del ejeEl 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áculosUn ultrasónico tipo HC-SR04 al frenteBasta para F3 y F4. Tres sensores (tema 32) darían más información pero no hay pines (sección siguiente)
ControladorArduino UNO R3Es la plataforma del curso. Un Mega tendría más pines y memoria, a costa de tamaño y precio
DriverPuente 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íaDos celdas de ión-litio 18650 en serie (7,4 V), 2 Ah, con protecciónEnergí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·m entre las dos ruedas, o 0,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.

Consumos supuestos (verificar con un amperímetro en el tema 46).
ConsumidorPromedioPico
Dos motores (0,30 A cada uno en marcha; 1,2 A con el eje bloqueado)0,60 A2,40 A
Arduino UNO0,05 A0,05 A
Cinco sensores de línea (LED infrarrojo de unos 20 mA)0,10 A0,10 A
Ultrasónico0,015 A0,015 A
Dos encoders y LED de estado0,025 A0,025 A
Total0,79 A2,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:

Lectura del ADC con el divisor 10 kΩ + 10 kΩ.
BateríaTensión en el pinLectura
8,4 V (llena)4,2 V859
7,4 V (nominal)3,7 V757
6,4 V (umbral de parada)3,2 V655

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).
Mapa de pines del robot.
PinFunciónMotivo
D2, D3Encoders, canales A (izquierdo, derecho)Interrupciones externas INT0 e INT1
D4, D9Encoders, canales B (izquierdo, derecho)Entradas digitales comunes: se lee su estado en la interrupción
D5, D6PWM de los motores (izquierdo, derecho)Pines con PWM
D7, D8Sentido de los motores (izquierdo, derecho)Salidas digitales
D10, D11TRIG y ECHO del ultrasónicoSalida y entrada digitales
D12Botón de arranque y paradaEntrada con resistencia interna de pull-up
D13LED de estadoEl LED incorporado de la placa
A0 a A4Cinco sensores de líneaEntradas analógicas
A5Tensió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.
Esquema general de hardware del robotLa batería alimenta al driver de motores directamente y al Arduino a través de su regulador. El Arduino recibe la señal de cinco sensores de línea, el ultrasónico, dos encoders y el botón, y envía PWM y sentido al driver. El driver mueve los dos motores, cada uno con su encoder. Un divisor de tensión lleva la batería a la entrada analógica A5. Batería 7,4 V2S, 2 AhArduino UNO R318 pines, 16 MHzPID, estados, odometríaDivisor 10k + 10kSensores de líneaA0–A4UltrasónicoD10, D11Sensoresde línea y ultrasónicoDriver de motoresPWM D5, D6; sentido D7, D8Motores + encoderscanales A en D2, D3Botón y LED

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:

  1. 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.
  2. 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).
  3. 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.
  4. Comportamientos: seguir la línea, evitar un obstáculo, buscar la línea; los temas 33 y 38.
  5. 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

Máquina de estados del robotCinco estados: espera, seguir línea, evitar obstáculo, buscar línea y falla. Desde espera se pasa a seguir línea con el botón. Seguir línea pasa a evitar obstáculo al detectar un obstáculo, y evitar pasa a buscar línea al rodearlo. Buscar línea vuelve a seguir línea cuando encuentra la línea. Desde cualquier estado en marcha se pasa a falla por parada, línea perdida o batería baja. Falla vuelve a espera con el botón. ESPERASEGUIR_LINEAEVITARBUSCAR_LINEAFALLA botónobstáculo ≤ 20 cmrodeadolínea halladabotón, línea perdida > 1 s,batería < 6,4 Vbotón (reinicio)

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):

Presupuesto de CPU de las tareas periódicas.
TareaPeríodoCosto por ejecuciónCarga
Interrupciones de encoderscontinuo (4231 por segundo)5 µs cada una2,1 %
Control y línea (cinco analogRead de unos 112 µs, cuentas y PWM)20 ms1,46 ms7,3 %
Ultrasónico (pulseIn con tiempo máximo de 6 ms)60 ms6,1 ms en el peor caso10,2 %
Telemetría (40 bytes a 115200 baudios)100 ms0,4 ms0,4 %
Medición de la batería1 s0,2 ms0,0 %
Total20,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:

Niveles de prueba.
NivelQué se pruebaCuándoEjemplos
MóduloCada driver y cada función, aisladosTema 46Sentido 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ónVarias capas juntas, con el robot levantado o en pista cortaTema 47El 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ónLos requisitos F, S y C sobre el robot completoTema 48Las cinco vueltas del circuito; el botón de parada; la batería baja
Pruebas de aceptación asociadas a los requisitos.
PruebaRequisitoProcedimientoCriterio de aprobación
P1F2Recta de 1,5 m con marcas; cronometrar 5 pasadasTodas entre 4,5 y 5,5 s
P2F1Circuito completo con la telemetría de la posición de la líneaDesvío máximo ≤ 15 mm
P3F3Caja de 10 × 10 cm en la recta; tres posiciones distintasDistancia final ≥ 10 cm en las tres
P4F4, F5Circuito con caja; cronometrar 5 vueltasManiobra ≤ 15 s; vuelta ≤ 45 s; dispersión ±3 s
P5S1Pulsar el botón en marcha; medir en la telemetríaMotores en cero en ≤ 100 ms
P6S2Levantar el robot de la línea o cortarlaDetención en ≈ 1 s y LED de falla
P7S3, C1Fuente regulable bajando de 7,4 a 6,0 V; descarga completa en otra pruebaParada al cruzar 6,4 V; autonomía ≥ 1 h
P8S4Conectar la batería con el robot en la pistaNo se mueve hasta pulsar
P9C3Medir con micros() el período de control durante 1 minutoVariación ≤ 2 ms

Riesgos del diseño

Riesgos previsibles y qué hacer.
RiesgoEfectoPrevención
Caída de tensión al arrancar los motoresEl Arduino se reiniciaCapacitor 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 infrarrojosLa línea parece estar en otro lugarCalibrar 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áculoFiltrar con la mediana de varias lecturas; exigir dos lecturas consecutivas
Ruido en los encodersVelocidades erráticasCables cortos y trenzados; verificar con osciloscopio o contador
Deslizamiento de ruedasLa odometría y la velocidad no concuerdan con el avance realRuedas con buen agarre; aceleraciones suaves; no confiar sólo en la odometría
Retardo y ruido en el lazo de líneaOscilaciones sobre la líneaAjustar el PID y filtrar (temas 43 y 44)
Pines agotadosNo se puede agregar un sensorRevisar 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.

Corriente media—
Autonomía—
Corriente pico—
Disipación del regulador—

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.

Distancia de reacción—
Distancia de frenado—
Recorrido total—
Margen final—

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

  1. Empezar a cablear sin haber escrito los requisitos: es imposible saber cuándo se terminó ni qué probar.
  2. Requisitos sin número («rápido», «suave», «seguro»). Se reescriben con una medida y un método.
  3. Alimentar los motores y la lógica de la misma fuente sin desacoplo: el primer arranque reinicia la placa.
  4. No contar los pines antes de comprar sensores; el último dispositivo no entra.
  5. Confundir la corriente media con la pico: la batería y el driver se dimensionan por el pico; la autonomía, por la media.
  6. Medir el ultrasónico con un bloqueo largo en medio del lazo de control: el período de control deja de ser constante.
  7. 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.