When making a pan/tilt mount *link & article pending* for my PiRover robot, I ran into some issues with badly jittering servos. Following some suggestions on the excellent YouTube video from Gary Explains, did not solve the issue. Neither did using a PCA9685 servo control board or pulling the 5v power from and external source. Looking through various forums, bad servo motors could be the cause. I had used a cheap TowerPro SG90 servo, but did have some ‘lofty ambition’ servos in my general box of bits. To investigate, I bought two other types that received good reviews on Amazon, a Longrunner KY66 and a Tiankongrc MG90s all metal gear servo.
The cause could also be software or related to something else on the Pi 3B I was using. To investigate, I used a Pi Zero W 2 (which had just arrived and I needed to find a use for) and a Raspberry Pi Pico. For the Pi Zero, I tried both direct PWN control and using the PCA9685. Even if the results prove inconclusive, this should prove a useful exercise into servo control under each device.
It should be noted, regarding my original problem, at the time I was also getting a number of Under-voltage detected issues, which is one reason I went for the PCA9685 and external power source. Not all the events related to moments of jitter.
Using a Raspberry Pi (Zero W 2)
Before we begin, make sure python 3 is the default python version with
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 1 followed by
The servos have three wires, red, brown and orange. Connect red to the Pi 5v output, brown to Ground and the orange to GPIO 14 (pin 06, next to Ground).
The following program should turn the servo through a range of angles:
#!/usr/bin/python # Servo test. Use gpiozero library to move a sg90 servo # Usage: servoTest.py [-f] [-p n] [-e] # -f : Force pin factory, ensure you sudo gpiod # -p n : Use pin n, rather than default # -e : Use extended pulse range from gpiozero import Servo from time import sleep import sys pin=14 # Default pin fact=False # Default is not to use a GPIO factory extpulse=False # Use standard pulse width or extended # Parse gpio arguments sys.argv.reverse() sys.argv.pop() # Dump first element while(sys.argv!=): elm=sys.argv.pop() if(elm=="-f"): fact=True print("Using factory") if(elm=="-p"): pin=sys.argv.pop() print("New pin", pin) if(elm=="-e"): extpulse=True print("Using extended pulse") # Check for -f argument if(len(sys.argv)>1): if(sys.argv=="-f"): fact=True if(fact==False): print("\n ** To call with a GPIO factory, use '-f'\n") if(extpulse==True): servo = Servo(pin, min_pulse_width=0.5/1000, max_pulse_width=2.5/1000) else: servo = Servo(pin) else: # See video at https://www.youtube.com/watch?v=_fdwE4EznYo # sudo apt install python3-pigpio # sudo pigpiod from gpiozero.pins.pigpio import PiGPIOFactory factory=PiGPIOFactory() if(extpulse==True): servo = Servo(pin, min_pulse_width=0.5/1000, max_pulse_width=2.5/1000, pin_factory=factory) else: servo = Servo(pin, pin_factory=factory) servo.mid() print("servo mid") sleep(3) print("Running through min to max") for x in range(2): print(x) servo.min() print("servo min") sleep(3) servo.mid() print("servo mid") sleep(3) servo.max() print("servo max") sleep(3) print("Setting particular values") for x in [-0.33,1,-1,0,-0.5,0.25,-1,0]: print("x=",x) servo.value=x sleep(1.5) print("Smooth pan") s=0.1 # Change in angle per cycle d=0.2 # Delay between steps pos=-1 while pos < 1: print("angle =",pos) servo.value=pos sleep(d) pos+=s servo.mid() print("Done")
Using the Tower Pro and calling the above script with no arguments (unless you need to change the default GPIO pin), the servo is jittery and only rotates about 90°. Calling with the
-e option does give the full 180° range of movement, but it is really jittery.
To use the GPIO pin factory:
$ sudo apt install python3-pigpio $ sudo pigpiod $ ./servoTest.py -e -f
Unfortunately this was no better, but calling
dmesg showed there were no under-voltage errors.
The lofty ambition servo was far worse initially, with no stability at all and limited range. However with both the extended pwm range and PIN factory usage, the output was pretty stable. However the gears made a grating sound in places that suggested this servo may not last long.
The Longrunner also was very poor without using the pin factory, but like the lofty ambition, using the
-e -f option give very good results, without the nasty grating noises. This is definitely the best so far.
Like the other two, this also needed the extended pulse width range and to use the pin factory, but it was very stable once both were used. With some stable output, the slow pan can be refined. Using a change angle (s) of 0.005 and a delay (d) of 0.1, gives quite a smooth output.
Using the PCA9685
The PCA9685 offloads the PWM from software on the Pi to a dedicated board and can support up to 16 servos (depending on model). There are some good instructions and wiring diagrams to follow at Adafruit. Vcc and GND connect to 3.3v and GND on the Pi, with V++ going to a 5v pin. This is used to power the servos. The board is I2C, so SDA and SCL go to the corresponding pins on the Pi (GPIO2 and GPIO3). Note these two wires cross over.
I have tested using the following code:
#!/usr/bin/python # Servo test using PCA9685 16 channel servo driver # Servos connected to 0 and 1 # Built following Adafruit guide: https://learn.adafruit.com/adafruit-16-channel-servo-driver-with-raspberry-pi # sudo pip3 install adafruit-circuitpython-servokit from adafruit_servokit import ServoKit import time kit = ServoKit(channels=16) servoList= angleList=[0,45,90,110,160,180,90,0] for s in servoList: print("Servo ", s) kit.servo[s].set_pulse_width_range(500,2500) print("Testing extreme angles first....") print("Middle") kit.servo[s].angle=90 time.sleep(2) print("Min") kit.servo[s].angle=0 time.sleep(2) print("Max") kit.servo[s].angle=180 time.sleep(2) for a in angleList: print(" angle =", a) kit.servo[s].angle=a time.sleep(2) # Slow pan print("Slow pan") st=0.5 # Angle step to change d=0.05 # Delay between steps pos=0 # Initial angle while(pos<180): kit.servo[s].angle=pos pos+=st time.sleep(d) kit.servo[s].angle=90
All servos performed well with the expansion board, even the Tower Pro which had been unreliable under my Pi 3B. As you can plug multiple servos in together, I plugged in all 4 and extended servoList in the above code to show one after the other.
Conclusion so far
The Longrunner and MG90S are much more reliable servos than the Tower Pro and the lofty ambition. The latter two perform considerably better with the use of a dedicated PWM controller.
Using a Raspberry Pi Pico
Using a Raspberry Pi Pico and connecting the servo directly, Vcc (red) on the servo goes to pin 40 (VBUS), GND (Black) to any of the GND pins (I used 38) and the data (orange) to any GPIO pin. I picked GPIO0, pin 1.
The following code will put the servo through a number of tests:
from machine import Pin, PWM from time import sleep pwm = PWM(Pin(0)) pwm.freq(50) def setServoCycle (pos): pwm.duty_u16(pos) sleep(0.01) def setServoAngle (a): d = int(a/180*8000+1000) print("Angle=",a,"d=",d) setServoCycle(d) for x in range (1,2): for pos in range(0,180,2): setServoAngle(pos) for pos in range(180,0,-2): setServoAngle(pos) Angles = [56, 157 ,4 ,89 ,39, 170, 2, 123] for a in Angles: setServoAngle(a) sleep(2) for pos in range(0,180,2): setServoAngle(pos) print("Done")
At this point I found all servos behaved pretty well, though again my ‘lofty ambtions’ servo made odd noises, it could be this is a slightly duff one.
The Raspberry Pi Pico has 16 PWM channels in hardware and copes with servos far better than the standard Pi. Because of the good performance connecting servos directly, I didn’t use the PCA9685 with it.
There are a range of 9g motors available which differ in quality. A traditional Raspberry Pi drives PWM in software. A combination of a poor quality servo and a Pi can result in a poor and jittery performance. This can be improved by using a servo break out board such as a PCA9685. In addition, the power drain from a servo can result in power problems within the Pi, especially if powering from a battery which can not supply sufficient current. Power the servos from an external source where possible.
Using the GPIO library and pin factory can improve performance without the need for an additional board. If you are only driving one or two servos, this may be an easier option. However if you are doing a lot of other things with your Pi or have other hardware attached, servo performance can suffer.
Servos with a good review from Amazon (or other sales/review) site, proved more reliable than pot-luck from eBay, and did not have a huge difference in cost.
By comparison, the Pi Pico is a very good board for handling PWM. Like the PCA9685, it can support 16 PWM channels and even with the poorer quality servos, it can give good performance. The prices of the two boards are similar (or the PCA9685 can be slightly more expensive).
When building robotics projects requiring PWM, with a Raspberry Pi, an additional board is required for increased performance. The PCA9685 is quick to get up and running. With libraries already available it is a ready to go solution. However another serious option is linking a Raspberry Pi Pico. While programming work is required on the Pico side, sensor and servo options can be offloaded and more intelligence/decision making can be added to the Pico, which is not possible using the PCA9685. This can leave the Pi free for other operations. I have not compared power consumption, which may be a consideration on battery powered projects.