ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32麦克纳姆轮底盘驱动:运动学解算与PID速度闭环实现

STM32麦克纳姆轮底盘驱动:运动学解算与PID速度闭环实现 简介基于STM32控制的麦克纳姆轮智能小车底盘驱动资源包专注底盘驱动这一核心环节。项目包含麦克纳姆轮运动解算、四路电机PWM控制、编码器反馈与闭环调速等内容适合单片机初学者快速上手也可作为毕业设计、课程设计、大作业或工程实训的参考方案。压缩包共985个文件总大小约24.13MB其中以C源码561个和头文件245个为主辅以汇编启动文件、Keil工程文件、CubeMX的ioc配置、hex烧录文件及静态库等。C源码和头文件构成完整控制逻辑启动文件完成芯片初始化ioc便于重新生成外设配置hex可直接烧录验证从中可清晰梳理从寄存器配置到运动控制的开发路径。资源目前已获369人学习下载学习者可基于完整源码、配置文件和编译产物快速理解麦克纳姆轮底盘驱动原理并移植到自己的项目中与CubeMX初始化代码衔接后即可投入实际改造。1. 麦克纳姆轮底盘驱动难的不是轮子而是解算做智能物流小车和工创赛项目时底盘从两轮差速换成麦克纳姆轮第一反应是“不就是多了两个电机嘛”真正上手才发现问题全在底盘驱动这一层四个轮子怎么转才能走出平移、旋转、任意角度斜向运动速度怎么分配才不会让轮子互相憋死以及STM32的PWM频率和编码器采样率该怎么匹配。这篇文章就把基于STM32F103ZET6的麦克纳姆轮小车底盘驱动拆开讲核心放在运动学解算和速度闭环上适合拿来当毕设或课程设计的底盘基础直接落地到代码角度不绕理论。2. 麦克纳姆轮运动学解算先搞定坐标系再谈驱动2.1 四个轮子的速度分配与辊子方向麦克纳姆轮的关键在轮毂上斜向安装的辊子通常为45度。底盘要走出全向运动本质上是把机器人坐标系下的目标速度分解到四个轮子的滚动方向。这里先把坐标系定下来x轴为小车前进正方向y轴为左移正方向逆时针旋转角度增量为正。底盘俯视图下四个轮子位置定义如下左前轮LF、右前轮RF、左后轮LB、右后轮RB。我调试时习惯用“X型排布”也就是左前轮和右后轮的辊子方向一致右前轮和左后轮方向一致。2.1.1 从底盘速度到轮子速度的推导设底盘目标线速度为(vx, vy)目标角速度为ω半车长和半车宽分别为l和w注意这里的w是宽度的一半不要和角速度符号混了。四个轮子的线速度计算公式右前轮速度vRF vx - vy - (l w) * ω左前轮速度vLF vx vy (l w) * ω左后轮速度vLB vx vy - (l w) * ω右后轮速度vRB vx - vy (l w) * ω这组公式是底盘驱动的核心。注意符号约定不同控制板给的公式可能正负号相反只要保证“前后轮对称”即可。我一般把正速度定义为电机正转小车前进方向不对就翻转某个轮的极性而不是去改公式。2.1.2 运动学逆解代码实现在实际工程里我会把运动学解算放在一个独立文件里输入是底盘目标速度输出是四个轮子的目标速度单位mm/s代码如下typedef struct { float vx; // 前进速度 mm/s float vy; // 横移速度 mm/s正值左移 float wz; // 角速度 rad/s } ChassisSpeed; typedef struct { float speed_rf; // 右前轮 float speed_lf; // 左前轮 float speed_lb; // 左后轮 float speed_rb; // 右后轮 } WheelSpeed; #define HALF_LEN 120.0f // 半车长 mm轮轴到几何中心 #define HALF_WID 90.0f // 半车宽 mm WheelSpeed Kinematic_Forward(ChassisSpeed vel) { WheelSpeed out; float factor HALF_LEN HALF_WID; out.speed_rf vel.vx - vel.vy - factor * vel.wz; out.speed_lf vel.vx vel.vy factor * vel.wz; out.speed_lb vel.vx vel.vy - factor * vel.wz; out.speed_rb vel.vx - vel.vy factor * vel.wz; return out; }这里factor等于半车长加半车宽作用是把角速度转换成线速度增量。代码里只做了公式映射没有做限幅实际使用时要在上层限制vx、vy和wz的范围。另外如果你发现某个轮子转向反了优先检查电机接线方向其次才检查公式符号因为四个轮子接线顺序很容易错。2.2 底盘正运动学用轮子反过来推算位置麦克纳姆轮底盘在工程上还有一个高频需求不依赖陀螺仪仅用编码器数据估算小车水平位移。这就用到正运动学也就是从四个轮子的实际速度推算出底盘速度vx (vRF vLF vLB vRB) / 4vy (vRF - vLF - vLB vRB) / 4wz (- vRF vLF - vLB vRB) / (4 * (l w))注意这个公式推算出来的wz在轮子出现打滑时误差会很大所以正运动学通常只用来做短时间的位置积分或者和陀螺仪数据做互补滤波。我在实际项目里一般不用它来做长时间定位只是用于验证运动学解算是否符合预期。计算项使用的轮速说明前进速度vx四个轮速之和除以4对打滑不敏感可短时积分横移速度vyvRF - vLF - vLB vRB 除以4依赖辊子吃地打滑误差大角速度wz对角轮差作差分建议用陀螺仪替代3. STM32底盘驱动硬件层PWM、编码器与电机驱动接口3.1 电机驱动选型与接线约定底盘驱动最常见的电机组合是直流减速电机配正交编码器驱动板用TB6612或DRV8701。STM32F103ZET6满配定时器资源够用两个高级定时器TIM1和TIM8四个通用定时器TIM2到TIM5。建议用TIM1输出两路PWM控制左前和右前电机TIM8输出两路控制左后和右后电机编码器接口用TIM2和TIM4的通道1、2。接线时注意TB6612的AIN1/AIN2是方向控制PWMA是速度控制。方向引脚用普通GPIOPWM波频率我习惯设在10kHz到20kHz之间。10kHz以下电机会有可闻噪声20kHz以上对MOSFET开关损耗比较高10kHz在工程上最稳。3.1.1 PWM初始化配置以下是TIM1两路PWM输出配置输出比较模式为PWM1占空比由比较寄存器控制void Motor_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_8 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Prescaler 72 - 1; // 72MHz / 72 1MHz TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period 100 - 1; // 1MHz / 100 10kHz TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM1, TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM1, TIM_OCInitStructure); TIM_OC3Init(TIM1, TIM_OCInitStructure); TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); }这里预分频设置为71计数器频率是1MHz自动重载值是99所以PWM频率为1MHz/10010kHz。脉冲值范围是0到99对应0到100%占空比。这个配置下速度控制的刷新周期是10kHz远高于速度环的500Hz到1kHz控制频率足够用了。3.2 编码器采集与四倍频编码器是底盘驱动的“眼睛”。常用电机编码器为霍尔式或光电式每圈线数在300到500之间。STM32定时器编码器模式可以硬件四倍频也就是把AB正交信号的两个边沿都计数实际分辨率是每圈线数的四倍。3.2.1 编码器模式配置以TIM2为例代码如下void Encoder_Init(TIM_TypeDef* TIMx) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period 0xFFFF; TIM_TimeBaseInit(TIMx, TIM_TimeBaseStructure); TIM_ICInitStructure.TIM_Channel TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 10; TIM_ICInit(TIMx, TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 10; TIM_ICInit(TIMx, TIM_ICInitStructure); TIM_EncoderInterfaceConfig(TIMx, TIM_EncoderMode_TI1_TI2, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); TIM_Cmd(TIMx, ENABLE); }编码器模式配置完成后定时器计数器会随着电机正转或反转自动增减。ICFilter设为10可以滤掉AB信号上的毛刺但这个值不能设太大否则响应变慢。读取速度时只需要周期性地读取TIM_GetCounter(TIMx)然后清零计数器即可。通过定时器中断以固定周期读取计数差值再除以该周期就能得到轮速。我一般把速度采集和控制放在同一个定时器中断里中断频率为500Hz也就是每2ms采集一次编码器差值并执行一次PID计算。这个频率对麦克纳姆轮底盘来说足够超过1kHz反而容易把PWM占空比抖得很厉害。4. 底盘驱动软件框架与PID速度闭环4.1 控制周期划分与任务调度底盘驱动软件不要写成一个while大循环里紧巴巴的顺序执行否则运动学解算、编码器读取、速度环PID全挤在一起中断延迟一高轮速就忽快忽慢。常见做法是分两层低速层1ms到2ms的定时器中断做编码器采集、四轮速度PID计算和PWM占空比更新。中速层10ms到20ms的周期任务做运动学解算、无线遥控数据解析或串口指令接收。这两层通过全局变量共享目标速度。注意在中断里改PWM占空比时不要让目标速度的写入被同时打断否则可能读到半新半旧的数据。实际工程中我会把“更新目标速度”这个动作放在主循环里中断只负责读主循环只负责写。4.2 增量式PID在四轮底盘上的实现速度环PID是底盘驱动里最重要的一环。麦克纳姆轮在横移时辊子与地面存在滑动摩擦力轮速响应比普通两轮差速底盘更不线性所以调试时不能用一套PID参数给四个轮子。常见做法是每个轮子单独调比例系数Kp积分项K i 和微分项K d 保持一致然后根据打滑情况微调。typedef struct { float target; // 目标速度 mm/s float last_error; // 上次误差 float integral; // 积分累计 float kp; float ki; float kd; float output; // 输出占空比 0~100 } SpeedPID; float SpeedPID_Update(SpeedPID *pid, float actual) { float error pid-target - actual; pid-integral error; // 积分限幅防止长时间堵转导致积分饱和 if (pid-integral 300.0f) pid-integral 300.0f; if (pid-integral -300.0f) pid-integral -300.0f; float pout pid-kp * error; float iout pid-ki * pid-integral; float dout pid-kd * (error - pid-last_error); pid-last_error error; pid-output pout iout dout; if (pid-output 100.0f) pid-output 100.0f; if (pid-output -100.0f) pid-output -100.0f; return pid-output; }这段代码用的是增量式PID输出是直接叠加在占空比上的增量不是绝对占空比。这样做的好处是换向时不会有大的冲击。但要注意output累计值一定要限幅否则小车悬空测试后落到地面的一瞬间输出会冲过头造成明显抖动。速度PID初始化阶段我一般先只用Kp关闭Ki和Kd让轮子按一个固定占空比转然后逐渐加快目标速度直到出现等幅振荡记录临界Kp和振荡周期再按工程整定公式算Ki和Kd。麦克纳姆轮底盘因为辊子方向问题起步时可能会有轻微打滑所以Kp不要调得太“硬”否则发出的声音会很难听轮子也在原地颤。4.3 PID参数建议与调试顺序下面是一组适合STM32F103ZET6加普通直流减速电机编码器每圈300线减速比30:1的调试起始参数参数数值范围说明控制频率500 Hz2ms中断编码器计数周期Kp20 - 60速度误差对应的占空比比例Ki0.5 - 2.0稳态误差消除数值小Kd0.1 - 0.5抑制超调打滑时降低PWM频率10kHz超过人耳可闻频率噪声小编码器滤波10降低毛刺干扰避免误计数调试时先把小车垫空用串口把四轮实际速度打印出来。让目标速度从0加到400mm/s查看四轮响应是否一致。随后放到地面在横移模式下观察底盘是否会斜着跑如果有明显偏航就对单个轮子的Kp做正负5%以内的调整不要大幅改动积分项。5. 麦克纳姆轮底盘驱动进阶限幅、平顺性与故障排查5.1 运动学输出限幅与加速度斜坡麦克纳姆轮底盘驱动里一个高频坑是运动学解算出来的四个轮速经常超过电机最大转速。比如底盘快速横移同时旋转某个轮子的速度需求很容易超过PWM满占空比。这时候直接将轮速饱和会造成底盘运动路径畸变表现为应该直线横移时跑出弧线。常见解决方案是“降比例限幅”先计算解算后四个轮子速度的绝对值的最大值然后按比例缩放而不是单独截断某一个。如下代码void WheelSpeed_Limit(WheelSpeed *ws, float max_speed) { float max_abs fabs(ws-speed_rf); if (fabs(ws-speed_lf) max_abs) max_abs fabs(ws-speed_lf); if (fabs(ws-speed_lb) max_abs) max_abs fabs(ws-speed_lb); if (fabs(ws-speed_rb) max_abs) max_abs fabs(ws-speed_rb); if (max_abs max_speed) { float ratio max_speed / max_abs; ws-speed_rf * ratio; ws-speed_lf * ratio; ws-speed_lb * ratio; ws-speed_rb * ratio; } }这样就不会出现某个轮子先饱和导致底盘跑偏的问题。另一种做法是对底盘目标速度vx、vy、wz分别限制但效果不如整体缩放精确。加速度斜坡也不能忽略。如果直接从0跳到目标速度底盘起步瞬间的急加速会让麦克纳姆轮辊子在地面打滑进而影响编码器反馈速度环会看到轮速滞后产生冲击。我会在底盘控制层加一个斜坡函数让vx、vy、wz每个周期朝目标值逼近一步步长根据控制周期计算。比如2ms控制周期加速度限制为2000mm/s²那么每周期速度变化量就是4mm/s。5.2 驱动层排错从现象定位问题调试中常见的现象是“小车在原地转圈”或“前进时明显偏航”。排查顺序我一般按这个来第一步检查编码器计数方向。将四个轮子分别正转和反转通过串口打印定时器计数器变化方向确认编码器方向与电机转向一致。第二步单独给四个轮子写入固定占空比看电机是否都正常启动。如果某个轮子不转优先检查TB6612的VM和STBY引脚STBY如果没拉高整个驱动芯片不输出。第三步做开环方向测试。设置所有轮子占空比为正底盘应该向前走。如果某个轮子反转就交换该电机的IN1和IN2接线而不是在程序里改符号。因为程序里的符号改动会影响运动学公式的一致性容易记混。第四步闭环测试时观察四轮实测速度的稳态误差。有一个轮子误差明显大于其他三个先看该轮编码器是否有轴向间隙其次是调高该轮的Ki不要全局调Ki。5.3 利用串口可视化四轮速度波形底盘驱动调参不能只靠眼睛看我建议把四轮实际速度以固定频率通过串口发送到上位机用匿名上位机或VOFA的波形模式观察。波形里能看到超调量、响应时间和四轮一致性这是判断底盘驱动好坏最直接的依据。调试时我会给底盘加一个“左右轮流移动”的命令让vx和vy交替变化观察四轮速度是否同步上升和下降。如果在切换方向时某个轮子的速度波形出现明显毛刺基本可以断定是该轮的PID参数过冲或编码器采样异常。最后说一个容易被忽略的小技巧速度PID的输出虽然是占空比但在PWM死去模式或者电机驱动芯片死区时间较长的方案里过小的占空比输出会让电机处于似转非转的状态产生明显的抖动。我会设置一个最小输出阈值当PID输出绝对值小于该值时直接置0避免电机长时间处于微震动状态。这个阈值通常取最大占空比的5%到8%具体数值根据电机启动电压决定。处理好这一步麦克纳姆轮底盘的起步和低速横移表现会稳定得多。本文还有配套的精品资源点击获取
返回列表