腿部与步态 第5课/共30课

🤖 静态稳定性

支撑多边形与稳定裕度:让机器人站得稳

⚖️ 静态稳定性的本质

静态稳定性是最基本的安全要求:即使机器人完全停止,也不会翻倒

核心原则:质心投影必须始终在支撑多边形内。

CoMproj ∈ Conv(Support_Polygon)
Smargin = min d(CoMproj, edgei) > 0

📐 支撑多边形

支撑多边形由所有着地脚的位置确定:

凸包计算保证支撑多边形是凸的:

Psupport = Conv(pi | legi ∈ Stance)

📏 稳定裕度计算

稳定裕度有三种度量方式:

1. 几何稳定裕度

Sgeo = min d(CoM, edgei)

2. 能量稳定裕度

Senergy = m·g·Δhmin

其中 Δhmin 是翻越最近边所需的最小质心升高量。

3. 归一化稳定裕度

Snorm = Sgeo / Lbody

🧮 仿真:稳定性分析

import math class StaticStability: def __init__(self, body_L=0.4, body_W=0.2, mass=6.2): self.mass = mass self.g = 9.81 self.legs = { 'LF': ( body_L/2, body_W/2), 'RF': ( body_L/2, -body_W/2), 'LB': (-body_L/2, body_W/2), 'RB': (-body_L/2, -body_W/2), } def convex_hull(self, points): pts = sorted(set(points)) if len(pts) <= 1: return pts lower = [] for p in pts: while len(lower) >= 2 and self.cross(lower[-2], lower[-1], p) <= 0: lower.pop() lower.append(p) upper = [] for p in reversed(pts): while len(upper) >= 2 and self.cross(upper[-2], upper[-1], p) <= 0: upper.pop() upper.append(p) return lower[:-1] + upper[:-1] @staticmethod def cross(O, A, B): return (A[0]-O[0])*(B[1]-O[1]) - (A[1]-O[1])*(B[0]-O[0]) def stability_margin(self, com, support_legs): pts = [self.legs[l] for l in support_legs] hull = self.convex_hull(pts) if len(hull) < 3: if len(hull) == 2: p1, p2 = hull dx, dy = p2[0]-p1[0], p2[1]-p1[1] seg_sq = dx*dx + dy*dy if seg_sq < 1e-10: return math.hypot(com[0]-p1[0], com[1]-p1[1]) t = max(0, min(1, ((com[0]-p1[0])*dx + (com[1]-p1[1])*dy)/seg_sq)) px, py = p1[0]+t*dx, p1[1]+t*dy return math.hypot(com[0]-px, com[1]-py) return 0 min_dist = float('inf') n = len(hull) for i in range(n): p1 = hull[i] p2 = hull[(i+1) % n] dx, dy = p2[0]-p1[0], p2[1]-p1[1] seg_sq = dx*dx + dy*dy t = max(0, min(1, ((com[0]-p1[0])*dx + (com[1]-p1[1])*dy)/seg_sq)) px, py = p1[0]+t*dx, p1[1]+t*dy d = math.hypot(com[0]-px, com[1]-py) min_dist = min(min_dist, d) return min_dist def is_inside_support(self, com, support_legs): return self.stability_margin(com, support_legs) > 0 def support_polygon_area(self, support_legs): pts = [self.legs[l] for l in support_legs] hull = self.convex_hull(pts) n = len(hull) if n < 3: return 0 area = 0 for i in range(n): j = (i+1) % n area += hull[i][0]*hull[j][1] - hull[j][0]*hull[i][1] return abs(area)/2 ss = StaticStability() print("=" * 60) print(" Static Stability Simulation") print("=" * 60) com = (0, 0) all_legs = ['LF', 'RF', 'LB', 'RB'] margin = ss.stability_margin(com, all_legs) area = ss.support_polygon_area(all_legs) print(f"\n [4-leg stance] CoM=({com[0]},{com[1]})") print(f" Stability margin: {margin*1000:.1f} mm") print(f" Support area: {area*1e4:.1f} cm2") print(f" Stable: {'YES' if ss.is_inside_support(com, all_legs) else 'NO'}") support_3 = ['RF', 'LB', 'RB'] for com_x in [0, 0.05, 0.1, 0.15, 0.2]: com = (com_x, 0) margin = ss.stability_margin(com, support_3) stable = ss.is_inside_support(com, support_3) s = "STABLE" if stable else "UNSTABLE" print(f" Lift LF, CoM=({com_x:.2f},0): margin={margin*1000:.1f}mm [{s}]") print(f"\n [Max Safe Offset Search]") for swing_leg in ['LF', 'RF', 'LB', 'RB']: support = [l for l in all_legs if l != swing_leg] max_fwd = 0 max_side = 0 for i in range(100): x = i * 0.005 if ss.is_inside_support((x, 0), support): max_fwd = x for i in range(100): y = i * 0.005 if ss.is_inside_support((0, y), support): max_side = y print(f" Lift {swing_leg}: fwd_max={max_fwd*1000:.0f}mm, side_max={max_side*1000:.0f}mm") print(f"\n [Walk Gait Stability]") walk_phases = [ (0.00, ['RF', 'LB', 'RB']), (0.25, ['LF', 'LB', 'RB']), (0.50, ['LF', 'RF', 'RB']), (0.75, ['LF', 'RF', 'LB']), ] for phase, support in walk_phases: swing = [l for l in all_legs if l not in support][0] margin = ss.stability_margin((0, 0), support) area = ss.support_polygon_area(support) print(f" Phase {phase:.2f} (lift {swing}): margin={margin*1000:.1f}mm, area={area*1e4:.1f}cm2") print(f"\n [CoM Offset Sensitivity]") com_offsets = [(0.05, 0), (0.10, 0), (0, 0.05), (0, 0.10)] for offset in com_offsets: margins = [] for phase, support in walk_phases: m = ss.stability_margin(offset, support) margins.append(m) min_m = min(margins) s = "STABLE" if min_m > 0 else "UNSTABLE" print(f" CoM offset=({offset[0]*1000:.0f},{offset[1]*1000:.0f})mm: min_margin={min_m*1000:.1f}mm [{s}]") print() print(" OK - Static stability simulation complete")

仿真结果:

============================================================ Static Stability Simulation ============================================================ [4-leg stance] CoM=(0,0) Stability margin: 100.0 mm Support area: 800.0 cm2 Stable: YES Lift LF, CoM=(0.00,0): margin=0.0mm [UNSTABLE] Lift LF, CoM=(0.05,0): margin=22.4mm [STABLE] Lift LF, CoM=(0.10,0): margin=44.7mm [STABLE] Lift LF, CoM=(0.15,0): margin=67.1mm [STABLE] Lift LF, CoM=(0.20,0): margin=89.4mm [STABLE] [Max Safe Offset Search] Lift LF: fwd_max=495mm, side_max=495mm Lift RF: fwd_max=495mm, side_max=495mm Lift LB: fwd_max=495mm, side_max=495mm Lift RB: fwd_max=495mm, side_max=495mm [Walk Gait Stability] Phase 0.00 (lift LF): margin=0.0mm, area=400.0cm2 Phase 0.25 (lift RF): margin=0.0mm, area=400.0cm2 Phase 0.50 (lift LB): margin=0.0mm, area=400.0cm2 Phase 0.75 (lift RB): margin=0.0mm, area=400.0cm2 [CoM Offset Sensitivity] CoM offset=(50,0)mm: min_margin=22.4mm [STABLE] CoM offset=(100,0)mm: min_margin=44.7mm [STABLE] CoM offset=(0,50)mm: min_margin=44.7mm [STABLE] CoM offset=(0,100)mm: min_margin=0.0mm [UNSTABLE] OK - Static stability simulation complete

🛡️ 提高稳定性的策略

  1. 加宽步距:增大支撑多边形面积
  2. 降低质心:降低重心高度
  3. 优化步态:选择3腿支撑的Walk步态
  4. 摆动补偿:摆动腿时主动偏移质心
  5. 负载分配:重物靠近质心

📉 稳定性图

稳定性图展示了在不同抬腿组合下,质心可以安全偏移的范围:

Walk步态的关键:确保在每个3腿支撑相,质心都在对应的三角形内。

📝 练习

  1. 计算4腿站立时,质心在x方向最大能偏移多少仍保持稳定?
  2. 如果抬LF腿,质心y方向的安全范围是多少?与抬RF比较。
  3. 设计一个步宽优化:在保持稳定裕度≥20mm的条件下,最小步宽是多少?
  4. 计算负载5kg放在躯干前方0.15m处时的等效质心偏移和新稳定裕度。
  5. 在10°坡上,Walk步态的稳定裕度会如何变化?推导公式。

📐 凸包算法详解

稳定裕度计算的核心是凸包(Convex Hull)。我们使用Andrew's Monotone Chain算法:

  1. 将支撑点按x坐标排序
  2. 构建下凸包:从左到右,每次检查新点是否使凸包"左转"
  3. 构建上凸包:从右到左,同样检查
  4. 合并上下凸包

时间复杂度O(n log n),对于4个点几乎瞬时完成。

🔥 能量稳定裕度

几何稳定裕度在斜面上不够准确,因为它不考虑重力方向。能量稳定裕度更通用:

SE = m·g·min(Δhi)

其中 Δhi 是质心翻越第i条边需要升高的最小距离:

Δhi = di · tan(α) + hedge_i - hCoM

其中 α 是坡度角。在平面上 SE 退化为几何裕度。

🌊 动态稳定性预览

当静态稳定性不满足时(如Trot步态),需要考虑动态效应:

零矩点(ZMP)

ZMP = CoM - (z&hat; × Ḣ) / (m·g)
H = I·ω + m·r × v(角动量)

ZMP在支撑多边形内时,机器人动态稳定。Trot步态正是利用惯性角动量维持平衡。

角动量调节

通过控制摆动腿的速度和轨迹,可以调节角动量,帮助维持动态稳定。这是Trot步态控制的核心技巧。

📊 稳定性度量对比

度量方法适用范围优点缺点
几何裕度静态/慢速计算简单忽略惯性和坡度
能量裕度静态/斜面考虑坡度仍忽略惯性
ZMP动态考虑惯性需要精确模型
CAP动态/跌倒预测预测跌倒计算复杂

💡 实际设计建议

  1. 静态场景(巡检、站立):保持Smargin ≥ 30mm
  2. Walk步态:每个3腿相的裕度 ≥ 10mm
  3. Trot步态:使用ZMP代替几何裕度
  4. 紧急停止:必须有4腿着陆的恢复策略
  5. 负载设计:质心偏移不应使裕度降到0以下
🏆
稳定守护者

掌握静态稳定性分析和支撑多边形理论

四足机器人课程 · 第5课/30 · 返回目录