Showing posts with label Autonomous. Show all posts
Showing posts with label Autonomous. Show all posts

Simple 18dof Hexapod, Arduino nano

Monday, 1 July 2013

Simple 18dof Hexapod, Arduino nano                 

The content and the project is by orangenarwhals
                                                                                 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Hello,
Here is a simple hexapod that can be built by hand very quickly. The mechanical design is not great, but it is very much in the KISS (keep it stupidly simple) style and should be doable in a weekend for builders of novice to medium experience.

I won't be improving this project any time soon, and people seem to visit my blog from pololu, so I thought I'd go ahead and document it as is. I built this for a sophomore mechanical engineering class at MIT. The wires and six legs make it look complicated, but since the legs are just the same thing repeated 6 times, it's simpler than it appears. Additionally, I did not implement remote controls so all the code runs autonomously (zero input, multiple output system).

[edit: Please vote for me in the epilog contest if you think this is worthy! I don't think this is lasercutter worthy, but I actually just really want an instructables shirt. :D ]

Again, this is documentation of the exact steps involved in a semi-working project. No theoretical underpinnings for designing your own hexapod are really explained here.

A complete picture set of the build process exists here: 2.007 Hexapod (Spring 2011). The first few pictures on there are from Aluminum Hexalinkagepod, based off of the Parallax boebot hexapod.
A set of blog posts exists here:http://j.gs/2Tgu I would specifically recommend this post:http://j.gs/2Tgv A video explaining the design process in 7 minutes (this instructables goes into the construction but not the design): http://j.gs/2Tgw and here is a video of it at the end:


Required items (hardware):
~ Vertical bandsaw (unless you have a lot of patience with a razor)
~ 1/4'' plastic sheet (any reasonable thickness such that the plastic is fairly rigid is fine. I used 1/4'' ABS)
~ About 6'' of
~ 18 R/C servos (I recommend standard size, I've seen hexapods with the tiny 9g servos, but I think that it would be hard to cut out the holes for such servos by hand), complete with the "+" shaped servo horns and servo center screws that should come with the kit.
I used 6 Hitec-311 and 12 Vigor VS-2 servos because that was what I could scavenge for.
~ Screwdriver
~ 4-40 bolts and locknuts (about 48 of them), or whatever bolts fit through your servo flange holes (the side holes). At least 3/8'' long (enough for the 1/4'' plastic or Al and a locknut to fit on there).
~ Drill and drill bit, ideally also a drill press
~ Ratchet or socket wrench for 4-40 bolts
~ Ideally, a vice or clamp
~ Ideally, a horizontal bandsaw
~Optional: Scrap 2x4 wood
~ Measuring instrument, ruler or vastly preferably calipers
~ Optional: Deburring tool


Required items (electronics): 
~ Arduino nano + breadboard + male headers (for the servos)
~ Either 6 Y-splitter servo cables or a pololu serial servo controller (because the default arduino library only supports 12 servos). I bought an 24ch one, but obviously didn't need all 24 ch, not sure why I did that. >.<;; but I am a conservative person and tend to make large purchases just in case. I'm working on fixing this.
I guess another option is to use an arduino mega.
~ Laptop and usb programming cable appropriate for your flavor of arduino
Male Headers
~ Jumper wires (or single core wire
 suitable for breadboards)
~ Potentially some servo extension cables, female-to-female (and you stick headers in them to make them female-to-male) will come in handy.
~ Breadboard (probably a standard 700 point one is best if you are putting the nano onto the breadboard)
~ Battery pack (I recommend a rechargeable battery flavor of battery pack, as the 18 servos are power hungry)
~~ so you could use a 4xAA battery pack and it'd be fine (the servos are nominally 5V servos but they will run fine at 6v, they will just be a bit twitchy because their circuitry/feedback+controls are designed for 5v use), but if they were alkaline batteries (~3000mAh) they would run out after an hour or less of use

~~alternatively, use a LM7805 chip to regulate a lipo battery pack, which runs at 7.4v, down to 5V. These linear power regulators dissipate the extra energy as heat. For how to use one, please google "7805 tutorial". For instance, see: http://jumptuck.com/2011/11/15/voltage-regulation-friend-7805/.


Time required: 1 weekend if you just follow my design. I encourage you to design your own hexapod though, once you see how easy it is!


What I used also included a 2.007 (that's a course at MIT) carrier board (looks like https://sites.google.com/site/2007arduino/). This just brings the servo pins out for easy access, as well as has a built in breadboard and a switching power regulator* that can supply up to 3A at 5v, which is probably enough for the 18 servos. It seemed to function a-okay, but my code only ever had 6 servos moving at any given time.

I also made my own battery pack out of some Sanyo UR18650U batteries that were donated to MITERS by Tesla. They are 3.6V, so I made a 2-series, 3 parallel battery pack for a 7.2V 3.3Ah battery pack. This is obviously overkill. At 3.3 Ah and continuously drawing 3A, I could run my hexapod for over an hour. I've found that 10-20 minutes is plenty of runtime for hexapod.

*as opposed to a LM7805 linear power regulator, a switching power regulator is much more efficient.

Note:The hexapod CAD files are for Solidworks 2012 and do not detail nuts and bolts, they are for reference only. I also made a diameter versus radius error on the body, so the body is too big. 

Step 1: Make the Paper Templates for Femur and Tibia Segments

 
 
 
 
 
 
 
 
 
 
 
We'll be using paper templates to mark out six of the same thing (six legs).

My tibia ended up (after a few iterations) being 1.4x3.75 inches, with holes marked appropriately for where the servo horns would go.

My coxa ended up being 1.42x4.83 inches, with an appropriately sized hole cut out for the servo. The curve on the leg is an arbitrary "it looks nice" curve I cut out directly on the bandsaw (I started out with rectangular legs).

The CAD model you see in the pictures is a later and better (read: smaller and lighter, more appropriate for the servos) revision than the paper templates, and should give you some idea of how it the hexapod is assembled. So ignore the dimensions written on the paper template in the pictures.

Once you are done making the templates, mark them onto the plastic sheet in preparation for cutting. For marking the holes, sharpie bleeds through paper so just line the paper templates up with the plastic and mark the template with sharpie and it should show up on the plastic. I actually cut the legs out and then marked the holes; you can do it in any order.

Step 2: Cut out the Femur and Tibia Segments

I used a vertical bandsaw for cutting out the femur and tibia segments. Make sure to wear safety glasses and take appropriate safety precautions (no loose hair/jewelry/clothing, etc.).

For the tibia and cutting out where the servo fits through, since the bandsaw blade can't just turn around when you hit the inside corners, instead of one continuous cut, you make two half-moon cuts from opposite sides and then go through and cut the back edge. See the first picture.

Step 3: Drill out the Servo Horns and Leg Segments

The servo horns and  should have holes the same size for the bolts to drop through and be locked in place with locknuts. Look up "tap and drill sizing chart" on google, then clearance or through hole fits, for whatever size screw you ended up getting. Basically, the holes should be the major diameter of the screws, so that the body of the screw fits through but not the cap.

For instance, for 4-40 screws (#4 size screws with 40 threads per inch), I look up http://www.physics.ncsu.edu/pearl/Tap_Drill_Chart.html and see that I can use either a size 32 or size 30 drill bit for a clearance hole. Probably I will use a size 32, in case my hole is drilled off-center, I have some leeway for the bolt to still fit through even if the femur/tibia segment holes are not lined up perfectly with the servo flange holes.

To prevent too much burring on the back side and give the vice something thick to clamp on to, I put a block of wood behind the servo horns. These holes can all be done free-form with just a hand-drill because the plastic is so soft, especially with a center drill for the femur/tibia segments, but you just won't get nice cuts (and be careful not to send things flying if you don't press down with your hand hard enough). Actually, I really can't recommend doing it without clamps or a vice, for safety's sake.

To make it easier the next time around: I would have used self-tapping screws, or perhaps even just normal screws as the plastic is pretty soft, and then drilled sizing for aluminum (75% thread), e.g. for 4-40 I would have used a size 43 bit. This way, I don't have to deal with the whole locknut thing which takes extra tools and forever to tighten. I would have to have self-tapping screws of the right length, though, since they have pointy ends which might hurt if they stuck out of the plastic.

Yea, locknuts are much better than just two nuts tightened together, which always loosen unless you use locktite, but if you mess up something they take forever to un-tighten and re-tighten.

Step 4: Attach Servos to Femur and Tibia

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Time to break out the screwdriver and socket wrench!

I used bins to keep all the servo stuff organized, but that's entirely optional.I used bins to keep all the servo stuff organized, but that's entirely optional.

It's easy to zone out and put one or the other servo horn facing the wrong way or flipped or whatever -- again, locknuts are a pain to redo.

Make sure to use the center screw to screw into the servo. This keeps the leg segment from falling off of the servo.

Step 5: Bandsaw and Drill the Coxa Segments

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
These segments are cut out of the 1/4'' angle aluminum stock. They should be just longer than the servo flanges. I did this on the horizontal bandsaw and cut them to by 0.45'' wide. You could in theory do it on the vertical bandsaw if you had parallels set up on one side and a push stick. Definitely don't try cutting this out just free form with two hands, because 0.45'' is way too close to the blade for you to not lose a finger.

I measured the distance to each of the two holes by using calipers on the servo and estimating by eye where the centers of the holes were.

I gently scribed where each hole would go by setting my calipers to the right distance and then locking it, then brushing the tip of my el cheapo calipers across the aluminum.  See http://www.botlanta.org/uploads/pdf/caliper_abuse.pdf for more details on how to do this (written by my friend, charles guan / teamtestbot / etotheipiplusone / e0designs). Sharpie may be applied beforehand so you can see the scribed line better afterwards.

Then I went in with a center punch and center punch all of the holes.

Finally I went in with a drill press and drilled out all of the holes, then used a deburring tool on all the edges.

Step 6: Attach Servos to Coxa Segments

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
What the title says.
If any of the holes are off-center, remember that you really only need to attach two screws (one on either side) to the servo, not all four flange screws.

Repeat six times. Yea, you see what I mean about using self-tapping screws instead of bolts+locknuts now, because that is a _lot_ of locknuts to tighten.

Step 7: Mark out the Body

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
If you can, print out the template I provided. Otherwise, as follows:

Design: Again, no real math went into the design of this hexapod. I simply eyeballed how close together the legs could be and drew out a circle that big. See the paper on the right in the first picture for how I thought about it.

I treated it as an exercise to mark out that circle with just calipers, no compass. To do so, refer to www.botlanta.org/uploads/pdf/caliper_abuse.pdf again, starting on page 27.

Specifically, for a hexagon, referring to (http://media.photobucket.com/user/oldtiffie/media/Black_book/Polygon_measure1.jpg), the side length is just one-half the diameter.

So using the caliper technique (scribing two arcs of 1/2*D and finding the center point to determine the next point on the hexagon), I marked out six dots and then draw arcs between them to complete a circle (diameter = 5.9'').

Then I placed the servo horn on there and marked out where the center of the servo horn would go, then marked out another circle that big (diameter = 5.3'') with 6 points. Finally, I marked out two holes for each of the servo horns (four is really overkill) by just placing the servo horn center over the center dot and then orienting like an "X" instead of a "+" and using pencil to mark the two holes.

Tape the template onto the plastic, bandsaw in a circle, drill out the holes. Done!

Step 8: Assemble Legs onto Body

Make sure to put the servo horns on the body first, assembling with bolts+locknuts, and then attach the servo with the center screw. Because it doesn't go on the other way!

The first picture shows some posts I hot glued on. These didn't last. But that brings to mind
Revision: I would have included some standoffs for the controlboard to sit on and with space underneath for the battery pack to velcro in.

The second picture is from an older revision, but shows the order of installation.

Congratulations! You have a complete hexapod body! That wasn't so bad.


Step 9: Wire 'er up! 12 servo output version

 
 
 
 
 
 
 
 
 
 
 
 
 
 
Note: Servos go Ground-Power-Signal, usually black-red-yellowororange, with convention on most PCBs placing the GND facing out.

12 SERVO OUTPUT VERSION 
Suitable for limitations of default Arduino servo library. See first two pictures for wiring with the 2.007 nano carrier board.

Breadboard Setup
If you don't have a 2.007 carrier board (not for sale as of 24 June 2013, although that may change), put the nano on the breadboard and wire accordingly, with the 5V source in the picture being replaced by your 5v source (e.g. LM7805 with a 4xAA battery pack as described in the first step). You don't want to just put 4xAA into your arduino on "VIN" and then use the "5V" on the arduino because that puts it through a regulator that cannot handle enough current to supply all 18 servos -- your hexapod will be droopy and sleepy as overcurrent protection kicks in on the arduino, unless you just fry your arduino. You must supply both the arduino and the lm7805 circuit from the 4xAA battery pack and then use the LM7805 circuit output to supply your servos in order to not starve them of current.

See  http://www.instructables.com/id/How-to-use-a-breadboard/ if you've never used a breadboard before

Servo Wiring
Okay, so we have six Y-splitter cables. They will be controlling 12 servos total (2 each). Then we have 6 more independent legs, for 12 servo outputs required total. Now pick a face to be the front of the hexapod, and mentally split the legs evenly down either side. The front and back servos (coxa, femur, tibia) of each side will be wired together, and the middle legs will be independent.

Thus, in the code, we will only need 12 servo outputs, like so:

/*
~front~
A  D
B  E
C  F
~back~
*/

Servo E_coxa;
Servo E_femur;
Servo E_tibia;

Servo B_coxa;
Servo B_femur;
Servo B_tibia;

Servo AC_coxa;
Servo AC_femur;
Servo AC_tibia;

Servo DF_coxa;
Servo DF_femur;
Servo DF_tibia;

Note: In the picture, I have the letters placed incorrectly around the circular hexapod. Notice that it doesn't matter for explaining how the tripod gait works.

Step 10: Wire 'er up! Pololu Serial servo controller, 18 servo version

18 SERVO OUTPUT VERSION
Pololu


The following instructions are for the 2.007 board, however, they are detailed enough that they should explain what you need to do. Essentially, you need 5 wires: a single "servo output" (just an output pin, plus ground and power for the pololu board) and VSRV and GND for the servos. The two grounds should be connected somewhere (anywhere) in your circuit.

1. Look at labeled picture http://www.pololu.com/docs/0J40/1.b

2. What you need on pololu-side: serial going through to RX pin on pololu, power to VSRV and VIN, ground to GND, and lots of servos

'3. According to our eventual code:
#include <NewSoftSerial.h> #NOTE: July 2013: NewSoftSerial is included by default now, do not use this code -- see later steps in this instructables
#define txPin 2
NewSoftSerial mySerial(rxPin, txPin);
void setup(){
  mySerial.begin(9600);
  delay(1000);
}
we should take a servo female-female wire, put one end on servo male header pins for pin 2 of Arduino.

4. RX/TX and Microntroller power: Put other end white wire (or yellow or whatever wire is SIG) on RX on maestro, and red (VIN) and black (GND). Should look like the second picture, the wire-with-masking-tape, with black facing "out" toward the USB port.

5. Servo power: see first picture, the two non-servo cables (the red wire and black wire going to the breadboard) are screwed into the blue terminal block on the maestro. The breadboard has 5V and GND from the 8.4V battery going through the linear regulator on the carrier board. I'm actually stealing 5V from a servo pin. See the black wire soldered to Dig9Output, 5V in the upper left of this pic (which is actually carrying 5V, not GND) (via a female header pin so I wasn't soldering straight to the carrier board pins) (ignore the gazillion extraneous wires)

6. Remove VSRV=VIN jumper
Why remove the jumper? Well, a. Makes the pololu RX pin happier (compare to setup below) b. Setting that jumper seems to current-limit the power going to the servos, leading to my sad-servo symptoms. aka unable-to-walk hexapod.

Whether you will find this to be true, I don't know, but certainly this was a very frustrating point for me.

Servo Wiring
Straightforward. Plug in your 18 servos. (Yep, an 18ch maestro would have worked just fine instead of the 24ch one I bought. Even a 12ch one would work, but you would have to split your servo code between arduino and pololu servos and that could get messy).

Step 11: 12 Servo Code

This is for a simple tripod gait. Note that I just fix the tibia joints in place, which is not ideal, but makes the code very understandable. I would say this code is a good jumping off point. The servos in this code are attached on pins 2 through 13.

E_coxa.attach(2);
  E_femur.attach(3);
  E_tibia.attach(4);

  B_coxa.attach(5);
  B_femur.attach(6);
  B_tibia.attach(7);

  AC_coxa.attach(11);
  AC_femur.attach(12);
  AC_tibia.attach(13);

  DF_coxa.attach(8);
  DF_femur.attach(9);
  DF_tibia.attach(10);

The only values you should need to adjust are AC_up, AC_down COXA_CW, COXA_CCW, and potentially TIBIA. Probably you don't need to adjust them much if you are also using standard 180deg servos like I am. These I experimentally determined to be the limits if I didn't want the legs crashing into each other or into the main body of the hexapod (going too far up).

https://github.com/nouyang/18-servo-hexapod/blob/master/arduino_may13_2011.pde
Or, as copied below:
======================

#include <Servo.h>

#define TIBIA 45
#define DELAY 300

#define COXA_CCW 70
#define COXA_CW 105


/*
~front~
A  D
B  E
C  F
~back~
*/

#define AC_UP 92
#define AC_DOWN 125


int UP = AC_UP;
int DOWN = AC_DOWN;

Servo E_coxa;
Servo E_femur;
Servo E_tibia;

Servo B_coxa;
Servo B_femur;
Servo B_tibia;

Servo AC_coxa;
Servo AC_femur;
Servo AC_tibia;

Servo DF_coxa;
Servo DF_femur;
Servo DF_tibia;

void setup()
{
  digitalWrite(2, OUTPUT);
  digitalWrite(3, OUTPUT);
  digitalWrite(4, OUTPUT);
  digitalWrite(5, OUTPUT);
  digitalWrite(6, OUTPUT);
  digitalWrite(7, OUTPUT);
  digitalWrite(8, OUTPUT);
  digitalWrite(9, OUTPUT);
  digitalWrite(10, OUTPUT);
  digitalWrite(11, OUTPUT);
  digitalWrite(12, OUTPUT);
  digitalWrite(13, OUTPUT);

  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);

  E_coxa.attach(2);
  E_femur.attach(3);
  E_tibia.attach(4);

  B_coxa.attach(5);
  B_femur.attach(6);
  B_tibia.attach(7);

  AC_coxa.attach(11);
  AC_femur.attach(12);
  AC_tibia.attach(13);

  DF_coxa.attach(8);
  DF_femur.attach(9);
  DF_tibia.attach(10);

}


void loop()
{
  for (int i=0; i<=2; i++){
    walkfwd();
  }
  for (int j=0; j<=2; j++){
    walkbwd();
  }
  for (int k=0; k<=2; k++){
    turnleft();
  }
  for (int l=0; l<=2; l++){
    turnright();
  }



void walkbwd() {
  tibia();
  b1();
  b2();
  b3();
  b4();
}
void walkfwd() {
  tibia();
  tri1();
  tri2();
  tri3();
  tri4();
}

void turnleft() {
  tibia();
  l1();
  l2();
  l3();
  l4();
}

void turnright() {
  tibia();
  r1();
  r2();
  r3();
  r4();
}
void tibia() {
  AC_tibia.write(TIBIA);
  B_tibia.write(TIBIA);
  DF_tibia.write(TIBIA);
  E_tibia.write(TIBIA);
}

void tri1() {
  AC_coxa.write(COXA_CW);
  E_coxa.write(COXA_CCW);

  DF_coxa.write(COXA_CW);
  B_coxa.write(COXA_CCW);

  delay(DELAY);
};
void tri2() {
  AC_femur.write(AC_DOWN);
  E_femur.write(DOWN);

  DF_femur.write(UP);
  B_femur.write(UP);

  delay(DELAY);
};

void tri3() {
  AC_coxa.write(COXA_CCW);
  E_coxa.write(COXA_CW);

  DF_coxa.write(COXA_CCW);
  B_coxa.write(COXA_CW);

  delay(DELAY);
};
void tri4() {
  AC_femur.write(AC_UP);
  E_femur.write(UP);

  DF_femur.write(DOWN);
  B_femur.write(DOWN);

  delay(DELAY);
};


void b1() {
  AC_coxa.write(COXA_CCW);
  E_coxa.write(COXA_CW);

  DF_coxa.write(COXA_CCW);
  B_coxa.write(COXA_CW);

  delay(DELAY);
};
void b2() {
  AC_femur.write(AC_DOWN);
  E_femur.write(DOWN);

  DF_femur.write(UP);
  B_femur.write(UP);

  delay(DELAY);
};

void b3() {
  AC_coxa.write(COXA_CW);
  E_coxa.write(COXA_CCW);

  DF_coxa.write(COXA_CW);
  B_coxa.write(COXA_CCW);

  delay(DELAY);
};
void b4() {
  AC_femur.write(AC_UP);
  E_femur.write(UP);

  DF_femur.write(DOWN);
  B_femur.write(DOWN);

  delay(DELAY);
};


void l1() {
  AC_coxa.write(COXA_CCW);
  E_coxa.write(COXA_CCW);

  DF_coxa.write(COXA_CW);
  B_coxa.write(COXA_CW);

  delay(DELAY);
};
void l2() {
  AC_femur.write(DOWN);
  E_femur.write(DOWN);

  DF_femur.write(UP);
  B_femur.write(UP);

  delay(DELAY);
};

void l3() {
  AC_coxa.write(COXA_CW);
  E_coxa.write(COXA_CW);

  DF_coxa.write(COXA_CCW);
  B_coxa.write(COXA_CCW);

  delay(DELAY);
};
void l4() {
  AC_femur.write(UP);
  E_femur.write(UP);

  DF_femur.write(DOWN);
  B_femur.write(DOWN);

  delay(DELAY);
};



void r1() {
  AC_coxa.write(COXA_CW);
  E_coxa.write(COXA_CW);

  DF_coxa.write(COXA_CCW);
  B_coxa.write(COXA_CCW);

  delay(DELAY);
};
void r2() {
  AC_femur.write(DOWN);
  E_femur.write(DOWN);

  DF_femur.write(UP);
  B_femur.write(UP);

  delay(DELAY);
};

void r3() {
  AC_coxa.write(COXA_CCW);
  E_coxa.write(COXA_CCW);

  DF_coxa.write(COXA_CW);
  B_coxa.write(COXA_CW);

  delay(DELAY);
};
void r4() {
  AC_femur.write(UP);
  E_femur.write(UP);

  DF_femur.write(DOWN);
  B_femur.write(DOWN);

  delay(DELAY);
};


Step 12: 18 Servo Code

DSCN2352.JPG
See "12 servo" for notes on adjusting the constants. Code is at
https://github.com/nouyang/18-servo-hexapod/blob/master/pololu_aug17-2012.pde.
Explanation:
See documentation at  http://www.pololu.com/docs/0J40/5.ehttp://q.gs/4UGL6

a. BYTE is a parameter that pecifies the base (format) to us http://www.arduino.cc/en/Serial/Print

b. target is a non-negative integer less than 8192 (it can be written in binary notation with 14 or fewer digits)
e.g. 6000, or in binary: 01011101110000

c. 0x7F is 01111111 in binary, which infinity zeros to the left, so "&"ing (bitwise AND) it masks out all the digits in target (when target is written in binary) except the last 7 digits (only 1 AND 1 == 1. all other combinations == 0)
  01011101110000
& 00000001111111
= 00000001110000

d. right shift operator, shifts last seven digits (numbers 7 through 13) in target off into empty space and so now the "new" last seven digits were originally bits #0 to 6 (see color-coded pololu doc). Mask with 0x7F again, just to be sure.
  01011101110000,>>7 to:
  00000000101110, then:
& 00000001111111
= 00000000101110

The main difference from the default code is that I mapped the values so that I could mindlessly port code from arduino-"Servo.write()"-style to pololu-"settarget()"-style.

void settarget(unsigned char servo, unsigned int target)
{
  target = map(target, 0, 180, 2400, 9500);






My code, up on github, is also copied below:
======================
#include <SoftwareSerial.h>
#define txPin 2
#define rxPin 3

#define TIBIA 25
#define DELAY 150

#define CW 70
#define CCW 105

#define UP 92
#define DOWN 125

// ~~~~~~~~~~~~~~~~~~~~ //

#define A_COX 1
#define A_FEM 2
#define A_TIB 3

#define B_COX 4
#define B_FEM 5
#define B_TIB 6

#define C_COX 7
#define C_FEM 8
#define C_TIB 9

#define D_COX 10
#define D_FEM 11
#define D_TIB 12
#define E_COX 13
#define E_FEM 14
#define E_TIB 15

#define F_COX 16
#define F_FEM 17
#define F_TIB 18

int numtimes = 3;

SoftwareSerial mySerial(rxPin, txPin);

void setup()// run once, when the sketch starts
{
  mySerial.begin(9600);
  delay(1000);
}
void loop()
{
  for (int i=0; i<=4; i++){
    walkfwd();
  }
  for (int j=0; j<=4; j++){
    walkbwd();
  }
  for (int k=0; k<=2; k++){
    turnleft();
  }
  for (int l=0; l<=2; l++){
    turnright();
  }
}

void tibia() {
  settarget(A_TIB, TIBIA);
  settarget(B_TIB, TIBIA);
  settarget(C_TIB, TIBIA);
  settarget(D_TIB, TIBIA);
  settarget(E_TIB, TIBIA);
  settarget(F_TIB, TIBIA);
}


// ~~~~~~~~~~fwd~~~~~~~~~~ //

void f1() {
  // [COXA] changed
  settarget(A_COX, CW);
  settarget(C_COX, CW);
  settarget(E_COX, CCW);

  settarget(D_COX, CW);
  settarget(F_COX, CW);
  settarget(B_COX, CCW);

  delay(DELAY);
}

void f2() {
  // [FEMUR] changed
  settarget(A_FEM, DOWN);
  settarget(C_FEM, DOWN);
  settarget(E_FEM, DOWN);

  settarget(D_FEM, UP);
  settarget(F_FEM, UP);
  settarget(B_FEM, UP);

  delay(DELAY);
}

void f3() {
  // [COXA] changed
  settarget(A_COX, CCW);
  settarget(C_COX, CCW);
  settarget(E_COX, CW);

  settarget(D_COX, CCW);
  settarget(F_COX, CCW);
  settarget(B_COX, CW);

  delay(DELAY);
}

void f4() {
  // [FEMUR] changed
  settarget(A_FEM, UP);
  settarget(C_FEM, UP);
  settarget(E_FEM, UP);

  settarget(D_FEM, DOWN);
  settarget(F_FEM, DOWN);
  settarget(B_FEM, DOWN );

  delay(DELAY);
}

// ~~~~~~~~~bwd~~~~~~~~~~~ //


void b1() {
  // [COXA] changed
  settarget(A_COX, CCW);
  settarget(C_COX, CCW);
  settarget(E_COX, CW);

  settarget(D_COX, CCW);
  settarget(F_COX, CCW);
  settarget(B_COX, CW);

  delay(DELAY);
}

void b2() {
  // [FEMUR] changed
  settarget(A_FEM, DOWN);
  settarget(C_FEM, DOWN);
  settarget(E_FEM, DOWN);

  settarget(D_FEM, UP);
  settarget(F_FEM, UP);
  settarget(B_FEM, UP);

  delay(DELAY);
}

void b3() {
  // [COXA] changed
  settarget(A_COX, CW);
  settarget(C_COX, CW);
  settarget(E_COX, CCW);

  settarget(D_COX, CW);
  settarget(F_COX, CW);
  settarget(B_COX, CCW);

  delay(DELAY);
}

void b4() {
  // [FEMUR] changed
  settarget(A_FEM, UP);
  settarget(C_FEM, UP);
  settarget(E_FEM, UP);

  settarget(D_FEM, DOWN);
  settarget(F_FEM, DOWN);
  settarget(B_FEM, DOWN );

  delay(DELAY);
}

// ~~~~~~~~~left~~~~~~~~~~~ //

void l1() {
  // [COXA] changed
  settarget(A_COX, CCW);
  settarget(C_COX, CCW);
  settarget(E_COX, CCW);

  settarget(D_COX, CW);
  settarget(F_COX, CW);
  settarget(B_COX, CW);

  delay(DELAY);
}

void l2() {
  // [FEMUR] changed
  settarget(A_FEM, DOWN);
  settarget(C_FEM, DOWN);
  settarget(E_FEM, DOWN);

  settarget(D_FEM, UP);
  settarget(F_FEM, UP);
  settarget(B_FEM, UP);

  delay(DELAY);
}

void l3() {
  // [COXA] changed
  settarget(A_COX, CW);
  settarget(C_COX, CW);
  settarget(E_COX, CW);

  settarget(D_COX, CCW);
  settarget(F_COX, CCW);
  settarget(B_COX, CCW);

  delay(DELAY);
}

void l4() {
  // [FEMUR] changed
  settarget(A_FEM, UP);
  settarget(C_FEM, UP);
  settarget(E_FEM, UP);

  settarget(D_FEM, DOWN);
  settarget(F_FEM, DOWN);
  settarget(B_FEM, DOWN );

  delay(DELAY);
}

// ~~~~~~~~~right~~~~~~~~~~~ //

void r1() {
  // [COXA] changed
  settarget(A_COX, CW);
  settarget(C_COX, CW);
  settarget(E_COX, CW);

  settarget(D_COX, CCW);
  settarget(F_COX, CCW);
  settarget(B_COX, CCW);

  delay(DELAY);
}

void r2() {
  // [FEMUR] changed
  settarget(A_FEM, DOWN);
  settarget(C_FEM, DOWN);
  settarget(E_FEM, DOWN);

  settarget(D_FEM, UP);
  settarget(F_FEM, UP);
  settarget(B_FEM, UP);

  delay(DELAY);
}

void r3() {
  // [COXA] changed
  settarget(A_COX, CCW);
  settarget(C_COX, CCW);
  settarget(E_COX, CCW);

  settarget(D_COX, CW);
  settarget(F_COX, CW);
  settarget(B_COX, CW);

  delay(DELAY);
}

void r4() {
  // [FEMUR] changed
  settarget(A_FEM, UP);
  settarget(C_FEM, UP);
  settarget(E_FEM, UP);

  settarget(D_FEM, DOWN);
  settarget(F_FEM, DOWN);
  settarget(B_FEM, DOWN );

  delay(DELAY);
}

// ~~~~~~~~~~~~~~~~~~~~ //


void walkbwd() {
  tibia();
  b1();
  b2();
  b3();
  b4();
}
void walkfwd() {
  tibia();
  f1();
  f2();
  f3();
  f4();
}

void turnleft() {
  tibia();
  l1();
  l2();
  l3();
  l4();
}

void turnright() {
  tibia();
  r1();
  r2();
  r3();
  r4();
}

// ~~~~~~~~~~~~~~~~~~~~ //


//Send a Set Target command to the Maestro.
//Target is in units of quarter microseconds, so the normal range is 4000 to 8000.
void settarget(unsigned char servo, unsigned int target)
{
  target = map(target, 0, 180, 2400, 9500);
  mySerial.write(0xAA); //start byte
  mySerial.write(0x0C) ; //device id
  mySerial.write(0x04); //command number
  mySerial.write(servo); //servo number
  mySerial.write(target & 0x7F);
  mySerial.write((target >> 7) & 0x7F);
}

Step 13: Further Reading

Congratulations on finishing! Now pose for a picture with your hexapod as it dances! :D

Further Reading
This whole hexapod shenanigans was inspired by youtube: dancing hexapods...



For more hexapod craziness, see my (a bit elderly) gallery here of ones around the internet here: http://orangenarwhals.blogspot.com/2011/07/strange-and-beautiful-hexapods-spider.html.

See also academic papers a reading group I led read here: http://ieee.scripts.mit.edu/urgewiki/index.php?title=S2012_-_Hexapods_and_Other_Cool_Things

An overview of the design process and other things I'd like to implement as of two years ago: http://orangenarwhals.blogspot.com/2011/05/dreaming-of-dancing-hexapods-2007.html
A video explaining the design process in 7 minutes (this instructables goes into the construction but not the design): http://youtu.be/qTh-OGA_LeM



Gaits
Tripod gaits are the simplest. Go out and read more about the different gaits out there. For instance, can you figure out which gait Stompy is using and why?

Forums
There are also many people on the forums, in particular:
pololu e.g. (http://forum.pololu.com/viewtopic.php?f=16&t=6188&p=29520#p29520)
trossenrobotics
lynxmotion
arcbotics e.g. (http://forum.arcbotics.com/viewtopic.php?f=12&t=386)

Hexacon
hexacon2013.mit.edu
On May 4th, 2013, the world's first hexapod conference was held at MIT. If I'm still around MIT in a year or so (I just graduated and am working on my own startup), there will be a second one (or do any of you all want to organize one? Let me know!).

===
Read more ...

Autonomous Directional Rotary Artificial Intelligence Navigational System (ADRAINS)

Friday, 28 June 2013

Autonomous Directional Rotary Artificial
Intelligence Navigational System
(ADRAINS) 

This project was done by 

David Drew djd36, Joanna Dai jxd2



Introduction

Our final project in ECE 476 is a mobile robot with a developed neural network such that it evolves to avoid collisions into a circular vertical white wall while traveling at the fastest speed and straightest line possible without human intervention or external computers.

The completion of this project required extensive capacity and application on both hardware and software ends. In constructing the robot, we needed to build the custom prototype board, apply infrared sensors as neural inputs, implement stepper motors for robot motion, and provide a mobile power supply to the MCU. The purpose of these design factors is to allow the autonomous movement of the robot while minimizing the size of our robot, to accurately sense distance and collisions into the white wall of our arena, and to calculate the velocity precisely while providing sustainable torque to move our robot. On the software end, we needed to execute an evolutionary spiking neurons algorithm that interfaced with our hardware. The purpose of this was to integrate a spiking neural model with infrared sensors as inputs and motor speeds as outputs to determine robot velocity and direction. We also implemented the evolutionary model based on assessing random individuals of a randomly generated population through a fitness equation and improving the population by discarding the worst individual in the population with the worst fitness. The fitness equation measured by the velocity of the robot, the direction change, and the amount of activity from sensors.

High Level Design


Rationale and Sources


While our partnership coalesced from our strong interest in projectile devices and their implementation through use of microcontrollers, which is why we signed up the class initially, we were unfortunately denied of such opportunity as a final project.

Nonetheless, we wanted our final project to be a valuable challenge that tested our comprehensive knowledge of electrical and computer engineering. We wanted a final product that was significant to society, unique to the course, beyond the scope of previous labs, unprecedented by previous final projects, and realistic to our abilities.

Therefore, after consulting with TA Idan Beck as well as previous and current students of this course, we decided to build this robot with a genetic evolutionary algorithm.After speaking with Professor Land and previous students of the course regarding the feasibility of our project and workload involved, we formed the following objective:
  • To produce a mobile robot with the neural network that autonomously avoids collisions into the white wall of a circular arena.

 

Background Math


We had no complicated derivations or integrations were involved in the calculations of this project. The math background needed for this project was geometry for stepper motor calculations, conversions to calculate velocity, timers, analog to digital, measurement units, algebra for analysis of the fitness equation and characterization of polynomials for infrared sensor behavior, and probability to analyze infrared sensor data, their polynomial characteristic equations, and corresponding least squares approximation and residuals.



Logical Structure

We used 8 AA batteries at 1.5V each as our mobile power supply. The 12V powers the Mega32, the Darlington arrays, and the stepper motors. The stepper motors are controlled by the Mega32 through Port C. The infrared sensors are powered at 5V and go through the ADC through Port A.



Hardware/Software Tradeoffs

After consulting with Bruce on possible approaches of neural theory, hardware, and software considerations for our neural network robot, we came across one significant hardware-to-software tradeoffs. Bruce introduced to us Hopfield and Hebbian neuron spiking networks, both of which require nonlinear amplifiers to simulate the analog inputs and spiking. He also referred us to Dario Floreano’s paper, “Evolutionary Bits’n’Spikes” (see References), that described a spiking neuron model algorithm based on incoming spike contributions to the connectivity of the network and an evolutionary model improving its population based on a simple fitness equation of speed, amount of collisions, and amount of infrared sensor activity.

Between implementing the spiking neural network on hardware or software, we chose software. We felt that hardware would take more tedious manual labor, be harder to debug, and create extra weight on our robot. With software, we can compile and debugged efficiently on CodeVisionAVR and have flexibility in adding and modifying our code.

Standards and Patents

We had no patents to adhere to in this project.

In terms of patents, lab TA Idan Beck gave us “Artificial Life IV”, a book with compilations of published papers on artificial intelligence and genetic algorithms. We got our idea of building an autonomous evolutionary robot from there, particularly the copyrighted Khepera robot. However, Floreano’s experiment and paper provided such substantial background on neural and evolutionary models for our project plan that we significantly based our final project off of his work. In adhereing to intellectual property, we exchanged emails for Floreano regarding permission to use the content described in his paper and contacted him for help when we came across a road block regarding initialization of our robot (see Intellectual Property Considerations).

 

Hardware

 Components required for our hardware design:
  • Structural design
    • Wood base, wooden wheels, free rotating wheels, foam core
  • Custom prototype board and Atmel Mega32 microcontroller unit
  • Stepper motors and Darlington arrays
  • Infrared light-emitting diode and phototransistor
  • Power supply

 

Structural Design

The design of the robot was circular for the purpose of better interaction with our software in developing the neural network. The symmetry of this design allows the robot to detect infrared sensor inputs with more accuracy when approaching the wall and more balance between the left and right sides, in comparison to square or rectangular. The circular shape gives better turning radius in our arena, especially when the robot is in close proximity to the wall and needs to make a quick turn.

A mechanical challenge for the design was finding all the suitable parts to assemble the rectangular base of our robot. We used two wooden wheels that attached perfectly to the stepper motors. Two more free rotating wheels were used to keep balance and permit mobility of the robot. We used L-brackets, nuts, bolts, and spacers to install the stepper motors onto the wood base.
To minimize the robot’s size, we created two layers: one for the power supply and one for the circuit boards. Thus, the foam core circles were no larger in diameter than the protoboard and solder board side by side.

 

Prototype board and Atmel Mega 32

The protoboard was assembled by following the instructions on the protoboard design page. Because the robot was running off batteris and not the AC power supply, we connected our 12V battery supply directly to the power ports on the protoboard. To program the Mega32, the 6-pin programming port was used throughout this project. Pins D.0 and D.1 of our Mega32 were used to display values through hyperterm.
The Port C pins were used to connect the Mega32 to the input of the Darlington arrays for the stepper motors. Port A was used to converting the analog voltage from the infrared sensors to the digital signals as inputs for our program. The remaining available pints on the protoboard were used to lay out the infrared sensor circuits.

Stepper Motors

The robot used PF35T-48L4 stepper motors to move forward, backward, or turn. The reason we chose stepper motors is because they offer precise control over the speed and direction of the robot. Because velocity of each wheel is an output of the neural network and is used to compute the fitness equation of each individual in our evolutionary model, the stepper motors’ accuracy was a key factor in our hardware design.
Stepper motors move at 7.5 degrees/step with one step at a time because each step in the motor draws 500mA at a time. To calculate the velocity of our robot, we know that 360/7.5 gives us 48 steps/rev. The diameter of our wheel is 2 inches, so (2 inch * 2.54 cm/ in * pi / 2) = 15.9593 cm/rev. Velocity is then 15.9593 cm / rev * 1/48 rev / (t2 / 1000 s) = .332485 cm / (t2/1000 s). Therefore, we could control the velocity of our robot by determining the value of t2.
The red and green wires of our stepper motor are connected to our 12V battery supply. We connected left motor pins C.0-C.3 to orange, yellow, brown, black, respectively. Likewise, we connected right motor pins C.4-C.7 in the same color order.
We needed to use Darlington arrays to connect the Port C pins as inputs from the Mega32 into the ULN2003AN and the outputs to the stepper motors. Darlington arrays output high voltage with common-cathode clamp diodes for switching inductive loads. Each pair operates at 500mA, which matches the current of each step in a stepper motor.

Infrared Sensors

For our distance sensing, we paired Infrared Phototransistor QSE113-ND with Plastic Infrared Light Emitting Diode QEE122. The sidelooker infrared sensors operate at 880nm. We connected the node at the collector of the phototransistor to Port A for the analog to digital conversion.
After testing the infrared LED and transistor on white breadboards, we found that we got optimal distance sensing when the two components were placed as closely to each other as possible. In assessing the infrared sensors' impact to the input of the neural robot, we created four ranges based on the characteristic curve of the IR sensors. We saw that there was a constant .01V drop from >25cm and characterized that as the top range. From 15cm to 25cm, we see a more drastic voltage drop and call that our middle range. From 15cm to 5cm, we see a steeper drop and call that our bottom range. Anything less than 5cm is defined as collision.
The graph below is a Matlab plot of our data. We used the function polyval() to approximate a polynomial equation. Below, red was n=3 polynomials, black was n=4 polynomials, and green was n=5 polynomials. We also used the function polyfit() to find the difference between each curve and our data. This created the residuals for each function and helped us determine the most accurate curve.

While this method gave very precise characterizations of our sensors' behavior, we found that the infrared sensors were very sensitive to room temperature and any slight difference in setup. Therefore, in the software we needed to take values the day of and use the robot in accordance to the room temperature that day.

Power Supply

For our power supply, we used 8 Duracell AA batteries connected in series. We needed to use lighter batteries to keep our robot weight down while allowing it to move freely. Another hardware concern for the batteries is drawing amps for the motor so that the voltage stays at approximately 12V and the does not drop. Because each motor step draws 500mA and there are two motors, we used AA batteries because we knew they supply voltage at 1.00A. The batteries are attached to the second layer of our robot, shooting 12V to the power ports on the protoboard and to the motors.

Software

Our software was based on a time based scheduler scheme with time-critical functions in an Interrupt Service Routine. The design was based of a paper called Evolutionary Bits’n’Spikes by Dario Floreano, Nicolas Schoeni, Gilles Caprari, and Jesper Blynel. The paper contained details on how to implement a spiking neural network. We split our program into six tasks with one ISR. The ISR was a timer0 compare match ISR set to occur every ms. Its sole purpose was to update task timers to keep them at millisecond accuracy. The first task was the neuron implementation which updated the neural network every 2 ms. The next two tasks controlled the left and right stepper motors, each causing the motor to step once. The task timers for these were variable based on the neural network’s spikes. The fourth task, which was executed every 20 ms, read the infrared sensors and updated the speed of the motors by changing the task timers. This task also calculated and accumulated fitness values. Our fifth task evolved the neural network based on the fitness accumulated in task 4 and was executed every 14s. Our final task was executed every 3 minutes and updated the EEPROM with the current population so that the robot could be turned off without losing what it learned.
The neural network was created to be fast, efficient, and cheap. Each run, a population would be created of 6 individuals. Each individual would have certain variables assigned to it. First is the variable signs, which gave the sign of the neurons for that individual. The variable is one byte long, with each bit corresponding to one neuron. The individual would also have a byte of nconnection for each neuron (for a total of eight bytes) which describes the connections from neurons to the one neuron. Likewise, the individual has a int of sconnection for each neuron to describe the connections from sensors to that neuron. This could not be fit into a byte due to the large amount of sensors we used. The individual also has its own threshold value. We decided that it would be best to make the threshold value a part of the evolution, instead of setting it to a certain value, since the way certain networks evolve may benefit more from one threshold then another. Finally, each individual had a fitness value. The neural network itself had variables to hold the current individual, a byte for the state of each neuron called neuron (spiking or not spiking), an int for the state of each sensor called sensor, and the membrane potential of all neurons called memb. To allow for easy program change, the majority of these variables are based on defines numindivid, numsensor, and numneuron. Any change to these will cause variables to adapt to the new number, though there are a few exceptions where additional lines of code may need to be added, especially if going above the amount of bits that a variable type can fit.
Our first task is the neural network, which operates on one individual at a time. This individual remains the same until 14 seconds when a new individual is randomly selected and mutated. The network implementation begins with checking to see if the sensors have been updated. If not, the sensor variable is set to zero. This allows the network to keep generating internal spikes without having to wait for new sensor input. The network then checks to see if the individual has just been evolved; if so it spikes the two neurons associated with forward motion of the left and right motors. Next the Refractory Period is taken into account. If a neuron spiked in the last run through of this task, it does not update its membrane potion or count the contribution of incoming spikes; it moves immediately to spike generation. For neurons not in the refractory period, the membrane potential is updated by calculating the contribution of spikes from other neurons/sensors. The fact that sensor/sconnection and neuron/nconnection/sign are all the same length allows these values to be ANDed together and bit masked/shifted to calculate the appropriate contribution to memb. Each spike adds/subtracts 1 to memb. After updating the membrane potential, the software then checks to see if the potential is greater than the threshold. We generate a random number between -2 and 2 to add to the threshold to prevent locked oscillations. If the neuron is spiking, we flip the corresponding bit in the variable neuron and reset the potential to zero, otherwise we clear the corresponding bit. We next check to see if any of the motor neurons are spiking, if this is the case we increment our variables to count the number of spikes per motor update cycle. Finally, we allow for leakage in the network by decrementing all the membrane potentials.
The second task controls the left motor. Our stepper motors move 7.5º per step, so there are 48 steps / revolution. The circumference of our wheels is 15.9593 cm so our velocity equals 15.9593 cm / rev * (1/48 rev)/(t2 ms / 1000 (ms/s)), where t2 is the task timer for task 2. This equation ends up being velocity = .332485 cm/(t2/1000 s) where t2 is in milliseconds. Knowing this, we can control our speed to a very fine precision by just changing t2, which occurs in task 4. The stepper motors are hooked up to PORTC and each step sets a different bit between C.0 to C.3 to step the motor. The motor can be stepped either forwards or backwards which gives us absolute control. The third task is almost identical to the second, except that it uses C.4 through C.7 and the order it goes through is different because the motor is facing a different direction. The variable DirL/DirR controls the direction and the way stepL/stepR is incremented or decremented.
The fourth task was one of our most complicated ones. Sensor updating was a long and complicated process, as was motor control and fitness calculation. Our sensor updating scheme begins with setting collision to zero to indicate that there are no collisions, which is an important factor in fitness calculation. We read all sensors through the ADC, and we used 10 bit precision instead of 8. Voltage is calculated using floating point variables by multiplying the ADC input by the reference voltage, 5V, and dividing by 2 number of bits. We change ADMUX after each read, and wait until the ADSC bit is set before reading the next value. Each IR sensor has a unique characteristic, and as a result it became important for us to constantly test the sensors to see how the distance-voltage values change. We use defines at the top to measure certain range values. The values we chose is nothing (sensorxnot), 25mm (sensorxtop), 15mm (sensorxmid), 10mm (sensorxbot), and 0mm (sensorxcol). We have if statements for each sensor that will set between 0 and 3 sensor bits in the variable sensorread, which will become sensor once all sensors have been read. In addition, the variable sensoractivity is changed for each sensor so we can calculate the sensor with the highest activity for fitness purposes. The variable collision is also set if any sensors are or below the value for 0 mm. Finally, to allow some sensors to spike even with nothing in range to keep the network going, we add a noise factor. If the range is between sensorxnot and sensorxtop, which would normally spike no sensors, then we calculate a probability and random value based on that range and if the value is less then the probability we set a bit anyway. We repeat this process for each sensor.
At the end, we also calculate an additional ‘sensor’ which is an open ADC socket. This is used because of the rand() function. This function gave us a lot of trouble throughout the project. Because of its pseudorandom nature, it was generating the same networks and same values again and again, making our network very hard to mature. A neural network requires many very random values to function. Initially, we tried to add up all the ADC inputs and average them, and use that value as the seed. This turned out not to be random enough, so we found a better solution. Since the final sensor doesn’t have anything in its port, it is just noise. We predict where that noise will lie and then we use the same probability calculation as before by taking into account how far into the range of noise it lies. We then amplify this probability by 1000, making the noisiest part of the number more prominent, and use that as a new random seed. This turned out to be much more effective for all cases except initialization, where the average ADC values proved better.
After examining all sensors including the noise sensor, we calculate the sensor with the most activity for the fitness equation. Lastly, for an additional amount of randomness, we add even more sensor noise by using the random seed calculated by the noise sensor to potentially set a few more bits in sensorread. At the next execution of task1, sensor will take on sensorread instead of 0.

The next part of task 4 is to update the motor speed. We initially did this every 20 ms along with sensor updating, but we found that this did not allow enough time for the motors to even execute if their timers were constantly being updated. It usually ended up being updated with 0 velocity which would max the amount of time the motors would take to step. To fix this problem, we added another counter to the motor algorithm. This counter was set at the end of the motor update to the highest of the two task timers, as long as they were less than 350 ms which is approximately 1 cm/s, otherwise we settled on 60 ms which was slightly more than 5.5 cm/s. The motor speed calculation involved using the spikes in the last 20 ms to calculate speed. If forward spikes where higher than backward spikes, the motor moved forward and velocity was equal to the different over the total number of spikes multiplied by 8, the max value of velocity (8 cm/s). If forward spikes were smaller than backward spikes, then the motors moved backward and velocity was calculated the same way except with forward spikes being subtracted from backward spikes. After velocity was calculated, we used the velocity equation given above to calculate the task timer. We did this separately for both the left and right motors. If either motors direction variable was backwards, then fitness would be set to zero.
The final part of task 4 was the fitness calculation/accumulation. If either wheel was moving backwards or if there was a collision, there was nothing added to the fitness. Otherwise, the fitness = fitness + (v2 + v3)*(1.0-((v2-v3)/8.0))*(1.0 – sensormaxactivity)). V2 and v3 are the absolute value of velocities of the left and right wheels respectively. We divide by 8.0 in the second part because that is the max speed. Finally, sensormaxactivity is a value between 0 and 1 (0 nothing in sight, 1 a collision). So, fitness will be maximized when v2 and v3 are largest, but equal, and there are no walls in sight.
Task5 performs the evolution, selection, and mutation of individuals. It begins by calculating the worst individual, in terms of fitness, in the population. It then checks if the new fitness calculated in task4 is greater than or equal to the worst individuals fitness and greater than or equal to its former values fitness. If so, the new fitness is kept along with the signs, sconnection, nconnection, and threshold that belong to it. Otherwise, the old values are restored. Next, an individual is picked at random and its signs, sconnection, nconnection, and threshold are backed up in variables. The task then mutates the current values by XORing a 1 with a randomly selected bit in each of the four variables. However, since changing the threshold can have such a drastic effect, and the threshold has to be kept between 2 and 6, if the mutation doesn’t happen to occur within that range, the threshold value is not changed. At the end, the task sends a message to task1 telling it that an evolution has occurred.
The final task handles the EEPROM for this program. In case of power loss or if you want to take a break, it is best to allow the population to not be destroyed when the power goes off. As a result, every 3 minutes, the current population is stores in EEPROM. In addition, if a member of the current population is the highest fitness value so far, it is saved in EEPROM as well to keep track of the best individuals.
The rest of our program is fairly standard. Main simply calls tasks when their task timers are at 0. Initialize makes PORTC the output for motors. Timer0 is initialized to be a 1 ms timer, by setting OCR0 to 250, and TCCR0 to 0b00001011 which sets the prescaler to 64 and enables CTC. Hyperterm is set up for debugging. The ADC is set to use AVCC, be left aligned, start at ADC0, but not begin the first conversion. A random seed in then generated by averaging all the current sensor values. This is necessary since the next step is to randomly initialize the majority of the neural network variables. If the robot has already built some of its network, EEPROM will replace the randomly generated values. All variables used in the various tasks are then intializied. Finally, if the robots network is partially built, the EEPROM will output the best individual and all the best fitness values. This allows us to examine what a good network looks like and see the progression of fitness.
Programming this project proved to be quite a challenge. While the initial coding was simple, it became much more complex as testing began. There were a few very large hurdles to get over. The first was just understanding how the network worked. There were several problems with this. The first was it was not very explicit how to begin the spiking of the network if we just placed it in a random area. We decided upon spiking the forward neurons to start it off but it is likely that there is a more accurate method. We have yet to achieve an evolution where when there are no sensor values the robot moves as fast and forward as possible, though we are getting closer. In an attempt to find a solution, we e-mailed one one of the main writers on the paper, Dario Floreano, who told us he used the noise of his ADC to induce random spikes. Amazingly though, our ADC was almost completely noiseless even with 10 bit precision. We settled on sampling noise but there is probably still a better way. Another issue was relating the neurons to the motors. There were many different ways to do this and it took a lot of additional research into papers to find the forward/backward spike method. Sensor updating was another problem, when and how to trigger the neurons was a huge issue, especially with the inconsistent readings of the IR sensors.
Another issue was the randomly generated number. It took us a very long time to figure out how to get a number that appeared good enough, and even at this point we can’t be sure it isn’t repeating as there are thousands of randomly generated numbers per second, and not nearly enough seeds. It is possible our network is going in mini loops because of the same numbers being randomly generated again and again. This is a huge problem as neural networks are so reliant on randomness.
One large issue was debugging. It is very hard to figure out if your network is working, especially before the robot is even built. We used printf statements throughout the entire program to see what values our variables were taking. Initially, we couldn’t gain any fitness and it took a lot of debugging to begin improving. We would examine neuron and sensor to see what was spiking, we would count the number of spikes to try and see if some spikes just weren’t coming. We even used the timing of the motors to try and find out how to make the robot move smoother and quicker without raising velocity limits or permanently setting the step time. At times we had to induce spikes to get things going to see how neurons would react and if internal spikes could be kept going. It is still a bit hard to understand how a few spikes can turn into many. When actually examining the robot, it is still hard to tell the difference between success and luck. Part of this is because the robot keeps switching individuals so it is hard to tell if you have a highly evolved individual or a weak one. Sometimes, the robot will stare at the wall moving closer and then moving back, slightly to the right or left, for a long period of time without hitting or gain any progress in a different direction. In other cases, the robot would suddenly 180 and start going the opposite direction. This is the hardest part about neural networks, they are unpredictable and each one could be completely different from the next.
Overall, the coding has really come together at the end and from tweaking we have been able to achieve higher fitness values at a faster rate. Through good commenting and debugging, the code is very clear and it is easy for anyone to begin debugging simply by searching for debug. Tasks help organize code and a sample main and ISR make everything more modular and easier to handle one by one.

 

Results

Our results ended up being pretty good. Though there was a little luck with what your starting population would be, the robot would usually begin moving quickly. Because of the stepper motors task times constantly being updated, the movement appears jerky though it is actually very exact. The speed of learning is a little longer than we expected, but it is clear there is learning going on. The design was very safe with the arena and foam board. Anyone can use it, just power it up and it begins learning. There is no interference as far as we know, though the temperature of the room may be effecting our IR sensors.
We were able to use eeprom to get results for one of our best individuals so far. Below is that individual’s network. It is interesting to note that the Forward Right neuron and FL neuron’s only exciter is the FL neuron, and that the majority of neurons are inhibitory. With so much inhibiting, it is hard to get going fast but really easy to avoid walls. This was reflected in the robots movement, it was very inhibitory but avoided walls well. Sensor networks are a little more unclear but the robot spent a long time with its right side towards a wall so it makes sense that there are more neurons connected in that area.

Here is the fitness curve for that population:

 

Conclusion

We are very happy with the way our robot came out. We found the topic very interesting but challenging. Understanding a neural network is not a simple task, but we both now feel we have a much better idea of how machines can learn and evolve. Overall we are very proud of how far our robot has come. We do feel there may be a better way to cause forward motor spikes to continue so that it can move a long distance over time, and with some tweaking we could probably achieve that. Nonetheless, we are happy to say our robot is completely unpredictable, but is clearly is abiding by its fitness. There are times where you can really see it hesitate near a wall and move away from hitting it. Learning has taken a little longer than we expected, probably due to the fact that the paper we modeled this off of used less bits and ran the network slightly faster. We wish we could have had more time just to let it go and see where the network went. We are sure there are network genetic strings we haven’t even come close to that can achieve fantastic performance. We also would have liked to spend some more time tweaking, and could we do it again, we would have finished building the hardware much faster since testing on the STK500 was not at all comparable to testing with an actual robot. There are so many small tweaks to the network that can have a large impact on learning; it would be great to have more time to test it all. Another problem was the IR sensors; we wish we could have found a way to make them more accurate and consistent. We also think it would have been very cool to take things a step further and have two robots with the same algorithm, so they can avoid each other and the wall.
We believe our design conformed to standards. We used all the hardware within the appropriate voltage ranges, and used the RS-232 protocol properly. We also attempted to follow, within the best of our ability, the standard of a genetic algorithm and how it is meant to be run.

 

Intellectual Property Considerations

In terms of intellectual property, we used the implementation of the neural network described in the paper ‘Evolutionary Bits’n’Spikes’. While we did modify the implementation to suit our purposes, as well as add a few special touches such as evolving the threshold, it really helped guide us along the way. To make sure there were no issues (and ask for advice), we contacted the writer of the paper we were using, Dario Floreano.
He had this to say:
We are happy to say we are locked oscillation free.

 

Ethical Considerations

We believe we were consistent with the IEEE code of ethics to the best of our ability. We made sure to contact the creator of the algorithms we used to avoid any damage. We did not attempt to fabricate data or fitness values of any kind to make our project look more effective. We did a large amount of research before this project to ensure it we had the understanding to complete it safely. To create a safe environment around our robot, we built it with a soft, circular design that ensures that no collision related accidents will occur. The arena further protects this since the robot will not be able to pass the walls (though one would hope it would avoid them entirely). We did not attempt to anything beyond the level we could safely handle, and contacted TAs or Bruce if we had a situation we were unsure about. We strongly desired the advice and criticism that Professor Floreano, Bruce, and the TAs had to offer, and without them we would not have been able to complete our project as well as we did. And of course, whenever we had the moment, we took the time to help others around us who may have been struggling with their project. At no point did we accept any bribes, or attempt to damage any other projects for our own personal gain.

 

Legal Considerations

There were no legal considerations in terms of the hardware for this project. For the software, we received the written permission of the designer of the algorithm we used.

 

Appendix

Link to fully commented code: robot.c
Link to stepper motor schematics
Link to infrared sensor schematics

Project Costs


Tasks

Researching - David and Joanna
Buying Parts - David and Joanna
Hardware - David and Joanna
Software - David
Website - Joanna
Testing - David and Joanna
Mechanical - Joanna
Documentation - David and Joanna

References

Datasheet for Infrared Phototransistor
Datasheet for Infrared Light Emitting Diode
Datasheet for ULN2003AN Darlington Array

Vendor websites: Digikey

Link to borrowed design: Khepera

Links to published papers: Evolutionary Bits'n'Spikes, Evolution of Spiking Neural Circuits in Autonomous Mobile Robots

Acknowledgements

Shout outs/Acknowledgements to the following people: Bruce Land for creating an awesome course. Dario Floreano for making such strides in evolutionary robotics and for helping us. All the TAs who advised all of our problems, big and small, and putting up with our harassment to keep the lab open longer. Wanling for helping us buy parts that one day and picking up a call from Bruce when Dave was running late and Joanna forgot her cell phone. Finally, Jack from Lowes for having the vision and a very nice saw.




Read more ...