22 votos

¿Hay alguna forma de detener que los servos "se sacudan"?

Muy simplemente, estoy controlando servos (servos micro 9g) basados en algunos datos leídos desde otro lugar. Todo funciona bien excepto que los servos constantemente "tiemblan". Es decir, vibran hacia atrás con movimientos muy sutiles (con movimientos intermitentes de 1/2 -> 1 cm aproximadamente).

Intenté corregir este problema en el software haciendo algo como:

  do{
    delay(DTIME);
    positionServo();
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Posición X: ");
    lcd.print(xRead);
    lcd.setCursor(0,1);
    lcd.print("Posición Y: ");
    lcd.print(yRead);
  }while( readChange() ); //mientras haya habido cambio

Donde el do-while es necesario para inicializar las variables que almacenan el valor mapeado del servo (usando la biblioteca de servo de arduino).

La función readChange() está definida como:

int readChange(){
  int x_Temp, y_Temp;

  x_Temp = map(analogRead(x_axisReadPin), 0, 1023, 0, 179);
  y_Temp = map(analogRead(y_axisReadPin), 0, 1023, 0, 179);

  if( abs(x_Temp - xRead) < DEG && abs(y_Temp - yRead) < DEG ) return 0; // sin cambio
  else return 1; //cambio
}

Donde xRead es el valor que se inicializó (la primera salida mapeada del servo).

Realmente este no es un buen enfoque. Requiere que AMBOS valores no hayan cambiado en un factor de DEG (~10 grados, o ~0.28V en mi caso). Si escribo la función de tal manera que cualquiera de ellos sea menos que DEG, ¿qué pasa si solo estaba cambiando un servo a la vez? Así que hay un dilema.

¿Es esto simplemente una propiedad de los servos (¿tal vez baratos?) o hay una solución alternativa?


Sería mucho más simple incluir un enlace de pastie. Aquí está todo el código.

He adjuntado dos servos juntos con un puntero láser para permitir dos grados de libertad (X, Y). Hay opciones, basadas en el estado de varios botones, para controlar los servos de varias maneras. La primera es "Movimiento" donde tengo dos fotorresistencias que, según la cantidad de luz expuesta, afectan la posición de los servos. Aún no he implementado el código para controlar los servos mediante un controlador de Xbox. La tercera opción es simplemente movimiento aleatorio.

introducir descripción de la imagen aquí

4 votos

Aparentemente tienes un poco de inestabilidad o ruido en tu controlador de servos. Sin embargo, te adentras en muchos detalles de cosas que parecen no tener nada que ver con el controlador de servos, aparte de la línea no documentada "positionServo();", que podemos suponer es donde están enterrados los detalles. ¿El controlador de servos está cerrado en el micro? ¿Cerrado externamente? ¿Análogo o digital? Si es digital, ¿a qué resolución se está midiendo? Muestra un diagrama de todo el sistema.

0 votos

¿Qué carga está poniendo en los servos?

0 votos

@OlinLathrop ¿Qué quieres decir con "cerrado"? Estoy usando la biblioteca de servos de Arduino. Si te refieres a algunos detalles que puedan estar tratados en el código de la biblioteca, no estoy seguro.

29voto

Van Gale Puntos 387

Cuando se usa la biblioteca Servo en un Arduino, una fuente común de zumbido en el servo es que las rutinas de servo impulsadas por interrupciones en realidad no proporcionan un pulso de salida muy estable. Debido a que el AVR toma interrupciones para el servicio del reloj millis() y otras cosas en el tiempo de ejecución de Arduino, la irregularidad en la biblioteca Servo es del orden de varios microsegundos, lo que se traduce en mucho movimiento en el servo.

La solución para esto es escribir tu propio pulso. Algo así:

cli();
long start = micros();
digitalWrite(PIN, HIGH);
while (micros() - start < duración)
  ;
digitalWrite(PIN, LOW);
sei();

Esto desactivará otras interrupciones y generará un pulso PWM mucho más limpio. Sin embargo, hará que el temporizador "millis()" pierda algunos toques de reloj. (La función "micros()" puede llamarse de otra manera, no recuerdo exactamente cómo.)

En general, para el código crítico en tiempo, quieres deshacerte por completo del tiempo de ejecución de Arduino y escribir el tuyo propio usando el compilador avr-gcc y la biblioteca avr-libc que alimenta el entorno de Arduino. Luego puedes configurar un temporizador para que toque 4 veces por microsegundo, o incluso 16 veces por microsegundo, y obtener una resolución mucho mejor en tu PWM.

Otra causa de zumbido en los servos son servos baratos con sensores baratos, donde los sensores son ruidosos, o cuando la posición exacta solicitada con el pulso en realidad no puede ser codificada por el sensor. El servo verá "moverse a la posición 1822" e intentará hacerlo, pero terminará con el sensor leyendo 1823. El servo luego dirá "moverse un poco hacia atrás" y acabará con el sensor leyendo 1821. ¡Repetir! La solución para esto es usar servos de alta calidad. Idealmente, no servos de hobby en absoluto, sino servos reales con codificadores absolutos ópticos o magnéticos.

Finalmente, si los servos no reciben suficiente energía, o si intentas alimentar su energía desde el riel de 5V en el Arduino, esto generará zumbido inducido por caída de voltaje en los servos, como se sugirió anteriormente. Es posible que puedas arreglarlo con grandes condensadores electrolíticos (que son una buena idea para el filtrado general de todos modos) pero es más probable que quieras asegurarte de que la fuente de energía del servo realmente pueda entregar varios amperios de corriente a la voltaje del servo.

1 votos

Las señales de control de los servos R/C son PWM. El ancho de pulso es nominalmente de 1 a 2 milisegundos, el intervalo de repetición de pulso puede variar entre 20 y 50 milisegundos. Esperaría que cualquier variación de más de aproximadamente 10 microsegundos en el ancho de pulso cause que el servo se ponga nervioso. La variación en el intervalo de repetición generalmente no será un problema si el ancho de pulso es estable. (Mi controlador simple de 555 varió tanto el ancho de pulso como el intervalo de repetición por la misma cantidad: al servo no le importaba).

0 votos

Todo lo que dices es cierto, excepto el jitter -- los servos se moverán antes de que el ancho del pulso se "apague" por 10 us. ¡Y el jitter de interrupción para el Arduino simple (antes de agregar bibliotecas) puede llegar hasta 10 us! El código que pegué está destinado a generar un pulso sólido en el entorno de Arduino, que generalmente no es tan bueno en pulsos de servo sólidos como un circuito 555 dedicado.

7 votos

Acabo de escribir un artículo mostrando cómo generar pulsos precisos en Arduino como el código anterior, excepto que utiliza el hardware Timer -y no es necesario desactivar las interrupciones y alterar el tiempo de ejecución de Arduino.

23voto

John R. Strohm Puntos 1559

Esto se llama "zumbido".

Hay un par de cosas que lo causarán. La inestabilidad en la alimentación al servo es una causa común. Los servos R/C pueden producir picos GRANDES cuando ponen el motor en movimiento por primera vez.

Hace muchos años, jugué con un servo estándar Tower Hobbies Royal Titan, controlándolo desde un 555 y un inversor de un transistor. Circuito de control muy simple. Aprendí que el motor del servo consumía 250 mA de la fuente de 5V mientras estaba en movimiento continuo. Zumbando, fácilmente consumía picos de medio amperio. (Tal vez más: solo estaba monitoreando el medidor de corriente en mi fuente de banco, no estaba midiendo con un shunt de detección de corriente.)

Tomó 220 uF directamente a través de mi servo para domarlo.

Prueba colocando un condensador electrolítico, al menos 100 uF, directamente a través de la fuente de alimentación al servo, tan cerca eléctricamente del servo como puedas, y ve si eso ayuda.

Basado en esos experimentos, nunca consideraría usar servos R/C para NADA sin agregar capacitores. Eso incluye modelos radiocontrolados.

Esto también puede ser causado por suciedad en el potenciómetro del servo dentro del servo. Prueba primero con el condensador.

6voto

Jeroen Landheer Puntos 3346

¿Tu zumbido/temblor solo ocurre cuando estás en o cerca de los límites del servo (0 grados o 180 grados)? Si es así, puede haber una solución sencilla para ti. He encontrado que los servos baratos no saben cómo mantenerse en los límites de su movimiento muy bien, lo que puede causar el zumbido/temblor que estás mencionando. Sin embargo, si solo limitas su rango a 10~170 grados, el problema se solucionará.

Si eso no es suficiente para ti, puedes seguir las soluciones más complejas mencionadas en las otras respuestas, como una mejor alimentación, mejores sensores de servo, etc.

0 votos

Sí, para mi SG90 estos valores son de 18 a 162. En realidad, no llegó a hacer que los 32 grados fueran inalcanzables, quizás solo la mitad de eso.

6voto

Alistair Bell Puntos 345

He solucionado mi problema "apagando el servo" después de moverlo. Ejemplo:

pinMode(PIN, OUTPUT);
myservo.write(grado);
//dar tiempo al servo para moverse
delay(5000);
pinMode(PIN, INPUT);

PIN es el pin PWM conectado a tu servo. Al cambiarlo a modo de entrada pude detener la vibración. Esta no es la solución óptima y sugeriría intentar las otras soluciones primero.

1 votos

Intenté las otras soluciones, esta fue la única que funcionó, +1. ¡Gran idea cuando todo lo demás falla!

3voto

C.Slade Puntos 8

Tuve el mismo problema con los servos MG90S (jittering), mis líneas de señal son relativamente largas (60~70cm), colocar un condensador de 103 (10nF) sobre las líneas de señal y tierra me solucionó el problema (coloqué el condensador en algún lugar en el medio, en el punto donde el cable original del servo se conecta a mi cable interno).

Además no podía usar la librería estándar de Servo porque el primer temporizador que coge en el Arduino Mega es Timer-5 y lo necesito para medir la frecuencia. Como sólo uso 10 servos extraje el código clave de la librería Servo y lo cambié para usar Timer-1 (cada temporizador soporta un máximo de 12 servos en el Mega).

El codigo stand-alone esta abajo como referencia, si quieres incluirlo en tu propio proyecto entonces puedes usar la parte de arriba solamente, la parte de abajo es para probar la parte de arriba (escucha en el puerto serie, puedes dar comandos sX y vX, donde sX selecciona un servo, s0 seleccionaria el primer servo, vX fija la posicion del servo en nosotros, asi que v1500 pondria el servo0 en la posicion media, asumiendo que diste un comando s0 primero).

//----------------------------------------------------------------
// This is the actual servo code extracted from the servo library
//----------------------------------------------------------------

#include <avr/pgmspace.h>

//----converts microseconds to tick (assumes prescale of 8)
#define usToTicks(_us)    (( clockCyclesPerMicrosecond()* _us) / 8)

#define MIN_PULSE_WIDTH     544     // the shortest pulse sent to a servo  
#define MAX_PULSE_WIDTH     2400    // the longest pulse sent to a servo 
#define DEFAULT_PULSE_WIDTH 1500    // default pulse width when servo is attached
#define REFRESH_INTERVAL    20000   // minumim time to refresh servos in microseconds

#define TRIM_DURATION       2       // compensation ticks to trim adjust for digitalWrite delays // 12 August 2009

struct s_servar {
    //----counter for the servo being pulsed for each timer (or -1 if refresh interval)
    int8_t  channel;
};
static volatile struct s_servar gl_vars;

//----maximum number of servos controlled by one timer 
#define SERVOS_PER_TIMER    12
//----this can not be higher than SERVOS_PER_TIMER
#define SERVO_AMOUNT        6

struct s_servo {
    volatile unsigned int   ticks;
    unsigned char           pin;
};
struct s_servo  gl_servos[SERVO_AMOUNT] = {
    { usToTicks(DEFAULT_PULSE_WIDTH), 22 },
    { usToTicks(DEFAULT_PULSE_WIDTH), 23 },
    { usToTicks(DEFAULT_PULSE_WIDTH), 24 },
    { usToTicks(DEFAULT_PULSE_WIDTH), 25 },
    { usToTicks(DEFAULT_PULSE_WIDTH), 26 },
    { usToTicks(DEFAULT_PULSE_WIDTH), 27 },
};

ISR(TIMER1_COMPA_vect) {
    unsigned char       servooff;
    if(gl_vars.channel < 0 ) {
        //----channel set to -1 indicated that refresh interval completed so reset the timer
        TCNT1 = 0;
    }
    else{
        servooff = gl_vars.channel;
        if(servooff < SERVO_AMOUNT) {
            //----end the pulse
            digitalWrite(gl_servos[servooff].pin, LOW);
        }
    }
    //----increment to the next channel
    gl_vars.channel++;
    servooff = gl_vars.channel;
    if(servooff < SERVO_AMOUNT) {
        //----set timer interrupt for pulse length
        OCR1A = TCNT1 + gl_servos[servooff].ticks;
        //----start the pulse
        digitalWrite(gl_servos[servooff].pin, HIGH);
    }
    else {
        // finished all channels so wait for the refresh period to expire before starting over
        //----allow a few ticks to ensure the next OCR1A not missed
        if(((unsigned)TCNT1) + 4 < usToTicks(REFRESH_INTERVAL)) {
            OCR1A = (unsigned int)usToTicks(REFRESH_INTERVAL);
        }
        else {
            //----at least REFRESH_INTERVAL has elapsed
            OCR1A = TCNT1 + 4; 
        }
        //----this will get incremented at the end of the refresh period to start again at the first channel
        gl_vars.channel = -1;
    }
}

void InitServoISR() {
    unsigned char   ct;
    gl_vars.channel = -1;
    //----init timer 1
    TCCR1A = 0;             // normal counting mode
    TCCR1B = _BV(CS11);     // set prescaler of 8
    TCNT1 = 0;              // clear the timer count
    TIFR1 |= _BV(OCF1A);    // clear any pending interrupts;
    TIMSK1 |= _BV(OCIE1A);  // enable the output compare interrupt
    //----set all servo pins to output
    for(ct = 0; ct < SERVO_AMOUNT; ct++) {
        pinMode(gl_servos[ct].pin, OUTPUT); 
    }
}

void SetServoMicroSecs(unsigned char servooff, unsigned short value) {
    uint8_t oldSREG;
    if(servooff < SERVO_AMOUNT) {
        //----ensure pulse width is in range
        if(value < MIN_PULSE_WIDTH) { value = MIN_PULSE_WIDTH; }
        else {
            if(value > MAX_PULSE_WIDTH) { value = MAX_PULSE_WIDTH; }
        }
        value -= TRIM_DURATION;
        value = usToTicks(value);
        oldSREG = SREG;
        cli();
        gl_servos[servooff].ticks = value;
        SREG = oldSREG;
    }
}

//------------------------------------------------
// This is code to test the above servo functions
//------------------------------------------------

#define ERR_OK          0
#define ERR_UNKNOWN     1
#define ERR_OUTOFRANGE  2

#define SERDEBUG_CODE
#define MAX_SER_BUF     12

void setup() { 
    InitServoISR();

    #ifdef SERDEBUG_CODE
    Serial.begin(9600);
    Serial.println(F("Start"));
    #endif
}

void loop() {
    #ifdef SERDEBUG_CODE
    uint8_t         ct, chr;
    char            buf[MAX_SER_BUF];
    ct = 0;
    #endif   
    //----main while loop
    while(1) {
        #ifdef SERDEBUG_CODE
        //--------------------
        // Serial Port
        //--------------------
        while (Serial.available() > 0) {
            chr = Serial.read();
            if(chr == '\n') {
                ProcSerCmd(buf, ct);
                ct = 0;
            }
            else {
                //----if for some reason we exceed buffer size we wrap around
                if(ct >= MAX_SER_BUF) { ct = 0; } 
                buf[ct] = chr;
                ct++;
            }
        }
        #endif
    }
}

//------------------------------
// Serial Port Code
//------------------------------

#ifdef SERDEBUG_CODE
uint16_t RetrieveNumber(char *buf, uint8_t size) {
    //--------------------------------------------------------------
    // This function tries to convert a string into a 16 bit number
    // Mainly for test so no strict checking
    //--------------------------------------------------------------
    int8_t  ct;
    uint16_t    out, mult, chr;
    out = 0;
    mult = 1;
    for(ct = size - 1; ct >= 0; ct--) {
        chr = buf[ct];
        if(chr < '0' || chr > '9') { continue; }
        chr -= '0';
        chr *= mult;
        out += chr;
        mult *= 10;
    }
    return(out);
}

void ProcSerCmd(char *buf, uint8_t size) {
    //-----------------------------------------------------------
    // supported test commands
    // sX   X = 0 to SERVO_AMOUNT       Sets the servo for test
    // vX   X = MIN to MAX PULSE WIDTH  Sets the test servo to value X
    //-----------------------------------------------------------
    static unsigned char    lgl_servooff = 0;
    uint8_t                 chr, errcode;
    uint16_t                value;
    errcode = 0;
    while(1) {
        chr = buf[0];
        //----test commands (used during development)
        if(chr == 's') {
            value = RetrieveNumber(buf + 1, size - 1);
            if(value < 0 || value >= SERVO_AMOUNT) { errcode = ERR_OUTOFRANGE; break; }
            lgl_servooff = (unsigned char)value;
            break;
        }
        if(chr == 'v') {
            value = RetrieveNumber(buf + 1, size - 1);
            if(value < MIN_PULSE_WIDTH || value > MAX_PULSE_WIDTH) { errcode = ERR_OUTOFRANGE; break; }
            SetServoMicroSecs(lgl_servooff, value);
            break;
        }
        errcode = ERR_UNKNOWN;
        break;
    }
    if(errcode == 0) {
        Serial.println(F("OK"));
    }
    else {
        Serial.write('E');    
        Serial.println(errcode);
    }
}
#endif

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X