基于STM32F103ZET6的麦克纳姆轮驱动底板设计与运动学解算
发布时间:2026/9/17 2:34:33 作者:尧图编辑部 阅读量:1,286

简介基于STM32F103ZET6的麦克纳姆轮小车驱动底板设计资料是一套个人大四期间经导师指导并高分通过的毕业设计/课程设计项目。资源面向嵌入式、计算机相关专业正在做课设毕设的学生也适合需要项目实战练习的入门到进阶学习者可系统解决麦克纳姆轮小车驱动底板的硬件电路设计与STM32程序开发问题覆盖从原理图设计、PCB布局到固件调试的关键环节。压缩包共396个文件约59.93MB其中以166个C源码和152个H头文件构成完整嵌入式程序框架配合12个PCB封装与10个原理图文档清晰展示驱动底板电路连接还包含汇编启动文件、HEX固件、Keil工程配置及设备器件清单便于直接烧录验证或按需修改。目前已有203人学习下载。资料附带项目说明与关键脚本既可用于课程设计、期末大作业也能作为学习借鉴和二次开发的基础对想掌握STM32小车开发全流程的读者有较高参考价值。1. 麦克纳姆轮小车为什么要把运动学解算跑在STM32F103ZET6驱动底板上麦克纳姆轮底盘和普通差速底盘的根本差异不在轮子外形而在控制自由度。四个轮子独立驱动后底盘可以同时做前后移动、左右平移和原地旋转三个方向的运动。很多人拿到这类源码后直接在应用层发PWM值结果负载一变化四个轮子互相别劲车身摆、电流大、跑不成直线。这套驱动底板把方案定得很明确STM32F103ZET6负责麦克纳姆轮运动学解算、编码器闭环和指令解析板子把电源、电机驱动和主控集成在一起整台STM32F103ZET6智能小车只需要外部供一路电池电、往串口发目标速度就能动起来。选F103ZET6而不是小封装型号理由是这颗512KB Flash和112引脚的主控能把四路编码器接口、四路PWM、两路串口、SWD调试口全部原生承载毕设进行到第二阶段要加IMU或者WiFi模块时不用重新画板。下载包内是完整Keil工程加PCB源文件可以直接作为课程设计、期末大作业的移动平台也适合做驱动层的二次开发起点。2. 驱动底板硬件电机驱动选型、电源树与PCB走线规则2.1 驱动芯片选型在TB6612FNG与BTN7971之间做决定麦克纳姆轮小车是四电机系统选驱动芯片时对比的不是理论峰值电流而是“四个电机同时堵转”时的母线电流。TB6612FNG和BTN7971是两套完全不同的设计路线决定了PCB的铜箔面积和散热方式。对比项TB6612FNGBTN7971单路连续电流1.2A峰值3.2A20A封装形态SSOP-24手工焊接难度中等T022-7焊盘带散热片PWM频率上限100kHz25kHz推荐10~15kHz外围电路逻辑电源去耦电容需要自举电容、RC滤波典型应用小型直流减速电机大扭矩底盘、竞赛电机如果毕设选的是JGA25-370这类额定电流1A以内的减速电机TB6612FNG是够用的。但要注意一点麦克纳姆轮四轮同时从静止加速时启动瞬时电流很容易冲到1.5A以上。我的经验是驱动底板选TB6612时一定要检查铜箔——电源输入到VM引脚的走线宽度至少要2mm最好加铺铜否则大电流时线路压降会把VM电压拉低导致驱动芯片进入欠压保护。切换BTN7971则完全是另一套思路。每个电机需要两片BTN7971组成半桥对光驱动通道就要8片散热要求也高。所以驱动底板在设计时得把电机驱动区域固定成一种方案不要想着上了一个板子再飞线兼容飞线在大电流下会变成热源。2.2 电源树设计12V电机电与3.3V逻辑电必须做单点接地这个驱动底板的电源树直接决定系统稳定性。12V电池输入后先经过SS34肖特基二极管防反接然后兵分两路一路直接给驱动芯片VM引脚供电另一路经MP1584降压到5V再经AMS1117-3.3给STM32F103ZET6供电。电池12V ── SS34 ── 驱动芯片VM电机电源域 │ │ │ └── MP1584(DC-DC 5V) ── AMS1117-3.3 ── MCU VCC │ └── 分压电阻(100K/10K) ── PA1ADC电压监测电机启动和堵转时电池线上会有很大的电流尖峰。如果3.3V电源的地和VM的地直接大面积连通这个尖峰就会通过地平面耦合进MCU表现为小车一动串口就乱码、程序死机。正确的做法是分电源域在PCB上用磁珠或0Ω电阻把模拟地、数字地、电机地在STM32的GND焊盘附近做单点汇合。DC-DC的反馈电阻取电压采样点要放在输出电容之后不要远距离拉到接插件处再回来否则输出纹波会明显偏大。2.3 布局布线细节与AD20 DRC检查麦克纳姆轮驱动底板的PCB面积通常不大但上面既有大电流回路又有MCU最小系统布局密度高。布线时我把电机驱动区域的规则单独设置电机输出A/A-、B/B-走线宽度2mm铜厚2oz时连续电流余量充足PWM信号线和编码器信号线与电机线保持至少3倍线宽的间距避免磁场耦合STM32的VDDA引脚单独用10Ω电阻和1μF电容做模拟电源滤波VREF接3.3V去耦电容放置顺序是100nF靠近电源脚10μF钽电容在稍远外圈不能只放一个大的。用AD20做PCB DRC检查时有一个设置很多人会忽略默认规则里Power Net的间距往往小于Signal Net。如果给GND单独设置了0.3mm间距但规则优先级里默认的0.254mm间距规则在上层DRC不会报错但实际板厂会按更松的规则做。检查规则优先级时把“Supersede Rules”选项打开再重新跑一次DRC重点看Un-Routed和Clearance约束的输出结果。提示投板前还要在AD20里执行Tools-Fabrication-Fabrication Features检查确认丝印没有压到焊盘否则回流焊时丝印会干扰上锡。3. 麦克纳姆轮运动学解算从四轮线速度到底盘速度的映射3.1 逆运动学公式与坐标约定麦克纳姆轮的辊子与轮体轴线呈45度角轮子转动时地面对辊子施加的摩擦力可以分解为沿轮轴方向和垂直辊子方向两个分量。对四个轮子的力进行矢量合成就得到了底盘的三自由度速度模型。设底盘中心速度为(vx, vy)旋转角速度为ω轮子半径为R底盘中心到前/后轮轴线的距离为a到左/右轮中心线的距离为b四个轮子的线速度如下轮1前左V1 (Vx − Vy − (a b)·ω) / R轮2前右V2 (Vx Vy (a b)·ω) / R轮3后左V3 (Vx Vy − (a b)·ω) / R轮4后右V4 (Vx − Vy (a b)·ω) / R这个公式隐含了一个前提四个辊子安装方向必须是前左轮和后右轮为右旋45度前右轮和后左轮为左旋45度。PCB板或装配图里辊子装反是麦克纳姆轮小车运动学解算最常见的物理错误表现是前进时正常左平移时车体却发生了明显的旋转。遇到这种情况不用改代码先把四个轮子拆下来重新对一遍辊子朝向。3.2 STM32F103ZET6上的解算函数实现运动学解算是纯算术逻辑适合放在主循环或定时器中断里周期性调用不需要占用独立任务。下面这段代码在工程里对应mecanum.c输入底盘目标速度输出四轮线速度单位统一为m/s。// mecanum.c - 基于STM32F103ZET6的麦克纳姆轮逆运动学解算 #define WHEEL_RADIUS 0.045f // 轮子等效半径单位m #define ROBOT_HALF_WIDTH 0.090f // 底盘中心到前/后轮轴距离单位m #define ROBOT_HALF_LENGTH 0.095f // 底盘中心到左/右轮中心距离单位m void Mecanum_IK(float vx, float vy, float wz, float wheel_vel[4]) { // wz正值为顺时针旋转 float sum ROBOT_HALF_WIDTH ROBOT_HALF_LENGTH; wheel_vel[0] ( vx - vy - sum * wz) / WHEEL_RADIUS; // 前左轮 FL wheel_vel[1] ( vx vy sum * wz) / WHEEL_RADIUS; // 前右轮 FR wheel_vel[2] ( vx vy - sum * wz) / WHEEL_RADIUS; // 后左轮 RL wheel_vel[3] ( vx - vy sum * wz) / WHEEL_RADIUS; // 后右轮 RR }wheel_vel数组输出的是车轮线速度后续PID闭环的控制目标就是这个值。注意这里除的是等效半径WHEEL_RADIUS不是轮毂半径。四路结果同时包含正负号负号代表反转代码在进入PWM函数前还要做一个方向判断把负速度映射到反方向占空比上。3.3 关键参数标定轮径不等于轮子直径麦克纳姆轮的辊子外表面不是完整的圆形直接用卡尺量轮子最外沿得到的直径是偏大的因为辊子间隙导致等效接触圆变小。正确做法是标定前进距离的映射关系在地面贴一条长胶带标记轮子某一根辊子作为起始位手动推着小车让轮子转20圈量行走距离。等效半径 行走距离 / (20 × 2π)。参数错误做法正确做法WHEEL_RADIUS直接量轮子直径再除以2转20圈实测距离反算ROBOT_HALF_WIDTH用底盘最外沿宽度左右轮子接地中线的距离/2ROBOT_HALF_LENGTH凭车架图纸估计前轴与后轴轮子中心距/2a和b的标定误差不会在空载时暴露但跑边长1米的正方形轨迹时旋转项(ab)·ω会放大误差轨迹会不闭合。用正运动学反推也可以验证参数正确性即用四个编码器的实测速度反算底盘速度对比期望值是否一致。4. STM32程序实现定时器PWM、增量式PID与编码器接口4.1 定时器资源分配与PWM频率选择F103ZET6的定时器资源在这个项目里分配得很紧凑。四路电机PWM使用TIM3和TIM4分别输出两组编码器接口使用TIM2和TIM5的编码器模式串口通信占用USART1。预留TIM1作为高级定时器后续如果需要输出带死区的互补PWM驱动大功率MOS管可以直接复用。定时器通道分配工作模式用途TIM3CH1~CH4PWM输出四路电机PWM信号TIM4CH1~CH4PWM输出备用舵机或预留TIM2CH1~CH2/CH3~CH4编码器模式FL、FR轮转速反馈TIM5CH1~CH2/CH3~CH4编码器模式RL、RR轮转速反馈TIM1CH1~CH3PWM互补输出预留大功率驱动PWM频率我选10kHz。这个频率对减速电机来说足够低不会产生明显的线圈啸叫又比TB6612的上限低很多留有换向死区的余量。PC6、PC7等实现方向控制的GPIO引脚输出电平变换必须在PWM更新之前完成否则电机换向的瞬间会出现一个占空比冲突的毛刺长期冲击驱动芯片。4.2 PWM初始化与方向控制逻辑下面这段是TIM3四路PWM通道配置的核心代码其中关键点是使用PWM1模式ARR设为3600-1预分频器设为36-1。TIM3挂在APB1上时钟36MHz所以实际PWM频率36MHz / (3600 × 36) ≈ 277Hz。如果要在10kHz工作预分频值应设为0自动重装载值设为3600-1。// motor.c - 基于STM32F103ZET6的标准库初始化 static void TIM3_Config(void) { GPIO_InitTypeDef gpio; TIM_TimeBaseInitTypeDef tim; TIM_OCInitTypeDef oc; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); // PA6/PA7 - TIM3_CH1/CH2前两轮PWM GPIO_StructInit(gpio); gpio.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); // PB0/PB1 - TIM3_CH3/CH4后两轮PWM gpio.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_Init(GPIOB, gpio); TIM_StructInit(tim); tim.TIM_Period 3600 - 1; // 10kHz占空比分辨率0.027% tim.TIM_Prescaler 0; tim.TIM_ClockDivision TIM_CKD_DIV1; tim.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, tim); TIM_StructInit(oc); // 需要重新初始化结构体 oc.TIM_OCMode TIM_OCMode_PWM1; oc.TIM_OutputState TIM_OutputState_Enable; oc.TIM_Pulse 0; oc.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM3, oc); TIM_OC2Init(TIM3, oc); TIM_OC3Init(TIM3, oc); TIM_OC4Init(TIM3, oc); TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_Cmd(TIM3, ENABLE); }方向控制由TB6612的IN1/IN2引脚决定。每个电机的方向占用两个GPIO在PWM配置完成后单独初始化。占空比写入使用TIM_SetComparex函数代码里需要先判断目标线速度的正负正数则IN11、IN20写入占空比负数则IN10、IN21写入占空比绝对值。这里要特别注意在切换方向前先把PWM设为0否则会有一个极短时间的电制动对齿轮损伤较大。4.3 增量式PID速度环让四个轮子真正同步四个麦克纳姆轮如果只是开环PWM驱动负载变化会导致转速漂移底盘运动轨迹完全不可预期。所以程序里对每个轮子都做了增量式PID闭环控制频率1kHz由SysTick中断驱动。PID的目标量是3.2节解算出的线速度反馈量来自编码器测速。// pid.c - 增量式PID控制器 typedef struct { float kp; // 比例系数 float ki; // 积分系数 float kd; // 微分系数 float target; // 目标线速度 m/s float integral; // 积分累计值 float last_error; // 上次误差 int16_t pwm_out; // PWM增量输出 } MotorPID; void PID_Calc(MotorPID *pid, float current_speed) { float error pid-target - current_speed; pid-integral error; // 积分限幅防止低速长时间堵转导致积分饱和 if (pid-integral 50.0f) pid-integral 50.0f; if (pid-integral -50.0f) pid-integral -50.0f; float derivative error - pid-last_error; pid-last_error error; // 增量式输出只需要累加PID系数与误差的组合 pid-pwm_out (int16_t)(pid-kp * error pid-ki * pid-integral pid-kd * derivative); if (pid-pwm_out 3599) pid-pwm_out 3599; if (pid-pwm_out -3599) pid-pwm_out -3599; }增量式PID输出的是PWM占空比增量而不是绝对值好处是没有积分饱和的累积阶跃转速变化时车体冲击小。实际调试时先给kp一个较小值如0.5ki先从0开始观察轮子是否来回振荡。如果轮子发出高频“嗡嗡”声说明kd太大把微分项放0.1再调。5. 联调实战单轮测试、全向验证与串口指令封装5.1 单轮方向与编码器极性验证焊接完驱动底板第一次上电先断开所有电机线用万用表量驱动芯片VM对地电压为12V、VCC对地3.3V后再接电机。下载程序后不要直接跑PID而是写一段单轮开环测试static void Wheel_OpenLoop_Test(uint8_t idx, int16_t pwm_value) { // 以左前轮FL为例PA6 PWMPC0/PC1方向 if (idx 0) { GPIO_WriteBit(GPIOC, GPIO_Pin_0, Bit_SET); GPIO_WriteBit(GPIOC, GPIO_Pin_1, Bit_RESET); TIM_SetCompare1(TIM3, (uint16_t)pwm_value); } Delay_Ms(3000); TIM_SetCompare1(TIM3, 0); }轮子正转时观察编码器计数值。如果计数为负说明编码器A/B相接反了此时可以选择在TIM编码器配置里交换CH1和CH2的极性也可以直接调换电机编码器排线。务必在标签纸上标好每个轮子的PWM通道、方向引脚和编码器计数方向四个轮子全部确认后再进入闭环调试。5.2 全向运动验证矩阵闭环调通后用串口依次发送三种运动指令验证底盘响应是否符合预期。下表是三种典型指令对应的输入参数和预期运动指令类型vx (m/s)vy (m/s)wz (rad/s)预期运动前进0.2000沿车头方向直线前进左平移00.200整体向左平移车头方向不变原地右转000.50绕底盘中心顺时针旋转如果前进时车体跑偏优先检查左右两侧轮子的WHEEL_RADIUS标定是否一致。如果左平移伴随旋转把前左轮和后右轮的辊子方向和公式再次比对。原地旋转必须严格不产生平移如果旋转时底盘画圈说明ab参数偏大或偏小。5.3 串口指令协议与中断同步封装联调阶段最实用的是封装一个极简串口指令协议。我这里用$开头、逗号分隔三个浮点、\n结尾的格式波特率115200// 串口中断里只做帧接收不解析 void USART1_IRQHandler(void) { static uint8_t buf[32]; static uint8_t len 0; if (USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { uint8_t ch USART_ReceiveData(USART1); if (ch $) { len 0; } else if (ch \n len 0) { buf[len] \0; sscanf((char *)buf, %f,%f,%f, cmd_vx, cmd_vy, cmd_wz); cmd_ready 1; len 0; } else { if (len 31) buf[len] ch; } } }中断里只做收帧和标志位置位运动学解算和PID计算放在主循环里读取cmd_ready后执行。这样串口数据不会打断PWM更新时序也避免了在中断里调用耗时函数导致的定时器抖动。本文还有配套的精品资源点击获取