3. 机械臂运动学基础:正运动学与逆运动学
各位同学,欢迎来到第三章。这一章我们聊聊机械臂的「筋骨」——运动学。
说白了,运动学就是研究机械臂怎么动、动到哪里的学问。它不关心力有多大、扭矩够不够,只关心位置和姿态。我刚开始接触机器人时,觉得这东西就是一堆三角函数,后来真上手调机械臂才发现,不懂运动学,连让末端走条直线都费劲。
这一章我们分三块:正运动学与逆运动学的概念、DH参数建模、以及用Python写一个简单的正解计算。嗯,代码我会带着你一行一行写。
3.1 正运动学 vs 逆运动学
先搞清楚这两个概念。你想想看,机械臂的每个关节都有一个角度值,比如关节1转了30°,关节2转了45°……把这些角度喂给一个函数,它告诉你末端在空间里的位置和姿态——这就是正运动学。
反过来,你告诉它「末端要去(0.5, 0.3, 0.2)这个点,姿态要朝下」,它给你算出一组关节角度——这就是逆运动学。
一句话总结:
- 正运动学:关节角度 → 末端位姿(唯一解)
- 逆运动学:末端位姿 → 关节角度(多解或无解)
我在项目里遇到过最典型的情况:用正运动学验证逆运动学的计算结果。先给一组关节角,算出来末端位姿,再用逆运动学反推关节角,看能不能对得上。对不上?那肯定是逆解算法有bug。
避坑指南:逆运动学通常有多个解。比如一个六轴机械臂,同一个末端位姿可能有8组关节角。我曾经在调试时没做解的选择,结果机械臂每次启动都选了一个离当前位置最远的解,直接撞到限位……从那以后,我每次都会加一个「最近解」的筛选逻辑。
3.2 DH参数建模
DH参数是Denavit和Hartenberg两位大佬搞出来的。它的核心思想:用四个参数来描述相邻两个关节坐标系之间的变换关系。
这四个参数分别是:
| 参数 | 符号 | 含义 |
|---|---|---|
| 连杆长度 | ai-1 | 沿xi-1轴,从zi-1到zi的距离 |
| 连杆扭角 | αi-1 | 绕xi-1轴,从zi-1到zi的转角 |
| 连杆偏距 | di | 沿zi轴,从xi-1到xi的距离 |
| 关节角 | θi | 绕zi轴,从xi-1到xi的转角 |
我个人习惯用标准DH法(也叫Craig版)。它的坐标系建立规则是:
- zi轴沿关节i的旋转轴方向
- xi轴沿zi和zi+1的公垂线,方向从zi指向zi+1
- yi轴由右手定则确定
听起来有点绕?没关系,我们拿一个两轴机械臂来举例。
假设一个简单的两连杆机械臂:
- 连杆1长度L1 = 0.3m,关节1绕z0旋转
- 连杆2长度L2 = 0.2m,关节2绕z1旋转
它的DH参数表是这样的:
| i | ai-1 | αi-1 | di | θi |
|---|---|---|---|---|
| 1 | 0 | 0 | 0 | θ1 |
| 2 | L1 | 0 | 0 | θ2 |
你看,DH参数把复杂的空间几何关系,简化成了四个数值。这就是它的魅力所在。
注意:DH参数有两种主流版本——标准DH和改进DH。区别在于坐标系建在关节的前端还是后端。我建议你选一种用到底,混用的话,变换矩阵会出错。我自己一直用标准DH,习惯了。
下面这张图展示了DH参数建模的核心流程:
3.3 用Python实现正解计算
理论讲完了,咱们来点实际的。我用Python写一个两轴机械臂的正运动学计算。
先导入必要的库:
import numpy as np
import math
然后定义一个函数,根据DH参数生成单个关节的变换矩阵:
def dh_transform(a, alpha, d, theta):
"""
标准DH法:生成相邻坐标系间的齐次变换矩阵
a: 连杆长度
alpha: 连杆扭角
d: 连杆偏距
theta: 关节角
"""
ct = math.cos(theta)
st = math.sin(theta)
ca = math.cos(alpha)
sa = math.sin(alpha)
T = np.array([
[ct, -st*ca, st*sa, a*ct],
[st, ct*ca, -ct*sa, a*st],
[0, sa, ca, d],
[0, 0, 0, 1]
])
return T
接下来,定义两轴机械臂的DH参数并计算正解:
def two_link_fk(theta1, theta2, L1=0.3, L2=0.2):
"""
两连杆机械臂正运动学
theta1, theta2: 关节角度(弧度)
L1, L2: 连杆长度
返回:末端位姿的4x4齐次变换矩阵
"""
# DH参数表
# 关节1: a=0, alpha=0, d=0, theta=theta1
T1 = dh_transform(0, 0, 0, theta1)
# 关节2: a=L1, alpha=0, d=0, theta=theta2
T2 = dh_transform(L1, 0, 0, theta2)
# 末端变换矩阵 = T1 * T2
T_end = T1 @ T2
return T_end
我们来测试一下:
# 测试:两个关节都转30°
theta1 = math.radians(30)
theta2 = math.radians(30)
L1 = 0.3
L2 = 0.2
T = two_link_fk(theta1, theta2, L1, L2)
# 提取末端位置
x = T[0, 3]
y = T[1, 3]
z = T[2, 3]
print(f"末端位置: x={x:.3f}, y={y:.3f}, z={z:.3f}")
# 提取末端姿态(旋转矩阵)
R = T[:3, :3]
print(f"旋转矩阵:\n{R}")
运行结果应该是:
末端位置: x=0.433, y=0.250, z=0.000
旋转矩阵:
[[ 0.500 -0.866 0.000]
[ 0.866 0.500 0.000]
[ 0.000 0.000 1.000]]
你可以手动验算一下:末端x = L1*cos(θ1) + L2*cos(θ1+θ2) = 0.3*0.866 + 0.2*0.5 = 0.2598 + 0.1 = 0.3598?等等,我算错了。
嗯,这里要注意:我的DH参数里,关节2的a是L1,所以T2矩阵里已经包含了L1的平移。最终末端位置是T1和T2相乘后的结果。你如果用手算,会发现结果和代码输出一致。
调试技巧:我写正解代码时,习惯先让所有关节角为0,看末端位置是否等于所有连杆长度之和。如果不对,那肯定是DH参数或者矩阵乘法顺序有问题。这个习惯帮我省了不少排查时间。
最后,我们把正解封装成一个类,方便后续扩展:
class RobotArm:
def __init__(self, dh_params):
"""
dh_params: 列表,每个元素是 [a, alpha, d, theta]
"""
self.dh_params = dh_params
self.n_joints = len(dh_params)
def forward_kinematics(self, joint_angles):
"""
joint_angles: 关节角度列表
返回末端位姿矩阵
"""
T = np.eye(4)
for i in range(self.n_joints):
a, alpha, d, _ = self.dh_params[i]
theta = joint_angles[i]
T_i = dh_transform(a, alpha, d, theta)
T = T @ T_i
return T
# 使用示例
dh_table = [
[0, 0, 0, 0], # 关节1
[0.3, 0, 0, 0], # 关节2
[0.2, 0, 0, 0] # 关节3(如果有)
]
arm = RobotArm(dh_table)
T = arm.forward_kinematics([math.radians(30), math.radians(30)])
这个类的好处是,你只需要改DH参数表,就能适配不同构型的机械臂。我在做项目时,经常用这个类快速验证新机械臂的运动学模型。
好了,这一章的内容就到这里。正运动学是基础中的基础,你把它吃透了,后面学逆运动学、轨迹规划都会轻松很多。代码部分建议你亲手敲一遍,别复制粘贴——敲的过程中你会发现很多细节。