ARTICLE DETAIL

资讯详情

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

STM32麦克纳姆轮小车底盘驱动设计:从运动学解算到PID闭环

STM32麦克纳姆轮小车底盘驱动设计:从运动学解算到PID闭环 简介基于STM32的麦克纳姆轮智能小车底盘驱动工程资源面向单片机入门与进阶学习者适合作为毕业设计、课程设计、大作业或工程实训的参考项目主要解决麦克纳姆轮小车底盘电机驱动、运动控制与程序框架搭建等问题。压缩包共985个文件以C源码和头文件为主同时包含启动汇编、链接脚本、Keil工程配置、CubeMX配置、hex烧写文件、map等辅助文件整体约24.13MB整体结构能够反映从外设初始化到驱动实现的完整链路。工程保留了编译中间文件与可执行输出方便学习者在编译报错或控制异常时对照排查内置的常用DSP数学库源码有助于理解速度环与运动学计算中涉及的数据表和变换逻辑。学习者可直接导入工程修改控制参数复现麦克纳姆轮底盘运行效果也可以将驱动模块提取后迁移到其他STM32项目中作为二次开发基础。目前已有369人学习下载适合正在备赛、做课程设计或准备工程实训的嵌入式开发者。1. 基于STM32控制的麦克纳姆轮智能小车底盘驱动是核心主线先把结论放前面麦克纳姆轮只是机械轮组真正决定整车性能的是底盘驱动。麦克纳姆轮智能小车和普通两轮差速小车的最大区别在于它可以在不改变车头朝向的前提下横向平移、原地旋转甚至斜向漂移。这种全向移动能力来自每个轮子上的45度辊子而辊子的布置方向决定了四个轮的转速必须按特定关系配合。如果底盘驱动不把运动学解算、电机闭环、PWM输出连成一条完整链路那么麦克纳姆轮要么推不动要么原地打转。下面以STM32F103系列为控制核心把底盘驱动的关键环节从数学模型、硬件选型讲到代码落地适合参加工创赛智能搬运小车、做基于STM32的毕业设计或验证产品底盘的开发者参考。2. 麦克纳姆轮运动学解算四个轮子的目标转速先算对2.1 轮组布局与辊子方向决定解算公式的符号麦克纳姆轮的辊子与轮轴成45度角分左旋和右旋两种。标准底盘把四个轮布置成左前与右后同旋、右前与左后同旋的形式四轮呈镜像对称。这样布置的目的是让四个轮子的辊子轴线指向底盘中心方向当四个轮子转速按特定比例组合时接触地面的摩擦力才会合成出前进、横移、旋转等方向可控的合力。写底盘驱动的运动学解算代码之前先把坐标系定义清楚。以下约定在STM32固件里统一使用以底盘中心为原点x轴指向车辆前进方向y轴指向车身左侧绕z轴逆时针为旋转正方向。底盘的目标速度用一个三元组(v_x, v_y, ω)表示v_x是前后速度v_y是横移速度ω是角速度。四个轮子的线速度记为v1、v2、v3、v4分别对应左前、右前、左后、右后。运动学逆解要做的事就是从这三个底盘速度推导出四个轮子线速度。不同资料里的麦克纳姆轮公式符号不一致根源是坐标系定义不同。有的把x轴指向右侧有的把y轴指向右边有的把逆时针作为负方向公式里每一项的加减号自然就变了。所以写代码时第一步不是抄公式而是把坐标系和轮子序号写死在代码文件头部的注释里。下表是本文使用的符号约定后续所有代码都按这个表写。符号含义单位示例值v_x底盘前后速度正值前进mm/s200v_y底盘左右速度正值左移mm/s0ω底盘旋转角速度正值逆时针rad/s0.5L前后轮距的一半mm120W左右轮距的一半mm100r轮子外缘半径mm402.2 逆解公式与量纲换算从线速度到电机转速按照上一小节的坐标系和轮子编号常用的麦克纳姆轮运动学逆解公式可以写成v1 v_x - v_y - ω(L W) v2 v_x v_y ω(L W) v3 v_x v_y - ω(L W) v4 v_x - v_y ω(L W)这条式子直接来自每个轮子的辊子受力分解。以左前轮为例它同时承担底盘向前的分量、向横移方向的分量和绕中心旋转的分量方向由辊子的倾角决定。ω(LW)是旋转分量单位为mm/s所以v1到v4的单位也是mm/s。要让底盘朝正前方直行v_y和ω都为0时四个输出相等所有轮子同速运转要让底盘原地旋转v_x和v_y为0四个输出两正两负产生绕中心的扭矩要让底盘横移v_y不为0左右两侧轮子的转速关系会反转。这个公式的输出是线速度但电机控制需要的是转速或者PWM占空比中间差两步换算。第一步把mm/s除以轮子半径得到rad/s第二步乘上60/(2π)再乘减速比得到电机轴的RPM。减速比很容易被漏掉漏掉之后速度环的目标值和编码器反馈值会差一个减速比的倍数PID无论怎么调都调不出好效果。反过来如果驱动板工作在电压模式直接把占空比设置为线速度的线性映射也能跑但换电池电压之后同一占空比下的实际速度会变化远不如转速闭环稳定。还要提一个实际现象电机标称空载转速和带载后的真实转速相差可能达到两成到三成。设计底盘最高速度时按空载转速的70%估算比较可靠。比如减速比30的电机空载转速600rpm带载后大概在420rpm上下对应80mm轮径的轮子外缘线速度约为1.76m/s。2.3 C语言实现运动学逆解先跑通再谈优化逆解代码在底盘驱动工程里很少改动通常一次写好之后只调参数。下面是一个可以直接放进STM32工程的基础实现typedef struct { float x; // 底盘前后速度mm/s正值前进 float y; // 底盘左右速度mm/s正值左移 float w; // 底盘旋转速度rad/s正值逆时针 } ChassisCmd_t; typedef struct { float v1; // 左前轮线速度mm/s float v2; // 右前轮线速度mm/s float v3; // 左后轮线速度mm/s float v4; // 右后轮线速度mm/s } WheelSpeed_t; void Mecanum_Inverse(ChassisCmd_t *cmd, WheelSpeed_t *wheel, float L, float W) { float lw L W; wheel-v1 cmd-x - cmd-y - cmd-w * lw; wheel-v2 cmd-x cmd-y cmd-w * lw; wheel-v3 cmd-x cmd-y - cmd-w * lw; wheel-v4 cmd-x - cmd-y cmd-w * lw; }这段代码有个关键点输出不仅是速度大小还带符号正负号代表电机转向。STM32控制直流电机时PWM只控制占空比转向靠GPIO的高低电平组合决定所以底盘驱动下发指令时要分两步走先根据符号设置方向GPIO再根据绝对值的大小设置PWM占空比。绝对值不要直接用负数去写寄存器先做一次fabsf()运算再判断是否超过当前PWM分辨率允许的最大值。提示四个轮的符号关系很容易在接线阶段被破坏。如果哪一路电机线接反这轮子的实际转向和运动学解算结果相反小车会呈现剧烈的偏转。排查时先给所有轮子发同一个正向PWM确认四个轮子同向转动后再做闭环。实际应用中横移时辊子与地面之间会产生滑动某些轮子的目标线速度很小如果不加处理PWM在低速区间会出现抖动。常见做法是增加一个速度死区当|vN|小于30mm/s就把这一路轮速清零并把方向GPIO置为制动或空转状态。这个死区值要结合底盘重量和轮径现场标定太大会让微调横移变得一顿一顿太小则滤不掉低速抖动。函数本身的计算量可以忽略不计放主循环或者定时器中断里都没问题。不要在主循环里反复调用输出函数去操作寄存器把解算结果保存到结构体统一由速度环模块在固定周期消费。3. 底盘驱动硬件选型与电机驱动电路PWM要送对管脚3.1 电机减速比与编码器分辨率的配合麦克纳姆轮底部结构与普通轮不同横移时辊子与地面有滑动摩擦启动瞬间要克服的阻力比两轮差速车大。选电机时我一般先看三个指标额定电压下扭矩是否够起步、空载转速是否满足底盘最大速度、编码器分辨率是否满足速度环采样需求。常见的底盘驱动方案是带霍尔编码器的直流减速电机12V供电、减速比1:30到1:60。减速比越大输出转矩越大但同尺寸电机最高转速越低所以要根据轮径和车身重量平衡。编码器分辨率直接影响速度环的平滑度。霍尔编码器常见规格是13PPR也就是电机出轴每转输出13个脉冲经过STM32定时器正交解码4倍频后每转有52个脉冲。再乘以减速比轮子端转动一圈得到的脉冲总数通常在1300到3000。以10ms采样周期为例低速2rpm的轮速换算成脉冲差约为2×3000/60001个脉冲勉强能用如果转速更慢或者采样周期更短测速量化噪声就会明显速度环低俗区会抖。下表是这些参数的参考范围注意只是参考具体数值以实际电机和机械结构为准。参数推荐范围说明工作电压6V~12V12V出口余量更大驱动板耐压要确认减速比1:30~1:60车身重或轮径大选大减速比编码器13PPR正交霍尔必须输出A/B相位轮子半径30mm~40mm麦克纳姆轮标准规格底盘L/W实测中心距的一半误差大于2mm会影响横移直线度3.2 驱动芯片选型TB6612与BTS7960的取舍STM32的GPIO驱动能力只有几毫安不能直连电机必须经过电机驱动芯片。最常见的底盘驱动方案是TB6612FNG。它工作在2.7V到13.5V最大输出电流约1.2A每路一颗芯片内置两路H桥可以带两个电机四轮底盘用两颗。TB6612的引脚也比较好接PWMA和PWMB接收PWM信号AIN1/AIN2、BIN1/BIN2是方向控制STBY待机脚必须拉高VM接电机电源VCC接逻辑电源GND和STM32共地。BTS7960则是另一种选择单颗芯片就是半个H桥要两颗组成一个全桥单路峰值电流可以到40A以上适合底盘更重、电流更大的比赛车。缺点是需要额外的逻辑电源和较大散热片接线复杂度高。对于大多数基于STM32的麦克纳姆轮智能小车TB6612完全够用重点是控制好通流和散热。两路同时堵转时芯片温度上升很快调试阶段把电源限流设置在1.5A以内比较保险。引脚分配不要临时随手接。以STM32F103ZET6为例PWM输出可以绑在TIM3和TIM4的通道上编码器输入绑在TIM1、TIM2上。这样做的原因同一颗定时器不能同时用作PWM输出和高精度正交解码编码器模式会占用定时器的所有通道和PWM复用会直接冲突。3.3 PWM通道分配与GPIO复用先画表再接线下面是四轮底盘驱动硬件里最常见的一组通道分配同样适用于STM32F103C8T6的部分引脚。功能定时器通道STM32引脚左前轮PWMTIM3CH1PA6右前轮PWMTIM3CH2PA7左后轮PWMTIM4CH1PB6右后轮PWMTIM4CH2PB7左前编码器A/BTIM2CH1/CH2PA0/PA1右前编码器A/BTIM1CH1/CH2PA8/PA9方向控制GPIO可以任意选普通引脚只要避开调试接口和晶振引脚即可。PA13/PA14是SWD调试引脚尽量不要占用否则后续下载程序会出问题。如果板子是STM32F103C8T6PA9是USART1_TX通常也被用来调试串口要和编码器输入复用同一引脚时建议调整方案。另一个常见坑是定时器通道重映射。STM32的某些引脚和定时器通道不是固定对应的需要检查AFIO重映射寄存器或数据手册。像TIM3_CH1默认就是PA6但如果PA6被其他外设占用可以重映射到PB4不过PB4和NJTRST冲突还需要设置AFIO的SWJ配置。这类问题会在项目中期才暴露提前确认芯片封装和引脚映射能省很多时间。方向GPIO的初始化比较简单下面是一个常见的配置// PB0/PB1作为一路H桥的方向控制推挽输出驱动能力足够控制逻辑引脚 GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStruct.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStruct);这段代码把PB0、PB1配置成推挽输出用来控制一路H桥的IN1、IN2。注意推挽输出模式下同一组引脚不能同时置1否则会形成上下桥直通驱动芯片立刻烧毁。以TB6612的方向脚为例当AIN11、AIN20时电机正转AIN10、AIN21时反转AIN1AIN20时电机制动。底盘驱动里切换方向时要先快速把两个方向引脚同时置零再切换避免中间状态产生直通。4. 底盘驱动固件实现从PWM输出到速度环闭环4.1 定时器PWM输出的配置与频率选择在底盘的驱动层PWM频率需要根据电机电感特性选择。频率太低电流脉动大电机会发出可听见的啸叫频率太高驱动芯片开关损耗变大温升更快。12V直流减速电机的电枢电感在几十到几百微亨之间10kHz到20kHz是比较常用的范围。以STM32标准库为例PWM初始化代码如下TIM_TimeBaseInitTypeDef TIM_BaseStruct; GPIO_InitTypeDef GPIO_InitStruct; TIM_OCInitTypeDef TIM_OCStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); GPIO_InitStruct.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStruct); TIM_BaseStruct.TIM_Prescaler 3; // 4分频72MHz/418MHz TIM_BaseStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_BaseStruct.TIM_Period 899; // 18MHz/90020kHz TIM_BaseStruct.TIM_ClockDivision 0; TIM_TimeBaseInit(TIM3, TIM_BaseStruct); TIM_OCStruct.TIM_OCMode TIM_OCMode_PWM1; TIM_OCStruct.TIM_OutputState TIM_OutputState_Enable; TIM_OCStruct.TIM_Pulse 0; TIM_OCStruct.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM3, TIM_OCStruct); TIM_OC2Init(TIM3, TIM_OCStruct); TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_Cmd(TIM3, ENABLE);代码里的关键参数是TIM_Prescaler3和TIM_Period899。系统时钟72MHz经过4分频后计数频率为18MHz再除以900得到20kHz PWM。占空比由TIM_SetCompare1(TIM3, value)设置value范围0到899。这里的说明Prescaler寄存器是3代表4分频因为计数是从0开始的Period899代表900个计数值。PWM分辨率约等于899级对电机控制足够。如果换成1000级或者2000级也可以但频率会降低容易听到噪声。PrescalerPeriod计数频率PWM频率占空比分辨率389918MHz20kHz900113996MHz15kHz400719991MHz1kHz1000注意初始化PWM时把TIM_Pulse设置为0是为了让电机在上电瞬间不转避免上电抖动。方向引脚也要在PWM使能之前初始化成全部输出低电平。4.2 编码器读取与测速代码4.2.1 定时器编码器模式初始化霍尔编码器输出两路正交信号A和B可以直接接到STM32定时器的编码器模式输入。以TIM2为例A相接PA0、B相接PA1。初始化代码TIM_TimeBaseInitTypeDef TIM_BaseStruct; TIM_ICInitTypeDef TIM_ICStruct; GPIO_InitTypeDef GPIO_InitStruct; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStruct.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStruct); TIM_BaseStruct.TIM_Prescaler 0; TIM_BaseStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_BaseStruct.TIM_Period 0xFFFF; TIM_BaseStruct.TIM_ClockDivision 0; TIM_TimeBaseInit(TIM2, TIM_BaseStruct); TIM_ICStruct.TIM_Channel TIM_Channel_1; TIM_ICStruct.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICStruct.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICStruct.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICStruct.TIM_ICFilter 6; TIM_ICInit(TIM2, TIM_ICStruct); TIM_ICStruct.TIM_Channel TIM_Channel_2; TIM_ICStruct.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICStruct.TIM_ICSelection TIM_ICSelection_IndirectTI; TIM_ICStruct.TIM_ICFilter 6; TIM_ICInit(TIM2, TIM_ICStruct); TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); TIM_Cmd(TIM2, ENABLE);编码器模式下TIM2-CNT的值会随着A/B相变化自动增减。TIM_ICFilter是输入滤波范围为0到15。底盘驱动电机的PWM会产生较强电磁干扰滤波值太小CNT会无故跳变滤波值太大信号边沿会被延迟高速时测速偏小。HALL编码器速度环我一般取6到10。TIM_Period设为0xFFFF是让CNT自由回绕避免上限太小导致频繁溢出。4.2.2 固定周期测速与计数器回绕转速要周期性读取一般用SysTick产生10ms中断中断里读TIM2-CNT并和前一次值做差。直接相减会因为16位计数器回绕产生一个巨大差值正确写法如下volatile int16_t last_cnt[4]; // 四路编码器上次计数值 volatile int16_t speed_pulse[4]; // 四路脉冲差正负代表方向 void speed_sample(void) { for (int i 0; i 4; i) { uint16_t cnt encoder_tim[i]-CNT; int16_t delta (int16_t)(cnt - last_cnt[i]); last_cnt[i] cnt; speed_pulse[i] delta; } }把两个无符号的16位计数值相减后转成int16_t利用补码特性不管计数器从0xFFFF到0还是从0到0xFFFF算出来的差都是正确的有符号数值。这个技巧比先判断方向再补差的方式更快更稳。用户调用时speed_pulse[i]再乘以一个标定系数就能换算成实际轮速比如每脉冲0.02mm/s这个系数需要在真实底盘上标定。4.3 速度环PID与限幅底盘驱动闭环速度环用增量式PID最合适。增量式只输出相对上次的调节量天然不容易发生积分饱和冲击。在10ms控制周期内完整的一次速度环更新可以用下面的结构体实现typedef struct { float kp, ki, kd; float target, last_err, integral, output; } PID_t; float PID_Update(PID_t *pid, float feedback) { float err pid-target - feedback; pid-integral err; float d_err err - pid-last_err; pid-output pid-kp * err pid-ki * pid-integral pid-kd * d_err; pid-last_err err; if (pid-integral 50) pid-integral 50; if (pid-integral -50) pid-integral -50; if (pid-output 300) pid-output 300; if (pid-output -300) pid-output -300; return pid-output; }PID_Update的返回值和输入都是脉冲差或者标定后的速度值单位要保持一致。kp、ki、kd分别对应比例、积分、微分系数。麦克纳姆轮底盘在横移时辊子滑动会让轮子出现打滑积分项容易在目标速度和实际速度长期不一致时一直增长所以integral和output都要加限幅。调参顺序是先令ki0、kd0只调kp让电机在额定转速附近不振荡再从小到大加ki消除稳态误差。对霍尔编码器而言kd一般不要用或者开得很小因为测速噪声会被微分项放大造成PWM抖动。方向切换时如果当前占空比绝对值很小电机会因为静摩擦停在原地。底盘驱动固件里要加入最小占空比处理例如float pwm fabsf(pid.output); if (pwm 30.0f) { pwm 0.0f; // 小于最小驱动占空比时直接关断 }这条代码能在低速微调时显著改善手感同时减少电机在零速附近的往复抖动。5. 底盘驱动调试技巧用串口和悬空测试定位四轮问题底盘驱动调试最难的不是代码或硬件而是不知道问题来自哪一层。以下调试顺序适合麦克纳姆轮智能小车反复使用过多次能快速收敛问题。先做悬空测试把底盘用支架垫起来四个轮子离地。这一步去掉了地面摩擦和辊子滑动能让电机和编码器先暴露自身问题。发送一个固定目标速度同时用串口打印四路实际速度。如果某一轮数值跳动比另外几轮大优先查这路的编码器接线和滤波参数。再看方向一致性。给所有轮子发同一个正PWM悬空状态下应该看到四个轮子同时正向旋转。旋转方向如果不对调换这一路电机的两根电源线。四路都确认后再做运动学解算验证给定vx0、vy200mm/s、w0四个轮子的速度应立即变成左右对称分布左右两侧呈相反方向这样底盘才会横移。解决低速抖动大概率是PID的微分项太大或者PWM最小占空比没有设置。第一阶段先关闭D加大P的阻尼方式观察串口的实际速度曲线是否收敛。霍尔编码器在低速时测得的脉冲在1到2附近跳变采样值不齐速度环会表现为周期性抽搐。串口数据格式可以这样组织// 目标转速和实际转速逐路打印方便在终端里对比四路一致性 printf(FOR %d %d %d %d, ACT %d %d %d %d\r\n, target[0], target[1], target[2], target[3], actual[0], actual[1], actual[2], actual[3]);注意用printf输出调试信息会占用USART1中断时间如果控制周期是10ms串口波特率建议不低于115200否则printf本身的时间开销会影响控制节拍。调试完毕之后把实时打印关闭只保留故障上报避免对控制周期产生干扰。最后一招是用数据确认结果给底盘一个固定vx、vy、w组合让它跑两米直线记录左右偏移量。如果偏移量随速度明显增大问题大概率在速度环四路一致性如果偏移量与速度无关只在启动瞬间出现则问题在方向GPIO切换时序或者运动学解算的符号。先用卷尺量出底盘运动轨迹再去改代码比凭空猜测快得多。本文还有配套的精品资源点击获取
返回列表