
做移动机器人的人大概率都绕不开两轮差速底盘。不管是入门用的平衡小车、实验室里的导航开发平台还是工厂里的AGV自动导引车很多项目的底层都长这样左右两个独立驱动的轮子加一个万向轮或从动轮撑着。结构确实简单但你真要把这辆车控得稳、走得准、能跑闭环光靠“给俩轮子发PWM”可远远不够。这篇文章我就从运动学建模、控制器设计到STM32工程落地把两轮差速机器人这套东西完整串一遍。文章内容适合正在做毕设、参加智能车比赛、或者刚接触移动机器人准备自己攒一台底盘的朋友。我会尽量把“为什么这么做”讲透而不只是给一堆公式和代码。数学上不会搞得太吓人工程实现上的坑会多聊一些——这些东西都是我实际调试中踩过的教科书里通常不写。1. 运动学建模先把“车怎么走”讲清楚1.1 从几何约束说起为什么要建模型两轮差速底盘看起来简单但它有个非常核心的几何约束车体只能沿着当前朝向方向运动不能像全向轮那样横着飘。也就是说你控制的是车体在二维平面上的平移速度前后方向和旋转角速度这两个量直接决定车的运动轨迹。建模的目的就是要把左右轮子的转速映射成车体的速度再把车体的速度映射回轮子的转速。前者叫正运动学后者叫逆运动学。正运动学用来做状态估计——比如通过编码器读数算出车到底走了多远、转了多少度逆运动学用来做运动控制——比如你告诉控制器“我要以0.5 m/s前进、同时以0.2 rad/s转弯”控制器就知道该给两个轮子多少转速。这里涉及一个非常重要的概念瞬时旋转中心ICR。差速车转弯时车体并不是绕某个固定的圆心转而是绕一个随速度变化而移动的点转。理解这一点你就明白为什么“给两个轮子不同的速度”会产生圆弧轨迹——因为两轮线速度不同车体相当于在绕着两轮连线延长线上的某个点画弧。1.2 正运动学模型推导从轮速到车体速度我们先把问题简化假设车轮不打滑车体是刚体。设轮距两驱动轮中心之间的距离为W轮子半径为R左轮线速度为v_L右轮线速度为v_R。车体质心其实就是两轮连线中点的前向速度为v旋转角速度为ω。推导思路很直接车体瞬时速度是左右轮速度的平均值瞬时旋转角速度则是左右轮速度差除以轮距。用公式写出来就是v (v_L v_R) / 2ω (v_R - v_L) / W这里有个细节需要特别提醒如果坐标系规定车头方向为x轴正方向逆时针旋转为正那么右轮速度大于左轮时车体向左转逆时针ω为正。不同的教材可能坐标定义不同符号会差一个负号工程上以自己代码里的约定为准但一定要统一。有了线速度v_L和v_R轮子转速n_L、n_R就和它直接关联v_L 2πR·n_L/60注意单位换算转速n常用的单位是转/分钟但PID控制器里一般要统一成 rad/s 或者 m/s。正运动学到这一步就完成了。它的作用体现在两个地方一是你在做仿真时给左右轮速就能推算出车体的轨迹二是在实际系统中利用编码器测出左右轮实际转速估算出车体的实际速度和角速度再做闭环控制。1.3 逆运动学模型把目标速度拆给两个轮子逆运动学是控制里更常用的方向。给定目标速度v_target和角速度ω_target反推左右轮速v_R v_target (ω_target × W) / 2v_L v_target - (ω_target × W) / 2注意如果目标角速度为正逆时针那么右轮应该比左轮快。这个公式非常直观让车转得更急就加大两轮速度差让车直线前进就把ω设成0。从这两个公式出发还能导出两个竞速场景下经常用到的概念旋转半径和曲率。如果v_L -v_R车体原地旋转旋转半径为0如果v_L v_R车体直线运动旋转半径无穷大。中间状态对应不同半径的圆弧。运动学模型是整个控制系统的“翻译官”把用户级的目标速度翻译成电机级的目标转速再把电机级的实际转速翻译成用户级的状态反馈。它的准确性直接决定控制精度。1.4 关于运动学模型的两个工程提醒轮距W不是随便测一下两轮中心距离就完事的。如果你用的是轮毂电机或者带减速箱的电机轮子本身有一定宽度实际的等效轮距可能会比几何中心距略大或略小尤其是转弯半径较小时误差会更明显。最可靠的做法是“用实验结果反推”让车原地旋转一定圈数对比编码器累计角度和实际角度反向标定W的等效值。车轮半径R同样存在“有效半径”问题。胎压不同、地面软硬不同实际滚动半径和几何半径可能差2%~5%。这在小半径转弯时可能不会暴露但跑长距离直线导航时里程计累计误差会让你怀疑人生。所以做导航类项目里程计标定是必须做的第一步。2. 控制策略设计让模型真正动起来2.1 从运动学到控制的闭环框架有了运动学模型下一步是控制。差速底盘的控制一般分两层底层轮速环和上层运动环。底层轮速环的任务是“让左右轮精确达到目标转速”属于电机级的高速控制通常在单片机的定时中断里跑控制周期1~10ms上层运动环的任务是“让车体达到目标速度和角速度”属于车体级的低速控制控制周期可以放宽到10~50ms。上层运动环的输出经过逆运动学分解变成左右轮的目标转速发给底层轮速环底层轮速环通过PID调节电机PWM使实际转速逼近目标值。编码器测得轮速后再通过正运动学反馈给上层形成完整的闭环。这种分层架构的好处是职责清晰。如果直接在运动环里调PWM电机响应差异、负载变化都会严重影响运动控制效果而有了底层速度环之后上层面对的是一个“近似线性”的执行器控制难度大大降低。2.2 轮速环PID位置式还是增量式STM32工程里最经典的底层轮速环就是PID。先分清楚两种形式的适用场景位置式PID输出是PWM的绝对占空比。它需要累积误差项容易积分饱和但实现简单适合不常改变目标值的场合。增量式PID输出是PWM占空比的增量。它天然自带积分截断效应对执行器饱和有抑制作用而且不会出现大的阶跃冲击适合电机这种需要频繁调速的负载。我实际做底盘控制时轮速环更推荐用增量式。原因很实际电机启停和负载突变时位置式PID的积分项容易“灌满”导致响应迟钝甚至超调增量式每次只改变一个小值动态响应更平滑安全性也更好。增量式PID的离散公式是Δu(k) Kp·[e(k) - e(k-1)] Ki·e(k) Kd·[e(k) - 2e(k-1) e(k-2)]然后 u(k) u(k-1) Δu(k)。注意这里的e(k)是目标转速与实际转速的差值。2.3 级联PID什么时候需要上层速度环之外再套位置环如果你只是做底盘运动控制轮速环就够了。但如果你做的是“走直线走准”“原地转固定角度”这类任务单靠轮速环还不够——你还需要一个位置环或角度环。位置环的输入是期望位置或期望角度输出是目标速度作为轮速环的给定值。这就形成了所谓的级联PID外环位置/角度环→ 内环速度环→ PWM输出。级联的好处是每个环只处理自己带宽范围内的干扰内环先把电机非线性吃掉外环就能用比较温和的参数。我之前做过一个自动归位的小车要求它每次前进2米误差不超过2厘米。只用速度环的话因为轮速存在稳态误差和启动延迟停下来位置飘到5厘米以上加上位置环之后靠近目标时外环会降低速度命令小车会“缓缓蹭过去”最终精度能稳在1厘米左右。级联PID整定有个基本顺序先整内环再整外环。内环调到“响应快、无超调”之后外环再逐步加P发现震荡就退回去加D抑制。切忌同时调两层参数那只会让你分不清问题出在哪个环。2.4 运动学控制的边界什么情况会被模型“坑”运动学模型成立的前提是“纯滚动无滑动”但现实中这个前提经常被打破。地面太滑轮子打滑编码器读数会“骗人”地面有凸起轮子瞬间悬空转速突变负载变化大电机响应变慢PID参数不再适用。这些问题不是靠调参能彻底解决的。工程上的常见做法是加入自适应控制或者至少做一个“异常检测”比如当目标速度和实际速度误差持续超过阈值时不再盲目加大PID输出而是降低速度设定值或者报警。另外就是做加速度限制不要让速度阶跃变化用梯形加减速或S曲线加减速平滑速度命令这样既能减少打滑又能保护电机和机械结构。3. 从模型到硬件STM32工程落地全流程3.1 硬件选型电机、编码器、驱动板怎么配以最常见的STM32F103C8T6就是俗称的“蓝丸”为例做一台两轮差速小车核心件就是直流减速电机加编码器。编码器推荐用霍尔式AB相增量编码器常见的是每转几十线的霍尔编码器经过减速箱输出轴后实际分辨率能达到千级别的脉冲数够用且抗干扰能力比光栅式好价格也低。电机驱动这边用TB6612或者DRV8833这类芯片就很稳它们内部集成了H桥逻辑输入兼容3.3V直连STM32不需要电平转换。注意电机驱动芯片逻辑电源和电机电源要分开走线共地但不要共用一根粗线否则电机启动瞬间的压降会把单片机拉复位——这个问题我在调试无数次后才意识到是电源布线的锅。轮速编码器的接线要特别注意。AB相脉冲输出是开漏结构的话必须外部上拉如果用推挽输出可以直接接STM32的输入捕获引脚。读取频率上霍尔编码器的脉冲频率一般不超过几千HzSTM32的定时器捕获完全能应对。3.2 编码器读取与速度计算核心代码结构编码器数据的正确读取是速度闭环的基础。在STM32上推荐用定时器的编码器模式把AB两相接到同一个定时器的两个通道上配置为Encoder Mode硬件自动完成鉴相和倍频CPU只需要读取计数寄存器的累加值。比如TIM3的CH1和CH2分别接左轮编码器的A、B相硬件会每产生一个有效沿就自动加减计数。速度计算的做法是在固定周期比如1ms或5ms内读取计数差值乘以一个换算系数得到线速度。我习惯用1ms定时中断里的高频任务来做编码器采样和PID运算这样速度环的控制周期是1ms响应足够快。核心换算逻辑大致是转速(n) 采样周期内的脉冲差值 / (编码器线数 × 减速比 × 采样秒数)再把轮子线速度算出来v 2πR×n/60。这里每个量都要带单位算清楚最笨但最可靠的方法是全部换成国际单位米、秒、弧度。单位写进代码注释里能省不少后面的排查时间。3.3 PID控制器实现与参数整定记录底层速度环的PID实现我建议用结构体封装typedef struct { float Kp; float Ki; float Kd; float target; // 目标值 float feedback; // 实际反馈值 float error; float last_error; float prev_error; float integral; float derivative; float output; float output_limit; // 输出限幅 } PID_t;每次进入1ms定时中断执行流程是读编码器差值→计算实际轮速→计算偏差→运行增量式PID→更新PWM占空比。两个轮子各跑一套独立的PID不要共用变量。参数整定我分享一下个人的“土办法”先只加P从小到大直到轮子出现轻微震荡听到电机“呜呜”声或者速度波形等幅震荡记录此时的Kp然后把Kp设为这个值的一半左右再加I慢慢消除稳态误差最后加一点D来抑制超调。实测下来这个方法比教科书里的“临界比例度法”更习惯因为直流电机的非线性太强理论计算出来的参数往往偏差不小。底层的目标转速不要直接丢给PID最好经过一个斜坡发生器做加减速限制。比如目标速度从0到1 m/s设定加速度为1 m/s²那么要1秒才能加到目标值。这样不仅启动平稳而且对编码器噪声的放大也会小很多。3.4 频率、中断优先级与实时性的取舍STM32工程做底盘控制实时性是最容易被低估的问题。我的建议是编码器采样与PID运算放在1ms定时中断里串口调试输出放在主循环里用非阻塞方式做无线通信模块比如ESP8266或者nRF24L01的数据接收用串口中断环形缓冲区。有一个很实际的注意点多个中断同时发生时优先级一定要设计好。编码器采样/PID中断优先级要高于串口中断否则某个时刻串口数据一多PID周期被拉长速度环带宽下降小车会“一顿一顿”。我遇到过类似现象排查了一天才意识到是USART1的接收中断抢占了TIM中断的CPU时间。另外PWM的更新频率也要匹配。电机驱动的PWM频率建议设置在10kHz~20kHz这个区间既可以避开人耳可听噪声又不会让MOS管开关损耗过大。PID输出直接把占空比写入CCR寄存器即可不需要做额外的滤波。4. 常见问题与排查技巧实录4.1 车轮不转或只往一个方向转这是新手最容易遇到的情况。先量电机驱动板的逻辑电源和电机电源是否正常再检查PWM引脚有没有波形输出最后看使能引脚如STBY是否拉高。TB6612这类芯片如果STBY不拉高输出端永远是高阻态电机自然不转。如果两个轮子转向相反先不要急着改程序——先判断是机械装配问题还是接线问题。最简单的办法是手动转动车轮观察编码器计数方向是否一致。如果硬件上两轮编码器方向反了软件里加个负号就能解决但这属于“掩盖问题”的做法时间久了容易坑自己。建议统一在机械装配时就保证左右轮编码器方向对称。4.2 小车走不直轮子一点点偏移就歪直线走歪的核心原因有两个一是左右轮速度闭环性能不一致一个响应快一个响应慢二是轮距或轮径标定不准。先检查速度环。把目标速度给到左右轮相同值用串口把实时轮速打印出来对比两轮的速度曲线。如果稳态误差虽然为零但动态响应差异很大一个超调多一个超调少说明两套PID参数不一致分别整定。另一种情况是电机本身空载转速不同即使PID参数一样输出相同的PWM占空比两轮转速也会不同——这正是速度闭环存在的意义它会自动修正这种差异。如果速度曲线一致但直线还是歪那大概率是左右轮直径不一致或者轮距标定有偏差。解决方案是做一个简单的比例补偿让车跑一段已知距离和已知偏航角算出修正系数把这个系数乘到某一侧的轮速命令上。这叫“软标定”工程上非常实用。4.3 小车原地转圈时位置漂移原地旋转要求左右轮速度大小相等、方向相反。如果旋转时车体中心位置发生明显偏移说明左右轮速度幅值不一致一个快一个慢。这时可以用轮速环的实时数据排查让车原地转几圈打印两个轮的累加里程如果差值越来越大锁定是速度幅值不对称问题。另外还有一个容易忽略的因素地面摩擦不均。如果地面有油污或者轮胎磨损程度不同即使左右轮电机转速一致实际线速度也可能不同。这时候不是控制器能解决的需要清理地面或更换轮胎。4.4 悬空测试正常、落地就跑偏该怎么定位这种情况十有八九是编码器采集被噪声干扰了或者是电机负载大导致电流突变影响了电源稳定性。排查方法是接上串口把编码器原始计数差值打出来观察数值跳动是否正常。正常的计数差值应该在某个范围内平滑变化如果频繁出现大跳变或毛刺优先怀疑编码器线缆的屏蔽和上拉电阻。电源方面建议给电机和单片机系统分别供电实在不行至少要在电机电源端加大容量电解电容几百微法到一千微法并在电机引脚上并接陶瓷电容和磁珠基本能消除大部分传导干扰。我之前有台小车总是悬空正常、落地就抽风最后发现是电机电刷火花干扰了编码器信号换用带屏蔽的编码器线后问题直接消失。5. 一条完整调试路径从零到闭环走通5.1 分五步走每一步验证完了再进行下一步我整理一条自己惯用的调试路径照着做可以少走弯路开环测试不用PID直接给固定占空比PWM确认左右轮转动方向和预期一致确认编码器计数正常。速度环开环标定手动给不同占空比测量对应转速做一个粗略的占空比-转速映射表看一下电机的线性区在哪里。速度环闭环接上PID让给定目标速度后轮速快速且稳定地跟随观察阶跃响应的超调和稳态误差。运动环开环测试用串口发目标线速度和角速度车体匀速直线前进、原地旋转观察实际轨迹是否接近预期。运动环闭环接上位置环或者角度环跑“前进2米停止”“转90度”这类任务验证级联控制效果。每一步验证时都要保留数据。比如步骤2建立一个标定表步骤3记录不同Kp下的阶跃响应数据后面就算出问题也能回溯。很多人调试卡住是因为跳步太多出了问题根本不知道是哪个环节的锅。5.2 调试过程中的数据记录与波形观察技巧底盘调试最怕“凭感觉”。有条件的话用串口把实际轮速、目标轮速、PID输出这些变量以CSV格式发出来然后用Python简单画一下曲线问题往往一眼就能看出来。如果没有上位机条件可以用一个更土的办法把PID输出发送到串口监视器观察数值变化的模式。比如输出在最高值和最低值之间持续震荡说明Kp过大或采样周期过长输出缓慢增大到饱和但误差还是很大说明电机堵转或者目标速度超过了物理极限。我个人习惯在代码里加一个调试开关可以通过串口命令实时切换“输出原始编码器值”还是“输出目标/反馈/PID输出”这样不用反复修改代码编译烧录调试体验会好很多。5.3 从底盘控制到后续扩展里程计与导航底盘控制只是移动机器人的第一步。当你把两轮差速底盘的运动学和控制打磨稳定之后前面还有很多扩展空间里程计推算Odometry基于编码器数据和运动学模型实时推算车体在世界坐标系中的位姿x, y, θ。这是SLAM和导航定位的前提。路径跟踪给定一条直线或圆弧路径基于当前位姿偏差计算目标速度和角速度这就是纯跟踪Pure Pursuit或Stanley控制要做的事。与上位机通信让底盘接收更高级别控制器的速度指令同时回报当前实际速度与里程计数据。以我自己做过的项目为例底层底盘控制跑稳后上层用ROS做SLAM和自主导航整条链路就通了。底层的响应质量和里程计精度直接决定了上层导航的稳定性一台“脚底下虚”的底盘再强的导航算法也跑不出效果。6. 部署时的实战经验与心得6.1 轮胎打滑所有控制算法的天敌两轮差速车的控制上限很多时候不是控制器决定的而是物理条件决定的。轮子和地面之间的摩擦力是唯一的驱动力来源一旦超出摩擦极限再精确的PID也无能为力。工程上的缓解手段有几个一是控制加速度不要让速度阶跃突变二是选择合适的轮胎材质橡胶轮比硬塑料轮的抓地力强很多三是有条件的场合可以增加悬挂机构避免过弯时内侧轮因载荷转移而打滑。做比赛和做产品侧重点不同但“降低打滑概率”这个目标是共通的。悬空测试和落地测试的差异也来源于此。悬空时轮子没有负载电机的动态特性完全体现不出来落地后地面摩擦、转动惯量都变了PID参数必须重调。所以别偷懒底盘调试一定要在地上跑。6.2 标定是工程里最不值钱但最见效的工作我见过很多团队花大量时间调PID却不愿意花10分钟标定轮距和轮径。实际上参数标定的收益往往是立竿见影的。轮距标定方法很简单让小车原地旋转N圈读取编码器估算出旋转角度与实际角度对比修正轮距参数。一个晚上就能把旋转精度从“差10度”提高到“误差小于1度”。轮径标定类似让小车直线跑一段已知距离根据实际距离反推有效轮径。这种标定不用做得很频繁但如果换了轮子、调了胎压、或者换了地面摩擦环境最好重新标一遍。我自己习惯了在代码里把轮距、轮径这两个参数设为编译期宏定义每次标定完直接修改宏重新编译简单粗暴。6.3 控制周期与滤波器设计一个常被忽视的维度底层轮速环的控制周期我推荐先设1ms。如果你的代码在主循环里跑PID周期抖动会很严重导致速度环带宽不稳定。用定时器中断固定1ms调用一次基本能满足大多数直流减速电机的需求。如果你用的是无刷电机或者对响应要求特别高可以考虑把周期缩到500us甚至用FOC方案那就是另一个层次的问题了。编码器计数差分得到的速度信号有一个固有的问题高频率下噪声被放大。PID里的微分项本身也放大噪声两者叠加会导致PWM输出“哗啦哗啦”乱抖电机发热严重。解决办法是加一阶低通滤波speed_filtered α×speed_raw (1-α)×speed_filtered_lastα取0.2~0.4比较合适。滤波会增加相位延迟所以α不要太小否则速度环响应会变得迟钝。6.4 另一条可行路径用现成轮毂电机方案如果你不想从电机、驱动、编码器这种分立器件开始做也可以考虑带轮毂电机的现成差速底盘方案。这类底盘一般自带闭环控制通过串口或CAN总线直接收速度命令内部已经完成了速度环和部分运动学处理。优点是开发速度快缺点是价格高、灵活性差而且出了问题很难排查到电机底层。对于以SLAM导航、视觉感知为主项目的团队我其实更推荐这种方案把精力集中在更上层的问题上。但如果你想深入理解移动机器人底层的运动学和控制系统自己从零搭一台差速底盘绝对是不可替代的学习路径。写在最后两轮差速移动机器人的运动分析、建模和控制是一个从理论到实践跨度很大的系统工程。运动学模型给了你理解底盘运动的数学框架PID控制解决了执行层面的跟踪问题而STM32工程落地则把所有逻辑变成真正能动起来的机器。我个人最大的体会是别被公式和术语吓住两轮差速底盘的核心逻辑非常简单——左右轮速度相加是前进相减是转向。把这句话吃透了再去看运动学推导和控制架构很多东西自然就通了。但也别轻视工程细节标定、滤波、中断优先级、电源布线这些“脏活累活”做得越扎实后面的调试就越顺利。这篇文章是按我自己的项目经验来写的很多参数和结论都是基于常见的直流减速电机和STM32平台。如果你用的是无刷电机、麦克纳姆轮或者其他平台部分内容需要做相应调整但整体思路是共通的。希望这篇内容能帮你少踩几个坑尽快地把底盘调稳、跑直。