基于STM32的智能小车避障与循迹实战(附江科大教程代码解析)
1. 从零开始搭建STM32智能小车第一次接触STM32智能小车项目时我完全被各种传感器和电机驱动搞晕了。后来跟着江科大的教程一步步做下来才发现其实并没有想象中那么难。这个项目非常适合刚学完STM32基础知识的同学练手能让你把GPIO、定时器、PWM这些抽象的概念变成看得见摸得着的实际应用。智能小车的核心功能很简单能自己避开障碍物能沿着黑线走还能用手机蓝牙控制。听起来是不是很酷我刚开始做的时候最头疼的就是TB6612电机驱动的接线经常把AIN1和AIN2接反导致电机转不起来。后来发现一个小技巧先用杜邦线接好电路测试电机转向正确后再焊接能省去很多麻烦。提示购买小车底盘时建议选择带编码器的电机版本虽然贵一点但后续扩展性更强可以轻松升级为PID速度控制。2. 硬件清单与选购指南2.1 必须准备的硬件做这个项目需要准备以下硬件我都列出来了具体型号和注意事项STM32F103C8T6最小系统板俗称蓝莓派性价比超高TB6612电机驱动模块比L298N发热小支持2A电流HC-SR04超声波模块新版支持3.3V老款必须5V供电TCRT5000红外循迹模块建议买3-5个方便扩展SG90舵机9g微型舵机用来旋转超声波探头HC-05蓝牙模块比HC-04更稳定兼容性好第一次买配件时我在电机驱动上栽了跟头。贪便宜买了个不知名的驱动芯片结果PWM频率一高就发热严重。后来换了正品TB6612连续工作半小时都只是微温。这里分享一个鉴别技巧正品TB6612的散热片是哑光黑色仿品往往是亮银色。2.2 硬件连接技巧接线最容易出错的是电机驱动部分。记住这个口诀PWMA接PWMAIN接GPIO。具体来说将STM32的PA0和PA1接TB6612的PWMA和PWMBAIN1/AIN2接PA3/PA4BIN1/BIN2接PA5/PA6VM接7-12V电源VCC接5V给逻辑电路供电超声波模块的Trig和Echo最好接在同一个GPIO端口的不同引脚上比如PB12和PB13。这样写代码时会方便很多可以直接用位带操作读取引脚状态。3. 让小车动起来的PWM秘籍3.1 PWM初始化详解很多教程只给代码不说原理这里我把江科大教程里的PWM初始化拆开来讲void PWM_Init(void) { // 1. 开启时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 2. 配置GPIO为复用推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 3. 定时器基础配置 TIM_TimeBaseInitStructure.TIM_Period 100-1; // ARR值决定PWM频率 TIM_TimeBaseInitStructure.TIM_Prescaler 72-1; // 72MHz/721MHz TIM_TimeBaseInit(TIM2, TIM_TimeBaseInitStructure); // 4. PWM模式配置 TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_Pulse 50; // 初始占空比50% TIM_OC1Init(TIM2, TIM_OCInitStructure); TIM_OC2Init(TIM2, TIM_OCInitStructure); }关键点在于TIM_Period和TIM_Prescaler的配置。这里72MHz主频先72分频得到1MHz然后ARR设为100-1最终PWM频率就是1MHz/10010kHz。这个频率对电机驱动来说正合适太高会导致MOS管开关损耗大太低会有可闻噪音。3.2 电机控制实战电机驱动最常遇到的问题是转向不对。我的经验是先用下面这个测试函数验证void Motor_Test(void) { // 右侧电机正转 GPIO_SetBits(GPIOA, GPIO_Pin_4); GPIO_ResetBits(GPIOA, GPIO_Pin_3); PWM_SetCompare1(50); // 左侧电机反转 GPIO_ResetBits(GPIOA, GPIO_Pin_6); GPIO_SetBits(GPIOA, GPIO_Pin_5); PWM_SetCompare2(50); Delay_ms(2000); Motor_Stop(); // 停止所有电机 }如果发现电机转向与预期相反不要急着改代码先把电机接线对调保持代码逻辑统一。我早期项目就因为不同电机转向定义不统一导致后面循迹算法特别难写。4. 超声波避障的坑与解决方案4.1 测距不稳定的真相HC-SR04模块最常见的问题是上电瞬间显示0距离或者数值跳变严重。经过反复测试我发现问题主要出在三个方面电源干扰超声波模块最好单独用LDO供电不要和电机共用电源回声检测Echo引脚必须设置为下拉输入避免悬空状态误触发多次采样建议连续测5次取中值不要用平均值改进后的距离获取函数应该是这样的float Get_Stable_Distance(void) { float buf[5]; for(int i0; i5; i){ buf[i] HCSR04_GetDistance(); Delay_ms(30); // 每次测量间隔至少60ms } // 冒泡排序取中值 for(int i0; i4; i){ for(int ji1; j5; j){ if(buf[i] buf[j]){ float temp buf[i]; buf[i] buf[j]; buf[j] temp; } } } return buf[2]; }4.2 舵机扫描策略单纯固定向前检测很容易撞上侧面障碍物。我给舵机加了30度左右摆动扫描void Obstacle_Scan(void) { for(int angle-30; angle30; angle15){ Servo_SetAngle(angle); float dist Get_Stable_Distance(); if(dist 20.0){ // 20cm内障碍物 Avoid_Obstacle(angle, dist); // 根据角度和距离避障 break; } } }这里有个细节每次改变舵机角度后要延时200ms再测距因为SG90舵机从-90°转到90°需要约300ms。实测发现如果不等舵机停稳就测距数据会严重失真。5. 循迹模块的进阶用法5.1 三路循迹算法优化江科大教程用的是两路循迹实际比赛常用三路或五路。下面是我的三路循迹状态机实现void Track_Line(void) { switch(Tracker_GetState()){ case 0b101: // 左右检测到中间没检测到 Car_Run(50); // 直行 break; case 0b001: // 只有左侧检测到 Car_LeftRun(30); // 左转 break; case 0b100: // 只有右侧检测到 Car_RightRun(30); // 右转 break; case 0b011: // 左中检测到 Car_LeftRun(40); // 急左转 break; case 0b110: // 右中检测到 Car_RightRun(40); // 急右转 break; default: Car_Stop(); // 其他情况停止 } }5.2 抗干扰设计实际环境中常会遇到反光干扰我的解决方案是在传感器探头周围加一圈黑色热缩管在代码中增加消抖判断uint8_t is_real_black 0; for(int i0; i5; i){ if(GPIO_ReadInputDataBit(GPIOA, pin) RESET){ is_real_black; } Delay_ms(1); } return (is_real_black 3); // 5次检测中3次为黑才确认6. 蓝牙控制的隐藏功能6.1 协议设计技巧直接发送单个字符控制小车太简陋了。我设计了一套简单协议F100表示前进速度100B050表示后退速度50L030表示左转速度30R000表示停止解析代码如下void Parse_Bluetooth_Cmd(uint8_t* buf) { char cmd buf[0]; int speed (buf[1]-0)*100 (buf[2]-0)*10 (buf[3]-0); switch(cmd){ case F: Motor_SetSpeed(speed, speed); break; case B: Motor_SetSpeed(-speed, -speed); break; case L: Motor_SetSpeed(-speed/2, speed); break; case R: Motor_SetSpeed(speed, -speed/2); break; } }6.2 手机APP优化江科大的蓝牙助手功能比较基础。我推荐用MIT App Inventor自己开发控制APP可以加入摇杆控制和参数设置页面。有个小技巧在APP里添加一个Turbo按钮按下时发送F255让小车全速前进效果非常带感7. 常见问题解决方案7.1 电机启动困难现象PWM占空比小于30%时电机不转 解决方法在电机两端并联100uF电容代码中设置最小占空比void Motor_SetSpeed(int speed) { if(abs(speed) 30) speed 30 * (speed0?1:-1); // 其余代码不变 }7.2 电池续航短实测发现用9V电池只能工作10分钟用18650锂电池组2串可工作1小时最佳方案是3.7V锂电池升压模块兼顾体积和续航7.3 程序跑飞问题加入看门狗是必须的IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); IWDG_SetPrescaler(IWDG_Prescaler_32); // 32kHz/321kHz IWDG_SetReload(1000); // 1秒超时 IWDG_Enable();在main循环中定期喂狗while(1){ IWDG_ReloadCounter(); // 其他代码 }做完这个项目最大的体会是嵌入式开发永远会有意想不到的问题有时候硬件问题看起来像软件bug反之亦然。记得有一次小车总是莫名其妙右转查了两小时代码没发现问题最后发现是右侧电机电源线接触不良。所以遇到问题时一定要用二分法排查先确定是硬件还是软件问题再逐步缩小范围。