基于45°布局的四轮全向轮(Omni Wheel)底盘运动学建模与ROS实现。
一、底盘结构
1.1 轮子布局
轮子安装方向(以x轴为起点,逆时针为正):
- 轮1(左前):45°
- 轮2(左后):135°
- 轮3(右后):-135°(225°)
- 轮4(右前):-45°(315°)
1 2 3 4 5 6 7 8 9
| 前进x轴方向 ↑ 轮1 ↗ ↖ 轮4 ┌────────┐ │ │ ←────┤ ● │ y轴 │ │ └────────┘ 轮2 ↘ ↙ 轮3
|
1.2 关键参数
| 参数 |
符号 |
说明 |
| 底盘边长 |
Lchassis |
正方形底盘 |
| 轮子半径 |
R |
全向轮半径 |
| 轮心到中心距离 |
L |
L=2Lchassis |
二、运动学模型
2.1 逆向运动学(速度 → 轮速)
输入:底盘目标速度 (vx,vy,ωz)
输出:四个轮子的线速度 (V1,V2,V3,V4)
矩阵形式:
V1V2V3V4=211−1−1111−1−12L2L2L2Lvxvyωz
展开形式:
⎩⎨⎧V1=21(vx+vy)+LωzV2=21(−vx+vy)+LωzV3=21(−vx−vy)+LωzV4=21(vx−vy)+Lωz
2.2 前向运动学(轮速 → 速度)
输入:四个轮子的实际速度 (V1,V2,V3,V4)
输出:底盘实际速度 (vx,vy,ωz)
矩阵形式:
vxvyωz=4122L1−22L1−2−2L12−2L1V1V2V3V4
展开形式:
⎩⎨⎧vx=421(V1−V2−V3+V4)vy=421(V1+V2−V3−V4)ωz=4L1(V1+V2+V3+V4)
2.3 轮速与电机转速转换
轮速转电机转速:
ωmotor,i=RVi⋅N
电机转速转轮速:
Vi=Nωmotor,i⋅R
从编码器增量计算轮速:
Vi=PPR⋅N⋅Δt2πR⋅Δencoderi
其中:
- R:轮子半径
- N:减速比
- PPR:编码器每转脉冲数
- Δt:采样周期(秒)
三、典型运动模式
3.1 前进运动
目标:vx=v,vy=0,ωz=0
V1V2V3V4=2v1−1−11
3.2 侧向平移
目标:vx=0,vy=v,ωz=0
V1V2V3V4=2v11−1−1
3.3 原地旋转
目标:vx=0,vy=0,ωz=ω
V1V2V3V4=Lω1111
四、速度约束
4.1 单轮速度限制
最大轮速:Vmax
最大前进速度:
vx,max=2⋅Vmax
最大旋转角速度:
ωz,max=LVmax
4.2 速度缩放
当计算出的轮速超限时:
k=max(∣V1∣,∣V2∣,∣V3∣,∣V4∣)Vmax
vx′vy′ωz′=kvxvyωz
五、C++代码实现
5.1 运动学类实现
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 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
| #include <cmath> #include <algorithm>
struct ChassisVelocity { double vx; double vy; double wz; };
struct WheelSpeeds { double v1, v2, v3, v4; };
struct Pose { double x; double y; double theta; };
class OmniKinematics { private: const double L; const double R; const double N; const double V_MAX; const double SQRT2_INV; const double RAD_TO_RPM;
public: OmniKinematics(double wheel_base, double wheel_radius, double gear_ratio, double max_speed) : L(wheel_base), R(wheel_radius), N(gear_ratio), V_MAX(max_speed), SQRT2_INV(1.0 / std::sqrt(2.0)), RAD_TO_RPM(60.0 / (2.0 * M_PI)) {} WheelSpeeds inverseKinematics(const ChassisVelocity& cmd, bool limit = true) { WheelSpeeds wheels; wheels.v1 = SQRT2_INV * ( cmd.vx + cmd.vy) + L * cmd.wz; wheels.v2 = SQRT2_INV * (-cmd.vx + cmd.vy) + L * cmd.wz; wheels.v3 = SQRT2_INV * (-cmd.vx - cmd.vy) + L * cmd.wz; wheels.v4 = SQRT2_INV * ( cmd.vx - cmd.vy) + L * cmd.wz; if (limit) { double max_speed = std::max({std::abs(wheels.v1), std::abs(wheels.v2), std::abs(wheels.v3), std::abs(wheels.v4)}); if (max_speed > V_MAX) { double scale = V_MAX / max_speed; wheels.v1 *= scale; wheels.v2 *= scale; wheels.v3 *= scale; wheels.v4 *= scale; } } return wheels; } ChassisVelocity forwardKinematics(const WheelSpeeds& wheels) { ChassisVelocity vel; double coeff = SQRT2_INV / 4.0; vel.vx = coeff * ( wheels.v1 - wheels.v2 - wheels.v3 + wheels.v4); vel.vy = coeff * ( wheels.v1 + wheels.v2 - wheels.v3 - wheels.v4); vel.wz = (wheels.v1 + wheels.v2 + wheels.v3 + wheels.v4) / (4.0 * L); return vel; } void wheelToMotorSpeed(const WheelSpeeds& wheels, double motor_speeds[4]) { motor_speeds[0] = wheels.v1 * N / R; motor_speeds[1] = wheels.v2 * N / R; motor_speeds[2] = wheels.v3 * N / R; motor_speeds[3] = wheels.v4 * N / R; } WheelSpeeds motorToWheelSpeed(const double motor_speeds[4]) { WheelSpeeds wheels; wheels.v1 = motor_speeds[0] * R / N; wheels.v2 = motor_speeds[1] * R / N; wheels.v3 = motor_speeds[2] * R / N; wheels.v4 = motor_speeds[3] * R / N; return wheels; } void wheelToMotorRPM(const WheelSpeeds& wheels, double motor_rpm[4]) { motor_rpm[0] = wheels.v1 * N * RAD_TO_RPM / R; motor_rpm[1] = wheels.v2 * N * RAD_TO_RPM / R; motor_rpm[2] = wheels.v3 * N * RAD_TO_RPM / R; motor_rpm[3] = wheels.v4 * N * RAD_TO_RPM / R; } WheelSpeeds motorRPMToWheel(const double motor_rpm[4]) { WheelSpeeds wheels; wheels.v1 = motor_rpm[0] * R / (N * RAD_TO_RPM); wheels.v2 = motor_rpm[1] * R / (N * RAD_TO_RPM); wheels.v3 = motor_rpm[2] * R / (N * RAD_TO_RPM); wheels.v4 = motor_rpm[3] * R / (N * RAD_TO_RPM); return wheels; } WheelSpeeds encoderToWheelSpeed(const int encoder_delta[4], double dt, int ppr) { WheelSpeeds wheels; double coeff = (2.0 * M_PI * R) / (ppr * N * dt); wheels.v1 = encoder_delta[0] * coeff; wheels.v2 = encoder_delta[1] * coeff; wheels.v3 = encoder_delta[2] * coeff; wheels.v4 = encoder_delta[3] * coeff; return wheels; } };
|
5.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
| class Odometry { private: Pose pose; OmniKinematics* kinematics; void normalizeAngle(double& angle) { while (angle > M_PI) angle -= 2.0 * M_PI; while (angle < -M_PI) angle += 2.0 * M_PI; } public: Odometry(OmniKinematics* kin) : pose{0.0, 0.0, 0.0}, kinematics(kin) {} void update(const int encoder_delta[4], double dt, int ppr) { WheelSpeeds wheels = kinematics->encoderToWheelSpeed(encoder_delta, dt, ppr); ChassisVelocity vel = kinematics->forwardKinematics(wheels); updatePose(vel, dt); } void updatePose(const ChassisVelocity& vel, double dt) { double cos_theta = std::cos(pose.theta); double sin_theta = std::sin(pose.theta); double vx_global = vel.vx * cos_theta - vel.vy * sin_theta; double vy_global = vel.vx * sin_theta + vel.vy * cos_theta; pose.x += vx_global * dt; pose.y += vy_global * dt; pose.theta += vel.wz * dt; normalizeAngle(pose.theta); } Pose getPose() const { return pose; } void resetPose(double x = 0.0, double y = 0.0, double theta = 0.0) { pose.x = x; pose.y = y; pose.theta = theta; } };
|
5.3 使用示例
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
| #include <cstdio>
int main() { double L = 0.3575; double R = 0.0855; double N = 19.0; double V_max = 1.8; int PPR = 1024; OmniKinematics kinematics(L, R, N, V_max); Odometry odom(&kinematics); printf("=== 逆向运动学示例 ===\n"); ChassisVelocity cmd = {0.5, 0.3, 0.2}; WheelSpeeds wheels = kinematics.inverseKinematics(cmd); printf("底盘速度: vx=%.2f vy=%.2f wz=%.2f\n", cmd.vx, cmd.vy, cmd.wz); printf("轮速: V1=%.3f V2=%.3f V3=%.3f V4=%.3f m/s\n\n", wheels.v1, wheels.v2, wheels.v3, wheels.v4); double motor_rpm[4]; kinematics.wheelToMotorRPM(wheels, motor_rpm); printf("电机转速: M1=%.1f M2=%.1f M3=%.1f M4=%.1f RPM\n\n", motor_rpm[0], motor_rpm[1], motor_rpm[2], motor_rpm[3]); printf("=== 前向运动学示例 ===\n"); int encoder_delta[4] = {100, 120, 110, 90}; double dt = 0.02; odom.update(encoder_delta, dt, PPR); Pose pose = odom.getPose(); printf("编码器增量: E1=%d E2=%d E3=%d E4=%d\n", encoder_delta[0], encoder_delta[1], encoder_delta[2], encoder_delta[3]); printf("当前位姿: x=%.3f y=%.3f theta=%.2f°\n\n", pose.x, pose.y, pose.theta * 180.0 / M_PI); printf("=== 运动模式测试 ===\n"); ChassisVelocity forward = {1.0, 0.0, 0.0}; wheels = kinematics.inverseKinematics(forward); printf("前进: V1=%.3f V2=%.3f V3=%.3f V4=%.3f\n", wheels.v1, wheels.v2, wheels.v3, wheels.v4); ChassisVelocity lateral = {0.0, 1.0, 0.0}; wheels = kinematics.inverseKinematics(lateral); printf("侧移: V1=%.3f V2=%.3f V3=%.3f V4=%.3f\n", wheels.v1, wheels.v2, wheels.v3, wheels.v4); ChassisVelocity rotation = {0.0, 0.0, 1.0}; wheels = kinematics.inverseKinematics(rotation); printf("旋转: V1=%.3f V2=%.3f V3=%.3f V4=%.3f\n", wheels.v1, wheels.v2, wheels.v3, wheels.v4); return 0; }
|
六、总结
6.1 核心公式
逆向运动学:
V1V2V3V4=211−1−1111−1−12L2L2L2Lvxvyωz
前向运动学:
vxvyωz=4122L1−22L1−2−2L12−2L1V1V2V3V4
6.2 性能参数
| 运动模式 |
最大速度 |
| 前进/后退 |
2⋅Vmax |
| 侧向平移 |
2⋅Vmax |
| 原地旋转 |
LVmax |