When I've done servo control, I've done it using software PWM.
Timing is based on a high frequency "clock interrupt" running from a timer - anything from one to 100KHz or more depending on requirements. That's divided down in the interrupt routine to decade values for different uses, down to eg. 100Hz or all the way to seconds-hours-days etc.
In the 100Hz routine, increment a counter that sequences through the servo outputs. Look at the low bit as you only need two outputs.
Or just invert it.
For more than two servos, use an array to store the required servo time (ie. position) values and get the value via the servo counter.
Load the position needed for that specific servo in to a variable - "servo_pwm" in my example. Then set the output pin that controls the appropriate servo high.
In a high frequency stage of the interrupt divider, see if the "servo_pwm" variable is zero. If so, clear the output pins. If not, decrement it.
These are fragments from a working design that had two servos.
This is the "start" section - ignore the prescale part, that's the divider that takes it down to around 100Hz.
C:
// Do low speed timing.
prescale++;
if(prescale > 95)
{
int i;
// 100Hz.
prescale = 0;
if(servo_toggle) {
servo_toggle = false;
output_high(PIN_B2);
i = servo_a + 9;
}
else {
servo_toggle = true;
output_high(PIN_B3);
i = servo_b + 9;
}
if(i > 19) i = 19;
servo_pwm = i;
And this is the actual PWM output fragment, which ran at 9600 Hz in this design - it only needed a few approximate positions for the servos. Running the interrupt faster would give higher precision - I've run software pwm at around 250 - 300KHz (256 step x mains half cycle rate) on some things using such as the 877, so it's definitely possible to get extremely fine precisions like this.
C:
if(!servo_pwm) {
output_low(PIN_B2);
output_low(PIN_B3);
} else
--servo_pwm;
In this specific program, the first part runs later in the same timer_0 interrupt & timing divider chain, so there can never be a race condition where the outputs could be cleared before the servo_pwm variable is set.
If you use independent timing sources for the two sections, set the servo_pwm value before setting the output high, so the countdown interrupt can't mess things up if it occurs in the middle of the setup.
Another option for multiple servos, if you are short of pins on the device, is use a CMOS shift register IC.
Connect one servo to each shift register output and you can control up to eight (or more with a second IC) with just two pins on the MCU.
(For info, this is the principle pre-MCU radio control receivers used to produce the individual servo outputs).
At a low rate (eg 20 - 50Hz), clear the servo_select counter and set the MCU pin feeding the shift register data in pin high.
Load the first servo time value to servo_pwm and pulse the shift reg clock. That sets the first output high.
Set the data pin low.
Each time servo_pwm gets to zero, increment the servo_select counter, "AND" in with a mask to prevent it getting out of bounds for the servo time array [eg. 0x07 for 8 values, 0x0f for 16] and copy the appropriate servo time value to servo_pwm and pulse the shift reg clock. That "moves" the high signal to the next servo in sequence.
The high bit will fall out the end of the register after 8 counts and the PWM timing may repeat, doing nothing until the next restart, but it's easier to let it do that than add code to stop it - it's harmless.
You can add a second shift register and clock pin, with them sharing the D input, to double the number of servos with just a second servo_pwm counter. Or more registers for silly numbers...