2. 双足机器人动力学建模
任务目标
为双足机器人建立可用于控制设计的动力学模型。本案例采用拉格朗日方程方法,以五连杆双足机器人(躯干+双腿,共5个刚体)为对象,推导其动力学方程,并给出可直接用于仿真的矩阵形式:
M(q)q̈ + C(q,q̇)q̇ + G(q) = τ
其中 q = [q1, q2, q3, q4, q5]ᵀ 为五个关节角,τ 为对应关节力矩。该模型是步态规划、平衡控制与仿真验证的基础。
模型定义与假设
- 五连杆结构:躯干1个 + 左右大腿2个 + 左右小腿2个,共5个刚体,5个旋转关节(髋×2、膝×2、躯干与髋连线虚拟关节×1)。
- 所有质量集中在各连杆质心,质心位于连杆几何中心。
- 运动限制在矢状面(二维平面)内,忽略脚掌与地面的滚动,支撑脚与地面为点接触且无滑动(单支撑相)。
- 各连杆参数:质量
m_i、长度l_i、质心到关节距离r_i、转动惯量I_i。
关键完整代码(Python + SymPy)
以下代码自动推导五连杆双足机器人的拉格朗日动力学方程,输出质量矩阵、科氏力/离心力项和重力项,并生成可被数值仿真直接调用的Python函数。
import sympy as sp
import numpy as np
from sympy.physics.mechanics import dynamicsymbols, ReferenceFrame, Point, Particle, Lagrangian, LagrangesMethod
# ========== 1. 定义符号参数 ==========
# 关节角(广义坐标)
q1, q2, q3, q4, q5 = dynamicsymbols('q1 q2 q3 q4 q5')
# 关节角速度
q1d, q2d, q3d, q4d, q5d = dynamicsymbols('q1 q2 q3 q4 q5', 1)
# 连杆参数(符号)
m1, m2, m3, m4, m5 = sp.symbols('m1 m2 m3 m4 m5') # 质量
l1, l2, l3, l4, l5 = sp.symbols('l1 l2 l3 l4 l5') # 杆长
r1, r2, r3, r4, r5 = sp.symbols('r1 r2 r3 r4 r5') # 质心到近端关节距离
I1, I2, I3, I4, I5 = sp.symbols('I1 I2 I3 I4 I5') # 绕质心转动惯量
g = sp.symbols('g') # 重力加速度
# ========== 2. 建立参考系与坐标 ==========
N = ReferenceFrame('N') # 惯性系(地面)
# 支撑脚踝位于原点,各连杆方向用旋转矩阵表示
# 定义各连杆坐标系(相对前一杆的转角)
theta1 = q1 # 支撑腿大腿相对竖直方向
theta2 = q1 + q2 # 支撑腿小腿相对竖直方向
theta3 = q1 + q2 + q3 # 躯干相对竖直方向
theta4 = q1 + q2 + q3 + q4 # 摆动腿大腿
theta5 = q1 + q2 + q3 + q4 + q5 # 摆动腿小腿
# 各连杆质心位置(从支撑脚踝出发,沿矢状面)
# 支撑腿大腿质心
p1 = r1 * sp.sin(theta1) * N.x - r1 * sp.cos(theta1) * N.y
# 支撑腿小腿质心(髋关节 + 小腿质心偏移)
hip = l1 * sp.sin(theta1) * N.x - l1 * sp.cos(theta1) * N.y
p2 = hip + r2 * sp.sin(theta2) * N.x - r2 * sp.cos(theta2) * N.y
# 躯干质心(髋关节 + 躯干质心偏移)
torso_base = hip
p3 = torso_base + r3 * sp.sin(theta3) * N.x - r3 * sp.cos(theta3) * N.y
# 摆动腿大腿质心(髋关节 + 大腿偏移)
p4 = torso_base + l3 * sp.sin(theta3) * N.x - l3 * sp.cos(theta3) * N.y + r4 * sp.sin(theta4) * N.x - r4 * sp.cos(theta4) * N.y
# 摆动腿小腿质心
knee_swing = torso_base + l3 * sp.sin(theta3) * N.x - l3 * sp.cos(theta3) * N.y + l4 * sp.sin(theta4) * N.x - l4 * sp.cos(theta4) * N.y
p5 = knee_swing + r5 * sp.sin(theta5) * N.x - r5 * sp.cos(theta5) * N.y
# ========== 3. 定义刚体(质点 + 转动惯量) ==========
# 各连杆角速度(二维平面内,绕Z轴)
omega1 = q1d * N.z
omega2 = (q1d + q2d) * N.z
omega3 = (q1d + q2d + q3d) * N.z
omega4 = (q1d + q2d + q3d + q4d) * N.z
omega5 = (q1d + q2d + q3d + q4d + q5d) * N.z
# 创建质点(平动动能)与刚体(转动动能)
def make_body(name, mass, pos, omega, inertia):
pt = Point(name + '_pt')
pt.set_pos(Point('origin'), pos)
pt.set_vel(N, pt.pos_from(Point('origin')).diff(N))
return Particle(name, pt, mass), inertia * omega.dot(omega) / 2
# 简化:用Particle表示平动,转动动能单独加入拉格朗日量
# 各连杆平动动能
KE_trans = 0
for m_i, p_i in [(m1, p1), (m2, p2), (m3, p3), (m4, p4), (m5, p5)]:
v_i = sp.diff(p_i, dynamicsymbols._t)
KE_trans += sp.Rational(1, 2) * m_i * v_i.dot(v_i)
# 转动动能
KE_rot = sp.Rational(1, 2) * (I1 * omega1.dot(omega1) + I2 * omega2.dot(omega2) +
I3 * omega3.dot(omega3) + I4 * omega4.dot(omega4) +
I5 * omega5.dot(omega5))
KE = KE_trans + KE_rot
# 势能(重力,y轴向上为负)
PE = m1 * g * p1.dot(N.y) + m2 * g * p2.dot(N.y) + m3 * g * p3.dot(N.y) + \
m4 * g * p4.dot(N.y) + m5 * g * p5.dot(N.y)
# ========== 4. 拉格朗日方程 ==========
L = KE - PE
q = [q1, q2, q3, q4, q5]
qd = [q1d, q2d, q3d, q4d, q5d]
# 计算动力学方程:d/dt(dL/dq̇) - dL/dq = τ
lagrange_eqs = []
for qi, qdi in zip(q, qd):
dL_dqdi = sp.diff(L, qdi)
ddt_dL_dqdi = sp.diff(dL_dqdi, dynamicsymbols._t)
dL_dqi = sp.diff(L, qi)
lagrange_eqs.append(sp.simplify(ddt_dL_dqdi - dL_dqi))
# ========== 5. 提取 M, C, G 矩阵 ==========
# 将方程线性化为 M(q)q̈ + C(q,q̇)q̇ + G(q) = τ
qdd = [dynamicsymbols(f'q{i}_dd') for i in range(1, 6)]
# 用符号替换二阶导数
subs_dict = {sp.diff(qdi, dynamicsymbols._t): qdd[i] for i, qdi in enumerate(qd)}
eqs_linear = [sp.expand(eq.subs(subs_dict)) for eq in lagrange_eqs]
# 提取质量矩阵 M(q̈ 的系数)
M = sp.zeros(5, 5)
for i in range(5):
for j in range(5):
M[i, j] = sp.simplify(sp.diff(eqs_linear[i], qdd[j]))
# 计算 C(q,q̇)q̇ + G(q) = τ - M q̈
# 将非M项分离
Cq_G = []
for i in range(5):
expr = sp.expand(eqs_linear[i] - sum(M[i, j] * qdd[j] for j in range(5)))
Cq_G.append(sp.simplify(expr))
# 提取重力项 G(q)(不含速度的项)
G_vec = sp.zeros(5, 1)
for i in range(5):
G_vec[i] = sp.simplify(Cq_G[i].subs({q1d: 0, q2d: 0, q3d: 0, q4d: 0, q5d: 0}))
# 科氏力/离心力项 C(q,q̇)q̇ = Cq_G - G
Cq_vec = sp.zeros(5, 1)
for i in range(5):
Cq_vec[i] = sp.simplify(Cq_G[i] - G_vec[i])
# ========== 6. 输出结果 ==========
print("=== 质量矩阵 M(q) ===")
print(M)
print("\n=== 重力项 G(q) ===")
print(G_vec)
print("\n=== 科氏力/离心力项 C(q,q̇)q̇ ===")
print(Cq_vec)
# ========== 7. 生成数值仿真函数 ==========
# 将符号表达式转换为数值函数(需指定具体参数)
# 示例参数(单位:kg, m, kg·m²)
params = {m1: 3.0, m2: 1.5, m3: 20.0, m4: 3.0, m5: 1.5,
l1: 0.4, l2: 0.4, l3: 0.5, l4: 0.4, l5: 0.4,
r1: 0.2, r2: 0.2, r3: 0.25, r4: 0.2, r5: 0.2,
I1: 0.05, I2: 0.02, I3: 0.5, I4: 0.05, I5: 0.02,
g: 9.81}
# 生成可调用的数值函数(示例:计算给定状态下的 M, C, G)
def compute_dynamics(q_vals, qd_vals):
"""输入:q_vals, qd_vals 为5维数组,输出:M, Cq, G"""
subs = {q[i]: q_vals[i] for i in range(5)}
subs.update({qd[i]: qd_vals[i] for i in range(5)})
subs.update(params)
M_num = np.array([[float(M[i, j].subs(subs)) for j in range(5)] for i in range(5)])
Cq_num = np.array([float(Cq_vec[i].subs(subs)) for i in range(5)])
G_num = np.array([float(G_vec[i].subs(subs)) for i in range(5)])
return M_num, Cq_num, G_num
# 测试调用
q_test = np.array([0.1, -0.2, 0.05, 0.3, -0.4]) # 关节角(rad)
qd_test = np.array([0.0, 0.0, 0.0, 0.0, 0.0]) # 静止状态
M_num, Cq_num, G_num = compute_dynamics(q_test, qd_test)
print("\n=== 数值测试(静止状态) ===")
print("M 矩阵维度:", M_num.shape)
print("G 向量:", G_num)
print("Cq 向量(应为0):", Cq_num)
运行与验证
- 依赖安装:
pip install sympy numpy - 运行方式:将上述代码保存为
biped_dynamics.py,执行python biped_dynamics.py - 验证方法:
- 程序输出5×5质量矩阵
M,应是对称正定矩阵(对角元素为正)。 - 静止状态(
qd=0)下,科氏力项Cq应为零向量。 - 重力项
G在q=0(直立姿态)时,各关节重力矩应满足静力平衡关系(支撑腿关节力矩不为零,摆动腿关节力矩为零)。 - 可通过
compute_dynamics函数在任意状态调用,检查输出维度与数值连续性。
- 程序输出5×5质量矩阵
模型使用说明
得到的 M(q), C(q,q̇), G(q) 可直接用于:
- 前馈控制:计算期望力矩
τ = M(q)q̈_des + C(q,q̇)q̇ + G(q) - 仿真:正向动力学
q̈ = M⁻¹(τ - Cq̇ - G),使用数值积分器(如RK4)递推状态。 - 控制设计:计算动力学参数用于LQR、计算力矩控制或步态优化。
本模型忽略脚掌与地面弹性、关节摩擦和电机动力学,适用于单支撑相步态规划与控制验证。如需双支撑相,需额外引入接触约束。