¿Hay alguna manera de evitar que los servos "tiemblen"?

20

Muy simple, estoy controlando los servos (9g Micro Servos) en función de algunos datos leídos desde otro lugar. Todo funciona bien, excepto que los servos se "agitarán" constantemente. Es decir, vuelven a vibrar con movimientos muy sutiles (con movimientos intermitentes de 1/2 -> 1 cm más 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 el do-while, inicialice las variables que almacenan el valor del servo mapeado (usando la biblioteca de servos 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 de servo asignada).

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

¿Es esto simplemente una propiedad de los servos (¿quizás baratos?) O hay una solución alternativa?


Sería mucho más simple incluir un enlace pastie. Aquí está el código completo: http://pastie.org/8191459

He adjuntado dos servos junto con un puntero láser para permitir dos grados de libertad (X, Y). Existen 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 por un controlador Xbox. Y la tercera opción es solo movimiento aleatorio.

ingrese la descripción de la imagen aquí

sherrellbc
fuente
44
Aparentemente tiene un poco de inestabilidad o ruido en su servocontrolador. Sin embargo, entra en muchos detalles de cosas que parecen no tener nada que ver con el servocontrolador, aparte de la línea indocumentada "positionServo ();", que solo podemos adivinar es dónde están enterrados los detalles. ¿Está cerrado el servocontrolador en el micro? Cerrado externamente? Analógico o digital? Si es digital, ¿a qué resolución se está midiendo? Muestra un diagrama de todo el sistema.
Olin Lathrop
¿Cuánta carga estás poniendo en los servos?
Chris Laplante
44
@OlinLathrop - (S) Está utilizando servos modelo estándar controlados por radio, que tienen todo el servo loop incorporado en el dispositivo. sherrellbc - "Servo" es un término muy, muy general. Desafortunadamente, los fabricantes de componentes del modelo RC eligieron el término menos descriptivo para los dispositivos que producen. Dado que aquí tratamos con la mayoría de los diferentes tipos de servos y servosistemas, probablemente sea una buena idea especificar que sus "servos" son servos modelo controlados por radio.
Connor Wolf
1
Su sistema es demasiado complejo para que podamos solucionarlo por usted. Simplifíquelo y vea si todavía tiene el problema. Cuando tiene un sistema mínimo que reproduce el problema y aún no puede solucionarlo usted mismo, entonces es apropiado pedir ayuda.
Phil Frost
12
Nota general para el diseño de sistemas de dirección láser: coloque espejos en los servos, luego dirija uno hacia el otro. De esa manera, no tiene que tener un servo montado en el otro, ni el láser montado en los servos, y luego puede atornillarlos a todos firmemente.
pjc50

Respuestas:

27

Cuando se usa la biblioteca de servos en un Arduino, una fuente común de servo zumbido es que las rutinas de servo controladas por interrupción en realidad no dan un pulso de salida muy estable. Debido a que el AVR toma interrupciones para dar servicio al reloj millis () y otras cosas en el tiempo de ejecución de Arduino, el jitter en la biblioteca de 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 apagará otras interrupciones y generará un pulso PWM mucho más limpio. Sin embargo, hará que el temporizador "millis () pierda algunos tics de reloj. (La función" micros () "puede llamarse de otra manera; no recuerdo exactamente qué).

En general, para sincronizar el código crítico, desea deshacerse por completo del tiempo de ejecución de Arduino y escribir el suyo propio utilizando el compilador avr-gcc y la biblioteca avr-libc que alimenta el entorno Arduino. Luego puede configurar un temporizador para marcar 4 veces por microsegundo, o incluso 16 veces por microsegundo, y obtener 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á "moverse a la posición 1822" e intentará hacerlo, pero termina con el sensor leyendo 1823. Luego, el servo dirá "retroceder un poco" y termina con el sensor leyendo 1821. ¡Repita! La solución para esto es usar servos de alta calidad. Idealmente, no son servos de hobby, sino servos reales con codificadores absolutos ópticos o magnéticos.

Finalmente, si los servos no obtienen suficiente potencia, o si intenta impulsar su potencia desde el riel de 5V en el Arduino, esto generará un zumbido inducido por la caída de voltaje en los servos, como se sugirió anteriormente. Es posible que pueda arreglarlo 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 entregar varios amperios de corriente al voltaje del servo.

Jon Watte
fuente
1
Las señales de servocontrol R / C son PWM. El ancho de pulso es nominalmente 1-2 milisegundos, el intervalo de repetición de pulso es de 20 a 50 milisegundos. Esperaría que más de 10 microsegundos de variación en el ancho del pulso provoquen que el servo se ponga nervioso. La fluctuación de fase en el PRI generalmente no será un problema si el ancho de pulso es estable. (Mi sencillo controlador 555 varió el ancho de pulso y el PRI en la misma cantidad: al servo no le importó.)
John R. Strohm
Todo lo que diga es cierto, excepto la fluctuación de fase: los servos fluctuarán antes de que el ancho de pulso esté "desactivado" por nosotros 10. ¡Y el jitter de interrupción para el Arduino simple (antes de agregar bibliotecas) puede llegar hasta 10 nosotros! El código que pegué tiene la intención de generar un pulso estable en roca en el entorno Arduino, que generalmente no es tan bueno en pulsos servos estables en roca como un circuito 555 dedicado.
Jon Watte
44
Acabo de escribir un artículo que muestra cómo generar pulsos precisos en Arduino como el código anterior, excepto que usa el hardware Timer y no es necesario apagar las interrupciones y estropear el tiempo de ejecución de Arduino.
bigjosh
Tenga en cuenta que Arduino solo admite la salida del temporizador en algunos pines (los pines PWM) y no puede usar los pines Timer0 para este método. Por lo tanto, solo hay 4 pines para los que esto realmente funciona en un Arduino UNO normal. Si necesita conducir 4 o menos servos y no necesita los temporizadores para otra cosa, esa es una buena opción.
Jon Watte
21

Esto se llama "zumbido".

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 dibujar algunos picos GRANDES cuando ponen el motor en movimiento por primera vez.

Hace muchos años, jugué con un servo Tower Titan Hobbies Royal Titan Standard, controlándolo desde un 555 y un inversor de un transistor. Circuito de control muy simple. Aprendí que el servomotor extraía 250 mA del suministro de 5V mientras estaba en movimiento continuo. Zumbando, fácilmente dibujó picos de medio amplificador. (Tal vez más: solo estaba monitoreando el medidor de corriente en el suministro de mi banco, sin detectar una derivación de detección de corriente).

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

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

Basado en esos experimentos, nunca consideraría usar servos R / C para NADA 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. Pruebe el condensador primero.

John R. Strohm
fuente
6

¿Su zumbido / temblor ocurre solo cuando está cerca de los límites del servo (0 grados o 180 grados)? Si es así, puede haber una solución simple para usted. He descubierto que los servos baratos no saben cómo mantenerse al límite de su movimiento muy bien, lo que puede causar el zumbido / temblor 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 mencionadas en las otras respuestas, como mejor potencia, mejores sensores de servo, etc.

simplón
fuente
Sí, para mi SG90 estos valores son de 18 a 162. En realidad, no era inalcanzable 32 grados, tal vez solo la mitad de eso.
Maxim Kachurovskiy
5

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

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

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

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

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

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

El código independiente se encuentra a continuación para referencia, si desea incluirlo en su propio proyecto, entonces puede usar solo la parte superior, la parte inferior es para probar la parte superior (escucha en el puerto serie, puede dar sX y comandos 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 intermedia, suponiendo que primero le dio un comando s0).

//----------------------------------------------------------------
// 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
walter K
fuente
2

Mi mejor opción en este caso era conectar y desconectar los Servos en cada operación.

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

PD. esto realmente no es calidad en absoluto, solo una solución alternativa.

Shikartoos
fuente
1

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

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

En mi caso, descubrí que el zumbido se detuvo cuando se conectó una fuente de alimentación de 9V / 2A a la placa Arduino. Pero la solución final más fácil fue simplemente mover el servo lentamente:

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

YMMV.

usuario2105117
fuente
1
#include <Servo.h>             //Servo library
Servo servo_test;        //initialize a servo object for the connected servo  

int angle = 0;
int sw1 = 7;   // pushbutton connected to digital pin 7
int val=0;

void setup()
{
   servo_test.attach(2);     // attach the signal pin of servo to pin2 of arduino
   pinMode(sw1, INPUT_PULLUP);
}

void loop()
{
    val = digitalRead(sw1);
    if (val == LOW)
    {  
        servo_test.attach(2);     // attach the signal pin of servo to pin2 of arduino
        for(angle = 0; angle < 90; angle += 1)   // command to move from 0 degrees to 90 degrees 
        {                                  
            servo_test.write(angle);                 //command to rotate the servo to the specified angle
            delay(5);                     
        } 
    }
    else
    {
        servo_test.detach();// After servo motor stops we need detach commmand to stop vibration
    }
}
Musthafa Kadersha
fuente
0

Para mí, esto parece errores o un ajuste incorrecto del ciclo de retroalimentación. Los sistemas de servocontrol de gama alta tienen cierto conocimiento de las características del motor (inductancia, par, corriente máxima, recuento de polos), la carga (momento de inercia) y las condiciones instantáneas (posición, rpm, retroceso, 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 puede imaginar, esto es algo complicado, pero una búsqueda en Internet sobre la retroalimentación del servo lo ayudará a comenzar.

EBlake
fuente