简介:这份小R树莓派仿生六足蜘蛛机器人二次开发源码包,面向机器人爱好者与进阶开发者,采用驱动层与AI层分离的模块化设计:驱动层基于C++编写并封装为so函数库,AI层使用Python结合OpenCV实现视觉识别,识别结果再回传驱动层做出相应反馈动作。包体整体约161KB,以zip格式压缩,解压后包含cpp与python两大目录,分别对应六足核心驱动程序(C++)和机器视觉功能模块(Python),可自行编译与修改,层次清晰。目前已有1184人浏览学习,适合具备一定C++或Python基础、希望扩展机器人视觉交互能力的创客参考。通过这套代码,既能理解六足机器人底层步态控制与上层视觉感知的协作思路,也可以基于现有驱动框架快速二次开发自定义功能,省去从零搭建通信协议与视觉管线的成本。
1. 树莓派仿生六足蜘蛛源码:拿到手先过这三关
很多人拿到这套树莓派仿生六足蜘蛛机器人二次开发源码,第一反应是“赶紧跑起来”,结果十有八九卡在舵机供电和步态抖动上。这个项目的价值恰恰不在“能走路”,而在于它把逆运动学、步态相位、PWM控制三层逻辑拆开了,让你从改一个步幅参数到加一个传感器,都有明确入口。对做课程设计、竞赛、创客项目的六足机器人爱好者来说,这份源码可以省掉从零写底层的几周时间。但源码跑通只是开始,怎么把它调稳、改顺、扩展成自己的东西,才是后面几章要解决的问题。
2. 硬件接线到源码跑通:先立一个可复现的运行基线
拆这份源码之前,我先把硬件环境整理成“能稳定复现”的状态。很多后续问题其实不是代码问题,而是硬件基线没立好。所以我建议任何拿到源码的人,都从硬件准备开始,不要直接上电跑。
2.1 硬件清单与三路供电原则
这套源码面向常见的六足蜘蛛结构:树莓派做主控,下挂一块PCA9685舵机驱动板,驱动12路舵机,每条腿用2个舵机实现髋关节和膝关节两个自由度。硬件选型不需要太高端,但供电方案不能省。
| 硬件 | 规格参考 | 数量 | 关键作用 |
|---|---|---|---|
| 树莓派 | 3B+ 或 4B,2GB 内存以上 | 1 | 运行 Python 主控、Web 控制端、图像算法 |
| PCA9685 舵机驱动板 | 16 路 PWM,I2C 接口 | 1 | 输出 50Hz 舵机脉冲,减轻树莓派实时负担 |
| 舵机 | 模拟舵机,扭矩 9kg·cm 以上 | 12 | 每条腿 2 个,驱动髋与膝 |
| 舵机供电电池 | 6V 镍氢电池组或 2S 锂电池降压 | 1 | 专供舵机,与树莓派电源隔离 |
| 树莓派电源 | 5V 2.5A 及以上 USB 电源 | 1 | 独立供电,不占用舵机电流 |
三路供电原则是我踩过最深的坑:舵机一路、树莓派一路、传感器扩展一路,三者负极必须接在同一参考地。最开始我以为“全部用一个5V 3A电源就行”,结果舵机一启动,树莓派直接黑屏重启,源头就是瞬间电流抽走导致电压跌落。
接线顺序上,PCA9685的SDA、SCL接树莓派对应I2C引脚,VCC接5V,GND与树莓派GND、舵机电源负极三者共地。12路舵机信号线依次接PCA9685的PWM0到PWM11,每组舵机正负极单独从电池取电,不要从树莓派5V引脚取电。
2.2 环境初始化和源码跑通步骤
树莓派建议直接用官方系统,不要用第三方精简版,因为源码依赖的I2C、蓝牙、网络服务在官方系统上最省心。拿到源码后,先放到固定路径,比如~/spider/,然后做环境初始化:
# 开启树莓派 I2C 接口 sudo raspi-config # 进入 Interface Options -> I2C -> Enable # 安装 Python 依赖,需要 numpy、smbus2、Flask sudo apt update sudo apt install -y python3-pip python3-smbus i2c-tools pip3 install numpy smbus2 flask # 确认 I2C 设备能被识别,PCA9685 默认地址是 0x40 i2cdetect -y 1这段命令做的事是:第一,打开系统I2C总线;第二,安装smbus2用于读写PCA9685寄存器,安装numpy用于逆运动学向量计算,安装flask用于运行手机控制页面;第三,用i2cdetect检查设备树,看到40出现在地址列表里,说明PCA9685已经被正确枚举。
这里有一个新手特别容易忽略的参数:-y 1中的1是 I2C 总线号。树莓派3B/4B默认总线是1,老款树莓派1代是0,如果i2cdetect报错,先确认总线号是否是1。
环境就绪后跑通主程序:
cd ~/spider # 先空载运行,不接舵机,观察日志能否正常滚动 python3 src/main.py主程序启动后,终端会打印步态周期、目标点坐标、每路舵机脉宽值。空载跑10分钟不报错,再接着做上电测试。我一般会在这一步顺手用htop看下CPU占用,如果空闲时占用超过30%,就要检查是不是有线程空转。
2.3 首次上电前的检查与通电流程
第一次真正带舵机上电前,要按这个顺序检查,缺一步都容易翻车:
先检查舵机信号线顺序:12路舵机的通道顺序必须与源码里的腿部编号映射一致,源码头部通常会有一张LEG_CHANNEL_MAP映射表,上电前先对一遍表格再接线。然后检查舵机电源电压:空载测一下电池输出,6V镍氢充满约7.2V,带降压模块的调到6V;如果电压超过6.5V,多数模拟舵机容易烧。接着检查舵机安装角度:每条腿的髋关节舵机在中位时,腿应该垂直于身体侧面,如果不垂直,先手工掰正再上电,不要靠代码硬拉。
最后是通电顺序:先开舵机电源,等1秒,再启动树莓派上的主程序。反过来容易导致树莓派初始化PWM时舵机瞬间收到一个未定义脉冲,整机猛抖一下然后卡死。
3. 逆运动学与步态生成:把“六条腿怎么动”算成可调参数
这一章是这份源码最有价值的部分。仿生六足不是简单让舵机转固定的角度,而是先定义每只脚的期望坐标,再用逆运动学算出髋关节和膝关节分别该转多少度。理解了这一点,你就知道为什么源码里那么多数学函数,以及为什么“改一个参数全腿乱动”。
3.1 腿部坐标系与几何模型定义
每条腿从身体侧面伸出去,我把腿部模型抽象成两个关节:髋关节负责整条腿在水平面内的摆动,膝关节负责抬腿和落地。坐标系以髋关节轴心为原点,X轴指向机器人前进方向,Y轴指向身体外侧,Z轴向上。
大腿长度记为L1,小腿长度记为L2,这两个值在源码的robot_config.py里定义。不同尺寸的六足机器人,这两个值差别很大,如果你用的是打印件机身,务必用卡尺实测后填进去,不要照抄默认值。还有一个容易被忽略的量:髋关节轴心到膝关节水平投影的偏移量hip_offset,实际安装时由于舵机外形限制,髋关节转动轴心并不在膝关节正上方,这个偏移一定要量。
这个模型之所以关键,是因为后续所有步态计算都是在这个坐标系下进行的。如果你重新设计机身,改变了舵机安装方式或腿长,只需要改配置,不需要改IK求解函数。
3.2 逆运动学求解代码与参数边界
下面的代码是这类源码最常见的IK求解写法,基于余弦定理求解膝关节,再反推髋关节角度:
import numpy as np def legs_ik(x, y, z, L1=58.0, L2=68.0, hip_offset=12.0): """ 将腿部末端目标坐标转为髋关节和膝关节角度 x: 前方坐标,y: 外侧坐标,z: 向上高度 返回 (hip_angle, knee_angle),单位角度 """ # 1. 髋关节转角:水平面内,用 atan2 计算目标相对于大腿根的方向 hip_angle = np.degrees(np.arctan2(y, x)) # 2. 扣除髋关节水平偏移后,取腿部末端到髋轴的水平距离 r = np.sqrt(x**2 + y**2) - hip_offset if r < 0: r = 0.0 # 3. 高度方向的差值,以髋轴安装高度为基准 z_diff = z # 4. 目标点到膝关节轴心的直线距离 d d = np.sqrt(r**2 + z_diff**2) d = np.clip(d, 0.1, L1 + L2 - 1.0) # 5. 用余弦定理求解膝关节内角(单位弧度) cos_knee = (L1**2 + d**2 - L2**2) / (2 * L1 * d) cos_knee = np.clip(cos_knee, -1.0, 1.0) knee_angle = np.acos(cos_knee) # 6. 求解髋关节俯仰(大腿与水平线夹角),再换算成舵机期望角 alpha = np.arctan2(z_diff, r) beta = np.arccos(np.clip(d**2 + L1**2 - L2**2) / (2 * d * L1)) hip_angle = np.degrees(alpha + beta) return hip_angle, np.degrees(knee_angle)这段代码做了三件事:第一步用atan2算髋关节水平转角;第二步到第四步求出末端点与膝关节的距离,然后用余弦定理算出膝关节内角;第六步把几何关系换算成舵机角度。其中np.clip是一个关键保护:当目标坐标超出机械臂可达范围时,把距离限制在L1+L2-1.0以内,避免舵机被掰到物理极限。
参数说明:L1是大腿长度,L2是小腿长度,单位毫米;hip_offset是髋关节偏移量,如果你的机器人腿部安装没有偏移,可以设为0。这个函数在源码头文件里有默认值,但强烈建议按照自己的机身实测修改,不然走起来会八字脚甚至绊腿。
IK的“边界”是另一个坑。每条腿的末端坐标不能随意传,z值不能小于某个最小值,否则腿部结构会反弯。源码里通常有一段边界约束函数,核心思想就是:让末端点始终落在髋关节前方的可达扇形区域内。改步幅参数时,我会先用一个plot脚本把目标点位画出来,确认都在可达域内再写进步态。
3.3 三角步态与波动步态的相位调节
六足机器人之所以稳,是因为至少有三条腿可以同时支撑身体。这份源码里默认实现了两种常见步态:三角步态和波动步态。三角步态把六条腿分为两组,组内三条腿同时抬起、同时落下,适合速度要求高的场景;波动步态是六条腿逐次抬起,同一时间只有一条腿在摆动,适合慢速精细移动。
相位控制是步态的核心,源码里类似这样组织:
# 腿部编号:R1 R2 R3 为右侧,L1 L2 L3 为左侧 # 三角步态分组:A组 = (R1, R2, L3),B组 = (L1, L2, R3) TRIPOD_PHASE_TABLE = { 0: {"R1": 0.0, "R2": 0.0, "L3": 0.0}, 1: {"L1": 180.0, "L2": 180.0, "R3": 180.0}, } def get_leg_target_xy(gait_phase, leg_name, step_length, step_height): group = 0 if gait_phase < 180 else 1 phase_in_group = gait_phase % 180 if leg_name in TRIPOD_PHASE_TABLE[group]: # 摆动相:抬腿向前 progress = phase_in_group / 180.0 offset_x = -step_length / 2 + step_length * progress offset_y = 0.0 offset_z = -step_height * np.sin(np.radians(180 * progress)) else: # 支撑相:保持支撑,向后滑动 offset_x = step_length / 2 - step_length * (phase_in_group / 180.0) offset_y = 0.0 offset_z = 0.0 return offset_x, offset_y, offset_z步态生成的核心思想是:给每条腿一个随gait_phase变化的偏移量,再把偏移量加到腿部中立位置坐标上,最后送入IK求解。摆动相的腿沿正弦轨迹抬高,避免抬腿瞬间把身体顶歪;支撑相的腿向后滑动,实现身体相对地面的前进。step_length控制步幅,值太大容易重心不稳;step_height控制抬腿高度,地面不平整时可以适当调大,但是调大会让舵机负载明显变大。
相位表参数需要注意:不同腿的相位不能随便写,六足机器人的稳定条件是身体重心始终落在支撑腿构成的三角形内。三角步态把对角腿分在一组,就是利用三角形的稳定性。如果你要自定义步态,第一步是画支撑多边形,第二步是调相位差,第三才是调步幅。
4. 舵机驱动与PWM校准:每个脉冲都要落在对的位置
逆运动学算出了角度,但舵机根本不认角度,它只认脉宽。这就是为什么很多开发者把IK改得很漂亮,机器人却不走路的根本原因:角度到脉宽的映射出了问题。这一章拆PCA9685的驱动逻辑和校准方法。
4.1 用PCA9685输出精确脉冲
PCA9685是I2C接口的16路PWM芯片,输出频率可调,驱动舵机时设成50Hz,对应周期20ms。每路PWM的脉宽用12位寄存器值控制,0到4096对应0%到100%占空比。50Hz下,每1度约需要11到13个寄存器步进,具体取决于你设定的脉宽范围。
源码里最底层的驱动长这样:
import smbus2 class PCA9685Driver: def __init__(self, bus=1, address=0x40, freq=50): self.bus = smbus2.SMBus(bus) self.address = address self.set_pwm_freq(freq) def set_pwm_freq(self, freq): """设置PWM频率,舵机标准是50Hz""" prescale = int(round(25000000 / (4096 * freq)) - 1) self.bus.write_byte_data(self.address, 0xFE, prescale) self.bus.write_byte_data(self.address, 0x00, 0x01) # 重启 self.bus.write_byte_data(self.address, 0x00, 0x00) # 恢复运行 def set_pwm(self, channel, pulse_us): """pulse_us 为脉冲宽度,单位微秒,例:1500us 表示中位""" if not (500 <= pulse_us <= 2500): pulse_us = max(500, min(2500, pulse_us)) # 20ms 周期分成4096步,每步约4.88us value = int(pulse_us * 4096 / 20000) self.bus.write_byte_data(self.address, 0x06 + channel * 4, 0) self.bus.write_byte_data(self.address, 0x06 + channel * 4 + 1, 0) self.bus.write_byte_data(self.address, 0x06 + channel * 4 + 2, value & 0xFF) self.bus.write_byte_data(self.address, 0x06 + channel * 4 + 3, value >> 8)关键在set_pwm函数里:脉宽先被限制在500到2500微秒之间,再做一次比例换算。value = pulse_us * 4096 / 20000这个公式的意思是,20ms周期内要输出多少占空比,乘以4096得到寄存器值。最后写入四个寄存器,前两个是PWM开始时间(设为0),后两个是PWM结束时间。
逻辑说明:舵机接收的脉宽越宽,舵机轴转得越多。500us对应0度,1500us对应90度中位,2500us对应180度左右。很多舵机实际量程不到180度,所以校准前先用pulse_us=1500让所有舵机回中位,确认机械安装是否正确。
4.2 中性点校准与最小脉宽设置
不同厂家的舵机,同样1500us对应的角度可能会有5到15度的偏差。这份源码在配置文件里保留了SERVO_MIN_PULSE和SERVO_MAX_PULSE两个全局量,但直接把默认值用上去大概率会偏。我的做法是写一个校准脚本,逐个通道测试:
def calibration_scan(driver, channel=0): """逐个脉宽扫描,记录舵机从 1000us 到 2000us 的实际位置""" print(f"通道 {channel} 校准开始") for pulse in range(1000, 2001, 50): driver.set_pwm(channel, pulse) print(f"脉宽 {pulse}us") # 用手掰或用角度尺确认舵机实际角度,记录到表格 driver.set_pwm(channel, 1500)这段脚本一次只测一个通道,脉宽从1000us开始,每次加50us,人工记录舵机实际转到哪个角度。注意:每次改脉宽后要停1到2秒,等舵机到位,不要连续快速扫,模拟舵机内部电位器反馈跟不上会拉回再摆动,数据就不准。
校准的关键结论:每只舵机的线性区间可能不同。我遇到过一个现象:左边腿和右边腿用同样脉宽,角度差了8度,跑直线时机器人明显向一侧偏。解决办法是给每个舵机单独维护min_pulse和max_pulse校准映射,这份源码里如果只有全局配置,建议在腿部映射表里追加一列校准偏移量。
地线共地这个事在校准时也要再强调一遍:如果不共地,PCA9685输出的脉冲基准和舵机电源基准不一致,表现在舵机上就是随机抖动或者角度漂移,查代码永远查不出来。
4.3 抖动、发热与供电质量的处理
四足和六足机器人最常见的玄学问题就是“舵机莫名抖动”。如果脉宽输出是稳定的,问题基本在供电。舵机启动瞬间电流是额定电流的2到3倍,六条腿同时切换状态时,瞬时电流峰值可能超过5A,普通稳压模块根本扛不住,输出电压一跌,舵机就反复重启。源码里能做的事是错峰控制:
def set_all_servos(driver, pulse_list): """设置12路舵机,但每路之间延时2ms,降低瞬时电流尖峰""" for channel, pulse in enumerate(pulse_list): driver.set_pwm(channel, pulse) time.sleep(0.002)这个set_all_servos函数虽然只是加了2ms延时,却能明显减少电源跌落概率。如果还抖,那就不是软件问题,而是电池和降压模块不行。我验证过,6V 2A的稳压模块带12个舵机,爬行时电压会掉到4.5V,换6V 5A的降压模块后,一切正常。
发热问题也要重视。如果某条腿的舵机温度明显高于其他腿,检查这条腿的膝关节是否在IK求解中被压到了极限角度,导致舵机长期顶在机械限位上。舵机本质上是PWM脉宽控制位置,如果位置到达不了,内部电机还在堵转,一秒内温度就会明显上升。处理办法是检查这个通道有没有被发送超过物理限位的脉宽,或者腿长配置值是否比实际偏长。
用示波器或逻辑分析仪看PWM波形是最直接的排查手段。没示波器也没关系,读取PCA9685的寄存器输出值:
# 读取通道0的PWM结束寄存器低位和高位 i2cget -y 1 0x40 0x0E i2cget -y 1 0x40 0x0F如果寄存器输出值稳定,舵机仍然抖,问题就在电源和机械虚位,而不是软件。
5. 二次开发接口与避坑排查:把别人的源码变成自己的机器人
源码工程本身是一个运行的机器人,但二次开发的价值在于你能在它的骨架上做自己的事。这一章讲我拆完这份源码后认为最值得改的接口,以及我实际踩过的几个坑。
5.1 从哪几处接口切入二次开发
源码工程通常分成config、ik、gait、driver、main五层。初学者最喜欢在主循环里直接改步态数据,结果改崩了很难回滚。正确的切入顺序是:
第一,改配置层。腿长、舵机通道、舵机脉宽范围、身体高度、爬行速度,全部集中在配置类里。这里改成适合你机身的数据,后面所有模块都会跟着生效。
第二,改步态层。如果你想让机器人上楼梯,就在步态层新增一个step_up()方法,复用现有的get_leg_target_xy接口,只改变抬腿阶段的轨迹,不需要碰IK和PWM。
第三,改行为层。主程序是一个行为状态机,接收消息加执行行为。新增自定义行为时,注册一个命令行:
def behavior_dance(self, duration=5.0): """示例:新增一个原地摇摆的表演行为""" start = time.time() while time.time() - start < duration: # 循环切换左右腿目标坐标,实现摇摆 for side in ["left", "right"]: targets = self.get_side_targets(side, height_offset=-10) self.set_leg_targets(targets) time.sleep(0.15)行为层的核心是一个注册表:字符串命令映射到处理方法。新增行为只需要在注册表补一条映射,维护成本很低,不会破坏原有流程。我自己习惯在开发时先写一个假的行为函数,打印日志而不是真动腿,等逻辑验证通过再接PWM。
5.2 扩展传感器与上位机的常见做法
这类源码普遍在sensor.py模块预留了传感器接口,常见扩展是超声波避障和摄像头视觉跟随。超声波模块的接口一般是这样:一个distance()函数返回前方障碍距离,行为层里判断小于阈值就切换步态。
def auto_avoid(self, threshold_cm=20.0): """带超声波避障的巡线移动逻辑""" dist = self.ultrasonic.distance() if dist is None or dist > threshold_cm: self.gait.set_speed(40) # 无障碍,正常前进 else: self.gait.set_speed(0) # 有障碍,先停下 self.gait.turn(angle=30) # 右转30度再重新决策这里有一个数据质量坑:超声波偶尔会返回异常值,比如突然变成0或者超过量程。常见做法是在传感器模块里加一个中值滤波,连续取5次距离排序后取中间值,避免一个毛刺让机器人原地发呆甚至撞上去。
摄像头模块一般用OpenCV跑颜色识别或二维码定位,输出目标物体在画面中的偏移量。这个偏移量要换算成身体的转向角度,换算系数和摄像头安装高度、视角有关。我习惯先固定机身,把目标物放在正前方1米处做一次标定,记录像素偏移与实际角度的比例,再写进配置。
5.3 避坑与排查清单:五个值得背下来的血泪经验
写代码是一回事,调机器人是另一回事。下面五条按“现象、原因、解决”的记录方式列出来,每一条都是我实际遇到过的,照着排查能省半天时间。
现象一:一上电六条腿同时抽搐,或者发出啪嗒啪嗒的乱跳声。
原因是舵机电源与树莓派没有共地,PCA9685输出的PWM信号基准与舵机电源基准不一致,导致脉宽解释随机。解决方法是检查GND线:PCA9685的GND、树莓派的GND、舵机电池的负极必须接在一起,用万用表量这三者之间的电阻,应该接近0欧姆。
现象二:某条腿的舵机转到物理极限后一直嗡嗡响,越响越热。
原因是脉宽值超出了该舵机的实际线性范围,舵机一直在尝试转到一个机械上不允许的位置。解决办法是把这条腿对应的脉宽强制限制在900us到2100us之间,然后重新校准。不要用全局的500到2500作为安全范围,那只是理论值。
现象三:程序报I2C bus error或PermissionError,主程序起不来。
原因是当前用户没有I2C设备访问权限,或者PCA9685的I2C地址不是默认的0x40。先用ls /dev/i2c*确认设备存在,再用i2cdetect -y 1查看实际地址,如果扫描出的是0x44或其他地址,就要改源码里的地址参数。权限问题执行sudo usermod -aG i2c 用户名,然后重新登录。
现象四:手机能打开控制页面,但点前进没反应,要等好几秒才动。
原因是Flask控制端默认绑定在127.0.0.1,只接受本机访问,或者主循环里IK计算阻塞了消息接收。解决方法是把app.run(host='0.0.0.0', port=5000)里的host改成0.0.0.0,让控制端和机器人主循环分线程跑。最干净的做法是主线程跑步态循环,单独线程跑Flask服务,消息通过队列传给主线程。
现象五:跑了几分钟后,某条腿先是动作迟缓,然后彻底不动。
原因是舵机过载烧毁,或者是该通道对应的PCA9685输出脚虚焊。先断电,把这条腿的舵机拆下来,单独接信号测脉宽,如果能动就是供电问题,不能动就是舵机坏了。如果是虚焊,用放大镜检查PWM通道焊盘。
6. 验证一个步态改动是否有效:三个能一直用下去的习惯
6.1 分级验证与进退位习惯
六足机器人改动最怕的是“改完不知道有没有更好”。我后来固定了一套三级验证流程,每次只改一个参数,改完跑完整流程,对比数据说话。
第一级是静态验证,专门验证IK和目标坐标。在不动舵机的情况下,只打印每条腿的目标坐标和IK求解结果,看坐标是否连续、有没有突变。第二级是脉宽验证,把机器人架起来,四脚离地空跑,这一步只验证舵机响应和目标角度一致,不验证步态稳定性。第三级才是地面验证,量一个固定距离,跑三次,取平均耗时和偏移量。
# 一键跑完三级验证的脚本骨架,每次改参数前后各跑一次 python3 tools/validate_static.py # 1. 检查IK坐标是否在可达域 python3 tools/validate_pwm.py # 2. 架空空跑,检查脉宽输出 python3 tools/validate_ground.py --distance 100 --loops 3地面验证记录两个数:跑完100cm的时间,以及终点相对起点的横向偏移。如果你把步幅从20mm改成25mm,时间缩短是正常的,但如果横向偏移从1cm变成5cm,那就不是步幅问题,而是左右腿相位不对称或者舵机校准不一致。
从那以后,我每次改完步态参数都会强制走一遍这三步,看着像多花十分钟,实际是给自己留了后悔药。数据对比会让你立刻知道改动是正向还是反向的,而不是大改一通然后看机器人原地打转。希望这个方法对你也有用,能帮你在六足机器人的调试里少走一段弯路。
本文还有配套的精品资源,点击获取