¿Hay alguna manera de evitar que los servos se “sacudan”?

20

Muy simplemente, estoy controlando los servos (9g Micro Servos) en base a algunos datos leídos desde otro lugar. Todo funciona bien, excepto que los servos constantemente "temblarán". Es decir, vibran con movimientos muy sutiles (con movimientos intermitentes de 1/2 - > 1 cm o menos).

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

  do{
    delay(DTIME);
    positionServo();
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("X position: ");
    lcd.print(xRead);
    lcd.setCursor(0,1);
    lcd.print("Y position: ");
    lcd.print(yRead);
  }while( readChange() ); //while there has been change

Donde sea necesario hacer do-while, inicialice las variables que almacenan el valor de servo asignado (usando la biblioteca de servo de arduino).

La función readChange () se define 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; // no change 
  else return 1; //change
}

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

Aunque, esto realmente no es un buen enfoque. Requiere que AMBOS valores no deban haber cambiado por un factor de DEG (~ 10 grados, o ~ 0.28 V en mi caso). Si escribo la función de modo que O sea menor que DEG, entonces, ¿qué sucede si solo cambio un servo a la vez? Así que hay un delimma ..

¿Esto es simplemente una propiedad de los servos (quizás algunos baratos) o hay una solución?

Sería mucho más simple incluir un enlace de pastel. Aquí está el código completo: enlace

He adjuntado dos servos junto 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. El primero es "Motion", donde tengo dos fotoresistores que, según la cantidad de exposición a la luz, afectan la posición de los servos. Todavía no he implementado el código para controlar los servos mediante un controlador de Xbox. Y la tercera opción es solo un movimiento aleatorio.

    
pregunta sherrellbc

8 respuestas

24

Cuando se utiliza la biblioteca de Servo en un Arduino, una fuente común de zumbido de servo es que las rutinas de servo controladas por interrupciones en realidad no dan un impulso de salida muy estable. Debido a que el AVR toma interrupciones para reparar el reloj millis () y otras cosas en el tiempo de ejecución de Arduino, el jitter 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 como esto:

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

Esto desactivará otras interrupciones y generará un pulso PWM mucho más limpio. Sin embargo, hará que el cronómetro "milis () pierda algunos tics del reloj. (La función" micros () "puede llamarse otra cosa - olvido exactamente qué).

En general, para el código crítico de tiempo, desea deshacerse completamente del tiempo de ejecución de Arduino y escribir el suyo propio mediante el compilador avr-gcc y la biblioteca avr-libc que alimenta el entorno Arduino. Luego puede configurar un temporizador para que marque 4 veces por microsegundo, o incluso 16 veces por microsegundo, y obtenga una resolución mucho mejor en su PWM.

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

Finalmente, si los servos no obtienen suficiente potencia, o si intentas expulsarlos del riel de 5 V en el Arduino, esto generará un zumbido inducido por la caída del voltaje en los servos, como se sugirió anteriormente. Es posible que pueda repararlo con condensadores electrolíticos grandes (que de todos modos son una buena idea para el filtrado general) pero es más probable que desee asegurarse de que su fuente de alimentación servo pueda suministrar varios amperios de corriente a la tensión servo.

    
respondido por el Jon Watte
21

Esto se llama "buzz".

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

Hace muchos años, jugué con un servo Tower Hobbies Royal Titan Standard, controlando desde un 555 y un inversor de un transistor. Circuito de control muerto-simple. Aprendí que el servomotor extraía 250 mA de la fuente de 5 V mientras estaba en movimiento continuo. Zumbando, fácilmente dibujaba picos de medio amperio. (Tal vez más: simplemente estaba monitoreando el medidor de corriente en el suministro de mi banco, no analizando una derivación de detección de corriente).

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

Intente colocar un condensador electrolítico, al menos 100 uF, directamente a través de la fuente de alimentación al servo, lo más cerca posible eléctricamente del servo, y vea si eso ayuda.

Basándome en esos experimentos, nunca consideraría el uso de servos R / C para CUALQUIER COSA sin agregar condensadores. Eso incluye modelos controlados por radio.

Esto también puede ser causado por la suciedad en la olla del servo dentro del servo. Prueba el condensador primero.

    
respondido por el John R. Strohm
6

¿Su zumbido / agitación se produce solo cuando está en o cerca de los límites del servo (0 grados o 180 grados)? Si es así, puede haber una solución simple para usted. Descubrí que los servos baratos no saben cómo mantenerse en los límites de su movimiento muy bien, lo que puede causar el zumbido / sacudida que mencionas. Sin embargo, si solo limita su rango a 10 ~ 170 grados, el problema se solucionará.

Si eso no es lo suficientemente bueno para usted, puede seguir las soluciones más complejas que se mencionan en las otras respuestas, como mejor potencia, mejores sensores de servo, etc.

    
respondido por el nitwit
4

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

pinMode(PIN, OUTPUT);
myservo.write(degree);
//give servo time to move
delay(5000);
pinMode(PIN, INPUT);

PIN es el pin PWM conectado a su servo. cambiando al modo de entrada pude apagar la vibración. Esta no es una solución óptima y sugeriría probar las otras soluciones primero.

    
respondido por el Ramast
3

Tuve el mismo problema con los servos MG90S (fluctuaciones), mis líneas de señal son relativamente largas (60 ~ 70 cm), colocando un condensador de 103 (10nF) sobre la señal y las líneas de tierra solucionaron el problema para mí (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 pude usar la biblioteca Servo estándar porque el primer cronómetro que toma en el Arduino Mega es el Timer-5 y lo necesito para la medición de frecuencia. Como solo uso 10 servos, extraje el código clave de la biblioteca Servo y lo cambié a usar el Temporizador-1 (cada temporizador admite un máximo de 12 servos en el Mega).

El código independiente se encuentra a continuación como referencia. Si desea incluirlo en su propio proyecto, puede usar solo la parte superior, la parte inferior es para probar la parte superior (se escucha en el puerto serie, puede dar comandos de sX y vX, donde sX selecciona un servo, s0 seleccionaría el primer servo, vX establece la posición del servo en nosotros, por lo que v1500 establecería servo0 en la posición central, asumiendo que usted dio 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
    
respondido por el walter K
1

Mi mejor opción en este caso fue adjuntar y separar los Servos en cada operación.

servo1.attach(pinServo1);
for (pos = 0; pos <= servoMax; pos += 1) {
    servo1.write(pos);
    delay(10);
}
servo1.detach(pinServo1);

PS. Esto realmente no tiene ninguna calidad, solo una solución.

    
respondido por el Shikartoos
1

Mientras que otros han sugerido varias soluciones a este problema del servo zumbido, en este hilo y en otros foros de Arduino, a saber:

  • Generar pulso propio
  • Suministre 5V de energía por separado
  • Evite empujar a sus límites (por ejemplo, use 10-170 en lugar de 0-180)
  • Ejecutar un condensador a través
  • Desacoplar después de mover

En mi caso, encontré que el zumbido se detuvo cuando se enchufó una fuente de alimentación de 9V / 2A a la placa Arduino. Pero la solución definitiva más sencilla era simplemente mover el servo lentamente:

for (pos = servo.read(); pos < 180; pos += 2) {
  servo.write(pos);
  delay(40);
}

YMMV.

    
respondido por el user2105117
0

Para mí, esto se ve como errores o falta de ajuste del bucle de retroalimentación. Los sistemas de control de servo de gama alta tienen cierto conocimiento de las características del motor (inductancia, par, corriente máxima, conteo de polos), la carga (momento de inercia) y las condiciones instantáneas (posición, rpm, back-emf, corriente). Con esta información, el programa de control del motor puede hacer predicciones sobre lo que hará el servo en respuesta a una entrada dada del controlador (es decir, entrada de corriente / voltaje) y sobre esa base generar la entrada óptima para lograr la salida deseada.

Como puedes imaginar, esto es algo complicado, pero una búsqueda en Internet con comentarios de servo te ayudará a comenzar.

    
respondido por el EBlake

Lea otras preguntas en las etiquetas