
做舵机或者LED矩阵项目的时候很多人一开始都会走弯路直接用树莓派的GPIO引脚去控制舵机。树莓派的普通GPIO本身不带硬件PWM软件模拟PWM既耗CPU又容易抖动更别说同时控制8个、16个通道。后来我接触到PCA9685模块一块不到二十块钱的板子通过I2C就能扩展出16路PWM输出配合树莓派使用一下子把整个项目从勉强能跑提升到稳定可控。这篇文章就围绕树莓派控制16路PWM输出的PCA9685模块这个组合把从接线、I2C配置、Python驱动到舵机角度控制、LED亮度调节的完整流程讲透也会把实际调试中遇到的各种坑一起分享出来。适合正在做多路舵机机械臂、六足机器人、LED灯板或者自制云台的朋友参考新手照着做也能跑起来。1. 项目目标与整体方案拆解1.1 为什么需要16路PWM从多舵机控制说起先想一个场景你准备做一台六足机器人每条腿至少需要2个舵机再加上头部、尾部动作12路到16路舵机很正常。树莓派上直接控制这么多舵机几乎不可能GPIO引脚数量够但软件PWM效果奇差舵机指定角度时要么抖要么响应慢。PCA9685模块就是专门干这个的它内部有专门的PWM控制逻辑只要通过I2C总线把角度值发过去模块自己就能维持对应宽度的脉冲信号完全不需要树莓派CPU参与后续的波形生成。PCA9685有16个通道每个通道都能独立输出PWM而且所有通道共享同一个PWM频率但占空比可以各自不同。这意味着你可以在同一个时刻让1号通道的舵机转到30度2号通道转到120度3号通道转到75度彼此互不干扰。对舵机来说PCA9685输出的PWM频率默认配置为50Hz周期20ms对应舵机常见的1ms~2ms脉宽控制范围。对LED调光来说可以把频率调到1000Hz以上占空比从0%平滑变化到100%实现无级调光。一个模块同时搞定舵机和LED两种负载这就是16路PWM的实用价值。1.2 PCA9685与树莓派为什么是最佳搭档树莓派作为主控的优点在于生态成熟Python库齐全IO扩展方便。但树莓派的GPIO存在两个明显短板一是多路同时输出PWM时CPU占用过高二是实时性不稳定偶尔的调度延迟会让舵机产生微小抖动。PCA9685把这些事全部下放到硬件层面只要树莓派通过I2C一次性写入对应通道的寄存器后面的事情模块自己搞定。树莓派负载非常轻控制16路舵机和控制1路舵机几乎没什么差别。另外PCA9685模块通常带一路3.5mm接口或接线端子用来外接独立电源。舵机启动瞬间电流可能达到几百毫安甚至1A以上单靠树莓派的5V引脚带不动必须给模块接上足够的电源。整个系统架构很清晰树莓派负责业务逻辑和计算PCA9685负责PWM波形生成外部电源负责大电流供给三者各司其职。1.3 硬件准备清单实际项目需要哪些东西我前前后后搭过三套这样的系统每次硬件清单基本固定树莓派任何型号都可以我用的是树莓派4B和树莓派Zero W。Zero W性能也够因为I2C传输的数据量很小。PCA9685模块常见的16路16bit PWM舵机驱动板板上有一片PCA9685芯片通常是绿板带两组排针。购买时注意有的商家卖的是3.3V版本有的卖5V版本两者都能用但接线方式略有区别。舵机若干根据自己的项目选SG90等小型舵机还是MG996R等金属齿轮大扭矩舵机。不同舵机驱动电流差异大但PCA9685主要输出信号电流由外部电源承担。外部电源。小舵机用5V 2A的电源适配器就够大舵机比如6个MG996R同时动作建议5V 5A甚至更高。不要把全部舵机都挂在一路电源上有条件可以分两组供电。杜邦线若干公对公、母对母都要备一些。I2C连接建议用公对母舵机信号线用公对公电源线尽可能短且粗。逻辑分析仪或者示波器二选一不是必需但排查舵机抖动、PWM波形异常时非常好用。有人会问既然PCA9685芯片本身支持3.3V到5V逻辑树莓派的GPIO是3.3V能不能直接用答案是可以的。PCA9685的VCC一般接3.3V或5V都可以但它处理舵机信号时输出高电平取决于VCC。如果VCC接3.3V信号电平是3.3V仍然满足舵机输入高电平要求。但稳妥起见模块的VCC接5V树莓派侧SDA、SCL是3.3V模块I2C引脚经过板载电平转换或者直接兼容我实测过树莓派的3.3V I2C信号直连模块的SDA/SCL完全正常没有烧过任何模块。如果你的模块明确写的是5V逻辑板最好串1kΩ电阻或者用电平转换模块避免长期使用出问题。2. 接线与基础配置2.1 接线顺序与细节先电源后信号接线顺序很重要别通电以后再插线。我是这样接的树莓派PCA96853.3V或5V → VCC我习惯接5V因为模块内部稳压后逻辑更稳定GND → GNDSDABCM引脚2物理引脚3→ SDASCLBCM引脚3物理引脚5→ SCLPCA9685外部电源外部电源5V → V或者标VIN的接线端子有的模块印的是V外部电源GND → GND和树莓派的GND必须共地舵机舵机信号线橙色或黄色 → PCA9685任意通道的PWM引脚舵机电源线红色 → 外部电源5V不推荐从树莓派5V取电舵机地线棕色或黑色 → PCA9685GND共地是这个系统能否正常工作的关键。PCA9685的电源GND、树莓派的GND、舵机GND必须连在一起否则I2C信号和PWM信号的电平参考点不一致会出现舵机乱转、信号不稳定之类的怪问题。我有一回没共地舵机偶尔抽搐一度以为模块坏了后来接上共地线瞬间正常。还有一个细节VCC和V不要插反。VCC是芯片的逻辑电源V是舵机电源输入如果反接轻则模块发热严重时芯片烧毁。买回来的模块上印刷一般比较清晰但有些廉价板子印得浅接线前用万用表确认一下哪个引脚和芯片第1脚、第32脚相连第1脚一般接VCC第32脚是V。2.2 启用I2C接口一条命令打开树莓派的总线树莓派默认I2C接口是关闭的先进入配置界面打开它。sudo raspi-config依次选择Interface Options → I2C → Yes启用然后重启。也可以直接编辑/boot/config.txt把dtparami2c_armon取消注释。重启后安装I2C工具集sudo apt update sudo apt install -y i2c-tools python3-smbus用以下命令扫描I2C总线确认模块是否被识别sudo i2cdetect -y 1这里-y 1表示I2C总线编号为1树莓派从旧型号的0和1两条总线到三代以后基本只用总线1。命令输出中会出现一个地址列表PCA9685的默认地址是0x40。如果看到0x40出现说明接线正确。如果什么都没看到检查接线、共地、模块供电还有SDA/SCL是否接反。常用的PCA9685还可以通过板上的A0到A5焊盘改变地址如果只有一个模块保持默认0x40不变即可。多个模块时通过把A0~A5焊盘接到VCC或GND组合出最多62个不同地址这是后话。2.3 确认设备地址与I2C读写测试i2cdetect扫描到的地址是7位地址而Python库中通常也使用这个地址比如0x40。先用简单的命令进行I2C读写验证sudo i2cset -y 1 0x40 0x00 0x20 sudo i2cget -y 1 0x40 0x00PCA9685的寄存器0x00是MODE1寄存器0x20十进制32表示Sleep位清零进入正常模式。读取应该返回0x20。如果返回错误或者0xFF说明通信链路有问题。有了这一步确认后面Python代码跑不通时基本可以确定是软件问题而不是硬件问题。3. Python驱动库安装与核心参数3.1 安装Python库Adafruit_PCA9685和smbus2怎么选Python控制PCA9685最常用的库是Adafruit_CircuitPython_PCA9685和Adafruit_PCA9685旧版。旧版依赖Adafruit_GPIO在树莓派上安装稍微复杂一点。目前推荐CircuitPython版本但它在树莓派上依赖libgpiod安装时可能会提示缺少依赖。更轻量的选择是直接用smbus2自己封装寄存器读写或者使用PCA9685这个纯Python驱动库。我的经验是最简单的入门方式是安装adafruit-pca9685pip3 install adafruit-pca9685但注意这个包依赖Adafruit_PureIO和smbus树莓派上通常还需要先安装系统包sudo apt install -y python3-libgpiod pip3 install adafruit-circuitpython-pca9685如果这些库安装过程中出现各种依赖报错不用慌直接退回最原始的方案——用smbus2自己写PCA9685的寄存器其实非常有规律后面我会把核心读写函数发出来。很多看起来玄乎的依赖问题都不如自己动手写几十行代码来得干脆。3.2 寄存器工作原理把PWM频率和占空比写进硬件PCA9685芯片内部有一堆寄存器但理解清楚后控制逻辑就不复杂。它有一个主时钟通常来自板载25MHz晶振内部经过一个预分频寄存器PRE_SCALE来分频从而产生基础PWM频率。对每个通道芯片为每个周期维护一个计数值从0递增到4095。每个通道有两个寄存器对ON寄存器设置输出变为高电平的时刻和OFF寄存器设置输出变为低电平的时刻。输出高电平的宽度就是OFF值和ON值之间的时间差。这里需要注意芯片的工作方式不是简单写一个占空比而是写入何时开和何时关。实现占空比最简单的方式是ON寄存器设为0OFF寄存器设为需要的值0~4095。这样每个周期开始就是高电平持续到OFF值对应的时刻剩下时间低电平占空比就是OFF/4096。这种写法对应舵机脉冲信号非常直观周期20ms脉宽1ms到2ms换算成计数值就是204到410之间。下面代码中的set_pwm(channel, 0, pulse_width)就是在做这件事。PWM频率由PRE_SCALE寄存器决定计算公式为PRE_SCALE round(25000000 / (4096 * freq)) - 1其中25000000是芯片内部晶振频率。例如freq50Hz时25000000/(4096*50)122.07取整122减1得1210x79。调整频率后芯片需要把MODE1寄存器的Sleep位置1写入PRE_SCALE后再清除Sleep并等待晶振稳定大约500μs。库函数set_pwm_freq内部已经处理了这些但自己实现时容易漏掉Sleep流程导致频率没有生效。3.3 频率、占空比与角度换算舵机和LED的参数差异舵机控制通常是50Hz对应周期20ms。舵机的脉宽一般在0.5ms~2.5ms之间常见SG90是0.5ms对应0度2.5ms对应180度。但不同舵机标准有差异我买的SG90实测1ms对应0度2ms对应180度中间线性度不错。这时脉冲宽度换算成计数值的公式是pulse_value (angle / 180) * (2ms - 1ms) / 20ms * 4096 1ms / 20ms * 4096简化一下1ms对应计数值2042ms对应409角度对应数在204到409之间线性变化。很多库提供set_pwm接口直接传0和300这样300对应约1.46ms脉宽大概83度。实际写代码时建议写一个角度转换函数避免每次手算def angle_to_pulse(angle): # 假设0度1ms, 180度2ms, 周期20ms4096步进 pulse_ms 1.0 (angle / 180.0) * 1.0 return int(pulse_ms / 20.0 * 4096)LED调光不一样。LED用PWM控制亮度通常使用更高的频率比如1000Hz避免频闪占空比决定亮度占空比从0%到100%对应PWM输出持续高电平的时间占比。调光时直接用0~4095的计数值作为OFF值即可比如set_pwm(channel, 0, 2048)就是50%亮度。注意LED负极要串一个限流电阻PCA9685每个通道最大输出电流约25mA不能让模块直接驱动大功率LED否则芯片容易烫手甚至烧毁。这就是为什么有的模块指示灯叫OEOE引脚低电平才使能输出接GND后所有通道持续输出如果把它接到GPIO还可以随时禁用全部输出。4. 控制舵机与LED的实操代码4.1 最简驱动代码点亮第一路PWM输出先跑一个最小程序验证模块。假设已经安装了adafruit-circuitpython-pca9685但为了减少依赖我在这里用自行封装的smbus2写法这个写法在任何Linux板子上通用性极好。先安装pip3 install smbus2然后写一个基础测试脚本import time from smbus2 import SMBus PCA9685_ADDR 0x40 MODE1 0x00 PRE_SCALE 0xFE LED0_ON_L 0x06 LED0_OFF_L 0x08 bus SMBus(1) def set_pwm_freq(freq): # 进入sleep模式以修改预分频 bus.write_byte_data(PCA9685_ADDR, MODE1, 0x10) prescale_val int(25000000.0 / (4096.0 * freq) - 1) bus.write_byte_data(PCA9685_ADDR, PRE_SCALE, prescale_val) # 回到正常模式使能输出 bus.write_byte_data(PCA9685_ADDR, MODE1, 0x00) time.sleep(0.0005) def set_pwm(channel, on_val, off_val): reg_base 0x06 channel * 4 # 先写入ON时刻低2字节高2字节 bus.write_byte_data(PCA9685_ADDR, reg_base, on_val 0xFF) bus.write_byte_data(PCA9685_ADDR, reg_base 1, (on_val 8) 0xFF) bus.write_byte_data(PCA9685_ADDR, reg_base 2, off_val 0xFF) bus.write_byte_data(PCA9685_ADDR, reg_base 3, (off_val 8) 0xFF) set_pwm_freq(50) while True: set_pwm(0, 0, 200) # 约0.98ms脉宽舵机接近0度 time.sleep(1) set_pwm(0, 0, 400) # 约1.95ms脉宽舵机接近180度 time.sleep(1)把舵机信号线接到通道0。运行后会看到舵机每隔1秒在0度和180度之间摆动说明最基础的功能已经通了。我在首次运行这个脚本时出现过只有一次动作然后没有反应的情况原因是没有在频率设置后延时500μs。加上time.sleep(0.0005)就好了。这个问题在Adafruit库中内部处理了但如果自己实现寄存器读写很容易忽略。4.2 舵机角度控制的平滑运动与定时刷新舵机直接跳变角度会显得生硬项目中需要平滑转动时我通常用渐进的步进值刷新脉宽。注意不是直接每秒刷新一次而是以固定时间间隔微调角度。下面是一个从0度转到180度再转回来的简单代码import time from smbus2 import SMBus bus SMBus(1) PCA9685_ADDR 0x40 def set_pwm_freq(freq): bus.write_byte_data(PCA9685_ADDR, 0x00, 0x10) prescale int(25000000.0 / (4096.0 * freq) - 1) bus.write_byte_data(PCA9685_ADDR, 0xFE, prescale) bus.write_byte_data(PCA9685_ADDR, 0x00, 0x00) time.sleep(0.0005) def set_pulse_ms(channel, ms): # 50Hz下周期20ms4096步进 off_value int(ms / 20.0 * 4096) bus.write_byte_data(PCA9685_ADDR, 0x06 channel*4, 0) bus.write_byte_data(PCA9685_ADDR, 0x06 channel*4 1, 0) bus.write_byte_data(PCA9685_ADDR, 0x06 channel*4 2, off_value 0xFF) bus.write_byte_data(PCA9685_ADDR, 0x06 channel*4 3, (off_value 8) 0xFF) set_pwm_freq(50) angle 0 direction 1 while True: # 0度对应1ms脉宽180度对应2ms脉宽 pulse_ms 1.0 (angle / 180.0) * 1.0 set_pulse_ms(0, pulse_ms) angle direction if angle 180: angle 180 direction -1 elif angle 0: angle 0 direction 1 time.sleep(0.01) # 每10ms更新一次平滑度足够这里的关键是每只舵机都要有自己独立的更新逻辑。假如控制6路舵机main loop里依次更新每个通道的角度每轮循环总耗时大约是6次I2C写入加上延时I2C写入本身是几十微秒级别毫秒级循环完全可以满足。用Adafruit官方库会更简洁但我上面自己封装的这个类已经可以满足绝大多数场景。需要注意I2C总线在多线程环境中要加锁否则两路线程同时写同一总线上数据可能会交错。树莓派上跑的很多项目都用单线程顺序更新没必要强行引入线程除非需要在舵机运动中并发监控传感器。如果用了线程请务必给SMBus操作加threading.Lock()。4.3 LED亮度渐变的呼吸灯效果多通道联合控制与舵机不同LED调光不需要50Hz通常直接设频率为1000Hz避免肉眼可见的闪烁。呼吸灯就是LED亮度从暗到亮再从亮到暗的循环本质是PWM占空比从0逐渐增加到4095再逐渐降低。import time from smbus2 import SMBus bus SMBus(1) addr 0x40 def set_pwm_freq(freq): bus.write_byte_data(addr, 0x00, 0x10) prescale int(25000000.0 / (4096.0 * freq) - 1) bus.write_byte_data(addr, 0xFE, prescale) bus.write_byte_data(addr, 0x00, 0x00) time.sleep(0.0005) def set_channel_brightness(channel, brightness): # brightness: 0-4095 base 0x06 channel * 4 bus.write_byte_data(addr, base, 0x00) bus.write_byte_data(addr, base 1, 0x00) bus.write_byte_data(addr, base 2, brightness 0xFF) bus.write_byte_data(addr, base 3, brightness 8) set_pwm_freq(1000) brightness 0 direction 1 while True: set_channel_brightness(0, brightness) brightness direction * 8 # 步进值调节呼吸速度 if brightness 4095: brightness 4095 direction -1 elif brightness 0: brightness 0 direction 1 time.sleep(0.02)这里我把步长设为8每20ms刷新一次一个完整的呼吸周期大概是4秒多。步长和延时决定了呼吸节奏可以按喜好调整。要注意的是LED必须串一个合适的限流电阻比如5V供电时接220Ω电阻否则LED电流过大。如果只是小电流LED指示灯PCA9685可以直接驱动但单个通道最大25mA千万别把它当成电机驱动或者大功率LED驱动。用同样的方式可以做一个16通道彩虹渐变的效果让每个通道的亮度错开一个相位差实现流动感。比如brightness (int((math.sin(t phase[i]) 1) / 2 * 4095))效果非常华丽。这里只要记住一个原则PWM频率满足舵机时50HzLED时1000Hz以上切换频率时必须重新set_pwm_freq。4.4 让16路同时输出关键操作有时候需要让多个通道在同一时刻输出不同的占空比比如六足机器人抬腿动作。用PCA9685时每个通道的寄存器是独立的依次写入不同通道并不影响已经写入的通道状态。理论上如果逐个更新8个通道期间可能产生几十微秒的时间差对舵机动作来说完全感知不到。但如果需要严格同步可以利用PCA9685的OE引脚先使OE为高电平禁用输出然后依次更新所有通道最后把OE拉低统一使能输出。这样所有通道会同时开始新的PWM周期。我做过一个8路舵机同步动作的小项目用GPIO控制OE引脚效果很好。接线时OE引脚不接GND而是接树莓派的一个GPIO比如BCM 18。代码中在更新前GPIO.output(oe_pin, GPIO.HIGH)更新完所有通道后GPIO.output(oe_pin, GPIO.LOW)。这个技巧在需要多关节联动时很重要建议保留。5. 常见问题与排查实录5.1 I2C设备扫描不到供电、地址、接线三连i2cdetect -y 1什么都看不到是最常见的问题。我的排查顺序固定检查模块VCC是否有3.3V/5V电压。用万用表量VCC引脚对GND电压。检查SDA和SCL是否接对。树莓派物理引脚3是SDABCM 2物理引脚5是SCLBCM 3。很多人把SDA和SCL交叉接了I2C是同步串行总线接反了完全无法通信。确认是不是多个I2C设备造成地址冲突。如果没有其他设备排除这个问题。检查I2C总线是否启用。可以运行ls /dev/i2c*如果看不到i2c-1说明没有启用内核模块重新检查raspi-config设置。接线太长了。使用杜邦线超过20cm时I2C信号容易受到干扰或者线上有跳线帽没插到位。如果是长距离建议用更短的线或降低I2C速率在/boot/config.txt中加dtparami2c_arm_baudrate100000。如果你在树莓派3B以后型号上用了硬件I2C默认速率100kHz一般没问题。但有个特殊情况某些PCA9685模块板上带有电平转换芯片如果这个芯片供电没接好即使VCC正常SDA/SCL信号也会无法通过导致扫描不到地址。这种情况下可以用万用表测量SDA引脚电压正常应该在3.3V左右如果接近0V说明上拉电阻或者电平转换芯片工作异常。5.2 舵机抖动或者突然跳到某个角度脉宽计算和供电问题舵机抖动通常分三种情况。第一种是频率不对的抖动。PCA9685设置成50Hz舵机才稳定。如果误设成200Hz舵机内部检测到的脉冲宽度变短会一直试图寻找中间位表现为高频振动。设置回调一个确认频率或者读回PRE_SCALE寄存器验证。第二种是脉宽值跳变。如果代码里的角度计算用了浮点数但没做防抖某一步出现了异常值舵机会瞬间跳到异常角度。检查有没有多线程同时写同一个通道或者用列表存储角度时索引越界。第三种是供电不足。舵机转动瞬间电流很大特别是多个舵机同时启动外部电源电压会被拉低到4.5V以下舵机就会抖动甚至乱跳。解决办法换更大电流的电源或者给每个舵机单独供电或者在电源两端并联一个470μF以上的电解电容。我在一个四足机器人上遇到六个舵机同时动作时5V 3A电源明显不够加上一个2200μF电容后稳定很多但根治还是换成了5V 5A电源。还有一种隐蔽情况PCA9685的VCC和V分别供电时如果V电压过高比如接了6V给舵机舵机虽然能转但PCA9685的OE引脚逻辑可能受到影响。大部分模块都能承受6V驱动但最好还是按照舵机额定电压来SG90用5V别乱加压。5.3 通道输出神奇地全部无输出但I2C正常OE引脚悬空有一次我调试了半天I2C能扫描到0x40i2cget读寄存器也正常但任何通道都没有输出波形舵机一动不动。后来发现是OE引脚悬空导致的。OE引脚低电平才使能输出悬空时电平不确定多数模块内部有上拉电阻导致OE为高输出全部关闭。把OE直接接GND立刻就有输出了。如果你的模块没有OE引脚那大概率是接地的跳线帽没插好某些板子上OE引脚默认通过跳线帽接地。检查一下跳线帽是否松脱。5.4 多模块串联地址冲突A0~A5焊盘的配置方法项目需要超过16路PWM时通常串联多个PCA9685模块。所有模块共用SDA、SCL、GND但每个模块需要不同的I2C地址。PCA9685有6个地址引脚A0到A5通过把引脚接到VCC或GND来设置地址。默认A0~A5全接GND时地址为0x40。若将A0接VCC地址变为0x41A1接VCC变成0x42以此类推。注意地址引脚在部分模块上以焊盘形式提供焊盘默认用小焊锡连接要用刀割断或者用焊锡搭接。接好两个模块后i2cdetect -y 1会同时看到两个地址比如0x40和0x41。Python代码里创建两个对象分别用不同地址然后组合使用。需要留意的是多个模块共地且外部电源要按模块分别接线尽量别让一个电源拖着几十个舵机压降问题会非常严重。5.5 电流倒灌问题树莓派会被舵机供电拉坏吗这里必须提醒一下舵机电源不能直接连到树莓派的5V引脚但共地必须做。如果树莓派5V引脚接了外部电源外部电源给舵机供电那电流会从树莓派USB口倒灌可能导致树莓派重启甚至损坏。正确的接法是树莓派用原装电源适配器单独供电PCA9685的V接外部5V电源外部电源的GND和树莓派的GND连在一起但不要让外部电源的正极接到树莓派5V引脚上。当然如果你只接一两个小舵机直接用树莓派5V引脚供电也能凑合但任何超过3个舵机的项目都建议外部供电。我的经验是始终把信号地和电源地分开理解PCA9685地线既连树莓派又连外部电源相当于在树莓派地参考面上接入外部电源这是安全且必要的。6. 扩展优化与经验心得6.1 使用官方Adafruit库的完整示例上面自封装的模式能让你彻底理解寄存器但日常开发追求效率时直接使用成熟库更舒服。我用adafruit-circuitpython-pca9685写过一个完整的多舵机控制脚本核心代码就几行import board import busio from adafruit_pca9685 import PCA9685 i2c busio.I2C(board.SCL, board.SDA) pca PCA9685(i2c, address0x40) pca.frequency 50 from adafruit_servokit import ServoKit kit ServoKit(channels16, address0x40) kit.servo[0].angle 90 kit.servo[1].angle 45ServoKit是更上层的库内部把舵机角度、脉宽范围都封装好了。对于舵机控制直接用ServoKit最省事对LED调光直接操作pca.channels[0].duty_cycle 0x8000即可。依赖安装如果卡住可以按官方文档先安装adafruit-blinka。6.2 平滑驱动算法的进一步优化加速度限制舵机从当前角度直接跳到目标角度尤其是大幅变化时会造成机械冲击和电流尖峰。更优雅的算法是限制角速度让舵机以一个最大角速度追踪目标。可以用简单的梯形速度规划或者更平滑的S曲线。我曾经在机械臂上使用简单的一阶低通滤波current_angle current_angle * 0.9 target_angle * 0.1这个滤波系数让动作柔顺很多。每20ms执行一次系数越小越慢。但要注意滤波不能让舵机长期达不到目标比如系数0.05就会显得反应迟钝。优化时可以设计一个死区当abs(target - current) 1时直接把current设为target。6.3 从16路到上百路多模块联动架构当你需要控制32路甚至更多舵机时可以串联多个PCA9685模块。地址从0x40开始A0接VCC得到0x41A0再接就0x42依此类推。代码中用列表管理多个ServoKit实例即可。我在某个小项目里用了4个模块64路输出I2C总线上有多个设备实测没有感觉到明显延迟。但如果你需要极高的刷新率比如所有通道都要在10ms内更新完I2C速率可能需要提升到400kHz。在/boot/config.txt中设置dtparami2c_arm_baudrate400000但前提是你的杜邦线足够短、质量足够好否则高速I2C很容易通信错误。6.4 实际项目中我总结的几条定律值电阻PCA9685输出的PWM高电平电压取决于VCC。如果VCC接5V输出高电平为5V部分舵机输入引脚标称5V耐受没问题。但有些3.3V逻辑舵机最好VCC接3.3V同时让V接舵机供电。我在接PS2摇杆转接模块时3.3V和5V混用吃过亏导致模块发热后来严格注意信号电平匹配问题消失。布线要短。舵机信号线尽量缩短太长容易引入干扰特别是与电机电源线走在一起时。PWM信号是模拟脉宽干扰会让舵机误动作。把信号线和电源线分开布线必要时用双绞线或者屏蔽线。顺序上电。每次开机先给树莓派上电再把舵机电源送上。如果反过来舵机上电瞬间的巨大电流可能影响树莓派供电导致系统启动异常。在代码里也可以设计一个初始化函数先设置PWM频率为50Hz将所有通道输出最大值禁用舵机再使能OE引脚这样舵机上电时不会突然乱跳。6.5 后续还可以扩展什么PCA9685本身很成熟但围绕着它可以继续做很多事。如果你在做机器人下一步可以加入PCA9685的多模块同步配合一个简单的动作序列编辑器实现预先编排动作组。如果你在做LED矩阵照明可以考虑加上光线传感器实现自动亮度调节PWM调光的平滑度比继电器控制好太多。也可以把树莓派换成其他单片机比如ESP32或者STM32代码逻辑相同只是I2C库不同。PCA9685作为外设芯片的通用性决定了这套控制方法学到之后可以平移到几乎任何嵌入式平台。回过头看树莓派与PCA9685的组合真正解决的是多路PWM同时输出这件事。它让复杂机械的控制变得模块化也让我不必为了每一个舵机再去消耗一个GPIO和一堆软件定时器。你如果正打算做多舵机项目与其纠结用树莓派内置PWM还是软件PWM不如直接上一块PCA9685把精力省在真正有意思的控制算法和机械结构上。我这里分享的代码和排查思路都是实际跑过的照着搭大概率一次成功就算出了问题回头对照第5节的内容也基本能定位到根因。