original in en Katja and Guido Socher
Katja is the German editor of LinuxFocus. She likes Tux, film & photography and the sea. Her homepage can be found here.
Guido is a long time Linux fan and he likes Linux because it is designed by honest and open people. This is one of the reasons why we call it open source. His homepage is at linuxfocus.org/~guido.
Robots have always fascinated us and so we were both excited when we found a book about robots some time ago which already included the robot kit to build a small insectlike robot called Stiquito. Stiquito is a somewhat special robot because it doesn't have a motor but walks because the legs are wired with nitinol and this way it walks totally silently just like a real insect. But when we had built it we noticed that because there wasn't enough friction with the surface where it walked on, its actual movement was very very slow. Fortunately the book also included some descriptions of other robot designs which finally inspired us to build the robot you can read here about.
To build the robot we used the following parts:
Fig 1: Circuit board |
Fig 2: needle-nose-pliers |
For the body you first need three parts of the circuit board, the
one with 6x6 holes and the two with 6x7 holes as well as 4cm of 2 mm diameter
brass tube together with 3.7 cm music wire.
Fig 4: backbone and powerbus
Cut the brass tube in peaces of 8, 17.5 and 8mm as shown in the picture.
You can do this by
rolling it back and forth under a sharp kitchen knife and then bending
it. The tubes will break where you made the notch with the knife. It is
important that the tube in the middle is slightly longer than the
6x6 holes circuit board. Cut off about 3.7cm of music wire. The final
length must be around 3 mm longer than the 3 tubes together. Put the music wire
through the three tubes
The tube in the middle must be able to rotate while the other two are
soldered to the music wire.
Fig 5: solder boards to backbone
The tube in the middle is now soldered to the 6x6 holes circuit board.
Take care that it can rotate. The other two tubes are soldered to the
other two circuit boards.
Now take the small, 2x7 holes, circuit board. It should stand
up on edge from the middle brass tube. The circuit board must be
notched with a little file or with the cutter. Solder it to the middle
brass tube and the middle circuit board as shown in the picture:
Fig 6: adding the small circuit board
Sand the 1mm brass tube and cut several 4mm long pieces of the tube.
Roll the tube under the kitchen knife and then bend it. You need 16 of
those crimps but make a few spare crimps.
As a lot of crimping is needed now you should better test it with a
little bit of nitinol before you start: Put the end of the nitinol wire
into the very thin brass tubes (1mm diameter outside) and then squeeze
the brass tube with the needle-nose-pliers. This is called crimping.
Take care to buy good needle-nose-pliers because the forces needed to
squeeze the brass tubes are very high. You can also slide the ends of
the nitinol through 600 grid sand paper to get good electrical
connections.
Now we will wire the nitinol wire that is needed to move the legs up
and down.
Fig 7: "the bridge"
You span the nitinol wire so as if you wanted to build a bridge. You
start on one side. There you put the nitinol wire through the last hole
that is possible on the left and straight side. You tie a knot in the nitinol
wire (in order to insure a better connection) and put
a crimp ( a ca. 4mm brass tube) over it and crimp it tight so that it
is tight and the nitinol wire can be put through the second hole from
above on the left side and then through the last possible hole on the
left and straight side. On the bottom again a knot is tied in the
nitinol wire and a crimp is put over and crimped tight (see Fig 7). The nitinol
wire must be tight but not too much. If you tip with the finger on it
then it should move 2-4 mm. If it isn't tight enough or if it is too
tight then the robot will not move properly later on. Solder the crimps
to the board.
Do the same on the second side.
Before continuing try out if it works. Use a 1.5V AA mignon battery and
connect it to one of the nitinol wires. When the wire contracts the
middle body part must rotate by 10-20 degrees. Take care not to connect
the battery longer than 1 second. You damage the wire if you overheat
it.
Fig 8: bend the wire
For the legs you cut three 10cm long parts off the music wire. Each is
bend 1.5cm on both sides. Then they are soldered to the three body
parts of the robot. They should be parallel to each other.
Fig 9, 10: legs on the robot
Now you must wire the nitinol wire to the 6 legs.
Fig 11: add the actuators
Put nitinol wire from above through a crimp and through a hole in the
circuit board. The distance to the music wire is 3 holes. Crimp it
tight (see pictures above).
Then pull a crimp over the music wire until you reach the knee bending.
Put the nitinol wire through it and crimp it tight. Now comes the most
difficult part. Hold the robot with a little vice and fix and bend the
legs with tape or extra cooper wire. The music wire acts as a
counterforce to the nitinol. For this to work the nitinol must not be
loose at all. The music wire must be pulled by 1 circuit board hole
towards the nitinol and then the crimp must be soldered to the leg.
Fig 12: nitinol and music wire on the same level
Make sure that the music wire and the nitinol are on the same level.
The legs must not move up or down when the nitinol contracts. The leg
must move backwards.
Do the same with the other five legs.
The legs and the music wire with the brass tubes in the middle of the
robot act as a power bus and therefore there must be an electrical
connection between all of them. As however the middle body part has
more freedom because it can rotate and therefore has no good connection
we improved this by taking 3 cm of the 0.1mm varnished cooper wire and
wraping it around the spare brass tube to get a little coil. Take out
the brass tube and then solder this coil in the middle to the inner leg
pair and to one of the outer leg pairs. The coil shape of the wire
ensures maximum flexibility.
When the robot is ready you can solder 0.5m long pieces (or longer if you want) of 0.1 mm varnished cooper wire to the crimps on the board and solder the body crimps themself to the circuit board. We need 9 wires, 6 for the legs, 2 for up/down and one for the common powerbus. You can solder the other ends of the wires to a small connector which you can then plug into a corresponding small socket on the driver circuit.
Our insect is designed to walk in a tripod gait. A tripoid gait
means that 3 legs are on the ground (two on one and one on the other side)
while the other 3 are up in the
air. When the robot walks then the 3 legs on the ground move in one
direction while the legs in the air walk in the opposite direction.
Fig 13: The gait
This circuit board allows you to use your PC to control the
actuators on the robot and plugs into the parallel port.
When we developed our computer program we first tested it with the LEDs
and only pluged the robot control wires into the socket on the board
when it worked correctly and the LEDs showed a correct working gait.
You should do the same when you experiment with the program.
The robot is quite hungry. You need to run between 200 to 250 mA of
current through the nitinol to contract it. The 3 cm long nitinol wires
on the legs have about 7 Ohms. Always start the software first before
you connect the power to the driver circuit because all data pins are
at first set to off by the software to prevent damaging the nitinol
wire. As the bios of the computer sets the parallel port data pins to random
values some of them are maybe in the state on and the nitinol can be damaged
if you run the current for much longer
than 1 second through it. The time for the nitinol to cool down should
be 1.5 times the time you heated it.
The circuit diagram:
Fig 14: circuit diagram
As you can see in the diagram above we use an electronically
stabilized power supply. This is to ensure good and stable power and to
protect the parallel port. As external power supply you can connect any
DC power supply between 6 and 24 V. The 7805 is a standard voltage
regulator. The only thing to pay attention to here is that the 2
capacitors (470uF and 0.1uF) are located very closely to the 7805
voltage regulator because otherwise it could happen that the 7805 chip
starts to oscillate which could destroy the 7805.
The actual driver has to be build 8 times. One for each leg and 2 for
twisting the robot (moving the legs up and down). We use a small NPN
Darlington transistor because our robot needs a lot of current. The
BC875 or BC618 can switch about 500mA. The 47K on the input ensures
that an open circuit (e.g the computer is not connected) is always
equivalent to "off". The voltage level on the parallel port is above 4V
for "on" and below 1V for the state "off". The transistor works only as
a switch. The 15 Ohm resistors limit the
current and protect both the legs of the robot and the transistor. The
LEDs show the state (on or off).
Below you see pictures of the circuit. The red LEDs (the ones which are
parallel to the robots actuators) are difficult to see as we used
transparent red LEDs. We built the 15 Ohm resistors from constantan
wire coils but this was just because we had plenty of that wire. It is
cheaper to buy ready made 2W resistors.
The parallel port was designed to serve as an output port from a
personal computer and to attach to a printer. Some parallel ports allow
both input and output. Here we only use the port for output. In a later
article we will connect sensors to the robot and then also use the
input lines. Although there are 25 pins for the parallel port, we only
use nine. Eight of the lines are used as data output lines and one line
serves as the electrical ground.
The pinout for the parallel port is as follows:
25 PIN D-SUB FEMALE at the PC. Pin Name Dir Description 1 STROBE [-->] Strobe 2 D0 [-->] Data Bit 0 3 D1 [-->] Data Bit 1 4 D2 [-->] Data Bit 2 5 D3 [-->] Data Bit 3 6 D4 [-->] Data Bit 4 7 D5 [-->] Data Bit 5 8 D6 [-->] Data Bit 6 9 D7 [-->] Data Bit 7 10 ACK [<--] Acknowledge 11 BUSY [<--] Busy 12 PE [<--] Paper End 13 SEL [<--] Select 14 AUTOFD [-->] Autofeed 15 ERROR [<--] Error 16 INIT [-->] Initialize 17 SELIN [-->] Select In 18 GND [---] Signal Ground 19 GND [---] Signal Ground 20 GND [---] Signal Ground 21 GND [---] Signal Ground 22 GND [---] Signal Ground 23 GND [---] Signal Ground 24 GND [---] Signal Ground 25 GND [---] Signal GroundYou connect the driver circuit to pin 18 (GND) and to the data pins (2-9).
==== pprobi.c ===== /* vim: set sw=8 ts=8 si : */ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License. * See http://www.gnu.org/copyleft/ for details. * * Written by Katja Socher <katja@linuxfocus.org> * and Guido Socher <guido@linuxfocus.org> * */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <stdarg.h> #include <string.h> #include <math.h> #include <signal.h> #include "robi.h" /* ----------- */ static int opt_r=0; static int fd=0; /* ----------- */ /* ----------- */ void help() { printf("pprobi -- control software for a walking robot\n\ USAGE: pprobi [-h] [parport-device]\n\ \n\ OPTIONS:\n\ -h this help\n\ -r reset the parallel port data pins (all zero) and exit\n\ \n\ The default device is /dev/parport0 \n\ "); #ifdef VERINFO puts(VERINFO); #endif exit(0); } /* Signal handler: all off then exit */ void offandexit(int code) { robi_setdata(fd,0); set_terminal(0); exit(0); } /* ----------- */ int main(int argc, char **argv) { int state,bpat,alternate; char *dev; /* The following things are used for getopt: */ int ch; extern char *optarg; extern int optind; extern int opterr; opterr = 0; while ((ch = (char)getopt(argc, argv, "hr")) != -1) { switch (ch) { case 'h': help(); /*no break, help does not return */ case 'r': opt_r=1; break; case '?': fprintf(stderr, "serialtemp ERROR: No such option. -h for help.\n"); exit(1); /*no default action for case */ } } if (argc-optind < 1){ /* less than one argument */ dev="/dev/parport0"; }else{ /* the user has provided one argument */ dev=argv[optind]; } fd=robi_claim(dev); /* robi_claim has its own error checking */ /* catch signals INT and TERM and switch off all data lines before * terminating */ signal(SIGINT, offandexit); signal(SIGTERM, offandexit); /* initialize parpprt data lines to zero: */ robi_setdata(fd,0); set_terminal(1); /* set_terminal has its own error handling */ state=0; alternate=0; if (opt_r){ offandexit(1); } while(1){ ch=getchoice(); if (ch!=0) state=ch; if (ch == ' '){ printf("Stop\n"); robi_setdata(fd,0); usleep(500*1000); } if (ch == 'q'|| ch == 'x'){ printf("Quit\n"); break; } if (state=='l'){ /*right */ printf("walking right\n"); walkright(fd); } if (state=='h'){ /*left */ printf("walking left\n"); walkleft(fd); } if (state=='j'){ printf("walking back\n"); walkback(fd); } if (state=='k'){ if (alternate){ printf("walking straight on a\n"); walkstraight_a(fd); }else{ printf("walking straight on b\n"); walkstraight_b(fd); } alternate=(alternate +1) %2; } } /* we get here if q was typed */ set_terminal(0); return (0); } ==== robi.c ===== /* vim: set sw=8 ts=8 si : */ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License. * See http://www.gnu.org/copyleft/ for details. * * Written by Katja Socher <katja@linuxfocus.org> * and Guido Socher <guido@linuxfocus.org> * */ #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <sys/types.h> #include <sys/time.h> #include <fcntl.h> #include <unistd.h> #include <signal.h> #include <linux/ppdev.h> #include <sys/ioctl.h> #include <termios.h> #include "robi.h" /* like printf but exit the program */ static int die(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); exit(1); } /* get one character from stdin * Returns non zero if char was read otherwise zero * The arrow keys are mapped as follows: * <- = h * -> = l * v = j * ^ = k */ int getchoice() { int c; char s[20]; if (fgets(s,20,stdin)){ c=s[0]; switch (c){ case 0x1b: /* ESC */ if (s[1] == 0x5b){ /* arrow keys are pressed */ switch (s[2]){ case 0x41: /*up arrow*/ c='k'; break; case 0x42: /*down arrow*/ c='j'; break; case 0x44: /*l arrow*/ c='h'; break; case 0x43: /*r arrow*/ c='l'; break; default: c=0; } }else{ c=0; } break; case ' ': case 'h': case 'j': case 'k': case 'l': case 'q': case 'x': break; default: c=0; } return(c); } return(0); } /* Set the Terminal to Non Canonical mode with echo off * or reset the terminal. * USAGE: set_terminal(1) for canonical */ int set_terminal(int canonical) { static struct termios originalsettings; struct termios newsettings; static int origok=0; /* set if originalsettings valid */ if (canonical){ /* save original settings and set canonical mode*/ tcgetattr(fileno(stdin),&originalsettings); newsettings=originalsettings; newsettings.c_lflag &= ~ICANON; newsettings.c_lflag &= ~ECHO; newsettings.c_cc[VMIN]=0; /* do not block */ newsettings.c_cc[VTIME]=1; /* 100 ms */ if (tcsetattr(fileno(stdin),TCSANOW,&newsettings) !=0){ die("ERROR: could not set terminal attributes on stdin\n"); } origok=1; }else{ if (origok){ /* restore settings */ tcsetattr(fileno(stdin),TCSANOW,&originalsettings); } } return(0); } /* open /dev/parportX device and claim it. * USAGE: fd=robi_claim("/dev/parport0"); * The return value is a file descriptor used by other * functions such as robi_setdata */ int robi_claim(char *dev) { int fd,i; fd = open(dev, O_RDWR ); if (fd < 0) { die("ERROR: cannot open device %s\n",dev); } i=0; /* we need exclusive rights as we do not set the control lines*/ /*ioctl(fd, PPEXCL, &i)&& die("ERROR: request for exclusive rights failed\n");*/ ioctl(fd, PPCLAIM, &i)&&die("ERROR: could not claim parport\n"); return(fd); } /* Walk left */ int walkleft(int fd) { /* first B legs to ground */ robi_setdata(fd,LEGBD); usleep(400 *1000); /* all A legs 1 step */ robi_setdata(fd, LEGB1 | LEGB3 ); usleep(1100 *1000); /* first A legs to ground, cool B*/ robi_setdata(fd,LEGAD); usleep(400 *1000); robi_setdata(fd,0); usleep(1000 *1000); return(0); } /* Walk right */ int walkright(int fd) { /* first A legs to ground */ robi_setdata(fd,LEGAD); usleep(500 *1000); robi_setdata(fd, LEGA3 | LEGAD); usleep(300 *1000); /* all A legs 1 step */ robi_setdata(fd, LEGA1 | LEGA3 ); usleep(1100 *1000); /* first B legs to ground, cool A*/ robi_setdata(fd,LEGBD); usleep(400 *1000); robi_setdata(fd,0); usleep(1000 *1000); return(0); } /* Walk with all 3 legs 1 step forward */ int walkstraight_a(int fd) { /* first A legs to ground */ robi_setdata(fd,LEGAD); usleep(800 *1000); /* all A legs 1 step */ robi_setdata(fd, LEGA1 | LEGA2 | LEGA3 ); usleep(1000 *1000); /* first B legs to ground, cool A*/ robi_setdata(fd,LEGBD); usleep(500 *1000); robi_setdata(fd,0); usleep(1200 *1000); return(0); } /* Walk with all 3 legs 1 step forward */ int walkstraight_b(int fd) { /* first B legs to ground */ robi_setdata(fd,LEGBD); usleep(400 *1000); /* all B legs 1 step */ robi_setdata(fd,LEGB1 | LEGB2 | LEGB3); usleep(1000 *1000); /* A down and cool */ robi_setdata(fd,LEGAD); usleep(800 *1000); robi_setdata(fd,0); usleep(1200 *1000); return(0); } /* Walk with all 6 legs 1 step back */ int walkback(int fd) { /* first A legs to ground */ robi_setdata(fd,LEGAD); usleep(800 *1000); /* all B legs 1 step in the air*/ robi_setdata(fd, LEGB1 | LEGB2 | LEGB3 ); usleep(500 *1000); /* first B legs to ground, cool A*/ robi_setdata(fd,LEGBD); usleep(500 *1000); /* all A legs 1 step in the air*/ robi_setdata(fd,LEGA1 | LEGA2 | LEGA3); usleep(500 *1000); /* A down and cool */ robi_setdata(fd,LEGAD); usleep(800 *1000); robi_setdata(fd,0); usleep(1000 *1000); return(0); } /*---------*/ /* Write a bit pattern to the data lines * USAGE: rc=robi_setdata(fd,bitpat); * The return value is 0 on success. */ int robi_setdata(int fd,unsigned char bitpat) { int rc; rc=ioctl(fd, PPWDATA, &bitpat); return(rc); } ==== robi.h ===== /* vim: set sw=8 ts=8 si et: */ #ifndef H_ROBI #define H_ROBI 1 #define VERINFO "version 0.2" /* the first thing you need to do: */ extern int robi_claim(char *dev); /* write a bit pattern to the data lines of the parallel port: */ extern int robi_setdata(int fd,unsigned char bitpat); /* input and terminal functions */ extern int set_terminal(int canonical); extern int getchoice(); extern int walkstraight_a(int fd); extern int walkstraight_b(int fd); extern int walkback(int fd); extern int walkleft(int fd); extern int walkright(int fd); /* data pins to legs: * A1------=------B1 * = * = * B2------=------A2 * = * = * A3------=------B3 * * * Pin to set A-legs to ground= AD * Pin to set B-legs to ground= BD * * parallel port leg name * ------------------------- * data 0 A1 * data 1 A2 * data 2 A3 * data 3 AD * data 4 B1 * data 5 B2 * data 6 B3 * data 7 BD */ #define LEGA1 1 #define LEGA2 2 #define LEGA3 4 #define LEGAD 8 #define LEGB1 16 #define LEGB2 32 #define LEGB3 64 #define LEGBD 128 #endif
The software uses the ppdev programming interface from the 2.4.x Kernel (You need a 2.3.x or 2.4.x Kernel. It will not work with older kernels). This is a clean and convenient interface for writing user space parallel port device drivers. In older kernels we would have had to write a kernel module or use a rather ugly method which would only allow the user root to run the program. The ppdev interface uses the device file /dev/parport0 and by adjusting the owner and permissions of that file you can control who is allowed to use this parallel port interface.
To compile the ppdev as a module into your kernel you need to compile the PARPORT module together with the PPDEV device. This should then look as follows in the .config file:
# # Parallel port support # CONFIG_PARPORT=m CONFIG_PARPORT_PC=m CONFIG_PARPORT_PC_FIFO=y # CONFIG_PARPORT_PC_SUPERIO is not set # CONFIG_PARPORT_AMIGA is not set # CONFIG_PARPORT_MFC3 is not set # CONFIG_PARPORT_ATARI is not set # CONFIG_PARPORT_SUNBPP is not set CONFIG_PARPORT_OTHER=y CONFIG_PARPORT_1284=y # # Character devices # CONFIG_PPDEV=m #
The program first claims (initializes) the parallel port with the
ioctl command PPCLAIM. Then it sets the terminal to non canonical mode.
This is to get the input directly from the keyboard without the user
always having to press return after each input. Next it goes into a
loop where it first checks if there was any user input and then lets
the robot walk according to the command. If you don't do anything the
program will just continue with the last command (e.g continue to walk
straight).
The command ioctl(fd, PPWDATA, &bitpat); is used to set
the data lines to a given bit pattern.
The pins from your robot need to be connected to the output lines of the driver circuit as follows:
Legs: A1------=------B1 = = B2------=------A2 = = A3------=------B3 Pin to set A-legs to ground= AD Pin to set B-legs to ground= BD Corresponding output lines of the driver circuit: data 0 A1 data 1 A2 data 2 A3 data 3 AD data 4 B1 data 5 B2 data 6 B3 data 7 BDData 0 is the output of the driver circuit that connects to the parallel port at pin 2 (D0).
We hope that you had a lot of fun building the robot. Just let us know about your robot, especially if yours is built with a different design!