Ball Bot

Keep a log of a project build here. Be sure to include pictures and as much documentation as possible.

Ball Bot

Postby machinelou » April 3rd, 2011, 9:15 pm

Hi... I'm trying to build a computer-controlled ball bot. I've been fascinated by these things for a while now. The body consists of a fairly large (~12") plastic rodent ball obtained from a local pet store. It has an internal frame made from wood and is supported by a bearing on one side and drill-bit / drill on the other. I'm planning on controlling the bot with an arduino + zigbee wireless adapter. Locomotion will be provided by a cheap black n' decker drill I've built into the frame -- the idea is that the internal frame will contain enough mass such that the energy required to rotate the body is less than that required to rotate the frame. Turning will be accomplished by a servo located at the center of the frame that causes a weight to lean left or right, which will in turn cause the body to lean left or right.

To give you a sense of my skills and experience, this is my first project that didn't involve anything other than just reading/sending IO over a zigbee or turning a servo. I have had very little success doing anything other than connecting a battery directly to a pre-built development board so, I expect motor control to be a challenge.

At the moment, there is only the body, the internal frame, and the motor. I need to get a motor driver to control the speed/direction of the motor using the arduino. Then, I'll connect the arduino + zigbee (which will probably also involve adding a separate battery package) and then figure out how to mount the servo/weight to control steering.

Here's the motor controller I'm considering: http://www.pololu.com/catalog/product/713
I'll probably use both channels for the one motor. The motor itself runs off of 4 AA batteries. I did some tests with a multimeter and could only get it to pull slightly less than an amp when trying to stop the drill with my hand. I'm trying to do this on a budget so I'd prefer not to just buy a shield or two. I already had the zigbees and arduino from another project.

Enough babbling, here are some pics. It's leaning to the left in the picture because I haven't tried to center it yet -- I'll do that after I finish added the rest of the components.

photo 1.JPG
The ball.
(90.91 KiB) Downloaded 2485 times

photo 2.JPG
Left side, frame / motor / batteries, right side
(53.6 KiB) Downloaded 2485 times

BBC ballbot.JPG
BBC Ballbot -- this is the guy that most recently inspired this project.
(21.2 KiB) Downloaded 2406 times

And, the obligatory XKCD reference: http://xkcd.com/413/

Edit: I just found this guy's project: http://www.photopete.com/swarm/hamsterball.htm
His design is certainly cleaner than mine so it'll make a good reference.
Last edited by machinelou on April 9th, 2011, 4:33 pm, edited 3 times in total.
machinelou
 
Posts: 21
Joined: April 3rd, 2011, 6:42 pm

Re: Ball Bot

Postby machinelou » April 9th, 2011, 3:22 pm

What's the preferred way of updating this logs: editing the original post, or posting updates beneath it?

So, I received my pololu motor controller this Wednesday and was able to hook everything up this morning. I'm using both channels of the controller for just the one motor. Because I don't have the zigbee in yet, it just waits 30 s, rolls forward for 4 s, waits 2 s, rolls backwards for 4 s, then sleeps for another 30 s. This configuration is just to make sure it works; think proof of concept. Pics and a video are below.

IMG_4930_web.jpg
Ball bot frame with arduino, motor controller, and batteries.
(83.39 KiB) Downloaded 2409 times

video: http://www.youtube.com/watch?v=Vuj8q_J-eRM

Todo:
Redesign the frame
Given that the current frame is made out of hardwood, pine, and hot glue, it's tough to move the center of mass to the horizontal center. You can see in the video that the tilt causes it to roll in an arc. It'll be easier to make controlled turns if I can get the center of mass right so the next thing on the todo list is to improve the frame and incorporate some way to adjust the center of mass.

The final frame will also have to incorporate some way to move the center of mass around automatically. I'd like the bot to be able to do this quickly, so I was thinking of just putting a small weight on the end of a servo arm. But, maybe I can figure out some frame design that would allow the bot to automatically adjust it's center of mass and use that same process to make controlled adjustments in order to turn. This might be too large a step forward for the next iteration.

Add Wireless
A wireless link will be necessary to do any reasonable debugging from here on out. I'm leaning toward setting up a serial link over zigbee right now because I've used it before and I have two spare adapters.

Add motion feedback
As you might be able to see from the video, when the motor initially starts turning, the internal frame rotates first. Once the frame approaches horizontal, the ball begins to roll and the frame returns to a vertical position. Getting the ball to move forward reasonably actually took some trial and error. Initially, I set the PWM too high and the frame mostly just spun in place. It might be possible to use an accelerometer (and maybe an encoder?) to set PWM in such a way as to accelerate quickly without simply spinning the internal frame. A two-axis accelerometer might be useful for allowing the bot to control side-to-side tilt by changing center-of-mass. Either one of this might necessitate some kind of PID -- a topic I've read about but haven't ever tried.

Thoughts on navigation sensors
Bots like these have trouble using traditional sensor methods like switches, IR, or ultrasound because the external skin of the bot is free to move in one axis relative to the electronics and the environment. It might be possible to attach IR sensors to the frame and detect distances to outside objects but, given that the frame rotates, it might be challenging to obtain horizontal distance (assuming IR can even transmit both ways through the skin. A very rudimentary collision detector might be obtained by filtering for large spikes in the accelerometer data or by using piezo sensors attached to the frame. Video might be another possibility. A recent BBC documentary about polar bears used a ball bot with a video camera facing out the side (where the motor and bearings are located on my bot). Engineering a smooth point of rotation that can accommodate a wireless camera might be beyond my skill but perhaps it's something to keep in mind. A forward-facing camera would be easier to mount but might not be able to see through the skin enough to be worth it.

Anyway, that's all I got right now. Hopefully I'll have an improved frame soon and more concrete ideas about control and sensors. Thanks for reading.
machinelou
 
Posts: 21
Joined: April 3rd, 2011, 6:42 pm

Re: Ball Bot

Postby machinelou » April 17th, 2011, 4:40 pm

Small update: I worked on the horizontal frame piece.

The new piece consists of two angled pieces of aluminum 1" by 1" by 9" glued to a 1/8" piece of hardboard (with white gorilla glue). The pieces are arranged facing each other to create a 1" by 2" channel. By sheer luck, the motor fit perfectly between the two pieces. Right now it's affixed with zip ties as can be seen in the pictures below but those will be replaced with something a little cleaner soon.

The freely-rotating side of the piece is made out of 1/2" HDPE (high-density polyethylene). Our local Borders is going out of business and has been selling everything include shelves and computer for pretty good prices. I picked up a pretty decent sized HDPE cutting board for $2.50 and cut out a 1"-square piece with my jigsaw. The bottom 1/2" of the HDPE is about 1/16" fatter on both sides. I cut it that way intentionally so downward loads on the piece would be less likely to pull the HDPE straight out of the aluminum channel. Then I glued (gorilla glue again) the material to the aluminum and screwed machine screws into it just to be sure it was secure (overkill probably).

The bolt going through it is 5/16" thick -- which is a perfect with the bearing attached to the inside of the ball. Given my lack of a drill press, I was concerned about how to drill that hole straight. Because the motor fit perfectly into the channel, I initially tried to use it to turn a drill bit. After about 3 min of pressing the bit into the HDPE, it was clear the motor wasn't anywhere fast enough. I ended up heating the end of the drill bit with my wife's creme-brulee torch until it turned bright red and trying again. This worked like a charm! Although, I'm not sure if I am going to be able to get the melted HDPE off the bit.

Here are the pics:
new_frame1.JPG
The new frame, from the top
(44.95 KiB) Downloaded 1556 times

new_frame2.jpg
the new frame, from the side
(30.38 KiB) Downloaded 1556 times


Right now, the bolt (with bolts & washers) and motor and be moved a bit so that I can better control over the center of mass when the frame is placed in the ball. The next step will involve fabricating and attaching a center link to hang down from the horizontal piece. This will hold the batteries, a servo to control the position of the link (to make the ball lean left or right), and possibly some electronics. I just downloaded the new Google Sketchup to try and model exactly what this will look like so I don't have to keep designing from the seat-of-my-pants.

Edit: Here's what I've done so far:
frame-model.1.png
Google sketchup of frame
(35.18 KiB) Downloaded 1548 times



Although I had the Xbee modules, I'm waiting for a USB xbee explorer shield from sparkfun before I hook it up. I also managed to dig up a 5DOF IMU I forgot I had (!!!). Somehow, I thought it was just a 3-axis accelerometer but after glancing through my order history to see when the shield would ship, it clearly said it was much more.

I've also been reading up on inverted pendulum bots done using an arduino (which is basically what this is except with 2-axes and "falling over" isn't so serious). From what I can gather, a Kalman filter + anything else might be too much for my little duemilanove. I happen to have another arduino in a closet somewhere (who doesn't). Maybe I could just use one to read the IMU and output the result of the Kalman filter over pwm to another arduino's ADC port? That second arduino would then actually do the motor control and xbee stuff. I've also read a little about alternatives to Kalman filters that might be appropriate when processing is constrained. Either way, this will all have to wait until I get the xbee and IMU hooked up. Any input on filter methods or whether or not this can be done using one arduino would be much appreciated. Thanks
machinelou
 
Posts: 21
Joined: April 3rd, 2011, 6:42 pm

Re: Ball Bot

Postby machinelou » April 27th, 2011, 10:07 pm

More progress... The xbee and IMU are connected to the arduino and sending telemetry data. I've created a proof-of-concept processing sketch to interpret the data and draw the bot's orientation -- or potential oriented, if I bothered to calibrate the sensors.

The purpose of this sketch is to be able to try out different methods of integrating the accelerometer and gyroscope data before doing it onboard the arduino.

video-screenshot.png
(489.97 KiB) Downloaded 1444 times

Video: http://www.youtube.com/watch?v=D_0-LyAiJeU

sketch-screenshot.png
(16.13 KiB) Downloaded 1444 times

Processing Sketch:
Code: Select all
import processing.serial.*;

Bot accel;
Bot gyro;
Serial myPort;   

String lastdata;
int stage;
int _min=900;
int _max;

void setup() {
  frameRate(60);
  size(640,480,P3D);
  fill(200);
  accel = new Bot(new PVector(-1200,0,-1800));
  gyro = new Bot(new PVector(1200,0,-1800));
 
  // The serial port:
     
   
  // List all the available serial ports:
  println(Serial.list());
  myPort = new Serial(this, Serial.list()[0], 9600);
 
  lastdata = new String("");
  stage = -1;
}

void draw() {
  float roll = 0;
  float pitch = 0;
 
  boolean fullread = false;
 
  while (myPort.available()>0) {
    String ch = Character.toString(myPort.readChar());
    if (ch.equals("\n")) {
      //print("[" + lastdata + "]\n");
      //lastdata = trim(lastdata);
      String[] bits = splitTokens(lastdata," \n\r");
      if (bits[0].equals("g:")) {
        fullread = true;
       
       
        if (bits.length == 6) {
          for (int i = 1; i < bits.length; i++) {
            //print(i + "[" + bits[i] + "] ");
            if (int(bits[i]) > _max) _max = int(bits[i]);
            if (int(bits[i]) < _min) {
              //println("newmin: [" + bits[i] + "] ");
              _min = int(bits[i]);
            }
          }
          roll = (int(bits[1])-_min)*6.28/(_max-_min);
          pitch = (int(bits[2])-_min)*6.28/(_max-_min);
        }
      }
     

      print(" min: " + _min + " max:" + _max + " (" + bits.length + ")\n");
      lastdata = "";
    } else {
      lastdata = lastdata + ch;
    }
 
  }
  if (fullread == false) return;
 
  lights();
  background(0);

  //noFill();
  fill(255,0,0);
  //stroke(255);
  accel.drawbot(roll,0);

  fill(0,0,200);
  gyro.drawbot(0,pitch);
 
 
}
class Bot {
  BotDrawing front;
  BotDrawing side;
  PVector pos;
 
  Bot(PVector _pos) {
    pos = _pos;
    front = new BotDrawing();
    side = new BotDrawing();
  }
 
  void drawbot(float roll, float pitch) {
    pushMatrix();
      translate(pos.x,pos.y,pos.z);
      pushMatrix();
        translate(-600,0,-1800);
        front.drawbot(roll, pitch);
      popMatrix();
      pushMatrix();
        translate(600,0,-1800);
        rotateY(radians(90));
        front.drawbot(roll, pitch);
      popMatrix();
    popMatrix();
  }
}

class BotDrawing {
  Cube frame;
  Cube leg;
 
  BotDrawing() {
    frame = new Cube(900,100,200);
    leg = new Cube(200,450,220);
  }
 
  void drawbot(float roll, float pitch) {
    pushMatrix();
      rotateX(roll);
      rotateZ(pitch);
      //translate(x,y,z);
      frame.create();
      pushMatrix();
        translate(0,150,0);
        leg.create();
      popMatrix();
    popMatrix();
  }
}

class Cube {

  PVector[] vertices = new PVector[24];
  float w, h, d;

  Cube(){ }

  Cube(float w, float h, float d){
    this.w = w;
    this.h = h;
    this.d = d;

    // Cube composed of 6 quads
    // Front
    vertices[0] = new PVector(-w/2, -h/2, d/2);
    vertices[1] = new PVector(w/2, -h/2, d/2);
    vertices[2] = new PVector(w/2, h/2, d/2);
    vertices[3] = new PVector(-w/2, h/2, d/2);

    // Left
    vertices[4] = new PVector(-w/2, -h/2, d/2);
    vertices[5] = new PVector(-w/2, -h/2, -d/2);
    vertices[6] = new PVector(-w/2, h/2, -d/2);
    vertices[7] = new PVector(-w/2, h/2, d/2);

    // Right
    vertices[8] = new PVector(w/2, -h/2, d/2);
    vertices[9] = new PVector(w/2, -h/2, -d/2);
    vertices[10] = new PVector(w/2, h/2, -d/2);
    vertices[11] = new PVector(w/2, h/2, d/2);

    // Back
    vertices[12] = new PVector(-w/2, -h/2, -d/2); 
    vertices[13] = new PVector(w/2, -h/2, -d/2);
    vertices[14] = new PVector(w/2, h/2, -d/2);
    vertices[15] = new PVector(-w/2, h/2, -d/2);

    // Top
    vertices[16] = new PVector(-w/2, -h/2, d/2);
    vertices[17] = new PVector(-w/2, -h/2, -d/2);
    vertices[18] = new PVector(w/2, -h/2, -d/2);
    vertices[19] = new PVector(w/2, -h/2, d/2);

    // Bottom
    vertices[20] = new PVector(-w/2, h/2, d/2);
    vertices[21] = new PVector(-w/2, h/2, -d/2);
    vertices[22] = new PVector(w/2, h/2, -d/2);
    vertices[23] = new PVector(w/2, h/2, d/2);
  }

  void create(){
    for (int i=0; i<6; i++){
      beginShape(QUADS);
      for (int j = 0; j < 4; j++){
        vertex(vertices[j+4*i].x, vertices[j+4*i].y, vertices[j+4*i].z);
      }
      endShape();
    }
  }
}


Arduino:
Code: Select all

int out_A_pwm = 11; //pwm
int out_A_in_1 = 4;
int out_A_in_2 = 5;
int out_stby = 6;
int out_LED = 13;

int xaccel_pin = A5;
int yaccel_pin = A4;
int zaccel_pin = A3;
int xrate_pin = A1;
int yrate_pin = A0;

int in_but1 = 10;
int in_but1_laststate = LOW;
unsigned long in_but1_lastpress = 0;

int program = 0;
int program_state = 0;
unsigned long program_laststatechange = 0;

unsigned long last_gyro_time = 0;

int bot_gas = 0; //100 - 250


void forward(int power) {
 digitalWrite(out_stby,HIGH);
 digitalWrite(out_A_in_1,HIGH);
 digitalWrite(out_A_in_2,LOW);
 analogWrite(out_A_pwm,power);
 Serial.println("Forwards");
}

void backward(int power) {
 digitalWrite(out_stby,HIGH);
 digitalWrite(out_A_in_1,LOW);
 digitalWrite(out_A_in_2,HIGH);
 analogWrite(out_A_pwm,power);
 Serial.println("Backwards");
}

void coast() {
 digitalWrite(out_stby,HIGH);
 digitalWrite(out_A_in_1,LOW);
 digitalWrite(out_A_in_2,LOW);
 Serial.println("Coast");
}

void setup()
{
  pinMode(out_A_pwm,OUTPUT);
  pinMode(out_A_in_1,OUTPUT);
  pinMode(out_A_in_2,OUTPUT);
  pinMode(out_stby,OUTPUT);
  pinMode(out_LED,OUTPUT);
 
  pinMode(in_but1,INPUT);
  pinMode(xaccel_pin,INPUT);
 
  Serial.begin(9600);
 
  bot_gas = 5;
 
  program = 0;
  program_state = 0;
   
}
 
int power(int level) {
  int power;
 
  if (level == 0) power = 0;
  else if (level == 1) power = 60;
  else if (level == 2) power = 80;
  else if (level == 3) power = 100;
  else if (level == 4) power = 120;
  else if (level == 5) power = 140;
  else if (level == 6) power = 160;
  else if (level == 7) power = 180;
  else if (level == 8) power = 200;
  else if (level == 9) power = 220;
  else if (level == 10) power = 240;
  else power = 250;
 
  return power;
}
void loop()
{
  //boolean has_been_read = false;
  if (millis() > last_gyro_time + 200) {
    int xaccel = analogRead(xaccel_pin);
    Serial.print("g: ");
    Serial.print(analogRead(xaccel_pin));
    Serial.print(" ");
    Serial.print(analogRead(yaccel_pin));
    Serial.print(" ");
    Serial.print(analogRead(zaccel_pin));
    Serial.print(" ");
    Serial.print(analogRead(xrate_pin));
    Serial.print(" ");
    Serial.print(analogRead(yrate_pin));
    Serial.print(" ");
    Serial.println("");
    last_gyro_time = millis();
  }
 
  /*while (Serial.available() > 0) {
    Serial.write(Serial.read());
    has_been_read = true;
  }
  if (has_been_read == true) Serial.flush();
 
  if ( Serial.available() > 0) {
    char inByte = Serial.read();
  }
  */
  //Code to detect button presses
  int cur_state = digitalRead(in_but1);
 
  if ((cur_state == HIGH) && (in_but1_laststate == LOW)) {
    if (millis() > in_but1_lastpress + 50) {
      in_but1_laststate = cur_state;
      in_but1_lastpress = millis();
     
      program = program * -1 + 1;
      Serial.println("Button Press");
     
      if (program == 1) {
        program_state = 0;
        program_laststatechange = millis();
        digitalWrite(out_LED,HIGH);
      } else {
        program = 0;
        coast();
        digitalWrite(out_LED,LOW);
      }
    }
  } else if ((cur_state == LOW) && (in_but1_laststate == HIGH)) {
    if (millis() > in_but1_lastpress + 50) {
      in_but1_laststate = cur_state;
      in_but1_lastpress = millis();
    }
  }
 
 
  //state code
  if (program == 1) {
    if (program_state == 0) {
      if (millis() - program_laststatechange > 30000) {
        program_state = 1;
        program_laststatechange = millis();
        digitalWrite(out_LED,LOW);
        forward(power(bot_gas)); //forward 25%
      }
    } else if (program_state == 1) {
      if (millis() - program_laststatechange > 4000) {
        program_state = 2;
        program_laststatechange = millis();
        coast(); //coast
        digitalWrite(out_LED,HIGH);
      }
    } else if (program_state == 2) {
      if (millis() - program_laststatechange > 1500) {
        program_state = 3;
        program_laststatechange = millis();
        digitalWrite(out_LED,LOW);
        backward(power(bot_gas)); //backward
      }
    } else if (program_state == 3) {
      if (millis() - program_laststatechange > 4000) {
        program_state = 0;
        program_laststatechange = millis();
        digitalWrite(out_LED,HIGH);
        coast(); //coast
        bot_gas++;
        if (bot_gas > 5) bot_gas = 5;
      }
    }
  }
//  analogWrite(out_A_pwm,bot_gas);
}
machinelou
 
Posts: 21
Joined: April 3rd, 2011, 6:42 pm

Re: Ball Bot

Postby machinelou » May 11th, 2011, 8:40 am

I've been working on software. I think I'm eventually going to try implementing a complementary filter to integrate the accelerometer and gyro data. Complementary filters are more appropriate than Kalman filters given the processing constraints of a typical avr and sufficient for monitoring attitude and roll. I've also found new respect for other robot builders. Fabricating stuff is hard. Fabricating stuff so that it plays nicely with electronics and motors is even harder.

Here's a recent pic. As you can probably tell, I've made some progress on the frame and have started moving electronics to it. The battery is currently zip-tied to the back of the arm. That position *seems* ok but I haven't tried moving the servo under power yet. Any lower, and the weight of the batteries (the moment?) causes the servo arm to move when I tilt the frame.
IMG_4947.small.JPG
(108.85 KiB) Downloaded 1356 times

That little bar connecting the servo and the arm is from a metal hanger. I cut it, bent the ends at 90 degrees, ground the servo-side so it would be small enough to fit into the horn, fit it in the horn, then hammered the part sticking through flat so that it wouldn't slide back out. The arm-side is connected via a brass bushing from a servo mount. After sticking them both into the arm, I flattened the parts sticking through with a vice-grip (there wasn't enough space to swing a hammer) so they wouldn't slide back out. That seems to have worked.
machinelou
 
Posts: 21
Joined: April 3rd, 2011, 6:42 pm

Re: Ball Bot

Postby machinelou » May 15th, 2011, 12:29 pm

I've placed all the components on the frame and it runs (imperfectly)!

Top View:
top_view_annotated.jpg
(84.29 KiB) Downloaded 1302 times


Front View:
front_view_annotated.JPG
(147.56 KiB) Downloaded 1302 times


Back View:
back_view_annotated.JPG
(149.13 KiB) Downloaded 1302 times
machinelou
 
Posts: 21
Joined: April 3rd, 2011, 6:42 pm


Return to Project Logs

Who is online

Users browsing this forum: Bing [Bot] and 1 guest