一文读懂麦克纳姆轮全向移动原理及剖析

扣扣技术交流群:460189483

参考文章如下,计算过程小白可能看不懂,于是做进一步补充,写出该文

https://zhuanlan.zhihu.com/p/20282234?utm_source=qq&utm_medium=social


什么是麦克纳姆轮
在竞赛机器人和特殊工种机器人中,全向移动经常是一个必需的功能。「全向移动」意味着可以在平面内做出任意方向平移同时自转的动作。为了实现全向移动,一般机器人会使用「全向轮」(Omni Wheel)或「麦克纳姆轮」(Mecanum Wheel)这两种特殊轮子。

全向轮:

 

麦克纳姆轮

 

全向轮与麦克纳姆轮的共同点在于他们都由两大部分组成:轮毂和辊子(roller)。轮毂是整个轮子的主体支架,辊子则是安装在轮毂上的鼓状物。全向轮的轮毂轴与辊子转轴相互垂直,而麦克纳姆轮的轮毂轴与辊子转轴呈 45° 角。理论上,这个夹角可以是任意值,根据不同的夹角可以制作出不同的轮子,但最常用的还是这两种。

全向轮与麦克纳姆轮(以下简称「麦轮」)在结构、力学特性、运动学特性上都有差异,其本质原因是轮毂轴与辊子转轴的角度不同。经过分析,二者的运动学和力学特性区别可以通过以下表格来体现。

 

计算过程如下,供参考,学霸可点开大图验算:

 

近年来,麦轮的应用逐渐增多,特别是在 Robocon、FRC 等机器人赛事上。这是因为麦克纳姆轮可以像传统轮子一样,安装在相互平行的轴上。而若想使用全向轮完成类似的功能,几个轮毂轴之间的角度就必须是 60°,90° 或 120° 等角度,这样的角度生产和制造起来比较麻烦。所以许多工业全向移动平台都是使用麦克纳姆轮而不是全向轮,比如这个国产的叉车: 全向移动平台 麦克纳姆轮叉车 美科斯叉车

另外一个原因,可能是麦轮的造型比全向轮要酷炫得多,看起来有一种不明觉厉的感觉……

的确,第一次看到麦轮运转起来,不少人都会惊叹。以下视频直观地说明了麦轮底盘在平移和旋转时的轮子旋转方向。

麦克纳姆轮工作原理

【物理篇-力学专题】E03 S1小车为什么横着走?~滚动摩擦与麦克纳姆轮

 

【初中-物理】E08 速度解算?绕圆运动?麦克纳姆轮-进阶

麦轮的安装方法

麦轮一般是四个一组使用,两个左旋轮,两个右旋轮。左旋轮和右旋轮呈手性对称,区别如下图。

 

安装方式有多种,主要分为:X-正方形(X-square)、X-长方形(X-rectangle)、O-正方形(O-square)、O-长方形(O-rectangle)。其中 X 和 O 表示的是与四个轮子地面接触的辊子所形成的图形;正方形与长方形指的是四个轮子与地面接触点所围成的形状。

  • X-正方形:轮子转动产生的力矩会经过同一个点,所以 yaw 轴无法主动旋转,也无法主动保持 yaw 轴的角度。一般几乎不会使用这种安装方式。
  • X-长方形:轮子转动可以产生 yaw 轴转动力矩,但转动力矩的力臂一般会比较短。这种安装方式也不多见。
  • O-正方形:四个轮子位于正方形的四个顶点,平移和旋转都没有任何问题。受限于机器人底盘的形状、尺寸等因素,这种安装方式虽然理想,但可遇而不可求。
  • O-长方形:轮子转动可以产生 yaw 轴转动力矩,而且转动力矩的力臂也比较长。是最常见的安装方式。

    麦轮底盘的正逆运动学模型

    以O-长方形的安装方式为例,四个轮子的着地点形成一个矩形。正运动学模型(forward kinematic model)将得到一系列公式,让我们可以通过四个轮子的速度,计算出底盘的运动状态;而逆运动学模型(inverse kinematic model)得到的公式则是可以根据底盘的运动状态解算出四个轮子的速度。需要注意的是,底盘的运动可以用三个独立变量来描述:X轴平动、Y轴平动、yaw 轴自转;而四个麦轮的速度也是由四个独立的电机提供的。所以四个麦轮的合理速度是存在某种约束关系的,逆运动学可以得到唯一解,而正运动学中不符合这个约束关系的方程将无解。

    先试图构建逆运动学模型,由于麦轮底盘的数学模型比较复杂,我们在此分四步进行:

    ①将底盘的运动分解为三个独立变量来描述;

    ②根据第一步的结果,计算出每个轮子轴心位置的速度;

    ③根据第二步的结果,计算出每个轮子与地面接触的辊子的速度;

    ④根据第三部的结果,计算出轮子的真实转速。

     

    一、底盘运动的分解

    我们知道,刚体在平面内的运动可以分解为三个独立分量:X轴平动、Y轴平动、yaw 轴自转。如下图所示,底盘的运动也可以分解为三个量:

       V_{tx} 表示 X 轴运动的速度,即左右方向,定义向右为正,这是人为设置的底盘水平方向的速度,是已知量;

       V_{ty} 表示 Y 轴运动的速度,即前后方向,定义向前为正,这是人为设置的底盘垂直方向的速度,是已知量;

       \underset{W}{\rightarrow} 表示 Z轴自转的角速度,定义逆时针为正,这是人为设置的底盘原地旋转的速度,是已知量;

       以上三个量一般都视为四个轮子的几何中心(矩形的对角线交点)的速度,也是底盘控制时设置的已知量

 

  •                                                          

    二、计算出轮子轴心位置的速度(以右上角轮子为例)

  • 定义:

      \underset{V}{\rightarrow}_{t} 表示几何中心的合速度(不包括W)向量,由已知量 V_{tx}和 V_{ty}组成的向量合速度,不一定与\underset{r}{\rightarrow}在一条线上;

      \underset{r}{\rightarrow} 为从几何中心指向轮子轴心的矢量;

      \underset{V}{\rightarrow} 为轮子轴心的运动速度矢量;

      \underset{V}{\rightarrow}_{r}为轮子轴心沿垂直于  \underset{r}{\rightarrow} 的方向(即切线方向)的速度分量;

    如下图所示: (手动绘制部分不喜勿喷,怕个别小白不理解,专门增加该部分讲解)

                   

那么可以计算出轮子中心速度: \underset{V}{\rightarrow} =  \underset{V}{\rightarrow}_{t} + \underset{V}{\rightarrow}_{r} =  \underset{V}{\rightarrow}_{t} +  \underset{W}{\rightarrow} x \underset{r}{\rightarrow}

分别计算 几何中心X、Y 轴的分量为:

V_{x}  =  V_{tx} - V_{rx}      

V_{y} =  V_{ty} + V_{ry}

其中V_{rx} 、V_{ry}表示为\underset{V}{\rightarrow}_{r}在X轴、Y轴的速度数值分量,V_{r}表示\underset{V}{\rightarrow}_{r}的值大小,不是向量

r_{x} 与 r_{y}表示 r在 X轴、Y轴的投影长度,也就是两个轮子之间的水平距离(宽度)一半、垂直距离(长度)的一半

V_{rx} = V_{r} X cosθ  = V_{r} X \frac{r_{y}}{r} = W x r X \frac{r_{y}}{r}W*r_{y}

V_{ry} = V_{r} x sinθ = V_{r} x \frac{r_{x}}{r}w x r x\frac{r_{x}}{r}W*r_{x}

所以可求得几何中心X、Y轴速度数值(非向量)分别为:

V_{x}  =  V_{tx} - V_{rx}     =  V_{tx} - W*r_{y}

V_{y} =  V_{ty} + V_{ry} = V_{ty} + W*r_{x}

同理可以算出其他三个轮子轴心的速度。

                               

三、计算辊子的速度

根据轮子轴心的速度,可以分解出沿辊子方向的速度 \underset{V_{||}}{\rightarrow}和垂直于辊子方向的速度\underset{V_{\perp }}{\rightarrow}。其中 \underset{V_{\perp }}{\rightarrow}是可以无视的(思考题:为什么垂直方向的速度可以无视?),而

V_{||} = \underset{V}{\rightarrow} * \hat{u}   此处表示向量\underset{V}{\rightarrow}点乘向量\hat{u},其中 \hat{u} 是沿辊子方向的单位向量。这一步表示向量\underset{V}{\rightarrow}在向量\hat{u}上的投影,是数值大小,不是向量噢,这样就可以求得沿辊子方向的速度数值大小V_{||}。例如向量a*b表示向量b在向量a上的投影a_{0},是一个长度单位,不是向量

向量几何知识了解参考 https://www.jianshu.com/p/6b37baa326ec

\underset{V}{\rightarrow} = \underset{V_{x}}{\rightarrow}  +  \underset{V_{y}}{\rightarrow}  ,其中\underset{V_{x}}{\rightarrow}\underset{V_{y}}{\rightarrow}表示V_{x}V_{y}的向量,上面我们求出来的是V_{x}V_{y}的数值,那向量如何表示呢?

可以通过膜*单位方向向量实现,如下图所示,设X轴单位方向向量\underset{i}{\rightarrow},坐标就是(1,0),Y轴单位方向向量\underset{j}{\rightarrow},坐标就是(0,1),那么V_{x}的方向向量就可以表示为V_{x}*\underset{i}{\rightarrow}V_{y}的方向向量就可以表示为V_{y}*\underset{j}{\rightarrow},且\underset{i}{\rightarrow}*\underset{i}{\rightarrow}=1,\underset{j}{\rightarrow}*\underset{j}{\rightarrow}=1,原因是两个向量点乘结果是数值,不是向量噢

\hat{u}是单位向量,可以表示为(-\frac{1}{\sqrt{2}}\frac{1}{\sqrt{2}}),或者\hat{u} = -\frac{1}{\sqrt{2}}*\underset{i}{\rightarrow} + \frac{1}{\sqrt{2}}*\underset{j}{\rightarrow}

V_{||} = \underset{V}{\rightarrow} * \hat{u} =(V_{x}*\underset{i}{\rightarrow} + V_{y}*\underset{j}{\rightarrow})*(-\frac{1}{\sqrt{2}}*\underset{i}{\rightarrow} + \frac{1}{\sqrt{2}}*\underset{j}{\rightarrow}

V_{||} = -\frac{1}{\sqrt{2}}*V_{x}*\underset{i}{\rightarrow}*\underset{i}{\rightarrow} +  \frac{1}{\sqrt{2}}V_{y}*\underset{j}{\rightarrow}*\underset{j}{\rightarrow} =  -\frac{1}{\sqrt{2}}*V_{x}  +  \frac{1}{\sqrt{2}}V_{y}

所以最终求得沿辊子方向的速度数值大小V_{||} =  -\frac{1}{\sqrt{2}}*V_{x}  +  \frac{1}{\sqrt{2}}V_{y}

                            

四、计算轮子的速度

从与地面接触的辊子速度到轮子线转速的计算比较简单:原因是辊子与轮子夹角是45°,

V_{v} =  \frac{V_{||}}{cos(45°)} =( -\frac{1}{\sqrt{2}}*V_{x}  +  \frac{1}{\sqrt{2}}V_{y})*\sqrt{2} = -V_{x} + V_{y}

根据上面求出的       V_{x}    =  V_{tx} - W*r_{y},       V_{y} =  V_{ty} + W*r_{x}由此可得

  V_{v} =-( V_{tx} - W*r_{y})+ (V_{ty} + W*r_{x}) = V_{ty}  - V_{tx} +  W*(  r_{x}  + r_{y})

这样求出来的是轮子与地面接触的一点的线速度,如果要求轮子转速V_{w}(单位rpm),需要再除以轮子半径r_{r}

  V_{w}  =   \frac{V_{v}}{r_{r}} ,单位是弧度/秒,转化到rpm(转/分钟),需要30*V_{w}/PI  ,到此,轮子速度已经求出来啦

          

根据上图定义,可知r_{x} = a , r_{y} = b,由此简化可得轮子线速度为:

  V_{v}  = V_{ty}  - V_{tx} +  W*\left ( a+b \right )

由此计算获得四个轮子线速度如下

  V_{v1}  = V_{ty}  - V_{tx} +  W*\left ( a+b \right )

  V_{v2}  = V_{ty}  + V_{tx} -  W*\left ( a+b \right )

  V_{v3}  = V_{ty}  - V_{tx} -  W*\left ( a+b \right )

  V_{v4}  = V_{ty}  + V_{tx} +  W*\left ( a+b \right )

以上方程组就是O-长方形麦轮底盘的逆运动学模型,而正运动学模型可以直接根据逆运动学模型中的三个方程解出来,此处不再赘述。

另一种计算方式

「传统」的推导过程虽然严谨,但还是比较繁琐的。这里介绍一种简单的逆运动学计算方式。

我们知道,全向移动底盘是一个纯线性系统,而刚体运动又可以线性分解为三个分量。那么只需要计算出麦轮底盘在「沿X轴平移」、「沿Y轴平移」、「绕几何中心自转」时,四个轮子的速度,就可以通过简单的加法,计算出这三种简单运动所合成的「平动+旋转」运动时所需要的四个轮子的转速。而这三种简单运动时,四个轮子的速度可以通过简单的测试,或是推动底盘观察现象得出。

当底盘沿着 X 轴平移时:

V_{v1} =  - V_{tx} 

V_{v2} =  + V_{tx} 

V_{v3} =  - V_{tx} 

V_{v4} =  + V_{tx} 

当底盘沿着 Y 轴平移时:

V_{v1} =  + V_{ty} 

V_{v2} =  + V_{ty} 

V_{v3} =  + V_{ty} 

V_{v4} =  + V_{ty} 

当底盘绕几何中心自转时:

  V_{v1}  = +  W*\left ( a+b \right )

  V_{v2}  =  -  W*\left ( a+b \right )

  V_{v3}  =  -  W*\left ( a+b \right )

  V_{v4}  =  +  W*\left ( a+b \right )

将以上三个方程组相加,得到的恰好是根据「传统」方法计算出的结果。这种计算方式不仅适用于O-长方形的麦轮底盘,也适用于任何一种全向移动的机器人底盘。

Makeblock 麦轮底盘的组装

理论分析完成,可以开始尝试将其付诸实践了。

第一步,组装矩形框架。

第二步,组装电机模块。

由于麦轮底盘的四个轮子速度有约束关系,必须精确地控制每个轮子的速度,否则将会导致辊子与地面发生滑动摩擦,不仅会让底盘运动异常,还会让麦轮的寿命减少。所以必须使用编码电机。

 

第三步,将电机模块安装到框架上。

 

 

第四步,将麦轮安装到框架上。

 

第五步,安装电路板并接线。

编码电机必须配上相应的驱动板才能正常工作。这里使用的 Makeblock 编码电机驱动板,每一块板可以驱动两个电机。接线顺序在下文中会提及,也可以随意接上,在代码中定义好对应的顺序即可。

 

第六步,装上电池。

 

至此,一个能独立运行的麦轮底盘就完成了。

 

控制程序

根据麦轮的底盘的运动学模型,要完全控制它的运动,需要有三个控制量:X轴速度、Y轴速度、自转角速度。要产生这三个控制量,有很多种方法,本文将使用一个 USB 游戏手柄,左边的摇杆产生平移速度,右边的摇杆产生角速度。

首先将一个 USB Host 模块连接到 Orion 主板的 3 口。

 

然后插上一个无线 USB 游戏手柄。

 

 

然后再添加其他细节,就大功告成啦!

 

 

其他细节:

 

#include <Wire.h>
#include <SoftwareSerial.h>
#include "MeOrion.h"

MeUSBHost joypad(PORT_3);
//	手柄代码(红灯亮模式)
//	默认:128-127-128-127-15-0-0-128
//	左一:128-127-128-127-15-1-0-128
//	右一:128-127-128-127-15-2-0-128
//	左二:128-127-128-127-15-4-0-128
//	右二:128-127-128-127-15-8-0-128
//	三角:128-127-128-127-31-0-0-128 (0001 1111)
//	方形:128-127-128-127-143-0-0-128 (1000 1111)
//	叉号:128-127-128-127-79-0-0-128 (0100 1111)
//	圆圈:128-127-128-127-47-0-0-128 (0010 1111)
//	向上:128-127-128-127-0-0-0-128 (0000 0000)
//	向下:128-127-128-127-4-0-0-128 (0000 0100)
//	向左:128-127-128-127-6-0-0-128 (0000 0110)
//	向右:128-127-128-127-2-0-0-128 (0000 0010)
//	左上:128-127-128-127-7-0-0-128 (0000 0111)
//	左下:128-127-128-127-5-0-0-128 (0000 0101)
//	右上:128-127-128-127-1-0-0-128 (0000 0001)
//	右下:128-127-128-127-3-0-0-128 (0000 0011)
//	选择:128-127-128-127-15-16-0-128
//	开始:128-127-128-127-15-32-0-128
//	摇杆:右X-右Y-左X-左Y-15-0-0-128


MeEncoderMotor motor1(0x02, SLOT2);
MeEncoderMotor motor2(0x02, SLOT1);
MeEncoderMotor motor3(0x0A, SLOT2);
MeEncoderMotor motor4(0x0A, SLOT1);

//  底盘:a = 130mm, b = 120mm

float linearSpeed = 100;
float angularSpeed = 100;
float maxLinearSpeed = 200;
float maxAngularSpeed = 200;
float minLinearSpeed = 30;
float minAngularSpeed = 30;

void setup()
{
    //  要上电才能工作,不能只是插上 USB 线来调试。
	motor1.begin();
	motor2.begin();
	motor3.begin();
	motor4.begin();
	
	Serial.begin(57600);
	joypad.init(USB1_0);
}

void loop()
{
    Serial.println("loop:");
	//setEachMotorSpeed(100, 50, 50, 100);
	if(!joypad.device_online)
    {
        //  若一直输出离线状态,重新拔插 USB Host 的 RJ25 线试一下。
        Serial.println("Device offline.");
        joypad.probeDevice();
        delay(1000);
    }
    else
    {
        int len = joypad.host_recv();
        parseJoystick(joypad.RECV_BUFFER);
        delay(5);
    }
	//delay(500);
}


void setEachMotorSpeed(float speed1, float speed2, float speed3, float speed4)
{
	motor1.runSpeed(speed1);
	motor2.runSpeed(-speed2);
	motor3.runSpeed(-speed3);
	motor4.runSpeed(-speed4);
}

void parseJoystick(unsigned char *buf)   //Analytic function, print 8 bytes from USB Host
{
    //  输出手柄的数据,调试用
    // int i = 0;
    // for(i = 0; i < 7; i++)
    // {
    //     Serial.print(buf[i]);  //It won't work if you connect to the Makeblock Orion.
    //     Serial.print('-');
    // }
    // Serial.println(buf[7]);
    // delay(10);

    //  速度增减
    switch (buf[5])
    {
        case 1:
            linearSpeed += 5;
            if (linearSpeed > maxLinearSpeed)
            {
                linearSpeed = maxLinearSpeed;
            }
            break;
        case 2:
            angularSpeed += 5;
            if (angularSpeed > maxAngularSpeed)
            {
                angularSpeed = maxAngularSpeed;
            }
            break;
        case 4:
            linearSpeed -= 5;
            if (linearSpeed < minLinearSpeed)
            {
                linearSpeed = minLinearSpeed;
            }
            break;
        case 8:
            angularSpeed -= 5;
            if (angularSpeed < minAngularSpeed)
            {
                angularSpeed = minAngularSpeed;
            }
            break;
        default:
            break;
    }


    
    if ((128 != buf[0]) || (127 != buf[1]) || (128 != buf[2]) || (127 != buf[3]))
    {
        //  处理摇杆
        float x = ((float)(buf[2]) - 127) / 128;
        float y = (127 - (float)(buf[3])) / 128;
        float a = (127 - (float)(buf[0])) / 128;
        mecanumRun(x * linearSpeed, y * linearSpeed, a * angularSpeed);
    }
    else
    {
        switch (buf[4])
        {
            case 0:
                mecanumRun(0, linearSpeed, 0);
                break;
            case 4:
                mecanumRun(0, -linearSpeed, 0);
                break;
            case 6:
                mecanumRun(-linearSpeed, 0, 0);
                break;
            case 2:
                mecanumRun(linearSpeed, 0, 0);
                break;
            case 7:
                mecanumRun(-linearSpeed/2, linearSpeed/2, 0);
                break;
            case 5:
                mecanumRun(-linearSpeed/2, -linearSpeed/2, 0);
                break;
            case 1:
                mecanumRun(linearSpeed/2, linearSpeed/2, 0);
                break;
            case 3:
                mecanumRun(linearSpeed/2,  -linearSpeed/2, 0);
                break;
            default:
                mecanumRun(0, 0, 0);
                break;
        }
    }
}

void mecanumRun(float xSpeed, float ySpeed, float aSpeed)
{
    float speed1 = ySpeed - xSpeed + aSpeed; 
    float speed2 = ySpeed + xSpeed - aSpeed;
    float speed3 = ySpeed - xSpeed - aSpeed;
    float speed4 = ySpeed + xSpeed + aSpeed;
    
    float max = speed1;
    if (max < speed2)   max = speed2;
    if (max < speed3)   max = speed3;
    if (max < speed4)   max = speed4;
    
    if (max > maxLinearSpeed)
    {
        speed1 = speed1 / max * maxLinearSpeed;
        speed2 = speed2 / max * maxLinearSpeed;
        speed3 = speed3 / max * maxLinearSpeed;
        speed4 = speed4 / max * maxLinearSpeed;
    }
    
    setEachMotorSpeed(speed1, speed2, speed3, speed4);
}

Makeblock 麦克纳姆轮 全向移动机器人

 PS:答疑

1. 这轮子这么灵活,为啥不都装成麦克纳姆轮呢,多方便啊

答:1. 如果上面的算法你掌握了,那么就明白所有的理论计算都是基于小车轮子在一个平面的,那么如果有悬挂的,或者地面不平坦的时候,很明显计算过程中的速度会有偏差,就会导致你的小车底盘跑的不准、不直,如果你因此计算里程计就更差了。2. 效率上不行,为啥呢?记得上面那个问题吗?棍子垂直方向的速度不考虑,为啥?因为这个速度主要用于棍子自转,对小车运行没有影响,那这就势必造成了能量浪费(相比普通轮子),所以效率就降低了 。3 .因为结构特性原因,不利于爬坡、越障,所以比较适合于平地使用,大大限制了应用场景

2. 程序中为啥有速度反向

因为电机运行方向的原因

  • 91
    点赞
  • 656
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
Yolov5 是一种广泛应用于目标检测的算法,其 loss 原理相对简单。Yolov5 通过将目标检测问题转化为一个回归问题,通过预测 bounding box 的坐标来实现目标检测。 Yolov5 的 loss 主要包括三个部分:分类损失、定位损失和目标置信度损失。 分类损失是用来衡量预测的类别与真实类别之间的差异。Yolov5 使用交叉熵损失函数来计算分类损失。对于每个边界框(bounding box),它将计算预测类别的 softmax 概率与真实类别的 one-hot 向量之间的交叉熵。 定位损失用于衡量预测的边界框位置与真实边界框位置之间的差异。Yolov5 使用 Smooth L1 损失函数来计算定位损失。它通过对预测边界框的坐标与真实边界框的坐标之间进行平滑处理,减小了异常值的影响。 目标置信度损失用于衡量预测的边界框与真实边界框之间的 IoU(Intersection over Union)之间的差异。Yolov5 使用 Binary Cross-Entropy 损失函数来计算目标置信度损失。它将预测的边界框是否包含目标与真实边界框是否包含目标之间的差异进行衡量。 最终,Yolov5 的总损失是通过将三个部分的损失加权求和得到的。这些权重可以根据具体的任务和数据集进行调整。 通过最小化 Yolov5 的 loss 函数,模型可以学习到更准确的目标检测结果。这样,我们就可以在图像中准确地检测和定位不同类别的目标。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值