Servo Motor Interfacing with PIC18F4550

Servo motor is an electromechanical device that consists of a Motor, Gear assembly, and feedback circuitry. It is used in Robotics applications, airplanes, rudders, quadcopters, etc.

24.4k views5 min readUpdated Aug 8, 2023

Overview of Servo Motor

This is the picture of Servo Motor
Servo Motor

 

A servo motor is an electric device used for precise control of angular rotation. It is used where precise control is required, like in the case of control of the robotic arm.

  • It consists of a suitable motor with control circuitry for precise position control of the motor shaft.
  • It is a closed-loop system.
  • The rotation angle of the servo motor is controlled by applying a PWM signal to it.
  • By varying the width of the PWM signal, we can change the rotation angle and direction of the motor.

For more information about Servo Motor and how to use it, refer to the topic Servo Motor in the sensors and modules section.

 

Generating PWM using PIC18F4550

SG90 servo has a practical duty cycle time for -90° to +90 rotation that is different from ideal.

At ~0.6ms (3% duty cycle) we get shaft position at -90° of its rotation.

At ~1.4ms (7% duty cycle) we get shaft position at 0° (neutral) of its rotation.

At ~2.4ms (12% duty cycle) we get shaft position at +90° of its rotation.

 

To control servo motor in between -90° to +90° rotation. We need to generate a PWM waveform of 50Hz with duty cycle variation from ~0.6ms to ~2.4ms. We can use a fast PWM mode of PIC18F4550 using Timer1.

For more information about generating PWM in PIC18F4550 and how to use it, refer to PIC18F4550 PWM.

 

Connection Diagram of Servo Motor to PIC18F4550

This is the picture of PIC18F4550 Interface with Servo Motor
PIC18F4550 Interface with Servo Motor

 

Servo Motor Example using PIC18F4550

Now let’s program PIC18F4550 to generate 50Hz PWM to control Servo Motor in an angle between -90° to +90° rotation.

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 PIC18F4550 to generate 50Hz PWM to control Servo Motor in an angle between -90° to +90° rotation using external potentiometer knob.

  • Here we are using ADC channel 0 of PIC18F4550 to read external potentiometer knob and according to the ADC value, we are varying duty cycle of PWM.
  • Refer ADC in PIC18F4550 for more information on ADC in PIC18F4550.

 

Connection Diagram of Servo Motor and Pot to PIC18F4550

This is the picture of PIC18F4550 Interface with Servo Motor
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);
    }
}

 

Video of Controlling Servo Motor using Potentiometer with PIC18F4550

Components Used

Powered byMouser Electronics

Downloads

Comments12

Join the discussion — share a question or tip.

  • ST
    STIPpro

    what happen if the input digital? how the conversion looks like?

  • LE
    leeyunjai1982

    Can this chip (PIC18F4550) control many servo motors (12 items) simultaneously?

  • GO
    gopalbiswas

    HI

  • PR
    pratiksawarkar09

    if my oscillator frequency is constant to 8MHZ then can i use timer2 postscallar to obtain 50HZ as output frequency

  • PR
    pratiksawarkar09

    Duty_Scale = (((float)(ADC_Read(0)/4.0)*9.0)/255.0) + 3.0; please explin me this calculation

  • BL
    blarblublublar

    and Why MINTHR and resolution are 8000 and 488?

    • LO
      lokeshc

      Minimum threshold to decide which timerprescale bit to be used and resolution for decide internal frequency to gain possible accuracy with period

    • BL
      blarblublublar

      Replying to @lokeshc

      Now i know overview but specifically why 8000 and 488 values are used?

    • MA
      manaskant1993

      Replying to @blarblublublar

      i could have a possible explanation for these values (of course i could be wrong....but still)...........typical operating frequency (Fosc) of pic is 8 MHz......now minimum and maximum prescale values of timer2 are 1 and 16 respectively........and the formula to find out value of PR2 register is PR2 = (Fosc/(4*Fpwm*TMR2_prescale_value))-1 as per datasheet.......since PR2 is an 8 bit register, the min and maximum values it can take for generating a valid pwm frequency is 1 and 255 respectively.......so to find out MINTHR value we substitute in above formula PR2 = 255 &amp; TMR2_prescale_value = 1, and we get Fpwm = 7812.5 Hz which can be rounded off to 8000 Hz approx., and thus MINTHR = 8000..............and then to find out RESOLUTION value we substitute in above formula PR2 = 255 &amp; TMR2_prescale_value = 16, and we get Fpwm = 488.28125 Hz which can be rounded off to 488 approx., and thus RESOLUTION = 488.............i guess this is the reason for choosing 8000 and 488..........if somebody as a different explanation then we all would be glad to know.

  • BL
    blarblublublar

    what is MINTHR?

  • NG
    nglethanhtrung

    Hi, i don't understand define MINTHR why is 8000 and next line of code why is 488. thanks