Overview of Servo Motor
Servo Motor
A servo motor is an electric device designed for precise control of angular rotation, making it ideal for applications requiring exact positioning, such as robotic arm control. It includes a motor paired with control circuitry to manage the motor shaft’s position with high accuracy. Operating as a closed-loop system, the servo motor’s rotation angle is adjusted by applying a Pulse Width Modulation (PWM) signal. By changing the width of this PWM signal, the rotation angle and direction of the motor can be precisely controlled. For further details on servo motors and how to use them, see the Servo Motor topic in the sensors and modules section.
Generating PWM using PIC18F4550
The SG90 servo motor has a practical duty cycle range for -90° to +90° rotation that differs slightly from the ideal. Specifically:
- At around 0.6ms (3% duty cycle), the shaft positions at -90°.
- At approximately 1.4ms (7% duty cycle), the shaft reaches 0° (neutral position).
- At close to 2.4ms (12% duty cycle), the shaft reaches +90°.
To control the servo motor across the -90° to +90° range, a 50Hz PWM waveform is required, with a duty cycle varying from approximately 0.6ms to 2.4ms. This can be achieved by using the fast PWM mode of the PIC18F4550 with Timer1.
For additional details on generating PWM with the PIC18F4550 and guidance on usage, refer to the PIC18F4550 PWM documentation.
Connection Diagram of Servo Motor to PIC18F4550
PIC18F4550 Interface with Servo Motor
Servo Motor Example using PIC18F4550
Let’s now program the PIC18F4550 to generate a 50Hz PWM signal to control the servo motor, enabling rotation between -90° and +90° angles.
Servo Motor Code for PIC18F4550
/*
* Servo control using PIC
* http://www.electronicwings.com
*/
#include <pic18f4550.h>
#include <stdio.h>
#include <math.h>
#include "Configuration_header_file.h"
#define MINTHR 8000
#define RESOLUTION 488
#define InternalOsc_8MHz 8000000
#define InternalOsc_4MHz 4000000
#define InternalOsc_2MHz 2000000
#define InternalOsc_1MHz 1000000
#define InternalOsc_500KHz 500000
#define InternalOsc_250KHz 250000
#define InternalOsc_125KHz 125000
#define InternalOsc_31KHz 31000
#define Timer2Prescale_1 1
#define Timer2Prescale_4 4
#define Timer2Prescale_16 16
void PWM_Init() /* Initialize PWM */
{
TRISCbits.TRISC2 = 0; /* Set CCP1 pin as output for PWM out */
CCP1CON = 0x0C; /* Set PWM mode */
}
int setPeriodTo(unsigned long FPWM)/* Set period */
{
int clockSelectBits, TimerPrescaleBits;
int TimerPrescaleValue;
float period;
unsigned long FOSC, _resolution = RESOLUTION;
if (FPWM < MINTHR) {TimerPrescaleBits = 2; TimerPrescaleValue = Timer2Prescale_16;}
else {TimerPrescaleBits = 0; TimerPrescaleValue = Timer2Prescale_1;}
if (FPWM > _resolution) {clockSelectBits = 7; FOSC = InternalOsc_8MHz;}
else if (FPWM > (_resolution >>= 1)) {clockSelectBits = 6; FOSC = InternalOsc_4MHz;}
else if (FPWM > (_resolution >>= 1)) {clockSelectBits = 5; FOSC = InternalOsc_2MHz;}
else if (FPWM > (_resolution >>= 1)) {clockSelectBits = 4; FOSC = InternalOsc_1MHz;}
else if (FPWM > (_resolution >>= 1)) {clockSelectBits = 3; FOSC = InternalOsc_500KHz;}
else if (FPWM > (_resolution >>= 1)) {clockSelectBits = 2; FOSC = InternalOsc_250KHz;}
else if (FPWM > (_resolution >>= 1)) {clockSelectBits = 1; FOSC = InternalOsc_125KHz;}
else {clockSelectBits = 0; FOSC = InternalOsc_31KHz;}
period = ((float)FOSC / (4.0 * (float)TimerPrescaleValue * (float)FPWM)) - 1.0;
period = round(period);
OSCCON = ((clockSelectBits & 0x07) << 4) | 0x02;
PR2 = (int)period;
T2CON = TimerPrescaleBits;
TMR2 = 0;
T2CONbits.TMR2ON = 1; /* Turn ON Timer2 */
return (int)period;
}
void SetDutyCycleTo(float Duty_cycle, int Period)
{
int PWM10BitValue;
PWM10BitValue = 4.0 * ((float)Period + 1.0) * (Duty_cycle/100.0);
CCPR1L = (PWM10BitValue >> 2);
CCP1CON = ((PWM10BitValue & 0x03) << 4) | 0x0C;
}
void delay(unsigned int val)
{
unsigned int i,j;
for(i=0;i<val;i++)
for(j=0;j<10;j++);
}
int main()
{
int Period;
PWM_Init(); /* Initialize PWM */
Period = setPeriodTo(50); /* 50Hz PWM frequency */
/* Note that period step size will gradually increase with PWM frequency */
while(1)
{
SetDutyCycleTo(3.0, Period); /* 3% duty cycle */
delay(1000);
SetDutyCycleTo(7.0, Period); /* 7% duty cycle */
delay(1000);
SetDutyCycleTo(12.0, Period); /* 12% duty cycle */
delay(1000);
}
}
Change the Angle of Servo Motor with Potentiometer using PIC18F4550
Now, let’s program the PIC18F4550 to generate a 50Hz PWM signal to control the servo motor, allowing rotation between -90° and +90°, using an external potentiometer knob.
In this setup, we use ADC channel 0 of the PIC18F4550 to read the position of the potentiometer. Based on the ADC value, we adjust the PWM duty cycle accordingly. For more details on using the ADC in the PIC18F4550, refer to the ADC in PIC18F4550 documentation.
Connection Diagram of Servo Motor and Pot to PIC18F4550
PIC18F4550 Interface with Servo Motor and POT
Code for Servo control using POT with PIC18F4550
/*
* Servo control using POT with PIC
* http://www.electronicwings.com
*/
#include <pic18f4550.h>
#include <stdio.h>
#include <math.h>
#include "Configuration_header_file.h"
#include "ADC_Header_File.h"
#define MINTHR 8000
#define RESOLUTION 488
#define InternalOsc_8MHz 8000000
#define InternalOsc_4MHz 4000000
#define InternalOsc_2MHz 2000000
#define InternalOsc_1MHz 1000000
#define InternalOsc_500KHz 500000
#define InternalOsc_250KHz 250000
#define InternalOsc_125KHz 125000
#define InternalOsc_31KHz 31000
#define Timer2Prescale_1 1
#define Timer2Prescale_4 4
#define Timer2Prescale_16 16
void PWM_Init() /* Initialize PWM */
{
TRISCbits.TRISC2 = 0; /* Set CCP1 pin as output for PWM out */
CCP1CON = 0x0C; /* Set PWM mode */
}
int setPeriodTo(unsigned long FPWM)/* Set period */
{
int clockSelectBits, TimerPrescaleBits;
int TimerPrescaleValue;
float period;
unsigned long FOSC, _resolution = RESOLUTION;
if (FPWM < MINTHR) {TimerPrescaleBits = 2; TimerPrescaleValue = Timer2Prescale_16;}
else {TimerPrescaleBits = 0; TimerPrescaleValue = Timer2Prescale_1;}
if (FPWM > _resolution) {clockSelectBits = 7; FOSC = InternalOsc_8MHz;}
else if (FPWM > (_resolution >>= 1)) {clockSelectBits = 6; FOSC = InternalOsc_4MHz;}
else if (FPWM > (_resolution >>= 1)) {clockSelectBits = 5; FOSC = InternalOsc_2MHz;}
else if (FPWM > (_resolution >>= 1)) {clockSelectBits = 4; FOSC = InternalOsc_1MHz;}
else if (FPWM > (_resolution >>= 1)) {clockSelectBits = 3; FOSC = InternalOsc_500KHz;}
else if (FPWM > (_resolution >>= 1)) {clockSelectBits = 2; FOSC = InternalOsc_250KHz;}
else if (FPWM > (_resolution >>= 1)) {clockSelectBits = 1; FOSC = InternalOsc_125KHz;}
else {clockSelectBits = 0; FOSC = InternalOsc_31KHz;}
period = ((float)FOSC / (4.0 * (float)TimerPrescaleValue * (float)FPWM)) - 1.0;
period = round(period);
OSCCON = ((clockSelectBits & 0x07) << 4) | 0x02;
PR2 = (int)period;
T2CON = TimerPrescaleBits;
TMR2 = 0;
T2CONbits.TMR2ON = 1; /* Turn ON Timer2 */
return (int)period;
}
void SetDutyCycleTo(float Duty_cycle, int Period)/* Set Duty cycle for given period */
{
int PWM10BitValue;
PWM10BitValue = 4.0 * ((float)Period + 1.0) * (Duty_cycle/100.0);
CCPR1L = (PWM10BitValue >> 2);
CCP1CON = ((PWM10BitValue & 0x03) << 4) | 0x0C;
}
int main()
{
float Duty_Scale;
int Period;
ADC_Init();
PWM_Init(); /* Initialize PWM */
Period = setPeriodTo(50); /* 50Hz PWM frequency */
/* Note that period step size will gradually increase with PWM frequency */
while(1)
{
/* Scale Duty Cycle in between 3.0-12.0 */
Duty_Scale = (((float)(ADC_Read(0)/4.0)*9.0)/255.0) + 3.0;
SetDutyCycleTo(Duty_Scale, Period);
}
}