Arduino-Python 4-Axis Servo Control
April 8th, 2008
Although the Arduino platform is ideal for standalone applications, it really comes to life when interfaced with a PC. Connect Arduino to a personal computer and you instantly add a ton of versatility and processing power to your project.
This tutorial will describe how to use Arduino to control a bank of four independent RC servos with your PC (or Mac, or *nix Box), using a USB cable and a modular Arduino-Python software stack.
The following discussion builds upon concepts presented in two previous articles, “Arduino Serial Servo Control” and “Joystick Control of a Servo.” As always, comments, critiques, or suggestions for improving or adapting this code are welcome and appreciated.
Project Outline
The primary goal for this project was to create a software stack that allows simple and flexible control of multiple servos from any type of Python script.
The solution has two basic components: (1) an Arduino sketch that waits for serial input from a connected PC, then moves each servo to its commanded position, and; (2) a Python module on the PC that opens the serial connection and formats the data packets expected by the Arduino.
Any other Python program written to sit on top of these two layers need not worry about the messy details of serial communication, but rather can just say something like, “Move servo #2 to 90 degrees.” Or, more precisely:
servo.move(2,90)
Easy, right? Let’s get started.
Part I: Smoke, Mirrors, and Hand-Waving
If you just want to get things up and running quickly, start here. These instructions will get your servos connected and obeying every whim of your PC in no time.
Hardware Setup
Hardware for this project consists of an Arduino module, four JR Sport ST47 standard servos, and a breadboard to create the circuit.
The servos each have three wires: Ground (brown), Power (red) and Control (yellow). Each of the Control wires will connect to a different digital pin on the Arduino board (pins 2 through 5 in our setup), and all of the Power and Ground wires will need to connect somehow to the 5V and Gnd pins.
The simplest way to accomplish this is to create a “bus” bar along one of the breadboard’s edges, as shown in the photo above. Simply route the Arduino’s 5V and Gnd to a convenient area on the breadboard, and connect all the servos.
Required Software: The Lower Layers
To get the effects seen in the video above, you’ll need at least the following two programs. Although this code is designed to control four servos, it also works as-is with fewer servos, and — with a few modifications — as many as twelve.
Download the code:
MultipleServos.pde: This is the Arduino sketch. Copy and paste this code into your Arduino IDE software and upload it to the board.
servo.py: This is the Python module which talks directly to the Arduino sketch “MultipleServos.” This script requires the pyserial module, available from Sourceforge. Save this script on your PC wherever you like, just be sure to name it “servo.py”.
Customize the code:
Depending on your computer system and Arduino hardware setup, you may need to make a few modifications to the code.
Arduino: In the “MultipleServos” sketch, take note of the following three variables and make adjustments as necessary for your setup. See “Arduino Serial Servo Control” for more details regarding the minPulse and maxPulse variables.
int pinArray[4] = {2, 3, 4, 5}; // digital pins for the servos
int minPulse = 600; // minimum servo position
int maxPulse = 2400; // maximum servo position
Python: In the “servo.py” script, you’ll most likely need to change the value of the usbport variable, which tells Python how to find your Arduino (On Windows, it’ll be something like ‘COM5′. On a Mac, ‘/dev/tty.usbserial-xxxxx’. On Linux, ‘/dev/ttyUSB0′.). Try running ls /dev/tty* from a Mac or Linux terminal for a list of available ports. [ToDo: Modify the script to make this step unnecessary.]
Test the code:
Once your hardware is set up and the software is installed, you can test the system’s basic functionality from the Python interactive interpreter, like so:
~/path/to/servo.py$ python
>>> import servo
>>> servo.move(2,150)
The servo.move() method takes two arguments, both integers. The first is the servo number you wish to move, 1-4. The second is the commanded angular position of the servo horn, from 0-180 degrees. So, if you want to move Servo #3 fully clockwise (180 degrees), you’ll type servo.move(3,180). Cake, baby!
Optional Software: From Totally Geek to Totally Chic
The following scripts are designed to leverage the functionality of the servo.move() method for simple and readable code. Make sure these files reside in the same directory as “servo.py”.
- servodance.py: A cascading effect that feels like watching a quarter spiral down one of those funnel-shaped wishing wells.
- multijoystick.py: Allows joystick control of the servos, with each joystick axis controlling a single servo. This code could the basis for a Wi-Fi RC vehicle of some kind.
- servorandom.py: The final servo sequence seen in the video, with individual servos moving to random positions and then waving “goodbye” in unison.
With any luck, you should now have everything up and running just like in the video!
Part II: Getting Down to Brass Tacks
Next, let’s take a look under the hood to see how it all works. If you’re the type that just wants to get things working and damn the details, STOP HERE. Otherwise, continue on, and I’ll do my best to explain how the code “do what it do.”
The Problem Set
Asynchronous serial communication is not perfect. Sometimes there are errors, dropped packets, confusion. Sometimes the mail does not get through. In both of the previous two serial/servo projects, the Arduino expected only one byte from the PC, and in both cases that byte represented a commanded servo position — and nothing more. If a byte was missed or skipped, it wasn’t a big deal, another one was sure to come along, and it was impossible to misinterpret.
This project presents a couple of new challenges. First, we are controlling more than one servo, so the Arduino needs more than one command element for each move. As we’ve seen above, it needs to know (at least) which servo to move, and how much to move it. Secondly, we have the problem of communication. This time, we’re sending two command elements for each move (servo number & position), and these elements are clearly not interchangable. That is, if we want to send servo.move(4,90), we need to make sure that Arduino knows that the ‘4′ means “Servo #4″ and the ‘90′ means “90 degrees.”
Tom Igoe’s article, “Interpreting Serial Data,” contains an excellent discussion of some of the problems involved in serial communication, and lists several issues that need to be addressed in every project, namely:
- How many bytes am I sending? Am I receiving the same number?
- Did I get the bytes in the right order?
- Are my bytes part of a larger variable?
- Is my data getting garbled in transit?
The Arduino’s Serial.read() function reads one byte of data at a time from its serial buffer. Think of the serial buffer as a mailbox. It’s a small (128 bytes) area of memory where incoming serial messages are stashed until the Arduino is ready to read them. Every character we send from the PC to Arduino is one byte. So, while we could send the Arduino something very unambiguous like, “Yeah, hi, Arduino, it’s the Linux Box again. What’s happening? If you could go ahead and move Servo #4 to the 90-degree position, that would be great. Thaaanks,” (163 bytes) it’s obviously better if we can come up with something a little more terse.
However, as we’ve seen, if we just send over the characters ‘4′, ‘9′, and ‘0′ — remember, each character is a byte — the Arduino might get confused. This problem is amplified when more commands start stacking up in the buffer. Let’s say now we command servo.move(2,180) and servo.move(3,120). Now the buffer should hold {4,9,0,2,1,8,0,3,1,2,0}, except–OOPS!–one of the bytes got dropped along the way, so now it holds {4,9,0,2,1,8,3,1,2,0}. “Wait, which servo did you want me to move?” You can clearly see a problem developing.
Solution: Data Packets and Start Bytes
Luckily, part of the solution is handled in the way Arduino communicates. Arduino uses ASCII encoding to represent alphanumeric characters. Each character sent over the wire is converted to the binary equivalent of a decimal value from 0 to 255. [See this conversion chart for specifics.]
So, for example, if we send over the character ‘A’, Arduino recognizes this as its decimal value, ‘65′. We won’t get too deep into this concept except to say that the implementation is great for our application, because as long as the values we’re sending are less than 255, they’ll fit neatly into one byte. Since the largest value we send is 180, we only have to send two bytes per command.
Now, if you’ve looked at the ASCII conversion chart, you’ll recognize that doing this every time you want to send a command would be a real pain. Also, trying to teach Python this chart would take up a lot of unnecessary code. Thankfully, this problem is already solved for us with Python’s chr() function. Wrap any decimal value from 0-255 in chr(), and you get back its ASCII equivalent. A few examples:
~$ python
>>> chr(65)
'A'
>>> chr(110)
'n'
>>> chr(13)
'\r'
>>> chr(9)
'\t'
You get the idea, but notice that ASCII doesn’t just represent letters and numbers, but also symbols and other “control” or “non-printing” characters like line-feeds, returns, and tabs.
So, now if we want to send servo.move(4,90), we only need two bytes, the ASCII equivalents of ‘4′ and ‘90′, represented in Python as chr(4) and chr(90), and interpreted by the Arduino sketch as, once again, simply ‘4′ and ‘90′. Easy! [Seriously, if your brain explodes at this point, or you're bleeding from the ears, it's understandable. I don't like it any more than you do, but stick with me, it'll all work out neatly in the end.]
Packets, Headers, and Payloads
Okay, great, now instead of just digits in Arduino’s serial buffer, we have meaningful values. Part of the problem is solved, but we still haven’t addressed the issue of dropped or missing bytes. That is, how will the Arduino know that a ‘4′ is “Servo #4″ and not “4 degrees” when pulling values out of a crowded buffer like {4,90,2,180,3,0,1,110} ?
The answer is data packets. Very simply, instead of just sending a long string of numbers to Arduino, we’ll send a very specific ordered message, a packet of values, that is intended to be read and interpreted as a whole and in order, or else discarded completely.
Now, the structure of a packet can be as simple or as complex as we need it to be, as you might have noticed if you followed that last link. But all we really need is some means of ensuring that Arduino doesn’t confuse one value for another.
Essentially, our Python script needs to tell Arduino three things:
- Here comes a new servo command.
- Servo number to move.
- Commanded servo position.
We’ve already been sending the last two elements, the servo number and position. Here, we’re adding a third element, which we’ll call the header or the start byte. Our header, like the rest of the data in our packet, will be just one byte long, and contain no real information other than the conceptual message, “I am a header.”
The order of this message is important. Every packet sent over the wire, or read from the serial buffer, will now have the following format:
(Header, Servo Number, Servo Position)
or, more tersely:
(startbyte, servo, angle)
What to use as a startbyte? Well, we’re only using the values from 0-180 as either our Servo Number or Servo Postion. Any value from 181 to 255 would be unique. We’ll use ‘255′ just to make it obvious. So, every packet will now look something like one of the following:
(255, 1, 90)
(255, 2, 180)
(255, 3, 0)
And the Arduino’s serial buffer would look something like:
{255,1,90,255,2,180,255,3,0}
Now, instead of reading byte after byte and hoping for the best, Arduino will wait until a minimum of three bytes arrive in the buffer, and then read the first byte to determine whether or not it is, in fact, a header (255). If it’s not, Arduino skips that value, and moves on to the next byte without touching the servos. When it finally sees a header, Arduino continues reading the next two bytes, in order, and assigning them to the Servo Number and the Servo Position, respectively. If either of those two values is ‘255′, Arduino assumes something is wrong, and skips everything until it reads a new header.
Side Note: Authoritarian Flow Control
“Now just a minute!” you’re saying. “If that is the case, then some of the commands Python sends to the Arduino will be totally ignored!” And you’re right. This method of serial flow control is definitely one-way, with no error-checking. Other methods, such as “call-and-response” or “handshaking” are much better at ensuring accuracy, since there’s a back-and-forth arrangement that can call for data to be re-sent in the event of dropped packets. But the two-way protocol this method requires is much slower.
We have to make an engineering decision. In our application, which is more important, accuracy or quick response? It depends on exactly how you are using the servos, but if you consider say, a joystick-controlled robot or RC vehicle application, then clearly an immediate response and quick visual feedback is preferable to perfect accuracy. If you command “turn right” with a joystick, and your vehicle doesn’t respond appropriately, you’ll just instinctively add more right stick input.
Perfect accuracy is not required.
Writing the Code
Very briefly, let’s look at how the above concepts are implemented in both the Python and Arduino software.
Python Implementation
Whenever we call the servo.move() method, the Python script servo.py handles the serial communication details using the pyserial module, and formats the arguments into the data packet outlined above. The bare-bones version looks like this:
#!/usr/bin/env python
import serial
usbport = '/dev/ttyUSB0'
ser = serial.Serial(usbport, 9600, timeout=1)
def move(servo, angle):
if (0 <= angle <= 180):
ser.write(chr(255))
ser.write(chr(servo))
ser.write(chr(angle))
else:
pass
Arduino Implementation
Arduino opens its own serial connection, waits for at least three bytes to fill the buffer, then starts reading:
/** MultipleServos.pde (bare bones) **/
void setup() {
// open serial connection
Serial.begin(9600);
}
void loop() {
// wait for serial input (min 3 bytes in buffer)
if (Serial.available() > 2) {
//read the first byte
startbyte = Serial.read();
// if it's really the startbyte (255)
if (startbyte == 255) {
// then get the next two bytes
for (i=0;i<2;i++) {
userInput[i] = Serial.read();
}
// first byte = servo to move?
servo = userInput[0];
// second byte = which position?
servoPosition = userInput[1];
// packet check
if (servoPosition == 255) { servo = 255; }
If Arduino gets a complete packet with header, servo, and angle values, it calculates the correct pulseWidth for the commanded servo angle, and assigns that value to the appropriate servo. If the value of servo is not between 1 and 4, the loop exits without assigning any new values.
// compute pulseWidth from servoPosition
pulseWidth = minPulse + (servoPosition * (pulseRange/180));
// stop servo pulse at min and max
if (pulseWidth > maxPulse) { pulseWidth = maxPulse; }
if (pulseWidth < minPulse) { pulseWidth = minPulse; }
// assign new pulsewidth to appropriate servo
switch (servo) {
case 1:
servo1[1] = pulseWidth;
break;
case 2:
servo2[1] = pulseWidth;
break;
case 3:
servo3[1] = pulseWidth;
break;
case 4:
servo4[1] = pulseWidth;
break;
}
}
}
Finally, once each loop, whether it receives any serial data or not, Arduino checks to see if the servos need a pulse. To hold their current positions, RC servos expect a pulse at 50Hz (every 20ms), so Arduino keeps track of the lastPulse. If the timer is up, all servos get a pulse.
// pulse each servo
if (millis() - lastPulse >= refreshTime) {
pulse(servo1[0], servo1[1]);
pulse(servo2[0], servo2[1]);
pulse(servo3[0], servo3[1]);
pulse(servo4[0], servo4[1]);
// save the time of the last pulse
lastPulse = millis();
}
}
// create the pulse
void pulse(int pin, int puls) {
digitalWrite(pin, HIGH); // start the pulse
delayMicroseconds(puls); // pulse width
digitalWrite(pin, LOW); // stop the pulse
}
Whew! We’re Done.
Well, if you’ve made it this far, congratulations: you’re totally insane. I hope the above dissertation helps at least one person better grasp these concepts, since it took me across many web pages and into several late nights to find the answers. Good luck!
References
- Tom Igoe, Making Things Talk: Practical Methods for Connecting Physical Objects
- Tom Igoe, “Serial Communication“
- Tom Igoe, “Interpreting Serial Data“
- Society of Robots, “Actuators and Servos“
- ITP Physical Computing, “Servo Lab“
- ITP Physical Computing, “Serial Lab“

11 comments so far Add your own
1. Allen Riddell | April 27th, 2008 at 12:07 pm
This is really excellent. Thank you!
p.s. any tips on how to extend servo wires? I’m finding that the wire is really thin and frays easily. Is there a name for that black terminal? Or does one need some sort of wire crimp?
2. Brian | April 27th, 2008 at 7:47 pm
Hey Allen, thanks for the props! The best solution I’ve found so far is the servo wire extensions you get at your local hobby store. They come in several different lengths, although it looks like there might be a practical limit of a few feet.
For longer wire runs or applications other than radio control, I’d probably chop the connectors off a couple of the short extensions and splice them to something beefier. There could be signal degradation and/or interference with the pulses over a longer run; you’ll probably have to experiment a bit with setup and voltages if you’re going beyond three feet or so.
3. John | May 12th, 2008 at 5:41 am
Gidday , this looks great. I am into wanting to make a motion platform for a flight simulator.am looking at using 12 v motors as servo’s. will this system be able to be beefed up to run these , or will do the job as is. My ? may sound silly ,But Be patient with me as I’m new to this .
4. Brian | May 12th, 2008 at 9:09 am
Hey John, you could certainly use Arduino and a PC to control your project, but the hardware setup would be substantially different, primarily due to your need to isolate the 12V devices from the Arduino’s 5V circuit. You code would probably need to be different as well, although the modular theory presented here could still apply. Try posting your question over on ladyada’s forum. You’re sure to get a wealth of helpful responses over there.
5. Keith Chester | May 12th, 2008 at 10:39 am
I’ve had to do a similar program that I will be posting about soon on my project blog. When you need to make sure information goes through but don’t want to try and synchronize the serial port connection, send data both ways through arrays. Certain spots in the arrays are reserved for data for a certain spot. If you continually resend the array, then it will catch dropped bits if you reserve the first and last spots for a certain value. If the array does not begin or end with the right values, it ignores those values. If it does, it just takes the values of the arrays and applies them to your outputs. This works for inputs as well.
Pingbacks & Trackbacks
6. Tutorial on Arduino-Python Servo Control at Windows on Last Miles
7. Arduino-Python 4-Axis Servo Control | The Kevin Pipe
8. Arduino-Python 4-Axis Servo Control » Developages - Development and Techno ...
9. antonolsen.com » Blog Archive » links for 2008-05-12
10. Electronics-Lab.com Blog » Blog Archive » Arduino-Python 4-Axis Serv ...
11. Link Right 2 » Blog Archive » links for 2008-05-13
Add a comment
Trackback Link | Comments RSS