# PIC 16F877 > Timers > 2 servos

#### kawauso

##### New Member
Dear members,
I want to control 2 servos, independently.
After analysis, i guess i need the 3 timers of the PIC :
1) timer1 : 20 mS for period signal,
2) timer0 : 1 mS with a loop and a counter cnt0 to count x time 1 mS for Ton for servo#1,
3) timer2 : 1 mS with a loop and a counter cnt2 to count x time 1 mS for Ton for servo#2
... but I have no idea how to start the code.
I tried, but only one servo turns.
Merci beaucoup for your help and feed-backs.
Éric

#### ronsimpson

##### Well-Known Member
When I started my software I could not see how to make many servos with only a small PIC and one timer.
Servo signal is high for 1 to 2mS out of 20mS.
In your case of only 2 servos so set a timer for 10mS. (interrupt)
Work Servo1 for 1 to 2mS out of 10mS, then work servo2 for 1 to 2mS out of 10mS. Loop.

#### alec_t

##### Well-Known Member
Perhaps use the WDT and so free up one of the other timers? For that chip the datasheet says the WDT period is in the range 7mS to 33mS and the WDT has a programmable prescaler. Although the '20mS' timing would be a bit vague, the 20mS period isn't generally critical; even ~50mS can work with some servos by all accounts.
Btw, the PWM module has two independent channels, albeit with the same clock.

#### Pommie

##### Well-Known Member
Here's an old thread from 11 years ago with some useful code.

Mike.

#### gophert

##### Well-Known Member
Show us the code you have already..

This chip has a PWM module so one can be used with this..
the OP will have to reduce clock speed to about 512k Hz to get PWM down fo 20mSec refresh period (If I remember correctly).
also, the PIC 18f46k80 is very similar chip but four independent PWM modules (two separate PIR registers if I remember correctly)

#### rjenkinsgb

##### Well-Known Member
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...

#### kawauso

##### New Member
Bonjour rjenkinsgb,
Thank you so much for all these details.
I will try to work on my code this week-end with my self mind logic and I will let it on the forum next week to get your advice.
Merci beaucoup.

#### Pommie

##### Well-Known Member
Using the Special Event Trigger function of the PWM module a single pic can drive 8 servos and be completely interrupt driven. The timing will vary by up to 2 clock cycles due to interrupt latency but you won't notice that on most servos.

Mike.

#### kawauso

##### New Member
Show us the code you have already..

This chip has a PWM module so one can be used with this..

Bonjour Ian,

This is the code I started to write ... With one servo it's works, but when i checked with a scope, I don't have a 20 mS period ! With 2 servos it doesn't work.

Eric

#### Attachments

• 35.5 KB Views: 5

#### Pommie

##### Well-Known Member
As I said, you can drive 8 servos via an interrupt using the Special Events Trigger as shown in the thread I linked earlier.

Assuming Timer1 is set to count uS then the interrupt would need to look something like,
Code:
    if(PORTB==0){                          //if zero then we need to output the next high pulse
CCPR1+=servoPos[count];            //next interrupt after servo pulse has completed

}else{                                //Servo pulse has finished
PORTB=0;                          //so creatw gap (low) so that each servo is
CCPR1+=2500-servoPos[count++];    //2.5mS total time
if(mask==0){                       //have we done all 8 servos
mask=1;                        //yes so start again at position 1
count=0;                       //used to get the servo position
}
}

Mike.

#### Ian Rogers

##### User Extraordinaire
Forum Supporter
Assuming Timer1 is set to count uS then the interrupt would need to look something like,
LOL... He's using a 4Mhz crystal... He'll be lucky to read the ADC..

One thing I want to know Mike? Is this driving 8 servo's independently, or are the synced?

#### Pommie

##### Well-Known Member
It'll drive 8 servos with a 4Mhz crystal. Simples.

Mike.

#### Pommie

##### Well-Known Member
And a 4M crystal will give a 1uS count on timer1!

Mike.

#### Mike - K8LH

##### Well-Known Member
Consider that the servoPos[] array values are probably in the range of 1000 to 2000 (microseconds) so a compare match interrupt will occur at 1000 to 2000 microseconds and then no sooner than about 500 microseconds (500 instruction cycles) within each individual 2.5-millisecond interval. So... plenty of processing time...

Last edited:

#### Pommie

##### Well-Known Member
I would be surprised if the above code used more than 5% of the processor time. Most of the work is done by hardware.

Mike.

#### Ian Rogers

##### User Extraordinaire
Forum Supporter
OIC.... You only get an interrupt with the event trigger... I'll have to write this up and stick in the sim.. I need to see this first hand...

The only thing I can't get my head around is the servo array holds different duty cycles, so the CCP will have to change.. Yep I'm going have to check your example out.

#### Pommie

##### Well-Known Member
The Special Event Trigger fires when CCP1 = timer1 so adding a uS value to CCP1 will give a delay of that length. So CCP1+=servoPos[n] followed by CCP1+=2500-servoPos[n] will always equal 2.5mS. ServoPos should be between 1000 and 2000. It would work better with a faster clock and a timer1 divider.

Mike.

#### Mike - K8LH

##### Well-Known Member
Please note that unlike the other "compare" modes, the special event trigger mode resets the CCPR1 register pair on a compare match.

--- correction ---
as noted in a following post, the timer rather than the CCPR1 registers are reset on a compare match.

Last edited: