01 · Punto de partida
De un eje a varios
En los temas 16 a 18 trabajamos con un solo eje: lo movimos con una rampa, con un perfil de velocidad y con realimentación de un encoder. Casi ningún robot tiene un único eje. Un brazo tiene varias articulaciones, un plotter tiene dos ejes lineales, una impresora 3D tiene tres o más, y un robot móvil tiene dos ruedas que deben ponerse de acuerdo.
Coordinar es hacer que varios ejes se muevan como un solo movimiento: que salgan juntos, lleguen juntos y recorran en el medio el camino que elegimos. Y hacerlo sin que el programa se quede esperando.
Seguimos con la idea de los temas anteriores: el planificador genera referencias y cada eje las sigue. Lo nuevo es que ahora hay una sola planificación para todos.
02 · El problema
Por qué coordinar
Supongamos un plotter con dos ejes lineales que debe dibujar una recta desde el origen hasta el punto (150 mm, 60 mm). Si cada eje se mueve a su máxima velocidad, el que tiene menos recorrido o más velocidad termina primero. Durante todo ese tiempo, el cabezal no sigue la recta: describe un camino quebrado.
En un plotter o una fresadora ese desvío es un defecto visible. En un brazo robótico es la diferencia entre acercarse a un objeto por arriba o chocar contra el borde. Y en un robot móvil de dos ruedas, si cada rueda llega a su velocidad a su ritmo, el robot toma una curva imprevista.
La solución no consiste en ir más rápido: consiste en que todos los ejes compartan un mismo reloj de movimiento.
03 · Un reloj común
Un parámetro de trayecto común
En el tema 16 vimos que, para llegar juntos, los ejes usan la misma duración. Lo generalizamos: el movimiento tiene un parámetro de trayecto s que va de 0 (inicio) a 1 (destino). Cada eje i con recorrido total Δi se ubica en:
La función s(t) se arma con los perfiles del tema 17, usando una distancia total D = 1: acelera, cruza y frena. Un solo perfil para todos. Como todos los ejes usan el mismo s, avanzan la misma fracción del recorrido en cada instante: en el espacio de los ejes, el camino es una recta.
La velocidad de cada eje es vi = |Δi| · ṡ. Los ejes con mayor recorrido se mueven más rápido; los de menor recorrido, más despacio. Todos empiezan y terminan a la vez, y todos arrancan y frenan de forma suave.
Esta técnica se llama interpolación lineal entre dos puntos. La palabra «lineal» describe el camino en el espacio de los ejes, no el perfil de velocidad (que sigue siendo trapezoidal).
04 · Quién manda
Límites de cada eje sobre el trayecto
Cada eje tiene su velocidad máxima Vi y su aceleración máxima Ai. Como vi = |Δi| · ṡ, el trayecto no puede ir más rápido que lo que permite el eje más exigido:
Con esos dos límites se aplican las fórmulas del tema 17 con D = 1. Si 1 ≥ ṡ²/s̈, el perfil es trapezoidal y dura T = 1/ṡ + ṡ/s̈; si no, es triangular y dura T = 2·√(1/s̈).
El eje que fija el mínimo es el eje limitante. Puede ser uno distinto para velocidad y para aceleración, y cambia de un tramo a otro. Los demás trabajan por debajo de su capacidad, y está bien: eso es lo que los hace llegar juntos.
Una variante equivalente, más cómoda en código por pasos, es medir el trayecto con los pasos del eje de mayor recorrido (el eje dominante). Los límites del trayecto, en pasos del dominante por segundo, son Vi · n / |Δi con n = máx|Δi|. Es la misma cuenta expresada en otras unidades.
05 · Pasos enteros
Repartir pasos: el acumulador de error
Con motores paso a paso, cada eje solo puede avanzar en pasos enteros. Si el dominante da n pasos y el otro debe dar m ≤ n, hay que decidir en qué instantes el eje menor da su paso. Con un reparto parejo, la recta queda bien aproximada.
El método clásico, de Bresenham, usa un acumulador entero sin divisiones ni flotantes. En cada paso del eje dominante se suma m al acumulador; cuando alcanza o supera n, el eje menor da un paso y se resta n:
Con n = 10 y m = 4, el eje menor da sus pasos en los pasos dominantes 2, 4, 7 y 9. El desvío respecto de la recta ideal nunca supera medio paso. Es un error de cuantización, el mismo del tema 14: la posición es múltiplo de un paso.
Para servos, el equivalente es más simple: cada eje calcula su referencia como x0 + Δ·s(t) en cada ciclo, sin pasos enteros que repartir.
06 · Un programa que sigue vivo
Moverse sin bloquear el programa
La versión ingenua de un movimiento es un bucle for o un while que da todos los pasos y recién entonces devuelve el control. Mientras dura, el programa no lee comandos, ni sensores, ni finales de carrera, ni un botón de parada. Con un solo eje, ya era un problema; con varios, es inaceptable.
La alternativa es una máquina de estados que avanza un poquito en cada vuelta de loop(). En cada vuelta se consulta el reloj y se hace lo mínimo que toca: dar un paso si llegó su momento, actualizar la velocidad cada milisegundo, pasar de una fase a otra. Entre medio, el programa queda libre para atender el resto.
- Estado y datos del movimiento en variables globales: posición, pasos restantes, velocidad actual, acumuladores.
- Un solo punto de avance por vuelta, medido con
micros()omillis(). - Pausas con reloj, nunca con
delay(): se guarda el instante de inicio y se compara. - Órdenes que modifican el estado, no que interrumpen el código: una parada pide frenar (reduce los pasos restantes) en vez de abortar un bucle.
Hay un límite honesto: con un microcontrolador pequeño y pulsos generados por software, las frecuencias de paso del orden de varios kHz por eje ya consumen buena parte del tiempo, y una lectura lenta puede retrasar un paso. Para velocidades altas se usan temporizadores por hardware o bibliotecas dedicadas.
En los temas 36 y 37 estudiaremos máquinas de estados con más detalle. Aquí usamos la más pequeña posible: reposo, moviendo y pausa.
07 · Varios movimientos seguidos
Secuencias de puntos
Una tarea típica es recorrer una lista de puntos: ir de A a B, pausar, ir a C, volver. Cada tramo es un movimiento coordinado de un punto al siguiente. La secuencia es una lista (un arreglo de coordenadas) y un índice que indica el tramo actual.
El ciclo de vida de un tramo es siempre el mismo:
- Preparar: calcular recorridos, eje dominante, límites de trayecto y direcciones.
- Mover: ejecutar el perfil hasta agotar los pasos.
- Pausar (opcional): esperar un tiempo con el reloj, por ejemplo para trabajar en el punto.
- Avanzar el índice: pasar al siguiente tramo o terminar.
En esta versión cada tramo parte y termina en reposo. Es simple y seguro, pero tiene tiempos muertos y desgasta más la mecánica. Una alternativa es el blending: empezar a acelerar hacia el punto siguiente antes de frenar del todo, pasando cerca del punto intermedio sin detenerse. Mejora la fluidez, pero cambia el camino en las esquinas y complica el cálculo. Conviene dominar primero la versión con reposo.
La posición de partida de cada tramo es la posición ordenada que el programa lleva en cuenta. Por eso, al energizar, hay que definir un origen conocido (un final de carrera, una posición de referencia manual) y recordar que todo lo que sigue es relativo a él.
08 · Cuando algo falla
Errores, fallos y seguridad
La coordinación hace que los fallos sean más visibles: si un eje se atrasa o pierde pasos, la geometría entera se deforma, no solo la posición de ese eje. Conviene pensar de antemano cómo se detecta y cómo se reacciona.
- Pérdida de pasos en lazo abierto. Un eje que se traba o pierde sincronismo (tema 14) sigue contando pasos ordenados. El programa cree estar en un punto y el eje está en otro. Los siguientes tramos parten de una posición falsa.
- Error de seguimiento en lazo cerrado. Con encoders (tema 18) cada eje sigue su referencia con un pequeño retraso. La diferencia entre los retrasos de dos ejes produce un error de contorno respecto del camino. Si se aprecia, hay que subir la ganancia del lazo o bajar la velocidad.
- Finales de carrera. Deben detener el movimiento coordinado completo, no solo el eje que los activó, porque seguir moviendo el resto cambiaría la geometría.
- Parada. Pedir una parada reduce los pasos restantes para frenar con la aceleración planificada. Una parada de emergencia requiere una vía independiente (tema 4), porque corta la potencia sin respetar ningún perfil.
Regla útil: si un eje no puede cumplir, se detienen todos. Un movimiento coordinado a medias no es un movimiento parcial correcto; es un movimiento incorrecto.
09 · Código para la placa
Dos ejes coordinados en Arduino
Supuestos que debés verificar: UNO R3; dos drivers STEP/DIR (eje X: STEP en D2 y DIR en D3; eje Y: STEP en D4 y DIR en D5), sin carga que pueda dañarse, y con las velocidades y aceleraciones de ejemplo dentro de lo que tus motores toleran (tema 14). El origen (0,0) se asume al encender: el programa no lo verifica.
La secuencia recorre tres puntos y vuelve: (0,0) → (1600,800) → (400,1200) → (0,0), con 500 ms de pausa en cada llegada. El comando G inicia la secuencia y S frena con la rampa planificada. Eje X: 2000 pasos/s y 4000 pasos/s². Eje Y: 800 pasos/s y 3000 pasos/s².
const byte PIN_STEP[2] = {2, 4};
const byte PIN_DIR[2] = {3, 5};
const float V_MAX[2] = {2000.0, 800.0}; // pasos/s por eje
const float A_MAX[2] = {4000.0, 3000.0}; // pasos/s² por eje
const float V_MIN = 100.0;
const unsigned long PERIODO_V_US = 1000, PAUSA_MS = 500;
const long PUNTOS[][2] = {{0, 0}, {1600, 800}, {400, 1200}, {0, 0}};
const byte N_PUNTOS = sizeof(PUNTOS) / sizeof(PUNTOS[0]);
enum Estado { REPOSO, MOVIENDO, PAUSA };
Estado estado = REPOSO;
long pos[2] = {0, 0}; // pasos ordenados desde el origen asumido
long absD[2], err[2], restantes, total;
int sentido[2];
byte dom, indice;
float v, vLim, aLim;
bool detener = false;
unsigned long ultimoPaso, ultimaV, inicioPausa;
void iniciarTramo() {
long delta[2];
for (byte i = 0; i < 2; i++) {
delta[i] = PUNTOS[indice][i] - pos[i];
absD[i] = labs(delta[i]);
sentido[i] = (delta[i] >= 0) ? 1 : -1;
digitalWrite(PIN_DIR[i], (delta[i] >= 0) ? HIGH : LOW);
}
dom = (absD[0] >= absD[1]) ? 0 : 1;
total = restantes = absD[dom];
if (total == 0) { estado = PAUSA; inicioPausa = millis(); return; }
vLim = aLim = 1e9;
for (byte i = 0; i < 2; i++) {
if (absD[i] == 0) continue;
vLim = min(vLim, V_MAX[i] * total / absD[i]); // límite del eje i sobre el dominante
aLim = min(aLim, A_MAX[i] * total / absD[i]);
err[i] = total / 2;
}
delayMicroseconds(10); // tiempo de establecimiento de DIR
v = min(V_MIN, vLim);
estado = MOVIENDO;
ultimaV = ultimoPaso = micros();
}
void darPaso() {
bool dar[2] = {false, false};
dar[dom] = true;
for (byte i = 0; i < 2; i++) {
if (i == dom) continue;
err[i] += absD[i];
if (err[i] >= total) { err[i] -= total; dar[i] = true; }
}
for (byte i = 0; i < 2; i++) if (dar[i]) { digitalWrite(PIN_STEP[i], HIGH); pos[i] += sentido[i]; }
delayMicroseconds(4);
for (byte i = 0; i < 2; i++) if (dar[i]) digitalWrite(PIN_STEP[i], LOW);
}
void informar(const char *texto) {
Serial.print(texto);
Serial.print(" X="); Serial.print(pos[0]);
Serial.print(" Y="); Serial.println(pos[1]);
}
void setup() {
for (byte i = 0; i < 2; i++) {
digitalWrite(PIN_STEP[i], LOW);
pinMode(PIN_STEP[i], OUTPUT);
pinMode(PIN_DIR[i], OUTPUT);
}
Serial.begin(115200);
Serial.println("G: iniciar secuencia; S: frenar con rampa");
}
void loop() {
if (Serial.available() > 0) {
const char cmd = Serial.read();
if (cmd == 'G' && estado == REPOSO) {
indice = 1; detener = false; iniciarTramo();
} else if (cmd == 'S' && estado == MOVIENDO) {
const long freno = (long)(v * v / (2.0 * aLim)) + 1;
if (freno < restantes) restantes = freno;
detener = true;
} else if (cmd == 'S' && estado == PAUSA) {
estado = REPOSO;
}
}
if (estado == PAUSA) {
if (millis() - inicioPausa >= PAUSA_MS) {
if (++indice >= N_PUNTOS) {
estado = REPOSO;
informar("Secuencia completa; posicion real no confirmada.");
} else {
iniciarTramo();
}
}
return;
}
if (estado != MOVIENDO) return;
const unsigned long ahora = micros();
if (ahora - ultimaV >= PERIODO_V_US) {
const float dt = (ahora - ultimaV) * 1e-6;
ultimaV = ahora;
float nueva = v + aLim * dt;
if (nueva > vLim) nueva = vLim;
const float vFreno = sqrt(2.0 * aLim * restantes);
if (nueva > vFreno) nueva = vFreno;
v = (nueva < V_MIN) ? min(V_MIN, vLim) : nueva;
}
if (ahora - ultimoPaso >= (unsigned long)(1000000.0 / v)) {
ultimoPaso = ahora;
darPaso();
if (--restantes <= 0) {
if (detener) { estado = REPOSO; informar("Detenido; posicion ordenada:"); }
else { estado = PAUSA; inicioPausa = millis(); informar("Punto alcanzado (ordenado):"); }
}
}
}
Cada tramo calcula el eje dominante y los límites del trayecto (vLim y aLim) con la fórmula Vi·n/|Δi|. Luego usa el mismo generador de velocidad del tema 17 sobre los pasos del dominante, y reparte los del otro eje con el acumulador de error.
Con esos números, el primer tramo (1600, 800) es limitado por la velocidad del eje Y (800·1600/800 = 1600 pasos/s del dominante) y dura 1,40 s. El segundo (−1200, 400) dura 1,10 s, limitado por el eje X. El tercero (−400, −1200), dominado por Y, dura 1,77 s. En total, unos 4,27 s de movimiento y 5,77 s con las tres pausas.
Dos advertencias: el programa solo cuenta pasos ordenados y lo dice en sus mensajes (no hay confirmación de posición); y no detecta finales de carrera ni atascos, algo imprescindible en una máquina real. Cuando S detiene un tramo, la secuencia termina y la posición queda donde frenó.
Los pulsos de los dos ejes se emiten juntos para que el instante de paso sea común, y duran unos 4 µs. Revisá el ancho mínimo de pulso y los tiempos de DIR de tu driver antes de confiar en estos valores.
10 · Exploración interactiva
Laboratorios: ejes coordinados y reparto de pasos
1. Independiente frente a coordinado
El eje X tiene límites fijos de 100 mm/s y 400 mm/s². Del eje Y podés elegir velocidad y aceleración. Se compara el camino XY y el tiempo con cada método. Ambos usan perfil trapezoidal o triangular.
Modificá recorridos, límites y método.
Con los valores iniciales, X tarda 1,75 s e Y 1,45 s por su cuenta, y el cabezal se aparta unos 10 mm de la recta. En modo coordinado el tiempo total es el mismo (1,75 s, lo marca X) y el desvío es cero; Y baja de 50 a 40 mm/s. No se pierde tiempo por coordinar: se gasta capacidad que de todos modos sobraba.
Probá recorridos de 150 y 150 mm con Y limitado a 30 mm/s y 100 mm/s²: Y necesita 5,3 s, el movimiento coordinado también, y X baja a 30 mm/s. Sin coordinar, el desvío llega a unos 73 mm. El eje lento fija el tiempo de todos.
2. Cómo se reparten los pasos
El eje dominante da n pasos y el menor m. Cada punto es un paso del dominante: naranja si en ese instante también da su paso el eje menor, verde si no. La recta ideal va punteada.
Elegí cuántos pasos da cada eje.
Con 20 y 8, el eje menor da su paso en las posiciones 2, 4, 7, 9, 12, 14, 17 y 19, con un desvío máximo de 0,4 paso. Probá 40 y 39: el menor casi acompaña al dominante y solo omite un paso. Con m igual a n, ambos dan paso en todos los ciclos: una diagonal perfecta a 45°.
11 · Código ejecutable
Planificar una secuencia en Python
Reproducimos en Python el cálculo que hace el sketch: límites del trayecto por tramo, perfil, duración total y desvío del reparto de pasos. Es una forma barata de predecir tiempos antes de mover nada.
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 motor.
import math
def segmento(delta, vmax, amax):
absd = [abs(d) for d in delta]
n = max(absd)
v = min(vm * n / d for vm, d in zip(vmax, absd) if d)
a = min(am * n / d for am, d in zip(amax, absd) if d)
if n >= v * v / a:
return n, v, a, n / v + v / a, "trapecio"
return n, math.sqrt(a * n), a, 2 * math.sqrt(n / a), "triangulo"
def bresenham(dx, dy):
n, m = max(abs(dx), abs(dy)), min(abs(dx), abs(dy))
err, menor, maxdev = n // 2, 0, 0.0
for k in range(1, n + 1):
err += m
if err >= n:
err -= n
menor += 1
maxdev = max(maxdev, abs(menor - k * m / n))
return menor, maxdev
PUNTOS = [(0, 0), (1600, 800), (400, 1200), (0, 0)]
VMAX, AMAX = (2000, 800), (4000, 3000)
total = 0.0
for i in range(len(PUNTOS) - 1):
delta = [b - a for a, b in zip(PUNTOS[i], PUNTOS[i + 1])]
n, v, a, T, forma = segmento(delta, VMAX, AMAX)
total += T
print(f"Tramo {i + 1}: delta={delta} n={n} vLim={v:.0f} aLim={a:.0f} T={T:.2f} s ({forma})")
print(f"Tiempo de movimiento: {total:.2f} s; con 3 pausas de 0,5 s: {total + 1.5:.2f} s")
pasos, desvio = bresenham(1600, 800)
print(f"Bresenham 1600/800: pasos del eje menor={pasos}, desvio maximo={desvio:.2f} paso")
pasos, desvio = bresenham(1200, 400)
print(f"Bresenham 1200/400: pasos del eje menor={pasos}, desvio maximo={desvio:.2f} paso")Los tres tramos dan 1,40 s, 1,10 s y 1,77 s: 4,27 s de movimiento y 5,77 s con las pausas. El reparto de pasos del primer tramo (1600 y 800) mantiene el desvío en 0,50 paso, y el del segundo (1200 y 400) en 0,33 paso.
Cambiá VMAX del eje Y a 2000 y observá cómo cambia el eje limitante del primer tramo. También probá PUNTOS con un tramo muy corto para ver el perfil triangular.
12 · Método y diagnóstico
Pruebas y diagnóstico
- Calculá a mano (o con Python) el tiempo de cada tramo antes de probar, y compará con un cronómetro.
- Probá cada eje por separado, a baja velocidad, antes de combinarlos.
- Verificá los sentidos: un eje invertido deforma la figura pero no se nota en un movimiento de un solo eje.
- Dibujá una figura de prueba (un cuadrado, una diagonal) y medila: lados, ángulos y cierre.
- Repetí la secuencia varias veces y comprobá que vuelve al origen; si no, hay pérdida de pasos o deslizamiento.
- Probá la parada a mitad de un tramo desde distintas velocidades.
- Aumentá velocidad y aceleración de a poco y anotá a partir de cuál aparecen errores.
«El cuadrado queda como un paralelogramo»
Los ejes no son perpendiculares o las escalas (pasos por milímetro) difieren entre X e Y. Calibrá cada eje por separado.
«La diagonal sale curva»
Si no se coordina, un eje termina antes. Si se coordina y aun así se curva, un eje puede estar perdiendo pasos o tener más holgura que el otro.
«La secuencia no vuelve exactamente al origen»
Posiciones ordenadas coinciden, pero las reales no: pérdida de pasos, holgura en los cambios de sentido o deslizamiento. En lazo abierto no se puede saber cuál sin una medición.
«Con delay() en la pausa funciona igual»
En una pausa corta, aparentemente sí. Pero durante ese tiempo no se atiende la orden de parada ni ningún sensor. Una pausa con reloj cuesta pocas líneas más.
«Si un eje va más lento, los otros pueden seguir a su ritmo»
Entonces dejan de estar coordinados: el camino se desvía. El eje más lento fija el ritmo de todos en ese tramo.
«Coordinar es más lento»
El tiempo total lo fija de todos modos el eje limitante. Coordinar solo reparte el tiempo que los demás ejes de otro modo gastarían esperando.
13 · Práctica guiada
Predecí antes de comprobar
1. Duración común
Dos ejes deben recorrer 300 mm y 100 mm, con velocidades máximas de 150 mm/s y 100 mm/s. Ignorando aceleraciones, ¿cuánto tarda cada uno por su cuenta? ¿Cuánto tarda el movimiento coordinado y a qué velocidad va cada eje?
Ver solución
X: 300/150 = 2 s; Y: 100/100 = 1 s. Coordinado: 2 s. X va a 150 mm/s e Y a 100/2 = 50 mm/s.
2. Eje limitante
Recorridos de 200 mm y 80 mm; velocidades máximas de 100 mm/s y 60 mm/s. ¿Qué eje limita? ¿Cuál es ṡmáx y a qué velocidad va el eje menor?
Ver solución
V/Δ: X: 100/200 = 0,5 1/s; Y: 60/80 = 0,75 1/s. Limita X, ṡmáx = 0,5. El eje Y va a 0,5 · 80 = 40 mm/s.
3. Un tramo con perfil
Con los límites del sketch (X: 2000 pasos/s y 4000 pasos/s²; Y: 800 y 3000), calculá el tramo de (400,1200) a (0,0).
Ver solución
Δ = (−400, −1200), n = 1200 (domina Y). vLim = mín(2000·3, 800) = 800; aLim = mín(4000·3, 3000) = 3000. v²/a ≈ 213 < 1200: trapecio. T = 1200/800 + 800/3000 ≈ 1,77 s.
4. Acumulador de error
El eje dominante da 10 pasos y el menor 4. Con err inicial 5, ¿en qué pasos del dominante da su paso el menor?
Ver solución
Se suma 4 en cada paso: 9, 13 (≥10, paso, queda 3), 7, 11 (paso, queda 1), 5, 9, 13 (paso, queda 3), 7, 11 (paso, queda 1), 5. Pasos 2, 4, 7 y 9: cuatro en total.
5. Pausa bloqueante
Un programa usa delay(500) entre un punto y el siguiente. Durante esa pausa llega el comando de parada. ¿Qué ocurre y cómo se corrige?
Ver solución
El comando queda sin leer hasta que termina el delay: la reacción tarda hasta 0,5 s más. Se corrige con un estado de pausa que guarde el instante de inicio con millis() y atienda el serie en cada vuelta.
6. Un eje pierde pasos
En un plotter en lazo abierto, el eje Y pierde pasos a mitad de una figura. ¿Qué pasa con el dibujo y qué medidas tomarías?
Ver solución
El resto de la figura se desplaza en Y y los tramos posteriores parten de una posición falsa; el programa no lo sabe. Medidas: bajar velocidad y aceleración, revisar corriente y mecánica, añadir un final de carrera o un encoder para detectar la discrepancia y detener todos los ejes.
14 · Síntesis
Ideas para recordar
- Coordinar es que todos los ejes compartan un mismo reloj de movimiento: salen y llegan juntos.
- Con un parámetro de trayecto
s, cada eje se ubica enx0 + Δ·sy el camino en el espacio de ejes es una recta. - Los límites del trayecto salen del eje más exigido:
mín(Vi/|Δi|)ymín(Ai/|Δi|). - El eje limitante fija el tiempo; coordinar no suele costar tiempo extra.
- Con pasos enteros, un acumulador de error reparte los pasos con un desvío de hasta medio paso.
- Moverse sin bloquear es avanzar un poco por vuelta con el reloj, con un estado y órdenes que modifican ese estado.
- Una secuencia es una lista de puntos con un ciclo preparar–mover–pausar–avanzar.
- Si un eje falla, se detienen todos; en lazo abierto, los pasos ordenados no son pasos realizados.
¿Listo para el tema 20?
Antes de avanzar, elegí dos ejes con límites distintos y un tramo de ida y vuelta. Calculá cuál limita en cada tramo, la duración del movimiento y cómo detectarías que uno de los ejes no está siguiendo la orden.
En el próximo tema cambiamos de escala: estudiamos coordenadas, ángulos y transformaciones, los sistemas de referencia, vectores, rotaciones y traslaciones que hacen falta para describir dónde está cada parte de un robot.