Esiste un modo per impedire ai servi di "scuotere"?


20

Molto semplicemente, sto controllando i servi (9g Micro Servos) in base ad alcuni dati letti da altrove. Tutto funziona bene, tranne che i servi "scuotono" costantemente. Cioè, vibrano indietro con movimenti molto sottili (con movimenti intermittenti di 1/2 -> 1 cm circa).

Ho provato a correggere questo problema nel software facendo qualcosa del tipo:

  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

Dove è necessario il do-while inizializzare le variabili che memorizzano il servo valore mappato (usando la servo library di arduino).

La funzione readChange () è definita come:

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
}

Dove xRead è il valore inizializzato (il primo output servo mappato).

Tuttavia, questo non è davvero un buon approccio. Richiede che ENTRAMBI i valori non debbano essere modificati di un fattore DEG (~ 10 gradi o ~ 0,28 V nel mio caso). Se scrivo la funzione in modo tale che OR sia inferiore a DEG, allora cosa succede se cambio solo un servo alla volta? Quindi c'è un delimma ..

È semplicemente una proprietà dei servi (forse economici?) O c'è una soluzione?


Sarebbe molto più semplice includere un link pastie. Ecco il codice completo: http://pastie.org/8191459

Ho collegato due servi insieme a un puntatore laser per consentire due gradi di libertà (X, Y). Ci sono opzioni, basate sullo stato di diversi pulsanti, per controllare i servi in ​​vari modi. Il primo è "Motion" in cui ho due fotoresistori che, in base alla quantità di esposizione alla luce, influenzano la posizione dei servi. Non ho ancora implementato il codice per controllare i servi da un controller Xbox. E la terza opzione è solo il movimento randomizzato.

inserisci qui la descrizione dell'immagine


4
Apparentemente hai un po 'di instabilità o rumore nel tuo servo controller. Tuttavia, ti addentrerai in molti dettagli di cose che sembrano non avere nulla a che fare con il servo controller, a parte la riga non documentata "positionServo ();", che possiamo solo immaginare dove sono sepolti i dettagli. Il servo controller è chiuso nel micro? Chiuso esternamente? Analogico o digitale? Se digitale, a quale risoluzione viene misurata? Mostra un diagramma dell'intero sistema.
Olin Lathrop,

Quanto carico stai caricando sui servi?
Chris Laplante,

4
@OlinLathrop - (S) Sta usando dei servi modello radiocomandati standard, che hanno l'intero loop di servo inserito nel dispositivo. sherrellbc - "Servo" è un termine molto, molto generale. Sfortunatamente, i produttori di componenti del modello RC hanno scelto il termine meno descrittivo per i dispositivi prodotti. Dato che qui trattiamo i più diversi tipi di servi e servosistemi, specificare che i vostri "servi" sono servi modello radiocomandati è probabilmente una buona idea.
Connor Wolf,

1
Il tuo sistema è troppo complesso per noi per essere in grado di risolverlo per te. Semplificalo e vedi se hai ancora il problema. Quando hai un sistema minimo che riproduce il problema e non riesci ancora a risolverlo da solo, allora diventa appropriato chiedere aiuto.
Phil Frost,

12
Nota generale per la progettazione di sistemi di direzione laser: posizionare gli specchi sui servi, quindi dirigere l'uno verso l'altro. In questo modo non devi avere un servo montato sull'altro, né il laser montato sui servi, e puoi quindi fissarli saldamente.
pjc50,

Risposte:


27

Quando si utilizza la libreria Servo su un Arduino, una fonte comune di buzz servo è che le routine servo guidate da interrupt non danno effettivamente un impulso di uscita molto stabile. Poiché l'AVR accetta interruzioni per la manutenzione dell'orologio millis () e altre cose nel runtime di Arduino, il jitter nella libreria Servo è nell'ordine di diversi microsecondi, il che si traduce in molti movimenti nel servo.

La soluzione per questo è scrivere il tuo polso. Qualcosa come questo:

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

Ciò disattiverà altri interrupt e genererà un impulso PWM molto più pulito. Tuttavia, il timer "millis () mancherà alcuni tick di clock. (La funzione" micros () "può essere chiamata qualcos'altro - dimentico esattamente cosa.)

In generale, per il codice critico di temporizzazione, si desidera eliminare completamente il runtime di Arduino e scrivere il proprio utilizzando il compilatore avr-gcc e la libreria avr-libc che alimenta l'ambiente Arduino. Quindi puoi impostare un timer per spuntare 4 volte per microsecondo, o anche 16 volte per microsecondo, e ottenere una risoluzione molto migliore nel tuo PWM.

Un'altra causa di ronzio nei servi è servi economici con sensori economici, in cui i sensori sono rumorosi o quando la posizione esatta richiesta con l'impulso non può essere effettivamente codificata dal sensore. Il servo vedrà "spostare in posizione 1822" e proverà a farlo, ma finirà con il sensore che legge 1823. Il servo allora dirà "sposta un po 'indietro" e finisce con il sensore che legge 1821. Ripeti! La soluzione per questo è usare servi di alta qualità. Idealmente, non servi per hobby, ma servi reali con encoder assoluti ottici o magnetici.

Infine, se i servi non ottengono abbastanza energia o se si tenta di alimentare la loro potenza dalla guida 5V sull'Arduino, questo genererà un ronzio indotto dalla tensione nei servi, come suggerito sopra. Potresti essere in grado di risolverlo con grandi condensatori elettrolitici (che sono comunque una buona idea per il filtraggio generale), ma molto probabilmente vorrai assicurarti che la tua fonte di alimentazione servo possa effettivamente fornire diversi ampere di corrente alla tensione del servo.


1
I segnali di controllo servo R / C sono PWM. L'ampiezza dell'impulso è nominalmente 1-2 millisecondi, l'intervallo di ripetizione dell'impulso è compreso tra 20 e 50 millisecondi. Mi aspetterei più di circa 10 microsecondi di variazione nella larghezza dell'impulso per far tremare il servo. Il jitter nel PRI non sarà generalmente un problema se l'ampiezza dell'impulso è stabile. (Il mio semplice controller 555 sporco variava la durata dell'impulso e il PRI della stessa quantità: al servo non importava.)
John R. Strohm

Tutto ciò che dici è vero, tranne il jitter - i servi jitter prima che l'ampiezza dell'impulso sia "off" da 10 noi. E il jitter di interrupt per il semplice Arduino (prima di aggiungere librerie) può arrivare fino a 10 noi! Il codice che ho incollato ha lo scopo di generare un impulso rock stabile nell'ambiente Arduino, che non è generalmente buono con i servoimpulsi rock stabile come un circuito 555 dedicato.
Jon Watte,

4
Ho appena scritto un articolo che mostra come generare impulsi precisi su Arduino come il codice sopra, tranne per il fatto che utilizza l'hardware Timer e non è necessario disattivare gli interrupt e rovinare il runtime di Arduino.
bigjosh,

Si noti che Arduino supporta l'uscita del timer solo su alcuni pin (i pin PWM) e non è possibile utilizzare i pin Timer0 per questo metodo. Quindi, ci sono solo 4 pin per cui funziona davvero su un normale Arduino UNO. Se devi guidare 4 o meno servi e non hai bisogno dei timer per qualcos'altro, questa è una buona opzione.
Jon Watte,

21

Questo si chiama "ronzio".

Ci sono un paio di cose che lo causeranno. L'instabilità nel potere del servo è una causa comune. I servi R / C possono disegnare alcuni GRANDI picchi quando mettono in moto il motore per la prima volta.

Molti anni fa, ho suonato con un servo Tower Hobbies Royal Titan Standard, controllandolo da un inverter 555 e un transistor. Circuito di controllo semplicissimo. Ho imparato che il servomotore ha prelevato 250 mA dall'alimentazione a 5 V mentre era in movimento continuo. Un ronzio, disegnava facilmente picchi da mezzo amplificatore. (Forse di più: stavo solo monitorando il misuratore di corrente sul mio alimentatore da banco, senza mirare a uno shunt con rilevamento di corrente.)

Ci sono voluti 220 uF direttamente sul mio servo per domarlo.

Prova a mettere un condensatore elettrolitico, almeno 100 uF, direttamente attraverso l'alimentazione del servo, il più vicino possibile al servo elettricamente e vedi se questo aiuta.

Sulla base di quegli esperimenti, non prenderei mai in considerazione l'uso dei servi R / C per QUALCOSA senza aggiungere condensatori. Ciò include i modelli radiocomandati.

Ciò può anche essere causato dallo sporco nella pentola del servo all'interno del servo. Prova prima il condensatore.


6

Il tuo ronzio / scuotimento si verifica solo quando o vicino ai limiti del servo (0 gradi o 180 gradi)? In tal caso, potrebbe esserci una soluzione semplice per te. Ho scoperto che i servi economici non sanno come stare molto bene ai limiti del loro movimento, il che può causare il ronzio / scuotimento che stai menzionando. Tuttavia, se limiti il ​​loro intervallo a 10 ~ 170 gradi, il problema verrà risolto.

Se non è abbastanza buono per te, puoi seguire le correzioni più complesse menzionate nelle altre risposte, come una migliore potenza, migliori servo sensori, ecc.


Sì, per il mio SG90 questi valori sono compresi tra 18 e 162. In realtà non ha reso 32 gradi irraggiungibili, forse solo la metà.
Maxim Kachurovskiy,

5

Ho risolto il mio problema "spegnendo il servo" dopo averlo spostato. Esempio:

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

PINè il pin PWM collegato al tuo servo. passando alla modalità Input sono stato in grado di spegnere la vibrazione. Questa non è una soluzione ottimale e suggerirei di provare prima le altre soluzioni.


Ho provato le altre soluzioni, questa era l'unica a funzionare, +1. Ottima idea quando tutto il resto fallisce!
Snappawapa,

3

Ho avuto lo stesso problema con i servi MG90S (jittering), le mie linee di segnale sono relativamente lunghe (60 ~ 70 cm), posizionando un condensatore 103 (10nF) sul segnale e le linee di terra hanno risolto il problema per me (ho posizionato il condensatore da qualche parte nel al centro, nel punto in cui il cavo servo originale si collega al mio cavo interno).

Inoltre non ho potuto usare la libreria Servo standard perché il primo timer che acquisisce su Arduino Mega è Timer-5 e ne ho bisogno per la misurazione della frequenza. Dato che utilizzo solo 10 servi, ho estratto il codice chiave dalla libreria Servo e l'ho cambiato usando Timer-1 (ogni timer supporta un massimo di 12 servi su Mega).

Il codice autonomo è di seguito come riferimento, se si desidera includerlo nel proprio progetto, è possibile utilizzare solo la parte superiore, la parte inferiore è testare la parte superiore (è in ascolto sulla porta seriale, è possibile fornire sX e comandi vX, dove sX seleziona un servo, s0 selezionerebbe il primo servo, vX imposta la posizione del servo in noi, quindi v1500 imposterà servo0 nella posizione centrale, supponendo che tu abbia dato prima 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

2

La mia migliore opzione in questo caso era quella di collegare e staccare i Servi in ​​ogni operazione.

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

PS. questa non è affatto una qualità, solo una soluzione alternativa.


1

Mentre altri hanno suggerito varie soluzioni a questo problema di ronzio servo, in questo thread e in altri forum Arduino, vale a dire:

  • Genera impulso personale
  • Fornire alimentazione 5V separatamente
  • Evitare di spingere fino ai suoi limiti (ad esempio, utilizzare 10-170 anziché 0-180)
  • Esegui un condensatore
  • Staccare dopo lo spostamento

Nel mio caso, ho scoperto che il ronzio si è fermato quando un alimentatore 9V / 2A è collegato alla scheda Arduino. Ma la soluzione definitiva più semplice era semplicemente spostare lentamente il 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

Per me, questo sembra errori o errata messa a punto del circuito di feedback. I sistemi di servocomando di fascia alta hanno una certa conoscenza delle caratteristiche del motore (induttanza, coppia, corrente di picco, numero di poli), carico (momento di inerzia) e condizioni istantanee (posizione, giri / min, back-emf, corrente). Con queste informazioni, il programma di controllo del motore può fare previsioni su cosa farà il servo in risposta a un determinato ingresso dal controller (cioè ingresso corrente / tensione) e su quella base generare l'ingresso ottimale per ottenere l'uscita desiderata.

Come puoi immaginare, si tratta di cose un po 'complicate, ma una ricerca in Internet sul feedback dei servi ti farà iniziare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.