original in en
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!