
Download the Swordfish program and Gerber files here: Dedicated Servo Controller Source Files
This project offers a dedicated solution to control servo motors, which can be controlled via USART data packets. This approach has it advantages, though Andy has made an excellent feature enriched Servo Library which can be used in pretty much any program.
Servo Motor's are one of the many hobby electronic marvels that make life a lot easier when working with motion/kinetic applications. They are somewhat precision instruments that offer their full range of motion with pulse widths usually between 1mS and 2mS. That's not much play, and further more, the positional information must be transmitted to the servo approximately fifty times a second (50Hz).
Take a moment to consider the resolution required to get any real degree of granularity with a servo; 0.001 second pulse for minimum deflection, and 0.002 second pulse for maximum deflection. Also take into account that a PIC usually operates at around 10MIPS (10 Million Instructions Per Second), each program cycle will take 100nS.
If you wanted 0.000001 second resolution (the capability to change a pulse width in 1uS increments), then the PIC would only have 10 program cycles to accommodate pulse width maintenance. Considering most normal PIC applications are doing more then one thing at a time, dedicating such hefty resources for the servo does not leave much leeway for other jobs.
The on-board CCP events are very useful for such an application, and allow very accurate and quick interrupts on exact intervals. This program makes use of both the CCP1 special event and CCP2 normal event to control 8 servos in real time. Further more, the PIC will continually monitor the UART for new servo position data.
Given the demand placed on the PIC to accommodate 8 servos, there's not much resource scope for other tasks. The use of UART definitely made good use of what resources are left over - keep in mind that I am using the hardware UART module which has little overheads to consider. The protocol employed is very lean, extremely tolerant to noisy conditions and detecting transmission errors.
Here's what the program looks like in operation;
Note 1: Servo positional information is received via UART at 38400 baud in the following manner:
| $FFFF | Servo(0) | Servo(1) | Servo(2) | Servo(3) | Servo(4) | Servo(5) | Servo(6) | Servo(7) | CRC Checksum |
| Header | Servo Positions | Validation | |||||||
Note 2: All data types are 16-Bit WORDS. This means that the header is of type WORD, so to is each Servo position and the CRC Checksum.
Note 3: CRC is calculated by XOR'ing all Servo positions together, for example:
Servo(0) = 1000
Servo(1) = 1100
Servo(2) = 1200
Servo(3) = 1300
Servo(4) = 1400
Servo(5) = 1500
Servo(6) = 1600
Servo(7) = 1700
Therefore:
CRC = 1000 XOR 1100 XOR 1200 XOR 1300 XOR 1400 XOR 1500 XOR 1600 XOR 1700
CRC = 1600
For example, to make every servo pulse equal 1.5mS, the following command would be used in SF:
USART.Write($FFFF,1500,1500,1500,1500,1500,1500,1500,1500,0)
Note 4: Program is designed to work with an 8Mhz External Oscillator.
Note 5: Ensure common ground connection between all circuits.
Thanks to XOR from the SF forum for the servo code, I've simply turned it into a dedicated controller with an UART interface.
{ Dedicated Servo Controller By Graham (Servo controller code developed by XOR) Servo positional information is received in the following manner; $FF,$FF,Servo(0),Servo(1),Servo(2),Servo(3),Servo(4),Servo(5),Servo(6),Servo(7),CRC Note 1: Both Servo(x) and CRC are WORD type variables. Note 2: CRC is calculated by XOR'ing all Servo positions together, for example; Servo(0) = 1000 Servo(1) = 1100 Servo(2) = 1200 Servo(3) = 1300 Servo(4) = 1400 Servo(5) = 1500 Servo(6) = 1600 Servo(7) = 1700 CRC = 1600 Note 3: Program is designed to work with an 16Mhz External Oscillator Note 4: Ensure common ground connection between all circuits } Device = 18F2620 Clock = 16 Config MCLRE = OFF Include "USART.bas" // define valid servo range Const Servo_Min = 600 Const Servo_Max = 2400 // default servo starting position Const DefaultPos = 1500 Dim tmpServo(8) As Word Dim ServoPeriodValue As CCPR1L.asWord Dim ServoCompareValue As CCPR2L.asword Dim ServoOnIntEnable As PIE1.bits(2) Dim ServoOnFlag As PIR1.bits(2) Dim ServoOffIntEnable As PIE2.bits(0) Dim ServoOffFlag As PIR2.bits(0) Dim Servo(8) As Word Dim Servo_Port As Byte Dim n As Byte // interrupt to service 8 servo channels Interrupt ServoManager() Save(FSR0, FSR1) // context save If ServoOnFlag = 1 Then // CCP1 Special Compare Event Interrupt LATB = Servo_Port // Turn On Next Servo ServoCompareValue = Servo(n) // Active Servo Value loaded in CCPR2 ASM rlncf Servo_Port, F, 0 End ASM Inc(n) n = n And 7 ServoOnFlag = 0 Else // CCP2 Normal Compare Event Interrupt LATB = 0 // Turn Off Active Servo ServoOffFlag = 0 EndIf Restore // context restore End Interrupt // initialize servos and interrupt settings Sub InitializeServos() Dim i As Byte LATB = 0 // servo port TRISB = 0 T1CON = %00100000 // prescaler = 1:4 = 1us ticks @16Mhz ; timer1 off TMR1L = 0 // Timer1 starts at 0 TMR1H = 0 CCP1CON = %00001011 // CCP1 triggers special compare event on Timer1 match CCP2CON = %00001010 // CCP2 normal compare event on Timer1 match ServoPeriodValue = 2500 // special event interrupt every 2500us; jumps to ServoON ISR Servo_Port = 1 // servo outputs start at PORTB.0, then shift and rotate n = 0 // start with servo(0) ServoOnFlag = 0 ServoOffFlag = 0 ServoOnIntEnable = 1 ServoOffIntEnable = 1 INTCON = %11000000 // enable GIE and PEIE For i = 0 To 7 Servo(i) = DefaultPos Next Enable(ServoManager) T1CON.0 = 1 // start Timer1 End Sub // sub that waits for the header ($FF,$FF) to be received Sub WaitForHeader() Repeat Until USART.ReadByte = 255 Repeat Until USART.ReadByte = 255 End Sub // collect each servo position, and return false if non-valid data is received Function GetServoPos() As Boolean Dim i As Byte Result = True For i = 0 To 7 tmpServo(i).byte0 = USART.ReadByte tmpServo(i).byte1 = USART.ReadByte If tmpServo(i) < Servo_Min Or tmpServo(i) > Servo_Max Then Result = False Break EndIf Next End Function // Receive the CRC, and validate data. Return false if non-valid. Function CheckCRC() As Boolean Dim i As Byte Dim CRC As Word Dim tmpCRC As Word Result = True tmpCRC = USART.ReadWord CRC = 0 For i = 0 To 7 CRC = CRC Xor tmpServo(i) Next If tmpCRC <> CRC Then Result = False EndIf End Function // update all 8 servo positions Sub UpdateServos() Dim i As Byte For i = 0 To 7 Servo(i) = tmpServo(i) Next End Sub // initialize program InitializeServos USART.SetBaudrate(br38400) // main program loop While True WaitForHeader // wait for valid data header ($FF,$FF) If GetServoPos Then // attempt to receive servo positions If CheckCRC Then // attempt to validate CRC UpdateServos // if all good, then update servo positions USART.Write(1) // send complete response (1) Else USART.Write(0) // send error response (0) EndIf EndIf Wend
The following program is an example of how to transmit the required data to the dedicated servo controller (the previous simulation animation is with this program in use);
Device = 18F2620 Clock = 40 Config OSC = HSPLL, MCLRE = OFF Include "USART.bas" Dim Servo(8) As Word, i As Byte, tmpPos As Word Sub SendServoPositions() Dim CRC As Word Dim i As Byte // send header USART.Write(255,255) // send each servo position For i = 0 To 7 USART.WriteByte(Servo(i).byte0) USART.WriteByte(Servo(i).byte1) Next // calculate and send CRC CRC = 0 For i = 0 To 7 CRC = CRC Xor Servo(i) Next USART.WriteWord(CRC) End Sub // start of main program USART.SetBaudrate(br38400) While True // rotate all servos in one direction For tmpPos = 1000 To 2000 For i = 0 To 7 Servo(i) = tmpPos Next SendServoPositions Next // rotate all servos in the other direction For tmpPos = 2000 To 1000 Step -1 For i = 0 To 7 Servo(i) = tmpPos Next SendServoPositions Next Wend
Note that the above program has the following hardware requirements:
You can simply edit either the program clock or config settings to change the above hardware requirements. This was just an example how to send packets of data to the dedicated controller.
As always, to make life even easier I prefer to have a purpose built development board. The desired features were;
The end result managed to include all of the above. Here's the PCB layout for the board;

The above design was to suit my requirements as a quick and easy development board. The end result could differ depending on what end users require. All downloads can be found at the top of the page.
Thoughts comments and feedback are all welcome!