ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

树莓派舵机控制:PWM原理、供电与树莓派5适配

树莓派舵机控制:PWM原理、供电与树莓派5适配 手里有一块树莓派想让几个舵机听指挥——这几乎是每个做硬件控制的人都会走的第一段路。听起来简单三根线一根电源、一根地、一根信号给个PWM就能转。可真正上手之后你会发现事情没这么顺舵机一抖一抖地抽、树莓派接上舵机就自动重启、明明代码写的是90度结果它硬是往一边转到底、换个树莓派5之后原来的代码直接报错跑不起来。这些都不是玄学每一项背后都有明确的物理原因和工程原因。这篇内容面向的是手上有树莓派4B、5、Zero 2 W、Pico 都算、想控制舵机完成一个具体动作的人。不管你是做机械臂、云台、仿生手臂这类树莓派项目还是单纯想搞明白舵机 PWM 控制原理我都会把这套东西从为什么讲到怎么做包括实测出来的角度换算系数、供电方案取舍、树莓派5 上必须换掉的软件栈以及几个只有踩过才知道的坑。代码部分给出的是可以直接粘贴运行的最小可复现版本硬件部分给的是我自己反复验证过的接线和电源配置。1. 从舵机为什么能听话说起开环控制与PWM的底层逻辑很多人第一次接舵机时是懵的为什么给它一根信号线它就知道该转到哪个角度这根线上跑的到底是什么搞不清楚这一层后面所有调试都只能靠瞎试。1.1 三线舵机内部到底装了什么拆开一个 SG90 你会看到四样东西直流有刷电机、一组塑料齿轮减速比通常在一百多到两百多之间、一块极小的控制板以及一个和输出轴联动的电位器。关键在于那个电位器。它本质上是个可变电阻输出轴转多少度它就给出对应的分压值。控制板做的事非常朴素把信号线上的脉宽读出来换算成目标角度同时读电位器的电压换算成当前角度两者一比较差值驱动电机正转或者反转差值归零就停。这就是一个纯模拟的闭环位置伺服只不过闭环是在舵机内部完成的对外表现成一个开环器件。理解这一点很重要因为它解释了一个常见误会舵机并不是你让它转多少度它就转多少度而是它拼命把电位器读数拉到目标值。齿轮打滑、电位器磨损、机械限位卡死都会让这三个环节对不上号于是出现代码写 90 度、实际停在 75 度这类现象。1.2 20ms周期与0.5~2.5ms脉宽角度到底怎么换算舵机的控制信号是标准 50Hz 的 PWM也就是周期 20ms。在这个周期内高电平持续的时间决定角度高电平脉宽对应角度标准180度舵机常见偏差0.5ms0 度部分舵机实际 0.6ms 才到底1.0ms45 度-1.5ms90 度中位大部分舵机这里最准2.0ms135 度-2.5ms180 度廉价舵机常常只能到 2.4ms换算成占空比也就是 2.5% 到 12.5%。这个数字记住很有用因为所有库最终都是在往这个区间里填数。真正的问题是上面这张表是教科书范围而实际拿到手的舵机个体差异不小。我在同一批买的五个 SG90 上测过最保守的一个 0.6ms 到底、2.35ms 到顶也就是有效行程只有 175 度左右而且中位在 1.48ms。所以后面第 4 节我会专门讲怎么逐台校准——这是把动作做准的唯一办法没有捷径。1.3 开环控制的边界为什么舵机不知道自己转到了哪对外部控制器来说舵机是开环的你发指令但你不读位置。这不是设计缺陷而是成本和场景决定的。航模、小车转向、云台俯仰这些场景不需要知道绝对角度只需要到位就行。但这带来三个必须接受的现实。第一没有到位反馈你无法确认它真的转了只能靠时间等待——通常按 60 度/秒的速度估算给足 0.3 到 0.5 秒的缓冲。第二扭矩不足时它会在负载下欠转你以为它到位了其实差一截。第三掉电后位置信息全部丢失重新上电时会朝中位冲——这就是第 2.4 节要展开的复位电流问题。如果你确实需要位置反馈那就该考虑总线舵机如飞特那类带串口反馈的或者自己在输出轴上装一个编码器/电位器做二次检测。这个取舍在第 5 节会详细对比。2. 硬件选型与接线先把电的问题解决干净我见过太多人代码写得漂亮得不行结果卡在硬件上折腾一星期。舵机项目里电的问题占故障总数的七成以上剩下三成里又有一半是接线接错。2.1 SG90、MG90S、MG996R、总线舵机的实际差异选型先看三个参数扭矩、电压、齿轮材质。型号工作电压堵转扭矩齿轮空载电流堵转电流适用场景SG904.8-6V约 1.8 kg·cm塑料100-200mA约 700mA微型云台、小关节MG90S4.8-6V约 2.2 kg·cm金属100-200mA约 800mASG90 的加强版推荐MG996R4.8-7.2V约 11 kg·cm金属100-200mA2.0-3.0A机械臂大臂、重载总线舵机6-12V10-30 kg·cm金属待机几十mA视型号多关节机械臂这里有个反直觉的点MG996R 的堵转电流能到 2.5A 甚至更高而树莓派 4B 推荐电源是 5V 3A、树莓派 5 是 5V 5A。也就是说只要有一个 MG996R 堵转你整台树莓派的供电预算就被吃掉了大半重启是必然的。还有一个热词里经常出现的型号MG996R 360 度版本。它不是位置舵机而是连续旋转舵机——信号脉宽控制的是转速和方向1.5ms 停转小于 1.5ms 朝一个方向转、大于 1.5ms 朝另一个方向转。如果你拿着 360 度版本按角度公式去控制表现就是不管怎么调都往一边转。这是第 6 节里的一个典型故障。2.2 供电方案对比USB口、DC-DC降压、独立电源结论放前面永远不要用树莓派的 5V 引脚给大扭矩舵机供电。它本来就是从 USB 输入直通的没有任何余量。我在几个方案上都实测过树莓派 5V 引脚直供只适合 1 到 2 个 SG90 且负载很轻的情况。MG996R 绝对不行。表现是树莓派随机重启、板载电压掉到 4.6V 以下。独立 5V 开关电源比如 5V 5A 的工业开关电源成本几十块带三四个 MG996R 毫无压力。这是最省心的方案缺点是体积大、要插 220V。2S 锂电池 DC-DC 降压模块7.4V 锂电经降压模块调到 6V便携、电流余量大。要注意选降压模块时看额定电流标称 5A 的实际连续电流往往只有 3A 左右要留一倍余量。另外电压别设到 7V 以上SG90 那类 4.8-6V 的舵机会烧。USB 充电宝供电应急可以但很多充电宝有低电流自动断电保护舵机静态电流小会被判成充满而自动关断。这个坑挺隐蔽。2.3 共地一根线决定成败不管你用哪种电源方案舵机电源的 GND 必须和树莓派的 GND 连在一起。这不是可选项。原因很直接PWM 信号是相对于信号源的地来定义的。如果两个系统地不同信号线上的电平在舵机那一侧就没有明确的高低基准表现是舵机乱转、不动或者收到信号但方向错乱。共地还有一个副作用要注意如果走线很长地线上的压降会让信号高电平抬高同样会造成误触发。我的做法是三根线尽量绞在一起走电源线用比信号线粗一号的线规长距离时在舵机端并一个 100nF 到 470uF 的电容做退耦。2.4 复位电流与上电浪涌热词里复位电流这个词出现频率很高很多人被它坑过。现象是上电瞬间树莓派直接重启或者舵机猛地一抽然后全部回到中位。原因有两层。一是舵机内部控制板的上电瞬间会有电容充电的浪涌电流二是上电后所有舵机同时朝中位或上电前设定位置运动多个舵机的启动电流叠加。三个应对手段按性价比排序给舵机电源端并一个大容量电解电容2200uF 起步正负极别接反。它能把浪涌吸收掉成本最低。分段上电先给树莓派上电稳定几秒后再给舵机电源上电。用一路带开关的独立电源或者加一个 MOS 管软启动电路。上电时先发中位指令再逐个唤醒代码里先把所有 PWM 输出设成中位脉宽再让舵机电源接通。如果你的驱动板支持通道使能PCA9685 就有可以逐个通道使能把浪涌摊开。3. 树莓派4B与树莓派5的软件栈差异老教程不能照抄这是我最近收到最多的问题照着两年前的教程写import RPi.GPIO在树莓派 4B 上跑得好好的换到树莓派 5 直接报This module can only be run on a Raspberry Pi。不是你没装好是底层变了。3.1 RPi.GPIO 在树莓派5上为什么失效树莓派 4B 及更早的型号GPIO 是由 SoC 的 GPIO 控制器直接管理的RPi.GPIO通过/dev/mem直接映射寄存器来操作。树莓派 5 换了新的 I/O 控制器RP1寄存器布局和访问路径都变了老的直接映射方式行不通。RPi.GPIO这个库本身也已经很久没有实质性维护所以官方推荐迁移到lgpio或者gpiozero。顺带说一句pigpio在树莓派 5 上同样有问题因为它也依赖对硬件外设的直接访问。如果你原来的项目重度依赖 pigpio 的高精度定时迁移时要重新评估。3.2 gpiozero / lgpio / pigpio 怎么选库树莓派5 支持抽象层级PWM 精度适合谁RPi.GPIO不支持低软件 PWM抖动大老项目维护新项目别用gpiozero支持lgpio 后端高中等快速上手、教学、简单动作lgpio支持低中等需要自己控制时序pigpio有兼容问题低高DMA 定时老项目Pi5 上慎用adafruit-circuitpython-servokit支持高由 PCA9685 决定多路舵机、机械臂我的建议是单路或两三路舵机、动作不复杂用gpiozero代码最短四路以上或者要做机械臂直接上 PCA9685 驱动板配servokit把 PWM 生成的活儿交给专用芯片树莓派只管发 I2C 指令稳定性和精度都不是一个量级。3.3 环境搭建与依赖安装先把系统时间对准不然 apt 会因为证书问题报错sudo apt update sudo apt full-upgrade -y树莓派 5Bookworm 系统上安装舵机相关依赖sudo apt install -y python3-lgpio python3-gpiozero python3-pip pip3 install --break-system-packages adafruit-circuitpython-servokit注意 Bookworm 开始pip 默认不允许往系统环境装包要么加--break-system-packages要么老老实实建虚拟环境python3 -m venv --system-site-packages ~/servo-env source ~/servo-env/bin/activate pip install adafruit-circuitpython-servokit--system-site-packages这个参数别漏它让虚拟环境能访问系统里的lgpio否则 gpiozero 找不到后端。启用 I2C用 PCA9685 才需要sudo raspi-config # Interface Options - I2C - Enable sudo i2cdetect -y 1i2cdetect输出里出现40就说明 PCA9685 被识别到了。默认地址是 0x40如果同时挂多个板子需要改板上的地址焊盘。3.4 三段能直接跑的最小代码先说 gpiozero 版本树莓派 4B 和 5 都能跑这是我最推荐的入门写法from gpiozero import Servo from gpiozero.pins.lgpio import LGPIOFactory from gpiozero import Device import time Device.pin_factory LGPIOFactory() # 这两个脉宽范围一定要按自己舵机实测调整 servo Servo(18, min_pulse_width0.5 / 1000, max_pulse_width2.5 / 1000, frame_width0.02) servo.value 0 # 约 90 度 time.sleep(1) servo.value -1 # 最小端 time.sleep(1) servo.value 1 # 最大端 time.sleep(1) servo.detach() # 断电释放消除嗡嗡声servo.value的取值范围是 -1 到 1映射到 min_pulse_width 和 max_pulse_width 两个端点。detach()这一步很关键舵机到位后不断开信号会持续微调发出持续的嗡嗡声长期下来发烫。如果你还在树莓派 4B 上用 RPi.GPIO 的写法是这样import RPi.GPIO as GPIO import time GPIO.setmode(GPIO.BCM) GPIO.setup(18, GPIO.OUT) pwm GPIO.PWM(18, 50) # 50Hz pwm.start(0) def angle_to_duty(angle): # 0度 - 2.5%180度 - 12.5% return 2.5 angle / 180.0 * 10.0 pwm.ChangeDutyCycle(angle_to_duty(90)) time.sleep(0.5) pwm.ChangeDutyCycle(0) # 停止脉冲避免持续嗡嗡声 pwm.stop() GPIO.cleanup()多路舵机走 PCA9685from adafruit_servokit import ServoKit import time kit ServoKit(channels16) for ch in range(4): kit.servo[ch].set_pulse_width_range(500, 2500) kit.servo[0].angle 45 kit.servo[1].angle 90 kit.servo[2].angle 135 kit.servo[3].angle 0 time.sleep(1)PCA9685 的频率默认是 50Hz正好匹配舵机。set_pulse_width_range这一步最好每路都设一遍把校准值写进去。4. 角度映射与平滑运动把抽搐变成顺滑代码能跑起来只是及格线。真正的差距在动作质量上——同样是从 0 度转到 90 度有人做出来是啪一下甩过去有人做出来是平滑加速减速像有生命一样。后者靠的是两件事角度校准和速度曲线。4.1 占空比公式与逐台校准标准公式我已经给过了但真的要做得准得逐个舵机测。方法很土但有效先写一个小脚本把脉宽从 400us 开始每次加 25us直到舵机刚好开始动记下这个值这就是实际最小脉宽再从 2600us 往下减找到刚好停住的最大值。然后在这两个值之间取中点就是真实中位。拿到这三个数之后你的映射函数就变成了import math class ServoCal: def __init__(self, min_us, mid_us, max_us): self.min_us min_us self.mid_us mid_us self.max_us max_us def angle_to_us(self, angle): angle max(0.0, min(180.0, angle)) if angle 90: return self.min_us (self.mid_us - self.min_us) * angle / 90.0 return self.mid_us (self.max_us - self.mid_us) * (angle - 90) / 90.0 cal ServoCal(580, 1480, 2380) print(cal.angle_to_us(0), cal.angle_to_us(90), cal.angle_to_us(180))分段线性比整体线性准得多因为舵机在中位附近的灵敏度和两端不一样。这套校准数据建议写进配置文件换舵机时只需要改三个数。4.2 抖动来源逐项排查抖动是问得最多的问题。它有三个独立来源得一个个排电源抖动。用示波器看舵机电源如果舵机运动瞬间电压掉超过 0.3V就是电源功率不够。加大电源或者并电容。PWM 信号抖动。软件 PWM 的定时精度受操作系统调度影响树莓派在跑其他进程时会产生几十微秒的抖动。表现是舵机发出轻微的、无规律的嗞嗞声。解决办法是换硬件 PWM树莓派上 GPIO12、13、18、19 支持或者干脆用 PCA9685 让专用芯片产生 PWM。机械负载或舵机本身的问题。塑料齿轮舵机在负载接近额定扭矩时会持续发生过冲-回调的振荡。这种抖动加电容没用要么换大扭矩舵机要么减负载。还有一个隐蔽原因舵机内部的死区设置偏小控制板一直在纠偏。这种情况可以试到位后detach()很多抖动会立刻消失。4.3 用缓动函数做梯形速度曲线直接改角度值舵机会用最大速度冲过去冲击大、还容易过冲。稍微改一下代码加个缓动就完全不一样import math import time def ease_in_out(t): # 余弦缓动速度在两端慢、中间快 return 0.5 * (1 - math.cos(math.pi * t)) def move_smooth(set_angle_func, start, end, duration0.8, steps60): dt duration / steps for i in range(steps 1): t i / steps angle start (end - start) * ease_in_out(t) set_angle_func(angle) time.sleep(dt) move_smooth(lambda a: kit.servo[0].__setattr__(angle, a), 0, 120, 0.8)sleep的精度不高如果动作要求高用time.monotonic()算绝对时间戳来控步长。我实测下来0.6 到 1.0 秒完成 90 度是比较自然的节奏太快会甩太慢会显得迟钝。4.4 多路舵机错相启动做机械臂的时候四个舵机同时启动的瞬间电流是单独启动的四倍很容易把电源拖垮。解决办法是错相让每个舵机错开 30 到 50 毫秒启动或者让它们同时启动但用不同的时长到达电流峰值就分散开了。对 PCA9685 来说还有个好处芯片内部是分时输出 16 路 PWM 的本身就不会完全同时。但电机的机械启动瞬间还是重叠的所以软件层面错相依然有意义。5. 多路扩展PCA9685驱动板与总线舵机两条路线三路以内用树莓派引脚直驱没毛病。超过四路就该考虑架构升级了。5.1 PCA9685 接线、频率与地址PCA9685 是一块 16 通道 PWM 驱动板通过 I2C 和树莓派通信只需要两根线SDA、SCL加电源和地。接线要点板子的 VCC 是芯片供电接树莓派 3.3V板子的 V 是舵机电源接外部 5V/6V不要接树莓派 5V板子的 GND 和树莓派 GND 必须连通树莓派 3B/4B 的 I2C 是 GPIO2SDA和 GPIO3SCL树莓派 5 上位置一致。频率设置上有个细节舵机要 50Hz但如果你想让它稍微更精细可以设到 60Hz很多舵机也吃得下。设太高比如 200Hz 以上会导致脉宽分辨率不够角度抖动。PCA9685 的 PWM 分辨率是 12 位50Hz 下每格约 4.88us够用。多块板子并联时改地址默认 0x40往上依次 0x41 到 0x7F。改地址要焊板子上的 A0-A5 焊盘别偷懒用软件改硬件地址最稳。5.2 总线舵机的串口玩法总线舵机这几年在树莓派毕设里越来越常见原因很简单它解决了开环舵机的所有痛点。它的核心区别是带反馈的闭环数字通信。树莓派通过半双工串口发出指令帧包含 ID、目标位置、速度、加速度舵机回传当前位置、负载、温度、电压。你终于能知道它到底转没转到位。代价是接线和协议都复杂一些需要一根信号线做半双工收发需要一个电平转换或者专用的总线适配板还要按协议拼帧。飞特那类舵机的协议文档是公开的但拼帧这一步对新手不算友好。我的建议是第一次做用 SG90/MG90S 加 PCA9685 就够了把整个链路跑通、把动作调顺。等你确实需要高扭矩、需要位置反馈、需要走总线拓扑一条线串十几个舵机的时候再上总线舵机。别一上来就上容易在协议细节上卡住而放弃。5.3 机械臂与云台场景的取舍场景推荐方案原因单轴指向、简单云台树莓派引脚直驱 MG90S结构最简单成本最低双轴云台俯仰偏航2 路 PCA9685 或直驱两路电流可控直驱也行四到六轴机械臂PCA9685 MG996R通道多必须独立供电仿生手臂、高精度关节总线舵机需要反馈和轨迹控制毕设级别的复杂项目总线舵机 ROS2可扩展、可标定树莓派 5 上跑 Ubuntu 加 ROS2 再控制舵机是现在很多毕业设计的技术栈。这种做法要注意的是ROS2 的节点通信有延迟抖动舵机控制节点最好用独立的定时器线程别把 PWM 输出放在回调里直接做。6. 实战排查链路几个高频故障的完整定位过程前面讲的是原理和方案这一节讲的是出问题了怎么查。我按现象 - 可能原因 - 验证方法的顺序写你可以直接照着走一遍。6.1 一上电树莓派就重启这是最常见也最吓人的一个。我的排查顺序是这样的先断开舵机电源只让树莓派跑确认树莓派本身稳定然后接上舵机电源但不接信号线看是否还重启——如果重启说明是纯电源问题跟信号无关接着接上信号线上电观察。如果确认是电源问题往前扒检查是不是从树莓派 5V 引脚取的电是的话直接改独立供电如果已经是独立供电量一下舵机电源的上电波形看有没有跌落最后给舵机电源端并 2200uF 电容再试。有个很隐蔽的情况舵机电源和树莓派共用一个插座但那个插座上的电源适配器功率不够两个设备加起来超了。这种就要换个更大的适配器或者完全分开。6.2 舵机只往一个方向转到底/不动先分清是完全不响应还是响应但方向不对。完全不响应的话按这个顺序查万用表量舵机电源是不是真的有 5V信号线是不是接对了引脚BCM 编号和物理引脚编号是两回事这个坑踩过太多人共地有没有做代码里的引脚号是不是和实际接线一致。如果三个条件都满足还是不动那可能是信号电平问题。舵机信号线期望 3.3V 到 5V 的方波树莓派的 GPIO 输出 3.3V绝大多数舵机能识别但少数老型号或者长线传输时会识别不到。可以在信号线上加一个简单的电平转换或者加个上拉。如果是响应但只往一个方向转到底八成是这两种情况。第一种你手里拿的是 360 度连续旋转舵机按角度公式去控制它就是这样。第二种脉宽明显超出了舵机行程比如写了个 3000us 的脉宽舵机一直朝一个方向顶到底。把脉宽压回 500 到 2500us 之间再试。还有一种情况是舵机内部电位器坏了或者齿轮卡死。判断方法断电后用手轻轻转输出轴能顺滑转动的机械正常转不动的就废了。6.3 MG996R发烫、抖动、位置漂移MG996R 是大扭矩舵机问题也最多。发烫通常是两个原因一是持续通电待机控制板一直在纠偏静态电流虽然只有 100mA 左右但长时间也会温热二是堵转负载超过额定扭矩电流飙到 2A 以上几分钟就能烫手。后者必须减负载或者加配重平衡别硬撑会烧。抖动大多是电源纹波大或者负载太接近极限。实测电压低于 4.8V 时抖动会明显加剧所以供电线要够粗长度要短。位置漂移是舵机的通病。开环舵机在反复动作几百次之后会有累积误差尤其是廉价型号。我的做法是每次动作开始前先回一次中位做归零用一个已知的参考点消除累积误差。如果你的电源支持也可以在每次动作结束后detach()减少舵机内部持续纠偏带来的磨损。6.4 长时间运行的稳定性经验最后几点是从长期跑下来的项目里总结的写进代码能省很多事。第一加看门狗。如果 PWM 输出线程挂了舵机会停在上次的位置不动。用一个独立的监控线程超过 500ms 没有心跳就主动把所有通道设成中位并断开输出。这比舵机乱转要安全得多。第二所有角度指令做边界裁剪。不管上层算出来什么值进驱动层之前统一 clamp 到 0 到 180 度避免一个异常值把舵机顶到极限。第三日志要有但别刷屏。记录每次指令的目标角度和时间戳出问题时能复盘。频率控制在每秒一条以内别把 SD 卡写满。第四散热和走线。机械臂在密闭壳子里连续工作舵机温度会堆上去。留通风孔或者每隔一段时间让舵机回中位休息几秒。最后补充一个我实际用下来很顺手的做法把所有的舵机参数、校准值、运动时长都放到一个 YAML 或 JSON 配置文件里代码只读配置。换舵机、调参数的时候不用改代码、不用重启程序这对反复调试机械结构的人特别友好。前面那套分段校准的三个数就放这里最合适。
返回列表