Existe uma maneira de impedir que os servos "agitem"?

20

Muito simplesmente, estou controlando servos (9g Micro Servos) com base em alguns dados lidos de outros lugares. Tudo funciona bem, exceto que os servos constantemente "tremem". Ou seja, eles vibram de volta com movimentos muito sutis (com movimentos intermitentes de 1/2 a> 1 cm ou mais).

Tentei corrigir esse problema no software fazendo 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

Onde for necessário, inicialize as variáveis ​​que armazenam o valor do servo mapeado (usando a biblioteca de servo do arduino).

A função readChange () é 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; // no change 
  else return 1; //change
}

Onde xRead é o valor que foi inicializado (a primeira saída servo mapeada).

Embora isso realmente não seja uma boa abordagem. Exige que AMBOS os valores não devam ter sido alterados por um fator de DEG (~ 10 graus ou ~ 0,28V no meu caso). Se eu escrever a função de modo que OR seja menor que DEG, e se eu estiver mudando apenas um servo por vez? Portanto, há um delimma ..

Isso é simplesmente uma propriedade de servos (talvez baratos?) Ou existe uma solução alternativa?


Seria muito mais simples incluir um link para pasta. Aqui está o código completo: http://pastie.org/8191459

Anexei dois servos juntamente com um ponteiro laser para permitir dois graus de liberdade (X, Y). Existem opções, com base no estado de vários botões, para controlar os servos de várias maneiras. O primeiro é "Motion", onde eu tenho dois fotorresistores que, com base na quantidade de exposição à luz, afetam a posição dos servos. Ainda não implementei o código para controlar os servos por um controlador Xbox. E a terceira opção é apenas movimento aleatório.

insira a descrição da imagem aqui

sherrellbc
fonte
4
Aparentemente, você tem um pouco de instabilidade ou ruído no seu servo controlador. No entanto, você detalha muitas coisas que parecem não ter nada a ver com o servocontrolador, exceto a linha não documentada "positionServo ();", que só podemos adivinhar é onde os detalhes estão enterrados. O servocontrolador está fechado no micro? Fechado externamente? Analógico ou digital? Se digital, em que resolução está sendo medida? Mostre um diagrama de todo o sistema.
Olin Lathrop
Quanta carga você está colocando nos servos?
31813 Chris Laplante
4
@OlinLathrop - (S) Ele está usando servos modelo controlados por rádio, que têm todo o loop do servo incorporado no dispositivo. sherrellbc - "Servo" é um termo muito, muito geral. Infelizmente, os fabricantes de componentes do modelo RC escolheram o termo menos descritivo para os dispositivos que produzem. Como lidamos com a maioria dos tipos diferentes de servos e sistemas servo aqui, especificar que seus "servos" são servos modelo controlados por rádio é provavelmente uma boa idéia.
Connor Lobo
11
Seu sistema é muito complexo para que possamos solucioná-lo. Simplifique e veja se você ainda tem o problema. Quando você tem um sistema mínimo que reproduz o problema e ainda não consegue consertar sozinho, torna-se apropriado pedir ajuda.
23913 Phil Phil Frost
12
Nota geral para o projeto de sistemas de direção a laser: coloque espelhos nos servos e depois direcione um para o outro. Dessa forma, você não precisa ter um servo montado no outro, nem o laser montado nos servos, e então pode prendê-los firmemente.
Pjc50

Respostas:

27

Ao usar a biblioteca Servo em um Arduino, uma fonte comum de servo buzz é que as rotinas de servo acionadas por interrupção não produzem um pulso de saída muito estável. Como o AVR interrompe a manutenção do relógio millis () e outras coisas no tempo de execução do Arduino, a instabilidade na biblioteca Servo é da ordem de vários microssegundos, o que se traduz em muito movimento no servo.

A solução para isso é escrever seu próprio pulso. Algo assim:

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

Isso desativará outras interrupções e gerará um pulso PWM muito mais limpo. No entanto, isso fará com que o timer "millis () perca alguns tiques do relógio. (A função" micros () "pode ​​ser chamada de outra coisa - esqueço exatamente o que.)

Em geral, para cronometrar o código crítico, você deseja se livrar completamente do tempo de execução do Arduino e escrever o seu próprio usando o compilador avr-gcc e a biblioteca avr-libc que alimenta o ambiente do Arduino. Em seguida, você pode configurar um timer para marcar 4 vezes por microssegundo ou até 16 vezes por microssegundo e obter uma resolução muito melhor no seu PWM.

Outra causa de zumbido nos servos são servos baratos com sensores baratos, onde os sensores são barulhentos ou quando a posição exata solicitada com o pulso não pode realmente ser codificada pelo sensor. O servo verá "mover para a posição 1822" e tentará fazê-lo, mas terminará com a leitura do sensor em 1823. O servo dirá "retroceder um pouco" e termina com a leitura do sensor em 1821. Repita! A correção para isso é usar servos de alta qualidade. Idealmente, não servos de hobby, mas servos reais com codificadores absolutos ópticos ou magnéticos.

Por fim, se os servos não obtiverem energia suficiente, ou se você tentar acionar sua energia a partir do trilho de 5V no Arduino, isso gerará zumbido induzido por queda de tensão nos servos, como sugerido acima. Você pode consertá-lo com grandes capacitores eletrolíticos (que são uma boa idéia para filtragem geral de qualquer maneira), mas é mais provável que você tenha certeza de que sua fonte de energia servo pode realmente fornecer vários amperes de corrente na tensão do servo.

Jon Watte
fonte
11
Os sinais de servocontrole R / C são PWM. A largura do pulso é nominalmente de 1-2 milissegundos, o intervalo de repetição do pulso é de 20 a 50 milissegundos. Eu esperaria que mais de 10 microssegundos de variação na largura do pulso fizessem com que o servo ficasse nervoso. A instabilidade no PRI geralmente não será um problema se a largura do pulso for estável. (Meu 555 controlador de largura de pulso variou sujeira-simples e PRI pela mesma quantidade: o servo não cuidado.)
John R. Strohm
Tudo o que você diz é verdade, exceto o tremor - os servos tremem antes que a largura do pulso seja "desligada" por 10 nós. E a instabilidade de interrupção para o Arduino comum (antes de adicionar bibliotecas) pode chegar a 10 nós! O código que colei destina-se a gerar um pulso estável à rocha no ambiente Arduino, que geralmente não é tão bom em servo pulsos estáveis ​​em rocha quanto um circuito 555 dedicado.
Jon Watte
4
Acabei de escrever um artigo mostrando como gerar pulsos precisos no Arduino como o código acima, exceto que ele usa o hardware do Timer - e não é necessário desligar as interrupções e atrapalhar o tempo de execução do Arduino.
bigjosh
Observe que o Arduino suporta apenas a saída do timer em alguns pinos (os pinos do PWM) e você não pode usar os pinos do Timer0 para esse método. Portanto, existem apenas 4 pinos para os quais isso realmente funciona em um Arduino UNO comum. Se você precisar dirigir 4 servos ou menos, e não precisar de temporizadores para outra coisa, é uma boa opção.
Jon Watte
21

Isso é chamado de "buzz".

Existem algumas coisas que irão causar isso. Instabilidade no poder do servo é uma causa comum. Os servos R / C podem atrair alguns picos GRANDES quando colocam o motor em movimento.

Muitos anos atrás, eu toquei com um servo Tower Hobbies Royal Titan Standard, controlando-o de um inversor 555 e de um transistor. Circuito de controle simples. Aprendi que o servo motor consumia 250 mA da fonte de 5V em movimento contínuo. Zumbindo, ele atraiu facilmente picos de meio amplificador. (Talvez mais: eu estava apenas monitorando o medidor de corrente no suprimento de bancada, sem definir um desvio de detecção de corrente.)

Foram necessários 220 uF diretamente no meu servo para domar.

Tente colocar um capacitor eletrolítico, pelo menos 100 uF, diretamente na fonte de alimentação do servo, o mais próximo eletricamente possível do servo, e veja se isso ajuda.

Com base nessas experiências, eu nunca consideraria o uso de servos R / C para QUALQUER COISA sem adicionar capacitores. Isso inclui modelos controlados por rádio.

Isso também pode ser causado por sujeira no servo-pot dentro do servo. Experimente o capacitor primeiro.

John R. Strohm
fonte
6

Seu zumbido / tremor acontece apenas quando está próximo ou dentro dos limites do servo (0 ou 180 graus)? Nesse caso, pode haver uma correção simples para você. Eu descobri que servos baratos não sabem como ficar muito bem nos limites de seus movimentos, o que pode causar o zumbido / tremor que você está mencionando. No entanto, se você limitar o intervalo a 10 ~ 170 graus, o problema será corrigido.

Se isso não for bom o suficiente para você, você pode seguir as correções mais complexas mencionadas nas outras respostas, como melhor potência, melhores servo sensores, etc.

nitwit
fonte
Sim, para o meu SG90 esses valores são de 18 a 162. Na verdade, os 32 graus eram inacessíveis, talvez apenas metade disso.
Maxim Kachurovskiy
5

Corrigi o meu problema "desligando o servo" depois de movê-lo. Exemplo:

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

PINé o pino PWM conectado ao seu servo. mudando-o para o modo de entrada, pude desligar a vibração. Esta não é a solução ideal e eu sugiro tentar as outras soluções primeiro.

Ramast
fonte
Tentei as outras soluções, essa foi a única a funcionar, +1. Ótima idéia quando tudo mais falhar!
Snappawapa #
3

Eu tive o mesmo problema com os servos MG90S (tremulação), minhas linhas de sinal são relativamente longas (60 ~ 70cm), colocando um capacitor de 103 (10nF) sobre o sinal e as linhas de terra corrigiram o problema para mim (coloquei o capacitor em algum lugar do meio, no ponto em que o cabo servo original se conecta ao meu cabo interno).

Além disso, eu não poderia usar a biblioteca Servo padrão, porque o primeiro timer que ele pega no Arduino Mega é o Timer-5 e eu preciso disso para medir a frequência. Como utilizo apenas 10 servos, extraí o código da chave da biblioteca Servo e mudei para o uso do Timer-1 (cada temporizador suporta no máximo 12 servos no Mega).

O código autônomo está abaixo para referência; se você quiser incluí-lo em seu próprio projeto, poderá usar apenas a parte superior, a parte inferior é testar a parte superior (ela escuta na porta serial, você pode fornecer o sX comandos vX, onde sX seleciona um servo, s0 seleciona o primeiro servo, vX define a posição do servo em nós, portanto v1500 configura o servo0 para a posição do meio, assumindo que você deu um comando s0 primeiro).

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

Minha melhor opção nesse caso foi conectar e desconectar os servos em cada operação.

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

PS. isso realmente não é de qualidade, apenas uma solução alternativa.

Shikartoos
fonte
1

Enquanto outros sugeriram várias soluções para esse problema de servo-zumbido, neste tópico e em outros fóruns do Arduino, a saber:

  • Gerar pulso próprio
  • Forneça energia de 5V separadamente
  • Evite ir ao limite (por exemplo, use 10-170 em vez de 0-180)
  • Execute um capacitor
  • Desconectar após o movimento

No meu caso, descobri que o zumbido parou quando uma fonte de alimentação de 9V / 2A é conectada à placa Arduino. Mas a solução final mais fácil era simplesmente mover o servo lentamente:

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

YMMV.

user2105117
fonte
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
fonte
0

Para mim, isso parece erros ou ajuste incorreto do loop de feedback. Os sistemas servocontroladores avançados têm algum conhecimento das características do motor (indutância, torque, corrente de pico, contagem de polos), carga (momento de inércia) e condições instantâneas (posição, rpm, contra-corrente, corrente). Com essas informações, o programa de controle do motor pode fazer previsões sobre o que o servo fará em resposta a uma determinada entrada do controlador (ou seja, entrada de corrente / tensão) e, nessa base, gerar a entrada ideal para alcançar a saída desejada.

Como você pode imaginar, isso é algo um pouco complicado, mas uma pesquisa na Internet sobre feedback de servo o ajudará a começar.

EBlake
fonte