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 package 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. ???