
先把结论放在前面树莓派4B控制舵机最稳的一条路线不一定是网上传烂了的“RPi.GPIO PWM”而很可能是“串口总线舵机 pigpio”。我去年做一台桌面机械臂时用RPi.GPIO调PWM舵机调到怀疑人生手一碰外壳脉冲就抖舵机咔咔作响位置也永远差那么一点。换成串口总线舵机之后事情突然变得清爽一根线把舵机串起来发指令帧、读角度反馈没有PWM抖动焦虑也没有GPIO库的CPU占用问题。这篇就把整套方案从头捋一遍从树莓派4B的串口释放、pigpio安装到Python脚本的封包调试一次性讲透给正准备入手机器人、云台、小型机械臂项目的朋友一份可以直接抄的作业。1. 舵机控制思路为什么是串口总线舵机 pigpio1.1 PWM舵机和串口总线舵机到底差在哪先聊原理不然后面出了问题你都不知道从哪排查。普通PWM舵机比如SG90、MG996R的核心是一个直流电机加一组减速齿轮再加一个电位器反馈当前位置。外部只需要给一根信号线周期20ms左右高电平时间1ms到2ms对应0到180度。舵机内部的比较器会拿输入脉宽和电位器反馈值做对比有差值就驱动电机转直到差值归零。这套东西很成熟便宜几块钱就能买到但问题也很明显它只接收脉宽不向主机回传任何状态。你发一个1.5ms的脉宽舵机理论上转到90度但它到底停没停在90度或者中途被卡住、堵转你完全不知道。堵转的时候电机还一直在那较劲发热、耗电时间久了直接烧。串口总线舵机是另一个思路。它内部有一块MCU背后有一个角度传感器电位器或磁编码器通讯靠串口指令帧完成。主机通过UART发一串十六进制指令比如“ID为1的舵机转到角度500”舵机收到后执行并且可以回传“我现在在角度497”这样的应答。这意味着你能拿到舵机的真实位置可以做闭环可以做限位保护可以让舵机以指定转速运动。多舵机场景下优势更明显PWM舵机一个GPIO只能带一个做六自由度机械臂就得占六个GPIO线缆乱成一团串口总线舵机一条总线从头串到尾两根线就能带一长串机械结构清爽得多。做个不太恰当的类比PWM舵机是“喊话式管理”你喊一声它去做做没做你不知道串口总线舵机是“微信式管理”你发消息它能回你群聊加人、分组管理都方便。1.2 在树莓派4B上pigpio凭什么比RPi.GPIO好用很多新手把树莓派当单片机用装了RPi.GPIO库就开始输出PWM然后发现舵机抖得像帕金森。原因在于RPi.GPIO的PWM是纯软件模拟靠内核线程不停地翻转GPIO电平。树莓派跑的是Linux系统调度一忙线程被抢占脉冲宽度就从1.5ms抖到1.7ms舵机自然跟着抖。pigpio不一样。它在底层用了DMA和硬件PWM资源脉冲波形由硬件精确产生CPU占用极低抖动可以控制在微秒级。这还只是它的一半功能。pigpio的另一个杀手锏是内置了串口接口可以直接用它的 serial_open、serial_write、serial_read 读写Linux串口设备波特率可以随便指定不需要再单独装pyserial。也就是说一个pigpio既能高精度输出PWM又能操作UART正好覆盖舵机控制的两条技术路线。我把三套方案的差异整理成一个对比一目了然方案PWM精度串口支持CPU占用适合场景RPi.GPIO差软件模拟抖动大无高只做LED闪烁、简单开关pyserial无标准串口中单纯串口透传pigpio高硬件PWM自带串口接口低舵机控制、精确波形、机器人项目实测下来在树莓派4B上跑pyserial做串口舵机控制也能用但如果你的项目里既有PWM需求又有串口需求pigpio一个库全包了省得维护多套依赖。1.3 这套方案适合什么项目不适合什么项目比较典型的应用是桌面机械臂、两轴云台、仿生机器人关节、小车转向舵机这类需要多舵机协同、又需要位置反馈的项目。树莓派4B有不错的算力跑个Python脚本做运动规划完全够再加上4B的USB和GPIO资源都充足接个摄像头做视觉引导也顺理成章。但也要泼盆冷水树莓派毕竟是Linux系统实时性天生不如单片机。如果你搞的是穿越机舵面、高速伺服或者需要对舵机进行毫秒级闭环控制那老老实实上STM32、ESP32或者给树莓派加一块MCU协同处理。树莓派适合做“决策大脑”不适合做“肌肉神经”。这个定位想清楚后面选型就不会跑偏。2. 硬件接线与树莓派串口释放2.1 物料清单和一分钟接线这套方案需要的硬件很少我列一个基本清单树莓派4B一台2GB内存起步系统用Raspberry Pi OS Lite或Desktop都行我推荐Lite版跑服务器型应用更干净串口总线舵机一个起步我这边用的是某常见品牌的LX-16A串口TTL电平其余品牌协议层大同小异USB转TTL模块一个调试阶段很有用建议选带3.3V/5V电平跳线的独立5V电源舵机供电不要省后面会讲为什么杜邦线若干接线核心是UART那两根线。树莓派40Pin排针上GPIO14是TXD对应物理Pin8GPIO15是RXD对应物理Pin10接地选Pin6就行。表格放这里方便对着查树莓派引脚物理Pin功能接舵机控制板GPIO14Pin8TXDRXDGPIO15Pin10RXDTXDGNDPin6GNDGND这里有个新手必踩的坑TXD一定要接对方的RXDRXD接对方的TXD交叉连接。有些人照着图片接成TXD对TXD、RXD对RXD然后怎么调都不通还以为是波特率错了。另外要注意半双工问题。很多串口总线舵机是半双工通讯总线上的数据线收发共用。树莓派的UART是标准两线TXD/RXD如果你的舵机控制板带方向控制引脚常见的叫TxEn、DET需要把它接到树莓派任意一个空闲GPIO在发送前拉高、接收前拉低。如果你的控制板内置了自动方向切换那就省事直接交叉接TX/RX即可。这个务必看舵机手册确认别想当然。2.2 把系统占用的UART让给舵机树莓派4B的系统串口默认不是给你用的。它有多个UART板载蓝牙默认占用ttyAMA0内核控制台默认占用另一个串口输出日志。你直接去打开串口会发现要么打不开要么打开了一堆没用的内核log。所以做串口舵机之前先做两步释放操作。第一步编辑/boot/config.txt新版系统路径可能是/boot/firmware/config.txt在文件末尾加一行dtoverlaydisable-bt这一行的作用是彻底关掉板载蓝牙把PL011这个硬件UART释放出来给GPIO14/15使用。树莓派4B上PL011是带硬件流控的稳定串口波特率不会漂移做舵机控制首选。有人会问不关蓝牙行不行可以但你需要额外处理蓝牙和UART的映射关系没必要除非项目里必须要蓝牙。第二步编辑/boot/cmdline.txt找到里面类似consoleserial0,115200的内容把它删掉。这行命令让内核把控制台日志输出到串口如果不删舵机通信时会混进来一堆系统文本指令帧直接被污染。操作完重启sudo reboot重启后检查设备节点ls -l /dev/serial0 /dev/ttyAMA0正常情况下/dev/serial0会指向/dev/ttyAMA0。这一步确认没问题串口才算是真正交到你手里。然后建议用raspi-gpio验证一下引脚功能raspi-gpio get 14 15输出里应该能看到GPIO14和GPIO15处于ALT0状态也就是复用为UART TX/RX。如果你之前用RPi.GPIO把这两个引脚设置成过输出模式这里会显示OUT需要先复位。2.3 用回环测试确认串口链路正常接线和系统配置都做完先别急着连舵机做个最基础的回环测试确认树莓派自己的串口收发是通的。操作极其简单拿一根杜邦线把GPIO14和GPIO15直接短接也就是TXD和RXD相连然后写一段小脚本发送数据。import pigpio import time pi pigpio.pi() if not pi.connected: print(无法连接pigpiod请检查守护进程是否启动) exit(1) handle pi.serial_open(/dev/serial0, 115200) pi.serial_write(handle, bhello) time.sleep(0.1) data pi.serial_read(handle, 5) print(收到回显:, data) pi.serial_close(handle) pi.stop()如果打印出来的data是bhello说明串口收发链路完全正常。如果读到空先查设备节点是否存在再查pigpiod是否启动后面会详细讲。这一步很有价值它能帮你把问题边界划清楚串口内部通了后面接舵机不转就是接线或协议问题串口内部都不通那就是系统配置问题。3. 安装pigpio并打通后端3.1 apt与源码编译两种安装方式pigpio的安装分两部分底层守护进程pigpiod和Python客户端库。守护进程负责直接操作硬件Python库通过socket或管道连接守护进程发指令。这个设计很巧妙它让非root用户的Python程序也能控制硬件同时把硬件操作集中在一个高性能的C进程里。最常见的安装方式是apt直接装sudo apt update sudo apt install pigpio python3-pigpioapt装完pigpiod服务会被自动注册到systemd你可以通过sudo systemctl enable pigpiod sudo systemctl start pigpiod启动它。apt版的好处是省事、稳定和系统包管理器兼容缺点是版本可能落后于官方。如果你想要最新特性可以走源码编译wget https://github.com/joan2937/pigpio/archive/master.zip unzip master.zip cd pigpio-master make sudo make install sudo pigpiod源码编译也不复杂几分钟搞定。我个人建议只要能联网装依赖优先用apt版足够用。除非你遇到官方修复的某个特定bug否则没必要折腾源码。3.2 启动pigpiod守护进程与权限检查装完pigpio后最常见的一个报错是“Cant connect to pigpio daemon”翻译过来就是Python客户端连不上pigpiod。遇到这个错八成是守护进程没启动或者启动后你的用户没有权限。先确认进程状态systemctl status pigpiod如果显示active (running)那再检查权限。pigpiod默认监听本机8888端口普通用户连接通常没问题但在某些系统上你需要在/etc/pigpio或systemd里配置允许访问。最省事的方式是把当前用户加入gpio组sudo usermod -aG gpio $(whoami)然后注销重新登录。还有一种情况如果你是自己手动启动的pigpiod用的却是一个受限用户也会出现连接失败。这种情况直接用sudo启动就行sudo pigpiod调试阶段不用纠结太多能连上就好。另外说个细节pigpio的Python库默认会尝试连接localhost:8888如果你改了端口或监听地址连接时就要手动指定host和portpi pigpio.pi(host127.0.0.1, port8888)3.3 用pigpio打开串口做首次连通验证守护进程通了就可以用Python打开串口了。pigpio的串口接口设计比较简单核心就几个函数serial_open(设备路径, 波特率)打开串口返回句柄serial_write(句柄, 字节数据)发送数据serial_read(句柄, 最大字节数)读取数据serial_data_available(句柄)查询缓冲区有多少字节可读serial_close(句柄)关闭串口有一个关键点要特别提醒serial_open的波特率参数必须显式写不能省略。有些教程里写serial_open(/dev/serial0)结果默认波特率不对舵机完全不响应。我通常这样写handle pi.serial_open(/dev/serial0, 115200)大多数串口总线舵机默认波特率是115200但也有个别厂商用1000000或9600务必查手册确认。波特率不匹配时舵机表现为“完全没反应”指令发过去石沉大海。打开串口后先不做任何协议操作直接向舵机总线发一个空帧或者查询帧用逻辑分析仪看一下有没有波形。没有分析仪的话也没关系后面用舵机的回应来判断。4. 用Python封装舵机控制脚本4.1 看懂串口总线舵机的指令帧协议串口总线舵机的指令帧虽然各家有差异但基本骨架是相通的。我这里以常见的LX-16A为例拆解其他品牌可以类比迁移。一个典型的角度控制帧长这样0x55 0x55 0x06 0x01 0x01 0xF4 0x01 0x00 0xF7拆开来看0x55 0x55帧头固定两个字节用来告诉舵机“一条新指令开始了”。有些舵机是单字节帧头有的这里还有厂家标记0x06长度字段表示从ID到校验和之前有多少个字节0x01舵机ID总线上靠它区分不同舵机0x01命令字。命令1通常代表角度控制其他命令字比如读取角度、设置ID、设置速度等都要查具体手册0xF4 0x01角度数据小端序0x01F4转换成十进制是5000x00速度或运行时间参数0通常表示无限速0xF7校验和算法是从ID开始到最后一个参数逐字节累加后取低8位。算一下0x010x010xF40x010x000xF7刚好对上所有串口总线舵机协议里校验和是绝对不能糊弄的部分。你发错一个字节校验不过舵机会直接丢弃整帧数据它并不会告诉你为什么没反应。所以我写代码时一律用函数自动生成校验绝不用手算的值填进去。4.2 一个可复用的BusServo类理解了协议结构写脚本就顺理成章。我封装了一个BusServo类把帧打包、校验、发送、回读解析都包进去项目里直接实例化就能控制舵机。import pigpio import struct import time class BusServo: # 命令字不同品牌可能不同按手册修改 CMD_ANGLE_WRITE 0x01 CMD_ANGLE_READ 0x04 def __init__(self, pi, servo_id, handle, angle_range(0, 240), raw_range(0, 1000)): self.pi pi self.servo_id servo_id self.handle handle self.angle_min, self.angle_max angle_range self.raw_min, self.raw_max raw_range def _checksum(self, payload): return sum(payload) 0xFF def _send_raw_frame(self, cmd, paramsb): payload bytes([self.servo_id, cmd]) params length len(payload) 1 # 1是校验和占位 frame bytes([0x55, 0x55, length]) payload bytes([self._checksum(payload)]) self.pi.serial_write(self.handle, frame) time.sleep(0.02) # 给舵机留出处理时间避免总线冲突 def set_angle(self, angle, speed0): # 将角度映射到协议原始值 ratio (angle - self.angle_min) / (self.angle_max - self.angle_min) raw int(self.raw_min ratio * (self.raw_max - self.raw_min)) raw max(self.raw_min, min(self.raw_max, raw)) params struct.pack(HH, raw, speed) self._send_raw_frame(self.CMD_ANGLE_WRITE, params) def read_angle(self): self._send_raw_frame(self.CMD_ANGLE_READ) time.sleep(0.05) available self.pi.serial_data_available(self.handle) if available 6: return None data self.pi.serial_read(self.handle, available) # 按协议解析回包帧头 长度 ID 命令 数据 校验 if len(data) 6 and data[0] 0x55 and data[1] 0x55: angle_raw struct.unpack(H, data[5:7])[0] ratio (angle_raw - self.raw_min) / (self.raw_max - self.raw_min) return self.angle_min ratio * (self.angle_max - self.angle_min) return None用起来也很简单pi pigpio.pi() handle pi.serial_open(/dev/serial0, 115200) servo BusServo(pi, servo_id1, handlehandle) servo.set_angle(90) # 转到90度 print(servo.read_angle()) # 读回角度关于角度范围要特别强调不同舵机的原始值范围和角度范围差异很大有的0-1000对应0-240度有的0-4095对应0-360度必须看手册里的比例关系。我见过有人拿LX-16A的参数套在另一款舵机上结果舵机咔咔顶到限位还在发指令非常危险。4.3 多舵机场景与上电顺序坑如果你的项目不止一个舵机那就涉及到ID配置和上电顺序问题。串口总线舵机的ID是软件配置的不是硬件拨码开关。出厂默认ID通常都是1所以多个舵机直接串联时指令会同时发给所有ID为1的舵机它们会一起动作。你需要在每个舵机单独连接时把它改成不同的ID。配置方法一般是发一条设置ID的命令同样要走协议帧具体命令字查手册。上电顺序这个坑很少有人提醒串口总线舵机断电再上电后会恢复到一个默认位置或者执行掉电前最后一条指令。如果你的机械臂在上电瞬间所有舵机都朝某个角度猛冲轻则撞限位重则齿轮扫齿。我的习惯是项目启动脚本里先给所有舵机发送一个“回到初始角度”的指令等它们到位后再开始业务逻辑。如果舵机支持还可以把默认上电角度参数写进舵机内部存储一劳永逸。多舵机顺序控制也要小心。总线上多个舵机同时发指令没问题但读回位置时一定要按顺序一个一个来不能同时向两个舵机发起读取请求否则应答帧会在总线上碰撞你收到的数据就是乱的。4.4 调试日志十六进制输出比什么都管用最后一定要说日志。串口舵机调试最大的痛苦是黑盒你看不到指令到底发没发出去、舵机到底回没回。我的经验是所有发出去的帧和接收到的回包全部用十六进制打印。def log_frame(name, data): print(f{name}: .join(f{b:02X} for b in data))在发送和读取的地方各插一行log_frame(TX, frame) ... log_frame(RX, data)这样跑一次脚本你能直观看到指令帧长什么样、舵机回包长什么样。一旦舵机不动作看TX就知道帧对不对看RX就知道舵机有没有应答。要检查校验和或者多字节对齐也方便对着协议文档逐字节核对。我调试这类东西从来不开什么花哨的可视化工具一个print搞定百分之八十的问题。日志里的时间戳也很关键尤其是排查半双工总线冲突时你需要知道两条指令之间隔了多久。5. 常见问题、排查方法与工具推荐5.1 问题速查表把实际操作中遇到的典型问题整理成表遇到问题直接对着查现象可能原因解决办法舵机完全无响应波特率不匹配核对舵机手册默认波特率serial_open时显式指定舵机完全无响应TX/RX接反交换GPIO14和GPIO15两条线舵机完全无响应串口被内核console占用检查cmdline.txt是否残留consoleserial0舵机偶尔丢指令半双工方向控制没处理确认控制板是否有方向引脚用GPIO切换收发舵机抖动、异响供电不足或共地不良换独立电源树莓派和舵机电源共地pigpio连不上pigpiod未启动systemctl start pigpiod 或 sudo pigpiod串口读回空数据接线错误或总线无应答先做TXD-RXD短接回环测试电脑USB转串口烧录失败CH340驱动没装或版本不对Windows装最新CH340驱动Linux确认ch341模块加载树莓派向Arduino/Pico烧录失败串口被pigpiod占用先关掉pigpiod再烧录舵机上电猛抽初始角度未设置在上电初始化时先发回中位指令5.2 四个让我记忆深刻的踩坑复盘第一个坑是miniUART波特率漂移。我之前用树莓派3B做过一版那时默认的板载串口是miniUART它的波特率依赖内核的core_freq。如果core_freq在运行时变化串口波特率就会跟着漂舵机时不时抽风。树莓派4B用dtoverlaydisable-bt释放出来的是PL011硬件串口没有这个问题。所以如果你用的是老型号树莓派一定要在config.txt里加一句core_freq250固定频率或者用PL011。第二个坑是引脚复用冲突。有次我调代码为了做测试用RPi.GPIO把GPIO14设成了PWM输出模式之后串口怎么都打不开报错说设备忙。折腾了半天才想起来是引脚复用冲突。树莓派同一时刻只能给一个引脚分配一种功能你把它复用为GPIO输出UART就没法用。解决办法是重启或者用raspi-gpio把引脚复位。第三个坑是USB转TTL模块供电不足。有一次我图省事直接用USB转TTL模块的5V输出给舵机供电结果舵机一动作屏幕上的串口监视器就刷乱码甚至整个模块重启。测了一下舵机启动瞬间电流可以飙到1A以上小模块根本扛不住。后来老老实实接独立电源舵机电源和树莓派共地问题立刻消失。第四个坑是校验和写错导致“间歇性失灵”。我最初写帧打包函数时长度字段和校验范围理解串了导致某些指令能跑某些指令舵机直接忽略。那种“有时候行有时候不行”的问题最难查后来打印了完整十六进制帧对着协议一个个字节按才发现长度字段多算了一位。这也再次说明协议细节必须较真不要靠猜。5.3 调试工具与排查顺序建议软件工具方面我常用的有这几个minicomLinux下最经典的串口终端命令行模式下直接收发适合快速验证XCOM和友善串口助手Windows上的串口工具调试USB转TTL接舵机很方便逻辑分析仪强烈建议家里备一个几十块钱的8通道抓UART波形一目了然能看到指令帧的起始位、停止位、波特率误差如果写Python脚本调试直接在脚本里打十六进制日志最简单有效排查顺序也有讲究我的固定套路是“先环回、再抓波、最后看日志”。TXD和RXD短接确认串口链路本身通然后用逻辑分析仪或串口助手确认舵机有没有回波形最后才看自己的Python脚本是不是帧格式错了。这个顺序能帮你快速缩小问题范围不至于在协议里瞎猜。再补一个实际小技巧如果确认帧格式没问题但舵机还是不动把波特率上下几个档位都试一遍比如115200不行就试9600、38400、1000000。有些舵机出厂波特率被改过跟手册对不上这种情况不是个例。写在最后玩舵机项目这几年的一个体会是先别急着写控制代码先把反馈链路打通。串口总线舵机最值钱的地方就是那句回执它能告诉你舵机到底在哪儿、卡没卡、转没转到位。把这个回执读通、读准整个项目就从“盲猜”进化到“可控”。树莓派4B加pigpio这套组合性能有余、生态也成熟非常适合做原型验证和小型机器人平台。真要上量产的实时控制还是建议把执行层下放到MCU树莓派只管规划。后面我还会做一版类似的方案对比用PCA9685接普通PWM舵机做同一个机械臂看看两条路线的性价比和坑差多少到时候再跟各位细聊。