Existe-t-il un moyen d'empêcher les servos de «trembler»?


20

Très simplement, je contrôle les servos (9g Micro Servos) sur la base de certaines données lues d'ailleurs. Tout fonctionne bien sauf que les servos "tremblent" constamment. Autrement dit, ils vibrent en arrière avec des mouvements très subtils (avec des mouvements intermittents de 1/2 -> 1 cm environ).

J'ai essayé de corriger ce problème dans le logiciel en faisant quelque chose comme:

  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

Lorsque le do-while est nécessaire, initialisez les variables qui stockent la valeur d'asservissement mappée (à l'aide de la bibliothèque d'asservissements Arduino).

La fonction readChange () est définie comme:

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
}

Où xRead est la valeur qui a été initialisée (la première sortie d'asservissement mappée).

Bien que ce ne soit vraiment pas une bonne approche. Cela nécessite que les DEUX valeurs ne soient pas modifiées par un facteur DEG (~ 10 degrés, ou ~ 0,28 V dans mon cas). Si j'écris la fonction de telle sorte que OU soit inférieur à DEG, que se passe-t-il si je ne change qu'un seul servo à la fois? Il y a donc un délimitation ..

Est-ce simplement une propriété des servos (peut-être bon marché?) Ou existe-t-il une solution?


Il serait beaucoup plus simple d'inclure un lien pastie. Voici le code complet: http://pastie.org/8191459

J'ai attaché deux servos avec un pointeur laser pour permettre deux degrés de liberté (X, Y). Il existe des options, basées sur l'état de plusieurs boutons, pour contrôler les servos de différentes manières. Le premier est "Motion" où j'ai deux photorésistances qui, en fonction de la quantité d'exposition à la lumière, affectent la position des servos. Je n'ai pas encore implémenté le code pour contrôler les servos par un contrôleur Xbox. Et la troisième option est juste un mouvement aléatoire.

entrez la description de l'image ici


4
Vous avez apparemment un peu d'instabilité ou de bruit dans votre servocommande. Cependant, vous entrez dans de nombreux détails qui semblent n'avoir rien à voir avec le servo-contrôleur, à part la ligne non documentée "positionServo ();", que nous ne pouvons que deviner où les détails sont enterrés. Le servo-contrôleur est-il fermé dans le micro? Fermé à l'extérieur? Analogique ou numérique? S'il est numérique, à quelle résolution est-il mesuré? Affichez un diagramme de l'ensemble du système.
Olin Lathrop du

Quelle charge mettez-vous sur les servos?
Chris Laplante

4
@OlinLathrop - (S) Il utilise des servos modèles radiocommandés standard, qui ont la boucle servo entière cuite dans l'appareil. sherrellbc - "Servo" est un terme très, très général. Malheureusement, les fabricants de composants de modèles RC ont choisi le terme le moins descriptif pour les appareils qu'ils produisent. Étant donné que nous traitons ici la plupart des différents types de servos et de servo-systèmes, il est probablement judicieux de spécifier que vos "servos" sont des servomoteurs radiocommandés.
Connor Wolf

1
Votre système est trop complexe pour que nous puissions le dépanner pour vous. Simplifiez-le et voyez si vous avez toujours le problème. Lorsque vous avez un système minimal qui reproduit le problème et que vous ne pouvez toujours pas le résoudre vous-même, il devient alors approprié de demander de l'aide.
Phil Frost

12
Remarque générale pour la conception de systèmes de direction laser: placez des miroirs sur les servos, puis dirigez-les l'un sur l'autre. De cette façon, vous n'avez pas besoin d'avoir un servo monté sur l'autre, ni le laser monté sur les servos, et vous pouvez ensuite les boulonner fermement.
pjc50

Réponses:


27

Lorsque vous utilisez la bibliothèque de servomoteurs sur un Arduino, une source courante de buzz de servomoteurs est que les routines de servomoteurs déclenchées par interruption ne donnent pas réellement une impulsion de sortie très stable. Parce que l'AVR prend des interruptions pour l'entretien de l'horloge millis () et d'autres choses dans l'exécution Arduino, la gigue dans la bibliothèque Servo est de l'ordre de plusieurs microsecondes, ce qui se traduit par beaucoup de mouvement dans le servo.

La solution est d'écrire votre propre pouls. Quelque chose comme ça:

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

Cela désactivera les autres interruptions et générera une impulsion PWM beaucoup plus propre. Cependant, cela fera manquer au chronomètre "millis () quelques tics d'horloge. (La fonction" micros () "peut être appelée quelque chose d'autre - j'oublie exactement quoi.)

En général, pour chronométrer du code critique, vous voulez vous débarrasser entièrement du runtime Arduino et écrire le vôtre en utilisant le compilateur avr-gcc et la bibliothèque avr-libc qui alimentent l'environnement Arduino. Ensuite, vous pouvez configurer une minuterie pour cocher 4 fois par microseconde, voire 16 fois par microseconde, et obtenir une bien meilleure résolution dans votre PWM.

Une autre cause de bourdonnement dans les servos est les servos bon marché avec des capteurs bon marché, où les capteurs sont bruyants, ou lorsque la position exacte demandée avec l'impulsion ne peut pas réellement être codée par le capteur. Le servo verra "passer à la position 1822" et essaiera de le faire, mais se retrouvera avec le capteur lisant 1823. Le servo dira alors "reculer un peu" et il finit avec le capteur lisant 1821. Répétez! La solution est d'utiliser des servos de haute qualité. Idéalement, pas de servos de loisirs, mais de vrais servos avec encodeurs absolus optiques ou magnétiques.

Enfin, si les servos ne reçoivent pas assez de puissance, ou si vous essayez de les alimenter à partir du rail 5V sur l'Arduino, cela générera un bourdonnement induit par la tension dans les servos, comme suggéré ci-dessus. Vous pouvez peut-être le réparer avec de grands condensateurs électrolytiques (qui sont de toute façon une bonne idée pour le filtrage général), mais vous voudrez probablement vous assurer que votre source d'alimentation servo peut réellement fournir plusieurs ampères de courant à la tension servo.


1
Les signaux de servocommande R / C sont PWM. La largeur d'impulsion est nominalement de 1 à 2 millisecondes, l'intervalle de répétition des impulsions est compris entre 20 et 50 millisecondes. Je m'attendrais à plus d'environ 10 microsecondes de variation de la largeur d'impulsion pour que le servo devienne nerveux. La gigue dans le PRI ne sera généralement pas un problème si la largeur d'impulsion est stable. (Mon contrôleur 555 tout simple variait la largeur d'impulsion et PRI de la même quantité: le servo s'en fichait.)
John R. Strohm

Tout ce que vous dites est vrai, sauf la gigue - les servos tremblent avant que la largeur d'impulsion ne soit "désactivée" par 10 us. Et la gigue d'interruption pour le simple Arduino (avant d'ajouter des bibliothèques) peut aller jusqu'à 10 nous! Le code que j'ai collé est destiné à générer une impulsion stable dans la roche dans l'environnement Arduino, qui n'est généralement pas aussi bon pour les impulsions d'asservissement stables dans la roche qu'un circuit 555 dédié.
Jon Watte

4
Je viens d'écrire un article montrant comment générer des impulsions précises sur Arduino comme le code ci-dessus, sauf qu'il utilise le matériel Timer - et pas besoin de désactiver les interruptions et de gâcher l'exécution d'Arduino.
bigjosh

Notez que l'Arduino ne prend en charge la sortie du minuteur que sur quelques broches (les broches PWM) et que vous ne pouvez pas utiliser les broches Timer0 pour cette méthode. Ainsi, il n'y a que 4 broches pour lesquelles cela fonctionne vraiment sur un Arduino UNO ordinaire. Si vous devez piloter 4 servos ou moins et n'avez pas besoin des minuteries pour autre chose, c'est une bonne option.
Jon Watte

21

C'est ce qu'on appelle "buzz".

Il y a deux ou trois choses qui vont le provoquer. L'instabilité de l'alimentation du servo est une cause courante. Les servos R / C peuvent dessiner de GRANDES pointes lorsqu'ils mettent le moteur en mouvement pour la première fois.

Il y a de nombreuses années, j'ai joué avec un servo Tower Hobbies Royal Titan Standard, le contrôlant à partir d'un 555 et d'un onduleur à un transistor. Circuit de commande très simple. J'ai appris que le servomoteur tirait 250 mA de l'alimentation 5 V en mouvement continu. Bourdonnant, il dessinait facilement des pointes de demi-ampli. (Peut-être plus: je surveillais simplement le compteur de courant sur mon alimentation de banc, pas la portée d'un shunt de détection de courant.)

Il a fallu 220 uF directement sur mon servo pour l'apprivoiser.

Essayez de mettre un condensateur électrolytique, au moins 100 uF, directement à travers l'alimentation du servo, aussi près que possible du servo électriquement, et voyez si cela aide.

Sur la base de ces expériences, je n'envisagerais jamais d'utiliser des servos R / C pour quoi que ce soit sans ajouter de condensateurs. Cela inclut les modèles radiocommandés.

Cela peut également être causé par la saleté dans le pot du servo à l'intérieur du servo. Essayez d'abord le condensateur.


6

Votre bourdonnement / tremblement se produit-il uniquement lorsqu'il se trouve aux limites du servo ou à proximité (0 degré ou 180 degrés)? Si c'est le cas, il peut y avoir une solution simple pour vous. J'ai trouvé que les servos bon marché ne savent pas très bien rester aux limites de leurs mouvements, ce qui peut provoquer le bourdonnement / tremblement dont vous parlez. Cependant, si vous limitez simplement leur plage à 10 ~ 170 degrés, le problème sera résolu.

Si cela ne vous convient pas, vous pouvez suivre les correctifs les plus complexes mentionnés dans les autres réponses, comme une meilleure puissance, de meilleurs capteurs d'asservissement, etc.


Oui, pour mon SG90, ces valeurs sont comprises entre 18 et 162. En fait, cela n'a pas rendu 32 degrés inaccessibles, peut-être seulement la moitié de cela.
Maxim Kachurovskiy,

5

J'ai résolu mon problème en "éteignant le servo" après l'avoir déplacé. Exemple:

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

PINest la broche PWM connectée à votre servo. en le commutant en mode d'entrée, j'ai pu arrêter la vibration. Ce n'est pas une solution optimale et je suggère d'essayer d'abord les autres solutions.


J'ai essayé les autres solutions, c'était la seule à fonctionner, +1. Grande idée quand tout le reste échoue!
Snappawapa

3

J'ai eu le même problème avec les servos MG90S (tremblement), mes lignes de signal sont relativement longues (60 ~ 70cm), plaçant un condensateur 103 (10nF) sur le signal et les lignes de masse ont résolu le problème pour moi (j'ai placé le condensateur quelque part dans le milieu, au point où le câble servo d'origine se connecte à mon câble interne).

De plus, je ne pouvais pas utiliser la bibliothèque de servomoteurs standard car le premier minuteur qu'il saisit sur l'Arduino Mega est le Timer-5 et j'en ai besoin pour la mesure de fréquence. Comme j'utilise seulement 10 servos, j'ai extrait le code clé de la bibliothèque Servo et l'ai changé en utilisant Timer-1 (chaque minuterie prend en charge un maximum de 12 servos sur le Mega).

Le code autonome est ci-dessous pour référence, si vous souhaitez l'inclure dans votre propre projet, vous pouvez utiliser uniquement la partie supérieure, la partie inférieure est pour tester la partie supérieure (il écoute sur le port série, vous pouvez donner sX et les commandes vX, où sX sélectionne un servo, s0 sélectionnerait le premier servo, vX définit la position du servo en nous, donc v1500 mettrait servo0 en position médiane, en supposant que vous avez donné une commande s0 en premier).

//----------------------------------------------------------------
// 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

2

Ma meilleure option dans ce cas était d'attacher et de détacher les servos à chaque opération.

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

PS. ce n'est vraiment pas une qualité du tout, juste une solution de contournement.


1

Alors que d'autres ont suggéré diverses solutions à ce problème de buzz, dans ce fil et dans d'autres forums Arduino, à savoir:

  • Générer sa propre impulsion
  • Fournissez une alimentation 5 V séparément
  • Évitez de pousser à ses limites (par exemple, utilisez 10-170 au lieu de 0-180)
  • Faites passer un condensateur sur
  • Détacher après le mouvement

Dans mon cas, j'ai constaté que le bourdonnement s'arrêtait lorsqu'une alimentation 9V / 2A était branchée sur la carte Arduino. Mais la solution ultime la plus simple était simplement de déplacer lentement le servo:

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

YMMV.


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
    }
}

0

Pour moi, cela ressemble à des erreurs ou à un mauvais réglage de la boucle de rétroaction. Les systèmes de servocommande haut de gamme ont une certaine connaissance des caractéristiques du moteur (inductance, couple, courant de crête, nombre de pôles), de la charge (moment d'inertie) et des conditions instantanées (position, tr / min, back-emf, courant). Avec ces informations, le programme de contrôle du moteur peut faire des prédictions sur ce que le servo fera en réponse à une entrée donnée du contrôleur (c'est-à-dire une entrée courant / tension) et sur cette base, générer l'entrée optimale pour atteindre la sortie souhaitée.

Comme vous pouvez l'imaginer, c'est quelque chose de quelque peu compliqué, mais une recherche sur Internet sur les commentaires des servos vous aidera à démarrer.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.