Joystick Control of a Servo

UPDATE: Please read Arduino-Python 4-Axis Servo Control for the most current and
detailed information on using a USB joystick to control one or more RC servos.

[blip.tv http://blip.tv/play/wkeilE8A width=”648″ height=”380″]

Inspired by Armadillo Aerospace and their laptop-controlled Pixel rocket, I decided to figure out how to use an Arduino module to achieve wireless remote control of a vehicle.

Along the path to development, an achievable intermediate goal would be something like a wireless RC rover with a video camera, monitored and controlled with a laptop and joystick on a WiFi network.

Step one in the process is simple joystick control of a servo over a USB connection. This project builds upon the process documented in “Arduino Serial Servo Control.” I welcome any comments or suggestions for improving or adapting this code.

Hardware

The hardware setup is very simple, and is described in detail in the serial-servo article. The JR Sport ST47 standard servo is wired directly to Arduino’s 5V power and ground, and the servo’s control wire is connected to Digital pin #2. The Arduino module is connected to a PC (running Linux in our case) with a USB cable, and a standard USB joystick is also connected.

Software

The simple two-layer software stack includes a Python script for interpreting inputs from the joystick, and the Arduino sketch to await serial inputs from the Python script and pulse the servo.

Let’s start with the Python script. This code lives on the PC, and requires the PySerial and PyGame modules to be installed, along with (obviously) Python’s standard library. The pygame module is primarily designed for graphics game creation, but it has a set of very straightforward methods for interpreting joysticks and other non-standard input devices. The pyserial module simply allows us to open a serial connection to the Arduino over a USB port.

The primary purpose of the Python script is to read and report on joystick inputs. A gaming joystick may have six different axes and a multitude of buttons. This script will sense inputs from each axis, and (if enabled) print the values of the stick positions.

Each joystick axis reports a range of decimal values between -1.0 and 1.0, with zero being the center position. This script converts those values (from the X-axis) into round numbers between zero and 180 and assigns that value as a servo position. Since the servo can travel through 180 degrees, each servo position value increments the servo horn by one degree, with the center at 90 degrees. The integers 0-180 are then converted to ASCII characters and sent over the serial connection to the Arduino.

#!/usr/bin/env python
#
# joystick-servo.py
#
# created 19 December 2007
# copyleft 2007 Brian D. Wendt
# http://principialabs.com/
#
# code adapted from:
# http://svn.lee.org/swarm/trunk/mothernode/python/multijoy.py
#
# NOTE: This script requires the following Python modules:
#  pyserial - http://pyserial.sourceforge.net/
#  pygame   - http://www.pygame.org/
# Win32 users may also need:
#  pywin32  - http://sourceforge.net/projects/pywin32/
#

import serial
import pygame

# allow multiple joysticks
joy = []

# Arduino USB port address (try "COM5" on Win32)
usbport = "/dev/ttyUSB0"

# define usb serial connection to Arduino
ser = serial.Serial(usbport, 9600)

# handle joystick event
def handleJoyEvent(e):
    if e.type == pygame.JOYAXISMOTION:
        axis = "unknown"
        if (e.dict['axis'] == 0):
            axis = "X"

        if (e.dict['axis'] == 1):
            axis = "Y"

        if (e.dict['axis'] == 2):
            axis = "Throttle"

        if (e.dict['axis'] == 3):
            axis = "Z"

        if (axis != "unknown"):
            str = "Axis: %s; Value: %f" % (axis, e.dict['value'])
            # uncomment to debug
            #output(str, e.dict['joy'])

            # Arduino joystick-servo hack
            if (axis == "X"):
                pos = e.dict['value']
                # convert joystick position to servo increment, 0-180
                move = round(pos * 90, 0)
                if (move < 0):
                    servo = int(90 - abs(move))
                else:
                    servo = int(move + 90)
                # convert position to ASCII character
                servoPosition = chr(servo)
                # and send to Arduino over serial connection
                ser.write(servoPosition)
                # uncomment to debug
                #print servo, servoPosition

    elif e.type == pygame.JOYBUTTONDOWN:
        str = "Button: %d" % (e.dict['button'])
        # uncomment to debug
        #output(str, e.dict['joy'])
        # Button 0 (trigger) to quit
        if (e.dict['button'] == 0):
            print "Bye!n"
            ser.close()
            quit()
    else:
        pass

# print the joystick position
def output(line, stick):
    print "Joystick: %d; %s" % (stick, line)

# wait for joystick input
def joystickControl():
    while True:
        e = pygame.event.wait()
        if (e.type == pygame.JOYAXISMOTION or e.type == pygame.JOYBUTTONDOWN):
            handleJoyEvent(e)

# main method
def main():
    # initialize pygame
    pygame.joystick.init()
    pygame.display.init()
    if not pygame.joystick.get_count():
        print "nPlease connect a joystick and run again.n"
        quit()
    print "n%d joystick(s) detected." % pygame.joystick.get_count()
    for i in range(pygame.joystick.get_count()):
        myjoy = pygame.joystick.Joystick(i)
        myjoy.init()
        joy.append(myjoy)
        print "Joystick %d: " % (i) + joy[i].get_name()
    print "Depress trigger (button 0) to quit.n"

    # run joystick listener loop
    joystickControl()

# allow use as a module or standalone script
if __name__ == "__main__":
    main()

Now for the Arduino sketch. This code merely waits for serial input from the PC, does a little math on the decimal values of the transmitted ASCII characters, then pulses the servo to the corresponding position. If no new serial input is received from the PC (i.e., the joystick is not moved), then the Arduino will maintain the last known position of the servo horn with an identical pulsewidth every 20ms. As always, the minPulse and maxPulse values should be tweaked to work with your particular servo model.

/*
 * JoystickSerialServo
 * --------------
 * Servo control with a PC and Joystick
 *
 * Created 19 December 2007
 * copyleft 2007 Brian D. Wendt
 * http://principialabs.com/
 *
 * Adapted from code by Tom Igoe
 * http://itp.nyu.edu/physcomp/Labs/Servo
 */

/** Adjust these values for your servo and setup, if necessary **/
int servoPin     =  2;    // control pin for servo motor
int minPulse     =  600;  // minimum servo position
int maxPulse     =  2400; // maximum servo position
int refreshTime  =  20;   // time (ms) between pulses (50Hz)

/** The Arduino will calculate these values for you **/
int centerServo;         // center servo position
int pulseWidth;          // servo pulse width
int servoPosition;       // commanded servo position, 0-180 degrees
int pulseRange;          // max pulse - min pulse
long lastPulse   = 0;    // recorded time (ms) of the last pulse


void setup() {
  pinMode(servoPin, OUTPUT);  // Set servo pin as an output pin
  pulseRange  = maxPulse - minPulse;
  centerServo = maxPulse - ((pulseRange)/2);
  pulseWidth  = centerServo;   // Give the servo a starting point (or it floats)
  Serial.begin(9600);
}

void loop() {
  // wait for serial input
  if (Serial.available() > 0) {
    // read the incoming byte:
    servoPosition = Serial.read();

    // compute pulseWidth from servoPosition
    pulseWidth = minPulse + (servoPosition * (pulseRange/180));

    // stop servo pulse at min and max
    if (pulseWidth > maxPulse) { pulseWidth = maxPulse; }
    if (pulseWidth = refreshTime) {
    digitalWrite(servoPin, HIGH);   // start the pulse
    delayMicroseconds(pulseWidth);  // pulse width
    digitalWrite(servoPin, LOW);    // stop the pulse
    lastPulse = millis();           // save the time of the last pulse
  }
}

This may not be the most elegant solution to the joystick servo control problem, but it does serve as a nice “Hello Servo” project, to let you see what’s going on at the lower levels of input and output.

The next step in the process is to remove the USB cable to the Arduino, and control the servo by sending serial data over a WiFi or ZigBee network. Also, for a real remote robotics project, the code will need to be modified so that each joystick axis controls a separate servo, e.g. pitch, roll, yaw, and throttle. Stay tuned!

Advertisements

17 responses to “Joystick Control of a Servo

  1. Great project! I’m about to start developping with Arduino, to drive motors, so thank’s for all these usefull tips.

    May I ask you what joystick you are using, which seems to work fine under linux?

    Frédéric

  2. Thanks for posting the code example. It helped me to debug some joystick testing I was doing. Now I have some basic potentiometers acting as a joystick – hehe

  3. Can you set it up to control 2 different servos from the single flight stick?
    ie one for left right and one for up down?

  4. @Jason: Sorry, no. I’m sure they’re out there, I just don’t have much experience with different types. Mine are definitely loud!

  5. Good work. I tried to replicate the work but found a weird problem on the py script using pyserial. The communication works good under python shell, but failed in python script. For example, the python code did not print “hello”. I tried this code on different Windows PCs.
    Did you observe similiar problems? Thanks.

    Python code

    import serial
    ss = serial.Serial(port='COM7',baudrate=9600,parity=serial.PARITYNONE,stopbits=serial.STOPBITSONE,bytesize=serial.EIGHTBITS)
    ss.write('A')
    ss.flush()
    print ss.read(ss.inWaiting())
    

    Arduino code

    void setup()
    {
      Serial.begin(9600);
    }
    void loop()
    {
      int b = 0;
      if(Serial.available())
      {
          b = Serial.read();
          Serial.println("HELLO");
      }
    }
    
  6. hey…
    I would be very thankful to you if you could give the full code for the joystick control of a single servo.The reason why i am asking is that ….its a bit difficult for newbies like me to interpret the code and play with it(The one which is given above doesnt explain which one to be run in python and which one to be loaded onto the freeduino board )…I would be thankful to you if you could do that as soon as possible [:)]

  7. Hello, great project.

    Please, do you know some method to link an USB phone connection directly to a servo ?

  8. Great project,
    I really need joystick which controls servo! It is clear with arduino interface. Could you please describe in short steps what to do with pigame, piserial, python’s standard library? Sorry guys I am from physics, I never did programming, it is difficult to understand how to run both programs (python, arduino) simultaneously in windows-xp for a joystick servo control? As I understand when we start python COM must be for joystick and arduino has different COM. Please give me all steps, we install, start command, type such command… for windows
    I am most grateful to you.

  9. hey there. nice guide.

    Im working on the same thing as you, but wanted to try an xbox or psp controller. do anyone have a nice tutorial how to read the signal from the usb cabel, through my pc and WiFi to the Rover/arduino?

    im stuck with alot of ideas but dont have the skills in this field to make em come to life =)

  10. I see what is missing, some store which would supply a working joystick with arduiono servo connection. I would pay a double price.

  11. Hey everyone, thanks for stopping by! The joystick/servo problem is easily solvable and most folks have been able to get it working — it just takes a USB joystick, an Arduino (or clone), and the Python scripting language (okay, and a bit of patience, too!). Python is installed by default on MacOS X and Linux, and is easily installed on Windows.

    The most up-to-date information on controlling servos with a USB joystick can be found by reading Arduino-Python 4-Axis Servo Control. The article is quite detailed, and most of the common problems have been identified and solved in the comments section. Thanks!

Comments are closed.