01 · Punto de partida
Qué vamos a construir
Un robot de tracción diferencial (dos ruedas motrices independientes y una rueda loca) que debe: seguir una línea negra de 19 mm sobre un piso claro a 0,3 m/s, detenerse ante un obstáculo, rodearlo y volver a la línea, y saber cuánto avanzó. Todo con un microcontrolador, a pilas, y de forma que una falla (un sensor mudo, una rueda trabada, la batería baja) lo deje detenido en vez de fuera de control.
| Función | Sensor | Actuador | Ley y período |
|---|---|---|---|
| Velocidad de cada rueda | Encoder en cuadratura (1440 cuentas por vuelta) | Motor DC con puente H | PI por rueda, 20 ms |
| Seguir la línea | Cinco sensores infrarrojos reflectivos | Diferencia de velocidades | PD sobre la posición de la línea, 5 ms |
| Detectar obstáculos | Tres ultrasonidos | — | Un sensor cada 60 ms, sin bloqueos |
| Evitar el obstáculo | Los anteriores | Los motores | Máquina de estados con giros por cuentas |
| Saber dónde está | Los encoders | — | Odometría |
| Seguridad | Encoders, batería | Motores | Detección de atasco, batería baja, parada |
Los tiempos son otros que en el invernadero: la velocidad se controla cada 20 ms, la línea se lee cada 5 ms y un obstáculo a 25 cm se alcanza en menos de un segundo. Por eso las tareas deben ser cortas (tema 41), la lectura del ultrasonido no puede bloquear (un pulseIn con 30 ms de límite en tres sensores serían 90 ms ciegos) y cada decisión se apoya en unas pocas cuentas que se pueden hacer antes de soldar nada: cuántas cuentas por milímetro, cuánto se tarda en frenar, cuánta deriva acumula la odometría.
02 · Componentes
Hardware y alimentación
| Componente | Característica | Nota |
|---|---|---|
| Chasis de dos ruedas y rueda loca | Ruedas de 65 mm, separación entre ruedas de 130 mm | La geometría entra en todas las fórmulas |
| Dos motorreductores DC de 6 V | Reducción 1:30, unas 200 RPM en vacío (0,68 m/s) | 0,3 A en marcha y 1,2 A bloqueados, cada uno |
| Encoders magnéticos (Hall) en el eje del motor | 12 pulsos por vuelta, ×4 en cuadratura = 48; con la reducción, 1440 cuentas por vuelta de rueda | Dos canales por rueda: dirección y cuenta |
| Puente H TB6612FNG | 1,2 A continuos y 3,2 A de pico por canal, PWM de hasta 100 kHz | Transistores MOSFET: casi sin caída (un L298N pierde unos 2 V) |
| Microcontrolador ESP32 | Interrupciones en cualquier pin, ADC, PWM | Un Uno solo tiene 2 interrupciones externas |
| Cinco sensores reflectivos (TCRT5000) | LED infrarrojo y fototransistor, separados 10 mm, a unos 8 mm del piso | Salida analógica; calibrar blanco y negro |
| Tres ultrasonidos HC-SR04 | 2 a 400 cm, haz de unos 15°, 5 V | Su eco de 5 V hay que bajarlo a 3,3 V |
| Cuatro pilas AA NiMH | 4,8 V y 2000 mAh | Con divisor resistivo para medir la batería |
- Cuántas interrupciones hacen falta: cuatro canales de encoder y tres ecos son siete fuentes: un ESP32 las maneja; un Arduino Uno exigiría interrupciones por cambio de pin o decodificar un solo canal por rueda (360 cuentas por vuelta en lugar de 1440).
- El consumo: 0,3 A por motor más 0,15 A de electrónica son 0,75 A, y con el 80 % de 2000 mAh la autonomía es de 2,1 horas. Con los dos motores bloqueados el consumo sube a 2,55 A: el puente H y los cables deben soportarlo, y por eso conviene detectar los atascos.
- La caída de tensión de la batería importa: a lazo abierto, la velocidad de un motor DC es proporcional a la tensión, y al bajar de 6,0 a 5,0 V el robot va un 16,7 % más lento. Con el control de velocidad por encoder, esa caída se compensa.
- El ruido de los motores reinicia microcontroladores y ensucia los sensores: capacitor cerámico de 100 nF entre los bornes de cada motor, uno electrolítico de 470 µF en la alimentación de potencia, masa común en estrella y cables de los encoders trenzados y lejos de los de los motores.
03 · Geometría
Cinemática y odometría
En un robot diferencial, las velocidades de las dos ruedas (vI y vD, a lo largo del suelo) determinan todo el movimiento. Con una separación b entre ruedas:
Si las dos ruedas van igual, el robot avanza recto; si van en sentido contrario con el mismo módulo, gira sobre sí mismo (v = 0). Con ruedas de 65 mm, la circunferencia es de 204,2 mm y cada cuenta del encoder equivale a 0,1418 mm: 7052 cuentas por metro. Un giro de 90° sobre el lugar hace recorrer a cada rueda un cuarto de la circunferencia de giro, π · b / 4 = 102,1 mm, es decir exactamente 720 cuentas: un giro se programa contando cuentas, sin tiempos.
La velocidad se obtiene contando cuentas en un período fijo (tema 26). A 0,3 m/s, el resultado depende del período de muestreo: con 10 ms son 21,2 cuentas por muestra (una cuenta de error es un 4,73 % de la velocidad); con 20 ms, 42,3 cuentas (2,36 %); con 50 ms, 105,8 (0,95 %) y con 100 ms, 211,6 (0,47 %). Un período corto da rapidez y mala resolución; uno largo, lo opuesto. El proyecto usa 20 ms para el PI de velocidad, un compromiso entre ambos.
La odometría integra los desplazamientos de las ruedas para estimar la pose (x, y, rumbo): en cada muestra, s = (sI + sD)/2 y Δθ = (sD − sI)/b, y se avanza s en la dirección media del tramo. Es muy exacta a corto plazo y deriva a largo plazo, porque cada error se suma y no hay nada que lo corrija: la causa principal es una diferencia entre los diámetros reales de las ruedas. Un robot que recorre 2 m a 0,3 m/s con la rueda derecha un cierto porcentaje más grande que la nominal (y una odometría que no lo sabe) acaba así:
| Diferencia | Avance | Desvío lateral | Rumbo |
|---|---|---|---|
| 0 % | 2000,0 mm | 0 mm | 0° |
| 1 % | 2002,1 mm | 154,3 mm | 8,8° |
| 3 % | 1958,7 mm | 460,2 mm | 26,4° |
Un 1 % de diferencia (que ni se ve a simple vista) desvía el robot más de 15 cm en 2 metros. Las defensas, en orden: calibrar (hacer que el robot recorra 2 m en línea recta y ajustar un factor por rueda); controlar la velocidad de cada rueda con encoder, de modo que las dos giren igual aunque los motores sean distintos (sección 4); corregir el rumbo con la diferencia de cuentas o con un giroscopio (tema 20); y corregir con referencias externas, como la propia línea o una pared. La odometría pura sirve para distancias cortas.
04 · Primer lazo
Control de los motores
Cada rueda tiene un lazo de velocidad con la estructura del tema 43: se mide la velocidad con el encoder cada 20 ms, un PI calcula la potencia entre 0 y 1 (con pinza anti-windup) y el motor la ejecuta por PWM; el signo (sentido de giro) se aplica al final. Detalles que importan en un motor DC:
- Zona muerta: un motor con reductor no arranca con menos de un 15 % de PWM, por la fricción. La potencia 0 a 1 se mapea al rango útil (de 15 % a 100 %), de modo que el PI no «pelea» con la zona muerta.
- Lazo de velocidad y no de potencia: con la velocidad de cada rueda controlada, la batería que se descarga o una pendiente no cambian el comportamiento, y las dos ruedas giran igual aunque un motor sea más fuerte.
- Rampas de aceleración: un escalón de velocidad produce un pico de corriente (la corriente de arranque es varias veces la de marcha) y patinaje de las ruedas, que falsea la odometría. Se limita la variación de la consigna.
- Frenado: con los dos bornes del motor a nivel alto o a masa (freno activo), el motor frena de golpe; con ambos en alta impedancia (rueda libre), el robot sigue de largo. Para detenerse a tiempo, se usa el freno, con la rampa.
- PWM por encima de lo audible (16 kHz o más) para que los motores no silben y la corriente sea más suave.
- Marcha recta: con la misma consigna en las dos ruedas y los dos lazos funcionando, el robot sigue recto; lo que queda de desvío (patinaje, diferencias de diámetro) se corrige con la diferencia de cuentas o con un giroscopio.
Y los encoders son un sensor de seguridad: con 0,68 m/s, cada rueda genera 4795 cuentas por segundo, y con una rutina de interrupción de 5 µs en las dos ruedas, el microcontrolador dedica a los encoders un 4,8 % de su tiempo (tema 41). Si una rueda recibe potencia y no cuenta, está trabada: es el atasco que se detecta en la sección 7.
05 · Los ojos del robot
Ultrasonido sin bloqueos
El HC-SR04 emite una ráfaga de ultrasonido al recibir un pulso de 10 µs en su pin de disparo y levanta su pin de eco durante el tiempo que tarda el sonido en ir al obstáculo y volver (tema 17). La distancia es d = t · c / 2. A 343 m/s, cada centímetro suma 58,3 µs de tiempo de ida y vuelta; un eco de 100 cm dura unos 5,8 ms, y con un tiempo límite de 30 ms se alcanza una distancia máxima de 5,14 m.
La velocidad del sonido depende de la temperatura: c ≈ 331,3 + 0,606 · T m/s. Si el programa supone 343 m/s fijos, un obstáculo a 100 cm se lee así:
| Temperatura | c real | Eco de 100 cm | Se lee | Error |
|---|---|---|---|---|
| 0 °C | 331,3 m/s | 6037 µs | 103,5 cm | +3,5 % |
| 20 °C | 343,4 m/s | 5824 µs | 99,9 cm | −0,1 % |
| 40 °C | 355,5 m/s | 5625 µs | 96,5 cm | −3,5 % |
Un error de ±3,5 % rara vez importa para esquivar un obstáculo, pero sí para medir una distancia con precisión; con un sensor de temperatura a mano, la compensación cuesta una línea. Los problemas serios del ultrasonido son otros:
- Bloqueo:
pulseIndetiene el programa hasta que llega el eco o vence el tiempo límite. Con tres sensores serían hasta 90 ms ciegos, con el lazo de velocidad parado (tema 41). La solución es disparar y capturar el eco con una interrupción que mide el tiempo entre el flanco de subida y el de bajada del pin de eco, sin esperar. - Ecos cruzados: un sensor puede oír el eco del vecino. Se dispara uno por vez, con al menos 60 ms entre disparos. Con tres sensores en rotación, cada uno se actualiza cada 180 ms, y a 0,3 m/s el robot avanza 5,4 cm entre dos lecturas del mismo sensor.
- Geometría: el haz es cónico, de unos 15°: un obstáculo angosto o fuera del eje se puede perder, y una superficie lisa inclinada desvía el eco y «desaparece». Las telas y la espuma lo absorben. Tres sensores (izquierda, centro y derecha) cubren mejor el frente.
- Rango mínimo: por debajo de unos 2 cm, el sensor no mide. Un «sin eco» no es «libre»: puede ser un obstáculo que no devuelve el sonido. Un filtro de mediana de tres lecturas elimina los falsos valores aislados.
06 · Segundo lazo
El seguidor de línea
Cinco sensores reflectivos a 10 mm entre sí cubren 40 mm de ancho sobre una línea de 19 mm. Cada uno entrega un valor analógico, que se normaliza entre su blanco y su negro calibrados (tema 5; la luz solar infrarroja engaña a estos sensores). La posición ponderada da la ubicación de la línea respecto del centro del robot, con una resolución mucho mejor que 10 mm porque se interpola con las lecturas analógicas:
El controlador es un PD (tema 43): ω = Kp · m + Kd · dm/dt, y las velocidades de las ruedas son v ∓ ω · b / 2. La derivada pura es viable aquí (sensores limpios, lazo rápido), y se usa solo si hace falta. Un modelo simple, con la barra de sensores a Ls = 80 mm por delante del eje, da una ecuación de segundo orden para el desvío lateral y:
Con v = 300 mm/s y Ls = 80 mm: Kp = 0,02 rad/s por mm da ζ = 0,33 (subamortiguado); 0,05 da 0,52; 0,10 da 0,73 y 0,20 da 1,03 (críticamente amortiguado). Una simulación completa (un robot con barra de 5 sensores, un retardo del motor de 80 ms, línea de 19 mm, arrancando a 15 mm de la línea) confirma el análisis y muestra lo que el modelo simple no ve:
| v | Kp | Kd | Ts | τ motor | Sobreimpulso | Entra en ±2 mm | Oscilación final | Línea perdida |
|---|---|---|---|---|---|---|---|---|
| 0,3 | 0,02 | 0 | 5 ms | 80 ms | 7,3 mm | 2,83 s | 4,08 mm | no |
| 0,3 | 0,05 | 0 | 5 ms | 80 ms | 4,4 mm | 1,17 s | 0,18 mm | no |
| 0,3 | 0,10 | 0 | 5 ms | 80 ms | 1,8 mm | 0,41 s | 0 | no |
| 0,3 | 0,20 | 0 | 5 ms | 80 ms | 0,0 mm | 0,35 s | 0 | no |
| 0,3 | 0,50 | 0 | 5 ms | 80 ms | 0,0 mm | 0,47 s | 0 | no |
| 0,6 | 0,10 | 0 | 5 ms | 80 ms | 7,0 mm | 1,25 s | 0,72 mm | no |
| 0,3 | 0,20 | 0 | 50 ms | 80 ms | 1,3 mm | 0,69 s | 0,53 mm | no |
| 0,3 | 1,00 | 0 | 50 ms | 80 ms | 9,0 mm | no | 17,1 mm | sí |
| 0,3 | 0,50 | 0 | 5 ms | 250 ms | 4,8 mm | no | 9,6 mm | no |
| 0,3 | 1,00 | 0,02 | 5 ms | 250 ms | 0,0 mm | 0,54 s | 0 | no |
Cuatro lecciones. Una ganancia muy baja da un robot lento y subamortiguado (Kp = 0,02: 4 mm de oscilación todavía a los 4 s). La velocidad empeora el amortiguamiento (ζ baja con √v): con 0,6 m/s y Kp = 0,1 el sobreimpulso pasa de 1,8 a 7,0 mm. Un muestreo lento o un motor lento vuelven inestable una ganancia que con 5 ms y 80 ms andaba bien: con 50 ms de período y Kp = 1, el robot oscila 17 mm y pierde la línea; con un motor de 250 ms de retardo y Kp = 0,5, oscila 9,6 mm. Y la derivada rescata el caso del motor lento: con Kd = 0,02 el sobreimpulso desaparece y se llega en 0,54 s. Cuando se pierde la línea (todos los sensores en blanco), el robot sigue girando hacia el lado donde la vio por última vez; en un cruce (todos en negro) sigue recto o aplica una regla de decisión.
07 · Tercer lazo
Frenar y rodear obstáculos
La pregunta de seguridad es ¿a qué distancia hay que reaccionar? Desde que un obstáculo aparece hasta que el robot se detiene transcurre un tiempo de reacción (el período de actualización del sensor más el procesamiento) durante el cual avanza a velocidad constante, y luego la frenada a una desaceleración a:
Con un sensor central que se actualiza cada 180 ms (tres sensores en rotación), 20 ms de procesamiento, una desaceleración de 2 m/s² y 5 cm de margen:
| Velocidad | Reacción | Frenado | Total con margen |
|---|---|---|---|
| 0,2 m/s | 4,0 cm | 1,0 cm | 10,0 cm |
| 0,3 m/s | 6,0 cm | 2,2 cm | 13,3 cm |
| 0,5 m/s | 10,0 cm | 6,2 cm | 21,3 cm |
| 0,8 m/s | 16,0 cm | 16,0 cm | 37,0 cm |
Con un umbral de detección de 25 cm, la velocidad máxima segura es de 0,58 m/s (resolviendo la ecuación anterior para v): por encima, el robot choca antes de frenar. La distancia crece con el cuadrado de la velocidad en el frenado y linealmente en la reacción; la forma de ganar velocidad con seguridad es acortar la reacción (más sensores o más rápidos, una frecuencia mayor) o aumentar la desaceleración (mejores ruedas y freno), y la forma de ahorrar es frenar proporcionalmente a la distancia en vez de todo o nada.
Para rodear el obstáculo, el robot sigue una secuencia de estados (tema 41), con los giros y los tramos medidos con los encoders: LÍNEA (sigue la línea y vigila el sensor central) → FRENAR (consigna cero hasta que las ruedas paran) → GIRAR 90° (720 cuentas por rueda, sobre el lugar) → LADO (avanza 25 cm, unas 1763 cuentas) → giro de vuelta de 90° → BUSCAR (avanza recto hasta que los sensores vuelven a ver la línea) → LÍNEA. Un estado más protege al robot: ATASCADO, al que se pasa si hay potencia en los motores y menos de 2 cuentas en 300 ms (la comprobación de respuesta del tema 44): detiene los motores, enclava y avisa.
08 · Todo junto
Prioridades y seguridad
Varios comportamientos pelean por las mismas ruedas: seguir la línea, frenar, rodear, detenerse por una falla. La solución es una jerarquía de prioridades (una «arquitectura de subsunción» simple): el comportamiento de mayor prioridad que está activo es el que fija la consigna de las ruedas.
- Seguridad (máxima): atasco, batería baja, parada de emergencia, datos inválidos del ultrasonido central durante más de 500 ms, perro guardián. Apaga los motores y enclava.
- Evasión: un obstáculo por debajo del umbral frena y toma el control mientras dura la maniobra.
- Seguimiento de línea (normal).
- Batería: un divisor resistivo y el ADC miden la tensión; las pilas NiMH de 4,8 V nominales no deben bajar de unos 4,0 V (1,0 V por celda): con una advertencia a 4,4 V y un corte a 4,0 V. Una batería débil, además, reinicia el microcontrolador al arrancar los motores (tema 44).
- Parada de emergencia: un botón, y una orden remota de parada que siempre tiene prioridad; si se pierde la comunicación (Bluetooth o Wi-Fi), el robot se detiene.
- Datos con estado: cada lectura (ultrasonido, línea, batería) se trata como una lectura con estado y edad (tema 40): «sin eco» y «dato viejo» no son «libre».
- Estado visible: un LED o un zumbador (temas 35 y 39) dicen el modo (línea, evasión, atasco, batería baja), y un registro de la pose y los eventos ayuda a depurar.
- Seguridad física: un robot pequeño a baja tensión es poco peligroso, pero las ruedas pueden pellizcar los dedos y las baterías de litio, que si se usan, requieren protección contra cortocircuito y sobredescarga.
09 · Programación
El programa
Las piezas del robot: encoders con decodificación en cuadratura por interrupción, odometría, motores con zona muerta y PI de velocidad, ultrasonidos por interrupción con compensación de temperatura, seguidor de línea con posición ponderada y PD, y la máquina de navegación con giros por cuentas y detección de atasco. El PID es el del tema 43 y las tareas se programan con el planificador del tema 41 (velocidad cada 20 ms, línea cada 5 ms, ultrasonido cada 60 ms, navegación cada 20 ms).
// ---------- Geometría y encoders (tema 25): 1440 cuentas por vuelta de rueda ----------
constexpr float D_RUEDA_MM = 65.0f, VIA_MM = 130.0f;
constexpr int CUENTAS_VUELTA = 1440;
constexpr float MM_CUENTA = PI * D_RUEDA_MM / CUENTAS_VUELTA; // 0,1418 mm por cuenta
struct Rueda { volatile int32_t cuentas = 0; int32_t previas = 0; };
Rueda izq, der;
int32_t leerCuentas(Rueda &r) { noInterrupts(); int32_t c = r.cuentas; interrupts(); return c; } // lectura atómica de 32 bits
void decodificar(Rueda &r, uint8_t a, uint8_t b, uint8_t &previo) { // cuadratura x4 con tabla, dentro de la ISR
static const int8_t TABLA[16] = {0,1,-1,0, -1,0,0,1, 1,0,0,-1, 0,-1,1,0};
previo = ((previo << 2) | (digitalRead(a) << 1) | digitalRead(b)) & 0x0F;
r.cuentas += TABLA[previo];
}
void isrIzq() { static uint8_t p = 0; decodificar(izq, PIN_IZQ_A, PIN_IZQ_B, p); }
void isrDer() { static uint8_t p = 0; decodificar(der, PIN_DER_A, PIN_DER_B, p); }
// ---------- Odometría: de las cuentas de cada rueda a la pose (x, y, rumbo) ----------
struct Pose { float x = 0, y = 0, th = 0; } pose;
void odometria(int32_t dI, int32_t dD) {
float sI = dI * MM_CUENTA, sD = dD * MM_CUENTA;
float s = (sI + sD) / 2, dth = (sD - sI) / VIA_MM;
pose.x += s * cos(pose.th + dth / 2); // arco aproximado por su cuerda
pose.y += s * sin(pose.th + dth / 2);
pose.th += dth;
}
// ---------- Motor con compensación de la zona muerta y velocidad por PI (tema 43) ----------
class Motor {
public:
Motor(uint8_t in1, uint8_t in2, uint8_t pwm) : in1_(in1), in2_(in2), pwm_(pwm) { alto(); }
void potencia(float p) { // -1 a 1
p = constrain(p, -1.0f, 1.0f);
float m = (fabsf(p) < 0.01f) ? 0.0f : ZONA_MUERTA + (1.0f - ZONA_MUERTA) * fabsf(p); // el motor no arranca con menos del ~15 %
digitalWrite(in1_, p > 0); digitalWrite(in2_, p < 0);
analogWrite(pwm_, (int)(m * 255));
}
void alto() { digitalWrite(in1_, LOW); digitalWrite(in2_, LOW); analogWrite(pwm_, 0); }
private:
static constexpr float ZONA_MUERTA = 0.15f;
uint8_t in1_, in2_, pwm_;
};
Motor motorIzq(M_IZQ_1, M_IZQ_2, M_IZQ_PWM), motorDer(M_DER_1, M_DER_2, M_DER_PWM);
PID pidIzq(0.0015f, 0.2f, 0.0f, 0.02f), pidDer(0.0015f, 0.2f, 0.0f, 0.02f); // velocidad en mm/s, lazo de 20 ms
float consignaIzq = 0, consignaDer = 0; // mm/s, las fija la navegación
void lazoDeVelocidad() { // cada 20 ms: 42 cuentas a 0,3 m/s, resolución de 2,4 %
static uint32_t ultimo = millis();
uint32_t ahora = millis(); float dt = (ahora - ultimo) / 1000.0f; ultimo = ahora;
int32_t ci = leerCuentas(izq), cd = leerCuentas(der);
int32_t dI = ci - izq.previas, dD = cd - der.previas; izq.previas = ci; der.previas = cd;
odometria(dI, dD);
float vI = dI * MM_CUENTA / dt, vD = dD * MM_CUENTA / dt; // mm/s medidas
float uI = pidIzq.calcular(fabsf(consignaIzq), fabsf(vI)); // el PI trabaja con módulos (potencia 0 a 1) y el signo se aplica al final
float uD = pidDer.calcular(fabsf(consignaDer), fabsf(vD));
motorIzq.potencia(consignaIzq < 0 ? -uI : uI);
motorDer.potencia(consignaDer < 0 ? -uD : uD);
}
// ---------- Ultrasonido por interrupción: sin esperar el eco, un sensor cada 60 ms ----------
constexpr uint8_t N_US = 3; // izquierda, centro, derecha
volatile uint32_t eco0[N_US], ecoUs[N_US]; volatile bool ecoListo[N_US];
float distCm[N_US] = {400, 400, 400};
void isrEco(uint8_t i) { if (digitalRead(PIN_ECO[i])) eco0[i] = micros(); else { ecoUs[i] = micros() - eco0[i]; ecoListo[i] = true; } }
void isrEco0() { isrEco(0); } void isrEco1() { isrEco(1); } void isrEco2() { isrEco(2); }
void tareaUltrasonido() { // cada 60 ms: evita ecos cruzados entre sensores
static uint8_t i = 0;
if (ecoListo[i]) { // llegó el eco del disparo anterior
float c = 331.3f + 0.606f * temperaturaC; // velocidad del sonido con la temperatura
distCm[i] = constrain(ecoUs[i] * c * 1e-4f / 2.0f, 2.0f, 400.0f);
ecoListo[i] = false;
} else distCm[i] = 400.0f; // sin eco en 60 ms: nada a la vista
i = (i + 1) % N_US;
digitalWrite(PIN_TRIG[i], HIGH); delayMicroseconds(10); digitalWrite(PIN_TRIG[i], LOW);
}
// ---------- Seguidor de línea: posición ponderada y PD (tema 43) ----------
constexpr float POS_MM[5] = {-20, -10, 0, 10, 20}; // positivo a la izquierda
float KP_LINEA = 0.2f, KD_LINEA = 0.0f, V_BASE = 300.0f; // rad/s por mm, mm/s
float mPrev = 0, ultimoSigno = 1;
void seguirLinea(const float lect[5], float dt) { // lect: 0 a 1 por sensor, ya normalizada
float suma = 0, pond = 0;
for (uint8_t i = 0; i < 5; i++) { suma += lect[i]; pond += POS_MM[i] * lect[i]; }
float m;
if (suma > 0.05f) { m = pond / suma; ultimoSigno = (m >= 0) ? 1 : -1; }
else m = 30.0f * ultimoSigno; // línea perdida: seguir girando hacia donde se la vio
float w = constrain(KP_LINEA * m + KD_LINEA * (m - mPrev) / dt, -8.0f, 8.0f); // rad/s
mPrev = m;
consignaIzq = V_BASE - w * VIA_MM / 2; // diferencia de velocidades = giro
consignaDer = V_BASE + w * VIA_MM / 2;
}
// ---------- Navegación: línea, frenar, rodear el obstáculo y volver a la línea ----------
enum class Nav : uint8_t { LINEA, FRENAR, GIRAR, LADO, BUSCAR, ATASCADO };
Nav nav = Nav::LINEA; uint8_t fase = 0; uint32_t tNav = 0; int32_t objetivo = 0;
constexpr float DISPARO_CM = 25.0f; // frenar a tiempo hasta ~0,58 m/s (con 20 ms de proceso)
int32_t cuentasGiro(float grados) { return (int32_t)(PI * VIA_MM * fabsf(grados) / 360.0f / MM_CUENTA); } // 90° = 720 cuentas por rueda
bool atascado() { // PWM alto y las ruedas no giran: comprobación de respuesta (tema 44)
static uint32_t desde = 0; static int32_t ref = 0;
int32_t total = abs(leerCuentas(izq)) + abs(leerCuentas(der));
bool potencia = fabsf(consignaIzq) > 100 || fabsf(consignaDer) > 100;
if (!potencia || abs(total - ref) > 2) { desde = millis(); ref = total; return false; }
return (uint32_t)(millis() - desde) > 300;
}
void tareaNavegacion(const float lect[5]) {
if (nav != Nav::ATASCADO && atascado()) { nav = Nav::ATASCADO; }
switch (nav) {
case Nav::LINEA:
seguirLinea(lect, 0.02f);
if (distCm[1] < DISPARO_CM) { consignaIzq = consignaDer = 0; nav = Nav::FRENAR; tNav = millis(); }
break;
case Nav::FRENAR: // esperar a que las ruedas paren (o 400 ms como máximo)
if ((uint32_t)(millis() - tNav) > 400) { objetivo = leerCuentas(der) + cuentasGiro(90); consignaIzq = -150; consignaDer = 150; nav = Nav::GIRAR; fase = 0; }
break;
case Nav::GIRAR: // giro sobre el lugar contando cuentas de la rueda derecha
if (leerCuentas(der) >= objetivo) {
consignaIzq = consignaDer = 200; objetivo = leerCuentas(der) + (int32_t)(250 / MM_CUENTA); // avanzar 25 cm al costado
nav = Nav::LADO;
}
break;
case Nav::LADO:
if (leerCuentas(der) >= objetivo) { consignaIzq = 150; consignaDer = -150; objetivo = leerCuentas(izq) + cuentasGiro(90); nav = Nav::BUSCAR; fase = 0; }
break;
case Nav::BUSCAR: // girar de vuelta y avanzar hasta ver otra vez la línea
if (fase == 0 && leerCuentas(izq) >= objetivo) { consignaIzq = consignaDer = 200; fase = 1; }
if (fase == 1 && (lect[0] + lect[1] + lect[2] + lect[3] + lect[4]) > 1.0f) nav = Nav::LINEA;
break;
case Nav::ATASCADO:
consignaIzq = consignaDer = 0; motorIzq.alto(); motorDer.alto(); // enclavado hasta un reinicio manual (tema 44)
break;
}
}
La separación en capas del tema 40 se mantiene: la navegación solo fija las consignas de velocidad de cada rueda (consignaIzq y consignaDer); no sabe de pines ni de PWM. El perro guardián (tema 44) se alimenta solo si las tareas de velocidad, ultrasonido y navegación respondieron.
10 · Puesta en marcha
Puesta en marcha y problemas típicos
Se prueba por etapas, con el robot levantado del suelo al principio (las ruedas al aire) y luego en una pista, de menor a mayor riesgo: primero los encoders (girar la rueda a mano y comprobar el signo y las cuentas por vuelta); después cada motor con su PWM; el lazo de velocidad con las ruedas al aire; la odometría (recorrer 2 m y medir); los sensores de línea y su calibración; el ultrasonido contra una regla; el seguidor sobre una pista con curvas suaves; y, al final, la evasión con un obstáculo blando y las protecciones (atasco, batería).
| Síntoma | Causa probable | Solución |
|---|---|---|
| Una rueda cuenta al revés | Canales A y B intercambiados en esa rueda | Intercambiar los pines o invertir el signo |
| El robot se desvía en recta | Diferencia de diámetros o de motores | Lazo de velocidad por rueda, calibrar un factor por rueda, corrección de rumbo |
| El microcontrolador se reinicia al arrancar los motores | Caída de tensión o ruido de los motores | Capacitores en los motores y en la alimentación, batería con carga |
| El seguidor oscila o pierde la línea | Kp alta, muestreo lento, velocidad excesiva | Bajar Kp, agregar D, muestrear a 5 ms o menos, bajar la velocidad |
| El seguidor falla con la luz del sol | La luz infrarroja ambiente satura los sensores | Sombrear los sensores, medir con y sin LED (modulación), calibrar en el lugar |
| Distancias del ultrasonido que saltan | Ecos cruzados o superficies inclinadas | Un sensor por vez, 60 ms, filtro de mediana |
| El robot se detiene sin motivo («atasco») | Detección con potencia cercana a la zona muerta | Umbral de potencia mayor, tiempo de espera más largo |
| Cuentas perdidas a alta velocidad | Interrupciones retrasadas por otro código largo | Rutinas cortas, tareas cortas, sin Serial ni I2C dentro |
| La velocidad baja con las pilas | Control a lazo abierto | Lazo de velocidad con encoder; medir y avisar de la tensión |
11 · Analogía con programación
Odometría, ultrasonido, seguidor de línea y frenado
Simulamos los cálculos del robot: la resolución y la deriva de los encoders, el error del ultrasonido con la temperatura, la distancia de frenado y la velocidad máxima segura, una simulación completa del seguidor de línea con un PD, y la autonomía de las pilas.
Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.
import math
# ---- 1. Geometría, encoders y odometría ----
D, B, CPR = 65.0, 130.0, 1440 # mm de diámetro de rueda, mm entre ruedas, cuentas por vuelta de rueda
MM_CUENTA = math.pi * D / CPR
print(f"circunferencia {math.pi * D:.1f} mm; {MM_CUENTA:.4f} mm por cuenta; {1000 / MM_CUENTA:.0f} cuentas por metro")
print(f"giro de 90° sobre el lugar: cada rueda recorre {math.pi * B / 4:.1f} mm = {math.pi * B / 4 / MM_CUENTA:.0f} cuentas")
v = 0.3
for ts in (10, 20, 50, 100):
cuentas = v * 1000 * ts / 1000 / MM_CUENTA
print(f"a {v} m/s y {ts:3d} ms por muestra: {cuentas:5.1f} cuentas -> resolución de velocidad de una cuenta: {100 / cuentas:4.2f} %")
def pose(vl, vr, t, b=B):
"""Pose (x, y, theta) tras t segundos con velocidades constantes de las ruedas (mm/s)."""
v_, w = (vl + vr) / 2, (vr - vl) / b
if abs(w) < 1e-12:
return v_ * t, 0.0, 0.0
return v_ / w * math.sin(w * t), v_ / w * (1 - math.cos(w * t)), w * t
print()
print("avance de 2 m a 0,3 m/s con la rueda derecha un cierto % más grande que la nominal (la odometría cree que no):")
for eps in (0.0, 0.01, 0.03):
x, y, th = pose(300.0, 300.0 * (1 + eps), 2000 / 300)
print(f" diferencia de {eps * 100:.0f} %: avance {x:7.1f} mm, desvío lateral {y:6.1f} mm, rumbo {math.degrees(th):5.1f}°")
# ---- 2. Ultrasonido ----
print()
def c_sonido(t):
return 331.3 + 0.606 * t
for t in (0, 20, 40):
c = c_sonido(t)
eco = 2 * 1.00 / c * 1e6
print(f"{t:2d} °C: c = {c:5.1f} m/s; eco de 100 cm: {eco:6.0f} µs; con c = 343 se lee {100 * 343 / c:6.1f} cm ({(343 / c - 1) * 100:+.1f} %)")
print(f"tiempo de ida y vuelta por centímetro a 343 m/s: {2 * 0.01 / 343 * 1e6:.1f} µs; con un tiempo límite de 30 ms se llega a {0.030 * 343 / 2:.2f} m")
print(f"tres sensores leídos de a uno cada 60 ms: cada uno se actualiza cada {3 * 60} ms; a 0,3 m/s el robot avanza {0.3 * 3 * 60 / 10:.1f} cm entre lecturas del mismo sensor")
# ---- 3. Distancia de frenado y velocidad máxima para evitar un obstáculo ----
print()
t_total, a, margen, disparo = 0.18 + 0.02, 2.0, 5.0, 25.0 # s (período del sensor + procesamiento), m/s², cm, cm
print("v (m/s) reacción frenado total con margen de 5 cm")
for v in (0.2, 0.3, 0.5, 0.8):
reaccion, freno = v * t_total * 100, v ** 2 / (2 * a) * 100
print(f"{v:5.2f} {reaccion:6.1f} cm {freno:6.1f} cm {reaccion + freno + margen:6.2f} cm")
v_max = a * (-t_total + math.sqrt(t_total ** 2 + 2 * (disparo - margen) / 100 / a))
print(f"con un umbral de detección de {disparo:.0f} cm la velocidad máxima segura es de {v_max:.2f} m/s")
# ---- 4. Seguidor de línea: sensores, controlador PD y respuesta ----
print()
SENSORES = (-20.0, -10.0, 0.0, 10.0, 20.0) # mm, positivo a la izquierda
ANCHO_LINEA, RADIO_SENSOR, L_S = 19.0, 4.0, 80.0 # mm
def cobertura(dist):
d = abs(dist)
lleno, vacio = ANCHO_LINEA / 2 - RADIO_SENSOR, ANCHO_LINEA / 2 + RADIO_SENSOR
if d <= lleno:
return 1.0
if d >= vacio:
return 0.0
return (vacio - d) / (vacio - lleno)
def seguidor(v=0.3, kp=0.2, kd=0.0, ts_ms=5, y0=15.0, tau_m=0.08, seg=4.0):
dt = 0.001
y, th, w = y0, 0.0, 0.0
ultimo_signo, m_prev, w_cmd = 1.0, 0.0, 0.0
historia, perdida = [], 0
for k in range(int(seg / dt)):
if k % ts_ms == 0:
lect = [cobertura(y + L_S * math.sin(th) + p * math.cos(th)) for p in SENSORES]
suma = sum(lect)
if suma > 0.05:
m = sum(p * s for p, s in zip(SENSORES, lect)) / suma
ultimo_signo = 1.0 if m >= 0 else -1.0
else:
m = 30.0 * ultimo_signo # línea perdida: se sigue girando hacia donde se vio por última vez
perdida += 1
w_cmd = max(-8.0, min(8.0, kp * m + kd * (m - m_prev) / (ts_ms / 1000)))
m_prev = m
w += (w_cmd - w) / tau_m * dt # el motor responde con un retardo de primer orden
th += w * dt
y += v * 1000 * math.sin(th) * dt
historia.append(y)
ultima = historia[int(len(historia) * 0.6):]
pico = max(-min(historia), 0.0)
entra = next((k for k in range(len(historia)) if all(abs(u) < 2.0 for u in historia[k:k + 500])), None)
return pico, (entra * dt if entra is not None else None), max(ultima) - min(ultima), perdida
print("v (m/s) Kp Kd Ts (ms) τ motor sobreimpulso en ±2 mm oscilación final muestras sin línea")
for v, kp, kd, ts, tau in ((0.3, 0.02, 0, 5, 0.08), (0.3, 0.05, 0, 5, 0.08), (0.3, 0.1, 0, 5, 0.08), (0.3, 0.2, 0, 5, 0.08), (0.3, 0.5, 0, 5, 0.08),
(0.6, 0.1, 0, 5, 0.08), (0.3, 0.2, 0, 50, 0.08), (0.3, 1.0, 0, 50, 0.08), (0.3, 0.5, 0, 5, 0.25), (0.3, 1.0, 0.02, 5, 0.25)):
pico, t, osc, perdida = seguidor(v, kp, kd, ts, tau_m=tau)
entra = f"{t:.2f} s" if t is not None else "no"
print(f"{v:5.2f} {kp:5.2f} {kd:6.3f} {ts:3d} {tau:5.2f} {pico:6.1f} mm {entra:>7s} {osc:7.2f} mm {perdida}")
# ---- 5. Energía ----
print()
motores, electronica, capacidad = 0.3, 0.15, 2000.0 # A por motor en marcha, A, mAh de 4 pilas NiMH de 4,8 V
consumo = 2 * motores + electronica
print(f"consumo medio {consumo:.2f} A -> autonomía de {capacidad * 0.8 / 1000 / consumo:.1f} h con el 80 % de {capacidad:.0f} mAh; bloqueo de ambos motores (1,2 A cada uno): {2 * 1.2 + electronica:.2f} A")
print(f"a lazo abierto, con la tensión de la batería bajando de 6,0 a 5,0 V, la velocidad cae {(1 - 5.0 / 6.0) * 100:.1f} %")
# ---- 6. Carga de las interrupciones de los encoders a la velocidad máxima ----
v_max = 0.68 # m/s: 200 RPM con ruedas de 65 mm
cuentas_s = v_max * 1000 / MM_CUENTA
print(f"a {v_max} m/s: {cuentas_s:.0f} cuentas por segundo por rueda; con una rutina de 5 µs en las dos ruedas, {2 * cuentas_s * 5e-6 * 100:.1f} % de la CPU")
Resultado: cada cuenta del encoder son 0,1418 mm (7052 cuentas por metro) y un giro de 90° sobre el lugar, 720 cuentas por rueda. A 0,3 m/s, una cuenta de error es un 4,73 % de la velocidad con 10 ms por muestra y un 0,47 % con 100 ms. Una diferencia de diámetro de 1 % desvía 154,3 mm y 8,8° en 2 m, y una de 3 %, 460,2 mm y 26,4°. El ultrasonido a 100 cm lee 103,5 cm a 0 °C y 96,5 cm a 40 °C con c = 343 m/s fijo; cada centímetro suma 58,3 µs, y tres sensores en rotación se actualizan cada 180 ms (5,4 cm de avance a 0,3 m/s). La distancia total de frenado con margen es de 10,0; 13,3; 21,3 y 37,0 cm a 0,2; 0,3; 0,5 y 0,8 m/s, y con un umbral de 25 cm la velocidad máxima segura es de 0,58 m/s. En el seguidor, Kp = 0,20 llega a ±2 mm en 0,35 s sin sobreimpulso; con 50 ms de muestreo y Kp = 1 pierde la línea (17,1 mm de oscilación) y con un motor de 250 ms y Kp = 0,5 oscila 9,6 mm, que Kd = 0,02 corrige. La autonomía es de 2,1 h (2,55 A con los dos motores bloqueados) y los encoders cargan la CPU un 4,8 % a 0,68 m/s.
12 · Exploración interactiva
Odometría, ultrasonido, línea y frenado
El primer instrumento calcula la trayectoria de un robot diferencial y la deriva de la odometría. El segundo calcula el eco de un ultrasonido y su error con la temperatura. El tercero simula el seguidor de línea con el mismo modelo del programa de Python. El cuarto calcula la distancia de frenado y la velocidad máxima segura.
Instrumento 1: cinemática y odometría
Instrumento 2: ultrasonido
Instrumento 3: seguidor de línea
Instrumento 4: distancia de frenado
Recorrido guiado
- Con las dos ruedas a 300 mm/s durante 6,67 s (2 m) y una diferencia de diámetro del 1 %: la pose real tiene un desvío lateral de unos 154 mm y 8,8° de rumbo, mientras que la odometría cree que está en línea recta. Con 3 %, 460 mm y 26,4°.
- Poné −150 y +150 mm/s durante 0,68 s: gira sobre el lugar unos 90° (ω = 2,31 rad/s) sin desplazarse. Con 100 mm/s y 300 mm/s, describe un arco de radio 130 mm.
- En la resolución de velocidad, con 300 mm/s: 21 cuentas por muestra de 10 ms (4,7 %) y 42 con 20 ms (2,4 %); con 100 ms, 0,47 %.
- En el ultrasonido, con 100 cm y 20 °C: eco de 5,82 ms y 99,9 cm. Bajá a 0 °C: se lee 103,5 cm (+3,5 %); con «compensar», 100,0 cm. Subí a 40 °C: 96,5 cm sin compensar.
- Con 3 sensores cada 60 ms y 0,3 m/s: cada sensor se actualiza cada 180 ms y el robot avanza 5,4 cm entre lecturas. Bajá el intervalo a 30 ms: el estado avisa de ecos cruzados.
- En el seguidor, con v = 0,3, Kp = 0,2 y 5 ms: sin sobreimpulso, a ±2 mm en 0,35 s (ζ ≈ 1). Con Kp = 0,02: sobreimpulso de 7,3 mm y 4 mm de oscilación aún a los 4 s.
- Con Kp = 1 y 50 ms de período, el robot oscila y pierde la línea. Con Kp = 0,5 y un motor de 250 ms oscila 9,6 mm; agregá Kd = 0,02 con Kp = 1: sin oscilación.
- En el frenado, a 0,3 m/s: reacción de 6,0 cm, frenado de 2,25 cm, 13,25 cm con margen y velocidad máxima de 0,58 m/s con el umbral de 25 cm. A 0,8 m/s se necesitan 37 cm y el estado avisa que choca antes de frenar.
Desafío: un robot que sigue una pared
Se quiere que el robot avance a 0,25 m/s manteniendo 20 cm de una pared a su izquierda, con un ultrasonido lateral. ¿Qué controlador y qué cuidados? Un PD (o PI) sobre el error de distancia (e = 20 − d, en cm) que corrige el giro con ω = Kp·e + Kd·de/dt: la misma estructura del seguidor de línea. El muestreo: el sensor lateral se actualiza cada 60 ms si es el único (o cada 180 ms en la rotación), y a 0,25 m/s el robot avanza entre 1,5 y 4,5 cm entre lecturas: un período largo agrega retardo al lazo y obliga a una ganancia menor. El sensor oblicuo (apuntando 45° adelante) permite anticipar los giros de la pared (una esquina se ve antes). Los falsos datos: una puerta abierta devuelve «sin eco» (400 cm): no es un error de 380 cm que haya que corregir de golpe; se limita la corrección máxima y se avanza recto hasta que vuelva la pared. La seguridad: el sensor frontal sigue siendo prioritario para frenar ante una esquina. Y el robot debe poder perder la pared sin salirse de control: tras 1 s sin lectura válida, se detiene.
13 · Método de trabajo
Cómo encarar un robot móvil
- Hacé las cuentas primero: milímetros por cuenta, cuentas por giro, resolución de velocidad, distancia de frenado y consumo.
- Empezá por los motores: encoders con el signo correcto, PI de velocidad por rueda con zona muerta y rampas.
- Agregá los sensores uno por uno, con lecturas sin bloqueos y con estado y edad; calibrá cada uno contra una referencia.
- Sintonizá cada lazo solo, de adentro hacia afuera: velocidad de rueda, después seguimiento de línea, después evasión.
- Organizá el comportamiento en estados con giros y tramos por cuentas, y con tiempos límite en cada uno.
- Definí las prioridades: seguridad por encima de evasión, y esta por encima del seguimiento de la línea.
- Protegé: detección de atasco, batería, parada de emergencia, perro guardián y comunicación perdida.
- Probá primero con las ruedas al aire, luego en una pista simple y por último con obstáculos, registrando la pose y los eventos.
Si el robot se desvía, falta el lazo de velocidad o la calibración; si oscila siguiendo la línea, la ganancia o el muestreo; si choca, la velocidad es mayor que la de frenado seguro; si se reinicia, la alimentación; si queda trabado sin avisar, falta la detección de atasco.
14 · Comprensión
Confusiones frecuentes
«Con la misma PWM en las dos ruedas el robot va derecho»
Dos motores nunca son idénticos, y la fricción y la batería cambian: con el mismo PWM, un 1 % de diferencia desvía 15 cm en 2 m. Hace falta un lazo de velocidad por rueda con encoder.
«La odometría me dice dónde estoy con exactitud»
Es exacta en distancias cortas y deriva sin límite: cada error se suma. Se corrige con calibración, giroscopio o referencias externas.
«Más ganancia en el seguidor siempre sigue mejor la línea»
Mejora hasta cierto punto; con un muestreo lento o un motor lento, una ganancia alta oscila y pierde la línea. La derivada ayuda, y la velocidad empeora el amortiguamiento.
«El ultrasonido mide la distancia exacta»
Depende de la temperatura (±3,5 % entre 0 y 40 °C), del ángulo y del material del obstáculo, y sufre ecos cruzados. Un «sin eco» no significa «libre».
«Basta con detenerse cuando el sensor ve el obstáculo»
Hay un tiempo de reacción y una frenada: a 0,8 m/s con 25 cm de umbral choca. La distancia de detección debe superar la de frenado, y la velocidad máxima sale de esa cuenta.
«Si las ruedas no giran, el programa se entera solo»
Un motor trabado no avisa: hay que comparar la potencia pedida con las cuentas del encoder (comprobación de respuesta) y detenerlo, o se quema o descarga la batería.
15 · Práctica guiada
Predecí antes de comprobar
1. Cuentas
Un robot con ruedas de 80 mm y un encoder de 1200 cuentas por vuelta de rueda, ¿cuántos milímetros es cada cuenta y cuántas cuentas hay en 1 m?
Ver solución
Circunferencia = π × 80 = 251,3 mm; 251,3 / 1200 = 0,2094 mm por cuenta; 1000 / 0,2094 = 4775 cuentas por metro.
2. Giro
Con una separación entre ruedas de 150 mm, ¿cuántas cuentas debe recorrer cada rueda (en sentidos opuestos) para girar 90° sobre el lugar con el encoder anterior (0,2094 mm por cuenta)?
Ver solución
π × 150 / 4 = 117,8 mm por rueda; 117,8 / 0,2094 = 563 cuentas.
3. Cinemática
Las ruedas van a 400 y 200 mm/s con 130 mm entre ellas. ¿Qué velocidad lineal, velocidad angular y radio de giro tiene el robot?
Ver solución
v = (400 + 200)/2 = 300 mm/s; ω = (400 − 200)/130 = 1,54 rad/s (hacia la izquierda); R = 300 / 1,54 = 195 mm.
4. Ultrasonido
Un eco dura 8,7 ms con el aire a 10 °C. ¿A qué distancia está el obstáculo?
Ver solución
c = 331,3 + 0,606 × 10 = 337,4 m/s; d = 8,7 × 10−3 × 337,4 / 2 = 1,468 m (con 343 m/s se habría leído 1,492 m, un 1,7 % más).
5. Frenado
A 0,4 m/s, con 150 ms de período del sensor, 30 ms de procesamiento, 1,5 m/s² de desaceleración y 5 cm de margen, ¿qué distancia mínima de detección hace falta?
Ver solución
Reacción = 0,4 × 0,18 = 7,2 cm; frenado = 0,4² / (2 × 1,5) = 5,33 cm; total = 7,2 + 5,33 + 5 = 17,5 cm.
6. Diagnóstico
El seguidor de línea funciona bien a 0,2 m/s pero oscila y pierde la línea a 0,6 m/s con las mismas ganancias. ¿Por qué y qué cambiás?
Ver solución
Con la velocidad, el amortiguamiento baja (ζ ∝ 1/√v) y el retardo del motor y del muestreo pesan más: la misma ganancia queda subamortiguada. Se agrega derivada (Kd), se acorta el período de muestreo, se verifica que el motor responda rápido (lazo de velocidad) y, si hace falta, se ajusta Kp para la velocidad más alta o se reduce la velocidad en las curvas.
16 · Síntesis
Ideas para recordar
- Un robot diferencial se describe con v = (vD + vI)/2, ω = (vD − vI)/b y R = v/ω; un giro sobre el lugar es un número exacto de cuentas por rueda (720 para 90° con ruedas de 65 mm y 130 mm de separación).
- La resolución de velocidad de un encoder depende del período de medición (4,7 % con 10 ms, 0,47 % con 100 ms a 0,3 m/s).
- La odometría deriva: un 1 % de diferencia de diámetro desvía 154 mm en 2 m; se corrige con calibración, lazo de velocidad por rueda, giroscopio o referencias externas.
- El lazo de velocidad por rueda (PI, 20 ms, zona muerta, rampas) hace que el robot ignore la batería y las diferencias entre motores.
- El ultrasonido se captura por interrupción, un sensor por vez cada 60 ms, y se compensa con la temperatura (±3,5 % entre 0 y 40 °C); «sin eco» no es «libre».
- El seguidor de línea es un PD sobre la posición ponderada; ζ = Kp Ls / (2√(v Kp)); el muestreo lento y el motor lento lo hacen oscilar y perder la línea, y la derivada lo corrige.
- La distancia de frenado es v(T + Tproc) + v²/2a + margen: con 25 cm de umbral la velocidad máxima segura es de 0,58 m/s.
- Prioridades: seguridad (atasco, batería, parada) sobre evasión y esta sobre el seguimiento de línea; una máquina de estados con giros por cuentas rodea el obstáculo.
¿Listo para el tema 47?
Antes de avanzar, calculá cuántas cuentas hay en un giro de 90° para un robot con ruedas de 70 mm, una separación de 140 mm y 1000 cuentas por vuelta de rueda, explicá por qué el ultrasonido se dispara de a un sensor por vez y decí qué pasa con un seguidor de línea si se aumenta mucho la velocidad sin cambiar las ganancias. Si podés justificar las tres respuestas, ya sabés integrar un robot móvil.
En el último tema repasamos todo el curso y miramos hacia adelante: el cierre y el puente a la comunicación, con las buenas prácticas y los próximos pasos.