STM32F103C8T6麦克纳姆轮小车制作全攻略:运动学解算与PID闭环调速

发布时间:2026/9/9 12:27:40
STM32F103C8T6麦克纳姆轮小车制作全攻略:运动学解算与PID闭环调速 简介面向STM32入门级开发者和机器人爱好者的麦克纳姆轮小车完整工程基于STM32F103C8T6主控涵盖硬件设计与嵌入式程序支持蓝牙和ESP8266 WiFi双无线控制方式适合学习四轮独立驱动、全向移动控制以及无线遥控小车改造。资源包共128个文件约3.77MB包含AD设计的原理图与PCB工程文件pcbdoc、schdoc、pcblib等以及单片机C语言源码h/c文件、Keil工程配置、hex烧录文件和辅助脚本各类型分门别类存放便于直接查阅。已有3874人学习/下载。通过这份资料可拿到从硬件原理图到软件控制的全链路方案原理图与PCB便于理解或二次改板源码覆盖定时器PWM调速、电机驱动、蓝牙/WiFi串口通信等关键模块hex文件可快速烧录验证适合作为课程设计或竞赛小车的参考基础。 做麦克纳姆轮小车我在实验室里前后折腾了快三周。STM32F103C8T6最小系统板加四个麦克纳姆轮加直流减速电机这套组合基本是学生项目、课设、电赛热身里最常见的配置。麦克纳姆轮最大的价值在于可以让车体实现横向平移、斜向运动、原地旋转这类普通差速底盘做不出来的动作灵活性直接上一个档次。这篇文章会把硬件选型、运动学解算、CubeMX工程配置、PID闭环调速还有我实际踩过的坑从头到尾写一遍适合刚拿到系统板、想尽快让四轮底盘跑起来的朋友。1. 项目概述与整体方案设计1.1 为什么选STM32F103C8T6加麦克纳姆轮STM32F103C8T6这块片子虽然发布年头不短但放在小车控制场景里完全够用。主频72MHzFlash 64KBRAM 20KB有4个通用定时器、3个USART、2个SPI、2个I2C还有足够多的GPIO。驱动四路PWM加四路编码器采集算上OLED显示、蓝牙或Wi-Fi模块的串口通信资源仍然有富余不需要为了跑系统去上F4系列。选麦克纳姆轮而不是普通轮胎核心诉求是灵活。大学竞赛里自动行驶、物流搬运这类题目经常要求小车在狭窄区域里调整姿态麦克纳姆轮可以横向平移路径规划时不需要留很大的转弯半径。坏处也明显结构复杂、辊子易磨损、对运动学解算有要求、对轮子安装方向敏感。所以它不是“无脑选”而是“值得选”。1.2 系统框架与硬件清单整个小车的控制链路其实很清晰上位机或遥控器给出设定速度STM32根据运动学逆解把底盘速度分配到四个轮子每个轮子由编码器反馈实际转速PID调节器修正PWM占空比最终驱动芯片控制电机。这个闭环结构是所有轮式机器人的通用骨架。我手头这套配置清单供参考主控STM32F103C8T6最小系统板蓝色板板载AMS1117稳压底盘四路麦克纳姆轮小车底盘铝合金框架带四个TT电机安装位电机TT直流减速电机带霍尔编码器减速比1:30单轴输出驱动TB6612FNG双路电机驱动模块共两片供电7.4V 2S锂电池经过DC-DC降压给电机和逻辑分别供电调试ST-Link V2串口TTL模块0.96寸OLED可选这里提醒一句TT电机所谓的“霍尔编码器”通常只有28线加上减速比1:30单圈脉冲才840左右。做精确速度闭环、精确里程积累时会觉得分辨率偏低。如果预算允许换带500线编码器的MG513电机或N20电机是个质的飞跃。我最初用TT电机跑PID速度低的时候编码器脉冲间隔太长测速的离散误差很大后面换成500线后明显稳了。2. 麦克纳姆轮运动学解算2.1 四轮速度分配原理麦克纳姆轮的奥秘在于辊子。每个主轮周围套着一圈可以自由转动的辊子辊子轴线和主轮轴线呈45度夹角。当主轮被电机驱动旋转时辊子与地面的接触会产生一个沿辊子轴线方向的摩擦力分量于是轮子不只是朝驱动方向走还会带着车体向侧面滑移。四个轮子按照辊子朝向分成两种类型左旋轮和右旋轮。安装时必须遵守“前左、后右一种前右、后左另一种”的交叉布局。如果装错四个轮子的受力互相抵消车只会原地发抖不动地方。这个小细节我在第一次装底盘时栽过跟头后来用胶带在轮毂上标了A/B标记才彻底避免。底盘运动可以分解成三个自由度前后方向的线速度vx左右方向的线速度vy绕车体中心旋转的角速度omega。麦克纳姆轮的逆运动学就是已知这三个速度求出四个轮子各自的线速度再换算成各电机的目标转速。2.2 正逆运动学公式推导与代码实现设底盘前轮距为L左右轮距为W车轮半径为r四个轮子的线速度分别记为v1前左、v2前右、v3后左、v4后右。在常见的辊子布置下逆运动学公式可以写作v1 vx - vy - (L W) * omega / 2v2 vx vy (L W) * omega / 2v3 vx vy - (L W) * omega / 2v4 vx - vy (L W) * omega / 2注意这个公式的符号不是唯一的取决于你定义的轮子类型和坐标方向。比如有的资料把轮距相关项写成LW的倍数有的写成lxly本质一样。我建议不要死记公式而是实际推一遍理解每个速度分量在四个轮子上的投影关系调车时才能快速判断是不是符号搞反了。代码实现就很简单了。我习惯把底盘控制写成一个独立的kinematic.c文件统一管理运动学解算typedef struct { float vx; float vy; float omega; float v1; float v2; float v3; float v4; } ChassisSpeed; void inverseKinematic(ChassisSpeed *chassis) { float lw (CHASSIS_L CHASSIS_W) / 2.0f; chassis-v1 chassis-vx - chassis-vy - lw * chassis-omega; chassis-v2 chassis-vx chassis-vy lw * chassis-omega; chassis-v3 chassis-vx chassis-vy - lw * chassis-omega; chassis-v4 chassis-vx - chassis-vy lw * chassis-omega; }这里的v1到v4是轮子的线速度还要除以车轮半径r才能得到角速度或者直接在标定阶段把“轮子线速度”和“目标PWM/目标编码器计数”做成线性映射。我实际做的时候是在单位统一层面直接使用“速度百分比”作为全局变量比如上位机下发vx30表示30%最大速度逆解后分配到的轮子速度也是百分比测试起来非常直观。3. 硬件接线与关键器件调试3.1 STM32F103C8T6最小系统板引脚分配STM32F103C8T6有PA0-PA15、PB0-PB15、PC13-PC15以及PD0/PD1总共大约37个可用IO。四路PWM、四路编码器信号、两片TB6612的控制IO加起来16个引脚左右极限情况下BLE模块、OLED、按键再占几个也勉强够用。我当时的分配思路是PWM输出TIM3的CH1-CH4对应PA6、PA7、PB0、PB1四个通道刚好驱动两片TB6612的PWMA/PWMB编码器A相TIM4的CH1-CH4对应PB6、PB7、PB8、PB9编码器B相TIM2的CH1-CH4对应PA0-PA3方向引脚TB6612的AIN1/AIN2、BIN1/BIN2共8个GPIO随意分配在PC13-PC15和PA4、PA5等空闲引脚这里有个坑要提醒F103C8T6的PB3、PB4、PA13、PA14、PA15默认是JTAG调试引脚。如果这些引脚被配置为普通IO输出必须在CubeMX里把调试接口改成SWD方式或者直接禁用JTAG否则你会发现某个引脚怎么都拉不高拉不低。我第一次就是把PA15当方向控制脚结果死活没输出后来查了参考手册才反应过来。3.2 电机驱动模块与编码器接线TB6612FNG相比L298N体积小、压降低、不需要外部散热片在小车场景里更合适。每个TB6612是一路双电机驱动有AIN1/AIN2、BIN1/BIN2四个方向控制脚和PWMA/PWMB两个使能PWM脚。逻辑电源VM接电池电压VCC接3.3V或5VGND必须和STM32共地这是很多人忽略的一点不共地会导致PWM控制信号无参考电平电机乱转甚至不转。编码器接线也有讲究。TT电机霍尔编码器一般输出A、B两相正交信号STM32定时器的编码器模式可以直接通过两相的上升沿和下降沿四倍频计数。需要注意的是编码器输出的信号通常是开漏要加10k上拉电阻到3.3V或确认板子上已经做了上拉。我用的电机模块自带上拉所以直接接IO就正常。电机电源部分STM32的板载3.3V稳压输出给逻辑电路用没问题但绝对不能给电机供电。电机启动瞬间电流可能超过1A如果和主控共用稳压源电压跌落会直接导致STM32复位。我遇到过一次小车一转就重启后来把动力电源和逻辑电源彻底分开再加了一个470uF电解电容在电机母线缓冲才解决。4. CubeMX工程配置与软件实现4.1 定时器PWM与编码器模式配置用CubeMX配置STM32F103C8T6的工程基本几步选择芯片型号、配置时钟树HSE外部8M晶振PLL倍频到72MHz、按下表配置定时器和GPIO然后生成代码。编码器模式配置TIM4的四个通道都设为Encoder Mode计数器工作在TI1和TI2组合模式这样四倍频输出。ARR要设成65535编码器计数是双向的计数溢出会自动翻转。配置完成后在代码里读CNT寄存器就能拿到带方向的累计脉冲。void MX_TIM4_Init(void) { TIM_Encoder_InitTypeDef sEncoderConfig {0}; sEncoderConfig.EncoderMode TIM_ENCODERMODE_TI12; sEncoderConfig.IC1Polarity TIM_ICPOLARITY_RISING; sEncoderConfig.IC2Polarity TIM_ICPOLARITY_RISING; sEncoderConfig.IC1Selection TIM_ICSELECTION_DIRECTTI; sEncoderConfig.IC2Selection TIM_ICSELECTION_DIRECTTI; sEncoderConfig.IC1Prescaler 0; sEncoderConfig.IC2Prescaler 0; sEncoderConfig.IC1Filter 10; sEncoderConfig.IC2Filter 10; HAL_TIM_Encoder_Init(htim4, sEncoderConfig); HAL_TIM_Encoder_Start(htim4, TIM_CHANNEL_ALL); }PWM配置TIM3的PWM频率我设成20kHz。之前用过1kHz电机能转但低速时电机会有明显啸叫20kHz可以避开人耳敏感频段。如果你用的是某些MOS驱动或TB6612太高的频率反而会增大开关损耗建议从10kHz到20kHz之间调试。4.2 PID闭环调速与主循环逻辑速度闭环是整个软件的核心。每个轮子用增量式PID目标是让编码器测出来的实际转速跟上运动学解算给出的目标转速。增量式的公式是delta_u Kp * (e_now - e_last) Ki * e_now Kd * (e_now - 2 * e_last e_last2)这里的输出是PWM占空比的增量累计到当前值上。我用10ms周期做一次控制这个时间对STM32来说很宽裕。PID结构体定义和单次计算可以写成这样typedef struct { float target; float current; float err; float err_last; float err_last2; float kp; float ki; float kd; float output; float output_max; } PidObject; void pidCalculate(PidObject *pid, float target, float current, float dt) { pid-target target; pid-current current; pid-err target - current; float delta pid-kp * (pid-err - pid-err_last) pid-ki * pid-err * dt pid-kd * (pid-err - 2.0f * pid-err_last pid-err_last2) / dt; pid-output delta; if (pid-output pid-output_max) pid-output pid-output_max; if (pid-output -pid-output_max) pid-output -pid-output_max; pid-err_last2 pid-err_last; pid-err_last pid-err; }主循环里做的事情很机械定时中断里读四个TIM的编码器CNT值算出当前速度把运动学解算的目标速度和当前速度一起丢给PID把PID输出转成PWM占空比和方向信号写入TIM3的CCR寄存器同时设置对应的GPIO方向。整个过程不建议在主循环while里裸跑用定时器或操作系统的软件定时器把它固定成周期任务效果会好很多。项目的实际表现我调完PID后小车在地砖上跑直线500线编码器配置下基本能压住偏差在正负2cm以内。横向平移时因为辊子滚动摩擦损耗不一样直行时最需要补偿的方向反而是沿底盘对角线这个问题我最后是靠给四个轮子分别标定不同Kp值解决的。5. 实测调试、避坑与常见问题速查5.1 实际跑起来遇到的问题第一类问题是运动学解算符号错误。表现为给vx小车不走给vy小车反而直行或者四个轮子里有两个在打架。排查方法很简单把omega设成0、vx设成非零单独看四个轮子目标速度的分配再用串口打印实际PID给定值确认每个轮子是否按预期方向转。只要有一个轮子反向就要赶紧去检查逆解公式里的加减号以及电机方向控制IO是高电平转还是低电平转。第二类问题是编码器测速值跳变。霍尔编码器在低速时脉冲间隔长单次采样时间内可能只来几个脉冲速度值看起来就一顿一顿。解决思路有两个一是提高测速周期比如从10ms改成20ms二是用M法测速也就是在固定时间内统计脉冲数速度越低越需要统计时间越长。低速要求特别高的场景还可以上定时器输入捕获去测量单脉冲的周期但实现复杂度会明显上升。第三类问题是PID振荡。电机高速时能听到轮子周期性“呜呜”声或者静止时车体轻微抖动通常就是P过大或者D过小。我调PID的习惯是先把Ki和Kd置零只给Kp从小到大加等系统发生等幅振荡再把Kp退回60%左右然后逐渐加Kd消除超调最后加适量Ki消除静差。整个过程大概需要半小时用串口把目标速度和实际速度曲线打出来看比肉眼看轮子转要靠谱得多。5.2 常见问题速查表调试中很多东西踩过一次就再也不犯我整理成一张速查表方便大家对照排查。现象可能原因排查方向车通电后轮子不转共地缺失、PWM频率不对、芯片复位检查GND是否连接示波器看PWM波形有的轮子正转有的反转运动学符号错、方向IO电平接反单独给每个轮子发固定PWM确认方向编码器数值始终为0编码器模式未开启、A/B相接反、上拉缺失读TIM_CNT寄存器用手拨动轮子观察变化小车直线跑偏四轮机械误差、四个电机特性不一致单独标定每个轮子的PID参数电机低速吱吱响PWM频率设置过低提高到15kHz以上电池没电时速度变慢且跑歪电池电压下降导致PWM输出非线性加电压前馈或改用较高电压电池主控一跑电机就复位电源共地不稳、电机母线电压跌落动力与逻辑分开供电加滤波电容举一个我之前最头疼的例子四轮编码器速度都有闭环但小车从静止起步时总有一侧轮子先动导致起步瞬间车身歪一下。查了好久最后发现是四个电机的启动摩擦力矩不一致PID的积分项又太弱低占空比下电机还没转起来编码器脉冲接近为零PID以为没到目标就持续加大输出结果一侧先克服静摩擦冲出去。解决办法是给PID输出加一个基础占空比偏置让所有电机在启动时都有一个小幅预驱动起步就变得很干脆。5.3 关于国产替代与后续扩展的补充最后补一个很多人问过的点STM32F103C8T6的国产替代型号比如APM32F103、MM32F103、GD32F103在引脚定义上是兼容的CubeMX选型时可以直接选对应型号或者先按STM32生成工程再移植HAL头文件。项目里实测过国产MCU在编码器模式、PWM输出这类外设上的寄存器兼容性没有问题价格也低很多学生打样或者批量课设完全可用。如果要做成完整的课程设计或者竞赛作品可以在现在这套系统上挂一个无线模块比如ESP8266或NRF24L01配合上位机发期望速度再往下就是加惯性测量单元做底盘姿态闭环或者接激光雷达做自动导航。麦克纳姆轮底盘的优势会在这些场景里更加突出。我在实际调车过程中还有一个很深的体会就是别迷信参数仿真的效果。底盘机械间隙、轮子磨损、地面摩擦系数这些因素在仿真里根本不存在。最好的方式是先把某个轮子的PID调通再复制到其他轮子最后通过整车直线和斜向测试做微调。每个轮子的参数可能有10%-20%的差异这个很正常属于机械系统本身的误差容限不必强迫四个轮子参数完全一样。本文还有配套的精品资源点击获取