第3章 机械臂运动学建模:D-H参数法详解
各位同学,今天我们来聊聊机械臂运动学建模里最经典的方法——D-H参数法。说实话,我刚入行那会儿,看到一堆坐标系和矩阵就头大。后来做了几个项目才明白,这东西其实就是个套路,掌握了规律,6轴、7轴甚至协作臂都能一套方法搞定。
核心思想:D-H参数法的本质,是用4个参数来描述相邻两个连杆之间的相对位置和姿态。你想想看,一个机械臂再复杂,也不过是一串连杆串起来的,对吧?
3.1 为什么要用D-H参数法?
我遇到过不少刚入行的工程师,上来就问:为什么不用欧拉角或者旋转矩阵直接算?嗯,理论上当然可以。但实际项目中,你面对的是一个6轴机械臂,每个关节都在动,如果用传统方法,每动一个关节就得重新算一遍全局坐标,那代码写出来你自己都不想看。
D-H参数法的好处,说白了就是标准化。它把每个关节-连杆对抽象成4个参数,然后通过4步变换矩阵,把局部坐标一步步传到末端。这样你改一个关节角度,只需要更新对应的变换矩阵,其他部分完全不用动。
| 方法 | 优点 | 缺点 |
|---|---|---|
| D-H参数法 | 标准化、模块化、适合编程 | 坐标系建立有固定规则,需要适应 |
| 直接几何法 | 直观、适合简单结构 | 复杂臂型计算量爆炸 |
| 旋量法 | 数学优雅、无奇异点问题 | 入门门槛高,工程落地少 |
我的建议:初学者先死磕D-H法。等你用熟了,再去看旋量法你会发现,哦,原来D-H法就是旋量法的一个特例。但那是后话了。
3.2 建立连杆坐标系——这一步最容易翻车
我记得第一次给一个6轴工业臂建D-H坐标系,折腾了一下午,结果算出来的末端位置差了10厘米。后来发现是Z轴方向搞反了。所以这一节,我带你手把手过一遍坐标系建立的规则。
3.2.1 基本规则
每个连杆i,我们定义两个坐标系:
- 坐标系{i-1}:固定在连杆i-1上,Z轴沿关节i-1的轴线方向
- 坐标系{i}:固定在连杆i上,Z轴沿关节i的轴线方向
然后,我们通过4步从{i-1}变换到{i}:
- 绕Zi-1轴旋转θi(关节角)
- 沿Zi-1轴平移di(连杆偏距)
- 沿Xi轴平移ai(连杆长度)
- 绕Xi轴旋转αi(连杆扭角)
我曾经踩过的坑:坐标系{i}的X轴方向,必须是从Zi-1指向Zi的公垂线方向。如果两个Z轴平行,那X轴方向可以任意选,但一旦选了,后面所有关节的坐标系都得保持一致。我见过有人每个关节换一种选法,结果矩阵乘出来全是错的。
3.2.2 6轴机械臂的坐标系分配
我们以一个典型的6轴工业臂为例(比如常见的KUKA、ABB那种构型)。它的关节配置是:
- 关节1:腰部旋转(绕Z0)
- 关节2:肩部俯仰(绕Z1)
- 关节3:肘部俯仰(绕Z2)
- 关节4:前臂旋转(绕Z3)
- 关节5:腕部俯仰(绕Z4)
- 关节6:腕部旋转(绕Z5)
下面这张图,是我用SVG画的坐标系分配示意,你感受一下:
3.3 正向运动学推导
坐标系建好了,接下来就是算。正向运动学说白了就是:给你6个关节角度,算出末端在基坐标系下的位置和姿态。
3.3.1 单个连杆的变换矩阵
每个连杆的变换矩阵长这样:
T_i = Rot(z, θ_i) * Trans(z, d_i) * Trans(x, a_i) * Rot(x, α_i)
展开成4x4矩阵:
T_i = [
[cosθ_i, -sinθ_i*cosα_i, sinθ_i*sinα_i, a_i*cosθ_i],
[sinθ_i, cosθ_i*cosα_i, -cosθ_i*sinα_i, a_i*sinθ_i],
[0, sinα_i, cosα_i, d_i ],
[0, 0, 0, 1 ]
]
个人习惯:我写代码的时候,从来不会手算这个矩阵。我会写一个函数,输入θ、d、a、α四个参数,自动生成4x4矩阵。这样既不容易出错,也方便调试。后面我会给你看代码。
3.3.2 从基座到末端的链式乘法
对于6轴机械臂,末端相对于基座的变换矩阵就是:
T_0_6 = T_1 * T_2 * T_3 * T_4 * T_5 * T_6
注意顺序!是从基座开始,依次乘到末端。我见过有人写成T_6 * T_5 * ...,那结果肯定不对。矩阵乘法不满足交换律,这个坑我当年也踩过。
最终T_0_6矩阵的左上角3x3是末端姿态(旋转矩阵),右上角3x1是末端位置(x, y, z坐标)。
3.4 正向运动学代码实现
好了,理论讲完了,咱们上代码。我用Python写了一个完整的正向运动学实现,你直接拿去用就行。
import numpy as np
def dh_transform(theta, d, a, alpha):
"""
计算单个连杆的D-H变换矩阵
参数:
theta: 关节角 (弧度)
d: 连杆偏距
a: 连杆长度
alpha: 连杆扭角 (弧度)
返回:
4x4 齐次变换矩阵
"""
ct = np.cos(theta)
st = np.sin(theta)
ca = np.cos(alpha)
sa = np.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
def forward_kinematics(joint_angles, dh_params):
"""
6轴机械臂正向运动学
参数:
joint_angles: 6个关节角 (弧度)
dh_params: D-H参数表, 每行 [d, a, alpha]
返回:
末端位姿矩阵 T_0_6
"""
T = np.eye(4)
for i in range(6):
theta = joint_angles[i]
d = dh_params[i][0]
a = dh_params[i][1]
alpha = dh_params[i][2]
T_i = dh_transform(theta, d, a, alpha)
T = T @ T_i # 注意: 左乘
return T
# 示例: 一个典型6轴工业臂的D-H参数 (单位: mm, rad)
# 这里用的是标准KUKA KR6构型的参数
dh_params = [
[400, 150, -np.pi/2], # 关节1
[0, 600, 0 ], # 关节2
[0, 200, np.pi/2 ], # 关节3
[500, 0, -np.pi/2 ], # 关节4
[0, 0, np.pi/2 ], # 关节5
[100, 0, 0 ] # 关节6
]
# 假设所有关节角度为0 (机械臂直立状态)
angles = np.zeros(6)
T_end = forward_kinematics(angles, dh_params)
print("末端位置 (x, y, z):")
print(T_end[:3, 3])
print("\n末端姿态 (旋转矩阵):")
print(T_end[:3, :3])
我曾经踩过的坑:D-H参数表里的d和a,单位一定要统一。我见过有人d用毫米,a用米,结果算出来的位置差了1000倍。还有,α的正负号一定要跟坐标系建立时的约定一致,否则旋转方向会反。
3.5 验证你的正向运动学
代码写完了,怎么知道对不对?我的做法是:
- 零位测试:所有关节角为0时,末端应该在什么位置?对于大多数工业臂,此时机械臂应该是直立状态,末端在基座正上方某个高度。
- 单关节运动:只动关节1,末端应该画一个圆(绕Z0轴旋转)。只动关节2,末端应该在垂直平面内画弧。
- 与实物对照:如果有真机,把计算出的关节角发给机器人,看末端是不是真的到了那个位置。
总结一下:D-H参数法就是把复杂的机械臂运动学问题,拆解成4个参数的标准化处理。你只要把坐标系建对了,参数表填对了,剩下的就是矩阵乘法的事。代码写一次,后面所有6轴臂都能复用——改改参数表就行。