In order to drive a servo we need to understand the mechanics of a servo:-
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:-
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;
}