)
1. 全向轮与麦克纳姆轮底盘基础认知第一次接触全向移动底盘时我被它灵活的走位惊艳到了——传统小车需要反复调整方向才能到达的位置这种底盘居然能直接横着走后来才知道这要归功于两种特殊轮子全向轮和麦克纳姆轮简称麦轮。它们虽然都能实现全向移动但原理和特性却大不相同。全向轮长得很像超市购物车的万向轮升级版轮子外围有一圈小滚轮。这些小滚轮的轴线与主轮轴线垂直就像给轮子穿了一条滑轮腰带。当主轮转动时小滚轮可以自由侧向滚动这就解除了传统轮子的非完整约束。常见的布局有三轮120°对称分布和四轮十字分布我最早做的三轮底盘成本低但容易打滑后来改用四轮稳定性明显提升。麦克纳姆轮则更像斜着安装的齿轮它的辊子与轮毂呈45°夹角。这种巧妙设计让地面接触点的速度矢量产生神奇分解当四个麦轮按特定组合转动时辊子产生的侧向分力会相互叠加或抵消最终合成任意方向的移动。RoboMaster比赛中大部分战队都采用这种方案因为它在保持灵活性的同时安装方式与传统四轮底盘完全兼容。实际选型时要特别注意两者的差异承载能力麦轮的金属辊子比全向轮的塑料滚轮更耐造地面要求全向轮在粗糙路面表现更好麦轮则需要平整硬质地面控制精度麦轮的运动合成更精确适合需要精准定位的场景维护成本全向轮的小滚轮容易卡进杂物需要定期清理// 典型轮子定义示例 typedef enum { OMNIDIRECTIONAL_WHEEL 1, // 全向轮 MECANUM_WHEEL 2 // 麦克纳姆轮 } WheelType;2. 运动学逆解核心原理拆解要让底盘按指定轨迹运动关键是把底盘的整体运动分解到每个轮子上这就是运动学逆解要解决的问题。想象你在推一个办公椅往前推时所有轮子都正转横向推时部分轮子要反转旋转时左右轮转向相反——全向底盘的控制逻辑类似只是数学描述更复杂。首先需要建立坐标系系统。我在代码里定义了两个坐标系场地坐标系世界坐标系固定不动Z轴垂直地面向上机体坐标系随底盘运动X轴指向车头方向当底盘旋转时这两个坐标系会产生夹角α。这里有个易错点陀螺仪返回的角度范围是-180°~180°而三角函数计算需要弧度值记得做好单位转换。我吃过这个亏当时底盘旋转时总会出现诡异的蛇形走位调试半天才发现是角度转弧度时没处理负值。运动分解的本质是速度矢量合成。底盘的运动可以分解为三个分量Vx前后方向速度m/sVy左右方向速度m/sω自转角速度rad/s对于三轮全向底盘运动方程是这样的M1 Vx*cos(60°α) Vy*sin(60°α) - ω*R M2 Vx*cos(60°-α) - Vy*sin(60°-α) - ω*R M3 -Vx*cos(α) - Vy*sin(α) - ω*R其中R是轮子到底盘中心的距离。看到这里你可能发现了问题当α90°时M3的计算会出现奇异点。这就是为什么实际项目中我更推荐四轮布局。3. 电机控制方程代码实现理论懂了接下来就是把它写成嵌入式代码。以STM32为例我们需要把数学公式转化为高效的C语言代码。这里有三个优化重点1. 三角函数优化标准库的sin/cos函数计算耗时太长我用泰勒展开做了近似计算float fast_sin(float x) { // 将角度限制在0~π之间 while(x 2*PI) x - 2*PI; if(x PI) return -fast_sin(x-PI); // 泰勒展开近似 float x2 x*x; return x*(1 - x2/6.0f x2*x2/120.0f); }实测在-π~π范围内这个函数的误差小于0.5%而计算时间只有标准库的1/8。2. 坐标系转换当使用陀螺仪实现无头模式时需要处理机体坐标系与场地坐标系的转换void body_to_world(float vx, float vy, float* world_vx, float* world_vy, float yaw) { float rad yaw * PI / 180.0f; *world_vx vx * cosf(rad) - vy * sinf(rad); *world_vy vx * sinf(rad) vy * cosf(rad); }3. 电机速度合成下面是四轮麦轮的完整控制函数void mecanum_control(float vx, float vy, float omega) { // 读取当前偏航角度 float yaw imu_get_yaw(); // 转换为机体坐标系速度 float body_vx, body_vy; world_to_body(vx, vy, body_vx, body_vy, yaw); // 计算四个电机转速归一化到-1~1 float wheel_speed[4]; wheel_speed[0] body_vx - body_vy - omega; // 右前 wheel_speed[1] body_vx body_vy - omega; // 左前 wheel_speed[2] -body_vx body_vy - omega; // 左后 wheel_speed[3] -body_vx - body_vy - omega; // 右后 // 限幅处理 for(int i0; i4; i) { wheel_speed[i] constrain(wheel_speed[i], -1.0f, 1.0f); } // 设置电机PWM set_motor_pwm(MOTOR_FL, wheel_speed[0]); set_motor_pwm(MOTOR_FR, wheel_speed[1]); set_motor_pwm(MOTOR_BL, wheel_speed[2]); set_motor_pwm(MOTOR_BR, wheel_speed[3]); }4. 工程实践中的坑与解决方案在实际项目中我踩过不少坑。最难忘的是第一次测试麦轮底盘时输入(0.5, 0, 0)期望前进结果底盘却走出了一个漂亮的弧线...后来发现是轮子安装方向搞反了。麦轮有左旋和右旋之分正确的安装方式应该是四个轮子的辊子延长线交于底盘中心形成X型布局。另一个常见问题是速度归一化。当同时输入Vx和Vy时合成速度可能超过电机上限。我的解决方案是void normalize_speeds(float* speeds, int count) { float max 0; for(int i0; icount; i) { max fmaxf(max, fabsf(speeds[i])); } if(max 1.0f) { for(int i0; icount; i) { speeds[i] / max; } } }对于需要高精度控制的场景还要考虑运动学标定测量实际轮距Dx, Dy让底盘做纯旋转运动记录陀螺仪角速度ω调整运动学参数直到实际角速度与期望一致重复直线运动校准线速度增益最后分享一个调试技巧用LED灯带显示各电机转速。我在底盘四周装了RGB灯带红色代表反转绿色代表正转亮度对应转速大小。这样一眼就能看出哪个电机工作异常比看串口数据直观多了。