用 PCA9685 一次驱动 16 个舵机:机械臂和六足的标准做法
- 说清单片机直接驱动多舵机的两个硬伤:PWM 通道不够、供电被拖垮
- 用 Adafruit_PWMServoDriver 封装 setAngle(channel, deg) 并让多个舵机依次扫动
- 正确接线:V+ 接独立 5V、VCC 接 3.3V 逻辑、共地、I2C 地址 0x40
- 通过焊接地址跳线把多块板级联到 32 路以上
你照着教程把一个舵机接到 ESP32,调好角度,挥得很爽。然后你想做个机械臂——底座转、大臂、小臂、手腕翻转、手腕旋转、夹爪,六个舵机。你刚接到第三个,问题就来了:引脚开始不够分,供电一上电主控直接重启,舵机抖得像帕金森。
这不是你接错了,是单片机直接扛多舵机这条路本来就走不通。这篇我们把它讲透,然后换成机械臂、六足、人形这些项目的标准做法:PCA9685。
如果你还没搞清楚单个舵机是怎么被 PWM 控制角度的,先去看 舵机入门,那篇讲了 50Hz、脉宽对应角度这些基础,这里不重复。
先把单舵机的控制原理捋一遍
舵机靠一路 PWM 信号定角度,关键就两个数:
- 频率 50Hz:也就是每 20ms 一个周期。这是模拟舵机的标准刷新率,几乎所有 SG90、MG996R 都吃这个。
- 脉宽 0.5ms ~ 2.5ms:高电平持续多久决定转到哪。0.5ms 大致对应 0°,1.5ms 对应 90°(中位),2.5ms 对应 180°。不同舵机的实际边界会差一点,后面校准时再说。
舵机内部有个电位器一直在量当前角度,比对你给的脉宽,差多少就往哪转。所以你只要持续喂对的脉宽,它就稳在那个角度。
一路 PWM 控一个舵机,听起来很简单。问题在「一路」这两个字。
为什么六个舵机就把 ESP32 逼到墙角
直接用主控的 ledcWrite 之类去生成多路舵机 PWM,你会同时撞上两堵墙。
第一堵墙:PWM 通道不够,而且精度被稀释。 ESP32 的 LEDC 外设有 16 个通道不假,但它们共享有限的定时器,你要给每路都配 50Hz 这种低频、又要保证脉宽分辨率,排起来很别扭。换成 Arduino UNO 这种,能稳定输出舵机 PWM 的引脚就更少了。控两三个还行,控六个、十二个,代码和定时器分配会乱成一锅粥。
第二堵墙,也是更致命的:供电。 舵机不是省油的灯。一个 MG996R 空载几十毫安,一旦带载、尤其是堵转(转不动还在使劲),瞬间电流能冲到 1A 以上。六个舵机同时启动或者同时堵转,瞬时电流好几安培。
如果你图省事,把舵机的电源线也接到主控的 5V 输出上——主控板上那个稳压器根本供不出这么大电流,电压被瞬间拉垮,主控欠压复位。现象就是:你一让多个舵机动,板子就重启。 很多人卡在这里查了一晚上代码,其实一行代码都没错,是电不够。
这两堵墙,PCA9685 一块板子全帮你拆掉。
PCA9685 是什么,凭什么能解决
PCA9685 是一颗 16 路 PWM 驱动芯片,Adafruit 把它做成了一块带舵机排针的现成模块。它干三件事:
- 用 I2C 接管控制。主控只需要两根线(SDA、SCL)跟它通信,发个命令说「第 3 路,脉宽设成 X」,剩下的它自己管。两根线就够,不再跟你抢引脚。关于 I2C 这套主从、地址、上拉的机制,可以看 I2C 总线原理。
- 板载振荡器自己生成 16 路 PWM。芯片内部有个 25MHz 时钟,配上 12 位分辨率(0~4095),独立算出 16 路各自的 PWM 波形,稳定输出。你设好一次频率(50Hz),16 路一起按这个频率走。PWM 是怎么用占空比表达「强度/时长」的,见 PWM 原理。
- 舵机电源走独立的 V+,跟逻辑电源彻底分开。板子上那排舵机座的正极,统一接到一个叫
V+的端子,你从这里灌入一个独立的 5V 大电流电源。舵机吃电吃的是这路,跟主控的供电井水不犯河水。主控重启的问题,根上解决。
一句话:PCA9685 把「控制」和「供电」分了家。 控制走 I2C 两根线,供电走独立 5V,主控只负责动嘴皮子发命令。
接线:四类线,别接错
这是整个项目最容易翻车的地方,照着来。
控制侧(主控 ↔ PCA9685):
| PCA9685 引脚 | 接到 ESP32 | 作用 |
|---|---|---|
| VCC | 3.3V | 芯片逻辑供电,不是给舵机的 |
| GND | GND | 逻辑地 |
| SDA | GPIO21(以你板子丝印为准) | I2C 数据 |
| SCL | GPIO22(以你板子丝印为准) | I2C 时钟 |
SDA/SCL 的具体引脚号每块 ESP32 开发板不一样,以板上丝印为准。大多数 ESP32 默认 SDA=21、SCL=22。
供电侧(独立 5V → PCA9685):
V+端子接独立 5V 电源正极(比如一个 5V/3A 以上的电源适配器,或者足够的电池组)。这路专门喂舵机。- 这个 5V 电源的地,必须和 ESP32 的 GND 接到一起——这就是「共地」。不共地,I2C 的电平就没有共同参考,命令发不进去,舵机不动。
- 舵机的三根线(信号/正/负)直接插 PCA9685 对应通道的排针,有防呆方向,黑(棕)线朝 GND 那一侧。
I2C 地址默认 0x40。板子背面有六个地址跳线(A0~A5),一块不焊就是 0x40,后面级联会用到。
记住这条铁律:舵机的电,永远从 V+ 那路独立电源来,绝不从主控取。
舵机供电务必分离,且共地。 三个安全点焊死在脑子里:① 舵机电源走独立 5V,容量按「舵机数 × 单个堵转电流」估,六个 MG996R 至少备 5V/6A;② 独立电源地和主控地必须共地,否则不工作甚至烧逻辑;③ 上电顺序先接好地线再上电,带电插拔舵机容易打火、冲坏 PCA9685。堵转电流很大,长时间堵转会烧舵机也会烧驱动板,代码里别让舵机硬顶到机械极限。
完整可跑代码:让多个舵机依次扫动
库用 Adafruit 的 Adafruit_PWMServoDriver(Arduino 库管理器搜 "Adafruit PWM Servo Driver" 装上)。核心是:初始化时 setPWMFreq(50),再封装一个 setAngle(channel, deg) 把角度翻译成 PCA9685 认的 0~4095 计数值。
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
// 默认 I2C 地址 0x40,不传参数也行,这里写明更清楚
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40);
// 舵机脉宽边界,单位微秒。0.5ms=500us 对应 0°,2.5ms=2500us 对应 180°
// 不同舵机会有偏差,先用这组通用值,后面"角度不准"再校准
const int SERVO_MIN_US = 500;
const int SERVO_MAX_US = 2500;
const int SERVO_FREQ = 50; // 50Hz,周期 20ms = 20000us
// 你要驱动的舵机接在哪几个通道
const int CHANNELS[] = {0, 1, 2, 3, 4, 5};
const int NUM_SERVOS = sizeof(CHANNELS) / sizeof(CHANNELS[0]);
// 把"微秒脉宽"换算成 PCA9685 的 12 位计数值(0~4095)
// 一个周期 20000us 对应满量程 4096 个计数
int usToTicks(int us) {
return (int)((long)us * 4096 / 20000);
}
// 核心封装:给某个通道设角度
void setAngle(int channel, int deg) {
if (deg < 0) deg = 0;
if (deg > 180) deg = 180;
// 角度线性映射到脉宽
int us = map(deg, 0, 180, SERVO_MIN_US, SERVO_MAX_US);
pwm.setPWM(channel, 0, usToTicks(us));
}
void setup() {
Serial.begin(115200);
Wire.begin(); // ESP32 默认 SDA=21 SCL=22,需要别的引脚:Wire.begin(SDA, SCL)
pwm.begin();
pwm.setPWMFreq(SERVO_FREQ); // 全部 16 路统一 50Hz
// 上电先全部回中位,避免猛地弹一下
for (int i = 0; i < NUM_SERVOS; i++) {
setAngle(CHANNELS[i], 90);
}
delay(500);
Serial.println("PCA9685 ready");
}
void loop() {
// 让每个舵机依次扫一遍:0° -> 180° -> 90°
for (int i = 0; i < NUM_SERVOS; i++) {
int ch = CHANNELS[i];
Serial.printf("舵机通道 %d 扫动\n", ch);
setAngle(ch, 0); delay(400);
setAngle(ch, 180); delay(400);
setAngle(ch, 90); delay(400);
}
delay(1000);
}
几个值得你停下来看一眼的地方:
usToTicks()把脉宽换算成计数。一个 20ms 的周期被切成 4096 份,你的脉宽占多少微秒,就换算成多少份。50Hz 是这个换算的前提,所以setPWMFreq(50)一定要先调对。setAngle里做了 0~180 的钳位,防止你手滑传个 200° 把舵机顶到机械极限。- 上电先全回中位再开始,是个好习惯——不然舵机可能从未知位置猛弹到目标,机械臂上电就甩一下,容易撞坏结构。
你应该看到什么
烧录后打开串口监视器(115200):
- 看到
PCA9685 ready,说明 I2C 通了、板子认到了。 - 六个(你接了几个就几个)舵机一个接一个地动:每个先转到 0°,停一下,转到 180°,停一下,回到 90°,然后轮到下一个。
- 串口同步打印「舵机通道 X 扫动」。
- 主控不重启——这是验证供电分离成功的关键信号。如果一动就重启,直接跳到下面排查表第三行。
如果舵机动作干脆、到位、不抖,主控稳稳没重启,恭喜,多舵机驱动这关你过了。
故障排查表
| 现象 | 最可能的原因 | 怎么修 |
|---|---|---|
| 舵机抖动、嗡嗡响、不停微动 | 供电电流不足或电压不稳 / 共地不良 | 换更大电流的独立 5V 电源;检查独立电源地与主控地是否真共地;线材太细换粗 |
| 舵机完全不动 | V+ 没接独立电源 / 接到了 VCC | V+ 必须接 5V 独立电源,VCC 只是 3.3V 逻辑供电,两者别搞混 |
| 一让多个舵机动主控就重启 | 舵机在从主控取电,把主控拖欠压 | 舵机供电走独立 V+,绝不从主控 5V 取;确认共地 |
| 角度不准、0° 不到位或 180° 过头 | 该型号舵机脉宽边界和 500/2500us 不一致 | 微调代码里 SERVO_MIN_US/SERVO_MAX_US,逐个试到位 |
| 串口报 I2C 找不到设备 / 卡住 | SDA/SCL 接反、没接上拉、地址不对 | 核对 SDA/SCL 引脚和板子丝印;I2C 需要上拉(模块通常自带),见 上拉电阻;用 I2C 扫描确认地址是不是 0x40 |
| 上电瞬间舵机猛甩一下 | 上电时通道是随机/历史值 | setup 里先把所有通道设到中位再开始动作 |
两个变体玩法
变体一:级联到 32 路、48 路。 一块 PCA9685 是 16 路,做人形或者多足明显不够。它支持级联:把第二块板的地址跳线 A0 焊上,它的地址就变成 0x41,代码里再 new 一个 Adafruit_PWMServoDriver(0x41) 就行。两块板的 SDA/SCL 并在同一条 I2C 总线上,各管各的 16 路。地址跳线 A0~A5 六位,理论上能挂到 62 块、近千路,远超你需要。
Adafruit_PWMServoDriver board1 = Adafruit_PWMServoDriver(0x40); // 通道 0~15
Adafruit_PWMServoDriver board2 = Adafruit_PWMServoDriver(0x41); // 焊了 A0
// 两块都要 begin() 和 setPWMFreq(50)
变体二:做一张「扫动表情表」。 把一组动作写成数组,让多个舵机按节奏一起动,做出像呼吸、点头、招手这种连贯姿态。比如把眼睛、嘴、头三个舵机的目标角度按帧排成表,loop 里按帧推进,一个机器人小表情就出来了。这其实就是关键帧动画的雏形,小智那类桌面机器人的「卖萌」动作就是这么做的,可以参考 小智的运动控制。
动手挑战:两个舵机协调做抓取
光让舵机一个个轮流动太初级。给你一个真实场景的挑战:
用两个舵机模拟一次抓取。 一个当「手臂下压」(通道 0),一个当「夹爪开合」(通道 1)。要求动作有先后、协调,像真的去夹一个东西:
- 初始:手臂抬起(0°)、夹爪张开(180°)。
- 手臂缓慢下压到 90°——注意是「缓慢」,别一步到位。试试用 5° 为步长、每步 delay 20ms 的方式插值,让动作平滑。
- 到位后,夹爪合拢(180° → 60°),夹住。
- 手臂再抬回 0°,把「东西」提起来。
- 停 1 秒,松爪、复位,循环。
做完你会发现两个新问题,刚好是下一阶段要解决的:一是怎么让动作更顺滑(插值/缓动),二是手臂末端到底停在空间哪个点——这就不是「设角度」能直接回答的了,得算坐标。
小结·下一步
回头看,多舵机这关的核心就一句话:把控制和供电分家。控制交给 PCA9685 走 I2C 两根线,供电交给独立 5V 走 V+,主控只发命令。抖动、不动、重启这些坑,根上几乎都是供电和共地的问题,记住排查表前三行能帮你省下好几个晚上。
你现在能让一堆舵机听话地各就各位了。但机械臂真正的难题还没碰:你想让夹爪精确到达空间里某个 (x, y, z) 点,而不是手动一个个试角度——这需要把「末端位置」反算成「每个关节的角度」,也就是逆运动学。下一篇我们就啃这块硬骨头:机械臂运动学。