☰
麦克纳姆轮底盘开环控制与PS2遥控实战:从运动学解算到四轮全向移动
2026/10/29 1:40:01 网站建设 项目流程

简介:基于STM32F103ZET6的麦克纳姆轮底盘开环控制方案,面向STM32学习者和机器人爱好者,解决PS2手柄遥控底盘移动的问题。资源采用PS2手柄作为输入,搭配L298N驱动板,包含控制原理图与完整代码,可以直接在Keil工程中打开使用。压缩包共212个文件,以.c/.h源文件、编译生成的.o/.crf/.hex/.axf等Keil工程文件为主,另有原理图png与说明文档txt/htm,整体仅7.26MB,目录结构直观,便于快速定位关键代码。项目中的PS2驱动代码经实测可用,能完成底盘前后左右等基本控制,但存在轻微延迟,适合不苛求实时响应的入门应用;作者还注明另有低延迟版本可另行下载,并留有联系方式供技术交流。目前已有845人学习下载,对想理解PS2通信协议、L298N电机驱动以及开环底盘控制逻辑的学习者而言,是一份完整的参考资料。 做麦克纳姆轮底盘这类移动平台,圈内有个普遍共识:第一次调通四个轮子真的能朝任意方向跑起来的那一刻,远比什么性能参数都上瘾。但真正把“麦克纳姆轮底盘(开环,PS2控制)”这套组合做扎实,很多人卡在了运动学解算和遥控信号处理上——不是车不走,就是走起来像喝醉了酒,或者转向时原地打转。

这篇文章是我自己从零搭过、拆过、改过的完整记录。以开环控制为基础,PS2手柄做遥控输入,重点讲清楚麦克纳姆轮的运动学解算怎么做、PS2通信怎么接、整车怎么从代码到落地跑起来。适合正在做竞赛小车、机器人课程项目、或者单纯想折腾一台全向移动底盘的朋友参考,也适合手里已经有一套底盘但四个轮子“各跑各的”的人排查问题。

1. 麦克纳姆轮底盘的核心原理与方案选型

1.1 麦克纳姆轮为什么能“横着走”:辊子与受力方向

先打消一个常见误解:麦克纳姆轮不是每个轮子都能自己转向。真正让它实现全向移动的,是轮毂外圈斜装的一排小辊子。标准麦克纳姆轮上,每个辊子与轮毂轴线成45度角,辊子是被动转动的,不提供驱动力。这样一来,轮子转动时,地面的摩擦力沿辊子轴线方向分解,实际产生的驱动力并不在轮子正前方,而是斜45度方向。

把四个这样的轮子按一定布局装在底盘四角,每个轮子都给一个不同方向和速度的旋转指令,四个斜向力合成起来,就能让底盘实现前后、左右、斜向、原地旋转等自由度。打个不太严谨但好记的比方:每个麦克纳姆轮像是一个“单向滑靴”,四个滑靴朝不同方向使劲,最终车身就往合力方向走。

这里有个核心参数需要注意:辊子角度。默认选45度,也有30度或更极端的设计,但STM32、Arduino配套的教材、开源库、轮毂供应商默认都是45度,没特殊需求别乱换。改装轮子前,先看辊子是否完好、轴承是否顺滑,不然解算做得再准,轮子本身在打滑或卡滞,整车表现照样不正常。

1.2 开环控制:舍掉反馈,换来简单与响应

开环控制的意思是,系统只按给定指令输出,不关心轮子实际转速是否达到预期。类比开车时只踩油门不看速度表,你给信号,车能走,但实际速度和负载有关。

在麦克纳姆轮底盘上,开环带来的最大问题就是四个轮子负载不均匀时,实际转速会不一致,导致跑偏。那为什么还要选开环?两个原因:其一,入门和调试阶段,开环足够验证运动学解算是否正确,先把方向搞对,再谈闭环精度;其二,开环省掉编码器、电机驱动板的电流环配置,大幅降低成本和代码复杂度,用一块Arduino或者入门级STM32就能跑起来。

我个人的建议是:开环适合18V以下的中小型底盘、室内平路场景。如果后续要上载重、跑户外不平地面,就得加编码器做闭环了。但开环做得好不好,直接影响后续加闭环的效果——运动学模型错了,闭环也救不回来。

1.3 遥控方案对比:为什么是PS2手柄

市面常见的遥控方式有蓝牙串口模块、2.4G无线模块、普通航模遥控器、PS2手柄。我最终选PS2手柄,核心原因是性价比和手感。

PS2手柄(二手拆机或国产兼容)加上接收器,成本二三十块钱,却提供两个模拟摇杆、十几个按键,SPI协议通信,抗干扰能力也不错。相比蓝牙串口,PS2不需要配对和AT指令配置,上电几秒就能用;相比航模遥控器,PS2接收器直接输出数字量,不需要额外接PPM解码电路。

当然PS2方案也有它的坑,比如接收器供电电压和逻辑电平问题、部分国产手柄协议兼容性等,这些后面专门讲。总的来说,PS2手柄+开环底盘,是一套低成本、验证运动控制逻辑的黄金组合。

2. 运动学解算:让四个轮子听懂摇杆

2.1 坐标系约定与速度分解

麦克纳姆轮底盘的全向移动,本质上是将底盘期望的合速度分解到四个轮子的线速度。首先约定坐标系:

  • x轴:车身正前方
  • y轴:车身正左方
  • 逆时针旋转方向为正

底盘需要三个输入量:vx(前后速度)、vy(左右速度)、ω(旋转角速度)。这三个量从哪里来?PS2手柄的左摇杆控制vx和vy,右摇杆控制ω,这是最常见的映射方式。

以底盘几何中心为原点,四个轮子到中心的距离在x和y方向的分量分别记为l(前后方向半间距)和w(左右方向半间距)。这两个值直接影响解算公式中的系数,所以要量准。我自己的底盘轮距约220mm,轴距约200mm,对应l≈110mm,w≈100mm,代入后面的公式就能算。

2.2 逆运动学公式与四轮速度计算

麦克纳姆轮逆运动学解算,即已知vx、vy、ω,求每个轮子的速度。对于X型布局,左前、右前、左后、右后四个轮子的速度公式如下(统一单位,角速度符号通常取逆时针为正):

v1(左前)= vx - vy - ω × (l + w) v2(右前)= vx + vy + ω × (l + w) v3(左后)= vx + vy - ω × (l + w) v4(右后)= vx - vy + ω × (l + w)

注意不同文章可能因为坐标系方向或辊子安装方向不同,符号会有差异。更重要的是轮子安装方向的A/B轮问题:麦克纳姆轮分A轮(辊子左上到右下)和B轮(辊子右上到左下)。常见的X型安装布局下,左前、右后用A轮,右前、左后用B轮。装反了,整车的运动方向会完全相反或者扭起来。

解算公式里的vx、vy、ω单位建议统一。实际代码里,摇杆输入的范围是-128到127,我会直接把这个范围映射到PWM输出范围,所以公式里的vx、vy、ω也用了“-127到127”的整数域,这样省去浮点运算,在Arduino这类单片机上跑更高效。

注意:公式中的ω乘以(l+w)后,量纲是速度。如果ω的取值范围也定在-127到127,那么旋转速度和平移速度的“权重”实际上由(l+w)这个系数决定,整车转向快慢和直线快慢的比例就体现在这里。调车时如果发现转向太猛或太弱,优先调ω的缩放系数,而不是改死区。

2.3 摇杆信号映射与死区处理

PS2手柄的模拟摇杆,中位输出理论上在128附近,实际会有偏移。如果不处理死区,手柄回中后摇杆读数可能停在120到135之间,导致车辆缓慢漂移。这在开环系统里尤其明显——因为没有反馈修正。

我的处理方式是设定dead zone:读数与128的差小于15(约12%)时,按0处理。这样既能容忍摇杆磨损和中位偏移,又不会让刚推一点摇杆时毫无反应。死区过大也会影响操控细腻度,我用15这个值在多数PS2兼容手柄上测下来手感比较平衡。

另外,摇杆方向通常和期望运动方向一致:左摇杆向上推,vx为负(因为约定前为正,而手柄往上推代表后退),这个映射关系最好用代码注释写清楚,不然后续调方向会把自己绕晕。建议先在代码里把摇杆值打印出来,确认真实读数范围再定映射。

3. 硬件搭建与PS2遥控配置

3.1 底盘选型与供电设计(附清单)

开环底盘对机械精度要求不算极端,但轮子安装孔位要正、四个轮子触地压力要尽量一致。我用的底盘是网上常见的那种铝合金板+四支麦克纳姆轮,单个轮子直径约60mm,搭配TT电机或者带减速箱的N20电机都可以。若负载小、室内跑,TT电机的扭矩够用,但精度一般;N20电机更稳,但转速可能偏慢。关键还是看减速比和额定电压。

供电是整个项目最容易翻车的一环。四个电机同时堵转或启动瞬间,电流可能冲到5A以上,主控板一旦被拉低电压就会重启,PS2接收器也会掉线。我用了7.4V 2S锂电直接给电机驱动板(如TB6612或L298N)供电,再通过降压模块输出5V给Arduino和PS2接收器。这里要特别强调:PS2接收器必须由稳定的5V供电,不能直接接在电机电源上,否则电机启动瞬间的毛刺会让它直接断连。

我在实际调试中最初用的是一块USB供电的Arduino,结果一推杆电机一转,上位机就掉串口,后来才意识到是电源问题,换成独立降压供电后整机稳定了很多。

3.2 PS2接收器接线与SPI通信要点

PS2接收器引脚一般有:VCC、GND、DAT、CMD、SEL、CLK,共6个。其中DAT、CMD、CLK、SEL这四根线和主控的SPI或模拟SPI相连。Arduino Uno建议接法如下:

PS2接收器引脚接到主控引脚
VCC5V(必须稳定)
GNDGND
DAT数字引脚12(SPI MISO)
CMD数字引脚11(SPI MOSI)
SEL数字引脚10(SPI SS)
CLK数字引脚13(SPI SCK)

用Arduino的PS2X库时,初始化通常写成:

#include <PS2X_lib.h> PS2X ps2x; void setup() { ps2x.config_gamepad(10, 11, 12, 13, true, false); }

config_gamepad的四个引脚参数依次是SEL、CMD、DAT、CLK,和接线顺序不同,容易写反。我初次接的时候就是这里搞反,导致手柄数据全是255,踩过坑。

3.3 装配与轮系X型布局检查

轮系布局直接决定运动学公式是否成立。麦克纳姆轮安装前,先区分A轮和B轮。一种快速判断方法:把单个轮子放在平地上,用手沿车身横向推一下,如果轮子本身很自然地“往斜前方滑”,那就能看出辊子朝向。普通轮子是被推着直走的,麦克纳姆轮是斜着走的,方向和辊子朝向有关。

安装完成后的检查方法:

  1. 抬起底盘,用手给四个轮子同时同方向转动,观察四个轮子的辊子触地方向是否形成一个X型。
  2. 上电后下发一个“直行后退”指令,观察车是否走直线。
  3. 如果车斜着走,大概率是某个轮子的A/B装反,或者运动学公式中对应位置的符号错了。

轮子装好后,确认四个轮子都能自由转动、辊子滚动顺畅。麦克纳姆轮对地面平整度要求高,地面上有石子或台阶都会让它颠簸甚至打滑,这也是开环系统无反馈时表现“像抽风”的常见原因之一。

4. 代码实现:从解算到出轮

4.1 代码结构:解算、通信、输出三层分离

写代码之前先把结构想清楚,别一股脑全塞在loop里。我的代码分三层:

  • 输入层:读取PS2手柄摇杆、按键状态,转换为vx、vy、ω期望值
  • 解算层:根据运动学公式,把vx、vy、ω换算成四个轮子的目标速度值
  • 输出层:把目标速度映射为PWM占空比和方向电平,驱动电机

分层的好处是后续升级闭环时,只需要改输出层,或者把解算层的结果送入PID控制器,其他部分不用大改。即使现在不做闭环,分层结构调试起来也清晰得多,比如某个轮子反了,直接在输出层反过来就行,不用翻解算代码。

4.2 核心代码实操(PS2初始化 + 运动学解算 + PWM输出)

下面给一个Arduino环境的核心代码框架。这个框架我实测过,用的是TB6612驱动,左右各一路PWM控制速度,方向由两个IO口控制。

#include <PS2X_lib.h> #define PS2_DAT 12 #define PS2_CMD 11 #define PS2_SEL 10 #define PS2_CLK 13 // TB6612引脚 #define AIN1 2 #define AIN2 3 #define BIN1 4 #define BIN2 5 #define PWMA 6 #define PWMB 9 PS2X ps2x; int LX, LY, RX; int vx, vy, w; int deadZone = 15; // 底盘尺寸参数(单位:mm,经过换算后的综合系数) // 这里不使用真实毫米,而是把摇杆范围-127~127直接映射为输入范围 // l + w 根据实际底盘大约为210mm,但要缩放成与摇杆匹配的一个系数 float scaleRotate = 1.0; // 转向灵敏度系数,实际可用0.5~1.2调整 void setup() { Serial.begin(9600); pinMode(AIN1, OUTPUT); pinMode(AIN2, OUTPUT); pinMode(BIN1, OUTPUT); pinMode(BIN2, OUTPUT); pinMode(PWMA, OUTPUT); pinMode(PWMB, OUTPUT); // 第三个参数是configure旋钮模式,我习惯设true // 如果发现手柄读不到数据,先尝试调低SPI时钟(第四参数) int err = ps2x.config_gamepad(PS2_SEL, PS2_CMD, PS2_DAT, PS2_CLK, true, false); if (err != 0) { Serial.print("PS2 init error: "); Serial.println(err); } } void loop() { ps2x.read_gamepad(false, false); LX = ps2x.Analog(PSS_LX); LY = ps2x.Analog(PSS_LY); RX = ps2x.Analog(PSS_RX); // 摇杆范围0~255,中位128;转换到-127~127 vx = map(LY, 0, 255, 127, -127); vy = map(LX, 0, 255, -127, 127); w = map(RX, 0, 255, -127, 127); // 死区处理 if (abs(vx) < deadZone) vx = 0; if (abs(vy) < deadZone) vy = 0; if (abs(w) < deadZone) w = 0; // 运动学解算 // 注意:如果发现轮子方向反了,把对应项的符号反一下 float v1 = vx - vy - w * scaleRotate; float v2 = vx + vy + w * scaleRotate; float v3 = vx + vy - w * scaleRotate; float v4 = vx - vy + w * scaleRotate; // 输出到电机 setMotor(AIN1, AIN2, PWMA, v1); setMotor(BIN1, BIN2, PWMB, v2); // 实际四个电机需要两路PWM+方向控制,或者四路PWM // 我这里只展示一路的控制逻辑,四个轮子逐一对应即可 } void setMotor(int in1, int in2, int pwmPin, int speedValue) { speedValue = constrain(speedValue, -255, 255); bool forward = speedValue >= 0; digitalWrite(in1, forward ? HIGH : LOW); digitalWrite(in2, forward ? LOW : HIGH); analogWrite(pwmPin, abs(speedValue)); }

真实项目中,四个电机要四路PWM,比如用TB6612FNG的双路输出,两块驱动板分别带左右各两个轮。上面代码只展示单路控制逻辑,展开后每个轮子都有自己的一组in1、in2、PWM引脚。

4.3 转向模式设计:原地转、差速转、全向移动

PS2右摇杆控制ω,默认是原地旋转模式:vx和vy都为0时,ω不为0则底盘绕中心原地旋转。这种模式适合狭小空间,但很多新手觉得太灵敏,一推就转得飞快。解决办法是给ω加一个平方映射或线性缩放,让轻微推杆时转向更柔和。

还有一种更自然的操控模式叫“差速转”:车子在前进的同时叠加旋转,右摇杆控制转向幅度,左摇杆控制前进方向。这种模式下,车身会根据转向角度自动画弧线,适合竞速类场景。实现方式很简单,在解算前把ω按一定比例叠加到vx、vy上。但开环下这种模式需要谨慎调比例,否则容易出现推杆过猛导致的甩尾。

我的习惯是做个模式切换按键(比如按下SELECT切换),一套代码里同时保留“全向模式”和“竞速模式”。全向模式下摇杆直接映射vx、vy、ω,竞速模式下右摇杆控制ω,左摇杆控制前进后退和左右平移,但平移优先级降低,保证转弯顺滑。

5. 常见问题与排查技巧实录

5.1 轮子打滑、机身抖动

现象:启动瞬间轮子有动作但车身不走,或者低速行驶时一顿一顿的。

排查顺序:

  1. 检查辊子是否卡住。麦克纳姆轮用久了,辊子轴承里容易进灰,或者辊子磨损后转动不顺,这时轮子在地面上是“蹭”而不是“滚”。
  2. 检查PWM输出频率。如果用的是analogWrite默认的490Hz左右,部分电机和驱动板在低速时扭矩输出不线性,抖动明显。可以改用更高频率PWM(比如用定时器配置到20kHz以上)再看效果。
  3. 检查驱动板使能脚是否接对。TB6612如果不拉高使能脚,输出是无效的,电机就在原地嗡嗡响但不出力。

开环系统对机械阻力差异非常敏感。我遇到过换了新轮子后,四个轮子里有两个明显“紧”,结果车速一慢就往一边偏,后来换了轴承才好。

5.2 跑偏与四轮速度不一致

现象:直行指令下,车不走直线,而是偏向一侧。

开环下跑偏很难完全消除,但能改善到可接受范围。首先要排除机戒问题:四个轮子触地是否一致?底盘四角高度是否一样?有的底盘用尼龙柱固定,时间长了会变形导致某个轮子悬空,那这个轮子彻底不参与运动,整车只能歪着走。

然后是代码层面:检查运动学公式里的符号。如果四个轮子速度都是正向,但车体在转,那是某一侧轮子的方向接反了,直接把对应轮子的方向逻辑取反即可。如果车斜着走,大概率是轮系布局和公式不匹配。

最后,如果以上都没问题,再考虑加软修正:比如在代码里给每个轮子一个独立的scale因子,手动校准到直行。开环方案的跑偏,可以用这种“每个轮子人工微调占空比”的方式压到肉眼看不太出来的程度。

5.3 PS2手柄断连、无响应

现象:开始时正常,跑一阵后手柄失效;或者一上电就连接不上。

我总结的三大主因:

  • 供电脏。电机启停瞬间,电源毛刺直接灌进PS2接收器的VCC,导致它丢数据。解决方法是PS2接收器的VCC独立走线,并且加一个100uF左右电容。
  • SIGNAL线过长或接触不良。PS2接收器到主控的排线不要超过15cm,尽量用短杜邦线,太长会增大信号干扰。另外杜邦线频繁插拔容易虚接,我后来直接焊死在PCB上。
  • 库的初始化问题。PS2X库有两个版本的兼容问题,有的手柄要求config_gamepad第四参数(时钟速度)设为false(低速),有的则必须为true。遇到初始化报错,试试切换这个参数。

实测下来,PS2手柄连接成功后,只要供电稳定,跑一整个下午都不会断。如果频繁断,九成是电源问题,别先怀疑手柄。

5.4 供电不足与电流跌落

现象:电机全速前进时主控重启,或者启动瞬间车没反应,过一会儿才缓缓动起来。

这个问题的本质是锂电内阻和降压模块瞬时响应不够。直接用2S锂电供电时,7.4V经过电机驱动板给电机,理论上没问题。但Arduino的5V如果是从驱动板的5V输出引出来的,而驱动板又是线性稳压,电机大电流时热量和压降都会很严重。

我的最终方案是:电机独立供电,主控和PS2接收器走独立的5V降压模块(比如MP1584或AMS1117方案小模块)。哪怕电池电压掉到6.5V,主控仍然稳定,PS2不重启。这也是整个项目中回报最高的一项改动——如果只能给读者留一条经验,我会选这一条。

6. 实测心得与后续扩展方向

做完这套开环底盘后,我最大的感受是:麦克纳姆轮的运动学公式并不复杂,真正让整车“听话”,功夫全在细节——轮子装配正不正、PS2引脚接没接对、供电扛不扛得住大电流、死区调得合不合理。

如果您接下来想继续玩下去,我认为最值得扩展的方向是加编码器和IMU做闭环。开环底盘在平地上摆摆造型、跑跑路线演示完全够用,但一旦要精确走固定轨迹、或者载重上坡,轮子打滑导致的实际位移和期望位移偏差会被累计放大。加了编码器后,可以按PID闭环控制每个轮子的实际转速,再加个MPU6050做转向角度反馈,整车的可玩性和精度立刻上一个台阶。

最后分享一个小技巧:调试开环底盘时,找个手机水平尺贴在底盘中心,观察车身在不平地面上是否水平,会比肉眼看来得准得多。底盘一旦翘角,不管代码怎么写,运动学模型都是错的——先把机械找平,再谈算力与控制。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询