1. 项目概述从零开始打造一台智能四驱小车最近在整理工作室翻出来一堆之前玩STM32剩下的开发板、电机和传感器看着它们吃灰总觉得有点可惜。正好一直想系统地做点东西把嵌入式开发里那些零散的知识点串起来于是决定动手造一台能跑、能避障、甚至能通过手机遥控的智能四驱小车。这个项目听起来像是个玩具但实际做下来你会发现它几乎涵盖了嵌入式开发的所有核心环节从硬件选型、电路设计、到固件驱动编写、多任务调度再到上层应用逻辑和通信协议。对于想深入理解单片机如何与真实物理世界交互的朋友来说这是一个绝佳的练手项目。我计划把这个过程写成系列文章今天是第一篇主要聚焦在硬件与软件的准备阶段。很多人觉得做硬件项目拿到板子就开始写代码是天经地义结果往往在焊接、供电、驱动兼容性这些“脏活累活”上栽跟头导致项目进度缓慢甚至失败。我的经验是前期准备做得越扎实后期开发就越顺畅。这篇文章我会把我搭建这台四驱小车底盘的完整物料清单、电路连接思路、开发环境搭建过程以及最基础的电机驱动测试代码毫无保留地分享出来。无论你是刚接触STM32的新手还是想找一个综合性项目练手的老鸟相信都能从中获得可以直接“抄作业”的实用信息。2. 硬件选型与物料清单把钱花在刀刃上硬件是项目的骨架选型决定了小车的性能上限和开发难度。我的原则是在满足功能需求的前提下优先选择社区支持好、资料丰富的成熟模块这样可以避免很多底层调试的坑。2.1 核心控制器为什么是STM32我选择了STM32F103C8T6核心板也就是大家常说的“蓝色药丸”。选择它理由很充分性价比极高ARM Cortex-M3内核72MHz主频64KB Flash20KB RAM性能对于控制小车绰绰有余价格却非常亲民。生态极其丰富无论是标准库、HAL库还是LL库资料和例程都多如牛毛。社区里你遇到的几乎所有问题几乎都能找到答案。引脚资源充足它提供了足够多的GPIO、定时器和通信接口USART、I2C、SPI方便后续扩展超声波、陀螺仪、蓝牙等模块。注意市面上有大量仿制板建议选择口碑好的卖家。我曾买过一块劣质核心板内部晶振误差巨大导致串口通信乱码排查了很久。2.2 动力与执行机构电机与驱动模块的选择这是小车的“心脏”和“肌肉”直接决定其动力表现。电机我选用的是N20减速电机搭配65mm橡胶轮。它的优点是体积小、扭矩大、带减速箱速度适中每分钟几百转非常适合这种小型底盘。电压常用6V或12V。电机驱动模块这是关键我强烈推荐使用TB6612FNG双路电机驱动模块而不是经典的L298N。原因如下表对比特性TB6612FNGL298N效率高发热小较低发热严重驱动方式MOSFET电压损耗小双H桥集成电路压降大控制逻辑简单AIN/BIN PWMA/PWMB稍复杂IN1/IN2/IN3/IN4 ENA/ENB待机电流极低μA级较高mA级体积小巧较大需散热片TB6612可以同时驱动两个电机我们只需要两个模块就能驱动四轮。其逻辑很简单用两个GPIO控制方向正转/反转/刹车/停止用一个定时器的PWM通道控制速度。2.3 电源系统稳定大于一切电源不稳定是硬件项目最常见的“玄学”问题根源。我的方案是动力电源采用一块7.4V 2S锂聚合物电池。其电压经过电机驱动模块后能较好地匹配N20电机的工作电压提供充足动力。控制电源STM32和传感器需要稳定的5V或3.3V。这里我使用了一个DC-DC降压模块如LM2596将电池的7.4V降至稳定的5V。然后STM32核心板上的AMS1117-3.3稳压芯片会将5V转为3.3V供核心板使用。电源开关与接口在电池输出端加入一个拨动开关并预留XT60或T插头方便电池插拔。务必在开关后、降压模块前加入一个大容量如470μF的电解电容用于滤除电机启停时产生的瞬间大电流冲击防止单片机复位。2.4 车体与辅助物料底盘亚克力或铝合金四驱小车底盘套件包含电机支架和轮子。连接件杜邦线公对公、公对母、母对母、螺丝包。调试工具ST-Link V2编程调试器、万用表、电烙铁和焊锡。未来扩展预留为超声波模块、MPU6050陀螺仪、蓝牙/WIFI模块预留出接口和安装位置。完整的物料清单BOM大致如下你可以根据预算微调STM32F103C8T6核心板 x1TB6612FNG电机驱动模块 x2N20减速电机带轮 x47.4V 2S锂电池带充电器 x1DC-DC降压模块LM2596 x1四驱小车底盘套件 x1杜邦线若干、开关、电容、扎带等3. 电路设计与连接理清电流的脉络硬件连接不是简单的插线必须理清电源路径和控制信号流。错误的连接轻则功能失常重则烧毁芯片。3.1 电源电路设计双路供电与隔离这是整个电路设计的核心我画了一个简化的电流路径图文字描述锂电池(7.4V) -- 电源总开关 -- [分支点A] 分支A动力支路-- TB6612的VM引脚两个模块并联 -- 驱动四个电机。 分支B控制支路-- DC-DC降压模块(7.4V to 5V) -- [分支点B] 分支B1-- STM32核心板的5V引脚为其上的3.3V稳压芯片供电。 分支B2-- 为未来的传感器如超声波HC-SR04提供5V电源。关键细节共地与隔离锂电池的负极GND、降压模块的GND、STM32的GND、TB6612的GND必须全部连接在一起形成一个统一的参考地。但电机的电流地大电流最好在物理走线上与单片机的地小电流一点连接减少干扰。滤波电容在分支点A开关后对地接一个470μF/16V的电解电容在分支点B降压模块输出5V对地接一个100μF的电解电容和一个0.1μF的瓷片电容分别滤除低频和高频噪声。TB6612供电其VCC引脚逻辑电源必须接STM32的同一电源通常是5V或3.3V看模块支持确保逻辑电平匹配。VM引脚接电池电压7.4V。3.2 控制信号连接GPIO与PWM分配以控制左前轮电机为例使用一个TB6612的A通道STM32 GPIO_Pin1(例如 PA0) --TB6612 AIN1(控制方向1)STM32 GPIO_Pin2(例如 PA1) --TB6612 AIN2(控制方向2)STM32 TIMx_CHy(例如 PA2 TIM2_CH3) --TB6612 PWMA(PWM速度控制)STM32的GPIO需要配置为推挽输出模式。PWM引脚需要配置为复用推挽输出并绑定到对应的定时器通道上。连接实操心得在焊接或插接前先用万用表通断档检查所有连线避免短路或虚接。给所有电源线电池到开关、开关到模块选用较粗的导线以减少压降和发热。信号线可以细一些但最好进行捆扎整理避免杂乱导致意外短路或接触不良。为每个TB6612模块的VM和GND之间也加一个0.1μF的瓷片电容紧贴模块引脚焊接能有效抑制驱动芯片自身产生的高频噪声。4. 软件开发环境搭建工欲善其事必先利其器软件环境是项目的“大脑”孵化器。我选择的是Keil MDK-ARMSTM32CubeMX的组合这是目前最主流、最高效的开发方式。4.1 工具链安装与配置安装Keil MDK-ARM从官网下载安装包注意需要注册并申请License有免费版限制。安装路径不要有中文。安装STM32CubeMX这是ST官方的图形化配置工具能极大简化引脚、时钟、外设的初始化工作。安装时它会自动在线下载或让你指定STM32F1系列的芯片支持包DFP。安装ST-Link驱动将ST-Link调试器连接到电脑根据系统自动安装或从ST官网下载驱动确保设备管理器中能正确识别。踩坑记录曾经因为Keil的芯片支持包没装对导致编译时找不到头文件。解决方法是在Keil的Pack Installer中搜索并安装Keil::STM32F1xx_DFP。4.2 使用CubeMX创建工程骨架这是最关键的一步能帮你生成正确、高效的初始化代码。新建项目打开CubeMX选择MCU型号STM32F103C8Tx。配置时钟树在RCC选项中将高速外部时钟HSE设置为Crystal/Ceramic Resonator。转到Clock Configuration标签页。将HSE输入频率设为8MHz根据你的核心板晶振。然后将PLL Source Mux选为HSE将PLLMUL设置为9倍频最终使HCLK达到72MHz。这是F103的典型最高主频。系统时钟SYSCLK也自动变为72MHz。配置GPIO和定时器根据之前的连接图找到对应的引脚。例如PA0、PA1设置为GPIO_Output控制方向。PA2设置为TIM2_CH3PWM输出。在左侧找到TIM2启用Channel 3为PWM Generation CH3。在下方参数设置中设置Prescaler预分频器和Counter Period自动重装载值ARR以生成特定频率的PWM。例如若时钟为72MHz预分频器设为71ARR设为999则PWM频率 72MHz / (711) / (9991) 1kHz。这是一个常用的电机PWM频率。生成代码在Project Manager标签页设置项目名称、路径选择Toolchain / IDE为MDK-ARM V5。在Code Generator中勾选Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral这样代码结构更清晰。点击GENERATE CODE生成Keil工程。4.3 基础驱动代码编写让电机转起来CubeMX生成的代码已经完成了硬件底层的初始化。我们只需要在main.c的用户代码区添加应用逻辑。首先在main()函数的while(1)循环前启动PWM输出HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_3); // 启动TIM2的通道3输出PWM然后我们可以编写一个简单的电机控制函数。假设我们控制左前轮电机的AIN1PA0 AIN2PA1 PWMAPA2TIM2_CH3// 定义电机状态 typedef enum { MOTOR_STOP, MOTOR_FORWARD, MOTOR_BACKWARD, MOTOR_BRAKE } Motor_State; // 控制左前轮电机 void LeftFront_Motor_Ctrl(Motor_State state, uint16_t speed) { // speed是PWM占空比范围0-999 (对应ARR999) __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_3, speed); switch(state) { case MOTOR_FORWARD: // 正转 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); break; case MOTOR_BACKWARD: // 反转 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); break; case MOTOR_BRAKE: // 刹车短接 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); break; case MOTOR_STOP: // 停止浮空 default: HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); break; } }最后在while(1)中调用测试while (1) { LeftFront_Motor_Ctrl(MOTOR_FORWARD, 500); // 以50%占空比正转 HAL_Delay(2000); LeftFront_Motor_Ctrl(MOTOR_BRAKE, 0); // 刹车 HAL_Delay(500); LeftFront_Motor_Ctrl(MOTOR_BACKWARD, 300); // 以30%占空比反转 HAL_Delay(2000); LeftFront_Motor_Ctrl(MOTOR_STOP, 0); // 停止 HAL_Delay(2000); }5. 系统联调与基础测试验证每一个环节硬件连接好代码写好就到了最激动人心也最容易出错的环节——上电联调。务必遵循**“分步上电逐步测试”**的原则。5.1 分步上电与静态测试断开电机首次上电前先将电机从TB6612的输出端断开或者只连接一个电机进行测试防止意外飞车损坏设备或伤人。只供控制电先不接锂电池仅通过USB线或稳压电源给STM32核心板供电5V。用万用表测量核心板的3.3V引脚电压是否正常。检查逻辑信号下载测试程序后用万用表电压档或逻辑分析仪甚至可以用一个LED加限流电阻测量控制电机方向的GPIO引脚如PA0 PA1。当调用正转、反转、刹车函数时这些引脚的电压组合应符合TB6612的真值表例如正转时为高、低电平。检查PWM信号测量PWM输出引脚如PA2应能看到一个频率为1kHz根据你的配置、占空比可变的方波信号。5.2 驱动模块与电机动态测试连接驱动模块逻辑电将TB6612的VCC和GND接到STM32系统的5V和GND上。STMIO控制线连接好。此时驱动模块的逻辑部分已上电。单独测试驱动模块仍然先不接电机用万用表测量TB6612的电机输出端A01 A02之间的电压。当你控制电机正转时此处应能测量到一个与PWMA占空比对应的平均电压。这证明了驱动模块的开关功能正常。接电机供动力电最后连接好电机接上锂电池7.4V。此时完整的电源回路建立。小心地将小车悬空轮子离地然后上电测试。电机应该能按照程序指令正转、反转、变速和停止。5.3 常见问题与排查实录即使准备再充分第一次调试也难免遇到问题。这里记录几个我踩过的坑和解决方法现象可能原因排查步骤与解决方法电机完全不转驱动芯片发烫电源接反或短路电机堵转。立即断电1. 检查电池极性。2. 用万用表测VM与GND间电阻排除短路。3. 手动转动电机轴检查是否机械卡死。电机抖动或转速不稳PWM频率不合适电源功率不足滤波不良。1. 调整PWM频率对于有刷电机1kHz-10kHz是常用范围避开音频段如500Hz以下。2. 检查电池电压带载时电压是否骤降。3. 检查电源路径上的滤波电容是否焊好。控制信号正常但电机只朝一个方向转TB6612方向控制引脚逻辑错误电机线接反。1. 对照TB6612数据手册核对AIN1/AIN2电平与转向关系。2. 交换电机两根线的接线看转向是否改变。单片机程序跑飞或不断复位电机启停引起电源电压跌落地线干扰。1.最有效的措施在电池端并联一个大容量电解电容如1000μF。2. 检查地线连接是否牢固尝试单点接地。3. 在程序里加强对看门狗IWDG的使用。下载程序失败ST-Link连接不稳芯片复位电路问题Boot引脚配置不对。1. 检查ST-Link的SWDIO、SWCLK、GND、3.3V四根线是否连接牢固。2. 测量NRST引脚电压正常应为高电平。3. 确保BOOT0引脚通过10k电阻下拉到地通常电路板已设计。一个关键的实操技巧在调试初期可以在电机电源回路中串联一个功率电阻如5Ω/10W或汽车灯泡作为假负载/限流器。这样即使电路短路或异常也能有效限制电流保护昂贵的芯片和电池是硬件调试的“安全气囊”。6. 项目框架与代码结构规划在让单个电机成功转起来之后我们不能满足于把所有代码都堆在main.c里。为了后续增加遥控、避障、循迹等功能必须提前规划一个清晰、可扩展的软件框架。6.1 模块化设计思路我将代码按功能模块进行划分每个模块有独立的.c和.h文件。这样不仅便于管理也方便团队协作和代码复用。motor.c/h电机驱动层。封装所有与TB6612控制相关的函数向上提供如Motor_SetSpeed(Motor_ID id, int16_t speed)的接口speed为正负值代表方向。pwm.c/hPWM服务层。对HAL库的PWM操作进行二次封装管理所有定时器通道。encoder.c/h未来编码器反馈层。用于读取电机转速实现闭环控制。control.c/h未来控制算法层。实现PID控制等根据目标速度与编码器反馈计算PWM输出。communication.c/h未来通信层。处理串口、蓝牙等指令的接收与解析。main.c主循环。协调各模块完成高层任务调度如遥控指令处理、自动避障逻辑。6.2 编写健壮的电机驱动模块基于之前的测试代码我们将其完善成一个正式的模块。在motor.h中定义清晰的数据结构和接口// motor.h #ifndef __MOTOR_H #define __MOTOR_H #include “main.h” // 包含HAL库定义 typedef enum { MOTOR_FL 0, // 前左 MOTOR_FR, // 前右 MOTOR_RL, // 后左 MOTOR_RR, // 后右 MOTOR_NUM // 电机数量 } Motor_ID; typedef struct { GPIO_TypeDef* dir_port_a; uint16_t dir_pin_a; // 对应AIN1 GPIO_TypeDef* dir_port_b; uint16_t dir_pin_b; // 对应AIN2 TIM_HandleTypeDef* pwm_tim; uint32_t pwm_ch; // 对应PWMA/B } Motor_Config_t; void Motor_Init(void); void Motor_SetSpeed(Motor_ID id, int16_t speed); // speed范围-1000 ~ 1000 #endif在motor.c中初始化所有电机的配置并实现速度设置函数// motor.c #include “motor.h” // 私有全局变量存储四个电机的硬件配置 static Motor_Config_t motor_conf[MOTOR_NUM]; void Motor_Init(void) { // 初始化左前轮电机配置根据你的实际接线修改 motor_conf[MOTOR_FL].dir_port_a GPIOA; motor_conf[MOTOR_FL].dir_pin_a GPIO_PIN_0; motor_conf[MOTOR_FL].dir_port_b GPIOA; motor_conf[MOTOR_FL].dir_pin_b GPIO_PIN_1; motor_conf[MOTOR_FL].pwm_tim htim2; motor_conf[MOTOR_FL].pwm_ch TIM_CHANNEL_3; // 初始化其他三个电机配置... // motor_conf[MOTOR_FR] ... // motor_conf[MOTOR_RL] ... // motor_conf[MOTOR_RR] ... // 启动所有PWM通道 for(int i0; iMOTOR_NUM; i) { HAL_TIM_PWM_Start(motor_conf[i].pwm_tim, motor_conf[i].pwm_ch); } } void Motor_SetSpeed(Motor_ID id, int16_t speed) { Motor_Config_t* m motor_conf[id]; uint16_t abs_speed (speed 0) ? -speed : speed; // 限制速度值在PWM比较寄存器范围内假设ARR999 if(abs_speed 999) abs_speed 999; __HAL_TIM_SET_COMPARE(m-pwm_tim, m-pwm_ch, abs_speed); if(speed 0) { // 正转 HAL_GPIO_WritePin(m-dir_port_a, m-dir_pin_a, GPIO_PIN_SET); HAL_GPIO_WritePin(m-dir_port_b, m-dir_pin_b, GPIO_PIN_RESET); } else if(speed 0) { // 反转 HAL_GPIO_WritePin(m-dir_port_a, m-dir_pin_a, GPIO_PIN_RESET); HAL_GPIO_WritePin(m-dir_port_b, m-dir_pin_b, GPIO_PIN_SET); } else { // 停止浮空滑行 HAL_GPIO_WritePin(m-dir_port_a, m-dir_pin_a, GPIO_PIN_RESET); HAL_GPIO_WritePin(m-dir_port_b, m-dir_pin_b, GPIO_PIN_RESET); // 或者使用刹车模式根据需求选择 // HAL_GPIO_WritePin(m-dir_port_a, m-dir_pin_a, GPIO_PIN_SET); // HAL_GPIO_WritePin(m-dir_port_b, m-dir_pin_b, GPIO_PIN_SET); } }6.3 实现基础运动控制函数有了底层的电机控制我们就可以实现小车的基本运动学函数。在car_control.c中// car_control.c #include “motor.h” // 设置四个轮子的速度实现基本运动 void Car_SetMotion(int16_t left_speed, int16_t right_speed) { Motor_SetSpeed(MOTOR_FL, left_speed); Motor_SetSpeed(MOTOR_RL, left_speed); Motor_SetSpeed(MOTOR_FR, right_speed); Motor_SetSpeed(MOTOR_RR, right_speed); } void Car_Stop(void) { Car_SetMotion(0, 0); } void Car_Forward(uint16_t speed) { Car_SetMotion(speed, speed); } void Car_Backward(uint16_t speed) { Car_SetMotion(-speed, -speed); } void Car_TurnLeft(uint16_t speed) { // 差速左转左轮后退右轮前进 Car_SetMotion(-speed, speed); } void Car_TurnRight(uint16_t speed) { // 差速右转左轮前进右轮后退 Car_SetMotion(speed, -speed); }现在在main.c中代码变得非常清晰// main.c #include “motor.h” #include “car_control.h” int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); // ... 初始化其他外设 Motor_Init(); // 初始化电机驱动 while (1) { Car_Forward(600); // 以60%速度前进 HAL_Delay(2000); Car_TurnRight(400); // 原地右转 HAL_Delay(1000); Car_Backward(600); // 后退 HAL_Delay(2000); Car_Stop(); HAL_Delay(1000); } }通过这样的模块化设计我们为小车搭建了一个坚实且可扩展的软件基础。后续无论是要加入遥控指令解析还是复杂的自动控制算法都只需要在高层添加或修改逻辑而无需触碰底层的电机驱动细节。这种分层的思想是嵌入式项目走向复杂和成熟的必经之路。硬件和软件的准备到此告一段落你的小车已经拥有了一个可靠的身体和初步受控的大脑。在接下来的系列文章中我们将为它装上“眼睛”传感器和“耳朵”通信模块并赋予它更智能的“行为”。