麦克纳姆轮运动学解算
一、麦克纳姆轮介绍
了解过Robomaster的同学都知道,RM战车所用的轮子均为麦克纳姆轮,这种轮子安装方式与普通轮子无异,可安装于平行轴上,但是麦克纳姆轮可以实现全向移动,即前后运动、水平移动、绕中心自转。正因为以上优点,许多工业上的全向移动平台都会应用这种轮子。缺点也有,就是不耐磨,需要定期更换。
麦克纳姆轮由两部分组成:轮毂和辊子,轮毂为轮子的主体,辊子为轮毂周围的类似椭球体的小轮子,轮毂和辊子都有自己的轴,且轮毂轴与辊子轴夹角为45°(可以为其他角度但45°角最为常见)

麦轮的安装方式也有讲究,虽然都是同轴安装,但与普通轮子不同,麦轮分为左旋和右旋两种,在一个四轮底盘上需要用两个左旋和两个右旋。安装方式为O型,如图所示:

左图为安装后你看到的样子,右图为四个轮子与地面接触的辊子围成的形状,也就是“O形”
这里的O形指的是与地面接触的辊子围成的形状噢,不要再问为什么左图看起来是个X了
二、麦克纳姆轮运动学模型
1. 基础知识
1.1 坐标系统
在ROS机器人中,坐标系统使用右手定义

对于ROS机器人,如果以它为坐标系的原点,那么
如图所示:

除此之外,对于旋转运动,也使用右手定义:

根据右手定义,围绕 z轴正旋转 是 逆时针旋转
1.2 测量单位
ROS使用公制 :
1.3 轮子序号定义
左前1 右前2
左后3 右后4
2. 逆运动学解析
逆运动学模型(inverse kinematic model)得到的公式可以根据底盘的运动状态解算出四个轮子的速度。
2.1 底盘运动的分解
刚体在平面内的运动可以分解为三个独立分量:X轴平动、Y轴平动、yaw 轴自转。底盘的运动也可以分解为三个量:
如下图所示:

- $v_{tx}$ 表示 X 轴运动的速度,即前后方向,定义向前为正;
- $v_{ty}$ 表示 Y 轴运动的速度,即左右方向,定义向左为正;
- $\overrightarrow{\omega}$ 表示 yaw 轴自转的角速度,定义逆时针为正。
2.2 计算轮子轴心位置的速度
如下图所示,以右前轮为例,蓝色的方框代表轮子,定义以下变量:
- $\overrightarrow{r}$为从底盘中心指向轮子轴心的矢量;
- $\overrightarrow{v}$为轮子轴心的速度矢量;
- $\overrightarrow{v_r}$为轮子轴心沿垂直于$\overrightarrow{r}$的方向(即切线方向)的速度分量;

可以计算出:
将$\overrightarrow{r}$分解为$r_x$和$r_y$,分别计算轮子轴心在X、Y轴的速度分量:
其他三个轮子同理
2.3计算与地面接触的辊子速度
由2.2算得的轮子轴心速度,可以分解为沿辊子轴方向的$\overrightarrow{v_\parallel}$ 和垂直辊子轴方向的 $\overrightarrow{v_\perp}$ ,如图所示

其中$\overrightarrow{v_\perp}$用于让辊子空转,可以忽略
定义一个沿辊子方向的单位矢量$\hat{e}$,对于右前轮来说,$\hat{e}=\frac{1}{\sqrt{2}}\cdot\hat{i}+\frac{1}{\sqrt{2}}\cdot\hat{j}$
则沿轴线的速度为$\overrightarrow{v}$在$\hat{e}$方向的投影:
2.4 计算轮子的转速(和地面接触点的线速度)
如图所示,轮子转速为$v_w$

由于辊子与轮轴呈45°角,则$v_\omega$可求得:
将2.2求出的$\left\{\begin{matrix}
v_x=v_{tx}+\omega\cdot{r_y} \\
v_y=v_{ty}+\omega\cdot{r_x}
\end{matrix}\right.$带入上式,可求出此轮的转速:
结合以上四个步骤,可以根据底盘运动状态解算出四个轮子的转速:
以上方程组就是O形麦轮底盘的逆运动学模型。
2.5 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #define ENCODER_RESOLUTION 1440.0 #define WHEEL_DIAMETER 0.058 #define D_X 0.18 #define D_Y 0.25 #define PID_RATE 50
double pulse_per_meter = 0; float rx_plus_ry_cali = 0.3; double angular_correction_factor = 1.0; double linear_correction_factor = 1.0; double angular_correction_factor = 1.0;
void Kinematics_Init(void) { pulse_per_meter = (float)(ENCODER_RESOLUTION/(WHEEL_DIAMETER*3.1415926))/linear_correction_factor; float r_x = D_X/2; float r_y = D_Y/2; rx_plus_ry_cali = (r_x + r_y)/angular_correction_factor; }
void Kinematics_Inverse(int16_t* input, int16_t* output) { float v_tx = (float)input[0]; float v_ty = (float)input[1]; float omega = (float)input[2]; static float v_w[4] = {0}; v_w[0] = v_tx - v_ty - (r_x + r_y)*omega; v_w[1] = v_tx + v_ty + (r_x + r_y)*omega; v_w[2] = v_tx + v_ty - (r_x + r_y)*omega; v_w[3] = v_tx - v_ty + (r_x + r_y)*omega;
output[0] = (int16_t)(v_w[0] * pulse_per_meter/PID_RATE); output[1] = (int16_t)(v_w[1] * pulse_per_meter/PID_RATE); output[2] = (int16_t)(v_w[2] * pulse_per_meter/PID_RATE); output[3] = (int16_t)(v_w[3] * pulse_per_meter/PID_RATE); }
|
3. 正运动学解析
3.1 正运动学模型
正运动学模型(forward kinematic model)让我们可以通过四个轮子的速度,计算出底盘的运动状态。可以直接根据逆运动学模型中的三个方程解出来,比如:
转换为底盘坐标系下对时间求积分即为里程计变化量
3.2 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| #define ENCODER_MAX 32767 #define ENCODER_MIN -32768 #define ENCODER_LOW_WRAP ((ENCODER_MAX - ENCODER_MIN)*0.3+ENCODER_MIN) #define ENCODER_HIGH_WRAP ((ENCODER_MAX - ENCODER_MIN)*0.7+ENCODER_MIN) #define PI 3.1415926
int32_t wheel_turns[4] = {0}; int32_t encoder_sum_current[4] = {0};
void Kinematics_Forward(int16_t* input, int16_t* output) { static double dv_w_times_dt[4]; static double dv_t_times_dt[3]; static int16_t encoder_sum[4]; encoder_sum[0] = -input[0]; encoder_sum[1] = input[1]; encoder_sum[2] = -input[2]; encoder_sum[3] = input[3]; for(int i=0;i<4;i++) { if(encoder_sum[i] < ENCODER_LOW_WRAP && encoder_sum_current[i] > ENCODER_HIGH_WRAP) wheel_turns[i]++; else if(encoder_sum[i] > ENCODER_HIGH_WRAP && encoder_sum_current[i] < ENCODER_LOW_WRAP) wheel_turns[i]--; else wheel_turns[i]=0; }
for(int i=0;i<4;i++) { dv_w_times_dt[i] = 1.0*(encoder_sum[i] + wheel_turns[i]*(ENCODER_MAX-ENCODER_MIN)-encoder_sum_current[i])/pulse_per_meter; encoder_sum_current[i] = encoder_sum[i]; } dv_w_times_dt[0] = -dv_w_times_dt[0]; dv_w_times_dt[1] = dv_w_times_dt[1]; dv_w_times_dt[2] = -dv_w_times_dt[2]; dv_w_times_dt[3] = dv_w_times_dt[3]; dv_t_times_dt[0] = ( dv_w_times_dt[3] + dv_w_times_dt[2])/2.0; dv_t_times_dt[1] = ( dv_w_times_dt[2] - dv_w_times_dt[0])/2.0; dv_t_times_dt[2] = ( dv_w_times_dt[1] - dv_w_times_dt[2])/(2*wheel_track_cali); output[0] += (int16_t)(cos((double)output[2])*dv_t_times_dt[0] - sin((double)output[2])*dv_t_times_dt[1]); output[1] += (int16_t)(sin((double)output[2])*dv_t_times_dt[0] + cos((double)output[2])*dv_t_times_dt[1]); output[2] += (int16_t)(dv_t_times_dt[2]*1000); if(output[2] > PI) output[2] -= 2*PI; else if(output[2] < -PI) output[2] += 2*PI; output[3] = (int16_t)(dv_t_times_dt[0]); output[4] = (int16_t)(dv_t_times_dt[1]); output[5] = (int16_t)(dv_t_times_dt[2]); }
|
参考文献:
【1】https://zhuanlan.zhihu.com/p/20282234
【2】https://blog.csdn.net/shixiaolu63/article/details/78496457