Built-in project · Arduino Uno R3

Hexapod robot on an Arduino Uno

Twelve servos on a PCA9685 walk a six-legged robot with the tripod gait: three feet lift and swing forward while the other three hold it up. A button starts and stops the walk. The whole build, an Arduino Uno and 14 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Advanced About 40 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $61. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • Motors
  • Moving a servo to an angle
  • Using the Servo driver
  • Using the Limit switch
  • Difficulty: Advanced
  • Time: About 40 minutes
  • Board: Arduino Uno R3
  • Parts: 28

How it works

Twelve servos on a PCA9685 walk a six-legged robot with the tripod gait: three feet lift and swing forward while the other three hold it up. A button starts and stops the walk.

Arduino Uno R3 runs the sketch below. The circuit around it: Servo driver, PCA9685 16-channel, Limit switch, lever microswitch, Hobby servo. pinMode sets BUTTON as an input with its pull-up on. digitalRead reads whether BUTTON is HIGH or LOW. delay waits STEP_MS milliseconds. Serial.begin opens the serial port at 115200 baud.

Parts list

28 parts, plus the jumper wires. They retail for about $61; on Mokxi they're free.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Servo driver, PCA9685 16-channel pin GNDArduino Uno R3 pin GND
Limit switch, lever microswitch pin NOArduino Uno R3 pin 2
Servo driver, PCA9685 16-channel pin VCCArduino Uno R3 pin 5V
Servo driver, PCA9685 16-channel pin SDAArduino Uno R3 pin A4
Servo driver, PCA9685 16-channel pin SCLArduino Uno R3 pin A5
Servo driver, PCA9685 16-channel pin V+5 V
Hobby servo (1) pin PWMServo driver, PCA9685 16-channel pin PWM0
Hobby servo (2) pin PWMServo driver, PCA9685 16-channel pin PWM1
Hobby servo (3) pin PWMServo driver, PCA9685 16-channel pin PWM2
Hobby servo (4) pin PWMServo driver, PCA9685 16-channel pin PWM3
Hobby servo (5) pin PWMServo driver, PCA9685 16-channel pin PWM4
Hobby servo (6) pin PWMServo driver, PCA9685 16-channel pin PWM5
Hobby servo (7) pin PWMServo driver, PCA9685 16-channel pin PWM6
Hobby servo (8) pin PWMServo driver, PCA9685 16-channel pin PWM7
Hobby servo (9) pin PWMServo driver, PCA9685 16-channel pin PWM8
Hobby servo (10) pin PWMServo driver, PCA9685 16-channel pin PWM9
Hobby servo (11) pin PWMServo driver, PCA9685 16-channel pin PWM10
Hobby servo (12) pin PWMServo driver, PCA9685 16-channel pin PWM11
Limit switch, lever microswitch pin COMGround
Hobby servo (1) pin GNDGround
Hobby servo (1) pin VCC5 V
Hobby servo (2) pin GNDGround
Hobby servo (2) pin VCC5 V
Hobby servo (3) pin GNDGround
Hobby servo (3) pin VCC5 V
Hobby servo (4) pin GNDGround
Hobby servo (4) pin VCC5 V
Hobby servo (5) pin GNDGround
Hobby servo (5) pin VCC5 V
Hobby servo (6) pin GNDGround
Hobby servo (6) pin VCC5 V
Hobby servo (7) pin GNDGround
Hobby servo (7) pin VCC5 V
Hobby servo (8) pin GNDGround
Hobby servo (8) pin VCC5 V
Hobby servo (9) pin GNDGround
Hobby servo (9) pin VCC5 V
Hobby servo (10) pin GNDGround
Hobby servo (10) pin VCC5 V
Hobby servo (11) pin GNDGround
Hobby servo (11) pin VCC5 V
Hobby servo (12) pin GNDGround
Hobby servo (12) pin VCC5 V

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// Hexapod: a six-legged walker, twelve servos on a PCA9685 servo board.
//
//   PCA9685  SDA to A4, SCL to A5, VCC to the Uno's 5V, GND to GND
//            V+ from a separate 5 V supply for the twelve servos
//   button   pin 2 to ground (internal pull-up): press to walk or stand
//   legs     left front  hip on channel 0,  knee on 1
//            left middle hip on 2,          knee on 3
//            left rear   hip on 4,          knee on 5
//            right front hip on 6,          knee on 7
//            right middle hip on 8,         knee on 9
//            right rear  hip on 10,         knee on 11
//
// # The tripod gait
//
// A robot is stable when its feet on the ground make a triangle around its
// middle. Six legs split into two triangles that can take turns:
//
//   tripod A  left front, right middle, left rear
//   tripod B  right front, left middle, right rear
//
// Each step is three moves on one tripod while the other stays planted:
//
//   1. the tripod's knees lift its feet off the ground
//   2. its hips swing the feet forward, while the planted tripod's hips
//      slide back together, which pushes the body forward
//   3. the knees put the feet down
//
// Then the two tripods swap jobs. At no time are fewer than three feet on the
// ground, and the three are always spread front, middle and rear, so the
// robot never has to balance. This is how most insects walk, and it is the
// fastest gait a hexapod has.
//
// # Mirrored servos
//
// The servos on the right side face the other way from the ones on the left,
// so "forward" is a bigger angle on the left and a smaller one on the right.
// SIDE[] holds that +1 or -1, and the gait only thinks in "degrees forward"
// and "degrees up".

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();  // address 0x40

// 544 to 2400 us is 0 to 180 degrees; the PCA9685 counts a 20 ms frame in 4096
// ticks, so that window is 111 to 492 ticks.
const int MIN_TICKS = 111;
const int MAX_TICKS = 492;

const int BUTTON = 2;

// Legs 0 to 5: left front, left middle, left rear, right front, right middle,
// right rear.
const char *LEG_NAMES[6] = {"left front", "left middle", "left rear", "right front", "right middle", "right rear"};
const int SIDE[6] = {+1, +1, +1, -1, -1, -1};
// Which tripod each leg is in: 0 is A, 1 is B.
const int TRIPOD[6] = {0, 1, 0, 1, 0, 1};

const int SWING = 20;  // how far a foot swings from its middle, degrees
const int LIFT = 35;   // how far a knee raises its foot, degrees
const int STEP_MS = 15;

// Where each leg is now: degrees forward of square and degrees lifted.
int forward[6] = {-SWING, -SWING, -SWING, -SWING, -SWING, -SWING};
int lifted[6] = {0, 0, 0, 0, 0, 0};

bool walking = false;
bool lastButton = HIGH;
int stepNumber = 0;

// Look at the button. A step takes most of a second, so this is called from
// inside glide() as well as from loop(): a press that came and went while a
// step was under way would otherwise be missed. Pressing never cuts a step
// short, because a robot stopped with three feet in the air would fall over;
// the walk ends when the step in progress does.
void checkButton() {
  bool button = digitalRead(BUTTON);
  if (lastButton == HIGH && button == LOW) {
    walking = !walking;
    Serial.println(walking ? "walking" : "standing");
  }
  lastButton = button;
}

void setAngle(int channel, int degrees) {
  degrees = constrain(degrees, 0, 180);
  pwm.setPWM(channel, 0, map(degrees, 0, 180, MIN_TICKS, MAX_TICKS));
}

void writeLegs() {
  for (int leg = 0; leg < 6; leg++) {
    setAngle(leg * 2, 90 + SIDE[leg] * forward[leg]);
    setAngle(leg * 2 + 1, 90 + SIDE[leg] * lifted[leg]);
  }
}

// Move every joint from where it is to the target, together, in ten small
// steps.
void glide(const int toForward[6], const int toLifted[6]) {
  int fromForward[6], fromLifted[6];
  for (int leg = 0; leg < 6; leg++) {
    fromForward[leg] = forward[leg];
    fromLifted[leg] = lifted[leg];
  }
  for (int step = 1; step <= 10; step++) {
    for (int leg = 0; leg < 6; leg++) {
      forward[leg] = fromForward[leg] + (toForward[leg] - fromForward[leg]) * step / 10;
      lifted[leg] = fromLifted[leg] + (toLifted[leg] - fromLifted[leg]) * step / 10;
    }
    writeLegs();
    delay(STEP_MS);
    checkButton();
  }
}

// One step by one tripod: lift, swing forward while the other tripod slides
// back, put down.
void step(int tripod) {
  int toForward[6], toLifted[6];
  for (int leg = 0; leg < 6; leg++) {
    toForward[leg] = forward[leg];
    toLifted[leg] = lifted[leg];
  }
  for (int leg = 0; leg < 6; leg++) {
    if (TRIPOD[leg] == tripod) toLifted[leg] = LIFT;
  }
  glide(toForward, toLifted);

  for (int leg = 0; leg < 6; leg++) {
    toForward[leg] = TRIPOD[leg] == tripod ? SWING : -SWING;
  }
  glide(toForward, toLifted);

  for (int leg = 0; leg < 6; leg++) toLifted[leg] = 0;
  glide(toForward, toLifted);

  Serial.print("tripod ");
  Serial.print(tripod == 0 ? "A" : "B");
  Serial.println(" stepped");
}

// Stand: every foot down, every hip in the middle of its swing.
void stand() {
  int toForward[6] = {-SWING, -SWING, -SWING, -SWING, -SWING, -SWING};
  int toLifted[6] = {0, 0, 0, 0, 0, 0};
  glide(toForward, toLifted);
}

void setup() {
  pinMode(BUTTON, INPUT_PULLUP);
  Serial.begin(115200);
  if (!pwm.begin()) {
    Serial.println("no PCA9685 at 0x40: check SDA, SCL and VCC");
  }
  pwm.setPWMFreq(50);
  writeLegs();
  Serial.println("hexapod: standing. press the button to walk");
  delay(500);
}

void loop() {
  checkButton();

  if (walking) {
    step(stepNumber % 2);
    stepNumber++;
  } else if (stepNumber != 0) {
    // The step that was under way has finished with every foot down: settle
    // into the stand.
    stand();
    stepNumber = 0;
  }
}

Code walkthrough

The key functions of the sketch, and what each one does.

setup sketch.ino

Runs once when the board starts. It gets the pins and libraries ready.

void setup() {
  pinMode(BUTTON, INPUT_PULLUP);
  Serial.begin(115200);
  if (!pwm.begin()) {
    Serial.println("no PCA9685 at 0x40: check SDA, SCL and VCC");
  }
  pwm.setPWMFreq(50);
  writeLegs();
  Serial.println("hexapod: standing. press the button to walk");
  delay(500);
}

loop sketch.ino

Runs over and over for as long as the board has power. This is where the project does its work.

void loop() {
  checkButton();

  if (walking) {
    step(stepNumber % 2);
    stepNumber++;
  } else if (stepNumber != 0) {
    // The step that was under way has finished with every foot down: settle
    // into the stand.
    stand();
    stepNumber = 0;
  }
}

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The servos do nothing after upload. Servo driver
    The chip starts in sleep mode. Call begin and set the frequency, such as 50 Hz for servos, before sending pulses. The default is not suited to servos.
  • The board resets or servos jitter. Servo driver
    Power servos from the V+ terminal with a separate 5 to 6 V supply, not the board's 5 V pin. Join the grounds and add a capacitor across V+.
  • The input reads the wrong level, or floats when released. Limit switch
    Use COM and one contact. NC is closed at rest, NO closes when pressed. Add a pull-up or INPUT_PULLUP so the pin never floats.
  • Readings chatter when the lever moves. Limit switch
    Contacts bounce for a few milliseconds. Debounce in software with a 10 to 20 ms delay, or add a small capacitor across the switch.
  • The servo jitters, twitches or resets the board when it moves. Hobby servo
    Power it from its own 5 V supply, not the Arduino 5 V pin, and join the grounds. A hobby servo can pull several hundred milliamps. Add a 100 uF to 470 uF capacitor across its supply.
  • The servo hums at the end of its travel or will not reach full angle. Hobby servo
    Stay inside the pulse range, about 1000 to 2000 us for most hobby servos. Pushing past the end stop makes it strain. Calibrate the min and max pulse for your servo.
  • Upload fails or nothing runs after wiring pins 0 and 1 to a module. Arduino Uno R3
    Pins 0 and 1 are the USB serial port. Move the module to other pins, or unplug it while uploading. Also check the right board is selected.
  • Pins misbehave or the board resets when you power motors or servos from it. Arduino Uno R3
    Each I/O pin gives about 20 mA, 40 mA max. Power motors and servos from their own supply and join its ground to the Uno's GND.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.