news 2026/9/28 1:52:41

树莓派4B+PCA9685舵机控制全攻略:从PWM抖动根源到平稳驱动

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
树莓派4B+PCA9685舵机控制全攻略:从PWM抖动根源到平稳驱动

舵机一上电就左右抖、走不到位、转起来像抽风——这在树莓派上玩舵机的人基本都遇到过。我最早用树莓派4B的GPIO口直接输出PWM来控制SG90时,就被这个问题折磨过两个晚上:代码看着没问题,频率也算对了,可舵机就是又颤又飘,时不时还发出高频滋滋声。后来把控制链路改成树莓派4B + PCA9685方案,由PCA9685这块16通道的硬件PWM驱动板来专职生成脉冲,问题才算真正解决。这篇东西就是我完整复盘下来的记录:为什么要这么改、硬件怎么接、代码怎么写、参数怎么算、抖动怎么排查,全部给你捋顺。不管你是刚入手树莓派准备做机械臂还是机器人小车,还是被PWM抖动搞到头大的老玩家,按这套流程走都可以少踩一半的坑。

1. 为什么树莓派直接输出PWM会抖:先搞清楚问题根源

1.1 树莓派原生PWM的硬伤

树莓派4B的实际硬件PWM通道非常有限,正常情况下能用的就那么两路,分别对应GPIO12、GPIO13,再加上它的互补输出一共也就四个引脚。硬件PWM够用吗?做一个两轴云台勉强够,想控制四足机器人、六足机器人那种八个甚至十几个舵机的项目,直接麻爪。

于是很多教程会让你用软件PWM,也就是通过GPIO库在Python里不断翻转电平来模拟出波形。问题是,树莓派跑的是Linux,它不是实时系统,Python解释器执行一条指令的时机本来就受操作系统调度影响,再加上time.sleep()的最小精度往往只有几毫秒,GPIO翻转的时间点就会随机漂移。

舵机需要的是什么?一个周期稳定在20毫秒左右的方波,脉冲宽度精确到几十微秒,而且每一帧的周期要均匀。软件PWM恰恰做不到这一点:今天你的树莓派后台多跑一个进程,明天WiFi信号不好触发网卡重传,PWM波形就会毛刺不断。表现出来就是舵机乱抖、发热、走到目标角度后还来回挣扎。

1.2 PCA9685到底解决了什么

PCA9685是NXP出品的一块16路PWM控制器,走I2C总线,输出12位分辨率(0到4095)的脉冲。它和树莓派的关系很简单:树莓派只负责把“频率”和“每个通道的占空比”这两个参数写进去,之后PCA9685内部用独立时钟自己生成PWM波形,跟树莓派CPU的调度完全解耦。

这一下就把问题解决了一大半。就算树莓派后台在编译程序、下载固件、跑神经网络推理,PCA9685这边的波形依然纹丝不动。而且它是16通道的,一路I2C总线最多能挂多块PCA9685,通过修改板上的地址跳线,最多可以扩展出上千路PWM通道,应付机器人、机械臂、云台的舵机阵列绰绰有余。

还有一个细节很多人忽略:PCA9685的每个通道都有独立的ON和OFF时间寄存器,意味着你可以让通道之间错开相位启动。这一点在做多关节动作时特别有用,能显著减小电源的瞬间冲击。

1.3 什么场景该用、什么场景别用

PCA9685不是万能药。如果只是控制一两个舵机,我建议直接用树莓派的硬件PWM引脚加pigpio库,接线更少,延迟更低,没必要折腾一块外设板。

如果控制的是需要几十kHz频率的电机调速,PCA9685最高PWM频率大概在1.5kHz左右,远远不够,得换专用的电机驱动方案。

PCA9685的最佳场景很明确:舵机数量多、动作要求平滑、需要长期稳定运行的机器人项目。我现在的机械臂项目就是树莓派4B + 三块PCA9685,分别管理六个关节舵机和两个云台舵机,实测下来无论是脉冲稳定性还是多路扩展性都让人放心。

2. 硬件清单、接线与供电方案

2.1 器件清单和选购避坑

标准配置如下:

  • 树莓派4B主板,任意内存版本都可以
  • PCA9685模块一块,舵机多可以买多块
  • 舵机若干,我用的是SG90、MG996R混合搭配
  • 5V外置电源,注意电流余量
  • 母对母杜邦线若干
  • 1000µF以上电解电容一个
  • 可选:示波器,I2C延长线

买PCA9685模块时最大的坑是晶振问题。看板子上有没有安装一个25MHz的石英晶振。有晶振的版本,PWM时钟由外部晶振锁定,非常稳定;没有晶振的版本,用的是PCA9685芯片内部振荡器,出厂精度一般,还容易受温度影响,表现出来就是微小漂移和周期抖动。做高精度舵机控制,尽量买带晶振的板子,价格也就多几块钱,省一堆麻烦。

舵机选择也要看你的应用。SG90是9克小舵机,扭矩小、电流小,适合做云台和人形机器人的小关节。MG996R是金属齿轮大扭矩舵机,峰值电流能到2A以上,做机械臂的主关节更合适。买舵机时看清楚是标准舵机还是360度连续旋转改版,连续旋转舵机的PWM映射和标准舵机完全不同,本文讲的是标准舵机。

2.2 接线步骤和共地原则

接线看起来简单,但很多人第一步就接错了。树莓派4B的I2C接口在GPIO2(SDA)和GPIO3(SCL),物理引脚分别是3号和5号引脚。对照表如下:

功能PCA9685引脚树莓派4B引脚
I2C数据SDAGPIO2(物理引脚3)
I2C时钟SCLGPIO3(物理引脚5)
逻辑电源VCC3.3V(物理引脚1)
逻辑地GNDGND(物理引脚6)
舵机电源正极V+外置5V正极
舵机电源负极GND外置5V负极

这里最关键的一条原则是“共地”。如果你用外置电源给舵机供电,外置电源的GND必须和树莓派的GND连在一起。道理很简单:I2C的信号是相对的电压差,如果两边的地电位不一致,树莓派发出的0和1在PCA9685看起来就会飘忽不定,轻则通信出错,重则烧芯片。

舵机信号线接到PCA9685的对应通道输出口,注意三个引脚的方向。标准舵机接口是三根线:棕线或黑线是GND,红线是VCC(即正极),橙线或黄线是信号线。接舵机时先确认舵机端的线序,尤其便宜舵机的线序偶尔会不按套路来,接反之前最好用万用表测一下。

2.3 供电设计的计算思路

供电是整个系统能不能稳定的核心。很多抖动问题根本不是PWM质量问题,纯粹是电源被拉垮了。

先算电流。一只SG90静止状态下大约是几十毫安,转动时200毫安左右,堵转时可能飙到500到700毫安。MG996R静止电流低,但带载转动轻松上1A,堵转瞬间冲到2A以上都不奇怪。如果系统里有6只MG996R同时启动,瞬时电流需求可能达到10A以上。

供电电流估算经验公式:总电流 = 舵机数量 × 单舵机峰值电流 × 同时系数。同时系数一般取0.5到0.8,因为不是所有舵机会在同一瞬间满负载转动,但做机械臂时如果你故意让所有关节同时大力抓取,就得按最大系数算。

给6只MG996R供电,我实际用了12V转5V的降压模块,峰值能输出12A。没外置电源只用树莓派USB-C口供电的话,一个MG996R的启动电流就能把树莓派电压拉低,轻则舵机抽搐,重则树莓派直接重启。

电源线也要讲究。同样5V电源,如果导线太细,走线过长,负载一上来线路压降就足以让舵机电压跌破4.5V。舵机电压不足的典型表现是:无负载时正常,一加载就抖或者力不从心。电源线尽量粗、短,可以用AWG18以上的硅胶线。另外,在PCA9685的V+和GND之间并一颗1000µF以上电解电容,能显著吸收瞬时电流尖峰,这是花几毛钱解决大问题的好习惯。

3. 环境准备:启用I2C并验证设备

3.1 开启树莓派I2C接口

树莓派官方系统的I2C接口默认是关闭的,需要手动打开。在终端运行:

sudo raspi-config

进入“Interface Options”,选择“I2C”,回车,选择“Yes”,系统会提示I2C内核模块已启用。重启后生效,或者命令行加载模块:

sudo modprobe i2c_dev

重启完成之后,先确认I2C设备节点存在:

ls /dev/i2c*

正常会输出/dev/i2c-1。树莓派4B的I2C控制器默认挂在1号总线上。

然后安装必要工具:

sudo apt update sudo apt install -y python3-pip python3-smbus i2c-tools

注意:如果你用的是Raspberry Pi OS Bookworm版本,直接用pip3安装Python包时可能会遇到“externally managed environment”的提示。这种情况可以用虚拟环境,或者加--break-system-packages参数强制安装,测试用怎么方便怎么来。

3.2 安装依赖与验证设备地址

验证PCA9685是否被系统识别,运行:

i2cdetect -y 1

如果接线正确,会在地址0x40处看到一个编号。PCA9685的默认I2C地址就是0x40,这是由板上A0到A5地址引脚默认全部接低电平决定的。

如果你插了多块PCA9685,需要给每块板设置不同地址。方法是把板上的A0、A1等引脚焊接到VCC或GND来改变地址值:A0接到VCC,地址变成0x41;再把A1也接上,地址变成0x42,以此类推。这个操作要在断电状态下进行,焊完之后再上电测试。

地址验证很重要,我见过不少“舵机怎么没反应”的求助,最后发现是i2cdetect根本没扫到设备,原因是SDA和SCL接反了,或者忘了共地。先用i2cdetect扫地址,再跑控制代码,能省去一大半无谓的排查时间。

Python控制还需要安装Adafruit的ServoKit库:

pip3 install adafruit-circuitpython-servokit

这个库封装了PCA9685寄存器的底层操作,并提供Servo类,把“角度转脉冲宽度”的换算也一并解决了,非常适合快速上手。

4. 软件实现:从PWM频率到舵机角度

4.1 PCA9685的PWM频率与预设分频计算

PCA9685内部时钟基准是25MHz,输出是12位分辨率,也就是每个周期分成4096个时间片。芯片通过一个8位的预设分频寄存器(PRE_SCALE)来调整最终PWM频率,计算公式是:

prescale = round(25000000 / (4096 × 频率)) - 1

标准舵机需要50Hz的PWM频率,代入计算:

prescale = round(25000000 / (4096 × 50)) - 1 = round(122.07) - 1 = 122 - 1 = 121

也就是把PRE_SCALE寄存器设为121时,实际PWM频率为:

实际频率 = 25000000 / (4096 × (121 + 1)) ≈ 50.03Hz

周期约为19.99毫秒,误差不到千分之一,完全可以接受。

这个计算过程建议自己算一遍,别看库函数一行代码就跳过了。因为实操中如果换了一个不是25MHz晶振的板子,或者想调成60Hz、100Hz的非标准频率,只有懂得原理才能自己改参数,不至于全靠试。

4.2 舵机角度与脉冲宽度的换算

舵机的转角控制规则是:周期固定为20毫秒,脉冲宽度决定角度。标准舵机以SG90为例,0°对应0.5毫秒脉宽,180°对应2.5毫秒脉宽,中间基本呈线性关系。

在PCA9685的4096个时间片里,每个时间片对应的时间是多少呢?

20毫秒 / 4096 ≈ 4.88微秒

所以脉宽换算成tick数的公式是:

tick数 = 脉宽(微秒) × 4096 / 20000

几个关键点可以直接查表:

舵机角度脉宽tick数
0°0.5ms102
45°1.0ms205
90°1.5ms307
135°2.0ms410
180°2.5ms512

注意不同舵机的脉宽范围会有差异,比如MG996R大多能到0.5到2.5毫秒,而有些舵机只能到0.7到2.3毫秒,强行驱动到极限位置会咔咔响,久而久之齿轮就崩了。所以写代码时,最好把每个舵机的可工作范围都测出来,单独设置。

4.3 基于Adafruit ServoKit的完整控制代码

如果你是第一次跑通整条链路,直接用ServoKit是最省事的:

from adafruit_servokit import ServoKit import time kit = ServoKit(channels=16) # 设置通道0的脉宽范围,单位是微秒 kit.servo[0].set_pulse_width_range(500, 2500) # 转到90度 kit.servo[0].angle = 90 time.sleep(1) # 转到30度 kit.servo[0].angle = 30 time.sleep(1) # 释放舵机,取消信号 kit.servo[0].angle = None

这里angle会帮我们自动完成角度到tick数的换算,内部走的就是4.2节的公式。运行这段代码前,先用i2cdetect确认设备在0x40,否则ServoKit会报错找不到设备。

ServoKit有个细节:每次设置angle,它内部会立即把脉宽写入PCA9685寄存器,PWM随之变化,所以不需要额外调用刷新函数。实测纯Python环境下,从调用angle到波形更新,延迟通常小于几毫秒,很多项目完全够用。

4.4 手动写寄存器:想彻底搞懂底层就这么来

ServoKit帮我们封得太高级,很多读者其实更想知道底层到底干了什么。下面给一套不依赖任何库的手写寄存器方案,用smbus2直接操作I2C。

import smbus2 import time bus = smbus2.SMBus(1) PCA9685_ADDR = 0x40 MODE1 = 0x00 PRESCALE = 0xFE CHANNEL0_ON_L = 0x06 def setup_pca9685(freq=50): osc_clock = 25_000_000 prescale = int(round(osc_clock / (4096 * freq))) - 1 # 读取当前MODE1 oldmode = bus.read_byte_data(PCA9685_ADDR, MODE1) # 进入SLEEP模式,才能修改PRE_SCALE bus.write_byte_data(PCA9685_ADDR, MODE1, (oldmode & 0x7F) | 0x10) # 写入分频值 bus.write_byte_data(PCA9685_ADDR, PRESCALE, prescale) # 恢复原模式,并从sleep唤醒 bus.write_byte_data(PCA9685_ADDR, MODE1, oldmode) time.sleep(0.005) # 设置RESTART,让PWM重新输出 bus.write_byte_data(PCA9685_ADDR, MODE1, oldmode | 0x80) def set_channel_pwm(channel, off_ticks): # 每通道4个寄存器:ON_L, ON_H, OFF_L, OFF_H reg = CHANNEL0_ON_L + channel * 4 # ON设为0,表示每个周期从第0个tick开始输出高电平 bus.write_byte_data(PCA9685_ADDR, reg, 0x00) bus.write_byte_data(PCA9685_ADDR, reg + 1, 0x00) # OFF写入目标tick数,12位值拆成低8位和高4位 bus.write_byte_data(PCA9685_ADDR, reg + 2, off_ticks & 0xFF) bus.write_byte_data(PCA9685_ADDR, reg + 3, (off_ticks >> 8) & 0x0F) def set_servo_angle(channel, angle, us_min=500, us_max=2500): # 角度转脉宽,再转tick数 us = us_min + (angle / 180.0) * (us_max - us_min) off_ticks = int(us * 4096 / 20000) set_channel_pwm(channel, off_ticks) setup_pca9685(50) set_servo_angle(0, 90) set_servo_angle(1, 45)

这里有几个细节要解释。第一,为什么改PRE_SCALE前必须进SLEEP模式?因为PCA9685要求分频寄存器只能在睡眠状态下修改,否则芯片运行状态不可预料。第二,为什么ON要写0?我们希望每个周期从0开始就输出高电平,然后到OFF的tick数时拉低,这样才产生一个从周期起点开始的脉冲。第三,OFF寄存器高4位掩码用0x0F,因为12位数据的高4位只占用一个字节的低4位,剩下的高位必须留0。

这份代码跑出来的效果和ServoKit一模一样,但你每一步都在做底层操作,以后遇到奇葩问题也更容易定位。

4.5 告别抖动的进阶操作:平滑渐变

很多舵机抖动的根源不是PWM精度,而是你的程序让舵机“瞬移”。比如从0度直接跳到180度,舵机为了在最短时间内到达目标,会以最大速度猛冲,产生的机械冲击和电流尖峰都会让系统一震,后续控制也会变形。

我习惯在代码里加平滑渐变,让舵机按一定步长逐步逼近目标角度:

def smooth_move(kit, channel, target, step=2, delay=0.02): current = kit.servo[channel].angle if current is None: current = target if target > current: value = current while value < target: value = min(value + step, target) kit.servo[channel].angle = value time.sleep(delay) elif target < current: value = current while value > target: value = max(value - step, target) kit.servo[channel].angle = value time.sleep(delay)

step代表每帧变化的角度,delay代表帧间隔。step=2、delay=0.02秒,意味着舵机每秒大致走100度,这个速度对做机械臂抓取比较平滑。你可以根据项目需要调节这两个参数:想快就加大step,想柔和就减小step、加大delay。

这里背后有两个关键点。第一,步进式的角度更新把一次大电流冲击拆成了若干小电流冲击,电源负担小得多。第二,每帧之间留了20毫秒以上的间隔,给舵机完整执行动作和稳定下来的时间,避免连续指令互相冲突。

如果你在做需不停切换角度的动作序列,还能用预计算脉宽表。把整个动作的每个角度提前映射成tick数存到数组里,运行时直接查表写寄存器,比每次做浮点运算快,也能减少计算抖动。

5. 常见问题排查与经验心得

5.1 抖动问题定位与解决

抖动分几种,解决思路完全不同。

第一类是持续性的高频抖动。舵机静止在某个角度时还嗡嗡响、左右小幅摆动。先在电源上下手:用万用表量一下PCA9685的V+和GND之间的电压,看是不是在4.8V以上。如果电压稳定,那就是PWM脉冲本身的问题,换一块带外部晶振的PCA9685再试。

第二类是动作过程中的卡顿或抽搐。这通常是平滑控制没做,或者程序里执行了太多高负载任务导致I2C写入不及时。先检查你同时开了多少线程和后台任务,再按4.5节的方式加入渐变逻辑。

第三类是偶发性的随机抖动。这种可能就是共地不良、接线松动、或者I2C数据线太长受干扰。把I2C线缩短到20厘米以内,适当降低I2C波特率,或者在树莓派配置里加一行dtparam=i2c_arm_baudrate=100000,让通信更稳定。

有示波器的话,可以直接量PCA9685输出的PWM引脚看波形。正常波形应该是矩形波,跳变沿干净、周期均匀。如果频率漂移明显,芯片时钟有问题;如果波形毛刺多,供电或接地有问题。示波器的波形图比肉眼判断靠谱得多。

5.2 常见故障速查表

现象可能原因排查方法
舵机完全不转PCA9685没供电或I2C接线错误i2cdetect确认设备地址,测V+电压
只有部分通道有输出舵机信号线插错通道对照通道编号检查接线
舵机一直往一个方向转输入了超出范围的脉宽检查角度映射公式和脉宽范围
静止时高频抖动供电不稳或无晶振板子频率漂移测电压,换带晶振的板子,加滤波电容
动作时卡顿抽搐缺少平滑过渡或程序负载过高加渐变逻辑,减少后台任务
多个舵机同时动作时掉电电源电流余量不足重新按峰值电流总和选用电源
i2cdetect扫不到设备SDA/SCL接反,或忘了共地重新查接线,确保共地

这张表我建议收藏起来,每次排障直接从现象到根因,不用每次重新人肉排查一遍。

5.3 我踩过的几个坑和最终建议

第一个坑是刚开始图便宜,买了没有晶振的PCA9685,结果舵机静止时总是轻微颤抖,用示波器测了PWM频率,发现中心频率在49.7Hz到50.3Hz之间来回飘。后来换了带25MHz晶振的板子,波形立刻稳了。所以我现在给所有朋友的建议都是:高精度舵机控制,PCA9685要选带晶振版本。

第二个坑是电源。一开始我用一个5V 2A的手机充电器给4只SG90供电,以为够用,结果舵机一转,电压就跌到4.2V,不仅舵机抖,树莓派的声音也开始怪叫。后来换成专门的大电流5V电源,问题立刻消失。千万别拿“额定5V 2A”的电源去带多个力矩舵机,要用就按峰值电流对总需求做足余量。

第三个坑是贪快,直接让舵机从0度瞬移到180度。第一版机械臂调试时,每次快速摆动都导致电源电压被瞬间拉低,其他舵机跟着抖动。加上平滑渐变之后,整个机械臂的动作才稳定下来。这事告诉我,舵机控制不光是发出正确的PWM波形,还要考虑机械系统的负载和电源的物理承受能力。

最后再分享一个小技巧。如果你想把舵机初始化得更有仪式感,可以开机后先让所有通道输出一个安全的中间角度(比如90度),停半秒,再执行真正的动作序列。这样做可以避免上电瞬间舵机突然冲到一个极端位置,既保护舵机齿轮,也让机械结构更安全。特别是在多关节机械臂上,这个习惯能少扭断好几个塑料齿轮。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 1:52:19

IEC 104测试工具实战:协议解析、参数配置与自动化调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:52:00

TSMaster实战:汽车总线报文分析与图形化显示全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:51:50

RT-Thread BSP移植到Keil5:GD32H759I-EVAL实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:51:14

计算机组成原理:DMA方式原理、三种传送方式与408考点解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:50:53

DMA控制器详解:从周期挪用原理到STM32实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:50:35

K230边缘计算实战:YOLOv5与YOLOv8模型部署性能对比与优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华