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.
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.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Servo driver, PCA9685 16-channel
- 1 × Limit switch, lever microswitchIn Claw Machine Kit, Vending Machine Kit.
- 13 × Power, 5 V
- 12 × Hobby servoIn Halloween Prop Kit, Christmas Lights Kit, Starter Projects Kit.
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 pin | Connects to |
|---|---|
| Servo driver, PCA9685 16-channel pin GND | Arduino Uno R3 pin GND |
| Limit switch, lever microswitch pin NO | Arduino Uno R3 pin 2 |
| Servo driver, PCA9685 16-channel pin VCC | Arduino Uno R3 pin 5V |
| Servo driver, PCA9685 16-channel pin SDA | Arduino Uno R3 pin A4 |
| Servo driver, PCA9685 16-channel pin SCL | Arduino Uno R3 pin A5 |
| Servo driver, PCA9685 16-channel pin V+ | 5 V |
| Hobby servo (1) pin PWM | Servo driver, PCA9685 16-channel pin PWM0 |
| Hobby servo (2) pin PWM | Servo driver, PCA9685 16-channel pin PWM1 |
| Hobby servo (3) pin PWM | Servo driver, PCA9685 16-channel pin PWM2 |
| Hobby servo (4) pin PWM | Servo driver, PCA9685 16-channel pin PWM3 |
| Hobby servo (5) pin PWM | Servo driver, PCA9685 16-channel pin PWM4 |
| Hobby servo (6) pin PWM | Servo driver, PCA9685 16-channel pin PWM5 |
| Hobby servo (7) pin PWM | Servo driver, PCA9685 16-channel pin PWM6 |
| Hobby servo (8) pin PWM | Servo driver, PCA9685 16-channel pin PWM7 |
| Hobby servo (9) pin PWM | Servo driver, PCA9685 16-channel pin PWM8 |
| Hobby servo (10) pin PWM | Servo driver, PCA9685 16-channel pin PWM9 |
| Hobby servo (11) pin PWM | Servo driver, PCA9685 16-channel pin PWM10 |
| Hobby servo (12) pin PWM | Servo driver, PCA9685 16-channel pin PWM11 |
| Limit switch, lever microswitch pin COM | Ground |
| Hobby servo (1) pin GND | Ground |
| Hobby servo (1) pin VCC | 5 V |
| Hobby servo (2) pin GND | Ground |
| Hobby servo (2) pin VCC | 5 V |
| Hobby servo (3) pin GND | Ground |
| Hobby servo (3) pin VCC | 5 V |
| Hobby servo (4) pin GND | Ground |
| Hobby servo (4) pin VCC | 5 V |
| Hobby servo (5) pin GND | Ground |
| Hobby servo (5) pin VCC | 5 V |
| Hobby servo (6) pin GND | Ground |
| Hobby servo (6) pin VCC | 5 V |
| Hobby servo (7) pin GND | Ground |
| Hobby servo (7) pin VCC | 5 V |
| Hobby servo (8) pin GND | Ground |
| Hobby servo (8) pin VCC | 5 V |
| Hobby servo (9) pin GND | Ground |
| Hobby servo (9) pin VCC | 5 V |
| Hobby servo (10) pin GND | Ground |
| Hobby servo (10) pin VCC | 5 V |
| Hobby servo (11) pin GND | Ground |
| Hobby servo (11) pin VCC | 5 V |
| Hobby servo (12) pin GND | Ground |
| Hobby servo (12) pin VCC | 5 V |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// 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.