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
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.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Servo driver, PCA9685 16-channel
- 1 × Robot arm, 4 servos
- 2 × Power, 5 V
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 |
| 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 |
| Robot arm, 4 servos pin BASE | Servo driver, PCA9685 16-channel pin PWM0 |
| Robot arm, 4 servos pin SHOULDER | Servo driver, PCA9685 16-channel pin PWM1 |
| Robot arm, 4 servos pin ELBOW | Servo driver, PCA9685 16-channel pin PWM2 |
| Robot arm, 4 servos pin GRIP | Servo driver, PCA9685 16-channel pin PWM3 |
| Robot arm, 4 servos pin VCC | 5 V |
| Robot arm, 4 servos pin GND | Ground |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// 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.