Built-in project · Arduino Uno R3

Pick and place arm on a PCA9685, on an Arduino Uno

A four-servo arm on a PCA9685 servo board with the Adafruit library: it picks a part up on one side, sets it down on the other and goes home, over and over. The whole build, an Arduino Uno and 2 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 $56. On Mokxi they're free.

Step by step, with the wiring and the common mistakes: Arduino PCA9685 Servo Driver: Wiring and Code

The circuit runs here once the page has loaded.

What you'll learn

  • Driving many servos from one I2C servo board
  • Turning an angle into a pulse width with map
  • Moving several joints together in small steps
  • A separate servo supply with a shared ground
  • Describing a task as a list of poses
  • Difficulty: Advanced
  • Time: About 40 minutes
  • Board: Arduino Uno R3
  • Parts: 5

How it works

The Uno talks to the PCA9685 servo board over two wires, SDA on A4 and SCL on A5. The board makes the servo pulses itself, 50 times a second on every channel. The sketch only speaks when a joint should move. The four joints sit on channels 0 to 3: base, shoulder, elbow and grip.

A move is four target angles. glide walks all four joints there together in 25 small steps, 30 ms apart, so the arm does not snap. loop chains eight moves: over the part, reach down, grip, lift, swing across, lower, let go and home.

The servos run from the board's V+ pin on a separate 5 V supply. The Uno's 5V pin only powers the chip's logic. All grounds are joined.

Parts list

5 parts, plus the jumper wires. They retail for about $56; 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
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
Robot arm, 4 servos pin BASEServo driver, PCA9685 16-channel pin PWM0
Robot arm, 4 servos pin SHOULDERServo driver, PCA9685 16-channel pin PWM1
Robot arm, 4 servos pin ELBOWServo driver, PCA9685 16-channel pin PWM2
Robot arm, 4 servos pin GRIPServo driver, PCA9685 16-channel pin PWM3
Robot arm, 4 servos pin VCC5 V
Robot arm, 4 servos pin GNDGround

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
// Pick and place arm on a PCA9685: a four-servo arm on the 16-channel servo
// board, driven with the Adafruit PWM Servo Driver library.
//
//   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 servos
//   arm      BASE on channel 0, SHOULDER 1, ELBOW 2, GRIP 3
//
// The board makes the servo pulses itself, fifty times a second on every
// channel, so the Uno only speaks when a joint should move: two wires for up
// to sixteen servos, and no pulse timing in the sketch at all.
//
// A move is a list of four angles. glide() walks every joint there together
// in small steps, which is how a real arm avoids slamming its gears and
// swinging what it carries.

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

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

// 544 to 2400 us for 0 to 180 degrees: the Arduino Servo library's window,
// which the servos in the arm answer to.
const int MIN_US = 544;
const int MAX_US = 2400;

// Channels 0 to 3 are the base, shoulder, elbow and claw, in that order.
const int OPEN = 110, SHUT = 25;

int now[4] = {90, 90, 90, OPEN};

void setAngle(int channel, int degrees) {
  pwm.writeMicroseconds(channel, map(degrees, 0, 180, MIN_US, MAX_US));
}

void glide(const char *what, int base, int shoulder, int elbow, int grip) {
  Serial.println(what);
  int target[4] = {base, shoulder, elbow, grip};
  for (int step = 1; step <= 25; step++) {
    for (int j = 0; j < 4; j++) {
      setAngle(j, now[j] + (target[j] - now[j]) * step / 25);
    }
    delay(30);
  }
  for (int j = 0; j < 4; j++) now[j] = target[j];
  delay(250);
}

void setup() {
  Serial.begin(115200);
  if (!pwm.begin()) {
    Serial.println("no PCA9685 at 0x40: check SDA, SCL and VCC");
  }
  pwm.setPWMFreq(50);
  for (int j = 0; j < 4; j++) setAngle(j, now[j]);
  Serial.println("pick and place arm: ready");
}

void loop() {
  glide("over the part", 150, 90, 90, OPEN);
  glide("reach down", 150, 130, 60, OPEN);
  glide("grip", 150, 130, 60, SHUT);
  glide("lift", 150, 90, 100, SHUT);
  glide("swing across", 30, 90, 100, SHUT);
  glide("lower", 30, 130, 60, SHUT);
  glide("let go", 30, 130, 60, OPEN);
  glide("home", 90, 90, 90, OPEN);
  delay(500);
}

Code walkthrough

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

setAngle sketch.ino

setAngle converts an angle from 0 to 180 degrees into a pulse between MIN_US (544) and MAX_US (2400) microseconds using map. It sends that pulse width to one channel of the board.

void setAngle(int channel, int degrees) {
  pwm.writeMicroseconds(channel, map(degrees, 0, 180, MIN_US, MAX_US));
}

glide sketch.ino

glide prints what the move is, then repeats 25 times: for each of the four joints it moves a fraction of the way from where it is (now) to the target, and waits 30 ms. At the end it stores the new angles and pauses a quarter second.

void glide(const char *what, int base, int shoulder, int elbow, int grip) {
  Serial.println(what);
  int target[4] = {base, shoulder, elbow, grip};
  for (int step = 1; step <= 25; step++) {
    for (int j = 0; j < 4; j++) {
      setAngle(j, now[j] + (target[j] - now[j]) * step / 25);
    }
    delay(30);
  }
  for (int j = 0; j < 4; j++) now[j] = target[j];
  delay(250);
}

setup sketch.ino

setup starts the board and prints a hint if no PCA9685 answers. It sets the pulse rate to 50 Hz, which hobby servos expect, and sends each joint to its starting angle so the arm begins in a known pose.

void setup() {
  Serial.begin(115200);
  if (!pwm.begin()) {
    Serial.println("no PCA9685 at 0x40: check SDA, SCL and VCC");
  }
  pwm.setPWMFreq(50);
  for (int j = 0; j < 4; j++) setAngle(j, now[j]);
  Serial.println("pick and place arm: ready");
}

Troubleshooting

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

  • Serial says no PCA9685 at 0x40. This project
    Check SDA on A4, SCL on A5, and VCC and GND on the board.
  • The servos twitch or the Uno resets. This project
    Power the servos from the V+ pin on their own 5 V supply, and join its ground to the Uno's ground.
  • A joint moves when another should. This project
    The base is channel 0, shoulder 1, elbow 2 and grip 3. Plug each servo into its own channel.
  • 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 arm jerks, resets the board or moves weakly when several joints move at once. Robot arm
    Four SG90 servos can pull an amp or more together. Power them from a separate 5 V supply with a 470 uF or larger capacitor. Join its ground to the board's ground.
  • A joint hits its end stop and buzzes, or the claw crushes. Robot arm
    Limit each servo to a safe angle range in code and move gradually in small steps. Find the open and closed angle of the claw by testing, and never command beyond them.
  • 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.

Change it and keep it

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