[转]基于Mathematica的机器人仿真环境(机械臂篇)

无论你从事的是机器人研发还是教学科研,一款好用的仿真软件都能对你的工作起到很大的帮助。那么应该选择哪个软件呢?最方便的选择就是成熟的商业软件,例如Adams、Webots。你的钱不是白花的,商业软件功能强大又稳定,而且性能一般都经过了优化。可是再强大的商业软件也有设计不合理的地方,它们的算法基本都是“黑箱”,你想做一点更改都不行。从学习和研究的角度出发,最好选择代码可修改的开源或半开源软件。在论文数据库中检索一下就会发现,很多人都选择借助Matlab这个数学软件平台进行机器人的建模仿真[1][1]。这并不奇怪,因为Matlab具有优秀的数值计算和仿真能力,在它的基础上开发会很便利。如果你非要舍近求远,用C编写一套仿真软件,先不要说仿真结果如何显示,光是矩阵计算的实现细节就能让你焦头烂额。

1.获取机器人的外观模型

2.导入机器人的外观模型

用SolidWorks2014软件打开机器人的装配体文件,并选择“另存为”STL格式。然后打开当前页面下方的“选项”对话框,如下图。这里我们要设置三个地方:1.“品质”表示对模型的简化程度,如果你想实现非常逼真的效果,可以选择“精细”,但缺点是数据点很多导致文件很大、处理起来比较慢。一般选择“粗糙”就够了;2.勾选“不要转换STL输出数据到正的坐标空间”;3.不要勾选“在单一文件中保存装配体的所有连杆”。

小技巧STL格式是一种三维图形格式,被很多三维建模软件支持(Mathematica也支持,所以我们要保存为这个格式)。STL格式只存储三维图形的表面信息,而且是用很多的三角形对图形进行近似的表示。如果你的机器人外形比较简单(规则的几何体),那么得到的STL文件大小一般只有几十KB;可是如果外形很复杂(比如包含很多的孔洞、曲面),生成的STL文件会很大(几MB~几十MB)。对于一般配置的计算机,模型文件超过100KB用Mathematica处理起来就比较慢了。为了让仿真显示地更流畅,可以利用免费软件MeshLab对其进行化简,MeshLab通常能够在基本不改变外形的前提下将文件大小缩减为原来的十分之一甚至更多。MeshLab的安装和操作都是傻瓜式的,打开后进入如下图左所示的菜单中,出现右图的页面,这里的“Percentagereduction”表示缩减的百分比(1表示不缩减,0.1则表示缩减为原来的10%),设置好后点击Apply并保存即可。

然后在Mathematica中导入STL文件,使用的代码如下(假设这些STL文件保存在D:\MOTOMAN文件夹下):

这里我偷了个懒,为了少打些字,我把导出连杆的文件名改成了从1到9的数字(这个机械臂的装配体一共包含9个零件)。我们把导入的模型显示出来,效果如下图。使用的代码如下

Graphics3D[{frame3D,robotParts}]

说明:frame3D是三维坐标系的三个正交的轴(xyzxyz轴的颜色分别是RGBRGB)。在机器人领域会用到大量的坐标系及其变换,直接看数字总是不直观,不如将坐标系显示出来更方便。定义frame3D的代码如下,这个坐标系默认原点的位置在(0,0,0)(0,0,0),以后我们称这个坐标系为“全局坐标系”。

frame3D={RGBColor[#],Arrowheads[0.05],Arrow[Tube[{{0,0,0},#},0.01]]}&/@IdentityMatrix[3];

你可能会好奇:导入的零件数据是以什么样的格式储存的呢?用来存储机器人外形数据的robotParts变量包含一共9个零件的数据,假如你想看第一个零件(对应的是基座,它通常用来将机械臂固定在大地上),可以输入:

robotParts[[1]](*双层方括号中的数字表示对应第几个零件*)

运行后的输出结果是一堆由GraphicsComplex函数包裹着的数字,主要可分为两部分:第一部分是零件几何体所有顶点的三维坐标;第二部分是组成零件几何体的三角形(注意:构成每个三角形的三个顶点是第一部分点的序号,而不是坐标值)。我们可以用以下代码将其分别显示出来:

pts=robotParts[[1]][[1]];(*零件1的第一部分:顶点的三维坐标数据*)triangles=robotParts[[1]][[2]];(*零件1的第二部分:三角形面片*)trianglesBlue=triangles/.{EdgeForm[]->EdgeForm[Blue]};(*三角形的边显示为蓝色*)Graphics3D[{GraphicsComplex[pts,trianglesBlue],Red,Point[pts]}]

所有零件都成功地导入了,而且它们的相对位置也是正确的。你可能会问:机械臂为什么是处于“躺着”的姿势呢?这是由于零件是按照SolidWorks默认的坐标系(yy轴向上)绘制和装配的。而在Mathematica中默认的坐标系是zz轴向上。那么我们采用哪个坐标系呢?当然你可以任性而为,用哪个都可以。不过根据国家标准GBT16977-2005《工业机器人坐标系和运动命名原则》,机械臂基座坐标系的zz轴应该垂直于基座安装面(一般是水平地面)、指向为重力加速度的反方向(也就是垂直地面向上),xx轴指向机器人工作空间中心点的方向。制定国家标准的都是些经验丰富的专家老手,我们最好跟国标保持一致(国标的作图水平就不能提高点吗?这图怎么感觉像小学生画的)。

为了让机器人变成国标规定的姿势,需要旋转各个连杆。我们先想想应该怎么转:结合我们之前导入的图形,可以先绕全局坐标系的xx轴转90°90°,再绕全局坐标系的zz轴转90°90°。还有一种方法是:先绕全局坐标系的xx轴转90°90°(记这个旋转后的坐标系为AA),再绕AA的yy轴转90°90°。两种方法的效果是一样的,但是注意合成矩阵时乘法的顺序(见以下代码),不懂的同学可以看看文献[5][5]中的31~~33页。当然,转动是有正负之分的:将你的右手握住某个坐标轴,竖起大拇指,让大拇指和轴的正方向一致,这时四指所示的方向就是绕该轴转动的正方向。为此,我们定义旋转矩阵(两种定义效果一样):

{Xaxis,Yaxis,Zaxis}=IdentityMatrix[3];(*定义三个旋转轴*)rot=RotationMatrix[90Degree,Zaxis].RotationMatrix[90Degree,Xaxis];(*注意第二次变换是在左边乘*)rot=RotationMatrix[90Degree,Xaxis].RotationMatrix[90Degree,Yaxis];(*注意第二次变换是在右边乘*)

然后用rot矩阵旋转每个连杆(的坐标,即保存在第一部分robotParts[[i,1]]中的数据):

robotParts=Table[GraphicsComplex[rot.#&/@robotParts[[i,1]],robotParts[[i,2]]],{i,9}];

经过姿态变换后的机器人看起来舒服点了,只是有些苍白。为了给它点个性(也方便区分不同的零件或者称为连杆),我们给连杆设置一下颜色,代码如下。你可能注意到了,这里我没有使用循环去为9个连杆一个一个地设置颜色,而是把同类的元素(颜色)写在一起,然后再和连杆列表一起转置即可把颜色“分配”给各个连杆。这样做的好处就是代码比较简洁、清晰,以后我们会经常这么做。

colors={Gray,Cyan,Orange,Yellow,Gray,Green,Magenta,LightGreen,Pink};(*1~9各连杆的颜色*)robotPartsColored=Transpose[{colors,robotParts}];(*把颜色分配给各连杆*)Graphics3D[robotPartsColored]

说明:现在的机器人姿势(大臂竖直、小臂前伸)是6自由度机械臂的“零位”状态,我们将此时机械臂各关节的角度认为是0。一般机械臂上都有各关节的零点位置标记,用于指示各关节的零点。我们用控制器控制机械臂时,发出的角度指令都是相对于这个零点位置的。零点位置不是必须遵守的,你可以将任意的角度设置为零位,不过为了统一,最好用机械臂固有的零位——也就是当前姿势下各关节的角度。

3.运动学仿真

前面的工作只是让机械臂的模型显示出来。如果想让它动起来,那就要考虑运动学了。机器人这个学科听起来高大上(很多都停留在理论上),可实际上现在大多数工业机器人的控制方式还是比较低级的,它们只用到了运动学,高级一点的动力学很少用,更不要提智能了(它们要说自己有智能,我们家的洗衣机和电视机都要笑掉大牙了)。看来要使用机器人,运动学是必不可少的,所以我们先来实现运动学。在建立运动学模型之前我们需要了解机器人的机械结构。前面提到,MOTOMAN-ES165D是一个6自由度的串联机械臂。而6个自由度的机器人至少由7个连杆组成(其中要有一个连杆与大地固定,也就是基座)。可是我们导入的连杆有9个,多出来的2个连杆是弹簧缸(基座上黄色的圆筒)的组成部分。MOTOMAN-ES165D机器人能够抓持的最大负载是165公斤,弹簧缸的作用就是作为配重平衡掉一部分负载的重量,要不然前端的关节电机会有很大的负担。可是弹簧缸给我们的建模造成了麻烦,因为它导致“树形拓扑”以及存在“闭链”,这不太好处理。为此,我们先忽略掉弹簧缸。3.1连杆的局部坐标系

然后回到机器人的装配体中,在左侧的连杆树中展开每个连杆找到并选中其绘图坐标系的原点,然后点击上方菜单栏“评估”中的“测量”即可看到图中出现了一组坐标值(如下图所示),这就是连杆绘图坐标系的原点在全局坐标系(本文将全局坐标系定义为装配体的坐标系)中的位置。

我们记录下所有连杆的绘图坐标系的原点位置(除去弹簧缸的2个,注意SolidWorks中默认的单位是毫米,这里除以10001000是为了变换到Mathematica中使用的国际标准单位——米):

drawInGlobalSW={{0,0,0},{0,650,0},{-315,1800,285},{-53.7,1800,285},{0,2050,1510},{0,2050,1510},{0,2050,1720.5}}/1000;

因为我们是在SolidWorks中测量的位置,所以这些位置值还是相对于SolidWorks的坐标系(yy轴朝上),要变到zz轴朝上,方法仍然是乘以旋转矩阵rot:

drawInGlobal=Table[rot.i,{i,drawInGlobalSW}];

以后我们会经常对点的坐标进行各种各样的变换(平移、旋转),而且很多时候是用一个矩阵同时对很多个点的坐标进行变换(例如上面的例子),不如定义一个算子以简化代码。我们可以定义算子(其实是一个函数):

CircleDot[matrix_,vectors_]:=(matrix.#)&/@vectors;

所以前面的变换用我们自定义的算子表示如下(复制到Mathematica中后\[CircleDot]会变成一个Mathematica内部预留的图形符号⊙⊙,这个符号没有被占用,所以这里我征用了):

drawInGlobal=rot\[CircleDot]drawInGlobalSW;(*哈哈!写起来是不是简单多了*)

SetDirectory[NotebookDirectory[]]<

还记得吗?最开始我们导入机器人模型时,各连杆的位置都已经按照装配关系确定好了,所以它们的坐标也是相对于全局坐标系描述的。可是现在我们要让机械臂动起来(并且显示出来),这就需要移动这些坐标。为了方便起见,最好能将每个连杆的坐标表示在自己的绘图坐标系中,因为这样我们只需要移动绘图坐标系就行了,而各点的坐标相对它们所属的绘图坐标系是不动的。应该怎么做呢?很简单,将连杆的坐标减去绘图坐标系的原点在全局坐标系中的坐标即可:

partsName={"1.stl","2.stl","3.stl","6.stl","7.stl","8.stl","9.stl"};(*已经去除了组成弹簧缸的2个零件:4号和5号*)robotPartsGraphics=Import[#,"Graphics3D"]&/@partsName;robotParts=robotPartsGraphics[[;;,1]];robotParts=Table[GraphicsComplex[rot\[CircleDot]robotParts[[i,1]],robotParts[[i,2]]],{i,7}];robotParts=Table[GraphicsComplex[(#-drawInGlobal[[i]])&/@robotParts[[i,1]],robotParts[[i,2]]],{i,7}];colors={Gray,Cyan,Orange,Green,Magenta,Yellow,Pink};(*重新定义连杆的颜色*)robotPartsColored=Transpose@{colors,robotParts};

坐标移动后的连杆如下图所示(图中的坐标系是各个连杆自己的绘图坐标系,我没有对坐标转动,所以绘图坐标系和全局坐标系的姿态相同)。我们一开始从SolidWorks导出文件时是一次性地导出整个装配体的。其实,如果我们挨个打开各个连杆并且一个一个的导出这些连杆,那么得到数据就是相对于各自的绘图坐标系的,只不过这样稍微麻烦一点。

3.2利用旋量建立运动学模型

定义关节旋量的代码如下。其中相对旋量ξrξr用于迭代运动学计算,它的含义是当前连杆的转轴表示在前一个连杆坐标系中。

axesPtInGlobal=rot\[CircleDot]{{0,257,0},{-88,650,285},{-280.86,1800,285},{0,2050,1318},{134,2050,1510},{0,2050,1720.5}}/1000;axesPtInDraw=axesPtInGlobal-drawInGlobal[[1;;-2]];axesDirInDraw=axesDirInGlobal={Zaxis,Yaxis,Yaxis,Xaxis,Yaxis,Xaxis};\[Xi]r=Table[\[Omega]r[i]=axesDirInDraw[[i]];lr[i]=axesPtInDraw[[i]];Join[Cross[-\[Omega]r[i],lr[i]],\[Omega]r[i]],{i,n}];

我们对关节的相对运动进行了表示,然而要建立运动学还缺少一样东西:连杆间的初始相对位姿(初始的意思是机械臂处于“零位”的状态下)。零位下,我们将所有连杆的姿态都认为和全局坐标系一样,所以不用计算相对姿态了。至于它们的相对位置嘛,我们已经知道了绘图坐标系原点在全局坐标系中的坐标,两两相减就可以得到它们的相对位置了,很简单吧!(见下面的代码)

Do[g[L[i],L[i1],0]=PToH[drawInGlobal[[i1]]-drawInGlobal[[i]]],{i,n}];

其中,PToH函数能将位置向量转换为一个4×44×4齐次变换矩阵,这是借助RPToH函数实现的(RPToH函数就是Screws工具包[3][3]中的RPToHomogeneous函数),它可以将一个3×33×3旋转矩阵和3×13×1位移向量组合成一个4×44×4齐次变换矩阵。将旋转矩阵和位移向量合成为齐次变换矩阵是我们以后会经常用到的操作。类似的,也可以定义RToH函数将旋转矩阵生成对应的齐次变换矩阵,代码如下:

RToH[R_]:=RPToH[R,{0,0,0}]PToH[P_]:=RPToH[IdentityMatrix[3],P]

说明:本文中,用符号I表示全局坐标系(同时也是惯性坐标系);符号L[i]表示第ii个连杆,变量g[L[i],L[i1]]表示第i1i1个连杆相对于第ii个连杆的位姿矩阵(它是一个4×44×4齐次变换矩阵);变量g[I,L[i]]表示什么你肯定猜到了,它表示第ii个连杆相对于全局坐标系的位姿矩阵。如果不特别说明,本文总是用g(或者g开头的变量)表示一个(或一组)齐次变换矩阵,这是约定俗成的。

现在可以正式推导机械臂的运动学模型了。在使用机械臂时,大家一般只关心其最末端连杆的位姿,更确切的说,是最末端连杆的位姿与关节角度的关系。不过为了得到最末端连杆的位姿,我们需要计算中间所有连杆的位姿。这里利用相邻连杆的迭代关系——每个连杆的位姿依赖前一个连杆的位姿——来提升计算效率。所以,可以定义机械臂所有连杆的运动学函数为:

robotPartsKinematics[configuration_]:=Module[{q,g2To7},{g[I,L[1]],q}=configuration;g2To7=Table[g[L[i],L[i1]]=TwistExp[\[Xi]r[[i]],q[[i]]].g[L[i],L[i1],0];g[I,L[i1]]=g[I,L[i]].g[L[i],L[i1]],{i,n}];Join[{g[I,L[1]]},g2To7]]

robotPartsKinematics函数的输入是:基座的位姿矩阵g[I,L[1]]和所有关节的角度向量q∈R6∈R6,这组变量完整地描述了一个串联机械臂的位置和姿势(用机器人中的专业术语应该叫“构型”:configuration,注意不要翻译为“配置”),而输出则是所有连杆相对于全局坐标系的4×44×4位姿矩阵(即g[I,L[i]],其中i=1~7)。其中,TwistExp函数来自于Screws工具包[3][3],作用是构造旋量的矩阵指数。

说明:在大多数的机器人教科书中,连杆的记号是从0开始的,也就是说将基座记为0号连杆,然后是1号连杆,最末端的连杆是n1n1号(假设机械臂的自由度是nn);而关节的记号是从1开始,也就是说1号关节连接0号连杆和1号连杆。这样标记的好处是记号一致,推导公式或编程时不容易出错:比如说我们计算第ii个连杆的速度时要利用第ii个关节的转动速度。可是本文中连杆的记号是从1开始的(基座标记为1号连杆),我们保留0号标记是为了以后将机械臂扩展到装在移动基座(比如一个AGV小车)的情况,这时0号就用来表示移动基座。

可以看到,只要定义好关节旋量,建立运动学模型非常简单。可是这样得到的运动学模型对不对呢?我们来检验一下。借助Manipulate函数,可以直接改变机械臂的各关节角度,并直观地查看机械臂姿势(应该叫构型了哦)的变化,如以下动画所示。可以看到,机械臂各连杆的运动符合我们设置的关节值,这说明运动学模型是正确的。

Manipulate[q={##}[[;;,1,1]];gs=robotPartsKinematics[{IdentityMatrix[4],q}];Graphics3D[{MapThread[move3D,{robotPartsColored,gs}]},PlotRange->{{-2,3},{-2,3},{0,3}}],##,ControlPlacement->Up]&@@Table[{{qt[i],0},-Pi,Pi,0.1},{i,n}]

move3D[shape_,g_]:=GeometricTransformation[shape,TransformationFunction[g]]

robotMoveToQ[q_]:=Module[{gs},gs=robotPartsKinematics[{IdentityMatrix[4],q}];MapThread[move3D,{robotPartsColored,gs}]]

而且,函数的使用也很灵活。例如,我们可以将不同构型下的机械臂同时显示出来,只需要两行代码,如下:

qs=Table[{a-Pi/2,a/2,-a/2Pi/6,0,0,0},{a,0,Pi,0.2}];(*生成关节变量列表,即不同构型*)Graphics3D[{robotMoveToQ/@qs}]

4.逆运动学仿真

借助运动学,我们成功地通过改变关节角度实现了对机械臂的控制。当然这没什么值得炫耀的,本质上不过是矩阵相乘罢了。本节我们考虑一个更有挑战性也更好玩的问题。如果告诉你所有连杆(局部坐标系)的位姿,你能不能算出机械臂的各个关节角来?你一定会说这很简单,求一下反三角函数就行了。但是实际应用时经常会遇到比这个难一些的问题:只告诉你机械臂最后一个连杆的位姿,如何得到各关节的角度?这个问题被称为“逆运动学”。RoboticToolbox工具箱[2][2]中给出了两个解逆运动学问题的函数:ikine和ikine6s,分别是数值解法和符号解析解法,本文我们也用两种方式解决逆运动学问题。

4.1数值解法之——解方程

上一节的运动学函数robotPartsKinematics能得到所有连杆的位姿。大多数时候,人们只关心最后一个连杆的位姿(因为它上面要装载操作工具),即Last@robotPartsKinematics[{IdentityMatrix[4],q}](注意q是一个六维向量,即q=(q1,q2,q3,q4,q5,q6q1,q2,q3,q4,q5,q6)),结果如下图所示(另存为可以看大图)。这里关节角没有设置数值,因此得到的是符号解,有些长哦。这也是为什么机器人领域经常使用三角函数缩写的原因:比如把cos(q1)cos(q1)记为c1c1。在[4][4]中提供了一个函数SimplifyTrigNotation,可以用来对下式进行缩写。

如果我们想让机械臂末端(连杆)到达某个(已知的)位姿gt,也就是让上面的矩阵等于这个位姿矩阵:

Last@robotPartsKinematics[{IdentityMatrix[4],{q1,q2,q3,q4,q5,q6}}]=gt(*逆运动学方程*)

说明:在求解逆运动学方程前还需要解决一个小问题:如何在Mathematica中表示一个期望的目标位姿gt呢?Mathematica提供了RollPitchYawMatrix函数和EulerMatrix函数用来表示三维转动(你用哪个都可以),然后利用前面的RPToH函数合成为位姿矩阵gt即可,示例代码如下。其中,cuboid函数用于绘制一个长方体。如果你使用Matlab,那我要可怜你了。因为Matlab没有绘制长方体的函数,一切你都要自己画。而Mathematica定义了一些常用几何图形,可以直接用。

cuboid[center_,dim_]:=Cuboid[center-dim/2,centerdim/2];(*输入center是长方体的几何中心,dim是长方体的长宽高*)

object=cuboid[{0,0,0},{0.3,0.2,0.05}];Manipulate[gt=RPToH[EulerMatrix[{\[Alpha],\[Beta],\[Gamma]}],{x,y,z}];Graphics3D[{Yellow,move3D[{frame3D,object},gt]},PlotRange->0.5],Grid[{{Control[{{x,0},-0.5,0.5,0.01}],Control[{{\[Alpha],0},-Pi,Pi,0.1}]},{Control[{{y,0},-0.5,0.5,0.01}],Control[{{\[Beta],0},-Pi,Pi,0.1}]},{Control[{{z,0},-0.5,0.5,0.01}],Control[{{\[Gamma],0},-Pi,Pi,0.1}]}}],TrackedSymbols:>True]

不过这个方程组是由4×44×4齐次变换矩阵得到的,里面有4×4=164×4=16个方程,去掉最后一列(0,0,0,1)(0,0,0,1)对应的44个恒等式还有1212个方程,超过了未知量(66个)的个数,这是因为3×33×3旋转矩阵的各项不是独立的,因此要舍去一部分。该保留哪三项呢?只要不选择同一行或同一列的三项就可以了,这里我保留了(1,2),(2,3),(3,3)(1,2),(2,3),(3,3)三项(但不是全都对,有时你需要试试其它项)。

Manipulate[gts=Last@robotPartsKinematics[{IdentityMatrix[4],{q1,q2,q3,q4,q5,q6}}];gt=RPToH[RollPitchYawMatrix[{\[Alpha],\[Beta],\[Gamma]}],{x,y,z}];Quiet[q={q1,q2,q3,q4,q5,q6}/.FindRoot[gts[[1,4]]==gt[[1,4]]&>s[[2,4]]==gt[[2,4]]&>s[[3,4]]==gt[[3,4]]&>s[[1,2]]==gt[[1,2]]&>s[[2,3]]==gt[[2,3]]&>s[[3,3]]==gt[[3,3]],{q1,0},{q2,0},{q3,0},{q4,0.3},{q5,0.3},{q6,0.3}]];planeXY={FaceForm[],EdgeForm[Thickness[0.005]],InfinitePlane[{x,y,z},{{0,1,0},{1,0,0}}],InfinitePlane[{x,y,z},{{0,1,0},{0,0,1}}]};lines={Red,Thickness[0.0012],Line[{{x,y,z}{100,0,0},{x,y,z}{-100,0,0}}],Line[{{x,y,z}{0,100,0},{x,y,z}{0,-100,0}}],Line[{{x,y,z}{0,0,100},{x,y,z}{0,0,-100}}]};Graphics3D[{planeXY,lines,robotMoveToQ[q],move3D[frame3D,g[I,L[7]]]},PlotRange->{{-1.5,2.5},{-2.5,2.5},{0,3}}],Grid[{{Control[{{x,1.3},-2,3,0.1}],Control[{{y,0},-2,2,0.1}]},{Control[{{z,2},0,3,0.1}],Control[{{\[Alpha],0},0,Pi,0.1}]},{Control[{{\[Beta],0},0,Pi,0.1}],Control[{{\[Gamma],0},0,Pi,0.1}]}}],TrackedSymbols:>True]

同样借助Manipulate函数改变值的大小,试验的效果见下图。

4.2数值解法之——迭代法

解方程的方法很多,下面我们换一种思路求解逆运动学方程,其思想来自于[2][2](英文版187页),代码如下:

forwardKinematicsJacobian函数用于计算(空间)雅可比矩阵和最后一个连杆的位姿,它修改自Screws工具包[3][3]。逆运动学计算函数inverseKinematics的输入是期望的末端连杆位姿gt,迭代的初始角度q0,以及误差阈值errorthreshold(默认值为0.00010.0001)。其中的modToPiPi函数(实现代码如下)用于将角度值转换到π~ππ~π的范围之间。这里为什么需要modToPiPi函数呢?因为角度是个小妖精,如果我们不盯紧它,它可能会时不时的捣乱。从外部看,机械臂的一个转动关节位于角度π/3π/3和角度π/32ππ/32π没什么区别。可是如果我们放任角度这样随意跳变,会导致轨迹不连续,这样机械臂在跟踪轨迹时就会出现麻烦。

modToPiPi[angle_]:=Module[{a=Mod[angle,2.0*Pi]},If[Re[a]>=Pi,a-2.0*Pi,a]]SetAttributes[modToPiPi,Listable];

其中,Ad函数就是Screws工具包[3][3]中的RigidAdjoint函数,它表示一个齐次变换矩阵的伴随变换(AdjointTransformation),diagF函数用于将多个矩阵合成为块对角矩阵,实现代码如下:

diagF=SparseArray[Band[{1,1}]->{##}]&(*用法为A={{1,2},{3,4}};B={{5,6},{7,8}};diagF[A,B]//MatrixForm*)

HToR函数和HToP函数分别用于从一个齐次变换矩阵中取出旋转矩阵(RR)和位移向量(PP),代码如下。

HToR[g_]:=Module[{n=(Dimensions[g])[[1]]-1},g[[1;;n,1;;n]]]HToP[g_]:=Module[{n=(Dimensions[g])[[1]]-1},g[[1;;n,n1]]]

我们以后会用到很多矩阵操作(比如转置、求逆),而Mathematica的函数名太长,为了写起来方便,我定义了简写的转置和求逆函数,代码如下:

T[g_]:=Transpose[g]Iv[g_]:=Inverse[g]

我们想让机械臂(的末端)依次到达一些空间点(这些点可能是机械臂运动时要经过的)。为此首先生成一些三维空间中的点:

Clear[x,y];pts2D=Table[{Sin[i],Cos[i]}/1.4,{i,0,4Pi,Pi/400}];(*先生成二维平面上的点,它们均匀地分布在一个圆上*)pts3D=pts2D/.{x_,y_}->{1.721,x,y1.4};(*再将二维坐标变换成三维坐标*)Graphics3D[Point[pts3D]]

然后调用逆运动学函数inverseKinematics挨个计算不同点处的关节值,代码如下:

gStars=PToH/@pts3D;(*将三维点的坐标转换成齐次变换矩阵,转动部分始终不变*)q=ConstantArray[0,n];(*inverseKinematics函数包含一个迭代过程,因此需要提供一个初始值*)g[I,L[7],0]=(robotPartsKinematics[{IdentityMatrix[4],q}];g[I,L[7]]);(*forwardKinematicsJacobian函数需要零位状态下的末端连杆位姿*)qs=Table[q=inverseKinematics[gt,q],{gt,gStars}];(*依次遍历所有点,我们用每次计算得到的q作为下一次迭代的初始值,这样迭代速度更快*)

计算结果qs中存储了机械臂到达不同点处的关节向量,如果以后我们想让机械臂跟踪这个向量序列,可以对其插值得到连续的关节函数,这是靠Interpolation函数实现的,代码如下。关于Interpolation函数我要啰嗦几句,因为以后我们可能会经常用到它。对于机械臂的每个关节来说,Interpolation得到的是一个插值函数(InterpolatingFunction),更确切地说是“Hermite多项式”或“Spline样条”插值函数。插值函数与其它的纯函数没什么区别,比如说我们可以对它求导、求积分。对这6个关节的插值函数求导就能得到关节速度和加速度函数:

画出插值后各关节的角度、角速度、角加速度的变化趋势,如下图。能看到有两个关节角速度变化剧烈,因此,理论上这个曲线不适合让机器人跟踪。

pq=Plot[Evaluate@Table[qfun[i][x],{i,n}],{x,0,time},PlotRange->All];pdq=Plot[Evaluate@Table[dqfun[i][x],{i,n}],{x,0,time},PlotRange->All];pddq=Plot[Evaluate@Table[ddqfun[i][x],{i,n}],{x,0,time},PlotRange->All];GraphicsGrid[{{pq,pdq,pddq}}]

4.3雅克比矩阵的零空间

对于本文中的6自由度机械臂,由于它不是冗余的,所以大多数时候计算零空间会得到空(也就是说不存在零空间)。为了形象地展示零空间的效果,我不得已只用了平移的部分。以下代码展示了机械臂在(平移)零空间中的一种运动,如下图所示。可以看到,不管机械臂如何运动,末端连杆(局部坐标系)的位置始终不动(但是它的姿态会改变,矩阵mask的作用就是滤掉转动分量,只剩下沿x、y、zx、y、z轴的平移运动)。BodyJacobian函数用于计算本体雅可比矩阵,它也来自于Screws工具包[3][3]。零空间是个线性空间,如果我们知道它的一组基向量,通过线性组合能够得到任意的(速度)向量。NullSpace函数返回的刚好就是构成矩阵的零空间的一组基向量。通过对这组基向量线性组合即可得到速度向量,其使机械臂末端始终不动。下面的例子中使用的组合系数都是1,也就是所有基向量相加(向量相加就是对应元素相加,由Total函数完成)。由于雅可比矩阵是机械臂构型q的函数,所以机械臂的构型一旦改变了,我们就要重新计算它的雅可比矩阵。如果还不理解,可以随时显示出dq的值,然后计算Jgm.dq看看结果是不是零。如果是零就说明dq在零空间里。

q=ConstantArray[0,n];dt=0.05;g[I,L[7],0]=Last@robotPartsKinematics[{IdentityMatrix[4],q}];{xl,yl,zl,xr,yr,zr}=IdentityMatrix[6];mask={xl,yl,zl};Animate[Jb=BodyJacobian[T@{\[Xi]a,q},g[I,L[7],0]];gIL7=Last@robotPartsKinematics[{IdentityMatrix[4],q}];Jg=diagF[HToR[gIL7],HToR[gIL7]].Jb;Jgm=mask.Jg;dq=Total[NullSpace[Jgm]];(*速度定义为零空间的一种线性组合方式,你可以试试其它线性组合*)q=qdq*dt;Graphics3D[{robotMoveToQ[q],move3D[frame3D,g[I,L[7],0]]}],{i,1,1000,1}]

5.碰撞检测

RegionDisjoint函数可以用于二维几何图形,也可以用于三维几何体,甚至可以用于非凸的几何图形或几何体,如下面的例子所示。例子代码如下,其中使用了Locator控件。

Manipulate[pts={{0.95,0.31},{0.36,-0.12},{0.59,-0.81},{0.,-0.38},{-0.59,-0.81},{-0.36,-0.12},{-0.95,0.31},{-0.22,0.31},{0.,1.},{0.22,0.31},{0.95,0.31}};obstacle1=Disk[pt1,1];obstacle2=Polygon[pt2#&/@pts];color=If[RegionDisjoint[obstacle1,obstacle2],Green,Red];Graphics[{EdgeForm[Black],color,obstacle1,obstacle2},PlotRange->3],{{pt1,{-1,-1}},Locator},{{pt2,{1,1}},Locator}]

robotPartsCH=Table[pts=robotParts[[i,1]];poly=robotParts[[i,2,2,1]];R=ConvexHullMesh[pts];pts=MeshCoordinates[R];poly=MeshCells[R,2];R=MeshRegion[pts,poly];Export["D:\\MOTOMANCH\\"<>partsName[[i]],R];GraphicsComplex[pts,poly],{i,7}];Graphics3D[robotPartsCH]

我们检验一下机械臂和外部障碍物的碰撞检测,至于机械臂连杆之间的碰撞我们暂时不考虑。代码如下,效果如下图所示。

Manipulate[Robs=cuboid[{1.3,0,0.5},{0.5,0.5,1.0}];(*障碍物,一个长方体*)gs=robotPartsKinematics[{IdentityMatrix[4],{q1,q2,q3,q4,q5,q6}}];Rparts=Table[MeshRegion[TransPt[gs[[i]]]/@robotParts[[i,1]],robotParts[[i,2,2]]],{i,7}];collisionQ=And@@(RegionDisjoint[Robs,#]&/@Rparts);color=If[collisionQ,Black,Red];text=If[collisionQ,"哈哈,没事","啊...碰撞了!"];Graphics3D[{Gray,Robs,Text[Style[text,FontSize->20,FontFamily->"黑体",FontColor->color],{-0.5,1,1.5}],{MapThread[move3D,{robotPartsColored,gs}]}},plotOptions],Grid[{{Control[{{q1,0},-Pi,Pi,0.1}],Control[{{q2,0},-Pi,Pi,0.1}]},{Control[{{q3,0},-Pi,Pi,0.1}],Control[{{q4,0},-Pi,Pi,0.1}]},{Control[{{q5,0},-Pi,Pi,0.1}],Control[{{q6,0},-Pi,Pi,0.1}]}}],TrackedSymbols:>True]

其中,TransPt[g][pt3D]函数的功能是用齐次变换矩阵g对三维向量(点)pt3D做变换,定义如下:

TransPt[g_][pt3D_]:=Module[{homogeneousPt},homogeneousPt=Join[pt3D,{1.0}];homogeneousPt=g.homogeneousPt;homogeneousPt[[1;;3]]]

6.轨迹规划

轨迹规划的目的是得到机器人的参考运动轨迹,然后机器人再跟踪这条轨迹完成最终的动作。轨迹规划是机器人研究领域非常重要的一部分。机器人要干活就离不开运动,可是该如何运动呢?像搭积木、叠衣服、拧螺钉这样的动作对人类来说轻而易举,可要是让机器人来实现就非常困难。工业机器人既没有会思考的大脑,也缺少观察世界的眼睛(又瞎又傻),要让它们完全自主运动真是太难为它们了。它们所有的运动都是人教给它的。你可以把机器人想象成木偶,木偶看起来像是有灵魂的生物,但实际上它的运动都是人灌输的,木偶只会死板地按照人的控制运动,自己没有任何主动性,只是行尸走肉罢了。实际工厂中,是由工程师操作着控制面板,一点点调节机械臂的各个关节角度,让它到达某个位置。控制程序会记录机械臂的角度变化,只要工程师示教一次,机械臂就能精确而忠实地重复无数次。不过这种不得已而为之的方法实在是太笨了,如果有一种方法能够自动根据任务生成机器人的参考轨迹多好,下面我们将介绍一种常用的轨迹规划方法。6.1路径、轨迹——傻傻分不清楚

路径轨迹

如果我们画出红色和黑色轨迹的x,y,zx,y,z坐标分量,就会看到它们从同一位置出发,又在另一个位置碰头,却经历了不同的过程,如下图所示(注意红黑两组曲线的开始和结尾)。

制作上面的轨迹需要以下几个步骤:

1.首先随机生成一些三维空间中的点。

pts=RandomReal[{-1,1},{6,3}];(*6个三维坐标点*)

2.然后利用BSplineFunction函数对点插值。

bfun=BSplineFunction[pts];

所得到的bfun是一个(B样条)插值函数,它的自变量的取值范围是0~10~1,你可以用ParametricPlot3D[bfun[t],{t,0,1}]画出这条曲线。

3.二次插值。我们虽然得到了插值函数,但它是一个向量值函数,难以进一步处理(比如求积分、微分)。所以,我们需要在bfun函数的基础上再处理。首先得到bfun函数图像上若干离散点(按照0.0010.001的间隔取):

bfpts=bfun/@Range[0,1,0.001];

然后分别对xyzxyz坐标分量进行单独插值(这里我同样将自变量的取值范围设定在0~10~1之间):

nb=Length[bfpts];ifunx=Interpolation[Transpose[{Range[0,1,1/(nb-1)],bfpts[[;;,1]]}]];ifuny=Interpolation[Transpose[{Range[0,1,1/(nb-1)],bfpts[[;;,2]]}]];ifunz=Interpolation[Transpose[{Range[0,1,1/(nb-1)],bfpts[[;;,3]]}]];

并定义一个新的插值函数为各分量的合成。这样我们就人为生成了一段轨迹(或者说,是一个向量值函数)。

ifun[t_]:={ifunx[t],ifuny[t],ifunz[t]}

我们能对这段轨迹做什么呢?●可以计算它的弧长,代码如下:

ArcLength[ifun[t],{t,0,1}]

●既然可以计算弧长,就能用弧长对这条曲线重新参数化(我以前在学高等数学时,一直想不通怎么用弧长对一个曲线参数化,现在通过编程实践就很容易理解了):

arcLs=Table[ArcLength[Line[bfpts[[1;;i]]]],{i,Length[bfpts]}]/ArcLength[Line[bfpts]];ifunArcx=Interpolation[Transpose[{arcLs,bfpts[[;;,1]]}]];ifunArcy=Interpolation[Transpose[{arcLs,bfpts[[;;,2]]}]];ifunArcz=Interpolation[Transpose[{arcLs,bfpts[[;;,3]]}]];ifunArc[t_]:={ifunArcx[t],ifunArcy[t],ifunArcz[t]}

我们可以观察两种参数化的轨迹的图像:

Animate[ParametricPlot3D[{ifun[t],ifunArc[t]},{t,0,end},PlotStyle->{{Thick,Black},{Thin,Dashed,Red}},PlotRange->1],{end,0.01,1,0.01}]

basis=Last@FrenetSerretSystem[ifun[x],x];p1=ParametricPlot3D[ifun[t],{t,0,1},PlotRange->1];Manipulate[pt=ifun[t];tangent=Arrow[Tube[{pt,pt(basis[[1]]/.x->t)/3}]];normal=Arrow[Tube[{pt,pt(basis[[2]]/.x->t)/3}]];binormal=Arrow[Tube[{pt,pt(basis[[3]]/.x->t)/3}]];p2=Graphics3D[{Arrowheads[0.03],Red,tangent,Green,normal,Blue,binormal}];Show[p1,p2],{t,0,1,Appearance->{"Open"}}]

还可以制作任意你想要的路径,例如制作一条“文字轮廓”路径的代码如下:

text=Text[Style["欢",Bold]];tg=DiscretizeGraphics[text,_Text,MaxCellMeasure->0.005];(*数值越小,点越密*)pts=MeshCoordinates[tg];pts=pts[[Last[FindShortestTour[pts]]]];

然后将其显示出来。这里得到的是二维点的坐标,要想让我们的机械臂跟踪,需要将其转换为三维坐标,这很简单,你可以直接用替换命令:pts3D=pts/.{x_,y_}->{x,y,1.5}。

Manipulate[Graphics[{Line[pts[[1;;i]]]},PlotRange->10,GridLines->Automatic],{i,1,Length[pts],1}]

6.2轨迹规划方法

(*RRT示例:此段程序不依赖任何自定义函数,可独立运行。这是我能想到的最短的RRT演示代码了*)step=3;maxIters=2000;start={50,50};range={0,100};pts={start};lines={{start,start}};obstaclePts={{20,1},{25,1},{25,25},{-25,25},{-25,-25},{25,-25},{25,-1},{20,-1},{20,-20},{-20,-20},{-20,20},{20,20}}50;Do[randomPt=RandomReal[range,2];{nearestPt}=Nearest[pts,randomPt,1];grownPt=nearestPtstep*Normalize[randomPt-nearestPt];If[!Graphics`Mesh`IntersectQ[{Line[{nearestPt,grownPt}],Polygon[obstaclePts]}],AppendTo[lines,{nearestPt,grownPt}];AppendTo[pts,grownPt]],{maxIters}];Animate[Graphics[{Polygon[obstaclePts],Line[lines[[1;;i]]],Blue,PointSize[0.004],Point[pts[[1;;i]]],Red,Disk[start,0.7]},PlotRange->{range,range}],{i,1,Length[pts]-1,1}]

RRT探索空间的能力还是不错的,例如下图左所示的例子,障碍物多而且杂乱(借助Mathematica本身具有的强大函数库,实现这个例子所需的所有代码应该不会超过30行)。还有没有环境能难住RRT呢?下图右所示的迷宫对RRT就是个挑战。这个时候空间被分割得非常严重,RRT显得有些力不从心了,可见随机策略不是什么时候都有效的。“随机”使得RRT有很强的探索能力。但是成也萧何败也萧何,“随机”也导致RRT很盲目,像个无头苍蝇一样到处乱撞。如果机器人对环境一无所知,那么采用随机的策略可以接受。可实际情况是,机器人对于它的工作环境多多少少是知道一些的(即使不是完全知道)。我的博客一直强调信息对于机器人的重要性。这些已知的信息就可以用来改进算法。一个改进的办法就是给它一双“慧眼”:在势场法中,势函数携带了障碍物和目标的信息,如果能把这个信息告诉RRT,让它在探索空间时有倾向地沿着势场的方向前进会更好。这样,RRT出色的探索能力刚好可以弥补势场法容易陷入局部极小值的缺点。

将RRT方法用在机械臂上的效果如下图所示(绿色表示目标状态)。我设置了4个障碍物(其中一个是大地),这对机械臂是个小小的挑战。由于我们生活在三维空间,没办法看到六维关节空间,所以我把六维关节空间拆成了两个三维空间,分别对应前三个关节和后三个关节(严格来说,六维转动关节空间不是一个欧式空间,它是个流形,不过这里我们不必纠结这个差别):

正式RRT的主程序代码如下。我将RRT树定义为由节点列表nodes和树枝列表edges组成,即RRTtree={nodes,edges}。其中节点列表nodes存储树中所有节点(每个节点都是一个6维向量,表示机械臂的关节值),树枝列表edges中存储所有树枝,树枝定义为两个节点的代号(节点的代号定义为节点被添加到树中的顺序。例如,添加新节点时树中已经有4个节点了,那么新节点的代号就是5)。

obsCenters={{0,0,-0.15},{1.4,-0.8,0.5},{1,1,0.7},{0.5,0,2.3}};obsDims={{8,8,0.2},{0.5,0.8,1.0},{0.7,0.3,1.4},{3,0.1,0.9}};Robs=MapThread[cuboid,{obsCenters,obsDims}];(*定义4个长方体障碍物*)step=0.2;(*树枝每次生长的长度,这里简单设置为固定值*)q0={-1.54,0.76,0.66,-1.14,-1.44,0};(*起点*)qt={1.76,0.76,0.46,0,0.36,0};(*目标点*)nodes={q0};(*以起点q0作为种子*)edges={};(*树枝初始为空*)RRTtree={nodes,edges};(*树的初始化由节点和树枝组成,它是全局变量*)maxIters=2000;(*最大迭代步数*)jointLims={{-Pi,Pi},{-Pi/2,Pi/2},{-Pi,1.45},{-Pi,Pi},{-2,2},{-Pi,Pi}};(*关节极限范围,有些关节值不可取*)qRandList=RandomPoint[Cuboid[ConstantArray[-Pi,n],ConstantArray[Pi,n]],maxIters,jointLims];(*一次性生成所有的随机点*)Do[nodes=RRTtree[[1]];If[Min[Norm/@((-qt#)&/@nodes)]<0.01,Print["哈哈,到达目标了!"];Break[]];qRand=RandomChoice[{qRandList[[i]],qt}];(*以50%的概率贪婪地着朝目标生长*)grow[qRand,step],{maxIters}];

collisionDetection[Rparts_,Robs_]:=And@@Flatten@Table[RegionDisjoint[i,#]&/@Rparts,{i,Robs}]

2.碰撞检测函数需要Region类型的变量,为此定义toRegion函数将几何体变换为Region类型,代码如下:

toRegion[q_]:=Module[{gs,Rparts},gs=robotPartsKinematics[{IdentityMatrix[4],q}];Rparts=Table[MeshRegion[TransPt[gs[[i]]]/@robotParts[[i,1]],robotParts[[i,2,2]]],{i,7}]]

3.向RRT树中添加节点和边的函数:

addNode[node_]:=Module[{},AppendTo[RRTtree[[1]],node];Length[RRTtree[[1]]]]addEdge[edge_]:=Module[{},AppendTo[RRTtree[[2]],edge];Length[RRTtree[[2]]]]

4.树枝朝着采样点生长(为了简单,只检测一点的碰撞情况):

grow[qRand_,step_:0.2]:=Module[{qNearestIdx,qNearest,growAngles},{qNearestIdx}=Nearest[nodes->Automatic,qRand,1];(*最近节点的代号*)qNearest=nodes[[qNearestIdx]];growDirection=Normalize[qRand-qNearest];qExpand=modToPiPi[qNeareststep*growDirection];(*生长*)Rrobot=toRegion[qExpand];If[collisionDetection[Rrobot,Robs],qNewIdx=addNode[qExpand];(*添加新节点,返回新节点的代号qNewIdx*)addEdge[{qNearestIdx,qNewIdx}]]]

下面的代码可以显示搜索到的(关节空间中的)路径。这条路径质量不高,如果用于机器人的轨迹跟踪还需要经过后期的平滑处理。

edges=RRTtree[[2]];targetIdx=edges[[-1,2]];qPath=backTrack[targetIdx];Animate[Graphics3D[{Robs,robotMoveToQ[qPath[[i]]]}],{i,1,Length[qPath],1}]

其中,backTrack函数用来从树中抽取出连接起点和目标点的路径:

backTrack[nodeIdx_]:=Module[{searchNodeIdx=nodeIdx,nodes=RRTtree[[1]],edges=RRTtree[[2]],searchNodePos,preNodeIdx,pathIdx={},pathCoords},Do[{searchNodePos}=FirstPosition[edges[[All,2]],searchNodeIdx];preNodeIdx=edges[[searchNodePos,1]];AppendTo[pathIdx,preNodeIdx];If[preNodeIdx==1,Break[],searchNodeIdx=preNodeIdx],{Length[edges]}];pathIdx=Reverse[pathIdx];pathCoords=nodes[[pathIdx]]]

7.动力学仿真

7.1惯性参数

然后在上方“评估”选项卡中单击“质量属性”就会弹出如下图所示的对话框。

skew[v_]:={{0,-v[[3]],v[[2]]},{v[[3]],0,-v[[1]]},{-v[[2]],v[[1]],0}}

这组公式来自于[6][6],我已经验证过了,是正确的(要想结果比较接近,这些惯性参数至少要取到小数点后5位,这依然是在“选项”页中设置)。我们得到了三个惯性张量,在动力学方程中我们应该使用哪个呢?下面的程序使用了ICIC,因为它是相对于绘图坐标系的,而我建立运动学时选择的局部坐标系就是绘图坐标系。(我以后在这里补充个单刚体动力学的例子)7.2正动力学仿真

如果给你一条轨迹,如何设计控制律让机械臂(末端)跟踪这条轨迹呢,控制律的跟踪效果怎么样呢?借助正动力学,我们就可以检验所设计的控制律。由于后面的程序所依赖的动力学推导过程[7][7]采用了相对自身的表示方法(也就是说每个连杆的速度、惯性、受力这些量都是相对于这个连杆自身局部坐标系描述的),旋量也是如此,为此需要重新定义旋量(代码如下)。其实旋量轴的方向没变(因为局部坐标系的姿态与全局坐标系一样),只是改变了轴上点的相对位置。

\[Xi]rb=Table[\[Omega]a[i]=axesDirInGlobal[[i]];la[i]=axesPtInGlobal[[i]]-drawInGlobal[[i1]];Join[Cross[-\[Omega]a[i],la[i]],\[Omega]a[i]],{i,n}];

其中,ad函数用于构造一个李代数的伴随表达形式,代码如下。(开始我们定义的关节旋量是李代数,这里连杆的本体速度也是一个李代数,但加速度却不是。实际上,要想把加速度搞明白可不是那么容易的事[8][8])

ad[\[Xi]_]:=Module[{w,v},v=skew[\[Xi][[1;;3]]];w=skew[\[Xi][[4;;6]]];Join[Join[w,v,2],Join[ConstantArray[0,{3,3}],w,2]]]

正动力学的输入是关节力矩,下面我们为关节力矩设置不同的值,看看机械臂的表现:●如果令关节力矩等于零(即\[Tau][k]=0.0),机械臂将在唯一的外力——重力作用下运动,如下图所示。

只受重力的情况下,机械臂的总能量应该守恒。我们可以动手计算一下机械臂的能量(由动能和重力势能组成,代码如下)。将仿真过程中每一时刻的能量计算出来并保存在一个列表中,再画出图像,如下图所示。可见能量几乎不变(轻微的变化是由积分误差导致的,如果步长取的再小一些,就更接近一条直线),这说明机械臂的总能量确实保持恒定,这也间接证实了正动力学代码的正确性。这个简单的事实让人很吃惊——虽然机械臂的运动看起来那么复杂,但是它的能量一直是不变的。从力和运动的角度看,机械臂的行为变化莫测,可是一旦切换到能量的角度,它居然那么简洁。机械臂的运动方程和能量有什么关系呢?聪明的拉格朗日就是从能量的角度去推导动力学方程的。

totalEnergy=Total@Table[1/2*V[i].\[ScriptCapitalM][i].V[i]mass[i]*gravityAcc*g[I,L[i]][[3,4]],{i,n1}];

●我们也可以让机械臂跟踪一条给定的轨迹,此时给定力矩为PD控制律:

Kp=10000;Kd=50;(*PD控制系数,需要自己试探选取*)\[Tau][k]=Kp(qfun[k][t]-q[[k]])Kd(dqfun[k][t]-dq[[k]]);

其中,qfun和dqfun是4.2节中定义的插值函数,这里用作机械臂要跟踪的(关节空间中的)参考轨迹。跟踪一个圆的效果如下图所示。

Manipulate[disturb[t_]:=peakExp[-fat(t-tp)^2];Plot[disturb[t],{t,0,10},PlotRange->All],{peak,50,300,0.1},{fat,0.5,40,0.1},{tp,0,10,0.1},TrackedSymbols:>True]

把扰动加到第二个关节的力矩上

\[Tau][k]=Kp(0-q[[k]])Kd(0-dq[[k]])If[k==2,disturb[t],0];

机械臂的响应如上右图所示,可见机械臂还是能回复零点姿势的。一开始机械臂有一个轻微的颤动,俗称“点头”。这是由于刚一开始机械臂的角度和角速度都为零,所以关节力矩也为零,导致机械臂缺少能够平衡重力的驱动力。在第5秒左右扰动出现,导致机械臂偏离了零点姿势,但在反馈控制作用下很快又回到了零点姿势。

7.3逆动力学仿真

输入力矩后,借助正动力学能得到关节角加速度,积分后可以得到角速度和角度。就像运动学和逆运动学的关系一样,逆动力学与正动力学刚好相反,它的用处是:如果告诉你机械臂的运动(也就是关节角度、角速度、角加速度),计算所需的关节力矩。

在重力作用下,机械臂保持“立正姿势”需要多大力矩呢?将初始状态设为0,经过逆动力学计算得到的答案是{0,38.1,38.1,0,2.06,0}{0,38.1,38.1,0,2.06,0}。如果把这个力矩再带入正动力学仿真就能看到机械臂保持静止不动,这证明我们的逆动力学模型也是正确的。8.结尾

本文以Mathematica通用数学计算软件为平台,针对串联机械臂的建模、规划和控制中的基本问题进行了仿真,差不多该说再见了。不过新的篇章即将展开——移动机器人是另一个有趣的领域。与固定的机器人相比,移动机器人更有挑战性,因此也会出现新的问题,比如信息的获取和利用。未来将加入移动机器人仿真的功能,支持地面移动机器人的运动控制、环境约束下的运动规划、移动机械臂、多机器人避障、多机器人编队运动等,并讨论环境建模、导航与定位、SLAM、非完整约束、最优控制、轮式车辆和履带车辆的动力学模型以及地面力学模型、群集协同运动等问题,敬请期待哟!

THE END
1.设计灵感弗兰卡的产品遍布全世界主要的设计之都。从米兰到巴黎,从柏林到纽约,弗兰卡家居解决方案以全球文化和室内设计风格为底蕴,打造经久不衰的整体厨房系列,快来深入探索吧。 设计一处隐逸避世的城市绿洲 米兰 在米兰的伊索拉区,Giulia沉醉于亲手打造的城市绿洲 —— 她受到 Bruno Munari 作品的启发,设计了这片都市中的世https://www.franke.com/cn/zh/home-solutions/design-inspiration.html
2.新创意设计叶锦添 “新东方主义”空间设计 最新动态 2024第6届阿联酋国际海报节获奖作品 自然界的素材库,这里来给你灵感 农副产品包装:承载城市的烟火气 马丁·帕尔的有趣的时尚摄影 ECO-X——英国游艇设计师芬恩·伯格的未来感设计 走心的茶几设计,每一款都是颜值担当! 版式设计关键:精通留白! 中国风二十四节气海报设计http://www.shejiol.com.cn/index.php
3.恒源祥9月28日上午,上海设计100+发布会暨“设计创新型城市”主题论坛在上海世博城市最佳实践区举行,发布会由上海市经济和信息化委主办,上海市人民政府副秘书长庄木弟、上海市经济和信息化委员会副主任葛东波、联合国教科文组织“创意城市网络”秘书长Denise Bax、国内外专家、企业家、设计师、政府相关负责人等出席本次发布会https://www.a8888.com/show_693_688_7737.htm
4.广州设计周—NARAR品牌全球发布会及中欧美学沙龙2024年12月6日—9日,NARAR品牌全球发布会及中欧美学沙龙,顺利在广州召开,活动特邀杨明博士、Guilia Barazzetta、Antonio Minonne、魏士能、Carlos Lorite Leza、Maurizio Burrato、Manuela Langehe、Francesca Magistri、STEFFEN、吴振宝、金永生、杨婷、谢迪生、潘悦等一众国内外知名设计师及行业专家,共同探讨设计趋势。 http://m.jiajux.cn/newsview153452.html
5.设计设计7月24日,兔宝宝将在浙江德清,迎来第十届南京林业大学“兔宝宝杯”家居国际设计大赛颁奖典礼暨首届兔宝宝产教融合发展论坛。[详情] 2024-07-23阅读量:4997 兔宝宝 第十届南京林业大学“兔宝宝杯”家居国际设计大赛颁奖典礼暨首届兔宝宝产教融合发展论坛活动圆满收官! https://www.chinayigui.com/news/t190771/
6.fotor懒设计官网fotor懒设计官网-在线设计网站神器,作图,封面,ps,海报,logo,ppt,抠图,简历,app免费下载 Fotor懒设计是全球最受欢迎的在线图片制作神器、平面设计工具和在线平面设计软件之一,提供海量海报,PPT,邀请函,banner,名片,logo等免费设计素材和模板,可在线一键稿定设计印刷,并能在线图片编辑 fotor懒设计官网: https://www.fhttps://168dmj.com/sites/lansheji.html
7.FloorplannerforfurnitureretailFloorplanner is the preferred 3D platform for furniture retail. Convert more sales via in-store or virtual consultations. A tool with a great ROI.https://www.floorplanner.com/retail
8.懒角落官网懒角落创立于2005年,至今我们一直秉承认真的造物态度,将简约、精致、柔和的元素与生活用品相结合,为热爱生活得人们精心打造产品,以收纳整理为业务发展核心,深入挖掘中国家庭生活中的每一个收纳需求,打造触手可及的美好生活。现有五金冠淘宝店、天猫旗舰店、京东旗舰店http://lhfx.cn/
9.懒角落官网懒角落创立于2005年, 主营收纳整理类目,覆盖用户家居生活的厨房、客厅、卧室、浴室等方方面面的场景。 现有五金冠淘宝店、天猫旗舰店、抖音旗舰店、 京东旗舰店、京东自营店、小红书旗舰店等各主要渠道的线上店铺, 同时在总部城市-汕头开设了三家实体店。 http://lcshopkids.com/index.html
10.别再翻了,大学私藏实用工具/网站全在这里了!你还在忍受PS漫长的加载?用懒设计吧,直接网页打开,不用下载安装,随时随地开始修图,任何简单的修图工作都能胜任。 fortor懒设计 网址:https://www.fotor.com.cn/photo-editor-beta/editor/basic 1 2. removebg 在线抠图 一键抠图,三秒完成PS几分钟的工作量。 https://blog.csdn.net/xia_yanbing/article/details/104634414
11.fortor懒设计手机版下载fortor懒人设计下载v1.0.20.26懒设计app是一款作图模板软件,用户可以通过该软件找到合适的模板,提供海量的专业素材,操作简单易上手,满足日常设计需求。功能强大,支持修改矢量图形各种参数。用户可自由DIY,让自己的l海报、logo更加出彩。需要的朋友快来下载体验吧! 软件亮点 1、严格选择数百万高清正版图片,用户可以在惰性设计中选择合适的图片。 2、https://www.qimu86.com/soft/56992.html
12.平面设计招聘平台大集合现在有越来越多的平面设计人才想要谋取一份不错的工作,但是苦于没有平台能让自己大展身手。毕竟对某些公司来说,他们更想要经验丰富的设计师。但是没有经验该怎么办呢?别急,我在这里整理出了一份平面设计招聘平台来解决大家的难题。 1.fortor懒设计网 https://modao.cc/design/graphic-design-recruitment-platforms.html
13.高一英语必修2必背句子(共2篇)琥珀屋的设计具有当时那个年代最为流行的奇特风格。 6.TheBrownswereatdinnerwhiletheirmaidwasatworkinthegarden. 布朗一家在吃饭时,他们的女仆在花园干活。4.Iamindependentanddon’tliketorelyonothersforhelp. 5.BeforeIhadthechancetogetfamiliarwiththeirmusic,thebandbrokeup. 6.Inadditiontobeingconfident,shehttps://www.oh100.com/a/201607/467868716155.html
14.Archivefor11月,2010Archive for 11月, 2010 | 2010年11月30日琢磨酷姐儿的月经问题 按照常规每隔十个月左右酷姐儿又来搜索Tor,结果发现,那也被墙了,要先下载那个软件才行,但是下载的网站打不开的。然后,就搜索怎么能和想象着小安努力设计和争取尽善尽美的良苦用心,娘便报以爽朗大笑和猛力表扬 以资鼓励小安开了学娘理https://anaisparis.wordpress.com/2010/11/
15.Rust语言2022年度回顾:开源生态发展语言&开发张汉东Rust for linux 支持状况 Linus Torvalds 于 2022 年 12 月 11 日发布了Linux Kernel 6.1,作为 2022 年的最终主线内核版本。出于多种原因,这个版本很重要TockOS 2.1 发布。 在 TockOS 迈向 2.0 时,许多核心的内核 API 被重新设计和重写。并且支持 11 个新的硬件平台,包括 RISC-V。 https://www.infoq.cn/article/hioW21XT8opvbip3hs5F
16.三十岁以上的男人才会用到的网站,不镐这是真的很多人不会设计LOGO,或设计的logo实在不符合需求,那么你不妨试试下面这个在线设计网站。 思维导图工具类 1、XMind 这是一款非常实用的商业思维导图软件 图片设计素材类 1、fortor 里面有很多图片素材,我们可以直接在上面修改图片,不需要用ps了。素材有很多,比如公众号的,简历也有,海报等等。 https://m.wang1314.com/doc/webapp/topic/21089178.html
17.设计师必去十大网站——博辉设计师3. www.art-kon-tor.de 4. www.ascom.ch/design 5. www.axenfeld.de 6. www.bf-design.de http://www.jmdesign.com.cn集美工业设计工作室 http://www.cida.org.tw台湾设计协会 http://www.haier.com海高设计公司 http://www.bestinfo.net.cn/bsti_sjss/北京工业设计中心 https://www.shejiben.com/sjs/1130780/log-29490-l129242.html
18.2020年,设计师最喜欢的89个书籍封面设计于2015年成立的文学中心(Literary Hub)是一个由美国的Grove Atlantic出版公司和图书网站Electric Literature合作创建的网站。自成立以来,网站每年都会对专业书籍封面设计师进行采访调查,请他们选出当年度最喜欢的封面设计。点击《2019年设计师最爱的书籍封面设计》,即可了解去年入选作品。 https://www.digitaling.com/articles/381059.html
19.殡仪馆建筑设计规范1.0.4 殡仪馆建筑设计除应符合本规范外,尚应符合国家现行的有关强制性标准的规定。 2 术语 2.0.1 殡仪馆funeral parlor 提供遗体处置、火化、悼念和骨灰寄存等部分或全部殡仪服务的场所。 2.0.2 业务区division tor business 洽谈并办理丧葬事宜的区域。 http://cdmzj.chengdu.gov.cn/cdmzj_gb/c121942/2012-07/24/content_b425a34add1740d984441e52eb45790c.shtml
20.全面提升中国重点非免疫规划疫苗覆盖率InnovationLabfors international health reputation. There are formidable challenges facing both the supply and demand side to increase the coverage of non-NIP vaccines in China. The primary issues facing the supply side include high prices, low production, and insufficient incentives for vaccinators. On the demandhttps://vaxlab.dukekunshan.edu.cn/paper/brief3/
21.gensler国际著名的建筑规划设计公司Gensler’s design for a vibrant mixed-use development, 1364ah Lifestyle Center, captures Riyadh’s existing essence as foundation for a fast-growing population. Want more of Gensler’s design insights? Sign up for ourdialogueNow newsletters to get regular updates sent directly to your inbox. https://www.gensler.com/
22.汉化Q&AwithBrandonSanderson布兰登·桑德森书迷问答(不【2014年TOR官网《王者之路》重读栏目的问答环节】 1. Michael Pye提问:在《光辉真言》发行期间,我曾留意到您指出《飓光志》实际上由两部长度为五卷本的系列构成。您是否想借此打消一些读者的畏惧心理?又或者这仅仅是您编写故事情节的初衷? 实话说,两者兼有。我确实想要谨慎行事,以免让那些读过《时光之轮》的入https://www.douban.com/note/277915261/
23.世界银行《企业调查手册和指南》中译版——上集理论基础职权范围(TOR)概述了调查项目的目标?(针对单个经济体或一组经济体)、不同层级中的目标访谈次数、调查方法、各方(供应商和世界银行集团)的角色和责任,项目交付成果,以及相关的时间表和付款时间。 企业分析部门(DECEA)跨项目使用的标准化职权范围(TOR),可以针对单个项目进行定制。 https://www.echinagov.com/news/343692.htm
24.StarWars:TheOldRepublic07.08.2024The Koth Vortena Cosplay Kit is now available! Read more Read more Download Game Installer Visit Community View Server Status Game Update 7.5 "Desperate Defiance" is now live! Padawan Sa’har Kateen asks for help in a risky mission to retrieve Darth Nul’s holocron, turning on Hehttps://www.swtor.com/
25.标小智LOGO设计神器公司logo设计在线制作生成器您的品牌设计总监 作为智能品牌创建平台,我们还将为您的 LOGO 定制生成配套名片,企业 VI,宣传海报,社交应用等全套的品牌资源。助你轻松实现品牌自动化! 智能LOGO 懂得品牌构建常识和设计规范的智能LOGO 引擎,使您的LOGO不但美观并且专业。 企业VI 从VI效果图到PPT/Word 模板,我们已为您准备了完整的品牌应用及推广资https://www.logosc.cn/
26.TheCommunityforOpenCollaborationandInnovationTheThe Eclipse Foundation provides our global community of individuals and organisations with a mature, scalable, and business-friendly environment for open source …https://www.eclipse.org/
27.Zabbix::TheEnterprisePick the tier that's right for your budget and monitoring requirements Optimal performance Every Zabbix Cloud instance is tuned to ensure the best possible performance 24/7 uptime Round-the-clock uptime makes sure that Zabbix keeps you and your team informed at all times https://www.zabbix.com/
28.印际Joshua Beck和Joana Gomes于2010年在美国的洛杉矶创立了CO-LAB事务所,并于2013年搬到墨西哥的图鲁姆。对他们而言,设计是一个漫长的累积过程,而不是“天才草图”一蹴而就的产物。 Joshua Beck and Joana Gomes founded co-lab in 2010 in Los Angeles. In 2013, the studio moved to Tulum. For them, designhttps://www.yinjispace.com/
29.TheJuliaProgrammingLanguageThe official website for the Julia Language. Julia is a language that is fast, dynamic, easy to use, and open source. Click here to learn more.https://julialang.org/
30.BreakingNews,LatestNewsandVideosCNNView the latest news and breaking news today for U.S., world, weather, entertainment, politics and health at CNN.com.https://cnn.com/
31.YouTube在YouTube 上畅享你喜爱的视频和音乐,上传原创内容并与亲朋好友和全世界观众分享你的视频。https://youtube.com/