简介:这是一份面向机器人爱好者、电子工程与嵌入式学习者的16舵机双足机器人完整工程资料,围绕舵机控制、电路设计、微控制器编程与无线通信展开,适合想动手实践双足行走项目的进阶玩家参考。压缩包共147个文件,约3.72MB,以49个pcb与44个sch电路设计文件为主,辅以C语言源码、头文件、工程文件及PDF说明文档,覆盖从硬件原理图到程序实现的完整链路。资源中涉及PWM舵机角度控制、多通道驱动与电源管理、步态算法规划,以及nrf24l01无线遥控通信等具体知识点,可帮助读者理解每个关节在不同时间点的位置与速度计算。目前已有489人学习下载,适合作为机器人综合实践、课程设计或毕业项目的参考方案,便于对照电路与代码梳理整体架构。
1. 16舵机双足机器人:从舵机选型到步态落地的完整拆解
16舵机双足机器人,指的是用16个舵机驱动两条腿、每条腿8个自由度的一类小型人形平台。它比常见的12舵机方案多了髋部侧摆和踝部侧摆,换来的是侧向平衡和转向能力,代价是控制复杂度和供电压力陡增。很多人第一次上手,装完发现站都站不稳,问题往往不在代码,而在选型和供电。这套方案适合想认真研究双足步态的开发者,也适合拿它做教学演示或算法验证。下面按选型、装配、控制、调参、避坑的顺序讲清楚,每一步都能照着复现。
2. 舵机选型与自由度分配:16个位置怎么定
2.1 为什么是16个而不是12个
12舵机方案每条腿6个自由度:髋部3个(俯仰、横滚、偏航)、膝部1个、踝部2个。这套配置能走直线,但侧向稳定性全靠身体摆动补偿,转向时容易摔。16舵机方案在每条腿上多加了两个自由度,常见分配是髋部4个、膝部1个、踝部3个,或者髋部3个、膝部2个、踝部3个。多出来的自由度让脚掌可以主动贴地,侧向支撑更稳。
从控制角度看,自由度越多,逆运动学的解空间越大,但同时也意味着你需要更精细的轨迹规划。12舵机时可以用简单的三角步态凑合,16舵机如果还用同样的思路,多出来的关节反而会互相打架。我一般建议先把多出的自由度锁定在中位,等基础步态跑通再逐步放开。
选型上,16个舵机意味着峰值电流可能超过30A。如果每个舵机堵转电流按1.5A算,16个同时发力就是24A,加上余量,电源至少要能持续输出30A。很多新手用一块5V/5A的电源带16个舵机,一上电就复位,这就是供电不足的典型表现。
2.2 舵机参数怎么读:扭矩、速度、电压三件套
选舵机只看三个参数:堵转扭矩、空载速度、工作电压。堵转扭矩决定机器人能不能撑住自身重量,空载速度决定步态频率上限,工作电压决定供电方案。
以一条腿8个舵机为例,髋部俯仰和膝部承受的力矩最大。假设机器人总重2kg,重心到髋部距离10cm,单腿支撑时髋部俯仰舵机需要承受约2kg×9.8×0.1m≈1.96N·m的力矩。考虑动态冲击,安全系数取2,就需要约4N·m的扭矩。市面上常见的20kg·cm舵机换算过来是1.96N·m,明显不够。所以髋部和膝部建议用35kg·cm以上的金属齿舵机,踝部和髋部侧摆可以用20kg·cm的。
速度方面,空载速度0.1s/60°是常见规格。步态频率如果做到1Hz,每个摆动相只有0.5s,舵机需要在这段时间内完成抬腿、前摆、落腿三个动作,速度太慢就跟不上。我一般选0.12s/60°以内的。
电压方面,6V和7.4V是主流。7.4V舵机扭矩更大,但对电源和驱动板要求更高。如果第一次做,建议从6V系统入手,成熟后再升压。
2.3 供电与驱动板:别让舵机把主控拖复位
16个舵机同时动作时,电流波动极大。主控和舵机如果共用一路电源,舵机启动瞬间的压降会让主控复位。正确做法是主控和舵机分开供电,共地但不共电源。
驱动板选型上,常见方案是PCA9685这类16路PWM驱动板,通过I2C和主控通信。PCA9685的好处是自带时钟,不占用主控PWM资源,16路刚好够用。但要注意它的输出电流有限,每路只能提供约10mA的驱动能力,必须外接舵机电源,信号线接PCA9685,电源线直接接电池。
下面是一个典型的供电接线检查脚本,用Python读取主控电压和舵机电源电压,确认没有共电源:
import time import board import busio import adafruit_ina219 # 初始化I2C和电压电流传感器 i2c = busio.I2C(board.SCL, board.SDA) sensor_main = adafruit_ina219.INA219(i2c, addr=0x40) # 主控电源 sensor_servo = adafruit_ina219.INA219(i2c, addr=0x41) # 舵机电源 def check_power(): """检查主控和舵机电源电压,确认没有共电源""" v_main = sensor_main.bus_voltage v_servo = sensor_servo.bus_voltage print(f"主控电压: {v_main:.2f}V, 舵机电压: {v_servo:.2f}V") # 如果两个电压完全一致且波动同步,说明可能共电源 if abs(v_main - v_servo) < 0.05: print("警告:两路电压过于接近,检查是否共电源") return v_main, v_servo while True: check_power() time.sleep(1)这段代码用两个INA219传感器分别监测主控和舵机电源。逻辑很简单:如果两路电压几乎一样,说明可能接在同一路电源上。参数上,INA219的I2C地址通过跳线设置,0x40和0x41是常见配置。实际使用时,舵机动作瞬间舵机电压会明显下降,主控电压应该保持稳定,如果主控电压也跟着掉,就要检查接线。
3. 机械装配与逆运动学:让脚掌按预期落点
3.1 腿部连杆参数测量与坐标系建立
装配完成后第一件事不是写代码,而是测量连杆长度。每条腿的连杆包括大腿长度、小腿长度、脚掌偏移量。这些参数直接决定逆运动学的精度。
我一般用卡尺测量舵机输出轴到下一个关节中心的距离,精确到毫米。测量时要把舵机转到中位,消除间隙影响。测完后建立坐标系:以髋部中心为原点,X轴向前,Y轴向左,Z轴向上。每条腿的末端位置用(x, y, z)表示。
下面是一个腿部参数配置表,实际使用时替换成你的测量值:
| 参数名 | 含义 | 示例值 |
|---|---|---|
| L1 | 大腿长度 | 80mm |
| L2 | 小腿长度 | 90mm |
| L3 | 脚掌到踝关节偏移 | 20mm |
| hip_offset_y | 髋部左右偏移 | 40mm |
| hip_offset_z | 髋部上下偏移 | 0mm |
这些参数写进配置文件,逆运动学求解时直接读取。如果参数不准,脚掌落点会偏差几毫米,走起来就会歪。
3.2 逆运动学求解:从脚掌位置反推关节角
逆运动学的目标是给定脚掌目标位置,算出髋部俯仰、膝部、踝部三个关节的角度。对于每条腿的矢状面(前后方向),可以用简单的几何法求解。
假设脚掌目标位置为(x, z),大腿长L1,小腿长L2,那么:
import math def inverse_kinematics_2d(x, z, L1, L2): """ 二维逆运动学求解,返回髋部俯仰角和膝部角度 x: 脚掌前后位置(向前为正) z: 脚掌上下位置(向下为负) L1: 大腿长度 L2: 小腿长度 返回: (hip_pitch, knee) 单位弧度 """ # 计算髋部到脚掌的距离 dist = math.sqrt(x**2 + z**2) # 检查是否超出工作空间 if dist > L1 + L2: raise ValueError("目标位置超出腿长范围") if dist < abs(L1 - L2): raise ValueError("目标位置过近,无法到达") # 用余弦定理求膝部角度 cos_knee = (L1**2 + L2**2 - dist**2) / (2 * L1 * L2) knee = math.acos(max(-1, min(1, cos_knee))) # 限制在[-1,1]避免数值误差 knee = math.pi - knee # 膝部实际弯曲角度 # 求髋部俯仰角 alpha = math.atan2(x, -z) # 目标方向角 beta = math.acos(max(-1, min(1, (L1**2 + dist**2 - L2**2) / (2 * L1 * dist)))) hip_pitch = alpha + beta return hip_pitch, knee # 示例:脚掌在正下方偏前20mm,向下150mm hip, knee = inverse_kinematics_2d(20, -150, 80, 90) print(f"髋部俯仰: {math.degrees(hip):.1f}°, 膝部: {math.degrees(knee):.1f}°")这段代码用余弦定理求解。逻辑上,先算髋部到脚掌的直线距离,再用三角形关系求膝部弯曲角度和髋部俯仰角。参数说明:x和z是脚掌相对髋部的位置,L1和L2是实测连杆长度。注意acos的输入要限制在[-1,1],否则浮点误差会导致报错。实际使用时,这个函数每条腿调用一次,得到三个关节角后再叠加侧摆和偏航的补偿。
3.3 舵机角度映射与中位校准
逆运动学算出的是理论角度,实际舵机需要的是PWM脉宽。映射关系是线性的:角度0°对应脉宽500us,角度180°对应2500us,中间线性插值。但每个舵机的机械中位不同,必须逐个校准。
校准方法是:给舵机发送1500us脉宽,观察腿部是否垂直。如果不垂直,调整舵机臂的安装角度,或者记录偏移量在代码里补偿。我一般会写一个校准脚本,逐个舵机微调,把偏移量存进配置文件。
import json class ServoCalibration: def __init__(self, config_path="servo_calib.json"): self.config_path = config_path self.offsets = self.load() def load(self): """加载校准偏移量""" try: with open(self.config_path, "r") as f: return json.load(f) except FileNotFoundError: # 默认16个舵机偏移量为0 return {f"servo_{i}": 0 for i in range(16)} def save(self): """保存校准偏移量""" with open(self.config_path, "w") as f: json.dump(self.offsets, f, indent=2) def set_offset(self, servo_id, offset): """设置单个舵机偏移量,单位微秒""" self.offsets[f"servo_{servo_id}"] = offset self.save() def angle_to_pulse(self, servo_id, angle): """角度转脉宽,叠加校准偏移""" # 标准映射:0°->500us, 180°->2500us pulse = 500 + (angle / 180.0) * 2000 # 叠加偏移 pulse += self.offsets.get(f"servo_{servo_id}", 0) # 限制在安全范围 return max(500, min(2500, pulse)) # 使用示例 calib = ServoCalibration() calib.set_offset(0, 30) # 0号舵机偏移+30us pulse = calib.angle_to_pulse(0, 90) print(f"0号舵机90°对应脉宽: {pulse}us")这段代码管理16个舵机的校准偏移。逻辑是:标准角度到脉宽的映射是线性的,但每个舵机机械安装有偏差,用偏移量补偿。参数上,偏移量单位是微秒,一般不超过100us。实际校准时,先给所有舵机发1500us,看哪条腿不直,然后调整对应偏移量,直到腿部垂直。
4. 步态生成与实时控制:让16个舵机协调动作
4.1 三角步态与重心转移时序
双足步态的核心是重心转移。走路时,重心必须先移到支撑腿上,摆动腿才能抬起。16舵机方案因为多了侧摆自由度,重心转移可以更平滑。
三角步态是最基础的方案:两条腿交替支撑和摆动,每个周期分为四个阶段——左腿支撑右腿抬起、右腿前摆落地、右腿支撑左腿抬起、左腿前摆落地。每个阶段的重心位置不同,需要实时调整髋部侧摆角度。
下面是一个步态时序表,定义了一个完整周期的关节角度:
| 阶段 | 时长(ms) | 左髋俯仰 | 左膝 | 左踝 | 右髋俯仰 | 右膝 | 右踝 | 重心侧移 |
|---|---|---|---|---|---|---|---|---|
| 左支撑 | 250 | -10° | 20° | -10° | 15° | 30° | -20° | +5° |
| 右摆动 | 250 | -10° | 20° | -10° | -5° | 45° | -30° | +5° |
| 右支撑 | 250 | 15° | 30° | -20° | -10° | 20° | -10° | -5° |
| 左摆动 | 250 | -5° | 45° | -30° | -10° | 20° | -10° | -5° |
这个表是简化版,实际使用时每个阶段还要插值,让动作平滑。重心侧移通过髋部侧摆舵机实现,支撑腿侧移+5°,摆动腿侧移-5°,保证重心始终在支撑脚上方。
4.2 用插值生成平滑轨迹
直接给舵机发阶跃角度,机器人会抖得厉害。必须做插值,把每个阶段的起止角度平滑过渡。我一般用余弦插值,加速度连续,冲击小。
import math import time def cosine_interpolate(start, end, t): """ 余弦插值,t从0到1 返回start到end之间的平滑值 """ # 余弦插值公式:start + (end-start) * (1 - cos(pi*t)) / 2 return start + (end - start) * (1 - math.cos(math.pi * t)) / 2 def generate_trajectory(keyframes, duration_ms, steps=50): """ 根据关键帧生成平滑轨迹 keyframes: [(time_ratio, angle_dict), ...] 时间比例0-1 duration_ms: 总时长 steps: 插值步数 返回: [(time_ms, angle_dict), ...] """ trajectory = [] for i in range(steps + 1): t = i / steps # 找到当前时间所在的区间 for j in range(len(keyframes) - 1): t0, angles0 = keyframes[j] t1, angles1 = keyframes[j + 1] if t0 <= t <= t1: # 区间内归一化 local_t = (t - t0) / (t1 - t0) if t1 > t0 else 0 # 对每个关节插值 interp_angles = {} for key in angles0: interp_angles[key] = cosine_interpolate( angles0[key], angles1[key], local_t ) trajectory.append((t * duration_ms, interp_angles)) break return trajectory # 示例:定义两个关键帧 keyframes = [ (0.0, {"hip_pitch": -10, "knee": 20, "ankle": -10}), (1.0, {"hip_pitch": 15, "knee": 30, "ankle": -20}), ] traj = generate_trajectory(keyframes, 250, steps=25) for time_ms, angles in traj[:5]: print(f"t={time_ms:.0f}ms, 髋俯仰={angles['hip_pitch']:.1f}°")这段代码用余弦插值生成平滑轨迹。逻辑是:给定关键帧和时间比例,在每个区间内用余弦函数插值,保证起止速度为零。参数上,steps决定插值密度,50步对应250ms周期,每5ms一个点,足够平滑。实际使用时,把轨迹点按时间顺序发给舵机驱动板。
4.3 实时控制循环与舵机刷新率
舵机控制需要稳定的刷新率。PCA9685支持最高1kHz刷新,但16个舵机全刷新一遍需要时间。我一般用50Hz控制循环,每20ms更新一次所有舵机角度。
控制循环的结构是:读取传感器(IMU)→ 计算当前步态阶段 → 生成目标角度 → 逆运动学求解 → 角度转脉宽 → 发送给PCA9685。这个循环必须稳定,任何一步耗时过长都会导致步态抖动。
import time from adafruit_pca9685 import PCA9685 import board import busio class RobotController: def __init__(self): # 初始化I2C和PCA9685 i2c = busio.I2C(board.SCL, board.SDA) self.pca = PCA9685(i2c) self.pca.frequency = 50 # 50Hz刷新率 self.calib = ServoCalibration() self.control_period = 0.02 # 20ms控制周期 def set_servo_angle(self, servo_id, angle): """设置单个舵机角度""" pulse = self.calib.angle_to_pulse(servo_id, angle) # PCA9685的占空比计算:pulse_us / 20000us * 65535 duty = int(pulse / 20000.0 * 65535) self.pca.channels[servo_id].duty_cycle = duty def control_loop(self): """主控制循环""" start_time = time.time() while True: loop_start = time.time() # 1. 读取IMU(这里省略具体实现) # imu_data = read_imu() # 2. 计算步态阶段(简化:用时间模拟) elapsed = time.time() - start_time phase = (elapsed % 1.0) # 1秒一个周期 # 3. 生成目标角度(这里用示例值) target_angles = self.compute_gait(phase) # 4. 发送给舵机 for servo_id, angle in target_angles.items(): self.set_servo_angle(servo_id, angle) # 5. 等待到下一个周期 loop_time = time.time() - loop_start sleep_time = self.control_period - loop_time if sleep_time > 0: time.sleep(sleep_time) def compute_gait(self, phase): """根据相位计算16个舵机目标角度,这里返回示例""" # 实际实现需要根据步态表插值 return {i: 90 for i in range(16)} # 启动控制 controller = RobotController() # controller.control_loop() # 实际运行时取消注释这段代码是控制循环的骨架。逻辑是:每20ms执行一次,读取传感器、计算步态、发送角度。参数上,control_period=0.02对应50Hz,PCA9685的frequency也设为50Hz。注意duty_cycle的计算:脉宽500-2500us对应占空比2.5%-12.5%,乘以65535得到PCA9685的寄存器值。实际运行时,compute_gait需要根据步态表和逆运动学完整实现。
5. 调试与避坑:16舵机双足机器人的常见翻车点
5.1 上电就复位:供电不足的排查
现象:机器人一上电,主控反复重启,舵机抖动几下就停。
原因:舵机和主控共用电源,舵机启动瞬间电流拉低电压,主控欠压复位。
解决:主控和舵机分开供电,共地不共电源。舵机电源用大容量锂电池或开关电源,持续输出至少30A。主控用独立的5V稳压模块。如果必须共电源,在舵机电源端并联大电容(如1000uF以上)缓冲。
5.2 走路画圈:逆运动学参数不准
现象:机器人能走,但总是往一个方向偏,走几圈就转回去了。
原因:左右腿的连杆参数不一致,或者舵机中位校准有偏差,导致两条腿的实际步长不同。
解决:重新测量左右腿的连杆长度,分别配置。用校准脚本逐个校准舵机中位,确保1500us时腿部垂直。如果还偏,在步态生成时给左右腿加不同的补偿系数。
5.3 舵机发热严重:堵转或频繁启停
现象:走几分钟后舵机烫手,甚至冒烟。
原因:舵机长时间处于堵转状态,或者步态频率太高导致频繁启停,电流持续过大。
解决:检查步态角度是否超出舵机行程,避免机械限位堵转。降低步态频率,从1Hz降到0.5Hz。在舵机电源端加散热片或风扇。如果某个舵机特别烫,检查对应的关节是否阻力过大。
5.4 步态抖动:控制循环不稳定
现象:机器人走路时全身抖动,动作不连贯。
原因:控制循环耗时波动大,或者插值步数太少,角度变化不平滑。
解决:用固定周期控制循环,避免在循环内做耗时操作(如打印日志)。增加插值步数,从25步增加到50步。检查I2C通信速率,PCA9685的I2C速率如果太低,16个舵机刷新一遍可能超过20ms。
5.5 摔倒后舵机扫齿:机械限位缺失
现象:机器人摔倒后,某个舵机咔咔响,之后就不动了。
原因:摔倒时外力强行转动舵机,金属齿轮被扫平。没有机械限位保护。
解决:在关键关节加机械限位,限制舵机转动范围。选用金属齿舵机,塑料齿更容易扫。如果已经扫齿,拆开更换齿轮组。预防措施是在代码里限制角度范围,避免舵机走到极限位置。
6. 进阶技巧:用IMU反馈做动态平衡
6.1 IMU数据融合与姿态解算
静态步态跑通后,下一步是加IMU做动态平衡。IMU选六轴(加速度计+陀螺仪),用互补滤波或卡尔曼滤波融合数据,得到机器人的俯仰角和横滚角。
互补滤波的公式很简单:角度 = 0.98 × (角度 + 陀螺仪角速度 × dt) + 0.02 × 加速度计角度。这个系数0.98是经验值,实际调试时根据噪声调整。
class ComplementaryFilter: def __init__(self, alpha=0.98): self.alpha = alpha self.angle = 0.0 def update(self, accel_angle, gyro_rate, dt): """ 互补滤波更新 accel_angle: 加速度计计算的角度 gyro_rate: 陀螺仪角速度(度/秒) dt: 时间间隔(秒) """ # 陀螺仪积分 gyro_angle = self.angle + gyro_rate * dt # 互补滤波 self.angle = self.alpha * gyro_angle + (1 - self.alpha) * accel_angle return self.angle # 使用示例 filter_pitch = ComplementaryFilter(alpha=0.98) # 假设加速度计算出俯仰角5°,陀螺仪角速度10°/s,dt=0.02s angle = filter_pitch.update(5.0, 10.0, 0.02) print(f"融合后俯仰角: {angle:.2f}°")这段代码实现互补滤波。逻辑是:陀螺仪积分短期准但会漂移,加速度计长期准但噪声大,用系数加权融合。参数alpha=0.98表示信任陀螺仪98%,加速度计2%。实际调试时,如果角度漂移快,降低alpha;如果抖动大,提高alpha。
6.2 用姿态反馈修正步态
得到俯仰角和横滚角后,可以实时修正步态。如果机器人前倾,说明重心偏前,需要加大支撑腿的髋部俯仰角,把重心拉回来。如果侧倾,调整髋部侧摆角。
修正量用PD控制计算:修正角 = Kp × 角度误差 + Kd × 角速度。Kp和Kd需要调试,一般从Kp=0.5、Kd=0.1开始,逐步增大直到机器人能稳定。
下面是一个修正示例:
class BalanceController: def __init__(self, kp=0.5, kd=0.1): self.kp = kp self.kd = kd self.prev_error = 0.0 def compute_correction(self, target_angle, current_angle, dt): """ 计算平衡修正量 target_angle: 目标角度(通常为0) current_angle: 当前角度 dt: 时间间隔 """ error = target_angle - current_angle # 角速度用差分近似 error_rate = (error - self.prev_error) / dt if dt > 0 else 0 self.prev_error = error # PD控制 correction = self.kp * error + self.kd * error_rate return correction # 使用示例 balance = BalanceController(kp=0.5, kd=0.1) # 当前俯仰角3°,目标0° correction = balance.compute_correction(0, 3.0, 0.02) print(f"俯仰修正量: {correction:.2f}°")这段代码用PD控制计算修正量。逻辑是:误差乘以Kp得到比例项,误差变化率乘以Kd得到微分项,两者相加。参数上,Kp太大会震荡,Kd太大会对噪声敏感。实际调试时,先调Kp到机器人能回正但有点震荡,再加Kd抑制震荡。
6.3 从静态步态到动态行走的过渡
静态步态是重心始终在支撑脚范围内,动态行走是重心可以短暂超出支撑脚,靠惯性维持平衡。过渡时,先降低步态频率,让机器人有更多时间调整姿态。然后逐步减小支撑脚范围,让重心偏移更大。
我一般会记录每次调试的IMU数据和步态参数,对比找出最佳组合。这个过程中,机器人摔几次是正常的,关键是每次摔完要知道为什么摔,是修正量不够还是修正方向反了。
最后说一个血泪经验:16舵机双足机器人的调试周期比12舵机长得多,因为多出的自由度让系统更复杂。但一旦跑通,侧向稳定性和转向能力是12舵机比不了的。如果你正在做这个方向,建议先把供电和机械校准做扎实,再花时间调步态。希望帮到你。
本文还有配套的精品资源,点击获取