ARTICLE DETAIL

资讯详情

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

STM32两轮差速小车开环控制:从PWM原理到工程实践

STM32两轮差速小车开环控制:从PWM原理到工程实践 1. 项目概述与核心价值最近在整理工作室的物料翻出来一堆之前做智能小车剩下的电机、轮子和几块STM32F103的开发板。看着这些零件我突然想到很多朋友在入门嵌入式开发时总觉得第一步就卡在了“闭环控制”、“PID算法”这些听起来就有点吓人的概念上。其实完全可以从更基础、更直观的“开环控制”开始玩起。这次我们就用最经典的STM32单片机来搭建一个两轮差速小车并实现它的基础开环控制。所谓开环控制简单说就是“我发指令你执行至于执行得准不准、外界有没有干扰我暂时不管”。这就像你蒙着眼睛指挥别人往前走三步你只关心“发出了走三步的指令”而不去核实他是不是真的只走了三步或者路上有没有被石头绊到。对于单片机学习而言开环控制是理解GPIO、PWM、定时器等核心外设以及建立“程序控制物理世界”这一概念最直接的桥梁。这个项目非常适合刚学完STM32基本外设比如点灯、按键想找个综合小项目练手的朋友。你不需要任何复杂的传感器如编码器、陀螺仪只需要一块STM32核心板、一个电机驱动模块比如L298N或TB6612、两个直流减速电机和轮子再加上一个电源建议用18650电池组就能动手了。通过这个项目你将清晰地掌握如何用程序产生PWM波来控制电机转速如何组合两个电机的转速来实现小车的前进、后退、转向和停止。虽然它没有“智能”跑起来可能因为电池电量、地面摩擦不同而画不出一条直线但这个过程能让你把书本上的寄存器配置、时钟树、中断等知识实实在在地转化为一个能跑起来的物理实体这种成就感是单纯调代码无法比拟的。我们接下来会从硬件选型、软件设计、代码实现到调试心得完整地走一遍。2. 硬件系统设计与核心模块解析2.1 主控与电机驱动选型考量主控芯片的选择几乎是确定的STM32F103C8T6也就是常说的“蓝色药丸”或“最小系统板”。它资源足够72MHz主频64KB Flash20KB RAM多个定时器社区资料极其丰富价格也便宜。最关键的是它几乎所有的通用定时器TIM1, TIM2, TIM3, TIM4都支持PWM输出这正是我们控制电机速度所需要的。电机驱动模块的选择则需要一些权衡。常见的有L298N和TB6612FNG。L298N经典、皮实、易获取。但它采用的是线性放大原理效率较低工作时发热比较明显需要加装散热片。其控制逻辑简单用两个GPIO口控制方向一个PWM口控制速度。TB6612FNGMOSFET H桥驱动效率高发热小体积也更小巧。控制方式与L298N类似但驱动能力和性能通常更好。对于这个开环小车项目两者都能完美胜任。我手头正好有TB6612所以本次以它为例。它的接线逻辑非常清晰VM接电机电源7-12VVCC接逻辑电源3.3V或5V接STM32的3.3VGND共地。每个电机需要三个控制信号PWMAPWM速度信号、AIN1和AIN2方向控制信号。STBY待机引脚直接接高电平VCC使其一直处于工作状态。2.2 电源系统设计与布线要点小车的“动力心脏”是电源系统设计不好轻则电机无力重则单片机复位甚至烧毁。核心原则是电机电源与单片机逻辑电源必须隔离。电机启动和堵转时会产生很大的瞬时电流和电压波动如果和单片机共用一套电源这些噪声会直接耦合进单片机的供电线路导致程序跑飞。因此标准的做法是使用两套独立的电源动力电源用于给电机驱动模块TB6612的VM端供电。推荐使用两节18650锂电池串联约7.4V-8.4V容量大放电电流足够。直接接到驱动模块的VM和GND。逻辑电源用于给STM32单片机、TB6612的VCC逻辑供电供电。可以从动力电源引出但必须经过一个降压稳压模块如AMS1117-3.3或效率更高的DC-DC降压模块稳定到3.3V。这个3.3V再给单片机和TB6612的VCC供电。重要提示务必确保单片机、TB6612的逻辑地GND和动力电源的地最终连接在一起即“共地”。这是信号正确参考的基础否则控制信号会失效。布线时尽量将电机动力线粗线与单片机信号线细线分开走避免平行长距离走线以减少电磁干扰。如果小车运行时单片机异常复位首先就要怀疑电源干扰问题。2.3 小车底盘与机械结构搭建对于开环控制机械结构的对称性和稳定性尤为重要因为它直接影响了两个轮子运动的一致性。建议使用现成的亚克力或金属小车底盘套件它们通常已经设计好了电机安装孔和电池盒位置。安装时需要注意电机安装紧固确保两个减速电机牢固地安装在底盘上不能有晃动。晃动会在运行时引入不可预测的摩擦变化。轮子安装同轴尽量让两个轮子保持在同一直线上。可以目测或者用直尺辅助。轮子不同心会导致小车天生就跑偏。万向轮或尾轮两轮差速小车需要至少一个辅助支撑点。常见的是在底盘前部或后部安装一个万向球轮。确保这个支撑轮高度合适使小车底盘基本水平两个驱动轮均匀着地。重心分配电池和主板尽量布置在底盘中心附近降低重心避免小车在启动、停止时因重心过高而晃动剧烈。3. 软件架构与PWM驱动原理3.1 定时器与PWM生成机制详解在STM32中PWM脉冲宽度调制波通常由通用定时器TIMx的PWM输出模式产生。其本质是通过一个自动重装载寄存器ARR和一个捕获/比较寄存器CCR来控制一个引脚的输出电平。你可以把定时器想象成一个向上计数的秒表。ARR决定了这个秒表的最大值比如计数到1000就归零重来。CCR是你设定的一个比较值比如300。定时器从0开始累加在计数值小于CCR时引脚输出高电平或低电平取决于极性配置当计数值大于等于CCR但小于等于ARR时引脚输出相反的电平。如此循环就产生了周期固定、但高低电平比例可调的方波也就是PWM波。关键参数计算PWM频率Fpwm 定时器时钟源频率 / ((ARR 1) * (PSC 1))定时器时钟源频率如果APB总线时钟是72MHz且定时器挂在APB1上默认2分频后为36MHz但STM32有一个倍频机制如果APB预分频系数≠1定时器时钟会×2所以TIM2-4的时钟通常是72MHz。ARR自动重装载值。PSC预分频器值。PWM占空比Duty Cycle CCR / (ARR 1)CCR值直接决定了高电平时间在一个周期内的占比。对于直流电机PWM频率不宜太低否则电机会抖动、啸叫也不宜太高否则驱动模块可能无法响应。通常选择在5kHz到20kHz之间。我们以10kHz为例定时器时钟72MHz设置PSC71ARR999则Fpwm 72,000,000 / ((711)*(9991)) 72,000,000 / 72,000 1000Hz。哦这里算错了是1kHz。要达到10kHz可以设PSC71ARR99则Fpwm 72,000,000 / (72*100) 10,000Hz。3.2 GPIO配置与电机控制逻辑抽象控制一个电机需要3个GPIO引脚。以控制右轮电机的TIM3_CH1PA6作为PWMPA4和PA5作为方向控制为例PWM引脚必须配置为复用推挽输出AF_PP并映射到对应的定时器通道上。方向控制引脚配置为通用推挽输出GPIO_PP即可。对于TB6612其控制真值表如下AIN1AIN2PWMA电机状态00X停止刹车10PWM正转01PWM反转11X停止刹车在软件中我们应该将这三个引脚的操作封装成一个“电机”结构体或对象并为其编写清晰的接口函数如Motor_Init(),Motor_SetSpeed(int16_t speed)。其中speed参数可以设计为-100到100负数代表反转正数代表正转绝对值大小代表占空比。这样上层控制逻辑会非常清晰。3.3 开环控制下的运动学模型两轮差速小车的运动学模型是理解其如何运动的基础。假设两个轮子间距为L轮距轮子半径为R。前进/后退当左轮转速V_left等于右轮转速V_right时小车沿直线前进同正或后退同负。原地旋转当V_left -V_right时小车将围绕两轮中心点原地旋转。左正右负则顺时针转反之逆时针转。转向更一般的情况是小车会围绕一个瞬时旋转中心ICR做圆弧运动。转弯半径ρ与两轮速度差有关。ρ (L/2) * (V_left V_right) / (V_right - V_left)。在开环控制中我们无法精确测量V_left和V_right的实际值因为没有编码器所以我们控制的是期望的PWM占空比。我们预设“当左右轮PWM占空比都设置为60%时小车应该直行”。但实际上由于电机个体差异、电池电压波动、轮子摩擦不同即使给相同的PWM两个轮子的实际转速也可能有细微差别导致小车跑偏。这就是开环控制的局限性也是我们后续引入闭环如编码器PID需要解决的问题。但在项目初期通过手动微调左右轮的基础PWM值一个略高于另一个可以在特定场地大致实现直行这是一个很实用的调试技巧。4. 代码实现与分步调试4.1 工程初始化与定时器PWM配置我们使用STM32CubeMX进行初始化配置这样效率最高。关键步骤如下选择你的STM32型号。配置时钟树RCC将HSE设置为外部晶振并使能PLL将系统时钟调到72MHz。配置两个定时器用于PWM生成。例如用TIM3的Channel1和Channel2控制右轮TIM4的Channel1和Channel2控制左轮。将对应的GPIO如PA6, PA7, PB6, PB7自动配置为复用功能。在定时器参数设置中Prescaler (PSC): 设置为71。Counter Mode: Up向上计数。Counter Period (ARR): 设置为99。这样PWM频率为10kHz。Pulse (CCR): 初始值设为0。这是占空比初始值。CH Polarity: 设置为High。表示默认有效电平为高。生成代码IDE选择Keil MDK或STM32CubeIDE。生成的代码会自动完成GPIO和定时器的初始化。我们需要在main.c中手动开启PWM输出通道HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); // 右轮PWM HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_2); HAL_TIM_PWM_Start(htim4, TIM_CHANNEL_1); // 左轮PWM HAL_TIM_PWM_Start(htim4, TIM_CHANNEL_2);4.2 电机驱动层封装与API设计接下来我们创建motor.c和motor.h文件封装电机控制逻辑。// motor.h typedef struct { TIM_HandleTypeDef *htim; // 定时器句柄 uint32_t channel; // PWM通道 GPIO_TypeDef *GPIOx_AIN1; // AIN1端口 uint16_t GPIO_Pin_AIN1; // AIN1引脚 GPIO_TypeDef *GPIOx_AIN2; // AIN2端口 uint16_t GPIO_Pin_AIN2; // AIN2引脚 int16_t current_speed; // 当前速度设定值 (-100 ~ 100) } Motor_t; void Motor_Init(Motor_t *motor, TIM_HandleTypeDef *htim, uint32_t channel, GPIO_TypeDef* GPIOx_AIN1, uint16_t GPIO_Pin_AIN1, GPIO_TypeDef* GPIOx_AIN2, uint16_t GPIO_Pin_AIN2); void Motor_SetSpeed(Motor_t *motor, int16_t speed);在motor.c中实现// motor.c void Motor_SetSpeed(Motor_t *motor, int16_t speed) { // 限幅 if(speed 100) speed 100; if(speed -100) speed -100; motor-current_speed speed; // 设置方向 if(speed 0) { // 正转 HAL_GPIO_WritePin(motor-GPIOx_AIN1, motor-GPIO_Pin_AIN1, GPIO_PIN_SET); HAL_GPIO_WritePin(motor-GPIOx_AIN2, motor-GPIO_Pin_AIN2, GPIO_PIN_RESET); } else if(speed 0) { // 反转 HAL_GPIO_WritePin(motor-GPIOx_AIN1, motor-GPIO_Pin_AIN1, GPIO_PIN_RESET); HAL_GPIO_WritePin(motor-GPIOx_AIN2, motor-GPIO_Pin_AIN2, GPIO_PIN_SET); } else { // 停止刹车 HAL_GPIO_WritePin(motor-GPIOx_AIN1, motor-GPIO_Pin_AIN1, GPIO_PIN_SET); HAL_GPIO_WritePin(motor-GPIOx_AIN2, motor-GPIO_Pin_AIN2, GPIO_PIN_SET); // 也可以同时置低取决于驱动模块逻辑 } // 设置PWM占空比取绝对值 uint16_t pulse (uint16_t)((__HAL_TIM_GET_AUTORELOAD(motor-htim) 1) * abs(speed) / 100); __HAL_TIM_SET_COMPARE(motor-htim, motor-channel, pulse); }这样我们就有了一个可以方便控制速度和方向的电机对象。4.3 上层运动控制函数实现基于封装好的电机驱动我们可以实现小车的基本运动函数放在car.c中// car.h extern Motor_t motor_left, motor_right; // 在car.c中定义 void Car_Init(void); void Car_Stop(void); void Car_Run(int16_t speed); // 速度范围 -100~100 void Car_Turn(int16_t speed, float ratio); // ratio为转向系数-1~1负左转正右转 void Car_Spin(int16_t angular_speed); // 原地旋转正为顺时针// car.c Motor_t motor_left, motor_right; void Car_Init(void) { // 初始化左电机假设使用TIM4_CH1 (PB6)为PWM, PB0为AIN1, PB1为AIN2 Motor_Init(motor_left, htim4, TIM_CHANNEL_1, GPIOB, GPIO_PIN_0, GPIOB, GPIO_PIN_1); // 初始化右电机假设使用TIM3_CH1 (PA6)为PWM, PA4为AIN1, PA5为AIN2 Motor_Init(motor_right, htim3, TIM_CHANNEL_1, GPIOA, GPIO_PIN_4, GPIOA, GPIO_PIN_5); } void Car_Run(int16_t speed) { // 简单直行左右轮同速 Motor_SetSpeed(motor_left, speed); Motor_SetSpeed(motor_right, speed); } void Car_Turn(int16_t speed, float ratio) { // ratio在 -1 到 1 之间 // ratio 0 直行 ratio 0 右转右轮减速 ratio 0 左转左轮减速 int16_t left_speed speed; int16_t right_speed speed; if(ratio 0) { // 右转 right_speed speed * (1.0f - ratio); } else if(ratio 0) { // 左转 left_speed speed * (1.0f ratio); // ratio为负所以是 1 - |ratio| } Motor_SetSpeed(motor_left, left_speed); Motor_SetSpeed(motor_right, right_speed); }在main函数中我们就可以通过调用Car_Run(60)让小车以60%的占空比前进了。4.4 调试技巧与参数微调实录代码写完下载后别急着让小车跑。分步调试是成功的关键静态测试不装轮子下载程序后用调试器或添加printf在Motor_SetSpeed函数里设置不同的速度值如0 50 -50。用手轻轻捏住电机轴感受其力度和转向是否与预期一致。用逻辑分析仪或示波器测量PWM引脚观察波形频率和占空比是否正确。这是检查硬件连接和软件配置最安全的一步。悬空测试抬起小车将小车抬起让轮子空转。调用Car_Run(30)观察两个轮子是否都朝前转转速是否大致相同。听电机声音如果发出尖锐的啸叫可能是PWM频率太低可以尝试调高ARR以降低频率或检查电源功率是否足够。地面直线测试将小车放在空旷平整的地面上如地板执行Car_Run(40)2秒钟后停止。观察小车轨迹。如果明显偏向一侧说明两个电机性能有差异。这时不要修改代码而是尝试微调物理连接检查轮子是否安装牢固、轮胎气压如果是充气胎是否一致。如果物理调整无效再考虑在软件中加入一个“校准偏移量”。例如在Car_Run函数中给其中一个轮子的速度加上一个小的补偿值Motor_SetSpeed(motor_left, speed CALIB_OFFSET)。通过实验找到一个能让小车大致直行的CALIB_OFFSET值可能是3或-5。记住这个值只在当前电池电量和地面条件下有效。转向与旋转测试测试Car_Turn和Car_Spin函数。观察转向是否平滑原地旋转是否围绕中心点。如果不理想调整转向算法中的ratio与速度的映射关系。5. 开环控制的局限性分析与进阶思考5.1 为何开环小车无法走直线通过上面的实验你已经切身感受到了开环控制的核心问题抗干扰能力差无纠错能力。具体到小车上干扰源包括电机特性差异即使是同一批次的电机其内部的磁钢、绕组、碳刷磨损程度都有微小差别导致在相同电压下的空载转速和扭矩特性曲线KV值不同。电池电压衰减随着电池放电输出电压下降电机转速也会随之下降。但两个电机对电压下降的敏感度可能不同导致速度差发生变化。负载与摩擦不均小车重心不完全居中、轮子与地面接触的摩擦力不同、地面本身不平整都会导致两个轮子实际受到的阻力不同。驱动电路细微偏差电机驱动芯片的两个H桥通道其内阻和开关特性也可能存在微小差异。在开环系统中控制器STM32对这些干扰一无所知。它只是忠实地输出预设的PWM信号而无法根据“小车是否真的在直行”这个结果来调整输出。因此开环小车跑偏是必然的不跑偏是偶然的在特定条件下刚好平衡。5.2 从开环到闭环传感器引入的必要性要让小车具备“纠偏”能力就必须引入反馈传感器构成闭环控制系统。最直接的传感器是旋转编码器它可以安装在电机轴上实时测量每个轮子的实际转速和转动距离。有了编码器反馈系统的工作流程就变成了设定目标我们希望小车以0.5米/秒的速度直线前进。控制器计算根据目标速度计算出左右轮的目标转速RPM。输出控制量控制器STM32输出PWM信号给电机驱动。测量反馈编码器实时测量电机的实际转速。计算误差比较目标转速和实际转速得到误差。调节输出根据误差通过PID等控制算法动态调整PWM的占空比使实际转速逼近目标转速。这样即使有外部干扰如上坡、摩擦增大导致左轮实际转速下降编码器会立即检测到控制器就会增加左轮的PWM占空比来补偿从而维持两个轮子的转速一致保证直线行驶。这就是闭环控制的魅力。5.3 开环项目的实用价值与扩展方向尽管有局限性开环小车项目绝非没有价值。它是理解控制系统最直观的起点。通过它你牢牢掌握了单片机控制外设的完整流程从时钟配置、GPIO初始化、定时器PWM生成到软件逻辑封装。硬件系统集成能力电源管理、信号连接、抗干扰布线。调试与问题排查的基本方法分步测试、现象观察、逻辑推理。在此基础上你可以轻松地将项目扩展升级增加编码器实现速度闭环购买带AB相编码器的直流减速电机利用STM32定时器的编码器接口模式读取脉冲实现单个电机的速度PID控制。增加陀螺仪实现姿态闭环集成MPU6050等IMU传感器通过PID控制让小车保持平衡平衡车或者实现更精确的转向角度控制。增加遥控功能接入蓝牙模块如HC-05或2.4G射频模块如NRF24L01用手机或遥控器控制小车。实现简单巡线增加几个红外对管或灰度传感器实现基本的黑白线巡线功能这仍然是开环控制对传感器开关量的反应但逻辑更有趣。5.4 常见问题与故障排查速查表在调试开环小车时你大概率会遇到下表所列的问题。根据现象可以按顺序排查现象可能原因排查步骤电机完全不转无声音1. 电源未接通或电压不足。2. 电机驱动模块待机引脚STBY未使能。3. 控制信号线未连接或接错。1. 用万用表测量电机驱动VM端和逻辑VCC端电压。2. 检查STBY引脚是否接高电平。3. 检查AIN1/AIN2/PWM引脚是否与单片机正确连接程序是否初始化了对应GPIO。电机抖动、啸叫、转速不稳1. PWM频率过低通常在几百Hz以下。2. 电源功率不足带载后电压被拉低。3. 电机损坏或驱动芯片某一相故障。1. 用示波器检查PWM波形频率调整定时器ARR和PSC值将频率升至5kHz以上。2. 检查电池电量尝试更换容量更大或放电能力更强的电池。3. 交换左右电机接线如果问题跟随电机走则是电机问题如果问题留在原通道则是驱动板或单片机引脚问题。单片机频繁复位或程序跑飞1. 电机电源干扰串入单片机电源。2. 电源线或电机动力线过长过细导致压降和噪声。3. 未共地。1.最可能的原因。确保电机电源与单片机逻辑电源通过稳压模块隔离并在电源入口处并联一个大电容如1000uF储能和滤除低频干扰再并联一个0.1uF陶瓷电容滤除高频噪声。2. 加粗缩短动力线。3. 确保驱动板GND、单片机GND、电池GND全部连接在一起。小车跑偏严重1. 左右轮电机性能差异。2. 机械结构不对称轮子安装不正。3. 地面不平或摩擦力差异大。1. 交换左右电机的驱动线注意方向信号也要交换如果跑偏方向反了说明是电机差异需软件补偿。2. 检查底盘、轮子安装确保对称。3. 在光滑平整的地面如瓷砖上测试。控制方向相反AIN1/AIN2控制逻辑接反。交换AIN1和AIN2的接线或者在软件中调换控制这两个引脚电平的逻辑。上电后电机一直全速转1. PWM引脚在初始化前可能为不定态驱动模块误动作。2. 程序未正确初始化方向引脚使其处于浮空状态。1. 在电机初始化函数中先设置方向引脚为刹车或停止状态再初始化PWM。2. 检查GPIO初始化代码确保方向引脚已正确配置为上拉或下拉输出模式避免浮空。这个开环小车项目就像学习骑自行车时用的辅助轮。它让你安全地、专注地练习踩踏板和掌握平衡对应单片机的基础控制而不必一开始就担心会摔倒对应复杂的闭环算法。当你熟练掌握了这一切拆掉辅助轮拥抱编码器和PID去实现更精准、更智能的控制就是水到渠成的事情了。动手做一遍你会对“控制”二字有完全不同的理解。
返回列表