跳转至

底盘正解算

1. 舵轮底盘

先看代码:

/**
 * @brief 舵轮速度正运动学解算
 *
 * 将四个舵轮的实际速度和角度转换为车身坐标系下的运动速度
 *
 * @param wheel_speed 四个轮子的实际速度(mm/s)
 * @param wheel_angle 四个轮子的转向角度(度)
 * @param speedx 输出的车身x方向速度
 * @param speedy 输出的车身y方向速度
 * @param speedw 输出的自转角速度
 */
void steering_wheel_forward_calc(float wheel_speed[WHEEL_NUM],
                                 float wheel_angle[WHEEL_NUM],
                                 float *speedx, float *speedy,
                                 float *speedw) {
    float out_x = 0.0f, out_y = 0.0f, out_w = 0.0f;

#if WHEEL_NUM == 4
    float wheel_x[WHEEL_NUM] = {0.0f};
    float wheel_y[WHEEL_NUM] = {0.0f};

    /* 第一步:将每个轮子的速度分解到固定坐标系下 */
    for (uint32_t i = 0; i < WHEEL_NUM; i++) {
        wheel_x[i] = -wheel_speed[i] * SIN_F32(wheel_angle[i]);
        wheel_y[i] = wheel_speed[i] * COS_F32(wheel_angle[i]);
    }

    /* 第二步:计算车身质心在固定坐标系下的运动(平均) */
    float body_x =
        (wheel_x[0] + wheel_x[1] + wheel_x[2] + wheel_x[3]) * 0.25f;
    float body_y =
        (wheel_y[0] + wheel_y[1] + wheel_y[2] + wheel_y[3]) * 0.25f;

    /* 第三步:计算车身的自转角速度 */
    /* rot_from_x: 由左右轮速度差计算旋转 */
    float rot_from_x =
        (wheel_x[0] + wheel_x[1] - wheel_x[2] - wheel_x[3]) /
       (4.0f * CHASSIS_ROT_KX);
    /* rot_from_y: 由前后轮速度差计算旋转 */
    float rot_from_y =
        (wheel_y[1] + wheel_y[3] - wheel_y[0] - wheel_y[2]) /
        (4.0f * CHASSIS_ROT_KY);
    float rot_speed = (rot_from_x + rot_from_y) * 0.5f;

    /* 第四步:将固定坐标系转换到车身坐标系 */
    /* world_angle 是车头朝向,转换需要取反 */
    float yaw = DEG2RAD(-(*steering_ctrl_handle.world_angle));
    float cos_yaw = COS_F32(yaw);
    float sin_yaw = SIN_F32(yaw);
    float vy_internal = body_x * sin_yaw + body_y * cos_yaw;

    out_x = body_x * cos_yaw - body_y * sin_yaw;
    out_y = -vy_internal;

    /* 第五步:计算输出的旋转角速度 */
    /* radius 为 0 时表示纯平移运动,不计算角速度 */
    if (steering_ctrl_handle.radius > 0.0f) {
        out_w = rot_speed / steering_ctrl_handle.radius;
    }
#endif /* WHEEL_NUM == 4 */

    *speedx = out_x;
    *speedy = out_y;
    *speedw = out_w;
}

有一个很让人困惑的点,就是:“我只是把四个轮子的速度拆成了 x、y 分量,为什么居然就能反推出整个车身的平移速度和角速度?” 答案很简单:

因为底盘是刚体
刚体上任意一点的速度,不是互相独立的,而是都必须满足同一个刚体运动规律。

平移速度是所有轮子共享的公共部分,先拿矩形底盘举例:

  • 左前:\(v1x=vx−ωb\)
  • 右前:\(v2x=vx+ωb\)
  • 左后:\(v3x=vx−ωb\)
  • 右后:\(v4x=vx+ωb\)

把四个加起来:

\(v1x+v2x+v3x+v4x=4vx\)

因为旋转项一正一负,抵消了。

所以:

\[vx=\frac{v_{1x}+v_{2x}+v_{3x}+v_{4x}}{4}​​\]

同理:

$\(vy=\frac{v_{1y}+v_{2y}+v_{3y}+v_{4y}}{4}vy\)$​​

然后进行旋转正解算

旋转和前面的平移刚好反过来:

平移看四轮的“共同部分”,旋转看四轮的“差分部分”。

因为纯平移时,四个轮子的 x/y 分量都差不多; 而纯旋转时,对称位置的轮子速度方向相反。

所以代码里先用 x 方向差分算一次旋转:

float rot_from_x =
    (wheel_x[0] + wheel_x[1] - wheel_x[2] - wheel_x[3]) /
    (4.0f * CHASSIS_ROT_KX);

这个式子的意思是:

\[rot_x=\frac{v_{0x}+v_{1x}-v_{2x}-v_{3x}}{4K_x}\]

前面两个轮子和后面两个轮子相减,公共的平移速度会被抵消,剩下的就是旋转造成的速度。

再用 y 方向差分算一次:

float rot_from_y =
    (wheel_y[1] + wheel_y[3] - wheel_y[0] - wheel_y[2]) /
    (4.0f * CHASSIS_ROT_KY);

也就是:

\[rot_y=\frac{v_{1y}+v_{3y}-v_{0y}-v_{2y}}{4K_y}\]

这两个值理论上应该一样,但实际会有编码器误差、打滑、舵向角误差,所以最后取平均:

float rot_speed = (rot_from_x + rot_from_y) * 0.5f;

这里的 rot_speed 还不是角速度,而是自转对应的切向线速度。

因为:

\[v=\omega r\]

所以:

\[\omega=\frac{v}{r}\]

对应代码:

if (steering_ctrl_handle.radius > 0.0f) {
    out_w = rot_speed / steering_ctrl_handle.radius;
}

最后再把前面算出来的 body_x/body_yworld_angle 做一次坐标旋转:

float yaw = DEG2RAD(-(*steering_ctrl_handle.world_angle));

out_x = body_x * cos_yaw - body_y * sin_yaw;
out_y = -(body_x * sin_yaw + body_y * cos_yaw);

这里 -world_angle 是把世界系转回车体系的逆变换;out_y 前面的负号是当前工程里的 y 轴方向适配。

所以整个正解算可以简单理解成:

轮速 + 舵向角 -> 每个轮子的 x/y 速度
四轮平均 -> 平移速度
四轮差分 -> 旋转切向速度
切向速度 / 半径 -> 角速度
最后按 yaw 做坐标变换

评论