做机械臂控制的人十有八九都会在DH建模这一关卡一下。我最早接触这东西是在做某台六轴机械臂的运动控制时当时想着无非就是算个正运动学结果坐标系怎么摆、参数怎么填、矩阵怎么写一串问题直接把我按在地上摩擦。后来花了一个多星期把DH建模捋顺之后回头看发现这东西一旦理解透了后面所有逆运动学、雅可比、轨迹规划都顺了。这篇就基于我的实际经验把DH建模从头到尾拆开讲透适合刚入门机器人学、准备写机械臂正运动学代码、或者做项目需要对接运动学模型的同学。1. DH建模到底在干什么它是给机械臂搭坐标系骨架1.1 你不可能靠一堆角度和距离硬算末端位置先想一个问题一台六轴机械臂给你六个关节角让你算出手腕末端在空间里的坐标和姿态你会怎么做没有运动学模型的话你得拿着CAD图量出每一段连杆的长度、每一个关节轴的方向然后自己手推一套三维几何公式。臂短还行六轴、八轴装在一起角度一变末端位置涉及几十上百个中间变量式子多到根本没法维护。DH建模解决的就是这件事。它用一套统一的规则在每个关节处建立坐标系然后只靠四个参数——a、alpha、d、theta——就能把相邻两个坐标系的变换关系完全确定下来。一串矩阵乘过去末端位姿自然就出来了。这套规则不挑机械臂类型串联机械臂、协作臂、直角坐标机器人、移动机器人关节臂都能用。说白了DH建模就是给机械臂的每段骨头都贴上一张标准化的身份证后续不管做正运动学还是逆运动学都拿这张身份证说话。1.2 四个参数的人话解释骨头长度、拧角、错位量和关节转了多少刚开始看DH参数表的人最容易背了一堆定义还是不知道自己在填什么。我换个说法a连杆长度相邻两个关节轴线之间的公垂线距离沿着X轴方向量。可以粗暴理解为这段骨头的长度单位用米或毫米。alpha连杆扭角绕X轴从一条关节轴线扭到另一条关节轴线的角度。它决定了相邻两个关节轴是平行、垂直还是斜交。生活里可以类比成骨头之间拧了多少度。d关节偏置沿当前Z轴方向两条公垂线之间的距离。更直白一点就是两个关节在轴线方向上错开了多少像楼层之间的层高。theta关节角绕当前Z轴从上一条X轴转到当前X轴的角度。对旋转关节来说它才是真正会变的量。这里必须提醒一句a和alpha这两个参数在标准DHSDH里带的下标是i-1d和theta的下标是i。什么意思就是a和alpha描述的是上一段连杆的几何特性而d和theta描述的是当前关节的特性。这个下标错位问题是我见过最多人栽跟头的地方。参数表第一行往往写着a0、alpha0很多人以为是个巧合其实是因为第一行确实没有上一段连杆可以描述。1.3 用一个二连杆平面臂验证直觉理论讲半天不如动手算。假设有一个两连杆平面机械臂第一段长L1第二段长L2两个关节都在纸面内旋转。这个例子所有几何都摆在你眼前最适合验证DH建模是不是算对了。按SDH定义Z0沿关节1轴线垂直纸面Z1沿关节2轴线同样垂直纸面与Z0平行Z2沿末端工具方向。因为Z0和Z1平行alpha00Z0到Z1的公垂线距离就是L1所以a0L1同理a1L2。参数表就是ia(i-1)alpha(i-1)d(i)theta(i)1000θ12L100θ23L2000然后把变换矩阵乘起来末端位置是X L1·cosθ1 L2·cos(θ1θ2) Y L1·sinθ1 L2·sin(θ1θ2)这正是几何上直接算出来的结果第一段端点绕基座转了θ1第二段端点相对于第一段又转了θ2。我第一次算出这个结果的时候心里那块石头才落了地——原来DH不是什么玄学它就是一套严谨的几何规则。记住这种感觉后面遇到再复杂的机械臂你就是在重复这件事建立坐标系、填参数、乘矩阵、验证结果。2. SDH和MDH千万别混同一台臂两套参数能算出不同结果2.1 两种模型的核心差别在坐标系原点放哪DH建模有两大流派标准DHSDH也叫经典DH和修正DHMDH。这两套东西都能正确描述机械臂但坐标系的摆放规则不一样参数表的数值也不一样。很多人拿着A教材的公式去套B软件的参数结果算出个莫名其妙的位置还以为是矩阵乘错了。SDH的关键特征是坐标系{i}建立在连杆i的末端Z轴沿第i1个关节的轴线。也就是说坐标系{i}的原点落在下一个关节的轴线上。MDH则反过来坐标系{i}的Z轴沿第i个关节本身的轴线原点就在当前关节轴线上。差别最直观的体现就在二连杆例子里。用SDH时第一个变换0T1只包含关节1的旋转连杆长度L1作为a0出现在第一个和第二个坐标系之间的变换里。而MDH下每个变换的几何参数直接跟当前关节挂钩参数下标习惯也不一样。这个差异导致同一台机器人SDH参数表和MDH参数表几乎不可能通用。2.2 怎么快速判断手头的资料是哪套DH我整理了一个快速判据适合你在网上扒资料或者看开源代码时用判断点SDH标准DHMDH修正DH坐标系{i}的Z轴沿关节i1轴线沿关节i轴线第一行参数通常a0、alpha0可能直接出现非零a、alpha参数下标习惯a、alpha下标为i-1a、alpha下标为i常见使用场景经典教材、传统多轴机器人树形结构、闭链结构、很多仿真SDK但光看文字还是容易错。我自己的判断方法是看旋转关节的转动轴到底在哪。主动动一下关节i看坐标系{i}的变化情况就能分辨出是SDH还是MDHSDH下动关节i时坐标系{i-1}到{i}的旋转体现在theta_i上MDH下也是类似但坐标系的Z轴和变换挂接的关节不同从结构上一眼能看出来。2.3 我的实用建议选定一套就不要换来换去说实话这两种模型没有高下之分SDH在教材里讲得多MDH在不少仿真项目里用得多。最怕的不是选错而是选了一套又按另一套的规则去读参数。我自己习惯在这类项目里全程用SDH因为在教学和正运动学推导上资料更多出错时更容易找到对照。如果你是从某个开源驱动或者仿真插件起步那就跟着那个项目用MDH别自己擅自改。还有一个防呆技巧无论SDH还是MDH参数表里都要写清楚哪一列是可变的关节变量、哪一列是固定常量。很多时候搞混不是流派的问题而是把theta和d当作变量的地方整反了。3. 六轴机械臂完整建模从坐标系摆放到Python代码跑通3.1 建模第一步不是填表而是画草图和定零位很多教程上来就给一张DH参数表看起来好像凭空就有。实际建模时第一步是画一张机械臂各关节轴线的简化草图标清楚每一根轴线的方向然后确定数学零位。有个非常容易踩的概念混淆是机械零位和数学零位。机械臂示教器上显示的零点通常是每个关节都转到某个物理位置比如对齐刻度线但不一定等于你DH表里theta0时的构型。比如你的机械臂在示教器上显示关节2为0度但你的DH模型里关节2要填90度才能让坐标系的姿态对齐那多出来的90度就是theta offset。这个偏移量必须单独记录不要硬塞进公式里否则后续标定、标零位的时候你会发疯。3.2 逐关节定坐标系填出这台臂的DH表我用一台典型的六轴关节型机械臂举例。关节1绕基座垂直轴旋转关节2和关节3做俯仰关节4、5、6组成手腕。按SDH规则各坐标系Z轴方向沿下一个关节轴线。经过一步步摆放我得到如下参数表这里是虚构的示例数据不代表任何真实机型itheta_id_i (m)a_{i-1} (m)alpha_{i-1}1θ10.30002θ200.0590°3θ300.3504θ40.350.0890°5θ500-90°6θ60.1000怎么读这张表theta列是可变的关节角d10.30是基座到肩关节的高度d40.35是肘部到手腕的轴向距离d60.10是手腕末端到法兰面的距离a20.35是大臂长度a30.08是手腕前的一小段偏置。alpha的90°和-90°表明相邻关节轴线互相垂直正负号决定了Z轴的方向是朝上还是朝下。这里要特别强调alpha正负号的判断。判断方法就是右手定则右手大拇指指向X轴方向其余四指从Z_{i-1}转向Z_i能转过去就是正反过来就是负。建模时宁可多花十分钟把每个alpha的正负号对着草图核一遍也不要写完代码后一整晚排查方向问题。3.3 齐次变换矩阵公式与乘法顺序标准DH的单个变换矩阵长这样T(i-1→i) [ cosθ_i -sinθ_i·cosα_{i-1} sinθ_i·sinα_{i-1} a_{i-1}·cosθ_i ] [ sinθ_i cosθ_i·cosα_{i-1} -cosθ_i·sinα_{i-1} a_{i-1}·sinθ_i ] [ 0 sinα_{i-1} cosα_{i-1} d_i ] [ 0 0 0 1 ]这个矩阵看着复杂但拆开就四个变换的复合先沿X_{i-1}方向平移a_{i-1}再绕X_{i-1}扭转alpha_{i-1}然后沿Z_i方向平移d_i最后绕Z_i旋转theta_i。写代码不需要手推这个矩阵直接按公式填入即可。整个机械臂的末端位姿就是把这些矩阵按顺序乘起来T(0→6) T(0→1) · T(1→2) · T(2→3) · T(3→4) · T(4→5) · T(5→6)最后得到的4x4矩阵其左上角3x3块是末端坐标系在基座坐标系中的姿态第四列前三个元素就是末端位置。3.4 用Python跑通正运动学我把上面的例子写成了一段可运行的正运动学代码用numpy矩阵相乘逻辑非常清晰import numpy as np def dh_transform(theta, d, a, alpha): T np.array([ [np.cos(theta), -np.sin(theta)*np.cos(alpha), np.sin(theta)*np.sin(alpha), a*np.cos(theta)], [np.sin(theta), np.cos(theta)*np.cos(alpha), -np.cos(theta)*np.sin(alpha), a*np.sin(theta)], [0, np.sin(alpha), np.cos(alpha), d], [0, 0, 0, 1] ]) return T def fkine(theta_vec): # 参数表theta取当前值d、a、alpha为固定参数 dh_table [ [theta_vec[0], 0.30, 0, 0], [theta_vec[1], 0, 0.05, np.pi/2], [theta_vec[2], 0, 0.35, 0], [theta_vec[3], 0.35, 0.08, np.pi/2], [theta_vec[4], 0, 0, -np.pi/2], [theta_vec[5], 0.10, 0, 0], ] T np.eye(4) for row in dh_table: T T dh_transform(row[0], row[1], row[2], row[3]) return T print(全关节0度时的末端位姿) print(fkine(np.zeros(6))) print(关节1转90度后的末端位置) print(fkine([np.pi/2, 0, 0, 0, 0, 0]))在我的示例参数下全零位时末端位置大约是[0.91, -0.35, -0.15]米姿态矩阵是[[1,0,0],[0,-1,0],[0,0,-1]]也就是说末端Z轴朝下。关节1转90度后末端位置变成[0.35, 0.91, -0.15]米可以看到它正好绕基座Z轴转了90度位置规律完全符合预期。这段代码你可以直接复制改参数。注意所有角度都用弧度这是新手最容易忽略的。另外我把参数表写在函数内部是为了演示方便实际项目里建议把它们放到配置文件里方便换机型、做标定。4. 验证模型对不对四招让错误在仿真阶段就暴露4.1 第一招零位比对法拿到任何一台机械臂我做的第一件事永远是算一遍全零位。把theta全设成0或者你定义的数学零位用DH模型算出末端位置然后跟机器人的CAD模型比对。如果没有CAD就找一根直尺或激光测距把机械臂摆在零位直接量末端到基座坐标系三个轴方向的距离。差了几毫米内可以接受差了几厘米甚至方向不对那就是DH参数有误不需要犹豫回头查。用我上面的例子全零位的末端位置是[0.91, -0.35, -0.15]米。什么意思就是末端法兰相对于基座向X方向伸出0.91米向Y方向偏了0.35米比基座低0.15米。如果你在真实机械臂上看到的形状完全对不上这个感觉那就要检查是不是坐标系的Z方向或者alpha符号搞反了。4.2 第二招单关节动起来看轨迹验证运动学模型最直观的方法是让某个关节单独动看末端轨迹是否符合几何预期。比如只转动关节1末端应该在基座Z轴周围画一个圆圆的高度应该保持不变半径等于末端到基座Z轴的横向距离。在代码里就是循环theta1从0到2π其他theta保持0记录末端位置画出来是一个在水平面上的圆。这个方法之所以好用是因为它能快速暴露坐标系方向定义错误和关节变量填错位置这类问题。如果关节1动了末端在Z方向上的高度也跟着变那可能是你的Z0方向定义和矩阵里的符号不一致如果末端轨迹不是圆大概率是旋转轴搞错了。4.3 第三招锁定部分关节把六轴降成二轴手算当整体轨迹看着没什么毛病但某个位置总觉得不对时我常用的一招是把机械臂锁成平面机械臂。具体做法选一组关节角度让大臂、小臂、手腕处在同一个平面内比如让关节1等于0这样整个手臂落在某个平面里然后固定其他关节只动关节2或关节3此时末端轨迹应该落在X0Z平面或者X0Y平面内。这种情况下你可以直接用平面几何手算末端位置跟DH模型的结果对比。比如关节2转90度、关节3转0度时大臂指向正上方小臂向前伸末端位置距离基座的高度应该是几个连杆长度的代数和。一旦手算和模型能对上整条链的a、d、alpha参数基本就都被你验证过了。4.4 第四招放到仿真环境或对准示教器数值有些机械臂自带示教器可以在里面看到每个关节角度对应的末端坐标。这是最省事的对标资源。把DH模型算出来的位置跟示教器显示的位置对比如果单位统一、零位定义统一数值应该几乎一致。很多厂家的标定精度在毫米级DH模型算出来如果差了一两毫米主要是机械加工和零位标定误差可以接受但如果差了几厘米别急着怪厂家先回去查单位是不是米和毫米混了theta是不是度数当成弧度用了。说实话我见过最多的一次性翻车现场就是单位问题。DH表里的d和a用米theta用弧度三个一混末端位置能偏得连方向都不对。建议从一开始就在注释里额外标注单位配置文件也统一规定好。5. 那些年我踩过的DH坑高频问题与自查清单5.1 alpha方向搞反Z轴指向不统一alpha的正负号问题太常见了。很多人在纸上画草图时Z轴习惯性朝上但DH建模里Z轴的方向是由下一个关节的轴线方向决定的不一定是朝上。比如关节4和关节5之间的扭转角在示例里是-90度如果填成90度坐标系的Z轴会翻转末端姿态矩阵直接变化位置计算也会随之偏移。所以每次填alpha我都会在草图边上写一行右手定则验证从Z_{i-1}向Z_i绕X_{i-1}旋转填完再审一遍。5.2 参数下标错位SDH的a、alpha是上一根连杆的前面反复提到过SDH里a_{i-1}和alpha_{i-1}描述的是上一段连杆。我在刚开始建模时经常把第i行的a直接填成当前连杆的长度结果整个模型错位。判断方法很简单对照你的草图看坐标系{i-1}到坐标系{i}的变换跨过的是哪一段骨头那根骨头就是a_{i-1}和alpha_{i-1}描述的。5.3 零位偏移没处理示教器显示0不等于数学0真实机械臂的θ0通常不是DH意义上的0。比如某台臂在机械零位时关节2的大臂其实是水平前伸的但在DH定义里可能需要θ290度才对应水平。这种偏移必须在模型里补上否则全零位验证绝对对不上。我的做法是在代码里单独维护一个theta_offset数组正运动学计算前先把offset加进去而不是改DH参数表本身这样标定或者重新调零位时只需要改配置文件。5.4 平移关节的变量处理d才是关节变量如果是直角坐标机械臂或者有平移关节的结构记得关节变量不是theta而是d。平移关节那一行的theta通常固定为0度或90度d根据位置变化。别把theta当变量使了不然算出来的末端位置会靠近得莫名其妙。5.5 DH模型自查清单检查项检查方法常见错误单位统一长度全部用同一单位角度全部用弧度米、毫米混用度、弧度混用alpha符号右手定则对照草图正负号相反Z轴翻转参数下标确认SDH中a、alpha是i-1填成当前连杆长度零位偏移数学零位和机械零位分离记录忽略offset全零位对不上变换顺序矩阵按关节顺序相乘顺序错误或漏乘单关节验证只动一个关节看轨迹轨迹不是圆或方向不符单位换算验证末端位置量级在合理范围误差到厘米级仍没发现拿这张清单去逐一核对我相信至少能筛掉八成以上的低级错误。剩下两成多半是机器人结构本身有奇怪的补偿或者耦合那已经不是基础DH建模的范畴了。最后说点个人经验。我现在的习惯是先写一个最小的二连杆平面臂作为自检工具每次要换新算法、新机型时先在这个简单模型上跑通框架再换上真正的六轴参数表。这样能保证出问题的一定是参数或者机型适配而不是正运动学代码本身。另外强烈建议把DH参数做成外部配置文件不要写死在代码里。我做过的项目里DH参数至少被改过三回——换了机械装配、标定了零位、调整了工具长度每次改参数都要重新编译排查改成配置后只改几行数据省下的时间够喝好几杯咖啡了。