STM32智能小车入门实战:从硬件选型到PWM电机控制

发布时间:2026/7/30 9:09:31
STM32智能小车入门实战:从硬件选型到PWM电机控制 1. 项目概述从零到一打造你的第一辆STM32智能小车如果你手头正好有一块STM32开发板几个电机和轮子想动手做点有意思的东西那么一辆能听你指挥前进、后退、左转、右转、停止的智能小车绝对是个完美的入门项目。这不仅仅是让轮子转起来那么简单它融合了单片机GPIO控制、PWM调速、电机驱动、电源管理以及最基础的软件逻辑设计是嵌入式开发从理论走向实践的绝佳跳板。我当年就是从这样一辆“傻跑”的小车开始一步步深入到更复杂的传感器融合和自动控制领域的。这个项目的核心目标非常明确通过STM32单片机实现对两个直流电机的独立控制从而组合出小车的五种基本运动状态。听起来简单但里面藏着不少新手容易踩的坑比如电机选型、驱动电路烧毁、PWM频率设置不当导致电机啸叫、电源干扰导致单片机复位等等。接下来我会结合我多次搭建小车的经验把这套流程掰开揉碎了讲清楚让你不仅能做出能跑的小车更能理解每一个环节背后的“为什么”。2. 核心硬件选型与电路设计解析一辆小车跑得稳不稳硬件是地基。盲目堆料不可取根据需求和预算做精准选型才是正道。2.1 主控芯片STM32家族如何选择STM32系列型号繁多对于基础的小车控制我们不需要顶级的性能。F1系列的C8T6、F4系列的F411CEU6都是性价比极高的选择。它们都拥有足够多的GPIO口和定时器来产生PWM信号。注意我个人更倾向于推荐STM32F103C8T6俗称“蓝桥杯”或“最小系统板”。原因有三一是资料极其丰富任何问题几乎都能找到答案二是价格便宜烧了不心疼三是性能对于本项目绰绰有余其拥有的高级定时器TIM1/8和通用定时器TIM2/3/4完全满足电机PWM和编码器测速未来升级用的需求。2.2 动力心脏电机与驱动模块选型这是决定小车动力和操控性的关键。电机类型最常用的是TT减速直流电机。它集成了直流电机和减速齿轮箱输出扭矩大转速适中通常空载转速在200RPM左右价格低廉。注意查看工作电压常用3-6V和空载电流。驱动模块单片机GPIO口的驱动电流通常20mA远不足以直接驱动电机必须使用电机驱动模块。最经典的是L298N和TB6612FNG。L298N老牌芯片皮实耐造驱动电流大单桥2A但发热严重效率较低需要外接散热片。其控制逻辑简单适合新手理解H桥原理。TB6612FNG我更推荐这个。它是MOSFET桥驱动效率高、发热小驱动能力单桥1.2A对于小型TT电机完全足够。关键它支持待机模式功耗更低逻辑控制也更简洁。这里给出一个对比表格方便你决策特性L298N驱动模块TB6612FNG驱动模块选择建议驱动方式双H桥集成电路MOSFET双桥驱动-驱动电流单桥2A峰值3A单桥1.2A峰值3.2A小型TT电机两者皆可效率较低压降大发热大高压降小发热小TB6612效率优势明显控制逻辑每路需2个IO控制方向1个PWM调速每路需2个IO控制方向1个PWM调速类似待机功能无有STBY引脚TB6612可降低静态功耗价格略低略高相差不大实操心得对于新手的第一辆车如果预算允许直接上TB6612。它能让你省去很多散热和电源波动的烦恼把精力更多集中在编程和逻辑上。我早期用L298N时就曾因散热不良导致芯片过热保护小车跑着跑着就停了排查了半天。2.3 电源系统设计稳定大于一切小车跑起来一窜一窜的或者单片机莫名复位八成是电源问题。双电源供电强烈建议电机驱动电源和单片机逻辑电源分开。电机启停时会产生很大的电流波动和电压毛刺如果共用电源会严重干扰单片机工作。通常方案是一块7.4V或12V的锂电池组如18650两串作为电机驱动电源直接接入驱动模块的VM电机电源输入端。驱动模块上的5V输出或独立的5V降压模块如LM2596作为单片机和其它逻辑器件如后续的传感器的电源。TB6612的VCC引脚逻辑供电也接这个5V。滤波电容在电机驱动模块的电源输入端口附近并联一个100uF以上的电解电容和一个0.1uF的瓷片电容。这是吸收低频和高频干扰的经典做法成本极低但效果显著。电压监测为后续升级考虑可以用STM32的ADC监测电池电压避免电池过放。3. 硬件连接与原理图详解我们以STM32F103C8T6搭配TB6612FNG驱动模块和两个TT减速电机为例进行连接。3.1 控制逻辑剖析H桥与PWM要想理解接线必须先明白驱动芯片如何控制一个电机的正反转和调速。TB6612控制一路电机需要三个信号AIN1/AIN2或BIN1/BIN2这两个是方向控制引脚决定电流方向。PWMA/PWMB这是PWM调速引脚输入不同占空比的方波来控制电机的平均电压从而实现调速。其真值表如下以A路为例STBYAIN1AIN2PWMA电机A状态100X停止刹车110PWM正转速度由PWM占空比决定101PWM反转速度由PWM占空比决定111X停止刹车0XXX待机所有输出关断核心原理AIN1和AIN2的电平组合控制了芯片内部H桥四个MOSFET的导通状态从而改变了电机两端的电压极性。PWM信号则快速开关对应的MOSFET通过改变一个周期内导通时间的比例占空比来改变电机两端的平均电压实现调速。3.2 具体接线步骤请务必在断电状态下操作电源部分将锂电池正负极接至TB6612的VM和GND。将TB6612的VCC逻辑电压和GND接至STM32的5V和GND。确保共地将TB6612的STBY引脚接高电平可直接接VCC或STM32的某个置高的IO口。信号控制部分我们使用STM32的TIM3的CH1和CH2产生两路PWM用普通GPIO控制方向。电机A右侧电机AIN1- STM32PA6(配置为普通GPIO输出)AIN2- STM32PA7(配置为普通GPIO输出)PWMA- STM32PA0(配置为TIM2_CH1 或 TIM5_CH1 这里我们假设用PA0作为TIM2_CH1)电机B左侧电机BIN1- STM32PB0(配置为普通GPIO输出)BIN2- STM32PB1(配置为普通GPIO输出)PWMB- STM32PA1(配置为TIM2_CH2)电机输出部分将电机A的两根线接至TB6612的AO1和AO2。将电机B的两根线接至TB6612的BO1和BO2。如果电机转向与预期相反只需对调这两根线即可。重要提示PWM引脚必须连接到STM32上具有定时器输出比较OC功能的引脚上。你需要查阅STM32F103C8T6的数据手册或引脚分配图来确定。上面举例的PA0/PA1是TIM2的通道1和2是一种常见选择。4. 软件编程STM32 HAL库实现五态控制我们使用STM32CubeMX进行初始化配置然后用HAL库编写应用逻辑。这是目前最主流、最高效的开发方式。4.1 使用STM32CubeMX进行工程初始化创建项目选择MCU型号STM32F103C8Tx。配置时钟在RCC中将HSE外部高速时钟设置为Crystal/Ceramic Resonator。然后在Clock Configuration标签页将系统时钟源选为HSE并适当倍频至72MHzF103的常用最高频率。配置GPIO将PA6,PA7,PB0,PB1设置为GPIO_Output并给它们起个有意义的用户标签如MOTOR_A_IN1,MOTOR_A_IN2等。配置定时器产生PWM找到TIM2或其他你选择的定时器。将Channel1和Channel2设置为PWM Generation CH1/CH2。在下方参数设置中Prescaler预分频器设置为72-1。因为系统时钟是72MHz分频后得到1MHz的计数器时钟。计算72MHz / (711) 1MHz。Counter Period自动重装载值ARR设置为1000-1。这样PWM的频率 1MHz / 1000 1kHz。这是一个对直流电机调速非常合适的频率既能保证响应平滑又能避免MOSFET开关损耗过大和产生人耳可闻的啸叫声。Pulse初始脉冲宽度先设为0。生成代码设置好工程路径和工具链MDK-ARM V5点击GENERATE CODE。4.2 编写电机驱动与控制函数在生成的工程中我们主要在main.c或自己新建的motor.c文件中编写代码。首先定义一些宏和变量让代码更清晰// motor.h 或 main.c 顶部 #define MOTOR_A_PWM htim2, TIM_CHANNEL_1 #define MOTOR_B_PWM htim2, TIM_CHANNEL_2 #define MOTOR_A_IN1_GPIO_Port GPIOA #define MOTOR_A_IN1_Pin GPIO_PIN_6 #define MOTOR_A_IN2_GPIO_Port GPIOA #define MOTOR_A_IN2_Pin GPIO_PIN_7 #define MOTOR_B_IN1_GPIO_Port GPIOB #define MOTOR_B_IN1_Pin GPIO_PIN_0 #define MOTOR_B_IN2_GPIO_Port GPIOB #define MOTOR_B_IN2_Pin GPIO_PIN_1 // 电机速度范围对应PWM占空比 0~1000 #define MOTOR_MAX_SPEED 800 // 为避免全速可能导致的电流过大设个上限 #define MOTOR_MIN_SPEED 200 // 电机有启动电压阈值低于这个值可能转不起来然后编写一个基础的电机控制函数// motor.c /** * brief 设置单个电机的速度和方向 * param pwm_handle: 定时器和通道句柄如 MOTOR_A_PWM * param in1_pin: 方向引脚1 * param in2_pin: 方向引脚2 * param speed: 速度值正数正转负数反转绝对值大小代表速度 * retval None */ void Motor_SetSpeed(TIM_HandleTypeDef *htim, uint32_t Channel, GPIO_TypeDef* IN1_Port, uint16_t IN1_Pin, GPIO_TypeDef* IN2_Port, uint16_t IN2_Pin, int16_t speed) { // 限制速度范围 if(speed MOTOR_MAX_SPEED) speed MOTOR_MAX_SPEED; if(speed -MOTOR_MAX_SPEED) speed -MOTOR_MAX_SPEED; // 根据速度正负设置方向 if(speed 0) { // 正转 HAL_GPIO_WritePin(IN1_Port, IN1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(IN2_Port, IN2_Pin, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(htim, Channel, speed); // 设置PWM占空比 } else if(speed 0) { // 反转 HAL_GPIO_WritePin(IN1_Port, IN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(IN2_Port, IN2_Pin, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(htim, Channel, -speed); // 取绝对值 } else { // 停止刹车模式两个方向引脚同电平 HAL_GPIO_WritePin(IN1_Port, IN1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(IN2_Port, IN2_Pin, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(htim, Channel, 0); // 另一种停止方式是让两个方向引脚都为低同时PWM为0滑行停止。刹车停止更迅速。 } }4.3 实现小车五态运动函数基于上面的单电机控制函数我们可以封装出小车整体的动作// car.c /** * brief 小车前进 * param speed: 前进速度 * retval None */ void Car_Forward(int16_t speed) { // 两个电机都正转 Motor_SetSpeed(MOTOR_A_PWM, MOTOR_A_IN1_GPIO_Port, MOTOR_A_IN1_Pin, MOTOR_A_IN2_GPIO_Port, MOTOR_A_IN2_Pin, speed); Motor_SetSpeed(MOTOR_B_PWM, MOTOR_B_IN1_GPIO_Port, MOTOR_B_IN1_Pin, MOTOR_B_IN2_GPIO_Port, MOTOR_B_IN2_Pin, speed); } /** * brief 小车后退 * param speed: 后退速度 * retval None */ void Car_Backward(int16_t speed) { // 两个电机都反转 Motor_SetSpeed(MOTOR_A_PWM, MOTOR_A_IN1_GPIO_Port, MOTOR_A_IN1_Pin, MOTOR_A_IN2_GPIO_Port, MOTOR_A_IN2_Pin, -speed); Motor_SetSpeed(MOTOR_B_PWM, MOTOR_B_IN1_GPIO_Port, MOTOR_B_IN1_Pin, MOTOR_B_IN2_GPIO_Port, MOTOR_B_IN2_Pin, -speed); } /** * brief 小车原地左转 * param speed: 转向速度电机速度 * retval None */ void Car_SpinLeft(int16_t speed) { // 左轮反转右轮正转实现原地旋转 Motor_SetSpeed(MOTOR_A_PWM, MOTOR_A_IN1_GPIO_Port, MOTOR_A_IN1_Pin, MOTOR_A_IN2_GPIO_Port, MOTOR_A_IN2_Pin, speed); // 右轮正转 Motor_SetSpeed(MOTOR_B_PWM, MOTOR_B_IN1_GPIO_Port, MOTOR_B_IN1_Pin, MOTOR_B_IN2_GPIO_Port, MOTOR_B_IN2_Pin, -speed); // 左轮反转 } /** * brief 小车原地右转 * param speed: 转向速度电机速度 * retval None */ void Car_SpinRight(int16_t speed) { // 左轮正转右轮反转实现原地旋转 Motor_SetSpeed(MOTOR_A_PWM, MOTOR_A_IN1_GPIO_Port, MOTOR_A_IN1_Pin, MOTOR_A_IN2_GPIO_Port, MOTOR_A_IN2_Pin, -speed); // 右轮反转 Motor_SetSpeed(MOTOR_B_PWM, MOTOR_B_IN1_GPIO_Port, MOTOR_B_IN1_Pin, MOTOR_B_IN2_GPIO_Port, MOTOR_B_IN2_Pin, speed); // 左轮正转 } /** * brief 小车停止 * retval None */ void Car_Stop(void) { Motor_SetSpeed(MOTOR_A_PWM, MOTOR_A_IN1_GPIO_Port, MOTOR_A_IN1_Pin, MOTOR_A_IN2_GPIO_Port, MOTOR_A_IN2_Pin, 0); Motor_SetSpeed(MOTOR_B_PWM, MOTOR_B_IN1_GPIO_Port, MOTOR_B_IN1_Pin, MOTOR_B_IN2_GPIO_Port, MOTOR_B_IN2_Pin, 0); }4.4 主函数逻辑与测试在main.c的main函数中初始化外设后我们可以编写一个简单的测试序列int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); // PWM定时器初始化 // ... 其他初始化 // 启动PWM信号输出 HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_2); while (1) { Car_Forward(600); // 以60%的速度前进 HAL_Delay(2000); // 持续2秒 Car_SpinRight(400); // 右转 HAL_Delay(1000); Car_Backward(500); // 后退 HAL_Delay(2000); Car_SpinLeft(400); // 左转 HAL_Delay(1000); Car_Stop(); // 停止 HAL_Delay(2000); } }5. 调试、问题排查与进阶优化代码烧录后小车可能不会按预期运动。别急这是调试的开始。5.1 分步调试法静态测试不上电用万用表通断档检查所有接线是否牢固有无虚焊、短路。重点检查电源正负极是否接反。上电无动作测试先不接电机给系统上电。用万用表电压档测量驱动模块输出端AO1/AO2电压。当设置电机正转时AO1应为高电平接近VM电压AO2应为低电平。反转时相反。停止时两者电压关系应符合真值表。这可以验证STM32的GPIO控制逻辑是否正确。用示波器或逻辑分析仪甚至一个简单的LED加电阻串联查看PWM引脚波形确认频率1kHz和占空比是否随代码改变而变化。单电机测试接上一个电机分别测试正转、反转、停止功能是否正常。注意观察电机转向。双电机联调两个电机都接上运行测试程序观察小车运动。5.2 常见问题与解决方案速查表现象可能原因排查步骤与解决方案电机完全不转1. 电源未接通或电压不足。2. 驱动模块使能端STBY未置高。3. STM32未正常启动或程序未运行。4. 控制IO口配置错误如配置成了输入。1. 测量电池电压、驱动模块VM和VCC电压。2. 检查STBY引脚是否接高电平5V或3.3V。3. 检查单片机复位电路LED闪烁测试程序是否运行。4. 用CubeMX重新检查GPIO配置模式。电机只朝一个方向转方向控制引脚AIN1/AIN2电平设置错误或其中一个IO损坏。用万用表或逻辑分析仪测量AIN1/AIN2引脚电平在代码中正反转切换时电平是否按真值表变化。电机抖动、转速不稳或啸叫1. PWM频率不合适太低会抖动太高可能超出驱动芯片响应能力且效率低。2. 电源功率不足或干扰大。3. 电机机械负载卡顿。1.调整PWM频率。尝试500Hz, 1kHz, 5kHz, 16kHz。对于TB66121k-5kHz是比较好的范围。避开人耳敏感频段~1-3kHz可减少啸叫。2. 加强电源滤波加大电容检查电池电量是否充足。3. 检查车轮、齿轮是否安装顺畅。小车跑不直两个电机的实际转速有差异即使PWM占空比相同。这是由于电机个体差异、轮子摩擦不同导致的。1.软件校准在代码中为两个电机设置不同的速度补偿值。例如如果小车右偏就稍微调低右轮的速度系数。2.闭环控制进阶为电机加装编码器使用PID算法进行精确速度闭环控制。单片机频繁复位电机启停时的大电流拉低了逻辑电源电压。1.严格进行电源分离电机电源和单片机电源从电池处就分开通过不同的稳压模块供电。2. 在单片机的电源入口处增加一个大电容如100uF缓冲。3. 检查地线连接是否良好、够粗。5.3 进阶优化方向当基础的五态控制实现后你可以考虑以下升级让你的小车变得更“智能”加入遥控最直接的是通过蓝牙模块如HC-05/06连接手机APP遥控或者用红外遥控器、2.4G射频模块如NRF24L01实现无线控制。这需要你学习串口通信或SPI通信。加入传感器超声波模块HC-SR04实现避障功能。当检测到前方障碍物时自动停止或转向。红外巡线模块TCRT5000实现巡线行驶。这是很多智能小车比赛的基础项目。陀螺仪加速度计MPU6050获取小车姿态可以实现更稳定的直线行驶或特定角度的精确转弯。速度闭环控制给TT电机加装霍尔编码器很多TT电机有自带编码器的版本通过STM32的定时器编码器接口读取转速然后使用PID算法调节PWM占空比让小车在不同负载、不同电量下都能保持设定速度彻底解决“跑不直”的问题。结构化你的代码将电机驱动、小车控制、传感器处理等模块分别写成独立的.c/.h文件使用头文件进行接口声明。这会让你的代码更易维护和扩展为后续添加复杂功能打下基础。从让轮子转起来到能按指令灵活运动再到能感知环境、自主决策这辆STM32智能小车就像一个微缩的机器人平台每一步的深入都能带你学习到嵌入式开发的一个核心知识点。硬件连接是筋骨软件逻辑是灵魂而调试排错则是让你真正理解系统如何运作的必修课。希望这份详细的指南能帮你少走弯路顺利启动你的嵌入式实战之旅。当你看到自己亲手打造的小车在地面上划出第一道轨迹时那种成就感就是学习技术最好的动力。