Driving 12 servos from one board: PCA9685 wiring, power and pulse ranges
- Build Logs
- 26 Jul, 2026
An Arduino Uno has six PWM-capable pins, and the standard Servo library fights with any other library that wants a timer. So the first real wall most people hit when a project grows past a pan-tilt bracket is this: you have eight, twelve, sixteen servos and nowhere to plug them in.
The PCA9685 solves it for about the price of two servos. This is the wiring and the power budget that make it work — and the one mistake that quietly destroys boards.
What the board actually is
The PCA9685 is a 16-channel, 12-bit PWM generator that talks I2C. Two signal wires from your microcontroller, sixteen independent outputs. Because it generates the pulses in hardware, your MCU is free the rest of the time — no timer conflicts, no jitter from a busy main loop.
- 16 channels, each 12-bit (4096 steps of resolution)
-
I2C, default address
0x40 - Address-selectable via the A0–A5 solder jumpers, so up to 62 boards share one bus — 992 channels if you ever need them
- 3.3 V or 5 V logic, so ESP32 and Arduino both work without a level shifter
The mistake that kills boards
The PCA9685 has two separate power inputs, and confusing them is the single most common way to destroy one.
- VCC is the logic supply. This comes from your microcontroller — 3.3 V from an ESP32, 5 V from an Uno. It powers the chip itself and draws almost nothing.
- V+ is the servo supply. This is the green screw terminal, and it must come from a separate external power supply. It never comes from your microcontroller.
Powering servos from the Arduino's 5 V pin is the mistake. The onboard regulator cannot supply the current, USB limits you to 500 mA, and the result is a brownout that resets the board mid-move — or worse, a dead regulator. The symptom people report is "my Arduino keeps restarting when the servos move." That is what is happening.
The one wire people forget: the external supply's ground must be tied to the microcontroller's ground. Without a common ground the I2C signals have no reference and nothing works.
Power budget: do this arithmetic first
An SG90 draws roughly 700 mA when stalled at 4.8 V, and somewhere in the 200–400 mA range while actually moving under light load. Holding position against no load is far less.
You do not need to budget for every servo stalling at once — that is a fault condition, not normal operation. But you do need headroom for all of them starting a move on the same tick, which is the biggest real spike. For twelve SG90s:
- A 5 V 2 A supply is not enough. It will sag and the servos will twitch.
- A 5 V 5 A supply is a comfortable choice and leaves room to grow.
- Add a 1000 µF electrolytic capacitor across V+ and GND, physically close to the board. This absorbs the inrush spike that a supply's regulation loop is too slow to catch. 470 µF is the minimum worth fitting.
If you are driving MG995-class servos instead, multiply everything — those pull well over an amp stalled.
Wiring
| PCA9685 | Arduino Uno | ESP32 |
|---|---|---|
| VCC | 5 V | 3.3 V |
| GND | GND | GND |
| SDA | A4 | GPIO 21 |
| SCL | A5 | GPIO 22 |
| V+ terminal | External 5–6 V supply, positive | |
| GND terminal | External supply ground, also tied to MCU GND | |
Before writing any servo code, run an I2C scanner sketch. You should see a device at 0x40. If you see nothing, the problem is wiring or a missing common ground, and no amount of servo code will fix it. Diagnose it here, not later.
Getting the pulse range right
This is where most tutorials hand you magic numbers. Here is where they come from.
At a 50 Hz refresh rate the period is 20 ms, and the PCA9685 divides that period into 4096 steps. So one step is:
20 ms / 4096 = 4.88 µs
Hobby servos expect a pulse between roughly 1.0 ms and 2.0 ms, with 1.5 ms as centre. Converting to step counts:
- 1.0 ms → 205 counts (roughly 0°)
- 1.5 ms → 307 counts (centre)
- 2.0 ms → 410 counts (roughly 180°)
You will see 150 and 600 in Adafruit's example sketch. Those correspond to about 0.73 ms and 2.93 ms — deliberately wider than the nominal band, because many servos travel further than 1–2 ms gives you. That extra range is real, but it can also drive a servo hard into its mechanical endstop, where it will stall, get hot and draw its full 700 mA indefinitely.
Start at 205 and 410. Widen a few counts at a time and stop the moment you hear the servo buzzing at the limit — that buzz is a stall.
Code
Install the Adafruit PWM Servo Driver Library from the Arduino Library Manager.
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40);
const int SERVO_MIN = 205; // ~1.0 ms
const int SERVO_MAX = 410; // ~2.0 ms
const int NUM_SERVOS = 12;
int angleToPulse(int angle) {
return map(angle, 0, 180, SERVO_MIN, SERVO_MAX);
}
void setup() {
Wire.begin();
pwm.begin();
pwm.setOscillatorFrequency(27000000); // calibrate this - see below
pwm.setPWMFreq(50); // 50 Hz for analogue hobby servos
// Centre everything, staggered. Moving all twelve on the same tick
// is the worst-case current spike - spread it over 600 ms instead.
for (int ch = 0; ch < NUM_SERVOS; ch++) {
pwm.setPWM(ch, 0, angleToPulse(90));
delay(50);
}
}
void loop() {
for (int angle = 60; angle <= 120; angle += 2) {
for (int ch = 0; ch < NUM_SERVOS; ch++) {
pwm.setPWM(ch, 0, angleToPulse(angle));
}
delay(20);
}
for (int angle = 120; angle >= 60; angle -= 2) {
for (int ch = 0; ch < NUM_SERVOS; ch++) {
pwm.setPWM(ch, 0, angleToPulse(angle));
}
delay(20);
}
}
Note the small steps in loop(). Commanding a servo straight from 60° to 120° makes it go as fast as it can, which is both a current spike and a mechanical shock. Stepping in 2° increments gives you a controlled speed for free.
About setOscillatorFrequency
The PCA9685's internal oscillator is nominally 25 MHz, but real parts drift, and the frequency error shows up directly as a pulse-width error. If your servos do not reach the angle you asked for, measure the actual output frequency with a scope or logic analyser and adjust this value until setPWMFreq(50) really produces 50 Hz. 27000000 is a commonly used starting point, not a universal constant.
When it twitches
Servo jitter has three usual causes, in order of likelihood:
- Not enough current. By far the most common. The supply sags, the servo loses position, its control loop hunts. Fix the supply and fit the capacitor before suspecting anything else.
- No common ground between the external supply and the microcontroller.
- A cheap servo at its endstop. Narrow your pulse range.
The OE (output enable) pin is also worth knowing about: pull it high and every channel switches off at once. Wire it to a button and you have a hardware kill switch, which is genuinely useful the first time a legged robot decides to throw itself off the bench.
Parts
- PCA9685 16-channel servo driver — comes with the header pins soldered
- SG90 and PCA9685 combo — five servos plus the driver, if you are starting from nothing
- Servos and motors — SG90 for light loads, MG995 when you need torque
Built something with this, or hit a problem the article does not cover? Leave a comment below — the fixes that come out of those threads are what the next version of this guide gets written from.