Version 6 of OWI535 robot arm control

Updated 2014-06-03 20:00:06 by kurtseel

I have used the wiki to great effect for many years so I thought I would finally throw something up here. This started as a project for my son's robotics after school activity. The OWI535 robotic arm is a very low cost and easy to assemble robot arm with 4 degrees os freedom and a gripper. It comes stock with a simple mechanical control box but there is a USB add on available which I also bought. The protocol work on 3 byte commands.

http://www.owirobots.com/store/catalog/robotic-arm-and-accessories/owi-535-robotic-arm-edge-kit-110.html

I took an OWI utility from a Raspberry utility and wrote a small SWIG interface for it. The link gives an excellent description of the protocol.

http://notbrainsurgery.livejournal.com/38622.html

Directory structure:

*Code_dir **package **scripts **tcl

*Makefile:

CC=cc
CFLAGS= \
        -fPIC \
        -g \
        -DFREEBSD

INCLUDES=-I/usr/include \
        -I/usr/local/include \
        -I/usr/local/include/tcl8.6

LDFLAGS=\
        -L/usr/local/lib \
        -lusb -ltcl86

LIBS=\
        -L/usr/local/lib \
        -lusb -ltcl86

SHFLAGS+=\
        -shared

all: tcl/libowi.so

clean:
        -rm rasp_util
        -rm owi_lib.o
        -rm tcl/libowi.so
        -rm tcl/owi_wrap.o
        -rm tcl/owi_wrap.c

rasp_util.o: rasp_util.c
        ${CC} ${CFLAGS} -c rasp_util.c -o rasp_util.o
        
rasp_util: rasp_util.o
        ${CC} ${LDFLAGS} rasp_util.o -o rasp_util
        
owi_lib.o: owi_lib.c
        ${CC} ${CFLAGS} -c owi_lib.c -o owi_lib.o 

tcl/libowi.so: tcl/owi_wrap.o owi_lib.o
        ${CC} ${SHFLAGS} ${LIBS} tcl/owi_wrap.o owi_lib.o -o tcl/libowi.so
        
tcl/owi_wrap.o: tcl/owi_wrap.c
        ${CC} ${INCLUDES} ${CFLAGS} -c tcl/owi_wrap.c -o tcl/owi_wrap.o

tcl/owi_wrap.c: owi.i
        ${SWIG} ${SWFLAGS} -tcl -itcl -namespace -outdir ./tcl/ -o ./tcl/owi_wrap.c  owi.i 

package: /dev/null
        cp tcl/libowi.so package/libowi.so
        cp scripts/owi.tcl package/owi.tcl
        
testload: package
        ./scripts/testload.tcl
        
testtime: package
        ./scripts/testtime.tcl

*owi.i

%module owi
%{
int owi_send(int, char **);        
%}
// ************************************
// SWIG voodoo
%typemap(in) char ** {
     Tcl_Obj **listobjv;
     int       nitems;
     int       i;
     if (Tcl_ListObjGetElements(interp, $input, &nitems, &listobjv) == TCL_ERROR) {
        return TCL_ERROR;
     }
     $1 = (char **) malloc((nitems+1)*sizeof(char *));
     for (i = 0; i < nitems; i++) {
        $1[i] = Tcl_GetStringFromObj(listobjv[i],0);
     }
     $1[i] = 0;
}

// more minor voodoo
%typemap(freearg) char ** {
     if ($1) {
        free($1);
     }
}
// Now, to get in the way ;)
%inline 
%{
int wrap_owi_send(char **argv) 
{
        int i = 0;
        while (argv[i]) 
        {
//                printf("argv[%d] = %s\n", i,argv[i]);
                i++;
        }
//        printf("count:%i\n", i);
        return owi_send(i, argv);
}
%}

*owi_lib.c

/************************************************************************
 * Raspberry Pi: USB Robotic Arm Controller               Version: 1.00 *
 * Project     : Operate the USB Robotic Arm using Linux CLI            *
 * Tested on   : Raspberry Pi, Model B                                  *
 * Exp. Level  : Beginner/Elementary                                    *
 ************************************************************************
 * How to get the libusb development driver:                            *
 * sudo apt-get install -y libusb-1.0-0-dev                             *
 *----------------------------------------------------------------------*
 * How to compile:                                                      *
 * gcc robotarm.c -Os -I/usr/include/libusb-1.0 -lusb-1.0 -o robotarm   *
 ************************************************************************
 * Operating System: Raspbian "Wheezy" Linux Kernel 3.6.11+             *
 * Created: May 28, 2013                  Latest Revision: May 28, 2013 *
 ************************************************************************
 * Original Credit goes to notbrainsurgery at LiveJournal               *
 * Reference: http://notbrainsurgery.livejournal.com/38622.html         *
 ************************************************************************
 *         MirandaSoft! - Pasig City, Metro Manila, Philippines         *
 *         http://www.element14.com/community/blogs/mirandasoft         *
 ************************************************************************/
 
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <libusb.h>
 
#define EP_INTR  (1 | LIBUSB_ENDPOINT_IN)
 
#define ARM_VENDOR       0x1267
#define ARM_PRODUCT      0
#define CMD_DATALEN      3
 
libusb_device * find_arm(libusb_device **devs)
{
  libusb_device *dev;
     int i = 0;
 
     while ((dev = devs[i++]) != NULL) {
             struct libusb_device_descriptor desc;
           int r = libusb_get_device_descriptor(dev, &desc);
               if (r < 0) {
//                    fprintf(stderr, "failed to get device descriptor");
                     return NULL;
            }
               if(desc.idVendor == ARM_VENDOR &&
                  desc.idProduct == ARM_PRODUCT)
                 {
                 return dev;
           }
     }
       return NULL;
}

// static unsigned char cmd[3];

int owi_send(int ac, char **av)
{
    if(ac!=3)
    {
    char buffer[80];
        sprintf(buffer, "USB Robotic Arm Control Software\nUsage: %s CMD0 CMD1 CMD2\n\n", av[0]);
//        fprintf(stderr, buffer);
        return 1;
    }
 
    unsigned char cmd[3];
 
    cmd[0]=(unsigned char)strtol(av[0],NULL,16);
    cmd[1]=(unsigned char)strtol(av[1],NULL,16);
    cmd[2]=(unsigned char)strtol(av[2],NULL,16);
    
  libusb_device **devs;
    libusb_device *dev;
    struct libusb_device_handle *devh = NULL;
       int r;
  ssize_t cnt;
 
   r = libusb_init(NULL);
  if (r < 0)
    {
//     fprintf(stderr, "failed to initialize libusb\n");
       return r;
    }
 
    libusb_set_debug(NULL,2);
    
    cnt = libusb_get_device_list(NULL, &devs);
      if (cnt < 0)
            return (int) cnt;
    dev=find_arm(devs);
    if(!dev)
    {
//           fprintf(stderr, "Robot Arm not found\n");
       return -1;
    }
 
    r = libusb_open(dev,&devh);
    if(r!=0)
    {
//        fprintf(stderr, "Error opening device\n");
//              libusb_free_device_list(devs, 1);
//             libusb_exit(NULL);
        return -1;
    }
 
 
//    fprintf(stderr, "Sending %02X %02X %02X\n",
//            (int)cmd[0],
//            (int)cmd[1],
//            (int)cmd[2]
//    );
 
    int actual_length=-1;
    
    r = libusb_control_transfer(devh,
                                0x40,                //uint8_t        bmRequestType,
                                6,                //uint8_t        bRequest,
                                0x100,                //uint16_t        wValue,
                                0,                //uint16_t        wIndex,
                                cmd,
                                CMD_DATALEN,
                                0         
    );
    
//    if(!(r == 0 && actual_length >= CMD_DATALEN))
//    {
//        fprintf(stderr, "Write err %d. len=%d\n",r,actual_length);
//    }
    
    libusb_close(devh);
     libusb_free_device_list(devs, 1);
       libusb_exit(NULL);
 
//    fprintf(stderr, "Done\n");
       return 0;
}

A script to test loading the extension

*scripts/testload.tcl:

package require tclreadline

lappend auto_path [file join [pwd] package]
# catch { package require owi }
package require owi

proc arm_exit { } \
{
        exit
}

        set ARM [::owi::arm new]

        ::tclreadline::Loop

I saw someone posted an Arduino like "Map()" proc on the wiki which got me thinking about the pieces I have found useful from the Arduino IDE so I made a "Metro()" like knock-off. The OWI arm is not a closed loop system and I was testing from the command line so something like thsi was nessesary. My target user interface is my Android tablet, using TouchOSC.

http://hexler.net/software/touchosc

There are 2 TCL OSC packages I know of. One is maintained by Dave Joubert who was kind enough to assist me with the library.

http://code.google.com/p/tcl-osc-library/

The other is from v2 labs' project.

https://trac.v2.nl/browser/OSC/tclosc?rev=1&order=name

Although I used the v2 labs package with this project, Dave's library and work are far more sophisticated, e.g. timed events, routing between recievers, 3D interfaces, etc. I have other projects that will require some of that stuff "on my bench".

*owi.tcl

package require cmdline
package require TclOO

package provide owi 0.0

puts [info script]
load [file join [file dirname [info script]] libowi.so]

# ------------------------------------------------------------------------------
# Like the Arduino Metro::Metro
#
oo::class create ::owi::metro \
{
        # -----------------------------
        #
        #
        constructor { {INT 1000} } \
        {
        my variable INTERVAL 
        my variable PREVIOUS 
        
                set INTERVAL $INT
                set PREVIOUS [clock milliseconds]
        }
        # -----------------------------
        #
        #
        method interval { INT } \
        {
        my variable INTERVAL
        
                set INTERVAL $INT
                return 1
        }
        # -----------------------------
        #
        #
        method check { } \
        {
        my variable INTERVAL
        my variable PREVIOUS
        
                set NOW [clock milliseconds]
        
                # puts [expr $NOW - $PREVIOUS]
                
                if { [expr $NOW - $PREVIOUS] >= $INTERVAL } \
                {
                        set PREVIOUS $NOW
                        return 1
                }
                return 0
        }
        # -----------------------------
        #
        #
        method reset { } \
        {
        my variable PREVIOUS
        
                set PREVIOUS [clock milliseconds]
                return 1
        }
}; # end of class metro
oo::class create ::owi::arm \
{
        # -----------------------------
        #
        #
        constructor { } \
        {
        my variable GR_MET
        my variable GR_BIN 
        my variable WR_MET
        my variable WR_BIN
        my variable EL_MET
        my variable EL_BIN
        my variable SH_MET
        my variable SH_BIN 
        my variable BS_MET
        my variable BS_BIN
        my variable LT_BIN 00
        my variable LC_MET
        
                set GR_MET        [::owi::metro new 80]
                set GR_BIN        00
                set WR_MET        [::owi::metro new 80]
                set WR_BIN        00
                set EL_MET        [::owi::metro new 80]
                set EL_BIN        00
                set SH_MET        [::owi::metro new 80]
                set SH_BIN        00
                set BS_MET        [::owi::metro new 80]
                set BS_BIN        00
                set LT_BIN        00
                
                set LC_MET        80
                #after $LC_MET        [self object] upd_bin
        }
        # -----------------------------
        # 
        #
        method upd_bin { } \
        {
        my variable GR_MET
        my variable GR_BIN 
        my variable WR_MET
        my variable WR_BIN 
        my variable EL_MET
        my variable EL_BIN 
        my variable SH_MET
        my variable SH_BIN 
        my variable BS_MET
        my variable BS_BIN 
        my variable LT_BIN 
        my variable LC_MET
                
                if { [$GR_MET check] } { set GR_BIN 00 }
                if { [$WR_MET check] } { set WR_BIN 00 }
                if { [$EL_MET check] } { set EL_BIN 00 }
                if { [$SH_MET check] } { set SH_BIN 00 }
                if { [$BS_MET check] } { set BS_BIN 00 }
                
                # puts "B1 : $GR_BIN $WR_BIN $EL_BIN $SH_BIN | B2 : 00 00 00 $BS_BIN | B3 : 00 00 00 $LT_BIN"
                after $LC_MET [self object] upd_bin
                [self object] send_bin
                return
        }
        # -----------------------------
        # 
        #
        method grip { { DIR stop} } \
        {
        my variable GR_MET
        my variable GR_BIN 
        
                if        { $DIR == "open" }        { set GR_BIN 01 } \
                elseif        { $DIR == "close" }        { set GR_BIN 10 } \
                else                                { set GR_BIN 00 }
                $GR_MET reset
                return
        }
        # -----------------------------
        # 
        #
        method wrist { { DIR stop} } \
        {
        my variable WR_MET
        my variable WR_BIN 
        
                if        { $DIR == "up" }        { set WR_BIN 01 } \
                elseif        { $DIR == "down" }        { set WR_BIN 10 } \
                else                                { set WR_BIN 00 }
                $WR_MET reset
                return
        }
        # -----------------------------
        # 
        #
        method elbow { { DIR stop} } \
        {
        my variable EL_MET
        my variable EL_BIN 
        
                if        { $DIR == "up" }        { set EL_BIN 01 } \
                elseif        { $DIR == "down" }        { set EL_BIN 10 } \
                else                                { set EL_BIN 00 }
                $EL_MET reset
                return
        }
        # -----------------------------
        # 
        #
        method shoulder { { DIR stop} } \
        {
        my variable SH_MET
        my variable SH_BIN 
        
                if        { $DIR == "up" }        { set SH_BIN 01 } \
                elseif        { $DIR == "down" }        { set SH_BIN 10 } \
                else                                { set SH_BIN 00 }
                $SH_MET reset
                return
        }
        # -----------------------------
        # 
        #
        method base { { DIR stop} } \
        {
        my variable BS_MET
        my variable BS_BIN 
        
                if        { $DIR == "cw" }        { set BS_BIN 01 } \
                elseif        { $DIR == "ccw" }        { set BS_BIN 10 } \
                else                                { set BS_BIN 00 }
                $BS_MET reset
                return
        }
        # -----------------------------
        # 
        #
        method light { { DIR stop} } \
        {
        my variable LT_BIN 
        
                if { $DIR == "on" } { set LT_BIN 01; return }
                set LT_BIN 00
                return
        }
        # -----------------------------
        # 
        #
        method start_arm { } \
        {
        my variable LC_MET
        
                after $LC_MET        [self object] upd_bin
                return
        }
        # -----------------------------
        # 
        #
        method stop { } \
        {
        my variable GR_BIN 
        my variable WR_BIN 
        my variable EL_BIN 
        my variable SH_BIN 
        my variable BS_BIN 
        
                set GR_BIN 00
                set WR_BIN 00 
                set EL_BIN 00 
                set SH_BIN 00 
                set BS_BIN 00
                
                [self object] send_bin
                return
        }
        # -----------------------------
        # 
        #
        method send_bin { } \
        {
        my variable GR_BIN 
        my variable WR_BIN 
        my variable EL_BIN 
        my variable SH_BIN 
        my variable BS_BIN
        my variable LT_BIN

                scan $SH_BIN$EL_BIN$WR_BIN$GR_BIN        "%b" B1
                scan 000000$BS_BIN                        "%b" B2
                scan 000000$LT_BIN                        "%b" B3
                
                # puts "B1:$B1 - B2:$B2 - B3:$B3"
                
                set H1 [format %X $B1]
                set H2 [format %X $B2]
                set H3 [format %X $B3]
                
                ::owi::wrap_owi_send [list $H1 $H2 $H3]
                return
        }
}

*osc.tcl

package require osc

set ARM [::owi::arm new]
$ARM start_arm

osc::createServer arm 8000 \
{
variable DEBUG                1
variable SCN1_FADER
variable SCN1_TOGGLE

        # ---------------------------------------------------------------------
        # * Screen 1
        #
        # -- Four joints
        # ** Joint one -base 
        method void /base/ccw { float X } \
        {
                puts "set shoulder to $X"
                $::ARM base ccw
                return 1
        } "Base"
        method void /base/cw { float X } \
        {
                puts "set shoulder to $X"
                $::ARM base cw
                return 1
        } "Base"
        #
        method void /shoulder/up { float X } \
        {
                puts "set shoulder to $X"
                $::ARM shoulder up
                return 1
        } "Shoulder"
        method void /shoulder/down { float X } \
        {
                puts "set shoulder to $X"
                $::ARM shoulder down
                return 1
        } "Shoulder"
        #
        method void /elbow/up { float X } \
        {
                $::ARM elbow up
                return 1
        } "Elbow"
        method void /elbow/down { float X } \
        {
                $::ARM elbow down
                return 1
        } "Elbow"
        #
        method void /wrist/up { float X } \
        {
                $::ARM wrist up
                return 1
        } "Wrist"
        method void /wrist/down { float X } \
        {
                puts "set shoulder to $X"
                $::ARM wrist down
                return 1
        } "Wrist"
        #
        method void /grip/close { float X } \
        {
                puts "set shoulder to $X"
                $::ARM grip close
                return 1
        } "grip"
        method void /grip/open { float X } \
        {
                puts "set shoulder to $X"
                $::ARM grip open
                return 1
        } "grip"
        #
        method void /light { float X } \
        {
                puts "set lifgt to $X"
                if { $X == 1.0 } { $::ARM light on } else { $::ARM light off } 
                return 1
        } "Shoulder"
        # fallback method - useful for debugging and tests
        # generic methods (*) are called last if at all, so
        # if we want to ...
        method int * {* args} \
        {
        variable DEBUG
                if { $DEBUG == 1 } \
                {
                        puts "* From $SENDER received $PATH ($TYPES) $args"
                }
                return -code ok 1
        } "Debug handler"

}

IMPROVEMENTS :

*Make the OSC interface respond to contact and release separately instead of just contact with a timer (Metro). *Make a more generic SWIG wrapper for libusb to be used for other projects. *Fit the whole mess into a proper TEA compliant package. *Implement a static global or hash table for the USB structures so I can stop opening and closing on every call 4. ???