05 - Example :- Driving a servo via PWM

Submitted by Webbot on November 30, 2008 - 4:46pm.

In order to drive a servo we need to understand the mechanics of a servo:-

  1. It should be sent a pulse every 20ms
  2. The width of the pulse should 1.5ms to zero the servo
  3. A width of 1ms will make it turn fully to one side
  4. A width of 2ms will make it turn fully to the other side

So the first PWM point is that the PWM repeat frequency should be every 20ms.

 

Secondly we note that the active pulse length is between 1ms and 2ms. This represents a duty cycle between 1ms per 20ms (5%) and 2ms per 20ms (10%) hence a 5% fluctuation between minimum and maximum. So out of our total comparator values then only 5% of them will actually make any difference to how the servo behaves.

 

For an 8 bit timer with 2^8 (ie 256) different values then this 5% fluctuation represents 5% of the total of 256 (= 12.8). So you could only set the servo to move to any one of 12 different positions. This is a much smaller value than a servo is capable of. But you may decide it is still enough for a given application.

 

For a 16 bit timer with 2^16 (65,536) different values then this 5% fluctuation represents 5% of the total of 65,536 (=3276.8). So you could set the servo to move to one any of 3,276 different positions. This is more steps than most servos can actually cope with but does, at least, cover all the physically achievable positions even though there will be a lot of duplicates ie position 1,234 may well be the same as position 1,235.

 

Since we are not driving a DC Motor then we will go with Fast PWM mode.

 

So lets make a decision, we will go with the 16 bit PWM because:-

  1. Each servo is a physical thing and its response may not be exactly 1ms to 2ms it could be smaller (say 1.2ms to 1.8ms in which case we would have less than 12 different settings). So a wider choice of values with the 16 PWM gives us more leeway
  2. Assuming we have two servo motors then the ATMega8 provides 2 x 16 bit PWM outputs on Timer1 which support fast PWM mode versus only one 8 bit PWM on Timer2
  3. The 16 bit timers allow us to use a mode of operation which allows us to define a more appropriate value of TOP to make sure that the frequency is every 20ms. The 8 bit timers don't allow us to do this.

So we are going to use 16 bit PWM via Timer1.

 

In order to setup the hardware we first have to tell Timer1 to repeat every 20ms or 50Hz. To calculate the value for TOP we will use the formula from the previous section:

TOP = (Clock_Speed / (Prescaler * Output_PWM_Frequency)) - 1

 

Since we are using a 16 bit timer then the value of TOP must be in the range 0 to 65,535. If the result is bigger than this then we need to bump up the pre-scaler in order to reduce the TOP to fit this range. So starting with a prescaler value of 1:

 

For a 1MHz controller then

TOP = (1,000,000 / (1 * 50)) - 1 = 19,999   This is 'in-range' so our answer is: prescaler=1 and TOP=19,999

 

For a 8MHz controller then

TOP = (8,000,000 / (1 * 50)) - 1 = 159,999  This is not 'in-range' so lets try again unsing the next available prescaler value of 8

TOP = (8,000,000 / (8 * 50)) - 1 = 19,999    This is 'in-range' so our answer is: prescaler=8 and TOP=19,999

 

 

 

 

Here is our code so far for an ATMega8 with 1Mhz clock speed:

TCCR1A = 0;          // disable all PWM on Timer1 whilst we set it up


ICR1 = 19999 ;   // frequency is every 20ms


// Configure timer 1 for Fast PWM mode using ICR1, with no prescaling

TCCR1A = (1<<WGM11)
    TCCR1B = (1 << WGM13) | (1<<WGM12) | (1 << CS10);


 

or for an 8Mhz processor:

TCCR1A = 0;          // disable all PWM on Timer1 whilst we set it up


ICR1 = 19999;   // frequency is every 20ms


// Configure timer 1 for Fast PWM mode via ICR1, with 8x prescaling

TCCR1A = (1<<WGM11)
     TCCR1B = (1 << WGM13) | (1<<WGM12) | (1 << CS11);


 

We can generalise by using compiler directives to insert the correct values by using:-

#if F_CPU == 8000000
    ICR1 = 19999

// 8x prescaling

TCCR1A = (1 << WGM11)

TCCR1B = (1 << WGM13) | (1<<WGM12) | (1 << CS11);

#elif F_CPU == 1000000
    ICR1 =19999;

// 1x prescaling

TCCR1A = (1 << WGM11)

TCCR1B = (1 << WGM13) | (1<<WGM12) | (1 << CS10);

#else
#error    No F_CPU has been set or it is an unrecognised value
#endif


 

The Timer1 is now set up to be able to provide up to 2 PWM outputs on pins OC1A and OC1B respectively. At the moment our statement 'TCCR1A=0;' means that we have disabled both of them.

 

To activate the first channel on OC1A, which is also port B1, we must do the following:-

 DDRB |= _BV(1);      // make port B1 an output pin


TCCR1A |= 2 <<  6;  // enable PWM on port B1 to use non-inverting mode - mode2


we can now adjust the duty cycle of the output pin by setting OCR1A to a value between 0 and ICR1.

 

 

To activate the second channel on OC1B, which is also port B2, we must do the following:-

 DDRB |= _BV(2);      // make port B2 an output pin


TCCR1A |= 2 <<  4;  // enable PWM on port B2 to use non-inverting mode - mode 2


we can now adjust the duty cycle of the output pin by setting OCR1B to a value between 0 and ICR1.

 

 

So we now have a repeating 20ms pulse on our output pins whose duration is set by either OCR1A or OCR1B. What values do we need to put in these registers in order to drive the servo?

Well ICR1 holds the value of TOP which represents 20ms. We know that a centered servo requires a pulse of 1.5ms so we need to write a comparator value to OCR1A or OCR1B of:  ICR1 * 1.5 / 20.

 

The coolest thing about PWM is that it is all taken care of in the hardware. All we have to is change the values of the comparator in OCR1A and/or OCR1B to change the position of the servo (with a modified servo this means its speed).

 

So assuming a differential drive robot that has two modified servos then we can demonstrate this as follows for a 1Mhz controller:-

int main(){
// set up 2 PWM channels on PB1 and PB2 using Timer1

TCCR1A = 0; // disable all PWM on Timer1 whilst we set it up
ICR1 = 19999; // frequency is every 20ms


// Configure timer 1 for Fast PWM mode via ICR1, with no prescaling
TCCR1A = (1 << WGM11)
TCCR1B = (1 << WGM13) | (1<<WGM12) | (1 << CS10);

// Set PB1 and PB2 as outputs
DDRB |= _BV(1) | _BV(2);
TCCR1A |= 2 << 6; // enable PWM on port B1 in non-inverted compare mode 2
TCCR1A |= 2 << 4; // enable PWM on port B2 in non-inverted compare mode 2

OCR1A = ICR1 * 2 /20; // 2ms pulse to left motor on PB1
OCR1B = ICR1 * 2 /20; // 2ms pulse to right motor on PB2


while(1){



// do nothing - the hardware is pumping out 2ms pulses every 20ms to the servos on PB1 and PB2



// for a differential drive robot the motors are on each side of the robot so the robot should be spinning around its midpoint
}


return 0;

}