22
\$\begingroup\$

Very simply, I am controlling servos (9g micro servos) based on some data read in from elsewhere. Everything works fine except that the servos will constantly "shake." That is, they vibrate back with very subtle movements (with intermittent movements of 1/2 -> 1cm or so).

I tried correcting this issue in software by doing something like:

  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

Where the do-while is necessary initialize the variables that store the mapped servo value (using the arduino servo library.)

The readChange() function is defined as:

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
}

Where xRead is the value that was initialized (the first, mapped servo output.)

This really is not a good approach. It requires that BOTH values must not have changed by a factor of DEG (~10 degrees, or ~0.28V in my case.) If I write the function such that either OR be less than DEG, then what if I was only changing one servo at a time? So there is a delimma.

Is this simply a property of servos (perhaps cheap ones?) or is there a workaround?


It would be much simpler to include a pastie link. Here is the full code.

I have attached two servos together with a laser pointer to allow for two degrees of freedom (X, Y.) There are options, based on the state of several buttons, to control the servos in various ways. The first is "Motion" where I have two photoresistors that, based on the amount of light exposure, affect the position of the servos. I have not yet implemented the code to control the servos by an Xbox controller. The third option is just randomized movement.

enter image description here

\$\endgroup\$
10
  • 4
    \$\begingroup\$ You apparently have a little instability or noise in your servo controller. However, you go into a lot of detail of stuff that seems to have nothing to do with the servo controller, other than the undocumented line "positionServo();", which we can only guess is where the details are buried. Is the servo controller closed in the micro? Closed externally? Analog or digital? If digital, what resolution is it being measured at? Show a diagram of the whole system. \$\endgroup\$ Commented Jul 30, 2013 at 21:21
  • \$\begingroup\$ How much load are you putting on the servos? \$\endgroup\$ Commented Jul 30, 2013 at 21:42
  • 4
    \$\begingroup\$ @OlinLathrop - (S)He's using standard radio-controlled model servos, which have the entire servo loop baked into the device. sherrellbc - "Servo" is a very, very general term. Unfortunately, RC model component manufacturers chose about the least descriptive term for the devices the produce. Since we deal with most different kinds of servos and servo-systems here, specifying that your "servos" are radio-controlled model servos is probably a good idea. \$\endgroup\$ Commented Jul 30, 2013 at 22:25
  • 1
    \$\begingroup\$ Your system is too complex for us to be able to troubleshoot it for you. Simplify it, and see if you still have the problem. When you have a minimal system that reproduces the problem, and you still can't fix it your self, then it becomes appropriate to ask for help. \$\endgroup\$
    – Phil Frost
    Commented Jul 30, 2013 at 23:10
  • 12
    \$\begingroup\$ General note for designing laser-directing systems: put mirrors on the servos, then direct one at the other. That way you don't have to have one servo mounted on the other, nor the laser mounted on the servos, and you can then bolt them all down firmly. \$\endgroup\$
    – pjc50
    Commented Jul 31, 2013 at 8:19

10 Answers 10

30
\$\begingroup\$

When using the Servo library on an Arduino, a common source of servo buzz is that the interrupt-driven servo routines don't actually give a very stable output pulse. Because the AVR takes interrupts for servicing the millis() clock and other things in the Arduino runtime, the jitter in the Servo library is on the order of several microseconds, which translates to a lot of movement in the servo.

The fix for this is to write your own pulse. Something like this:

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

This will turn off other interrupts, and generate a much cleaner PWM pulse. However, it will make the "millis() timer miss some clock ticks. (The "micros()" function may be called something else -- I forget exactly what.)

In general, for timing critical code, you want to get rid of the Arduino runtime entirely, and write your own using the avr-gcc compiler and avr-libc library that powers the Arduino environment. Then you can set up a timer to tick 4 times per microsecond, or even 16 times per microsecond, and get a much better resolution in your PWM.

Another cause of buzz in servos is cheap servos with cheap sensors, where the sensors are noisy, or when the exact position requested with the pulse can't actually be encoded by the sensor. The servo will see "move to position 1822" and try to do it, but ends up with the sensor reading 1823. The servo will then say "move back a little bit" and it ends up with the sensor reading 1821. Repeat! The fix for this is to use high-quality servos. Ideally, not hobby servos at all, but real servos with optical or magnetic absolute encoders.

Finally, if the servos don't get enough power, or if you try to drive their power from the 5V rail on the Arduino, this will generate voltage-sag-induced buzz in the servos, as suggested above. You may be able to fix it with large electrolytic capacitors (which are a good idea for general filtering anyway) but you more likely want to make sure your servo power source can actually deliver several amps of current at the servo voltage.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ R/C servo control signals are PWM. Pulse width is nominally 1-2 milliseconds, pulse repetition interval is anywhere from 20 to 50 milliseconds. I'd expect more than about 10 microseconds of variation in the pulse width to cause the servo to get jittery. Jitter in the PRI will generally not be a problem if the pulse width is stable. (My dirt-simple 555 controller varied pulse width and PRI by the same amount: the servo didn't care.) \$\endgroup\$ Commented Aug 2, 2013 at 13:47
  • \$\begingroup\$ Everything you say is true, except the jitter -- servos will jitter before the pulse width is "off" by 10 us. And the interrupt jitter for the plain Arduino (before you add libraries) can go as high as 10 us! The code I pasted is intended to generate a rock stable pulse in the Arduino environment, which is not generally as good at rock stable servo pulses as a dedicated 555 circuit. \$\endgroup\$
    – Jon Watte
    Commented Aug 3, 2013 at 15:22
  • 7
    \$\begingroup\$ I just wrote an article showing how to generate precise pulses on Arduino like the above code, except it uses the Timer hardware -and no need to turn off interrupts and mess up the Arduino run-time. \$\endgroup\$
    – bigjosh
    Commented Mar 12, 2015 at 23:25
  • \$\begingroup\$ Note that the Arduino only support timer output on a few pins (the PWM pins,) and you can't use the Timer0 pins for this method. Thus, there are only 4 pins this really works for on a regular Arduino UNO. If you need to drive 4 or less servos, and don't need the timers for something else, that's a good option. \$\endgroup\$
    – Jon Watte
    Commented Mar 13, 2015 at 0:33
23
\$\begingroup\$

This is called "buzz".

There are a couple of things that will cause it. Instability in the power to the servo is a common cause. R/C servos can draw some BIG spikes when they first put the motor in motion.

Many years ago, I played with a Tower Hobbies Royal Titan Standard servo, controlling it from a 555 and a one-transistor inverter. Dead-simple control circuit. I learned that the servo motor drew 250 mA from the 5V supply while in continuous motion. Buzzing, it easily drew half-amp spikes. (Maybe more: I was just monitoring the current meter on my bench supply, not scoping a current-sensing shunt.)

It took 220 uF directly across my servo to tame it.

Try putting an electrolytic capacitor, at least 100 uF, directly across the power supply to the servo, as electrically close to the servo as you can, and see if that helps.

Based on those experiments, I would never consider using R/C servos for ANYTHING without adding capacitors. That includes radio-controlled models.

This can also be caused by dirt in the servo pot inside the servo. Try the capacitor first.

\$\endgroup\$
7
\$\begingroup\$

I've fixed my problem by "switching the servo off" after I move it. Example:

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

PIN is the PWM pin connected to your servo. by switching it to Input mode I was able to shutdown the vibration. This is not optimal solution and I'd suggest trying the other solutions first.

\$\endgroup\$
1
  • 2
    \$\begingroup\$ I tried the other solutions, this was the only one to work, +1. Great Idea when all else fails! \$\endgroup\$
    – Snappawapa
    Commented Oct 6, 2017 at 0:26
6
\$\begingroup\$

Is your buzzing/shaking happening only when at or close to the servo's limits (0 degrees or 180 degrees)? If so, there may be a simple fix for you. I have found that cheap servos don't know how to stay at the limits of their movement very well, which can cause the buzzing/shaking you're mentioning. However, if you just limit their range to 10~170 degrees, the issue will be fixed.

If that's not good enough for you, you can follow the more complex fixes mentioned in the other answers, like better power, better servo sensors, etc.

\$\endgroup\$
1
  • \$\begingroup\$ Yes, for my SG90 these values are 18 to 162. It didn't actually make 32 degrees unreachable, maybe only half of that. \$\endgroup\$ Commented Oct 9, 2016 at 19:00
3
\$\begingroup\$

I had the same problem with MG90S servos (jittering), my signal lines are relatively long (60~70cm), placing a 103 (10nF) capacitor over the signal and ground lines fixed the problem for me (I placed the capacitor somewhere in the middle, at the point where the original servo cable connects to my internal cable).

In addition I couldn't use the standard Servo library because the first timer it grabs on the Arduino Mega is Timer-5 and I need that for frequency measurement. As I use only 10 servos I extracted the key code from the Servo library and changed it to using Timer-1 (each timer supports a maximum of 12 servos on the Mega).

The stand-alone code is below for reference, if you want to include it in your own project then you can use the top part only, the lower part is to test the top part (it listens on the serial port, you can give sX and vX commands, where sX selects a servo, s0 would select the first servo, vX sets the servo position in us, so v1500 would set servo0 to the middle position, assuming you gave a s0 command first).

//----------------------------------------------------------------
// 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
\$\endgroup\$
2
\$\begingroup\$

My best option in this case was to attach and detach the Servos in each operation.

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

PS. this is really no quality at all, just a workaround.

\$\endgroup\$
2
\$\begingroup\$

While others have suggested various solutions to this servo buzzing issue, in this thread and other Arduino forums, namely:

  • Generate own pulse
  • Supply 5V power separately
  • Avoid pushing to its limits (e.g. use 10-170 instead of 0-180)
  • Run a capacitor across
  • Detach after move

In my case, I found that the buzzing stopped when a 9V/2A power supply is plugged into the Arduino board. But the easiest ultimate solution was simply to move the servo slowly:

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

YMMV.

\$\endgroup\$
1
\$\begingroup\$

To me, this looks like errors or mis-tuning of the feedback loop. High-end servo control systems have some knowledge of the motor characteristics (inductance, torque, peak current, pole count), the load (moment of inertia), and instantaneous conditions (position, rpm, back-emf, current). With this information, the motor control program can make predictions about what the servo will do in response to a given input from the controller (i.e. current/voltage input) and on that basis generate the optimum input to achieve the desired output.

As you can imagine, this is somewhat complicated stuff, but an internet search on servo feedback will get you started.

\$\endgroup\$
1
\$\begingroup\$
#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
    }
}
\$\endgroup\$
1
\$\begingroup\$

Make sure that the Arduino itself is being powered properly.

I was attaching Arduino Uno to a nearly out of battery laptop via USB cable, resulting in a jittering servo. As soon as I've put the laptop on charge, the servo started working smoothly!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.