目的
本文手把手教你在 Mathematica 科学计算软件中搭建机器人的仿真环境,具体包括以下内容:
1 导入机械臂的三维模型
2 正\逆运动学仿真
3 碰撞检测
4 轨迹规划
5 正\逆动力学仿真
6 运动控制
文中的代码和模型文件点击此处下载,或者此处:https://github.com/robinvista/Mathematica/blob/master/code.rar 。使用的软件版本是 Mathematica 11.1,较早的版本可能缺少某些函数,所以最好使用最新版。进入正文之前不妨先看几个例子:
无论你从事的是机器人研发还是教学科研,一款好用的仿真软件都能对你的工作起到很大的帮助。那么应该选择哪个软件呢?最方便的选择就是成熟的商业软件,例如Adams、Webots。你的钱不是白花的,商业软件功能强大又稳定,而且性能一般都经过了优化。可是再强大的商业软件也有设计不合理的地方,它们的算法基本都是“黑箱”,你想做一点更改都不行。从学习和研究的角度出发,最好选择代码可修改的开源或半开源软件。
在论文数据库中检索一下就会发现,很多人都选择借助Matlab这个数学软件平台进行机器人的建模仿真 这并不奇怪,因为Matlab具有优秀的数值计算和仿真能力,在它的基础上开发会很便利。如果你非要舍近求远,用 C++ 编写一套仿真软件,先不要说仿真结果如何显示,光是矩阵计算的实现细节就能让你焦头烂额。
与大名鼎鼎的Matlab 相比,Mathematica在国内知名度并不高,但是不要小看它哦,一旦熟悉了你会刮目相看。我简单对比了一下二者在机器人仿真方面的特点,见下表。由于Mathematica不俗的表现,我选择在它的基础上搭建仿真环境。如果你对Mathematica不熟悉,可以看网络教程,也可以参考我的学习笔记。Mathematica有着陡峭的学习曲线,入门并不容易,其实初学者最快的入门方法就是照着大量的例子演练。本文面向Mathematica的初学者,所以不会使用过于高超的编程技巧。
1. 获取机器人的外观模型
制作逼真的仿真首先需要的是机器人的外观模型。如果你有机器人的三维模型最好,没有的话也不要紧,著名的机器人制造商都在官网提供其各种型号机器人的真实模型,例如 ABB、安川,供大家免费下载和使用。为了防止山寨,这些模型都是不可通过建模软件直接修改的格式,例如 IGES 和 STEP 格式。但我们只用来显示和碰撞检测,所以并不影响仿真。
2. 导入机器人的外观模型
获得模型后要导入Mathematica中进行处理并显示,下面我们借助一个例子说明具体的步骤。Motoman ES165D 是安川公司生产的一款6自由度点焊机器人,其最后三个关节轴线相交于一点,这是一种非常经典而且有代表性的设计,因此我们选择以这款机器人为例进行讲解(这个机器人的完整模型点击此处下载)。
用SolidWorks 2014软件打开机器人的装配体文件,并选择“另存为”STL 格式。然后打开当前页面下方的“选项”对话框,如下图。这里我们要设置三个地方:
1. “品质”表示对模型的简化程度,如果你想实现非常逼真的效果,可以选择“精细”,但缺点是数据点很多导致文件很大、处理起来比较慢。一般选择“粗糙”就够了;
2. 勾选“不要转换 STL 输出数据到正的坐标空间”;
3. 不要勾选“在单一文件中保存装配体的所有连杆”。
然后在 Mathematica中导入 STL 文件,使用的代码如下(假设这些 STL 文件保存在 D:\MOTOMAN 文件夹下):
这里我偷了个懒,为了少打些字,我把导出连杆的文件名改成了从1到9的数字(这个机械臂的装配体一共包含9个零件)。我们把导入的模型显示出来,效果如下图。使用的代码如下
你可能会好奇:导入的零件数据是以什么样的格式储存的呢?
用来存储机器人外形数据的robotParts变量包含一共9个零件的数据,假如你想看第一个零件(对应的是基座,它通常用来将机械臂固定在大地上),可以输入:
运行后的输出结果是一堆由 GraphicsComplex 函数包裹着的数字,主要可分为两部分:第一部分是零件几何体所有顶点的三维坐标;第二部分是组成零件几何体的三角形(注意:构成每个三角形的三个顶点是第一部分点的序号,而不是坐标值)。我们可以用以下代码将其分别显示出来:
经过姿态变换后的机器人看起来舒服点了,只是有些苍白。为了给它点个性(也方便区分不同的零件或者称为连杆),我们给连杆设置一下颜色,代码如下。你可能注意到了,这里我没有使用循环去为9个连杆一个一个地设置颜色,而是把同类的元素(颜色)写在一起,然后再和连杆列表一起转置即可把颜色“分配”给各个连杆。这样做的好处就是代码比较简洁、清晰,以后我们会经常这么做。
3. 运动学仿真
前面的工作只是让机械臂的模型显示出来。如果想让它动起来,那就要考虑运动学了。机器人这个学科听起来高大上(很多都停留在理论上),可实际上现在大多数工业机器人的控制方式还是比较低级的,它们只用到了运动学,高级一点的动力学很少用,更不要提智能了(它们要说自己有智能,我们家的洗衣机和电视机都要笑掉大牙了)。看来要使用机器人,运动学是必不可少的,所以我们先来实现运动学。
在建立运动学模型之前我们需要了解机器人的机械结构。前面提到,MOTOMAN-ES165D 是一个6自由度的串联机械臂。而6个自由度的机器人至少由7个连杆组成(其中要有一个连杆与大地固定,也就是基座)。可是我们导入的连杆有9个,多出来的2个连杆是弹簧缸(基座上黄色的圆筒)的组成部分。MOTOMAN-ES165D 机器人能够抓持的最大负载是165公斤,弹簧缸的作用就是作为配重平衡掉一部分负载的重量,要不然前端的关节电机会有很大的负担。可是弹簧缸给我们的建模造成了麻烦,因为它导致“树形拓扑”以及存在“闭链”,这不太好处理。为此,我们先忽略掉弹簧缸。
3.1 连杆的局部坐标系
机器人的运动也就是其构成连杆的运动。为了表示连杆的运动,我们要描述每个连杆的位置和姿态(合称为“位姿”)。通常的做法是在每个连杆上固定一个坐标系(它跟随连杆一起运动),这个坐标系被称为“局部坐标系”。通过描述局部坐标系的位姿我们就可以描述每个连杆的位姿。如何选择局部坐标系呢?理论上你可以任意选择,不过局部坐标系影响后续编程和计算的难易程度,所以我们在选择时最好慎重。在运动学建模和动力学建模中,坐标系的选择通常是不同的:
● 运动学建模时,连杆的局部坐标系一般放置在关节处,这是因为常用的 D-H 参数是根据相邻关节轴定义的;
● 动力学建模时,连杆的局部坐标系一般放置在质心处,这是因为牛顿方程是关于质心建立的,而且关于质心的转动惯量是常数,这方便了计算。
我们先考虑运动学,因此将局部坐标系设置在关节处。在SolidWorks中打开任何一个连杆,都能看到它自己有一个坐标系。描述一个连杆的每一条边、每一个孔的坐标都以这个坐标系为参考,我们称它为“绘图坐标系”。绘图坐标系通常不在质心处,因为在你还没画完连杆之前你根本不知道它的质心在哪里。绘图坐标系通常在连杆的对称中心或者关节处,我们不妨将每个连杆的绘图坐标系当做它的局部坐标系。
那么下一个问题是每个连杆的绘图坐标系在哪儿呢?我们以第三个连杆为例说明,如下图左所示,点击SolidWorks左侧的“原点”标签,图中就会显示绘图坐标系的原点。(如果你想将绘图坐标系显示出来,可以先选中“原点”标签,然后点击上方菜单栏中的“参考几何体”,再选择“坐标系”,然后直接回车即可看到新建的绘图坐标系,如右图,可见它位于上面的关节轴)
然后回到机器人的装配体中,在左侧的连杆树中展开每个连杆找到并选中其绘图坐标系的原点,然后点击上方菜单栏“评估”中的“测量”即可看到图中出现了一组坐标值(如下图所示),这就是连杆绘图坐标系的原点在全局坐标系(本文将全局坐标系定义为装配体的坐标系)中的位置。
我们记录下所有连杆的绘图坐标系的原点位置(除去弹簧缸的2个,注意 SolidWorks 中默认的单位是毫米,这里除以 1000 是为了变换到 Mathematica 中使用的国际标准单位——米):
还记得吗?最开始我们导入机器人模型时,各连杆的位置都已经按照装配关系确定好了,所以它们的坐标也是相对于全局坐标系描述的。可是现在我们要让机械臂动起来(并且显示出来),这就需要移动这些坐标。为了方便起见,最好能将每个连杆的坐标表示在自己的绘图坐标系中,因为这样我们只需要移动绘图坐标系就行了,而各点的坐标相对它们所属的绘图坐标系是不动的。应该怎么做呢?很简单,将连杆的坐标减去绘图坐标系的原点在全局坐标系中的坐标即可:
坐标移动后的连杆如下图所示(图中的坐标系是各个连杆自己的绘图坐标系,我没有对坐标转动,所以绘图坐标系和全局坐标系的姿态相同)。我们一开始从 SolidWorks 导出文件时是一次性地导出整个装配体的。其实,如果我们挨个打开各个连杆并且一个一个的导出这些连杆,那么得到数据就是相对于各自的绘图坐标系的,只不过这样稍微麻烦一点。
3.2 利用旋量建立运动学模型
下面我们讨论如何建立运动学模型。描述机器人连杆之间几何关系的经典方法是采用 D-H 参数(Denavit - Hartenberg parameters)。D-H 参数巧妙在什么地方呢?我们知道,完全确定两个坐标系(或者刚体)的位姿关系需要6个参数,因为三维空间中的刚体有6个自由度。如果不考虑关节转动(平移)仍需要5个参数。然而 D-H 参数居然只用了4个参数就能够确定相邻连杆的位姿关系,可见 Denavit 和 Hartenberg 这哥俩确实动了番脑筋。不过为了避免 D-H 参数的一些缺点,我们弃之不用而采用旋量的表示方法。刚接触旋量的同学会觉得它很难理解。其实旋量有什么性质、它和刚体运动的关系又是什么?这些问题数学家也是用了很长时间才搞清楚。在本文中你可以把旋量简单想象成一个描述关节转动的向量。三维空间中的旋量是一个6维向量,要描述一个关节旋量需要确定一个关节轴线的方向向量(3个参数)和轴线上任意一点的坐标(又要3个参数)。
旋量和向量相似的一个地方是,对它的描述也是相对于一个坐标系的。我们选择哪个坐标系呢?这里我们要参考 D-H 参数,每一个连杆坐标系在定义时都相对于前一个连杆的坐标系。所以我们将每个关节轴的旋量表示在前一个连杆中。这次我们以2号连杆为例说明如何确定关节运动对应的旋量:
1. 首先来看关节轴线的方向,这个要相对于2号连杆的绘图坐标系。(我们要确定关节2的旋量,至于关节1的旋量最好在连杆1中确定)。从下图中看关节2的轴线方向似乎是 x 轴,可是我们前面将绘图坐标系的姿态和全局坐标系的姿态设定为一样的,所以应该在全局坐标系中确定,也就是 y轴。
2. 关节轴线上任意一点的坐标,这个同样要相对于2号连杆的绘图坐标系。我们在轴线上任选一点即可。步骤是:点击 SolidWorks 上方菜单栏的“参考几何体”,选择“点”,然后在左侧面板选择“圆弧中心”,然后选择图中的关节轴周围的任意同心圆弧即可创建一个参考点,这个点就是我们想要的。我们可以在连杆视图中测量这个点的坐标,也可以在机器人完整装配体中测量,这里我选择后者。(测量步骤参照前面测量“连杆绘图坐标系的原点”)
定义关节旋量的代码如下。其中相对旋量 ξr 用于迭代运动学计算,它的含义是当前连杆的转轴表示在前一个连杆坐标系中。
我们对关节的相对运动进行了表示,然而要建立运动学还缺少一样东西:连杆间的初始相对位姿(初始的意思是机械臂处于“零位”的状态下)。零位下,我们将所有连杆的姿态都认为和全局坐标系一样,所以不用计算相对姿态了。至于它们的相对位置嘛,我们已经知道了绘图坐标系原点在全局坐标系中的坐标,两两相减就可以得到它们的相对位置了,很简单吧!(见下面的代码)
现在可以正式推导机械臂的运动学模型了。在使用机械臂时,大家一般只关心其最末端连杆的位姿,更确切的说,是最末端连杆的位姿与关节角度的关系。不过为了得到最末端连杆的位姿,我们需要计算中间所有连杆的位姿。这里利用相邻连杆的迭代关系——每个连杆的位姿依赖前一个连杆的位姿——来提升计算效率。所以,可以定义机械臂所有连杆的运动学函数为:
可以看到,只要定义好关节旋量,建立运动学模型非常简单。可是这样得到的运动学模型对不对呢?我们来检验一下。借助 Manipulate 函数,可以直接改变机械臂的各关节角度,并直观地查看机械臂姿势(应该叫构型了哦)的变化,如以下动画所示。可以看到,机械臂各连杆的运动符合我们设置的关节值,这说明运动学模型是正确的。
4. 逆运动学仿真
借助运动学,我们成功地通过改变关节角度实现了对机械臂的控制。当然这没什么值得炫耀的,本质上不过是矩阵相乘罢了。本节我们考虑一个更有挑战性也更好玩的问题。如果告诉你所有连杆(局部坐标系)的位姿,你能不能算出机械臂的各个关节角来?你一定会说这很简单,求一下反三角函数就行了。但是实际应用时经常会遇到比这个难一些的问题:只告诉你机械臂最后一个连杆的位姿,如何得到各关节的角度?这个问题被称为“逆运动学”。Robotic Toolbox工具箱中给出了两个解逆运动学问题的函数:ikine 和 ikine6s,分别是数值解法和符号解析解法,本文我们也用两种方式解决逆运动学问题。
同样借助 Manipulate 函数改变值的大小,试验的效果见下图。
4.2 数值解法之——迭代法
解方程的方法很多,下面我们换一种思路求解逆运动学方程,其思想来自于(英文版187页),代码如下:
我们以后会用到很多矩阵操作(比如转置、求逆),而 Mathematica 的函数名太长,为了写起来方便,我定义了简写的转置和求逆函数,代码如下:
计算结果 qs 中存储了机械臂到达不同点处的关节向量,如果以后我们想让机械臂跟踪这个向量序列,可以对其插值得到连续的关节函数,这是靠 Interpolation 函数实现的,代码如下。关于 Interpolation 函数我要啰嗦几句,因为以后我们可能会经常用到它。对于机械臂的每个关节来说, Interpolation 得到的是一个插值函数(InterpolatingFunction),更确切地说是“Hermite多项式” 或“Spline 样条”插值函数。插值函数与其它的纯函数没什么区别,比如说我们可以对它求导、求积分。对这6个关节的插值函数求导就能得到关节速度和加速度函数:
4.3 雅克比矩阵的零空间
在上一节求解逆运动学问题时我们使用了机械臂的雅克比矩阵,它能够将关节速度映射到末端连杆的速度。由于末端连杆的速度有不止一种定义方式(例如有:空间速度、本体速度、全局速度,它们的定义见我的另一篇博客),所以对应了不同的雅克比形式(也就是逆运动学函数中的 Js、Jb、Jg)。
雅克比矩阵是机器人学里的“红人”,它出现在很多场合,在这个圈子混的时间长了你经常能见到它。雅克比矩阵有一些有趣的性质,比如它的零空间。只要机械臂的关节速度在其雅克比矩阵的零空间中,那么末端连杆的速度总是零,零空间由此得名。通俗的说就是:不管关节怎么动,末端连杆始终不动(就像被钉死了一样)。这个性质还挺有用的,因为有些场合要求机械臂在抓取东西的时候还能躲避障碍物。在其它领域,例如摄影,为了保证画面稳定需要摄像机能防抖动;在动物王国中,动物觅食时头部要紧盯猎物(被恶搞的稳定鸡);在军事领域(例如坦克、武装直升机),要求炮口始终瞄准目标,不管车身如何移动和颠簸。
对于本文中的 6 自由度机械臂,由于它不是冗余的,所以大多数时候计算零空间会得到空(也就是说不存在零空间)。为了形象地展示零空间的效果,我不得已只用了平移的部分。以下代码展示了机械臂在(平移)零空间中的一种运动,如下图所示。可以看到,不管机械臂如何运动,末端连杆(局部坐标系)的位置始终不动(但是它的姿态会改变,矩阵mask 的作用就是滤掉转动分量,只剩下沿 x、y、z 轴的平移运动)。BodyJacobian 函数用于计算本体雅可比矩阵,它也来自于 Screws 工具包。零空间是个线性空间,如果我们知道它的一组基向量,通过线性组合能够得到任意的(速度)向量。NullSpace函数返回的刚好就是构成矩阵的零空间的一组基向量。通过对这组基向量线性组合即可得到速度向量,其使机械臂末端始终不动。下面的例子中使用的组合系数都是 1 ,也就是所有基向量相加(向量相加就是对应元素相加,由Total函数完成)。由于雅可比矩阵是机械臂构型q的函数,所以机械臂的构型一旦改变了,我们就要重新计算它的雅可比矩阵。如果还不理解,可以随时显示出dq的值,然后计算Jgm.dq看看结果是不是零。如果是零就说明dq在零空间里。
5. 碰撞检测
我们生活的物质世界有一个简单的法则:两个物体不能同时占据同一个空间位置,如果它们试图那么做会有很大的力将它们分开。可是仿真是在虚拟的数字世界中进行的,这个世界可不遵守物质世界那套力的法则,因此仿真还不够真实。为了让机器人仿真更接近实际,我们需要考虑“碰撞检测”(Collision Detection)功能。为了追求效率,工业机器人的运动速度通常比较快,而且抓着较重的负载,它一旦碰到障碍物或者人,结果一般是“物毁人伤”。所以在仿真时提前检测是否有碰撞很有必要。在一些规划算法中,碰撞检测也是很重要的一部分。
值得一提的是,现在一些先进的机器人控制器开始配备简易的碰撞检测功能,如果在机器人工作时有人突然挡住了它,它会自动停止。这是通过检测机械臂关节处电机的电流大小实现的。当机械臂碰到人时,它相当于受到了一个阻力,电机要想保持原来的速度运行需要加大电流,灵敏的控制器会感知到电流的波动,这样我们就能通过监视电流来判断机械臂有没有发生碰撞,如果电流超过一定范围就认为机械臂发生碰撞了,需要紧急刹车。可是这种碰撞检测方法只适用于小负载(<5kg)的机械臂。因为对于重型机械臂,即便它也会停下来,可是它的惯性太大需要一段刹车距离,这足以对人造成伤害。
碰撞检测是一个比较有难度的几何问题,目前有很多成熟的算法(AABB、GJK)。我们的关注点在机器人,所以不想在碰撞检测算法上浪费太多时间。为此,我们使用 Mathematica 自带的 RegionDisjoint 函数实现碰撞检测。在帮助文档中,可以了解到 RegionDisjoint 函数用于判断多个几何体是否相交,如果两两之间都不相交则返回 True ,而两个几何体出现了相交,就表示它们发生了碰撞(太好了,这简直是为碰撞检测量身定做的函数)。
不过有了 RegionDisjoint 函数并不意味着一帆风顺。“碰撞检测”是出了名的吞噬者,它会霸占 CPU 大量的计算资源。如果不把它伺候好,你的计算机再先进都会卡死。我们一般希望碰撞检测越快越好,可是精度和速度是一对矛盾,追求速度只能牺牲一定的精度。机械臂的真实外形往往都是不规则的复杂几何体,这使得精确的碰撞检测很浪费时间。多数情况下,我们没有必要达到非常逼真的检测效果。如果不追求很高的精度,碰撞检测应该保守一些。也就是说,在实际没发生碰撞时允许误报,但在发生碰撞时不能漏报——宁可错杀一千,不可放过一个。碰撞检测的计算量与模型的复杂程度有关。我们导入的机器人模型虽然已经经过了“瘦身”,但对于RegionDisjoint函数来说还是有些复杂。为此,我们需要进一步缩减简化。为了保守一点,我们采用比真实机械臂连杆稍大些的模型,比如连杆的凸包(Convex Hull)。虽然 Meshlab 软件可以制作凸包,但是我发现效果不太好。好在 Mathematica 自带的 ConvexHullMesh 函数可以计算任意几何体的凸包。我采用的方法是先用 ConvexHullMesh 分别计算各连杆的凸包,再导出凸包用 Meshlab 进一步简化,最后再导入回 Mathematica 中。计算连杆凸包及导出所需的代码如下。(注意:由于连杆数据已经是变换后的了,简化后的连杆导入后不需要旋转等变换)
我们检验一下机械臂和外部障碍物的碰撞检测,至于机械臂连杆之间的碰撞我们暂时不考虑。代码如下,效果如下图所示。
我们检验一下机械臂和外部障碍物的碰撞检测,至于机械臂连杆之间的碰撞我们暂时不考虑。代码如下,效果如下图所示。
其中,TransPt[g][pt3D] 函数的功能是用齐次变换矩阵 g 对三维向量(点) pt3D 做变换,定义如下:
6. 轨迹规划
轨迹规划的目的是得到机器人的参考运动轨迹,然后机器人再跟踪这条轨迹完成最终的动作。轨迹规划是机器人研究领域非常重要的一部分。机器人要干活就离不开运动,可是该如何运动呢?像搭积木、叠衣服、拧螺钉这样的动作对人类来说轻而易举,可要是让机器人来实现就非常困难。工业机器人既没有会思考的大脑,也缺少观察世界的眼睛(又瞎又傻),要让它们完全自主运动真是太难为它们了。它们所有的运动都是人教给它的。你可以把机器人想象成木偶,木偶看起来像是有灵魂的生物,但实际上它的运动都是人灌输的,木偶只会死板地按照人的控制运动,自己没有任何主动性,只是行尸走肉罢了。实际工厂中,是由工程师操作着控制面板,一点点调节机械臂的各个关节角度,让它到达某个位置。控制程序会记录机械臂的角度变化,只要工程师示教一次,机械臂就能精确而忠实地重复无数次。不过这种不得已而为之的方法实在是太笨了,如果有一种方法能够自动根据任务生成机器人的参考轨迹多好,下面我们将介绍一种常用的轨迹规划方法。
6.1 路径、轨迹——傻傻分不清楚
“轨迹”是什么?要理解轨迹可离不开路径。路径(Path)和轨迹(Trajectory)是两个相似的概念,它们的区别在于:
● 路径只是一堆连续空间坐标,它不随时间变化。例如下图左侧的三维曲线就是一段路径。
● 轨迹是运动的坐标,它是时间的函数,一个时刻对应一个空间坐标点。轨迹包含的信息更多,我们可以对它微分得到速度、加速度等信息,而路径是没有这些的。下图右侧展示了两条轨迹,它们虽然经过相同的路径,但却具有不同的速度——黑色轨迹开始运动较快,随后被红色反超,最后二者又同时到达终点,所以它们是两条轨迹(而不是一条)。
如果我们画出红色和黑色轨迹的 x,y,z坐标分量,就会看到它们从同一位置出发,又在另一个位置碰头,却经历了不同的过程,如下图所示(注意红黑两组曲线的开始和结尾)。
制作上面的轨迹需要以下几个步骤:
1. 首先随机生成一些三维空间中的点。
3. 二次插值。我们虽然得到了插值函数,但它是一个向量值函数,难以进一步处理(比如求积分、微分)。所以,我们需要在 bfun 函数的基础上再处理。首先得到 bfun 函数图像上若干离散点(按照 0.001 的间隔取):
我们说路径携带的信息比轨迹少,那么从路径中能提取出什么有价值的信息呢?
路径只包含几何信息:对于一个三维空间中的光滑路径,我们能计算这条路径上每一点处的切线和法线,它们刚好能唯一地确定一个右手直角坐标系(这个坐标系又被称为 Frenet 标架),如下图所示。对应的代码如下。大家都知道,平面上的曲线可以用曲率描述它的弯曲程度,可是要描述三维空间曲线的弯曲程度还需要一个量,叫挠率,它是描述扭曲程度的。如果把Frenet 标架想象成过山车,你坐在上面就能更直观地感受曲率和挠率的含义。
6.2 轨迹规划方法
“轨迹规划”中的“规划”又是什么意思呢?
规划的英文是 plan,也翻译为“计划、打算”。你肯定知道“计划”是什么意思。对于人来说,计划就是在做事之前先想想应该怎么做才好,比如沿着什么途径、按照哪几个步骤。而且,通常你有一个要到达的目标,没有目标谈不上计划(当然一般还得有一个出发点,但这不是必需的)。假如我想放假出去玩,在制定了详细的开车路线后我连要去哪都不知道,那我是不是神经病呢。正常人都是先决定去哪,然后才选择交通线路。此外,计划还有个评价的标准——怎么样才算“好”呢?如果没有标准,那我们还计划个什么劲儿啊(反正没有好坏之分)?把目标和评价标准推广到机器人的轨迹规划领域就是:机器人怎么(运动)才能到达一个目标位置(或者区域、构型),而且不仅仅是到达目标,有时我们还想以最好的方式(比如最快、消耗能量最少)到达,这就是轨迹规划的任务。“轨迹规划”的叫法挺多,有叫“轨迹生成”的,有叫“运动规划”的,但不管怎么叫其实大概都是一个意思。
对于机械臂来说,轨迹规划方法可以根据有没有障碍物来划分。如果没有障碍物,那就简单些了,我们可以直接规划轨迹;如果有障碍物则一般先规划路径(因为路径包含信息更少,相对更简单),然后对路径设置速度得到轨迹(因为主要的工作都在规划路径,因此也可称其为“路径规划”)。
路径规划都有哪些方法呢?比较流行的有:图搜索、势场法、RRT 等等。下面我们来实现 RRT 方法。
RRT(快速探索随机树) 是一种通用的方法,不管什么机器人类型、不管自由度是多少、不管约束有多复杂都能用。而且它的原理很简单,这是它在机器人领域流行的主要原因之一。不过它的缺点也很明显,它得到的路径一般质量都不是很好,例如可能包含棱角,不够光滑,通常也远离最优路径。
RRT 能在众多的规划方法中脱颖而出,它到底厉害在哪里呢?
天下武功唯快不破,“快”是 RRT 的一大优点。RRT 的思想是快速扩张一群像树一样的路径以探索(填充)空间的大部分区域,伺机找到可行的路径。之所以选择“树”是因为它能够探索空间。我们知道,阳光几乎是树木唯一的能量来源。为了最大程度地利用阳光,树木要用尽量少的树枝占据尽量多的空间。当然了,能探索空间的不一定非得是树,比如Peano曲线也可以做到,而且要多密有多密,如上图左所示的例子。虽然像Peano曲线这样的单条连续曲线也能探索空间,但是它太“确定”了。在搜索轨迹的时候我们可不知道出路应该在哪里,如果不在“确定”的搜索方向上,我们怎么找也找不到(找到的概率是0)。这时“随机”的好处就体现出来了,虽然不知道出路在哪里,但是通过随机的反复试探还是能碰对的,而且碰对的概率随着试探次数的增多越来越大,就像买彩票一样,买的数量越多中奖的概率越大(RRT名字中“随机”的意思)。可是随机试探也讲究策略,如果我们从树中随机取一个点,然后向着随机的方向生长,那么结果是什么样的呢?见上图右。可以看到,同样是随机树,但是这棵树并没很好地探索空间,它一直在起点(红点)附近打转。这可不好,我们希望树尽量经济地、均匀地探索空间,不要过度探索一个地方,更不能漏掉大部分地方。这样的一棵树怎么构造呢?
RRT 的基本步骤是:
1. 起点作为一颗种子,从它开始生长枝丫;
2. 在机器人的“构型”空间中,生成一个随机点 A
3. 在树上找到距离 A 最近的那个点,记为 B吧;
4. B 朝着 A 的方向生长,如果没有碰到障碍物就把生长后的树枝和端点添加到树上,返回 2;
随机点一般是均匀分布的,所以没有障碍物时树会近似均匀地向各个方向生长,这样可以快速探索空间(RRT名字中“快速探索”的意思)。当然如果你事先掌握了最有可能发现路径的区域信息,可以集中兵力重点探索这个区域,这时就不宜用均匀分布了。
RRT 的一个弱点是难以在有狭窄通道的环境找到路径。因为狭窄通道面积小,被碰到的概率低,找到路径需要的时间要看运气了。下图展示的例子是 RRT 应对一个人为制作的很短的狭窄通道,有时RRT很快就找到了出路,有时则一直被困在障碍物里面。对应的代码如下(这段代码只用于演示 RRT 的原理,不是正式代码,但它有助于理解正式代码的运算过程):
RRT探索空间的能力还是不错的,例如下图左所示的例子,障碍物多而且杂乱(借助 Mathematica 本身具有的强大函数库,实现这个例子所需的所有代码应该不会超过30行)。还有没有环境能难住RRT呢?下图右所示的迷宫对RRT就是个挑战。这个时候空间被分割得非常严重,RRT显得有些力不从心了,可见随机策略不是什么时候都有效的。
“随机”使得RRT有很强的探索能力。但是成也萧何败也萧何,“随机”也导致 RRT 很盲目,像个无头苍蝇一样到处乱撞。如果机器人对环境一无所知,那么采用随机的策略可以接受。可实际情况是,机器人对于它的工作环境多多少少是知道一些的(即使不是完全知道)。我的博客一直强调信息对于机器人的重要性。这些已知的信息就可以用来改进算法。一个改进的办法就是给它一双“慧眼”:在势场法中,势函数携带了障碍物和目标的信息,如果能把这个信息告诉 RRT,让它在探索空间时有倾向地沿着势场的方向前进会更好。这样,RRT 出色的探索能力刚好可以弥补势场法容易陷入局部极小值的缺点。
将RRT方法用在机械臂上的效果如下图所示(绿色表示目标状态)。我设置了4个障碍物(其中一个是大地),这对机械臂是个小小的挑战。由于我们生活在三维空间,没办法看到六维关节空间,所以我把六维关节空间拆成了两个三维空间,分别对应前三个关节和后三个关节(严格来说,六维转动关节空间不是一个欧式空间,它是个流形,不过这里我们不必纠结这个差别):
正式 RRT 的主程序代码如下。我将 RRT 树定义为由节点列表 nodes 和树枝列表 edges 组成,即 RRTtree = {nodes, edges}。其中节点列表 nodes 存储树中所有节点(每个节点都是一个 6 维向量,表示机械臂的关节值),树枝列表 edges 中存储所有树枝,树枝定义为两个节点的代号(节点的代号定义为节点被添加到树中的顺序。例如,添加新节点时树中已经有4个节点了,那么新节点的代号就是 5)。
构造 RRT 树用到了以下自定义的函数:
1. 首先是碰撞检测函数 ,如果机械臂没有碰到障碍物就返回。为了节省时间,碰撞检测使用的是机械臂各连杆的凸包,在最后显示的时候才使用原始连杆。构造 RRT 树用到了以下自定义的函数:
1. 首先是碰撞检测函数 ,如果机械臂没有碰到障碍物就返回。为了节省时间,碰撞检测使用的是机械臂各连杆的凸包,在最后显示的时候才使用原始连杆。
构造 RRT 树用到了以下自定义的函数:
1. 首先是碰撞检测函数 collisionDetection,如果机械臂没有碰到障碍物就返回True。为了节省时间,碰撞检测使用的是机械臂各连杆的凸包,在最后显示的时候才使用原始连杆。
2. 碰撞检测函数需要 Region 类型的变量,为此定义 toRegion 函数将几何体变换为 Region 类型,代码如下:
3. 向RRT树中添加节点和边的函数:
4. 树枝朝着采样点生长(为了简单,只检测一点的碰撞情况):
下面的代码可以显示搜索到的(关节空间中的)路径。这条路径质量不高,如果用于机器人的轨迹跟踪还需要经过后期的平滑处理。
其中,backTrack 函数用来从树中抽取出连接起点和目标点的路径:
7. 动力学仿真
如果在淘宝花2块钱买个知网账号,然后检索以“工业机器人控制”为关键词的学位论文,在粗略地浏览了20$\sim$30篇论文的目录之后,你就会像我一样总结出一个朴素的规律:
● 硕士论文一般都建立了机器人的运动学模型。
● 博士论文一般都建立了机器人的动力学模型。
既然运动学已经能够帮助机器人动起来了,为什么还需要费那么大劲建立动力学(以至于需要博士出马)?
在前面的运动学一节中,我们能通过改变各个关节角度控制机械臂运动。但是在实际机械臂上,关节需要由电机驱动才能改变角度。那么电机应该输出多大的力才能驱动机械臂运动呢,所需要的电流又是多大呢?只有知道这些我们才能真正实现对机械臂的控制。传统的工业机器人大多采用两层的控制方式,上层控制器直接输出角度信号给底层驱动器,底层驱动器负责控制电机的电流实现上层给出的运动。上层不需要知道机器人的动力学特性也可以,更不用管需要输出多大电流。如果你的机器人不需要太高的运动速度和精度,动力学没什么太大用处(运动学是必需的,动力学不是必需的)。可是如果你的机器人速度很快,动力学效应就很明显了,这时就要考虑动力学,否则上层发出的指令底层驱动器跟踪不上,就会出现很大的误差。要想把抓着很重的负载而且高速运动的六轴机器人控制好非常困难,中国好像还没有人能做到。这也是为什么国产机器人大多应用在精度不高的场合,比如搬运、码垛,而进口机器人霸占高端应用的原因。在高级的机器人控制器中,都有力矩补偿功能(例如汇川、KEBA的控制器)。这个补偿的力矩是怎么来的呢?就是通过动力学方程计算得到的。补偿力矩用作前馈控制信号,将其添加到驱动器上能使机器人更好地跟踪一段轨迹。现在机器人在中国变得火热起来了,动力学也成为一些人吹嘘的噱头了。
我们如何得到机器人的动力学模型呢?
宅男牛顿首开先河,在同时代的人还浑浑噩噩的时候初步搞明白了力、速度、惯性都是怎么回事,并用数学对其进行了定量描述,从而建立了物体做平移运动时的动力学方程。从牛顿的身上我们知道,学好数学是有多重要。在那个遍地文盲的蛮荒年代,年轻的牛顿已经偷偷地自学了欧几里得、笛卡尔、帕斯卡、韦达等大师的著作,这为他日后发明微积分打下了基础。牛顿谦虚地自称站在巨人的肩上,事实的确如此。
勤奋的欧拉再接再厉,将牛顿的方程推广到转动的情况。哥俩的工作结合起来刚好可以完整地描述物体的运动,这就是牛顿-欧拉法。
博学的拉格朗日发扬光大,又将牛顿和欧拉的工作总结提炼,提出了拉格朗日法。拉格朗日真聪明啊,只需要计算物体的动能,然后再一微分就得到了动力学方程,这是多么简洁统一的方法啊。可是拉格朗日法的缺点是它的效率太低了。对于4自由度以下的机械臂,计算符号解的时间我们还能忍受。至于6自由度以上的机械臂,大多数人都没这个耐心了(十几分钟到数小时)。而且计算出来的动力学是一大坨复杂的公式,很难分析利用。所以本文我们采用牛顿-欧拉法建立机械臂的动力学模型(更准确的说是它的升级版——迭代牛顿-欧拉法)。
7.1 惯性参数
早期工业机器人不使用动力学模型是有原因的,一个是动力学的计算量太大,在高效的计算方法被发现之前,早年的老计算机吃不消;另一个原因就是动力学需要惯性参数。运动学只需要几何参数,这些相对好测量;可是动力学不仅需要几何参数,还需要惯性参数。测量每个连杆的质量、质心的位置、转动惯量很麻烦,尤其是当连杆具有不规则的形状时,精度很难保证。如果使用动力学带来的性能提升并不明显,谁也不想给自己找麻烦的 mdl_puma560.m 文件就存储了 PUMA-560 机器人的惯性参数。不同型号的机器人具有不同的惯性参数,而且机器人抓住负载运动时,也要把负载的惯性考虑进来。
有些情况下,我们不需要知道很精确的惯性参数,差不多够用就行了;可是有些场合对精度有要求,比如拖动示教就要求参数与实际值的误差一般不能超过10%。对于精度要求不高的场合,可以使用一个近似值。大多数三维建模软件(例如 SolidWorks、CATIA)以及一些仿真软件(例如 Adams)都提供惯性计算功能,一些数学软件(Mathematica)也有用于计算惯性的函数(我没有对比过,所以不敢保证这些软件的计算结果都是一样的,我估计很有可能是不一样的)。本文以 SolidWorks 为例介绍如何获取惯性参数。
计算之前首先要设置连杆的材质。在 SolidWorks 中打开一个连杆,在左侧的“材质”上单击右键弹出“材料”对话框,如下图所示。在这里可以设置机器人本体的材质,MOTOMAN-ES165D 这款机器人的连杆是铸铝(铸造铝合金:Cast Aluminum)制造的。不过连杆不包含电机等部件,为此选择密度大一点的材料,本文选择钢铁。这里最重要的是材料的密度,钢铁的密度一般是7.8吨/立方米(在计算惯性时,软件假设连杆的密度是均匀的,这明显是简化处理了)。设置好后点击应用即可。
然后在上方“评估”选项卡中单击“质量属性”就会弹出如下图所示的对话框。
这组公式来自于,我已经验证过了,是正确的(要想结果比较接近,这些惯性参数至少要取到小数点后 5 位,这依然是在“选项”页中设置)。
我们得到了三个惯性张量,在动力学方程中我们应该使用哪个呢?下面的程序使用IC,因为它是相对于绘图坐标系的,而我建立运动学时选择的局部坐标系就是绘图坐标系。(我以后在这里补充个单刚体动力学的例子)
7.2 正动力学仿真
如果给你一条轨迹,如何设计控制律让机械臂(末端)跟踪这条轨迹呢,控制律的跟踪效果怎么样呢?借助正动力学,我们就可以检验所设计的控制律。
由于后面的程序所依赖的动力学推导过程采用了相对自身的表示方法(也就是说每个连杆的速度、惯性、受力这些量都是相对于这个连杆自身局部坐标系描述的),旋量也是如此,为此需要重新定义旋量(代码如下)。其实旋量轴的方向没变(因为局部坐标系的姿态与全局坐标系一样),只是改变了轴上点的相对位置。
正动力学的 迭代牛顿-欧拉算法 代码如下,输入是力矩,输出是运动。可以看到,动力学模型比运动学模型复杂多了(动力学用到运动学,运动学却不需要动力学)。对于很多第一次接触机器人的同学来说,动力学是一只可怕的拦路虎,要搞明白十几个变量都是什么含义可不容易(在仿真的时候可能包含几十个变量,任何一个弄错了都会全盘皆输,动力学可比运动学难伺候多了)。因为动力学模型是一个微分方程,所以整个仿真过程就是个数值积分的过程。
其中, ad 函数用于构造一个李代数的伴随表达形式,代码如下。(开始我们定义的关节旋量是李代数,这里连杆的本体速度也是一个李代数,但加速度却不是。实际上,要想把加速度搞明白可不是那么容易的事
正动力学的输入是关节力矩,下面我们为关节力矩设置不同的值,看看机械臂的表现:
● 如果令关节力矩等于零(即 \[Tau][k] = 0.0),机械臂将在唯一的外力——重力作用下运动,如下图所示。
只受重力的情况下,机械臂的总能量应该守恒。我们可以动手计算一下机械臂的能量(由动能和重力势能组成,代码如下)。将仿真过程中每一时刻的能量计算出来并保存在一个列表中,再画出图像,如下图所示。可见能量几乎不变(轻微的变化是由积分误差导致的,如果步长取的再小一些,就更接近一条直线),这说明机械臂的总能量确实保持恒定,这也间接证实了正动力学代码的正确性。这个简单的事实让人很吃惊——虽然机械臂的运动看起来那么复杂,但是它的能量一直是不变的。从力和运动的角度看,机械臂的行为变化莫测,可是一旦切换到能量的角度,它居然那么简洁。机械臂的运动方程和能量有什么关系呢?聪明的拉格朗日就是从能量的角度去推导动力学方程的。
其中,qfun 和dqfun 是 4.2 节中定义的插值函数,这里用作机械臂要跟踪的(关节空间中的)参考轨迹。跟踪一个圆的效果如下图所示。
● 机械臂在实际工作时可能会受到干扰。PD控制律对于扰动的效果怎么样?我们施加一个扰动信号试试。这里我选择一个尖峰扰动,模拟的实际情况是机械臂突然遭受了一个撞击。扰动函数的定义代码如下,你可以自己修改扰动的大小和尖峰出现的时间。
把扰动加到第二个关节的力矩上
机械臂的响应如上右图所示,可见机械臂还是能回复零点姿势的。一开始机械臂有一个轻微的颤动,俗称“点头”。这是由于刚一开始机械臂的角度和角速度都为零,所以关节力矩也为零,导致机械臂缺少能够平衡重力的驱动力。在第5秒左右扰动出现,导致机械臂偏离了零点姿势,但在反馈控制作用下很快又回到了零点姿势。
7.3 逆动力学仿真
输入力矩后,借助正动力学能得到关节角加速度,积分后可以得到角速度和角度。就像运动学和逆运动学的关系一样,逆动力学与正动力学刚好相反,它的用处是:如果告诉你机械臂的运动(也就是关节角度、角速度、角加速度),计算所需的关节力矩。
在重力作用下,机械臂保持“立正姿势”需要多大力矩呢?将初始状态设为 0,经过逆动力学计算得到的答案是 {0,−38.1,−38.1,0,−2.06,0}。如果把这个力矩再带入正动力学仿真就能看到机械臂保持静止不动,这证明我们的逆动力学模型也是正确的。
8. 结尾
本文以 Mathematica 通用数学计算软件为平台,针对串联机械臂的建模、规划和控制中的基本问题进行了仿真,差不多该说再见了。不过新的篇章即将展开 —— 移动机器人是另一个有趣的领域。与固定的机器人相比,移动机器人更有挑战性,因此也会出现新的问题,比如信息的获取和利用。未来将加入移动机器人仿真的功能,支持地面移动机器人的运动控制、环境约束下的运动规划、移动机械臂、多机器人避障、多机器人编队运动等,并讨论环境建模、导航与定位、SLAM、非完整约束、最优控制、轮式车辆和履带车辆的动力学模型以及地面力学模型、群集协同运动等问题,敬请期待哟!
9. 补充:Mathematica 的缺点
在笔者就读研究生期间,Matlab 的使用率颇高。每次参加答辩、听报告,看着同学或老师用 Matlab 制作的丑陋不堪的图表和动画,心中就想把 Matlab 的界面设计师枪毙十分钟。再加上呆板的函数定义和使用方式、缺乏对部分机器人仿真功能的支持,让我不得不寻找其它的替代软件。可是在网络发达的今天,我居然找不到稍微像点样的介绍机器人仿真的文章以及原理性代码,要么过于低端,要么是东拼西凑,于是想把自己的经验写出来,并公开代码。
就像 Matlab 有很多让人不爽的地方一样,Mathematica 用于机器人仿真同样存在一些缺陷。我们之前在碰撞检测部分已经提过,要想达到很快的检测速度就不得不使用简单的几何模型。虽然 Mathematica 的函数也经过了优化,但是只适用于需要较少计算次数的场合,在多次处理大量数据时还是比较慢。Mathematica 本身是用 C 语言写成的,如果某个函数被大量调用可以考虑用 C 语言写成动态链接库(dll),然后在 Mathematica 中调用,这就像 Matlab 中的 MEX 文件。
调试找错是个痛苦的过程,在 Mathematica 中更是这样。Mathematica 支持调试时设置中断,可惜使用起来不太方便。Mathematica 提供了一个专门用来开发调试的软件——Workbench,操作同样繁琐。在调试时,我只好使用 Print 函数打出中间计算结果来检查程序是否正确。Mathematica 缺少像 Matlab 一样的变量监控窗口可以实时看到变量的值,这在调试时显得很不方便(虽然在堆栈中可以监视局部变量)。所以,尽量不要在 Mathematica 中使用中断 Matlab 中如果出现语法错误,编译会中止并显示出错位置,但在 Mathematica 中却不会自动停止,它仿佛像推土机一样停不下来。一旦出错你通常只会在计算结果中看到一堆冗长的代码,却很难发现错在哪了。对于包含许多步的计算过程,调试起来可能很浪费时间,最好的方法是把代码切割成功能简单清晰的模块,挨个检验每个模块要比检验一堆纠缠不清的代码更容易。
---------------------
作者:robinvista
来源:CSDN
原文:https://blog.csdn.net/robinvista/article/details/70231205
放心!PLC不会被淘汰该学还得继续学。但有3点要重视
全球最先进机器人大集结
工业机器人控制系统架构介绍
搞了10年非标自动化设备,今天终于明白应该这样搞
都是搞PLC有人月薪5000但有人年薪30万,他们是怎么做到的
10万人正在用的PLC仿真学习软件
搞工业自动化不懂常用英语确实有点亏,以下是工业自动化常用英语
非标自动化设备开发流程
详细用西门子S71200作为AGV控制器的方案 |