树莓派扩展16路PWM控制舵机:PCA9685接线、寄存器配置与Python驱动实战

📅 发布时间:2026/10/17 1:21:54
树莓派扩展16路PWM控制舵机:PCA9685接线、寄存器配置与Python驱动实战
先交代一下背景。我最早碰 PCA9685是因为自制的机器人需要 16 路舵机树莓派原生引脚根本不够用软件模拟 PWM 一多就发抖CPU 占用高得离谱。后来把树莓派控制16路PWM输出的PCA9685模块这套方案完整跑通才真正体会到这类扩展芯片解决的从来不只是引脚不够这么简单它还顺手把实时性问题一起解决了。这篇文章就是我从接线、寄存器、频率计算、Python 驱动到供电方案和故障排查的全过程复盘给正在做舵机集群、LED 阵列、电机调速的朋友一份能直接照着操作的手册。不管你是第一次听到 I2C 的纯新手还是已经会跑示例代码但想彻底搞懂原理的老手都可以按需跳读对应章节。1. 为什么需要16路PWM扩展树莓派原生通道根本不够用1.1 树莓派PWM的真实家底树莓派有 40 个 GPIO 引脚看起来资源充足但如果你真去查引脚复用表就会发现能作为硬件 PWM 使用的引脚非常有限。所谓硬件 PWM 是指芯片内部有一套专门的外设可以在不占用 CPU 的情况下自动输出波形而大多数人用的RPi.GPIO.PWM其实跑的是软件 PWM靠一个后台线程不断翻转电平来模拟。软件 PWM 的问题在小负载时不明显控制一个舵机做简单动作完全没问题。如果你接 12 个甚至 16 个舵机每个舵机又要同时连续输出 20ms 周期的脉冲那个后台线程会被打断得乱七八糟波形周期不稳定舵机就开始嗡嗡响、角度发抖。更难受的是树莓派的 CPU 会被这种低级操作消耗掉一大块你想同时再跑图像识别或者传感器融合系统响应立刻变得卡顿。所以我的结论很直接做多路 PWM 控制特别是多路舵机控制就别折腾树莓派原生 PWM 了。这颗芯片不是干这个的。1.2 PCA9685把发波形这件事外包给专用芯片PCA9685 是一颗 I2C 接口的 16 通道 PWM 驱动芯片每通道 12 位分辨率。说得直白一点就是树莓派通过两根线告诉芯片哪个脚该输出多少,剩下 16 路波形全部由芯片自己产生树莓派可以去做别的事情。这个架构很像公司里的分工树莓派是老板只负责下达决策指令PCA9685 是执行层把老板一句话变成 16 个下属各自持续的稳定工作。老板不需要每秒去盯着每个下属的手部动作只有在任务变化时才需要说一句。对于需要平滑插值的机械臂、六足机器人这种分工的价值非常大你可以每隔几十毫秒计算一次新的目标角度然后把脉宽指令发给芯片中间的状态完全由芯片维持。更关键的是PCA9685 支持级联。芯片上有 A0~A5 六个地址引脚通过跳线可以设置不同 I2C 地址。一条 I2C 总线上挂两块、三块甚至更多模块都没问题每个模块提供 16 路 PWM。换算一下树莓派只用了两个 GPIO 引脚就能换回上百路稳定的 PWM 输出这个扩展效率是塞满 GPIO 再用软件模拟完全比不了的。1.3 这个方案适合谁基础要求是什么PCA9685 最常见的落地场景有三类舵机集群控制四足、六足机器人、机械臂、云台这是最典型的使用场景。舵机标准信号频率大约 50HzPCA9685 的设计目标里就包含了这个场景。LED 阵列调光呼吸灯、跑马灯、灯牌、氛围灯。LED 需要较高 PWM 频率来避免肉眼可见的闪烁用 PCA9685 一样能跑。直流电机调速配合 TB6612、DRV8833 这类电机驱动板用 PWM 信号控制电机转速。基础要求方面如果你是完全没接触过树莓派 GPIO 的纯新手建议先把点亮 LED、读取按键这类简单项目跑一遍再上手这个模块如果你已经会写基本的 Python但第一次接触 I2C 和寄存器那直接跟着下文走问题也不大。下面我从接线开始一步步把整个链路拆开。2. 接线和地址识别让系统先认出PCA96852.1 模块引脚与树莓派引脚对照表常见的 PCA9685 模块丝印一般有VCC、GND、SDA、SCL、OE、V以及标着0~15的 PWM 输出口。第一次上手时最容易被搞混的就是VCC和V这两个完全不是一回事。我这里建议的接线方式如下模块引脚接哪里说明VCC树莓派 3.3VPCA9685 逻辑电源一般接 3.3V 没问题GND树莓派 GND逻辑地必须和树莓派共地SDA树莓派 GPIO2物理引脚 3I2C 数据线SCL树莓派 GPIO3物理引脚 5I2C 时钟线OE树莓派任意 GPIO 或直接接 GND输出使能低电平有效不能悬空V外部负载电源正极给舵机、LED 等负载供电不直接控制芯片逻辑OUT0~15舵机信号线 / LED 电阻 / 驱动板 PWM 输入波形输出口你可能会看到很多教程说 VCC 接 5V这也不是不行但 VCC 接 3.3V 更稳妥。因为树莓派 I2C 总线是 3.3V 逻辑模块逻辑供电接 3.3V 可以有效避免电平不匹配的问题。V 才是给负载用的具体电压取决于你的舵机或者灯带规格常见舵机用 5V 或 6V。2.2 默认地址 0x40 和 A0~A5 跳线PCA9685 的 I2C 地址由 A0~A5 六个引脚决定。绝大多数模块默认 A0~A5 全部悬空或接低电平所以默认地址是0x40。如果你看到的模块不是这个地址大概率是出厂时跳线电阻不一样或者你手里这块板子地址被改过了。地址计算公式很简单0x40 二进制权重。其中 A0 是最低位A5 是最高位。举例来说A0 拉高地址变成0x41A1 拉高地址变成0x42A5 拉高地址变成0x60。实际操作中改地址最常见方式是焊接焊盘。很多模块会把 A0~A5 对应的焊盘做成通断可调的样子你用烙铁把某个焊盘粘连就相当于把对应位拉高。改完以后代码里的地址常量也要同步改不然后面 i2cdetect 扫描会一头雾水。如果你要级联多块模块第一块保持0x40第二块改成0x41第三块改成0x42依次类推保证总线上每个设备的地址唯一即可。2.3 开启树莓派 I2C 并用 i2cdetect 验证树莓派系统里 I2C 默认不是开启状态。开启方式有两种任选其一运行sudo raspi-config进入 Interface Options选择 I2C启用后重启或者手动修改/boot/config.txt确认存在dtparami2c_armon这一行然后重启。重启后安装工具和 Python 库sudo apt update sudo apt install -y i2c-tools python3-smbus2然后执行i2cdetect -y 1这里的1表示 I2C 总线编号。树莓派 40 针引脚上的 SDA/SCL 对应的是 bus 1。如果硬件都正常扫描结果里应该能看到一个40。如果你改了地址就显示对应数值。如果扫描结果里什么都没有或者整个表格刷出一大片地址别急着换芯片。第 6 节我会把排查思路完整写出来这里先记住一个原则先看系统配置再看物理接线最后才怀疑芯片本身。3. 寄存器与频率计算把芯片内部机制吃透再写驱动3.1 必须认识的几个关键寄存器PCA9685 的控制全部通过寄存器完成不需要什么复杂的 SDK。下面这几个寄存器你写代码前最好先有个概念寄存器地址名称作用0x00MODE1芯片工作模式包含 SLEEP、RESTART、AI 自动递增等位0x01MODE2输出极性、输出驱动类型开漏/推挽0x06~0x09LED0_ON_L ~ LED0_OFF_H第 0 通道的 ON/OFF 计数值每通道占 4 个寄存器0x0A~0x45后续通道寄存器第 1~15 通道每通道 4 个寄存器连续排列0xFEPRE_SCALEPWM 频率预分频值0xFF保留无实际用途注意 MODE1 寄存器里的 SLEEP 位bit4这个位决定了你能否修改 PRE_SCALE。改频率之前必须先让芯片进入 SLEEP改完 PRE_SCALE 之后再唤醒芯片最后置 RESTART 位恢复输出。很多自己写驱动的初学者改了 PRE_SCALE 没生效就是漏了 SLEEP/RESTART 这个流程。MODE2 里的 OUTDRV 位决定输出级是开漏还是推挽。常见模块默认输出是开漏模式靠板载电阻把输出拉到 V。对这个位不需要频繁改动但如果你的模块输出电平总是不正常回读一下 MODE2 看看有没有被人为改过是值得的。3.2 PRE_SCALE 公式的推导过程PCA9685 内部振荡器频率是 25MHzPWM 周期由 12 位计数器控制也就是从 0 数到 4095总共 4096 个数。公式是这个PRE_SCALE round(25_000_000 / (4096 * 频率)) - 1这个公式不是凭空来的。每一步计数对应的时间等于25MHz / (PRE_SCALE 1)分频之后的时钟周期而 4096 步凑成一个完整 PWM 周期所以最终 PWM 频率就是PWM频率 25MHz / ((PRE_SCALE 1) * 4096)反推一下就得到上面的公式。实际算两个常用频率50Hz舵机标准频率25MHz / (4096 * 50) 122.07四舍五入取整为 122再减 1所以PRE_SCALE 121。1526Hz高频 LED 调光25MHz / (4096 * 1526) 3.997四舍五入为 4再减 1所以PRE_SCALE 3。写入PRE_SCALE 121后的实际频率是25MHz / ((1211)*4096) 50.03Hz误差不到 0.1%对舵机毫无影响。另外要注意PRE_SCALE 的有效值不能无限大也不能无限小。频率太低时计算出来的预分频值超过寄存器上限频率太高时预分频值低于 3 芯片也不接受。所以这颗芯片的可用频率范围大致在几十赫兹到 1526Hz 左右需要更高频 PWM 时应该换方案而不是硬压公式。3.3 12位计数器、占空比和脉宽的换算逻辑PCA9685 的每路输出本质上是比较器逻辑芯片内部计数器到ON_cnt时输出拉高到OFF_cnt时输出拉低。所以ON_cnt和OFF_cnt是 12 位的计数时刻占空比由这两个计数值共同决定。最简单的用法是固定ON 0只调整OFF。比如要 50% 占空比就设置OFF 2048。12 位分辨率意味着一个周期被分成 4096 份每份在 50Hz 下大约 4.88 微秒。如果你要的是精确脉冲宽度比如舵机常见的 1.5ms 脉宽在 50Hz 下周期是 20ms比例是1.5/20 7.5%那么OFF 4096 * 0.075 307。这里有个容易踩的坑PCA9685 某些寄存器值有特殊含义。最关键的是OFF 0会导致输出一直为低OFF 0x1000也就是 4096会导致输出一直为高。很多初学者的代码里占空比算出来恰好接近 0 时通道会突然死掉。所以我的驱动代码里会强制把 OFF 限制在1~4095之间。4. 一套可复制的Python驱动从原始寄存器到舵机角度映射4.1 环境准备和最小读写函数在树莓派上推荐直接用smbus2这个库比老的smbus更好用错误处理也更友好。先安装sudo apt install -y python3-smbus2为了不用每次加sudo才能访问 I2C把当前用户加进i2c组sudo usermod -a -G i2c $USER然后退出当前终端重新登录或者直接重启再继续。最小读写函数如下import smbus2 ADDR 0x40 bus smbus2.SMBus(1) def read_reg(reg): return bus.read_byte_data(ADDR, reg) def write_reg(reg, value): bus.write_byte_data(ADDR, reg, value)写入一个通道的 PWM 时要用到0x06作为第 0 通道的起始地址。每个通道占 4 个寄存器ON_L、ON_H、OFF_L、OFF_H。所以通道channel的起始寄存器是0x06 channel * 4。4.2 完整驱动类代码下面这个类可以直接抄走用。它把初始化、设置频率、设置通道脉冲宽度、设置舵机角度都封装好了import time import smbus2 class PCA9685: def __init__(self, i2c_bus1, addr0x40): self.bus smbus2.SMBus(i2c_bus) self.addr addr self.freq 50 self.set_pwm_freq(50) def _read(self, reg): return self.bus.read_byte_data(self.addr, reg) def _write(self, reg, value): self.bus.write_byte_data(self.addr, reg, value) def set_pwm_freq(self, freq): prescale int(round(25_000_000.0 / (4096.0 * freq)) - 1) if prescale 3: raise ValueError(请求的频率太高PCA9685 支持不了) old_mode self._read(0x00) # 先进入 SLEEP才能修改 PRE_SCALE self._write(0x00, (old_mode 0x7F) | 0x10) self._write(0xFE, prescale) # 唤醒 self._write(0x00, old_mode) time.sleep(0.005) # 置 RESTART恢复 PWM 输出 self._write(0x00, old_mode | 0x80) self.freq freq def set_pwm(self, channel, on0, off0): off int(max(1, min(4095, off))) reg 0x06 channel * 4 self._write(reg, on 0xFF) self._write(reg 1, (on 8) 0x0F) self._write(reg 2, off 0xFF) self._write(reg 3, (off 8) 0x0F) def set_pulse_ms(self, channel, pulse_ms): counts int(round((pulse_ms / 1000.0) * self.freq * 4096)) - 1 self.set_pwm(channel, 0, counts) def set_servo_angle(self, channel, angle, min_pulse_ms0.5, max_pulse_ms2.5): ratio max(0.0, min(1.0, angle / 180.0)) pulse_ms min_pulse_ms (max_pulse_ms - min_pulse_ms) * ratio self.set_pulse_ms(channel, pulse_ms)代码里我给set_pwm做了一个钳位操作OFF 绝对不会落到 0也不会超过 4095。刚开始你可能不理解为什么要这么小心等你真的遇到某个通道突然没输出了再回来读这一节就会明白。4.3 舵机角度到脉冲宽度的换算舵机不是直接接收角度的它接收的是特定宽度的脉冲。最常见的约定是0.5ms对应 0°2.5ms对应 180°1.5ms对应 90° 中位。但请注意不同品牌的舵机工作脉宽区间差异很大有的舵机是0.6ms ~ 2.4ms有的是1ms ~ 2ms。第一次用某个具体舵机之前优先看舵机手册上面的脉冲范围没有手册就先用中位值 1.5ms 试再慢慢扩张边界。下面的换算表和代码对应角度脉宽50Hz 下的 OFF 计数值近似0°0.5ms10190°1.5ms306180°2.5ms511所以没必要一直记着这个数字表直接让代码去做映射即可pca PCA9685() pca.set_pwm_freq(50) pca.set_servo_angle(0, 90)注意一点有些舵机标称 0°~180°但实际旋转范围可能只有 0°~270° 或者被限制得更小。你手动扫角度时如果听到舵机撞到机械限位发出的咔哒声要立刻停止继续增大角度否则会损伤舵机齿轮。4.4 同一颗芯片上混用不同负载的坑PCA9685 有一个硬约束所有 16 路共享同一个 PRE_SCALE也就是同一个 PWM 频率。你不能让第 0 路跑 50Hz 舵机同时让第 1 路跑 1kHz 的 LED 呼吸灯。这个点很多人是在做到一半才发现的。解决方案也简单需要不同频率就分模块。我会习惯把舵机全部放在一块模块上把 LED、电机调速放在另一块模块上两块地址分别设置成0x40和0x41。这样频率规划清晰调试时也更不容易互相干扰。5. 供电边界与负载选型PCA9685不是电源模块5.1 16路PWM不等于16路大电流驱动这是我见过最容易出问题的误解。很多人看到模块上写着 16 路输出就以为可以直接把 16 个舵机或者灯带的电源线插到模块上结果模块发烫、输出不稳定、芯片烧掉。PCA9685 本质上只是一个信号发生器。它输出的是 PWM 波形不是电力配送。舵机这种设备标准接法是三线分开信号线接 PCA9685 的 OUT 脚电源线接外部电源地线接 GND。如果把舵机的红黑电源线也接到 OUT 脚上模块的输出级根本承受不了舵机启动瞬间的电流冲击。5.2 V的正确用法和共地原则模块上的 V 不是用来给芯片逻辑供电的而是用来给输出级提供参考电源的。常见模块会在输出端通过上拉电阻接到 V所以 V 的电压会直接影响输出高电平幅度同时也给舵机这类设备提供电源路径。正确接法V 接外部电源正极电压依据负载而定舵机一般用 5V 或 6V外部电源的 GND 必须和树莓派 GND、模块 GND 全部接到一起不要试图用树莓派 USB 电源带多个大舵机几个舵机同时动作时电流很容易冲到数安培树莓派会瞬间重启。我自己踩过这个坑。第一次带六个舵机时图省事直接用一个树莓派 USB 电源供电一上电还没开始跑动作树莓派就反复重启。换成独立 6V 电源之后问题立刻消失。为了缓解舵机启动瞬间的电流尖峰还可以在 V 和 GND 之间并联一个大容量电容比如 1000uF 到 2200uF再串一个保险丝。这种做法成本很低但对稳定性的提升非常明显。5.3 OE引脚悬空会让输出全部罢工OE 是输出使能引脚低电平有效。意思是只有把这个引脚拉低16 路 PWM 才会真正从 OUT 脚走出来。某些模块出厂时 OE 引脚通过排针引出但没有默认接低你如果不处理芯片的寄存器写什么都正常但输出口就是没有波形。解决方式有两种把 OE 直接接到模块的 GND最简单粗暴把 OE 接到树莓派任意 GPIO 引脚程序里拉低。这样做的好处是紧急情况下你可以在代码里快速把输出全部禁用适合做安全开关。我强烈建议你从一开始就把 OE 接好不要让它悬空。这个坑几乎没有任何报错信息纯靠肉眼和万用表才能发现。5.4 各类负载的正确接法负载类型PCA9685 能否直接驱动推荐接法小功率 LED可以但需要串限流电阻OUT - 限流电阻 - LED - GND舵机不可以直接通过 OUT 供电OUT 接信号线V 给舵机供电电机驱动板TB6612 等只提供 PWM 信号OUT 接驱动板 PWM 输入电机接驱动板高功率 LED 灯带不可以OUT 接 MOSFET 栅极灯带独立供电继电器或电磁阀不可以通过三极管或光耦隔离驱动这个表格看着像常识但在各种技术社区里为什么模块很烫这个问题十有八九是因为有人把输出口当成了电源输出口来用。管住电流这套方案才能稳定跑很多年。6. 常见故障排查实录从找不到设备到舵机抽搐6.1 故障一i2cdetect 怎么都看不到 0x40我会按下面这个顺序排查而不是瞎猜执行i2cdetect -y 1如果提示总线不存在先回raspi-config确认 I2C 已经启用重启后再试。对照物理引脚图确认 SDA 接的是 GPIO2物理引脚 3SCL 接的是 GPIO3物理引脚 5。树莓派引脚编号有好几套BCM 编号和物理引脚编号如果不分清楚很容易接反。用万用表量 VCC 和 GND 之间的电压。如果是 3.3V 或者 5V说明供电没问题没有电压就先把电源查清楚。确认地址跳线没有被改过。默认地址一般是0x40但有些模块默认是0x60也有的曾经被别人跳过线。执行i2cdetect -y 1时不要死盯0x40先看整个扫描表格有没有其他可疑地址。断开 PCA9685再执行一次i2cdetect -y 1。如果此时总线干净了说明是模块这边的问题如果断开后扫描结果还是大量随机地址说明总线上的其他设备或者树莓派自身有问题。用 Python 读一下0x01寄存器正常情况下不会返回0xFF如果返回0xFF基本可以断定设备没有响应。这套思路本质上是把问题分成三段系统配置段、物理接线段、设备本身段。每次只验证一段很快能锁定问题。6.2 故障二寄存器能写舵机却发抖或无法到位舵机抖动的排查顺序我的经验是先查供电再查频率最后查脉宽映射。供电不足的症状最典型单独接一个舵机测试一切正常接多个舵机同时动作就开始抽搐或者转到一半回退。这是因为舵机启动瞬间电流过大电源电压被拉低。解决方法是独立电源、加大电容、检查线径。注意别用那种很细的杜邦线给多路舵机供电电流一大导线压降就够你喝一壶。频率不对的症状是舵机发出明显的嗡嗡声而且角度比预期偏大或偏小。可以用示波器看波形没有示波器就回读 PRE_SCALE 寄存器验证数值。如果频率被改成 60Hz 而不是 50Hz舵机响应会变得很怪。脉宽映射错误多发生在移植代码时。有些人直接把0~180的角度值当成 OFF 写入寄存器那输出几乎全高或者全低舵机不是冲到最大就是纹丝不动。正确做法是先换算成脉宽再换算成计数代码里的set_servo_angle已经把这条链路包装好了。6.3 故障三多模块级联后一起失灵如果两块模块地址都是0x40I2C 总线上就会出现地址冲突。奇怪的是i2cdetect -y 1依然可能只显示一个0x40但你读数据时会发现一会儿对一会儿错因为两块芯片同时在响应同一个地址。排查和解决都简单把第二块模块的 A0 焊盘连上让地址变成0x41再重新扫描。如果要挂第三块继续改 A1让地址变成0x42保持唯一即可。级联时还有一个小细节多块模块的 SDA/SCL 并联后会增大总线上的电容负载。如果总线电容太大I2C 通信速率会下降或者出错。你可以在/boot/config.txt里加dtparami2c_arm_baudrate100000把 I2C 速率降到 100kHz通常就能解决。6.4 故障四写入成功但输出没有波形这个问题的隐蔽程度比前几个高因为 I2C 通信完全正常读寄存器也能读回来但 OUT 脚就是没波形。我会用下面的思路排查优先检查 OE 引脚。把 OE 直接接地再试一次大概率直接就好了。这是我在实际项目里真正遇到过的情况而且查了很久才意识到是这个引脚的问题。其次检查 ON/OFF 寄存器的写入顺序。如果你先写了 OFF 高字节再写 OFF 低字节芯片可能把中间状态误判成一个非法值。上面的驱动类里是按固定顺序写四个寄存器的不要随意乱改顺序。第三检查 MODE1 和 MODE2。如果之前有代码把芯片设置成一个奇怪的状态比如 SLEEP 没有被正确唤醒输出也会罢工。重新把 MODE1 写成 0x00MODE2 写成 0x04再初始化一次。最后我没有示波器的情况下会直接用万用表 DC 档测 OUT 对 GND 电压。把占空比设置到 50%OFF 2048正常时电压应该大约是 V 的一半。如果电压始终接近 0 或始终接近 V说明输出级根本没在工作也反过来验证了上面几步有问题。到这里硬件、驱动、供电、排查四条线就全串起来了。最后聊点我的个人体会。这套方案跑通之后最大的收获不是学会了怎么控制舵机而是理解了信号控制和能量供给是两个层面的事情。模块负责精确的信号电源负责足够的能量两者配合好系统才会稳定。如果你准备长期做机器人项目我的建议是提前规划好地址表和供电拓扑哪块模块管舵机、哪块模块管灯、每个地址是多少、电源线用多粗都写进自己的工程文档里。这些看起来琐碎的准备工作会在项目规模变大之后帮你省下大量排错时间。