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 #include #include #include #include #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. ??? <>Enter Category Here