← Índice del curso

Robótica y Automatización · Tema 16

Posicionamiento de servos y movimientos suaves

Pedirle a un servo «ir a 1600 µs» es fácil; lograr que llegue sin tirones, sin picos de corriente y a una velocidad que elegimos nosotros es otro problema. Vamos a generar referencias intermedias en el tiempo, limitar la velocidad y sincronizar articulaciones sin bloquear el programa.

01 · Punto de partida

Del destino a la trayectoria

En el tema 15 aprendimos a enviar una referencia a un servo: un ancho de pulso, una calibración y límites de seguridad. Cada orden era un destino. El servo resolvía por su cuenta cómo llegar, y lo hacía a la velocidad que le permitían su electrónica y su carga.

Un robot real necesita decidir cómo llegar: con qué velocidad, durante cuánto tiempo y con qué forma de movimiento. Esa decisión se programa generando una secuencia de referencias intermedias, una trayectoria, que se envía cada pocos milisegundos.

Nada de esto agrega un sensor: seguimos en lazo abierto respecto del ángulo. Lo que mejora es la calidad de la orden que le damos al servo, no el conocimiento de dónde está su eje.

02 · El problema

Qué pasa con un salto de referencia

Si el programa pasa de 1400 a 1600 µs en un solo paso, el control interno ve de golpe un error grande. Un servo típico puede responder aplicando todo el esfuerzo disponible hasta reducirlo. Las consecuencias dependen del modelo y de la carga, pero conviene prever estas:

  • Pico de corriente: el arranque exigido es el de mayor demanda y puede hundir una fuente justa (tema 15).
  • Movimiento brusco: la carga se acelera y frena de golpe; los engranajes y el montaje reciben el choque.
  • Velocidad no elegida: el robot se mueve «tan rápido como puede», que cambia con la carga, la tensión y la temperatura.
  • Articulaciones desincronizadas: dos servos con órdenes simultáneas llegan en momentos distintos y el extremo del brazo recorre un camino imprevisto.
  • Sobreimpulso y oscilación: una carga con inercia puede pasarse del objetivo y corregir varias veces.

La solución es no pedir el destino de una vez. Se pide una referencia que avanza de forma gradual y el servo, que corrige un error pequeño, la sigue con más suavidad.

Planificador de movimiento entre el programa y el servo El programa define un objetivo. Un planificador por software, con velocidad máxima y perfil, calcula cada pocos milisegundos una referencia intermedia que se envía al servo. El lazo interno del servo mueve el eje. No hay retorno de posición real hacia el planificador. Programaobjetivo finalPlanificadorrampa o interpolaciónServolazo interno y ejeVelocidad máxima y duraciónparámetros de diseñoSoftware del controladorcada ~20 msreferencia

El planificador es código nuestro. No sustituye al lazo interno del servo: le entrega referencias razonables. Y por ahora no recibe información del eje, de modo que su «posición» es la que él mismo ordenó.

03 · La idea más simple

Velocidad limitada: el limitador de rampa

El método más corto es acotar cuánto puede cambiar la referencia en cada ciclo. Con velocidad máxima vmáx y período de actualización Δt, el paso máximo es:

pasomáx = vmáx · Δt
xk+1 = xk + clamp(objetivo − xk, −pasomáx, +pasomáx)

Si el error es mayor que el paso, se avanza un paso completo hacia el objetivo. Si es menor, se llega exactamente y se termina el movimiento. No hace falta calcular la duración por adelantado ni distinguir sentidos.

Con un cambio de 200 µs, vmáx = 100 µs/s y Δt = 20 ms, el paso es de 2 µs y el movimiento dura 200/100 = 2 s, es decir 100 actualizaciones.

En grados el cálculo es igual. Con un servo calibrado a 1000 µs por 90° (unos 11,11 µs/°), 100 µs/s equivalen a 9 °/s. Convertir de una unidad a otra depende de la calibración del tema 15, no de la biblioteca.

Este perfil tiene un defecto estético: la velocidad pasa de 0 a vmáx en un ciclo y vuelve a 0 en otro. La referencia ya no salta, pero sus cambios de velocidad sí. Para un servo pequeño y una carga ligera suele ser suficiente; para mecanismos más pesados conviene suavizar también esos extremos.

04 · Una forma para el movimiento

Interpolación en el tiempo

Otra estrategia es fijar la duración T del movimiento y calcular la referencia como función del tiempo:

x(t) = x0 + (x1 − x0) · f(s),   s = t / T,   0 ≤ s ≤ 1

La función f(s) va de 0 a 1 y define la forma. Dos opciones habituales:

Con Δ = x1 − x0. Velocidades y aceleraciones son las ideales de la referencia, no del eje.
Perfilf(s)Velocidad picoAceleración máxima
LinealsΔ / T (constante)No acotada: salto de velocidad en los extremos
Suave (polinomio cúbico)3s² − 2s³1,5 · Δ / T, en el punto medio6 · Δ / T², en los extremos

El perfil cúbico parte y termina con velocidad nula, por lo que no hay saltos de velocidad. Su costo es una velocidad pico un 50 % mayor que la media: para una misma duración, la parte central es más rápida que en el lineal.

Este perfil cúbico todavía tiene un salto de aceleración en los extremos (pasa de 0 a 6Δ/T²). Es un buen primer paso, pero no el único. El tema 17 construye el perfil trapezoidal, que controla la aceleración de forma explícita.

Ambos enfoques pueden convivir: el interpolador define la forma y un chequeo previo verifica que su velocidad pico no supere el máximo permitido, alargando T si hace falta.

05 · Un programa que sigue vivo

Tiempo real sin bloquear el programa

La tentación inicial es avanzar un poco y esperar con delay(20). Funciona en un ejemplo mínimo, pero detiene todo lo demás: lectura de comandos, sensores, otros servos y la detección de fallos. Es la misma lección del tema 14 sobre pulsos de pasos.

El esquema que usaremos es consultar el reloj en cada vuelta de loop() y actuar solo cuando pasó el período:

  • Guardar el instante del último ciclo con millis().
  • Si ahora − ultimo ≥ período, calcular el paso y actualizar el servo.
  • Medir dt real (ahora − ultimo) en lugar de asumir que siempre vale 20 ms.

Usar el dt medido hace que la velocidad física no dependa de cuánto tarde cada vuelta. Si una escritura por puerto serie retrasa un ciclo, el paso siguiente es proporcionalmente mayor y la velocidad promedio se conserva. La documentación de millis() describe el reloj; recordá que vuelve a cero tras unos 49 días y que la resta con unsigned long lo maneja correctamente.

Contar iteraciones («cada vuelta avanzo 3 µs») no es una medida de tiempo: la velocidad depende de lo que ejecute el resto del programa. Es una causa frecuente de robots que se mueven a distinta velocidad al agregar una función.

Los pulsos hacia el servo los sigue generando la biblioteca en segundo plano (tema 15). Nuestro código solo cambia el ancho solicitado a ritmo moderado; no hace falta escribir más rápido que la frecuencia de la señal.

06 · Dos posiciones distintas

Posición comandada y posición real

El planificador conserva una variable: la posición comandada, la última referencia que envió. El eje del servo está en la posición real, que no conocemos. Bajo carga ligera y velocidades moderadas ambas se parecen; no tienen por qué coincidir.

Esto importa en tres situaciones:

  • Nuevo objetivo en pleno movimiento. Se replanifica desde la posición comandada actual, sin saltar. En el limitador basta cambiar el objetivo. En una interpolación con duración fija hay que iniciar un tramo nuevo con x0 igual a la referencia actual (y, si importa la continuidad de la velocidad, tenerla en cuenta o aceptar un arranque desde reposo).
  • Velocidad de rampa mayor que la que admite el servo con carga. La referencia avanza sin que el eje la siga; el error crece y, cuando la rampa termina, el servo continúa su recorrido a máxima velocidad. Elegí vmáx con margen respecto de lo que se haya verificado con la carga montada.
  • Primer movimiento tras energizar. No se conoce dónde está el eje. La primera orden es un salto desde una posición desconocida, por más suave que sea la rampa posterior. Arrancá cerca de una posición segura, sin carga, como en la primera prueba del tema 15.

Una pausa también merece criterio: detener la rampa a mitad de camino no detiene el servo por sí solo; deja de moverse porque la referencia se congela. Si hay una emergencia real, la respuesta es retirar la potencia por una vía física, no ralentizar la referencia.

07 · Los números también tienen límites

Resolución, redondeo y frecuencia de actualización

writeMicroseconds() recibe un entero. Con la calibración de 1000 µs por 90° la unidad mínima de orden es unos 0,09°. Un movimiento lento puede pedir pasos menores que 1 µs por ciclo: por ejemplo, 20 µs/s con ciclos de 20 ms avanza 0,4 µs por actualización.

Si en cada ciclo se redondea la posición ya redondeada más el paso, el avance puede quedar siempre en cero y el servo no se mueve. La solución es conservar la posición comandada en float (o en una escala con más resolución) y redondear solo al enviar:

xcomandada (float) → us = round(xcomandada) → writeMicroseconds(us)

La frecuencia de actualización también define cuánta «escalera» ve el servo. Con 50 Hz y un movimiento de 60° a 120 °/s, el avance máximo es 2,4° por actualización. Con 10 Hz sería 12°, perceptible como saltos. Actualizar con más frecuencia que la señal de control no aporta, porque el servo solo toma una referencia por período.

Una frecuencia moderada (del orden de 20 a 50 Hz para servos RC) suele equilibrar suavidad y carga del procesador. Es un punto de partida; el valor adecuado depende del modelo, de la velocidad y de la mecánica.

08 · Más de un eje

Varias articulaciones a la vez

Si cada articulación se mueve a su propia velocidad máxima, termina en un instante distinto. El efector, que depende de todas, recorre una trayectoria que no es la que imaginamos. Una solución simple es que todas compartan la misma duración:

Ti = |Δi| / vmáx,i    Tsinc = máx(Ti)    vi' = |Δi| / Tsinc ≤ vmáx,i

La articulación más lenta fija el tiempo y las demás se ralentizan para llegar juntas. Como vi' nunca supera vmáx,i, la sincronización no rompe los límites individuales.

Con perfil lineal, ese escalado por articulación hace que todas avancen la misma fracción del recorrido en cada instante. En el espacio de articulaciones el camino es una recta; en el espacio cartesiano del efector depende de la geometría y suele ser curvo.

El tema 19 desarrolla el movimiento coordinado con varios ejes y secuencias. Aquí fijamos la idea base: una duración común.

09 · Código para la placa

Movimiento con velocidad limitada en Arduino

Continuamos con el montaje del tema 15: UNO R3, servo de posición identificado con centro en 1500 µs y ventana verificada 1400–1600 µs, señal a D6 y alimentación regulada compatible con masa común. Sin carga que pueda caer o trabarse.

Los comandos son los mismos: C conecta y lleva a centro, L y R fijan un objetivo de 1400 o 1600 µs, S retira la señal. La diferencia es que ahora el programa avanza hacia el objetivo a 150 µs/s, un paso por cada 20 ms de reloj.

#include <Servo.h>

Servo eje;
const byte PIN_SERVO = 6;
const float MIN_US = 1400, CENTRO_US = 1500, MAX_US = 1600;
const float VMAX_US_S = 150.0;               // velocidad máxima de la referencia
const unsigned long PERIODO_MS = 20;         // actualización de la referencia
const unsigned long SIN_ORDEN_MS = 5000;     // retirar señal tras inactividad

float comandado = CENTRO_US;
float objetivo = CENTRO_US;
unsigned long ultimoCiclo = 0, ultimaOrden = 0;

float limitar(float actual, float meta, float pasoMax) {
  const float error = meta - actual;
  if (error > pasoMax) return actual + pasoMax;
  if (error < -pasoMax) return actual - pasoMax;
  return meta;
}

void quitarSenal() {
  eje.detach();
  digitalWrite(PIN_SERVO, LOW);
}

void setup() {
  digitalWrite(PIN_SERVO, LOW);
  pinMode(PIN_SERVO, OUTPUT);
  Serial.begin(115200);
  Serial.println("C: centro; L/R: objetivo con rampa; S: quitar señal");
}

void loop() {
  const unsigned long ahora = millis();

  if (eje.attached() && ahora - ultimaOrden >= SIN_ORDEN_MS) {
    quitarSenal();
    Serial.println("Sin pulsos; estado mecanico no confirmado");
  }

  if (Serial.available() > 0) {
    const char cmd = Serial.read();
    if (cmd == 'S') {
      quitarSenal();
    } else if (cmd == 'C') {
      if (!eje.attached()) {
        comandado = CENTRO_US;               // salto inicial desde posición desconocida
        eje.attach(PIN_SERVO, 1000, 2000);
        eje.writeMicroseconds((int)(comandado + 0.5));
        ultimoCiclo = ahora;
      }
      objetivo = CENTRO_US;
      ultimaOrden = ahora;
    } else if (eje.attached() && (cmd == 'L' || cmd == 'R')) {
      objetivo = (cmd == 'L') ? MIN_US : MAX_US;
      ultimaOrden = ahora;
    }
  }

  if (eje.attached() && ahora - ultimoCiclo >= PERIODO_MS) {
    const float dt = (ahora - ultimoCiclo) / 1000.0;
    ultimoCiclo = ahora;
    comandado = limitar(comandado, objetivo, VMAX_US_S * dt);
    eje.writeMicroseconds((int)(comandado + 0.5));
  }
}

La variable comandado es float y solo se redondea al escribir. El paso usa el dt medido, de modo que la velocidad de la referencia no cambia si otra parte del programa demora una vuelta.

Con 150 µs/s, ir de 1400 a 1600 µs lleva 200/150 ≈ 1,33 s. Si llega L en mitad del recorrido, el objetivo cambia y la referencia continúa desde donde estaba, sin saltar. Todas las órdenes siguen dentro de 1400–1600 µs porque el objetivo solo toma tres valores constantes.

Las condiciones de seguridad del tema 15 se mantienen. La primera orden C sigue siendo un salto desde una posición desconocida; al vencer el plazo se retira la señal, no la alimentación; y el comportamiento sin pulsos depende del servo. El plazo ahora es de 5 s para no cortar una rampa lenta; ajustalo si aumentás el recorrido o reducís la velocidad.

L y R siguen siendo nombres de comandos: el sentido físico depende del montaje. El programa no verifica llegada ni carga. El perfil sigue siendo el de una rampa: el próximo tema agrega aceleración y desaceleración controladas.

10 · Exploración interactiva

Laboratorios: perfil de movimiento y sincronía

1. Tres formas de ir de A a B

Un movimiento de Δ grados con velocidad máxima vmáx. El salto no tiene planificación. El lineal usa T = Δ/vmáx. El suave usa un polinomio cúbico con T = 1,5·Δ/vmáx, de modo que su velocidad pico coincide con el máximo. La escala supone 1000 µs por 90°.

Posición comandada y velocidad en el tiempoLa línea escalonada muestra la referencia que se envía en cada actualización. La línea punteada muestra la velocidad ideal del perfil elegido. El eje horizontal cubre el tiempo del movimiento más largo posible con esos parámetros.BA0— sLínea continua: referencia comandadaLínea punteada: velocidad ideal
Duración del movimiento—
Velocidad pico de la referencia—
Aceleración máxima ideal—
Avance máximo por actualización—

Modificá recorrido, velocidad y perfil.

Con Δ = 60° y 120 °/s, la rampa lineal dura 0,5 s y la suave 0,75 s. Ambas llegan al mismo punto con la misma velocidad pico, pero la suave pasa más tiempo lenta cerca de los extremos y su aceleración máxima es 6·60/0,75² = 640 °/s². Con 50 Hz, el avance máximo es de unos 2,4° (unos 27 µs) por actualización.

Pasá a 10 Hz y mirá la escalera: el mismo movimiento se vuelve más tosco. Elegí «Salto directo» para ver el caso sin planificación: la referencia llega de inmediato a B y el programa no controla la velocidad real, que queda en manos del servo.

2. Dos articulaciones, un mismo instante de llegada

La articulación 1 tiene un límite de 90 °/s. La articulación 2 permite elegir su límite. Con perfil lineal, cada una necesita Ti = Δi/vmáx,i. Si se sincronizan, ambas usan el mayor de los dos tiempos.

Tiempo propio articulación 1 / 2—
Tiempo sincronizado—
Velocidades sincronizadas 1 / 2—
Diferencia sin sincronizar—

Modificá recorridos y límites.

Con los valores iniciales, la articulación 1 necesita 0,667 s y la 2 solo 0,5 s. Sincronizadas, ambas usan 0,667 s y la 2 avanza a 135 °/s en lugar de 180 °/s. Bajá el límite de la 2 a 60 °/s: tarda 1,5 s, ahora es ella la que manda y la 1 baja a 40 °/s. Ningún límite individual se supera.

El modelo ignora aceleraciones, pérdidas y la respuesta real del servo. Muestra el reparto de tiempos, no una garantía de llegada.

11 · Código ejecutable

Comparar perfiles en Python

Vamos a mover una referencia de 1400 a 1600 µs en 2 s con dos métodos: el limitador a 100 µs/s y la interpolación cúbica con T = 2 s. Muestreamos cada 0,25 s para que la tabla sea legible; en el robot el período sería de 20 ms.

Probalo en el navegador. Python se descarga con Pyodide la primera vez y requiere conexión. El código es editable y no controla un servo real.

def limitar(actual, objetivo, vmax, dt):
    paso = vmax * dt
    error = objetivo - actual
    if abs(error) <= paso:
        return objetivo
    return actual + (paso if error > 0 else -paso)

def suave(p0, p1, t, duracion):
    s = min(1.0, max(0.0, t / duracion))
    return p0 + (p1 - p0) * (3 * s**2 - 2 * s**3)

p0, p1, dt = 1400, 1600, 0.25
tiempos = [k * dt for k in range(9)]

lineal = [p0]
for _ in tiempos[1:]:
    lineal.append(limitar(lineal[-1], p1, 100, dt))
cubico = [suave(p0, p1, t, 2.0) for t in tiempos]

for t, a, b in zip(tiempos, lineal, cubico):
    print(f"t={t:4.2f} s  limitador={a:6.0f} µs  suave={b:6.0f} µs")

salto_a = max(abs(y - x) for x, y in zip(lineal, lineal[1:]))
salto_b = max(abs(y - x) for x, y in zip(cubico, cubico[1:]))
print(f"Máximo cambio por ciclo: limitador={salto_a:.1f} µs; suave={salto_b:.1f} µs")
print(f"Velocidad media de ambos: {(p1 - p0) / 2.0:.0f} µs/s")

Ambos llegan a 1600 µs en 2 s, pero el limitador avanza 25 µs por muestra (100 µs/s) y el suave llega a 36,7 µs por muestra, cerca de 147 µs/s medidos con este muestreo grueso (el pico teórico es 150 µs/s). La velocidad media es 100 µs/s en ambos casos.

La lección práctica es que una misma duración no implica una misma exigencia. Si el servo o la carga aceptan 100 µs/s como máximo, el perfil cúbico de 2 s no sirve: hay que alargarlo a 3 s para que su pico sea 100 µs/s.

Probá cambiar el período de muestreo a 0,02 y reducir la tabla a unos pocos puntos para ver el comportamiento fino. También probá el límite a 300 µs/s para comprobar que la rampa llega antes y que el último paso es más corto que los anteriores.

12 · Método y diagnóstico

Pruebas y diagnóstico

  1. Verificá primero el movimiento sin planificador (tema 15) en una ventana pequeña y sin carga.
  2. Elegí una vmáx baja y comprobá visualmente que el eje sigue a la referencia.
  3. Registrá con millis() el instante de inicio y de fin de la rampa, y compará la duración con el cálculo.
  4. Subí la velocidad de a poco con la carga real montada; observá vibración, calentamiento y caída de tensión.
  5. Probá un cambio de objetivo a mitad de recorrido y comprobá que no haya saltos.
  6. Provocá una demora en el programa (por ejemplo, una impresión larga) y verificá que la velocidad se mantiene.
  7. Con varias articulaciones, comprobá que llegan juntas y que ninguna supera su límite individual.
«Si el movimiento se ve brusco, subo la frecuencia de actualización»

Ayuda hasta cierto punto, pero el servo toma una referencia por período de señal. Si el tirón viene de aceleraciones en los extremos, hace falta un perfil mejor, no más actualizaciones.

«La rampa dura 2 s, así que el eje tarda 2 s»

La duración es la de la referencia. El eje puede retrasarse bajo carga o tardar en asentarse después. Para saberlo hace falta medirlo.

«Avanzo 3 µs por vuelta de loop(), eso es una velocidad»

Solo si el período de cada vuelta es constante, y no lo es. Usá millis() y el dt medido.

«El servo no se mueve con velocidades lentas»

Revisá si estás redondeando la posición en cada ciclo. Con pasos menores que 1 µs la posición entera no avanza: acumulá en float y redondeá solo al escribir.

«El interpolador cúbico es más suave, así que es siempre mejor»

Tiene el mismo recorrido y duración, pero mayor velocidad pico y un salto de aceleración en los extremos. Es una mejora de continuidad, no una garantía de menor esfuerzo.

«Con rampa lenta ya no necesito fuente robusta»

Se reduce el pico de arranque, pero la retención y el movimiento cargado siguen exigiendo corriente. Verificá con la carga real.

13 · Práctica guiada

Predecí antes de comprobar

1. Limitador de rampa

Una articulación debe moverse 80° con límite de 40 °/s y actualización a 50 Hz. ¿Cuánto dura el movimiento, cuántas actualizaciones requiere y cuánto avanza en cada una?

Ver solución

T = 80/40 = 2 s. A 50 Hz: 2 × 50 = 100 actualizaciones. Paso: 40/50 = 0,8°, unos 8,9 µs con 11,11 µs/°.

2. Perfil cúbico

Un movimiento de 90° dura 1,5 s con interpolación cúbica. ¿Cuál es la velocidad pico y la aceleración máxima ideal?

Ver solución

Pico: 1,5 · 90 / 1,5 = 90 °/s. Aceleración máxima: 6 · 90 / 1,5² = 240 °/s², en los extremos. La velocidad media es de 60 °/s.

3. Paso por ciclo

La referencia avanza a 150 µs/s con un ciclo de 20 ms. ¿Cuántos µs por ciclo? ¿Cuánto tarda de 1500 a 1600 µs?

Ver solución

150 × 0,02 = 3 µs por ciclo. Tiempo: 100/150 ≈ 0,667 s, aproximadamente 33 ciclos.

4. Cambio de objetivo

La referencia comandada está en 1540 µs yendo hacia 1600 µs y llega la orden de ir a 1400 µs, con 150 µs/s. ¿Desde dónde parte el nuevo movimiento y cuánto tarda?

Ver solución

Desde la posición comandada, 1540 µs, sin saltar. Debe recorrer 140 µs: 140/150 ≈ 0,933 s. Si el eje iba retrasado, su posición real puede diferir de 1540 µs; el planificador no lo sabe.

5. Sincronización

La articulación A debe recorrer 40° con límite de 80 °/s y la B, 100° con límite de 100 °/s. ¿Cuál es el tiempo sincronizado y las velocidades resultantes?

Ver solución

TA = 0,5 s, TB = 1 s, Tsinc = 1 s. Velocidades: A = 40 °/s (bajo su límite de 80) y B = 100 °/s (en su límite).

6. Tiempo mal medido

Un programa avanza 3 µs por cada vuelta, pensando que dura 20 ms (150 µs/s). Al agregar impresiones por serie cada vuelta dura 25 ms. ¿Qué velocidad tiene ahora y cómo se corrige?

Ver solución

Avanza 3 µs cada 25 ms: 3/0,025 = 120 µs/s, un 20 % menos. Se corrige midiendo dt con millis() y calculando el paso como v · dt.

14 · Síntesis

Ideas para recordar

  • Un salto de referencia deja la velocidad y el esfuerzo en manos del servo; una trayectoria los pone en manos del programa.
  • El limitador de rampa avanza como máximo vmáx · Δt por ciclo y no necesita conocer la duración.
  • La interpolación fija la duración y una forma; el perfil cúbico tiene velocidad pico 1,5 veces la media.
  • Se compara por velocidad pico y aceleración, no solo por duración.
  • El tiempo se mide con millis() y dt real; delay() y contar vueltas son malas bases de velocidad.
  • La posición comandada se acumula en float y se redondea al enviar.
  • Un objetivo nuevo se replanifica desde la posición comandada; esta no es una medición del eje.
  • Para llegar juntas, las articulaciones comparten la duración de la más lenta.

¿Listo para el tema 17?

Antes de avanzar, elegí un movimiento y una velocidad máxima, calculá su duración con rampa lineal y con perfil cúbico, y estimá la aceleración máxima de este último. Explicá qué le pasaría al movimiento si el eje no pudiera seguir la referencia.

En el próximo tema estudiamos perfiles de velocidad: aceleración y desaceleración trapezoidal para evitar derrapes y tirones.