简介:面向STM32与麦克纳姆轮底盘开发者的开环控制方案,以STM32F103ZET6为主控,配合PS2手柄实现无线遥控,覆盖机器人竞赛、智能车课程设计、毕设入门等场景。资源内包含实测能用的PS2驱动代码(存在轻微延迟,逻辑完整)、L298N电机驱动原理图与底层控制代码,并给出完整Keil工程。工程基于标准外设库,涉及GPIO、定时器、PWM、串口等外设配置,源文件与头文件一一对应,可直接编译烧录。压缩包共212个文件,以C源码、H头文件、编译中间文件.o为主,另有PNG原理图、uvprojx工程配置、HEX烧录文件等,整体约7.26MB。读者可参考其L298N接线与控制逻辑,结合PS2按键映射快速移植到自己的车架;即便不打算用PS2,也能提取开环底盘的主循环调度思路。已有846人学习下载,适合需要快速搭建麦克纳姆轮开环底盘并验证遥控逻辑的开发者。
1. 麦克纳姆轮底盘:STM32 开环控制与 PS2 手柄的完整落地
做麦克纳姆轮底盘,最难的不是让轮子转,而是让四个轮子按你想要的矢量方向协同转动。很多初学者一上来就研究闭环 PID、编码器测速,结果被参数调得焦头烂额,车却跑不直。其实对于 PS2 手柄控制、场地巡检、竞赛演示这类场景,开环控制完全够用,而且调试周期能压缩到一晚上。这份资源的核心就是一套基于 STM32F103 的麦克纳姆轮底盘完整工程,含 PS2 接收解析、四路 PWM 驱动、斜向运动学解算,适合正在做智能车竞赛、课程设计或机器人入门项目的开发者,照着接线、烧录、配对就能跑起来。
全文按「硬件拆解 → PS2 协议 → 运动学代码 → 整合调试 → 进阶技巧」五个层次展开,中间穿插我实际调试时踩过的坑。你拿到的源码不是我临时拼的 Demo,而是从实际跑过比赛的车上扒下来的,每个函数都有存在的必要。
2. 底盘硬件选型:轮组布局、驱动板与 STM32 的资源分配
2.1 麦克纳姆轮的辊子朝向与轮组编号规则
拿到一套麦克纳姆轮,先别急着装。轮子上的辊子(小滚柱)是斜向 45 度排列的,这个斜向角度决定了整车运动学公式里的正负号。拆开包装看,辊子轴线与轮子主轴线的夹角有两种——左旋和右旋。标准装法是:俯视底盘,左前轮和右后轮用同一种旋向,右前轮和左后轮用另一种旋向。你要是装反了,车会往意想不到的方向飘,代码怎么调都救不回来。
我习惯按「左前 LF、右前 RF、左后 LB、右后 RB」编号,这四个轮子分别接 STM32 的四路 PWM 输出通道。每个轮子需要一个电机驱动芯片(常见的是 TB6612 或 DRV8833,我这份资源里用的是 TB6612),驱动板上的 IN1/IN2 控制电机正反转,PWM 引脚控制转速。底盘供电建议 7.4V 锂电池,经 5V BEC 给 STM32 供电,驱动板直接吃电池电压。
| 轮位 | 旋向(俯视) | PWM 定时器通道 | 电机驱动引脚 |
|---|---|---|---|
| 左前 LF | 左旋 | TIM1_CH1 | PA8 / PB13 |
| 右前 RF | 右旋 | TIM1_CH2 | PA9 / PB14 |
| 左后 LB | 右旋 | TIM1_CH3 | PA10 / PB15 |
| 右后 RB | 左旋 | TIM1_CH4 | PA11 / PB16 |
选 TIM1 是因为它有四路互补通道,带死区插入,虽然开环用不到互补输出,但如果你后续要加电流反馈或刹车逻辑,TIM1 的硬件资源更充裕。PA8~PA11 是 TIM1 的默认映射引脚,不冲突的话别重映射。
2.2 开环控制为什么够用:场景边界与误差容忍度
开环不是「没有反馈」,而是「不需要高精度反馈」。PS2 手柄的摇杆是个模拟量,人手操作本身就带 ±10% 的误差,你就算把轮速闭环到 1% 精度,操作手感也不会变好多少。开环适合的场景有三个特征:速度不高(低于 1.5m/s)、地面平坦(室内地砖或短毛地毯)、运行时间短(单次连续行驶不超过 10 分钟)。这三个条件同时满足时,开环的漂移量肉眼几乎看不出来。
反过来,如果你要做的是循迹、里程计定位、视觉跟随,那就必须上编码器闭环。麦克纳姆轮的辊子与地面是点接触,打滑率比普通轮子高,开环跑 20 米后横漂可能累计到半米。所以拿到这份资源,先别改代码,先确认你的使用场景在不在开环的舒适区里。
2.3 从 CubeMX 初始化到 PWM 输出:一套可直接跟做的配置流程
我用的开发环境是 STM32CubeMX + Keil MDK。CubeMX 里只需要三块配置:RCC 外部晶振、TIM1 的 PWM 输出、一个普通 GPIO 作为 PS2 接收(或者用 USART 接收,但 PS2 手柄是 SPI 类时序,更多地是软件模拟时序)。具体说:
先配置时钟树,把 APB1 外设时钟设为 36MHz,APB2 设为 72MHz,TIM1 挂在 APB2 上,这样 PWM 频率可以做高一些。TIM1 的 PSC 设为 71,ARR 设为 999,得到 72MHz / (71+1) / (999+1) = 1kHz 的 PWM 频率,对应电机驱动板的开关频率刚好在听不见的频段。
// 以下为 CubeMX 生成后微调的关键参数 htim1.Init.Prescaler = 71; // 预分频:72MHz / 72 = 1MHz htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 999; // 自动重载:1MHz / 1000 = 1kHz htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; // PWM 通道初始化参数 sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 0; // 初始占空比为 0,防止上电猛冲 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;参数说明:Prescaler 和 Period 决定了 PWM 频率。1kHz 是大多数直流电机驱动芯片的甜点区,太低会听到电机啸叫,太高会增大驱动芯片的开关损耗。Pulse 初始化为 0 是血泪教训——之前有一次上电时 Pulse 默认是 255,四个电机全速转,车直接从桌上飞出去了。另外注意,TIM1 是高级定时器,必须在__HAL_TIM_MOE_ENABLE(&htim1)之后 PWM 才会真正输出到引脚,这是新手最容易漏的一步。
3. PS2 手柄通信协议:从物理层到按键数据的完整拆解
3.1 手柄工作模式与 SPI 时序特点
PS2 无线手柄(带接收器那种)和 STM32 之间走的是类似 SPI 的串行协议,时钟线、数据线、命令线、片选线各一根。接收器上电后默认处于模拟模式(红灯常亮),这个模式下摇杆的模拟量才会输出——如果你发现摇杆数据全是 0x7F(中间值),先检查手柄是不是处于数字模式(绿灯),数字模式只输出按键,不输出摇杆模拟量。
协议层的关键是命令帧:主机每发送一个字节,从机返回一个字节。完整的数据读取过程需要发送 9 个命令字节,对应接收 9 个响应字节,其中第 3、4 字节是按键状态,第 5、6 字节是右摇杆和左摇杆的 X/Y 模拟量。整个时序用 GPIO 模拟即可,不需要硬件 SPI,因为 PS2 的时钟不是标准 SPI 极性,用硬件 SPI 反而要处理 CPOL/CPHA 匹配问题。
// PS2 接收器引脚初始化(软件模拟时序) // DI 接 PA4,DO 接 PA5,CS 接 PA6,CLK 接 PA7 // 发送一个命令字节并读取一个响应字节 unsigned char PS2_ByteRead(unsigned char command) { unsigned char res = 0; for (int i = 0; i < 8; i++) { if (command & (0x01 << i)) { PS2_DI_HIGH(); } else { PS2_DI_LOW(); } PS2_CLK_FALLING(); // 时钟下降沿,手柄采样命令位 delay_us(4); res |= (PS2_DO_READ() << i); // 数据线上读回响应位 PS2_CLK_RISING(); delay_us(4); } return res; }逻辑说明:命令字节从低位开始逐位发送,每发一位拉低一次时钟,手柄在该下降沿锁存命令位,同时把响应位放到数据线上,主机在同一时刻读取。delay_us(4)是经验值,PS2 协议要求时钟高电平持续时间不低于 2 微秒,留一倍余量防止线材电容导致边沿变缓。你要是用杜邦线超过 20cm,把这个延时加到 6 微秒更稳。
3.2 摇杆数据处理:死区过滤与线性映射
摇杆返回的原始值是 0x00~0xFF,中间值 0x7F。PS2 手柄的摇杆物理结构决定了它回中之后不一定精确停在 0x7F——我拆过三只手柄,两只回中是 0x7C,一只回中是 0x83。如果把原始值直接送给运动学公式,车会在原地缓慢漂移。所以第一步必须做死区过滤:把差值绝对值小于 8 的区间视为 0。
#define JOY_DEAD_ZONE 8 // 死区阈值,需根据实际手柄标定 #define JOY_MID_VALUE 0x7F // 理论中值 int16_t Joy_Process(uint8_t raw_value) { int16_t delta = (int16_t)raw_value - JOY_MID_VALUE; if (delta > -JOY_DEAD_ZONE && delta < JOY_DEAD_ZONE) { delta = 0; } return delta; // 输出范围 -127 ~ +127 }第二个坑是线性映射。摇杆推到极限时输出是 0xFF,差值是 128,但电机 PWM 占空比如果直接给 128,在 1kHz 频率下电机转速很低——因为 0~128 正好落在电机的死区电压附近。我一般会把 0~127 的摇杆差值映射到 PWM 的 20~130 区间,留出余量给运动学公式叠加:四路 PWM 最终输出是四个方向分量的叠加,如果映射到满量程 0~255,叠加后必然溢出。映射关系是PWM = 20 + (abs(delta) * 110 / 127),方向由 IN1/IN2 的电平决定。
3.3 PS2 库的配置细节:Proefile 数组与四个坑
网上流传的 PS2 库很多,我用过的版本里最稳定的还是那个带PS2X类的库,但移植到自己的板子上需要改三个地方:引脚宏定义、延时函数、以及内存分配。库内部有个Proefile数组用于缓存按键状态,在某些编译优化等级下(比如 -O2),局部数组没初始化会导致按键数据是随机值。解决方法是把这个数组声明为全局数组并显式置零:
// 移植 PS2 库时的关键修改点 static unsigned char Proefile[9] = {0}; // 必须全局 + 初始化 // 初始化 PS2 手柄 void PS2_Init(void) { PS2_CS_HIGH(); PS2_CLK_HIGH(); delay_ms(10); // 等手柄接收器上电稳定 PS2_ByteRead(0x01); // 进入配置模式 PS2_ByteRead(0x42); // 读取数据命令 PS2_ByteRead(0x00); PS2_ByteRead(0x00); PS2_ByteRead(0x00); delay_ms(20); // 配置切换需要时间 }参数说明:0x01是进入配置模式命令,0x42是读数据命令。如果你发现手柄初始化后数据全为 0xFF,大概率是这些命令字节没有按顺序发完,或者 CS 片选信号没有在整个读过程中保持低电平。还有一点容易忽略,PS2 接收器上电瞬间会自检,此时给它发命令会被忽略,所以初始化前必须加 10ms 延时。这个问题在资源和常见项目里很容易翻车,建议固化一个PS2_Init流程,不要在别的函数里重复调用初始化。
4. 麦克纳姆轮运动学:从遥杆矢量到四轮独立转速的映射
4.1 运动学公式的推导逻辑与正负号约定
麦克纳姆轮底盘的运动学核心就一个公式:任意方向的平面运动可以分解为三个独立分量——前后平移(X 轴)、左右平移(Y 轴)、原地旋转(Z 轴)。每个轮子的转速是这三个分量的线性组合。设前方为 Y 轴正方向,右侧为 X 轴正方向,逆时针旋转为 Z 轴正方向,则四个轮子的目标转速是:
LF 轮速 = +Y分量 + X分量 + Z分量 RF 轮速 = +Y分量 - X分量 - Z分量 LB 轮速 = -Y分量 - X分量 + Z分量 RB 轮速 = -Y分量 + X分量 - Z分量注意观察正负号:左右两侧的轮子对 X 分量的符号相反,对 Z 分量的符号也相反,这正是斜向辊子产生侧向力的结果。代码里把这个公式展开成一个宏或内联函数,比先算旋转矩阵再调库函数更直观,也省掉浮点运算的开销。
// 运动学映射函数:输入操纵杆矢量 (joy_x, joy_y) 与旋转量 rotate // 输出四路 PWM 占空比 void Kinematic_Map(int16_t joy_x, int16_t joy_y, int16_t rotate) { int16_t m1 = joy_y + joy_x + rotate; // LF 轮速 int16_t m2 = joy_y - joy_x - rotate; // RF 轮速 int16_t m3 = - joy_y - joy_x + rotate; // LB 轮速 int16_t m4 = - joy_y + joy_x - rotate; // RB 轮速 // PWM 占空比限幅,防止叠加后超限 m1 = Clamp(m1, -MOTOR_MAX, MOTOR_MAX); m2 = Clamp(m2, -MOTOR_MAX, MOTOR_MAX); m3 = Clamp(m3, -MOTOR_MAX, MOTOR_MAX); m4 = Clamp(m4, -MOTOR_MAX, MOTOR_MAX); Motor_SetPWM(MOTOR_LF, m1); Motor_SetPWM(MOTOR_RF, m2); Motor_SetPWM(MOTOR_LB, m3); Motor_SetPWM(MOTOR_RB, m4); }逻辑说明:joy_y推杆向前推为正,车前进;joy_x向右推为正,车右平移;rotate为正值时逆时针旋转。这个正负号约定必须和你的轮子安装旋向对应上。如果你的车装上后推杆向前但车向后,把joy_y的符号取反即可,不要改公式里四个轮的排列——改排列容易漏改导致对角轮转向矛盾。
4.2 斜向运动(全向平移)与原地旋转的实现差异
麦克纳姆轮的招牌动作是「斜向 45 度平移」——推杆同时向前和向右推,车头朝向不变,整体向右前方平移。这个动作的本质是让 X 分量和 Y 分量同时参与叠加,四个轮子的转速各不相同。很多新手在这里会犯一个错:以为斜向移动就是把左右轮设成不同速度、后轮保持不动,那是差速转向的思路,用在麦轮上会导致车轮侧滑加剧、电机过流。
原地旋转则比较简单:前后轮对旋转分量的贡献方向一致,但左右侧相反。注意原地旋转时不要同时推平移摇杆,否则合成运动是「旋转 + 平移」的螺旋轨迹,视觉上很不稳。我从赛场上学到的是:在代码里加一个互斥逻辑——rotate非零时,将joy_x、joy_y直接清零,这样手柄怎么推都不会出现意外的螺旋。
// 旋转与平移的互斥逻辑,防止遥杆误触导致螺旋轨迹 void Kinematic_Update(void) { int16_t joy_x = Joy_Process(PS2_GetData(JOY_RX)); int16_t joy_y = Joy_Process(PS2_GetData(JOY_RY)); int16_t rotate = Joy_Process(PS2_GetData(JOY_LX)); if (abs(rotate) > 20) { // 旋转优先 joy_x = 0; joy_y = 0; } Kinematic_Map(joy_x, joy_y, rotate); }参数说明:右摇杆 X 控制左右平移,右摇杆 Y 控制前后移动,左摇杆 X 控制旋转。这个分配是我试了几种方案后最顺手的一套:右手负责方向,左手负责姿态,跟玩射击游戏的直觉一致。你在自己的代码里不必照搬,但建议保持「移动和旋转分属两个摇杆」的原则,否则单手同时控制平移和旋转非常容易误触。
4.3 斜向运动时 PWM 输出与电压的关系
这里有一个开环控制特有的问题:电池电压从满电 8.4V 降到 7.2V 时,同样占空比下电机转速会掉 15% 左右。开环系统没有测速反馈,所以这 15% 的掉速会直接表现为车越跑越慢、转向越来越飘。我的应对方案是给运动学加一个「电压补偿系数」——用 ADC 实时采样电池电压,低于 7.4V 时按比例放大 PWM 占空比。
uint16_t Battery_GetVoltage(void) { // 假设 ADC 通道 1 采样电池分压后的电压,分压比 2:1 uint16_t adc_val = ADC_GetValue(ADC_CH1); return (uint16_t)(adc_val * 2 * 3300 / 4095); // 单位 mV } // 电压低于 7400mV 时给 PWM 一个提升系数 uint16_t Voltage_Compensate(uint16_t pwm_out) { uint16_t volt = Battery_GetVoltage(); if (volt < 7400) { float ratio = 7400.0f / volt; pwm_out = (uint16_t)(pwm_out * ratio); } return (pwm_out > MOTOR_MAX) ? MOTOR_MAX : pwm_out; }代码逻辑:ADC 采样值先换算成毫伏,再乘以分压比。补偿系数是一个简单的分数放大,没有做智能 PID 平滑,所以电池电压突变时 PWM 会跳变一次,体感上会有一个轻微的「咯噔」。这个方案能延后换电池的时间点,但不能根治电压影响——根治需要编码器闭环。如果你用的是带保护板的锂电池,电压低于 6.5V 直接断电保护,补偿逻辑就没机会工作,这时候应当把保护板的低压阈值当作最后一次换电池提醒。
5. 整合调试与常见问题:从接通电源到稳定奔跑的 90 分钟
5.1 上电自检流程的正确顺序与初始化陷阱
拿到源码后不要直接编译烧录,先按下面的顺序做一遍硬件自检。这个顺序是我血泪教训总结的:先电源、后信号、最后协议。
第一步,单独给驱动板通电,确认四个电机在不上 PWM 时不转(有些驱动板 EN 引脚悬空时会默认使能,电机微微抖动)。第二步,给 STM32 烧录一个纯 PWM 输出的测试程序,让四路 PWM 依次以 30% 占空比转动 2 秒,确认轮子转向是否符合 2.1 节的方向约定——这一步能排除插线接反的问题。第三步,烧录完整程序,连接 PS2 接收器,把手柄放平,观察初始化完成后手柄灯是否常亮红色。
这里有个常见的初始化陷阱:delay_ms(10)用的延时函数如果是基于 SysTick 的,而 SysTick 初始化接在主时钟之后,那么主时钟没配好之前延时是失效的。CubeMX 生成的代码顺序是先配时钟再配 SysTick,一般没问题,但如果你是手动移植的工程,务必检查 SysTick 配置是否在延时调用之前。
5.2 避坑记录:调试过程中最常踩的四类问题
我把这个项目从零调试到稳定运行的 90 分钟里,遇到最多的四类问题罗列如下:
问题一:手柄数据全为 0xFF现象:PS2_Init 后读取到的按键和摇杆全是 0xFF。 原因:手柄接收器上电瞬间的复位毛刺导致初始化命令没有被正确接收;或者是硬件 SPI 模式下 CPOL/CPHA 不匹配。我排查时发现 CS 线在上电后先拉高了 1ms 又拉低,而手柄需要 CS 从高到低的跳变作为命令帧起始。 解决:在 CS 初始化后先强制拉高并延时 1ms,再拉低等待 2ms,最后拉高进入空闲状态,然后再执行 3.3 节中的初始化时序。
问题二:电机的嗡嗡声很大但车不移动现象:PWM 占空比给到 80,电机有声音但轮子不动。 原因:PWM 频率太低或占空比落在死区。我的板子驱动芯片是 TB6612 的低电平使能逻辑,PWM 频率 1kHz 下占空比低于 20(约 8% 有效电压)时,电机扭矩不足以克服静摩擦。 解决:把MOTOR_MIN_PWM设为 20,所有参与运动学的占空比先取绝对值,小于 20 一律视为 0。另外检查驱动板和电池的共地——TB6612 的逻辑地必须和 STM32 的 GND 相连,不共地时 PWM 无法形成有效回路。
问题三:左转和右转速度不对称现象:同一个 rotation 值,左转比右转快,而且伴随明显的车体抖动。 原因:四个电机中存在一个或两个接线虚焊,导致某轮的实际电压降比其他轮多。另一个常见原因是左旋和右旋辊子的安装顺序混乱——再次确认 2.1 节的编号表。 解决:用示波器或万用表量四个电机端子的有效电压,正常时四个电压应该一致。如果不一致,检查对应轮的驱动输出。另外开机后跑 5 秒直线,观察车是否走偏,走偏超过 20cm 则说明某个轮的辊子方向装反了。
问题四:摇杆回中后车还在缓慢漂移现象:手柄摇杆完全松手,车 3 秒内移动了 5cm。 原因:摇杆物理回中位置不在 0x7F,手柄老化后电位器磨损导致中值偏移。我遇到的一只旧手柄回中在 0x84,差值 5 在我的死区阈值 8 以内会被过滤,但如果死区阈值设成 3,这个漂移就会出现。 解决:把死区阈值提到 10 并在上电时增加一个「摇杆中值自校准」:初始化完成后读取 20 次摇杆值取平均,作为该手柄的中值基准,替代固定的 0x7F。这个方法能兼容大部分旧手柄,但增加约 50ms 的自检时间,比赛场合看需求取舍。
5.3 现场调试手法:用 LED 和串口观察内部变量
调试这种开环系统,最怕的是「车动了但不知道对不对」。我习惯在工程里开一个串口调试通道,用 115200 波特率周期输出四个轮子的 PWM 计算值和摇杆原始值。这样出现的异常运动能立刻反推是「输入错误」还是「映射错误」。手边没有串口调试助手时,我还会接两个 LED 到 PB0/PB1,一个指示手柄连接状态,一个指示电池电压是否过低,这两个 LED 在调试中能省掉大量猜疑。
// 串口打印调试信息的代码片段 void Debug_Print(void) { char buf[64]; sprintf(buf, "RX=%d RY=%d LX=%d | M1=%d M2=%d M3=%d M4=%d\r\n", joy_rx_raw, joy_ry_raw, joy_lx_raw, pwm_m1, pwm_m2, pwm_m3, pwm_m4); UART_SendString(USART1, buf); }这段代码要放在主循环里用定时器以 10Hz 频率触发,不要塞进死循环,否则会占用大量 CPU 时间导致 PS2 的时序延时不稳定。串口输出对新手最大的价值是:当你说「车往左偏」时,你能立刻区分是摇杆值不对称,还是轮子转速不对称。前者是输入问题,后者是硬件问题,排查方向完全不同。
6. 进阶技巧:状态机模式切换与开环系统的手感调校
主循环逻辑我们一直用的裸奔式while(1),但如果你想让车具备「训练模式」「表演模式」「急停模式」这类功能切换,就得把主循环改成状态机。我在竞赛前的最后一版代码里加入了模式判断逻辑,思路很简单:PS2 手柄的 SELECT 和 START 组合键切换模式,每个模式对应不同的速度上限和摇杆映射曲线。
typedef enum { MODE_NORMAL = 0, // 普通模式,最高 50% PWM MODE_TURBO, // 高速模式,最高 80% PWM MODE_SLOW // 精调模式,最高 25% PWM } RunMode_t; RunMode_t mode = MODE_NORMAL; void Mode_Toggle(void) { if (PS2_IsPressed(PS2_START) && PS2_IsPressed(PS2_SELECT)) { mode = (mode + 1) % 3; delay_ms(200); // 防抖,避免一次按键切换多次模式 } }模式切换最大的用处是保护新手阶段的误操作:有人在刚上手时推满摇杆,高速模式下车直接蹿出去。切换到精调模式后,PWM 上限被限制在 25%,即使摇杆推满,车也只会慢悠悠地移动,非常安全。这个限幅是在运动学输出前统一乘系数实现的,不需要改四个电机各自的映射逻辑,只用把MOTOR_MAX变成动态值即可。
另一个更隐蔽的调校点是「摇杆响应曲线」。PS2 摇杆是物理电位器,它的输出在中间段变化较快、在两端接近饱和——直接线性映射会让 PWN 在小幅度操作时变化太剧烈,车表现为「轻推就跑、再推就不动」。我一般在映射时加一个平方根曲线:PWM = MAX * sqrt(|delta| / 127) * sign(delta)。这样轻推时 PWM 是满推的 1/4 左右,中段操作更细腻,适合室内狭窄空间挪车。代价是满推和轻推的差异感会被放大,需要两三天适应手感,但习惯了之后你会觉得车「听话」很多。
最后说一个从那次把车飞下桌之后养成的习惯:每次改代码重新烧录后,先把车架空(四个轮子离地),跑一遍全向运动和旋转测试,确认 PWM 输出和摇杆响应正常后再放地上跑。这个习惯后来救了我两次——一次是某个通道的 Pulse 初始化改错,一次是驱动板的 IN 引脚虚焊导致反转。架空测试只要 30 秒,但避免了车在地上原地打转或者突然全速冲出去的尴尬。期间也遇到过手柄电池没电导致的信号中断,当时车还在原地旋转,差点撞到人,于是后续我把「手柄信号超时自动停电机」也写进了主循环,成本很低但收益很大。
希望这套拆解和经验能帮你在做麦轮底盘时少走几个月的弯路。代码和工程文件都在资源包里,解压后先看README.md,里面有引脚对照总表和 PS2 库的使用限制说明,配好环境后直接按我的顺序烧录调试即可。
本文还有配套的精品资源,点击获取