
Servo Motor Control with PIC Microcontroller

Hello friends! Today we will control a servo motor with the PIC16F877A and observe its movement in Proteus ISIS. We will begin with the three servo connections, understand the command waveform, and then write a small program that selects positions using two pushbuttons.
In the previous tutorials, we explored DC motor direction control and stepper motor sequencing. A positional hobby servo is different because the motor, gearbox, position sensor, and controller are already assembled together. Our PIC requests a position rather than switching the motor windings itself.
We will keep the original PIC and button arrangement, but use a repeated control pulse with a sensible frame period. Along the way, we will separate what the simulated angle display tells us from what a physical servo must be calibrated to do. The example is deliberately small enough that you can trace every input and output.
How a Positional Servo Works
A conventional positional hobby servo contains a motor connected through a reduction gearbox to an output shaft. A position sensor measures that shaft's position. Internal electronics compare the requested position with the measured position and drive the motor to reduce the difference. This closed-loop arrangement is the reason we can request a position with one signal wire.
The signal does not provide motor power. The other two connections supply energy to the servo electronics and motor. A three-wire cable therefore normally contains supply, ground, and command, but their physical order and colours must be checked against the actual product. Do not identify the signal wire only by assuming that it is in the middle.
Also distinguish a positional servo from a continuous-rotation servo. A positional unit tries to reach an angle within its mechanical travel. A continuous-rotation unit generally interprets the pulse as a speed and direction request, with a neutral command near its stopped condition. The same firmware can therefore produce very different motion when the wrong type is selected.
| Motor arrangement | Command supplied by the controller | Position information |
|---|---|---|
| DC motor with an H-bridge | Direction and drive level | Requires an additional position sensor for closed-loop positioning |
| Stepper motor with a suitable driver | Winding sequence or step and direction signals | Often inferred from commanded steps unless feedback is added |
| Positional hobby servo | Repeated pulse-width position command | Internal feedback controls the shaft; ordinary three-wire control does not report the measured angle to the PIC |
Understand the Servo Command Pulse
A common starting point for a hobby servo is a pulse repeated about every 20 ms, with a neutral pulse near 1.5 ms. The width of the high portion carries the position request. The exact acceptable range, direction of motion, neutral point, and travel depend on the servo. Pololu's measurements of the servo interface illustrate why a universal pulse-to-angle conversion should not be assumed.
For our initial simulation, we will select 1.3 ms, 1.5 ms, and 1.7 ms commands. These give three nearby requests without immediately using wide endpoint pulses. On physical hardware, even this starting range must be checked against the servo specification and the attached mechanism. First movement should be observed with the linkage unloaded.
The frame frequency is f = 1 / T. For T = 0.020 s, this becomes f = 50 Hz. A 1.5 ms high pulse in that frame has duty cycle D = 1.5 / 20 × 100 = 7.5%. Duty cycle is useful when measuring the waveform, but pulse width is the command quantity we intend to control.
| Requested state | High time | Nominal low time | Total programmed delay |
|---|---|---|---|
| Position A | 1.3 ms | 18.7 ms | 20 ms |
| Centre request | 1.5 ms | 18.5 ms | 20 ms |
| Position B | 1.7 ms | 18.3 ms | 20 ms |
Instruction execution adds a little overhead around these delays. We will measure the generated waveform rather than claiming that a blocking software loop is an exact hardware pulse generator. The example has no interrupt-driven background tasks that could unexpectedly stretch the high pulse.
Prepare the PIC Circuit in Proteus ISIS
Add a PIC16F877A, a positional servo model, a crystal, reset and button resistors, two pushbuttons, and the required supply connections. The original component-selection screenshot is retained below. Component names and selector categories can vary between Proteus versions, so identify the part and its model rather than relying only on its position in the list.
The original design uses a 16 MHz oscillator and two buttons on RD0 and RD1. We will use RB0 for the command signal because that is the pin connected to the servo in the original schematic. In the 40-pin DIP package, RB0 is pin 33, RD0 is pin 19, and RD1 is pin 20.
Treat this older screenshot as a connection reference. Its crystal capacitors are labelled 1 nF, which should not be copied as a general-purpose physical oscillator design. Select the crystal capacitors from the crystal load specification and the PIC oscillator guidance. Likewise, the supply symbols in the drawing do not specify the current capability of a real power supply.
| Item | Connection | Reason |
|---|---|---|
| Servo signal | PIC RB0 | Receives the position-command pulse |
| Servo supply | Supply rated for the selected servo | Powers its internal motor and controller |
| Servo ground | Common ground with the PIC | Provides the command's voltage reference |
| Button A | RD0 to ground when pressed, with a 10 kΩ pull-up | Active-low request for position A |
| Button B | RD1 to ground when pressed, with a 10 kΩ pull-up | Active-low request for position B |
| MCLR | Appropriate reset pull-up; 10 kΩ in this example | Keeps the PIC out of reset during normal operation |
| Oscillator | 16 MHz crystal and suitable load capacitors | Establishes the instruction timing used by the compiler |
Connect both PIC supply pins and both ground pins in a physical 40-pin device: VDD is on pins 11 and 32, and VSS on pins 12 and 31. Add local decoupling near these pairs. Proteus may hide power pins behind named nets; check those nets instead of assuming the missing pins mean that power connections are unnecessary.
The Microchip PIC16F87XA datasheet provides the authoritative pinout, oscillator configuration, and port behaviour. Configure PORTD as ordinary digital I/O rather than as a parallel slave port. The firmware below clears PSPMODE through TRISE and makes RD0 and RD1 inputs.
Write the mikroC Program
Create a mikroC project for PIC16F877A with a 16 MHz clock. Select the appropriate HS oscillator configuration for this crystal arrangement and disable the watchdog for this simple delay-based demonstration. Set the simulated processor frequency to 16 MHz too. A different compiler clock setting changes the pulse widths even when the delay numbers in the source remain unchanged.
With neither button pressed, the program retains the last requested position. Button A selects the shorter pulse; button B selects the longer pulse. Pressing both selects the centre request. Two consecutive matching button samples are required before accepting a change, providing a basic debounce step for this demonstration.
unsigned char position = 1;
unsigned char previousButtons = 3;
unsigned char buttons;
void main() {
PORTB = 0;
TRISB = 0;
TRISE = 0x07; // PSPMODE off
TRISD = 0xFF;
while (1) {
buttons = PORTD & 0x03;
if (buttons == previousButtons) {
if (buttons == 2) position = 0; // RD0 pressed
if (buttons == 1) position = 2; // RD1 pressed
if (buttons == 0) position = 1; // both pressed
}
previousButtons = buttons;
PORTB.F0 = 1;
if (position == 0) Delay_us(1300);
else if (position == 2) Delay_us(1700);
else Delay_us(1500);
PORTB.F0 = 0;
if (position == 0) Delay_us(18700);
else if (position == 2) Delay_us(18300);
else Delay_us(18500);
}
}
The source uses mikroC's device-register syntax and delay routines. It is not an Arduino sketch or an XC8 project. Build it using the matching compiler, select the generated HEX file in the PIC's Program File property, and restart the simulation after loading the new firmware.
Read the button masks carefully: 2 means binary 10, so RD1 is high and RD0 is low. Because the switches connect to ground, the low input is the pressed button. The pull-ups make the unpressed state deterministic instead of leaving the input floating.
The debouncing rule samples approximately once per frame. A stable change is therefore accepted after roughly one to two frame intervals, depending on when the button changes. This is sufficient for understanding the example, but a product should choose a debounce policy that matches its switches and responsiveness requirements.
Measure the Signal Before Interpreting the Angle
- Run the simulation with neither button pressed and connect the oscilloscope to RB0.
- Check for a high pulse near 1.5 ms and a repetition period near 20 ms.
- Press button A and confirm that the high pulse becomes approximately 1.3 ms.
- Press button B and check for approximately 1.7 ms.
- Press both buttons and confirm the return to the centre command.
- Release the buttons and verify that the last command continues to repeat.
The original simulation screenshot displays an angle such as +90 degrees. That is a result of the model's configuration and its reference convention; it does not prove that a particular physical servo reaches the same angle for the same pulse. Check pulse timing first, then examine how the selected model maps that timing into movement.
A repeated position request does not mean the shaft instantly reaches that position. Travel takes time and depends on the servo's speed, load, supply, and controller. A simulation may simplify those effects. Avoid interpreting the timing of a screen animation as a measured mechanical specification.
Calibrate Pulse Width to Your Required Movement
Suppose a particular servo and linkage have been measured to move from 20 degrees at 1.2 ms to 140 degrees at 1.8 ms. Within that measured region, a first linear approximation is t = tA + (θ - θA)(tB - tA)/(θB - θA). These are illustrative calibration points, not universal servo endpoints.
For a requested 80 degrees, the calculation gives t = 1.2 + (80 - 20)(1.8 - 1.2)/(140 - 20) = 1.5 ms. If the measured response is nonlinear, use more calibration points instead of assuming a straight line describes the entire travel. Mechanical linkage geometry can also make output movement nonlinear even when the servo shaft response is approximately linear.
Clamp software requests to the calibrated range. A requested angle beyond the mechanism's travel should not silently become a wider pulse. If you replace the servo or change the horn position, repeat the calibration because the mechanical reference may have changed.
Power, Timing, and Practical Improvements
A servo can draw short current peaks when starting, changing direction, or resisting a load. Supply it from a source rated for its operating voltage and expected current, including the manufacturer's stall-current information. Keep motor-current paths away from sensitive PIC supply wiring, use suitable local energy storage, and maintain a clear ground reference for the command.
A capacitor cannot compensate indefinitely for an undersized supply. The simple relationship ΔV = IΔt/C shows why: if a capacitor alone supplied 0.5 A for 1 ms, limiting its voltage drop to 0.25 V would require C = 0.5 × 0.001 / 0.25 = 0.002 F, or 2000 µF, ignoring resistance and other effects. The supply must still provide the average demand and recharge it.
The blocking loop is easy to follow but spends nearly all its time waiting. For a larger project, schedule the rising and falling edges with a timer or compare peripheral. That lets the main program process communications and sensors without inserting unpredictable work inside the high pulse. Check the available peripheral's timing range before assuming a standard high-frequency PWM setup can generate a 20 ms frame directly.
Troubleshooting and Review
Plan a Timer-Based Version
We can estimate the timing resources before writing an interrupt-driven version. With a 16 MHz oscillator, the PIC's internal instruction clock is 4 MHz, so one instruction-clock period is 0.25 µs. If Timer1 uses that internal clock with a 1:8 prescaler, one timer count represents 2 µs. This calculation concerns the timer tick; it does not mean that the entire program executes one useful operation every tick.
A 1.5 ms interval then requires 1500 / 2 = 750 timer counts. A 20 ms frame requires 20000 / 2 = 10000 counts. Both fit within a 16-bit timer's counting range. For an overflow-based approach, an ideal preload for the high interval would be 65536 - 750 = 64786. The remaining 18.5 ms corresponds to 9250 counts and an ideal preload of 56286.
Those values are a design calculation, not a complete interrupt routine. A real implementation must account for the time between overflow, interrupt entry, output switching, and timer reload. Repeatedly adding that latency can lengthen the frame and vary the command. A hardware compare event or a schedule based on a continuously running timebase can reduce dependence on software execution time.
When a button changes the requested width, copy that request into the active timing state at a defined frame boundary. Otherwise, the rising half of one command and the falling half of another can accidentally form a pulse you never intended. Keep interrupt work short and leave text formatting, display updates, and communications processing outside the time-critical path.
Choose What the Application Should Report
A user interface should distinguish requested position from measured position. The ordinary three-wire connection in this tutorial sends a command but returns no angle measurement. A label reading “Position B requested” accurately describes the available information. A label claiming “Position B reached” would require additional evidence, such as a feedback output, encoder, or limit sensor appropriate to the mechanism.
This distinction becomes useful when a linkage can jam or the supply can dip. The PIC may continue producing excellent pulses while the shaft fails to move. Add feedback when the application needs confirmation of movement, and define how the controller should respond when the requested and observed states disagree.
| Symptom | Useful check |
|---|---|
| No response to buttons | Check RD0 and RD1 pull-ups, common ground, and the selected HEX file. |
| Wrong pulse widths | Compare the compiler clock with the Proteus processor frequency. |
| Servo jitters while other code runs | Measure high-time variation and move pulse generation to a predictable timer schedule. |
| PIC resets when the servo moves | Measure the PIC supply during motion and review current paths and decoupling. |
| Angle differs from the screenshot | Check the model mapping or calibrate the actual servo and linkage. |
| Servo rotates continuously | Confirm that it is a positional model rather than a continuous-rotation type. |
This tutorial now provides a complete learning path from command timing to a button-controlled simulation. The useful result is understanding what RB0 must generate and how the motor assembly interprets it. Simulation checks the control logic; physical measurements establish real travel, current demand, and mechanical behaviour.
Frequently Asked Questions
Does a 1.5 ms pulse always mean exactly 90 degrees?
No. It is commonly used as a neutral request, but the angle depends on the servo and its installed mechanical reference. Use measured calibration points for meaningful angle commands.
Can I stop sending pulses after the servo reaches its position?
Do not assume that this preserves holding behaviour. Response to a missing signal varies between servo designs. Keep sending valid commands when the application requires continued position control, and check the selected servo's documented behaviour.
Why not connect the servo motor through an L293D here?
The hobby servo already contains a motor driver and feedback controller. Its external signal wire expects a position command. An H-bridge is appropriate when you are controlling a separate bare motor.
Can I use the same idea with Arduino?
Yes. See the related Arduino servo control tutorial. The command waveform remains the central idea, even though the programming tools and pin configuration differ.
You can also compare the 555 timer servo-control circuit or continue to relay simulation in Proteus ISIS. In each case, begin by identifying the signal the device actually expects.
I was suggested this blog by my cousin. I am not sure whether this post
is written by him as no one else know such detailed about my difficulty.
You are amazing! Thanks!
wonderul post, very informative. I ponder why the opposite specialists of this sector don't understand this.
You must proceed your writing. I'm confident, you've a great readers' base already!
I NEED VEDIO TUTOR FOR PROTEUS 8.1
BEFORE I USED 7.2 SP2
U TUBE IS NORKING ON MY COMLAPTOP
IS THER ANY TUTOR IN OTHER THEN UUBE?
tHANKYOU
a m tAHIR
Note:
The code and the Proteus Simulation has already been emailed to all the subscribed members. If you are new here then subscribe to our newsletter and it will be emailed to you as well.
!! Did not received anything by email !!
1 reply
JUST SUBSCRIBED
Bom, montei o projeto e na hora de testar observei que o servo obedecia apenas ao comando de B1 e em seguida não obedecia a mais comandos!
fiz uma pequena alteração no código e o sistema passou a responder aos comandos de B2 assim como o s comandos de B1!
Sendo assim, o servo motor gira para +90.0 e também para -90.0
veja como ficou o código depois de alterado.
#define M1 PORTB.F7
#define M2 PORTB.F6
#define B1 PORTD.F0
#define B2 PORTD.F1
int a;
void main() {
TRISB=0;
TRISD=0xFF;
PORTB = 0;
a = 1;
while(1){
if((B1 == 0) && (a==1)){
PortB=0xFF;
delay_ms(100);
PortB=0x00;
delay_ms(1);
PortB=0xFF;
a=0;
}
if((B2 == 0) && (a == 0 )){
PortB=0x00;
delay_ms(100);
PortB=0xFF;
delay_ms(1);
PortB=0x00;
a=1;
}
}
}

























Comments
7