Development Step #11 – Controlling Pan/Tilt servos with Raspberry Pi
I will try to calculate the Duty Cycle of the servos since they might be different from what the manufacture has claimed. I found a nice instructables tutorial to take me through the process.
o Learning Experience.
I found the manufacturer’s data sheet for the servos:
The Raspberry Pi has no analog output, but we can simulate this, using a PWM (Pulse Width Modulation) approach. What we will do is to generate a digital signal with a fixed frequency, where we will change the pulse train width, what will be “translated” as an “average” output voltage level.
Note that what matters here is not the frequency itself, but the “Duty Cycle”, that is the relation between the time that the puls is “high” divided by the wave period. For example, suppose that we will generating a 50Hz pulse frequency on one of our Raspberry Pi GPIO. The period (p) will the inverse of frequency or 20ms (1/f). If we want that our LED with a “half” bright, we must have a Duty Cycle of 50%, that means a “pulse” that will be “High” for 10ms.
This principle will be very important for us, to control our servo position, once the “Duty Cycle” will define the servo position.
I am connecting the pan/tilt servos to the following GPIO pins:
- Tilt Servo – GPIO 17
- Pan Servo – GPIO 27
Both of them are taking 5V power from Pin#2 and Pin#4, and they are also grounded to the Raspberry Pi.
In theory, the servo will be on its
- Initial Position (0 degrees) when a pulse of 1ms is applied to its data terminal
- Neutral Position (90 degrees) when a pulse of 1.5 ms is applied to its data terminal
- Final Position (180 degrees) when a pulse of 2 ms is applied to its data terminal
To program a servo position using Python will be very important to know the correspondent “Duty Cycle” for the above positions, let’s do some calculation:
- Initial Position ==> (0 degrees) Pulse width ==> 1ms ==> Duty Cycle = 1ms/20ms ==> 2.0%
- Neutral Position (90 degrees) Pulse width of 1.5 ms ==> Duty Cycle = 1.5ms/20ms ==> 7.5%
- Final Position (180 degrees) Pulse width of 2 ms ==> Duty Cycle = 2ms/20ms ==> 10%
So the Duty Cycle should vary on a range of 2 to 10 %.
Now lets find out that for each servo individually.
by typing the following commands in the Terminal
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM) tiltPin = 17
Now, we must specify that this pin will be an “output”
And, what will be the frequency generated on this pin, that for our servo will be 50Hz:
tilt = GPIO.PWM(tiltPin, 50)
Now, let’s start generating a PWM signal on the pin with an initial duty cycle (we will keep it “0”):
Now, you can enter different duty cycle values, observing the movement of your servo. Let’s start with 2% and see what happens (we expect that the servo goes to “zero position”):
Those are the results for both the Pan and the Tilt servos:
The PWM commands to be sent to our servo are in “duty cycles” as we saw on the last step. But usually, we must use “angle” in degrees as a parameter to control a servo. So, we must convert “angle” that is a more natural measurement to us in duty cycle as understandable by our Pi.
I know that duty cycle range goes from 2% to 12% and that this is equivalent to angles that will range from 0 to 180 degrees. Also, we know that those variations are linear.
So, given an angle, we can have a correspondent duty cycle:
dutycycle = angle/18 + 2
Now we can make a Python script using the formula
from time import sleep import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) def setServoAngle(servo, angle): pwm = GPIO.PWM(servo, 50) pwm.start(8) dutyCycle = angle / 18. + 3. pwm.ChangeDutyCycle(dutyCycle) sleep(0.3) pwm.stop() if __name__ == '__main__': import sys servo = int(sys.argv) GPIO.setup(servo, GPIO.OUT) setServoAngle(servo, int(sys.argv)) GPIO.cleanup()
sudo python3 angleServoCtrl.py 17 45
The above command will position the servo connected on GPIO 17 with 45 degrees in “elevation”. A similar command could be used for Pan Servo control (position to 45 degrees in “azimuth”):
sudo python servo5.py 27 45
Now lets use the script for controlling both of the servos at the same time:
from time import sleep import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) pan = 27 tilt = 17 GPIO.setup(tilt, GPIO.OUT) # white => TILT GPIO.setup(pan, GPIO.OUT) # gray ==> PAN def setServoAngle(servo, angle): assert angle >=50 and angle <= 130 pwm = GPIO.PWM(servo, 50) pwm.start(8) dutyCycle = angle / 18. + 2. pwm.ChangeDutyCycle(dutyCycle) sleep(0.3) pwm.stop() if __name__ == '__main__': import sys if len(sys.argv) == 1: setServoAngle(pan, 110) setServoAngle(tilt, 50) else: setServoAngle(pan, int(sys.argv)) # 30 ==> 90 (middle point) ==> 150 setServoAngle(tilt, int(sys.argv)) # 30 ==> 90 (middle point) ==> 150 GPIO.cleanup()
When the script is executed, you must enter as parameters, Pan angle and Tilt angle. For example:
sudo python3 servo6.py 120 50
I found the proper angles for my Servos
- Min – 50
- Mid – 110
- Max – 150
- Min – 50
- Mid – 60
- Max – 90
Now the final test is a loop cycle on the pan and tilt axis:
from time import sleep import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) pan = 27 tilt = 17 GPIO.setup(tilt, GPIO.OUT) # white => TILT GPIO.setup(pan, GPIO.OUT) # gray ==> PAN def setServoAngle(servo, angle): assert angle >=50 and angle <= 130 pwm = GPIO.PWM(servo, 50) pwm.start(8) dutyCycle = angle / 18. + 2. pwm.ChangeDutyCycle(dutyCycle) sleep(0.3) pwm.stop() if __name__ == '__main__': for i in range (50, 140, 15): setServoAngle(pan, i) setServoAngle(tilt, i) for i in range (130, 50, -15): setServoAngle(pan, i) setServoAngle(tilt, i) setServoAngle(pan, 110) setServoAngle(tilt, 50) GPIO.cleanup()
Development Step #12 – Object Tracking with OpenCV and Raspberry Pi
First I will start with some examples using Object Tracking and OpenCV and after that I will proceed with live video and integrating the servo function.
o Learning Experience.
I already have installed the OpenCV library in a previous dev. step.
I will start by analyzing the code by Adrian posted on his page ( https://www.pyimagesearch.com/2015/09/14/ball-tracking-with-opencv/ )
I have to verify that the OpenCV library is installed correctly:
I want to test if OpenCV is working as before, by running that test python script:
import numpy as np import cv2 cap = cv2.VideoCapture(0) while(True): ret, frame = cap.read() frame = cv2.flip(frame, -1) # Flip camera vertically gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) cv2.imshow('frame', frame) cv2.imshow('gray', gray) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
To execute the OpenCV code I need to get into the OpenCV’s virtual environment from the terminal with the following commands:
source ~/.profile workon cv
Unfortunately I bumped into an error “workon cv is not valid”
I started backtracking the issue and reached the conclusion that there could be something wrong with the OpenCV version I am using 3.2.0 and maybe I have missed some packages during my installation, so I decided to look for them.
I headed to this installation guide : https://www.pyimagesearch.com/2017/09/04/raspbian-stretch-install-opencv-3-python-on-your-raspberry-pi/
I started installing all the mentioned libraries, but nothing fixed the issue. So after many tries I decided to completely reinstall the OpenCV library, which is a long process that I wanted to avoid.
Everything was going smooth until I started getting multiple error messages and the process stopped.
I tried again, but no effect:
This time I receive a “free space” error, even though that I use a 32GB sd card.
Going through the comments the only solution to the issue is to make a clean install of Raspbian and start installing OpenCV, VNC and all the other libraries that are needed.
Sadly because of the time scope of this project, I am unable to continue the development.