
Download the module: Servo.bas
Full credit is given to MichealM, whose method of controlling servos - posted here - forms the basis of this module.
This module controls between 1 and 8 hobby servos connected to PortB. The module will only affect the pins which have servos connected - if less than 8 servos are used then the remaining pins on PortB can be used for other I/O.

The module is interrupt driven and uses the Timer1 and the CCP1 modules of the PIC. Although the interrupt routine is pretty lightweight (the assembler is less than 40 instructions long), users should be aware that their main program will have its execution paused every now and then to service the servos. In most cases I haven't found this to be a problem. On an 8MHz clock, I've used the module to control 8 servos whilst sending continuous UART data and reading an analogue voltage without any problems. If you do have timing-sensitive code then there are ways to mitigate problems caused by the interrupt - see below.
Please Note: The module will only work correctly if the PIC is clocked at 4, 8, 16, or 32MHz
The module's options and their default values are:
#option Servo_NumberOfServos = 8 'Number of servos (1 - 8) #option Servo_Priority = ipHigh 'Servo interrupt priority
Servo_NumberOfServos sets the number of servos which will be controlled. Valid values are 1 to 8.
Servo_Priority sets whether the module's interrupt will be run as high or low priority. For best results, this should be left as high priority.
Servo 0 should be connected to PortB.0; Servo 1 connected to PortB.1; and so on.
Servos are controlled by sending them a pulse of between 600uS and 2400uS duration every 20mS. The length of the pulse determines the servo's position, with 1500uS being approximately halfway.
The module stores the position of each servo in the word array ServoPosition. To set a particular servo's position, load the length of the desired pulse (in uS) into the array. i.e. to set Servo 3 to the maximum position, use ServoPosition(3) = 2400
Servo.On
Calling this routine will enable the interrupt and start sending pulses to the servos.
Servo.Off
Calling this routine stops the interrupt from firing and stops sending pulses to the servos. Note: If they are not receiving control pulses then servos will only hold their current position if no force is acting on them. However, turning off the interrupt may be useful if you need to execute a piece of code which must not be interrupted.
This example program controls 3 servos. Servo 0 is controlled by an analog input on AN0 while Servos 1 & 2 constantly move through their full range of motion.
Device = 18F2520 Clock = 4 #option Servo_NumberOfServos = 3 Include "Servo.bas" Dim Counter As Word Function GetVoltage() As Byte 'Returns byte value representing voltage on AN0 pin (0 = 0V, 255 = 5V) ADCON0.0 = 1 'Turn on A/D module ADCON0.5 = 0 '} ADCON0.4 = 0 '}Set A/D channel to AN0 ADCON0.3 = 0 '} ADCON0.2 = 0 '} ADCON0.1 = 1 'Start conversion While ADCON0.1 = 1 '}Wait for conversion to finish Wend '} Result = ADRESH 'Store result (8 MSBs only) ADCON0.0 = 0 'Turn off A/D module End Function 'Main Program '---Set-up analog to digital converter ADCON1.5 = 0 '}Set A/D Vref+ and Vref- to Vdd and Vss ADCON1.4 = 0 '} ADCON1.3 = 1 '} ADCON1.2 = 1 '}AN0 set to analog input ADCON1.1 = 1 '} ADCON1.0 = 0 '} ADCON2.7 = 0 'A/D result left justified ADCON2.5 = 1 '} ADCON2.4 = 0 '}Set A/D Aquisition time to 12 Tad ADCON2.3 = 1 '} ADCON2.2 = 0 '}Set A/D Converstion Clock to Fosc / 8 ADCON2.1 = 0 '}This is the minimum possible with a ADCON2.0 = 1 '}clock speed of 4MHz (Tad = 2uS) Input(PORTA.0) Servo.On 'Start sending servo control pulses While True For Counter = 600 To 2400 Servo.ServoPosition(0) = GetVoltage * 7 + 600 Servo.ServoPosition(1) = Counter Servo.ServoPosition(2) = 3000 - Counter DelayMS(1) Next For Counter = 2400 To 600 Step -1 Servo.ServoPosition(0) = GetVoltage * 7 + 600 Servo.ServoPosition(1) = Counter Servo.ServoPosition(2) = 3000 - Counter DelayMS(1) Next Wend
This Boolean variable is set to "True" when the interrupt has finished executing. The interrupt is only called at the end of each servo pulse. Therefore, if you have a piece of code which must not be interrupted then you can do something like this:
Servo.ServoInterruptComplete = False Repeat '}Wait for the interrupt to complete Until Servo.ServoInterruptComplete = True '} 'Do important bit of code
Once the flag is set to True, you know you have at least 600us (minimum servo pulse duration) to execute code before the interrupt will trigger again.