Sunday, October 22, 2017

Controlling Servo Motor using Raspberry Pi, Python (RPi.GPIO) software based Pulse Width Modulation(PWM)

Hello everybody. Today I am going to talk about controlling servo motors using Raspberry Pi. A servo motor is a rotary or linear actuator that allows for precise control of angular or linear position, velocity and acceleration. This project uses Python script, which uses RPi.GPIO library, run on a Raspberry Pi micro controller to send General-purpose input/output(GPIO) Pulse Width Modulation(PWM) outputs to a servo motor to set its angle. However, it is not an easy task since PWM period cannot be defined by the user. It is servo motor and manufacturer-specific.

For this project, I used the following items:

1 - Raspberry Pi 2 Model B
2 - Micro Servo Motor
3 - Jumper Male to Female Wires

The Raspberry Pi 2 has 40 pins and you can see the explanation of each pins below. The servo motor has three terminals. We need to connect these 3 terminals to the proper pins of Raspberry Pi.

1 - Position signal(PWM Pulses)
2 - Vcc (From Power Supply)
3 - Ground





As you can see from the last figure above. Servo pins are connected properly to the Raspberry Pi. The next step is the most important thing which is the specification of the servo motor. Unfortunately, I could not find the exact specification of my servo. But, I found the similar model and according to this model we have the following specifications:

- Operating Voltage : 4.8-7.0V
- PWM Input Range : Pulse Cycle 20±2ms, Positive Pulse 1~2ms

The first one means that we need at least 4.8V, at most 7.0V to run the servo motor. If the V is less than 4.8, then servo motor will not work. If it is more than 7.0V, it might be damaged. The second one shows the Pulse cycle and positive pulse which are the most essential part and it depends on the servo motor.

The servo motor angular position is controlled by applying PWM pulses of specific width. The duration of pulse varies from about 1.0ms(for 0 degree rotation) to 2.0ms(for 180 degree rotation). Check the first specification above(Positive Pulse 1~2ms).

The pulses need to be given at frequencies of about 50Hz. Check the second specification above(Pulse Cycle between 18-22ms). This means that the servo motor expects to see a pulse every 20 milliseconds(ms) which is 0.02 seconds(s) and from the following formula, we have found around 50Hz:

$$frequency(Hz) = \dfrac {1}{period(s)} \xrightarrow{} 50Hz = \dfrac {1}{0.02s}$$

Let's check the following graph.




As you see from the graph, if we want to rotate servo to 0 degree, then we should generate a pulse with "on duration" of 1.0ms which is the 5% of the pulse cycle(20ms) or another name duty cycle.

if we want to rotate servo to 90 degree, then we should generate the a pulse with "on duration" of 1.5ms which is the 7.5% of the pulse cycle(20ms) or another name duty cycle.

if we want to rotate servo to 180 degree, then we should generate the a pulse with "on duration" of 2.0ms which is the 10% of the pulse cycle(20ms) or another name duty cycle. In summary,

- if you want to rotate servo to 0 degree -> 5%
- if you want to rotate servo to 90 degree -> 7.5%
 -if you want to rotate servo to 180 degree -> 10%

However, in my case the best rotation was succeed by applying the following:

- if you want to rotate servo to 0 degree -> 2.5%
- if you want to rotate servo to 90 degree -> 6.25%
 -if you want to rotate servo to 180 degree -> 10.0%

Which means that Pulse Cycle is around 20ms±2ms which is 50Hz, Minimum Positive Pulse around 0.5ms and Maximum Positive Pulse around 2.5ms. Of course these are my assumptions, they can be different values. But, I will implement the code according to these values. 

Now, the next step is to implement the Python script. I assume that Raspberry Pi has RPi.GPIO library. The whole code is below. I also commented for you to understand it. The most important line is inside the set_angle(angle) function. The calculation of the duty_cycle = (float(angle)/24.0) + 2.5; shows how to compute the duty cycle with respect to the my assumptions.

# External module imports
import RPi.GPIO as GPIO;
from time import sleep;

# set the rotation of the servo motor
def set_angle(angle):
    # calculate the duty cycle
    duty_cycle = (float(angle)/24.0) + 2.5;
    # activate the servo pin
    GPIO.output(SERVO_PIN, True);
    # change the duty cycle to calculated value
    pwm.ChangeDutyCycle(duty_cycle);
    # send signal 0.5 seconds
    sleep(0.5);
    # deactivate the servo pin
    GPIO.output(SERVO_PIN, False);
    # change the duty cycle to 0
    pwm.ChangeDutyCycle(0);
    # print the duty cycle
    # print(duty_cycle);

# Pin Definitons:
SERVO_PIN = 11;
hertz = 50;

# close warnings
GPIO.setwarnings(False);
# setup configuration
# name all of the pins, so set the naming mode by writing
GPIO.setmode(GPIO.BOARD);
# set output pin to send our Pulse Width Modulation(PWM) signal on
GPIO.setup(SERVO_PIN, GPIO.OUT);

# Pulse Width Modulation
# setup PWM on pin 21 at 50Hz
pwm = GPIO.PWM(SERVO_PIN, hertz);
# Then start it with 0 duty cycle so it doesn't set any angles on startup
pwm.start(0.0);
# Set angle to 90 degree
set_angle(90);

print('START');

try:
    # servo motor will turn left and right
    while True:
        # Set angle to 180 degree
        set_angle(180.0);
        # Set angle to 0 degree
        set_angle(0.0);
except KeyboardInterrupt:
    pass;

# stop the PWM
pwm.stop();
# clean the all pins
GPIO.cleanup();
 
print('END');

We can validate the results like in the followings:

- if you want to rotate servo to 0 degree -> 2.5%
duty_cycle = (float(0.0)/24.0) + 2.5; -> 2.5

- if you want to rotate servo to 90 degree -> 6.25%
duty_cycle = (float(90.0)/24.0) + 2.5; -> 6.25

 -if you want to rotate servo to 180 degree -> 10.0%
duty_cycle = (float(180.0)/24.0) + 2.5; -> 10.0

As I said above, these values are my assumptions, because I could not find the exact specification. I did reverse engineering to find the values. Even if I had the exact specification, It could be different maybe. Because, sensors always give a little bit different results than it is supposed to be.

No comments:

Post a Comment