在这里插入图片描述
我的毕设题目定为《基于机械臂触觉伺服的物体操控研究》,这个系列主要用于记录做毕设的过程。

前言:UR系列是优傲公司的代表产品,也是目前比较通用的产品级机械臂。所以我打算用该机械臂进行毕设的仿真实现。关于其运动学建模,网上有很多参考的博客及文献,但是很多都与《机器人学导论》第四版中的建模方式有所出入(不是指建模有问题,只是坐标系定义不太一致)。所以我按照自己的理解进行了运动学模型搭建,并使用webots进行了仿真验证

1. 正运动学

模型的坐标系建立如下图所示。
在这里插入图片描述

DH参数表

注:列表的长度参考了webots中的模型,可能跟实体的参数有所出入。

a α \alpha α d θ \theta θ
0 0 0.1625 θ 1
0 − π / 2  0 − π / 2 + θ 2
0.425 0 0 θ 3
0.3922 0 0.127 π / 2 + θ 4
0 π / 2  0.1 θ 5
0 − π / 2  0.1 θ 6

求解

将DH参数列表转为变换矩阵
[100a010000100001]

[10000cos(α)sin(α)00sin(α)cos(α)00001]
[10000100001d0001]
[cos(θ)sin(θ)00sin(θ)cos(θ)0000100001]


坐标系变换

代码实现

#include <vector>
#include <eigen3/Eigen/Dense>

std::vector<Vec4<T>> getDHparams(void){
	/* define link length */
	_worldBaseDistance = 0.1625;
	_shoulderLinkLength = 0.138;  
	_upperarmLinkLength = 0.425; 
	_forearmLinkLength = 0.3922;   
	_wrist1LinkLength = 0.127; 
	_wrist2LinkLength = 0.1;   
	_wrist3LinkLength = 0.1;
	    
	Vec4<T> dh_param;
	std::vector<Vec4<T>> DH_params;
	DH_params.clear();
	dh_param << 0,0, _worldBaseDistance, 0;                         DH_params.push_back(dh_param);
	dh_param << 0, -M_PI_2, 0, -M_PI_2;                             DH_params.push_back(dh_param);
	dh_param << _upperarmLinkLength, 0, 0, 0;                       DH_params.push_back(dh_param);
	dh_param << _forearmLinkLength, 0, _wrist1LinkLength, M_PI_2;   DH_params.push_back(dh_param);
	dh_param << 0, M_PI_2, _wrist2LinkLength, 0;                    DH_params.push_back(dh_param);
	dh_param << 0, -M_PI_2, _wrist3LinkLength, 0;                   DH_params.push_back(dh_param);
}

/**
 * @brief forward kinematics
 * @param q joints angle 1 - 6
 * @param p data order is x, y, z, pitch, roll, yaw
 */
template <typename T>
void forwardKinematics(const Vec6<T>& q, Vec6<T>* p){
	std::vector<Vec4<T>> DH_params = getDHparams();
    Mat4<T> Pro_Mat[4];
    Mat4<T> Trans_Mat = Mat4<T>::Identity();
    for(int i = 0; i < JOINTS_NUM; i ++){
        /* init */
        for(int j = 0; j < 4; j ++){
            Pro_Mat[j] = Mat4<T>::Identity();
        }
        /* create transform matrix refer to DH params */
        Mat2<T> rot;

        /* a trans */
        T a = DH_params[i][0];
        Pro_Mat[0](0, 3) = a;

        /* alpha trans */
        T alpha = DH_params[i][1];
        rot << cos(alpha), -sin(alpha), sin(alpha), cos(alpha);
        Pro_Mat[1].block(1, 1, 2, 2) = rot;

        /* d trans */
        T d = DH_params[i][2];
        Pro_Mat[2](2, 3) = d;

        /* theta trans */
        T theta = q[i] + DH_params[i][3];
        rot << cos(theta), -sin(theta), sin(theta), cos(theta);
        Pro_Mat[3].block(0, 0, 2, 2) = rot;
        
        /* combine trans matrix */
        Trans_Mat =  Trans_Mat * Pro_Mat[0] * Pro_Mat[1] * Pro_Mat[2] * Pro_Mat[3];

    }

    /* xyz */
    p->operator()(0) = Trans_Mat(0, 3);
    p->operator()(1) = Trans_Mat(1, 3);
    p->operator()(2) = Trans_Mat(2, 3);

    /* rpy */
    Mat3<T> rot = Trans_Mat.block(0,0,3,3);
    Vec3<T> rpy = ori::rotationMatrixToRPY(rot);
    p->operator()(3) = rpy(0);
    p->operator()(4) = rpy(1);
    p->operator()(5) = rpy(2);
}

2. 逆运动学

因为UR5e满足Pieper准则(末端三个关节轴相交于一点),所以其逆运动学具有封闭解。这里采用解析法进行求解。求解过程参考如下的文章,因为建模方式不同,求解的公式也有一定的区别,但思路一致。
UR机械臂正逆运动学求解

角度求解用到的公式(推导过程参考上述文章)
已知:
c o s ( θ ) p y − s i n ( θ ) p x = d 
可求得:

参数定义
[rxoxlxpxryoylypyrzozlzpz0001]


以下的推导公式中, c 1 c_1 c1表示 c o s ( θ 1 )  s 1 2 表示 s i n ( θ 1 + θ 2 ) 

求解 θ 1 \theta_1 θ1, θ 5 \theta_5 θ5, θ 6 \theta_6 θ6

根据变换矩阵公式可得以下关系
1 0 T − 1 ∗ T ∗ 6 5 T − 1 = 5 1 
等式左边
[rxc1c6oxc1s6+ryc6s1oys1s6lxc1+lys1oxc1c6oyc6s1rxc1s6rys1s6pxc1+pys1d6lxc1d6lys1ryc1c6oyc1s6rxc6s1+oxs1s6lyc1lxs1oxc1s6oyc1c6ryc1s6+rxs1s6pyc1pxs1d6lyc1+d6lxs1rzc6ozs6lzozc6rzs6pzd1d6lz0001]


等式右边
[c234c5c234s5s234a4s23+a3s2+d5s234s5c50d4s234c5s234s5c234a4c23+a3c2+d5c2340001]

求解 θ 1

利用等式左右两边第二行,第四列对应相等进行求解。

求解 θ 5

利用等式左右两边第二行,第二列对应相等进行求解。
l y c 1 − l x s 1 = c 5  

θ 5 = ± A c o s ( l y c 1 − l x s 1 ) 

求解 θ 6

利用等式左右两边第二行,第一列对应相等进行求解。
r y c 1 c 6 − o y c 1 s 6 − r x c 6 s 1 + o x s 1 s 6 = s 5

求解 θ 2 , θ 3 , θ 4

根据变换矩阵公式可得以下关系
1 0 T − 1 ∗ T ∗ 6 5 T − 1 ∗ 5 4 T − 1 = 4 1 T 
等式左边

等式右边
[c234s2340a4s23+a3s2001d4s234c2340a4c23+a3c20001]

求解 θ 3

利用等式左右两边第一行,第四列,以及第三行,第四列对应相等进行求解。


因为
s 23 s 2 + c 23 c 2 = c 3

θ 3 = ± A c o s ( m 2 + n 2 − a 3 2 − a 4 2 2 a 3 a 4 )

求解 θ 2

将(1)(2)式进行展开
a 4 ( s 2 c 3 + s 3 c 2 ) + a 3 s 2 = m

a 4 ( c 2 c 3 − s 2 s 3 ) + a 3 c 2 = n 
整理得
( a 4 c 3 + a 3 ) s 2 + ( a 4 s 3 ) c 2 = m

( a 4 c 3 + a 3 ) c 2 − ( a 4 s 3 ) s 2 = n

(a_4c_3+a_3)s_2+(a_4s_3)c_2 =m
解得
s 2 = m ( a 4 c 3 + a 3 ) − a 4 s 3 n a 4 2 + 2 a 4 a 3 c 3 + a 3 2  

c 2 = n + ( a 4 s 3 ) s 2 a 4 c 3 + a 3   θ 2 = A t a n 2 ( s 2 , c 2 ) 

求解 θ 4

利用等式左右两边第三行,第一列,以及第三行,第二列对应相等进行求解。
s 234 = − ( r z c 5 c 6 − l z s 5 − o z c 5 s 6 ) c 234 = − ( o z c 6 + r z s 6 ) 

θ 4 = A t a n 2 ( s 234 , c 234 ) − θ 2 − θ 3

奇异点

  1. θ 5 = 0 θ5=0时, θ 2 , θ 3 , θ 4 与 θ 6  θ2θ3θ4θ6平行,此时需要自定义 θ 6 的值使系统可解。
  2. θ 3 = π 时, θ 2 可以为任何值。

对于解析解而言,只要找出求解公式中超出函数定义域或者值域的取值,并将其重新定义为有效值即可避免到达奇异位点。当然,前提是保证目标点位于机械臂的工作空间内,

结果筛选

因为上述推导公式可以得到8组解,所以需要筛选出最合适的那个解进行输出。筛选的方法要根据情景而定,一般是选择与当前位姿最接近的解或者与最优位姿最接近的解
下面给出一种筛选思路。

参数定义
X i :逆运动学求取的一组解
r e f :参考点
ω  权重系数
d ( x , y ) :x,y两点的距离函数

对每组解分别求取误差
e r r i = ω ∗ d ( X i , r e f )

选取最优解
X o p t i m a l = X j e r r j = m i n ( e r r ) 

  1. 权重系数可以设置为各个驱动关节对应的连杆长度,这样子越长的连杆权重越大,最终得到的结果会趋向于移动短连杆,节省能量。
  2. 距离函数可以选择为绝对值距离或者欧氏距离等。

代码实现

/**
 * @brief inverse kinematics
 * @param p data order is x, y, z, pitch, roll, yaw
 * @param q joints angle 1 - 6
 */
template <typename T>
bool inverseKinematics(const Vec6<T>& p, Vec6<T>* q, const Vec6<T>* ref_q){
    /* convert to rotation matrix */
    Vec3<T> rpy(p(3), p(4), p(5));
    Mat3<T> rot =  ori::rpyToRotMat(rpy);
    T rx = rot(0, 0); T ox = rot(0, 1); T lx = rot(0, 2); T px = p(0);
    T ry = rot(1, 0); T oy = rot(1, 1); T ly = rot(1, 2); T py = p(1);
    T rz = rot(2, 0); T oz = rot(2, 1); T lz = rot(2, 2); T pz = p(2);

    /* define DH params */
    T a3 = DH_params[2][0];
    T a4 = DH_params[3][0]; 
    T d1 = DH_params[0][2];
    T d4 = DH_params[3][2];
    T d5 = DH_params[4][2];
    T d6 = DH_params[5][2];

    /* consider multiple solution */
    Vec6<T> solve_angles[8];
    for(int i = 0; i < 8; i ++){
        /* change sign */
        T signs[3];
        for(int s = 0; s < 3; s ++){
            signs[s] = i & (1 << s) ? -1 : 1; 
        }
        
        /* theta1 */
        T m = py - d6 * ly; 
        T n = px - d6 * lx;
        T verify = pow(m,2) + pow(n,2) - pow(d4,2);
        if(verify < 0) {
            verify = 0;
        }
        T t1 = atan2(m, n) - atan2(d4, signs[0] * sqrt(verify));
    
        /* theta5 */
        T t5 = signs[1] * acos(ly * cos(t1) - lx * sin(t1));

        /* theta6 */
        m = ry * cos(t1) - rx * sin(t1);
        n = oy * cos(t1) - ox * sin(t1);
        T t6;
        if(t5 == 0){
            t6 = 0;
        }
        else{
            t6 = atan2(m, n) - atan2(sin(t5), 0);
        }

        /* theta3 */
        m = px*cos(t1) + py*sin(t1) - d6*lx*cos(t1) - d6*ly*sin(t1) + d5*ry*sin(t1)*sin(t6)
            + d5*ox*cos(t1)*cos(t6) + d5*oy*cos(t6)*sin(t1) + d5*rx*cos(t1)*sin(t6);
        n = pz - d1 - d6*lz + d5*oz*cos(t6) + d5*rz*sin(t6);
        verify = (pow(m,2) + pow(n,2) - pow(a3,2) - pow(a4,2)) / (2*a3*a4);
        if(verify > 1) {
            verify = 1;
        }
        else if(verify  < -1) {
            verify = -1;
        }
        T t3 = signs[2] * acos(verify);

        /* theta2 */
        T s2 = (m * (a4*cos(t3) + a3) - a4*sin(t3)*n) / (pow(a4,2) + 2*a4*a3*cos(t3) + pow(a3,2));
        T c2 = (n + a4*sin(t3)*s2) / (a4*cos(t3) + a3);
        T t2 = atan2(s2, c2);

        /* theta4 */
        T s234 = -rz*cos(t5)*cos(t6) + lz*sin(t5) + oz*cos(t5)*sin(t6);
        T c234 = -oz*cos(t6) - rz*sin(t6);
        T t4 = atan2(s234, c234) - t2 - t3;

        solve_angles[i] << t1, t2, t3, t4, t5, t6;
    }

    /* filter optimal solution */
    if(ref_q == NULL){
        *q = solve_angles[0];
    }
    else{
        uint8_t optimal_index = 0;
        T min_err = 0; 
        for(int i = 0; i < 8; i ++){
            T err = 0;
            for(int j = 0; j < JOINTS_NUM; j ++){
                err += abs(ref_q->operator()(j) - solve_angles[i][j]) * (*links_length[j]);
            }
            if(!i){
                min_err = err;
            }
            else if(err < min_err){
                min_err = err;
                optimal_index = i;
            }
        }
        *q = solve_angles[optimal_index];
    }
    return true;
}