教程 · 中级 · 阅读约 18 分钟
用 PCA9685(I²C PWM 驱动器)驱动舵机
如何通过 I²C 总线用一块 PCA9685 驱动多个舵机:设定 PWM 频率、把角度换算成经过标定的脉冲宽度,并安全地给舵机供电。
单片机的硬件 PWM 通道只有寥寥几个。一两个舵机没问题,但一条机械臂要四个,六足机器人要十八个,而 Arduino 在你用完关节之前就先耗尽了定时器。PCA9685解决的正是这件事:一块小板子在硬件上生成十六路独立 PWM 信号,而所有指令只走两根 I²C线。在真实硬件上驱动机械臂模拟器里的每一个关节,用的就是这个办法。
为什么不干脆把舵机接到 Arduino 上
两个原因。第一是引脚:Uno 的定时器只够支撑几个 Servo 对象,再往上这个库就开始自己跟自己打架。第二条更要紧,是电源:舵机堵转时会拉 500–700 mA,四个同时动能拉到几安培。这股电流没法从 Arduino 的 5 V 引脚来——它会让板子欠压并在动作做到一半时复位。PCA9685 把两个问题都解决了。它把 PWM 生成卸载给一颗专用芯片,并从自己的 V+ 端子引入舵机电流,让大电流完全远离你的逻辑。
设置 PWM 频率
每个业余级舵机都期望 50 Hz 的控制信号——每 20 ms 一个脉冲。PCA9685 是整颗芯片设一个频率,所以在 setup() 里设一次:
#include <Adafruit_PWMServoDriver.h>
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40); // default I²C address
void setup() {
pwm.begin();
pwm.setPWMFreq(50); // 50 Hz — the standard servo refresh rate
}
现在十六个通道都以 50 Hz 脉冲。每个舵机保持的角度由脉冲的宽度决定,而不是频率——这是你能逐通道控制的那一部分。
角度到脉冲宽度——真正要紧的标定
舵机把脉冲宽度读作位置指令:大约1 ms 对应 0°,1.5 ms 对应居中,2 ms 对应 180°,每秒重复 50 次。SG90用的就是这套“脉宽即角度”的编码;PCA9685 只是把它挪到硬件里生成。
PCA9685 不直接接受毫秒。它把每个 20 ms 周期切成 4096 个计数(12 位分辨率),你告诉它脉冲要维持高电平多少个计数。在 50 Hz 下,一个整周期就是 4096 个计数 = 20 ms,于是:
- 1 ms ≈ 205 计数(0°)
- 1.5 ms ≈ 307 计数(90°)
- 2 ms ≈ 410 计数(180°)
这些是标称数值。真实舵机之间有差异——同一款 SG90 可能在 130 就到底、也可能到 490 才到底。**这是你必须标定的那一个数字。**给每个舵机存一对最小值和最大值,把你的角度映射进那个范围:
// Calibrate these two per servo by eye — see "Gotchas" below.
const int SERVO_MIN = 130; // counts at the servo's 0° hard stop
const int SERVO_MAX = 490; // counts at the servo's 180° hard stop
int angleToCount(int deg) {
return map(deg, 0, 180, SERVO_MIN, SERVO_MAX);
}
跳过标定,正是“下 90°”的指令在一个关节落到 78°、在另一个落到 96° 的原因——也是一只用未标定舵机搭起来的机械臂,永远到不了逆解求解器所要求的位姿的原因。
接线:两条电源轨,一个地
- 逻辑——来自 Arduino 的
VCC、GND、SDA、SCL。这只给芯片供电。 - 舵机电源——一路独立的 5–6 V 电源接到 V+ 螺丝端子,按所有舵机同时堵转时的电流配容,而不是按它们的空闲消耗。
- 共地——电源的地和 Arduino 的地必须连在一起,否则 PWM 信号没有参考,舵机会抽搐或者干脆不理你。承载指令的 I²C 线同样适用。
在 V+ 上并一只电容,吸收舵机启动时的电流尖峰;PCA9685 转接板上正好留了这么一块焊盘。
代码:让一个关节动起来
#include <Adafruit_PWMServoDriver.h>
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40);
const int SERVO_MIN = 130;
const int SERVO_MAX = 490;
int angleToCount(int deg) {
return map(deg, 0, 180, SERVO_MIN, SERVO_MAX);
}
void setServo(uint8_t channel, int deg) {
pwm.setPWM(channel, 0, angleToCount(deg)); // on at count 0, off at the angle count
}
void setup() {
pwm.begin();
pwm.setPWMFreq(50);
setServo(0, 90); // base to centre
setServo(1, 45); // shoulder
setServo(2, 120); // elbow
setServo(3, 10); // gripper open
}
void loop() {}
每次 setPWM(channel, 0, count) 的意思是“在周期开始时打开这个通道,经过 count 个计数后关闭”。要扫过一个关节,就在循环里按步长改变角度、每步之间加一个短 delay(),这样舵机有时间去动,而不是啪地跳过去。
坑
- **给每个舵机分别标定
SERVO_MIN/SERVO_MAX。**下发 0°,记下舵盘实际停在哪里,微调计数直到对上。180° 同样做一遍。这里花十分钟,省下的是下游每一个位姿。 - **永远不要用 Arduino 给 V+ 供电。**一个舵机也许撑得住,四个会让板子欠压复位。
- **电源按堵转配,不按空闲配。**电源不够大时,几个舵机一起动就会塌,整块板子跟着抖。
- **查地址。**默认是
0x40。跨接焊跳线可以把板子串起来,然后把新地址传给构造函数。 - **温柔地动。**朝目标一步步走比直接跳过去好——它降低了电流尖峰,也对塑料齿轮更温柔。
- **夹爪通道才是会把东西烧掉的那一路。**其他每个关节转到角度后就空转;夹爪夹着物体时,舵机是顶着一个东西堵转,持续吃大约半安培。零件一放到位就立刻松开,关于更细的 PWM 分辨率为什么买不到更细的力,见舵机夹爪力、堵转与柔顺。
一旦每个关节都听角度指挥,机械臂就可以处理几何问题了:把一个目标点变成够到它的关节角,而这正是二连杆臂逆解和机械臂模拟器存在的意义。
探索关联图谱
本教程所属的搭建
包含本教程的项目和学习路线。
延伸阅读