基于STM32与51双MCU的AGV小车寻迹避障系统详解
发布时间:2026/9/6 22:24:48 作者:尧图编辑部 阅读量:1,286

简介面向嵌入式开发工程师与智能车爱好者这是一份基于STM32与51单片机的AGV智能小车寻迹避障系统设计资料包系统讲解从硬件搭建到软件实现的完整流程。资源以STM32C8T6为核心控制板配套L298N电机驱动、直流电机、TCRT5000红外寻迹模块、HC-SR04超声波测距模块以及蜂鸣器LED声光报警单元硬件上采用双层亚克力底盘布局压缩包共1个PDF文档大小4.29MB内容紧凑、目录结构分明。目前已有186人浏览学习。软件部分详解PID路径跟踪、超声波避障算法、蓝牙无线控制与状态显示逻辑并给出硬件连接示意图、可运行源码和51单片机版本的设计源码说明同时结合AGV小车在智能物流、工业自动化、无人驾驶等场景中的实际应用介绍红外检测黑色轨迹、超声波避障触发、PID调速等关键原理对调试中的常见问题与排错思路也有涉及适合用于课程设计、毕业设计、电子竞赛及工程预研能为读者提供从硬件接线到代码移植、参数调试的清晰对照。 做AGV智能小车尤其是寻迹避障这个方向算是嵌入式入门到进阶之间最经典的一个综合项目了。我当年也是从51单片机点亮LED开始一路折腾到STM32最后才把这套系统跑通。这次直接把从硬件选型、原理分析到源码实现、调参避坑的完整过程整理出来工程目录和核心代码我都会拆开讲想拿来做课程设计、毕业设计或者自己练手的读者跟着这条线走能省不少弯路。先交代一下这套系统是什么整车用一块STM32F103C8T6做主控负责避障决策和串口通信一块STC89C52单片机负责快速巡检循迹传感器直接控制电机驱动。两块MCU之间通过UART通信协同工作实现小车沿黑色引导线行驶遇到障碍物自动停车绕行后继续寻迹。这个方案特别适合刚入门的群体——51写循迹逻辑足够简单STM32能跑更复杂的避障状态机两块芯片分工协作学习曲线比一开始就上RTOS要平缓得多。1. 项目整体设计与双MCU分工思路1.1 为什么选STM32 51双主控很多读者第一个问题肯定是一颗STM32都能干完的活为什么要塞两块芯片这个设计看起来多此一举但其实是我把两种方案都做了一遍之后才定下来的。先说51单片机的短板STC89C52的IO翻转速度和处理能力都有限跑一个简单的循迹PWM控制逻辑没问题但一旦要同时处理超声波触发、多个回波定时器捕获再加串口收发主循环的时序很容易混乱。而STM32F103C8T6是72MHz主频的Cortex-M3内核定时器、USART、ADC外设非常丰富处理这些任务绰绰有余。但反过来说循迹控制本身是一个高频实时任务需要对传感器状态快速响应如果让一个主控同时干循迹和避障避障的超声波测距过程尤其是我最初用阻塞方式写会打断循迹巡检的节奏导致小车在过弯时反应迟钝。所以双MCU架构的核心价值在这几个地方任务分区明确51跑高频实时循迹控制环STM32跑低频避障决策环两者互不阻塞。学习和调试成本低51部分逻辑极其直观适合快速验证传感器和电机驱动STM32部分则专注在状态机、串口协议和定时器捕获上知识边界清晰。容错性好即使STM32因为避障临时占用大量时间51依然能维持小车基本运动状态不会直接失控。答辩/汇报有亮点双MCU协同工作比单芯片方案看起来更有系统设计感也顺便覆盖了更多嵌入式知识点。1.2 系统工作流程与信号走向整个系统的工作流程我从上电开始理一遍这样后面看代码会更有全局感。上电后51单片机先完成IO初始化和PWM定时器配置然后进入主循环不断读取循迹传感器模块输出的数字电平。默认情况下STM32同样完成初始化并通过串口向51发送“前进”控制指令。运行过程中STM32每隔大约60ms触发一次HC-SR04超声波测距当检测到正前方障碍物距离小于设定阈值我一般设为20cm时程序会进入避障状态机通过串口向51发送“左转”或“右转”的指令字。51收到指令后根据转向指令调整左右轮PWM占空比直到STM32测到前方距离恢复安全值再发指令恢复寻迹。这里面有一个关键设计循迹传感器本身实时监测黑线偏差51在每个控制周期都会根据偏差计算差速转向而STM32的避障指令是作为一个“高优先级覆盖”事件插入到控制环节当中。也就是说51的默认行为是沿黑线走但收到避障指令时会临时切换运动模式。这种“默认覆盖”的架构比把所有判断逻辑都堆在一个主循环里要干净得多信号流向也清晰。2. 硬件选型、传感器原理与电路要点2.1 核心硬件选型表与理由选型这部分我直接给出一版稳定跑通的配置都是市面上比较容易买到、价格合适、资料多的型号。部件选型选型理由主控决策端STM32F103C8T6Cortex-M3内核72MHz主频USART/TIM资源充足LQFP48封装焊接友好主控循迹端STC89C52RC经典51内核IO直接驱动逻辑简单支持串口ISP下载成本低电机驱动TB6612FNG压降小、体积小、支持PWM调速L298N更皮实但功耗和发热较大循迹传感器TCRT5000红外对管 ×4单路模块带电压比较器输出数字高低电平灵敏度可调避障传感器HC-SR04超声波 ×1测距3cm到400cm时序简单抗光线干扰能力强电池供电两节18650串联7.4V电机驱动直接取电逻辑电源通过降压模块单独供给我在初期版本用过L298N但发现它的压降太大两节18650供电时电机端电压掉得厉害而且模块体积大在小车底盘上不好固定。换成TB6612之后同样占空比下整车动力明显提升板载稳压也稳定了很多。如果手头正好只有L298N也不是不能用关键是逻辑电源和电机电源要分开。2.2 循迹模块原理、布局与阈值调节TCRT5000是我在市面小车上见得最多的一种循迹传感器核心原理是红外反射式检测。它由一枚红外发射管和一枚红外接收管组成发射管持续发出红外光光线照射到地面后反射到接收管。白色地面反射率高接收管接收的光强较大输出低电平黑色引导线吸收红外光接收管收到的光弱输出高电平。所以循迹这个动作本质上就是不断读取这组高低电平的分布判断黑线相对车身的位置。传感器布局上我建议至少用4路最好5路。4路的话中间两路对准黑线外侧两路负责检测大角度偏差5路时中间一路可以作为“完全居中”的精确标志。我之前贪快用的两路方案小车只要偏一点就直接丢线后来换成4路才真正稳定。安装高度控制在1.5到2.5cm之间太低容易受到地面起伏影响太高红外反射信号衰减严重黑色和白色的输出电平差会变小。每个TCRT5000模块上都有一个旋转电位器用来调节比较器阈值。调的时候有个笨但有效的方法把模块分别放在黑色引导线和白色地面上用万用表测输出引脚旋转电位器直到黑线时输出高电平、白底时输出低电平并保留足够余量。注意不要在阳光直射或强光环境下调节环境红外光干扰会让阈值判断不稳定实测在室内正常灯光下调节即可。2.3 超声波测距原理与定时器捕获避障部分我用的HC-SR04这是入门测距最常用的一款模块原理其实非常简单向Trig引脚发送至少10us的高电平模块内部自动发出8个40kHz的超声波脉冲同时Echo引脚变为高电平当模块收到回波后Echo引脚恢复低电平。这个高电平持续时间就是超声波往返的总时间。距离计算公式很简单距离(cm) 高电平时间(us) / 58也可以写成距离 时间 × 0.017cm/us。因为声速约340m/s除以2就是单程距离。代码上我最初用了最简单的阻塞等待方式HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_SET); delay_us(10); HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET); while (HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN) GPIO_PIN_RESET); while (HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN) GPIO_PIN_SET) { cnt; delay_us(1); } distance cnt / 58;实测下来这种写法在遮挡物比较近、回波正常时没问题但一旦传感器正对斜面或者被测物体吸音效果太强Echo引脚一直不跳变整个while循环就会卡死导致避障状态机直接“假死”。所以后来我改用了输入捕获的方式Trig送完脉冲后用定时器捕获Echo高电平时间同时设置一个超时保护。工程里已经用HAL库的输入捕获实现代码会放在stm32f1xx_it.c中断回调里不占用主循环稳定性好很多。3. 软件分层、源码结构与核心代码解析3.1 51端寻迹算法与差速控制51端最核心的任务是实时确定黑线偏差并转换成左右轮的差速控制。我这里的传感器接在P2口低5位每路传感器输出0表示检测到黑线输出1表示未检测到黑线。最基本的逻辑可以用查表法实现u8 sensor P2 0x1F; switch (sensor) { case 0x04: // 00100 中间检测到黑线直行 LeftMotorPWM SPEED_STRAIGHT; RightMotorPWM SPEED_STRAIGHT; break; case 0x02: // 00010 黑线偏右 LeftMotorPWM SPEED_TURN_HIGH; RightMotorPWM SPEED_TURN_LOW; break; case 0x08: // 01000 黑线偏左 LeftMotorPWM SPEED_TURN_LOW; RightMotorPWM SPEED_TURN_HIGH; break; default: break; }这只是一版最基础的实现实际跑起来会发现问题如果直接把占空比从一个值跳到另一个值小车在过弯时会一顿一顿非常生硬。我后来把“直接赋值”改成了“按步长递进”让占空比平滑过渡走线就顺滑了很多。更进一步的优化是用P值比例控制。假设4路循迹传感器从左到右编号为1到4可以定义当前偏差error为各个传感器位置的加权和。中间两路权重低外侧权重高例如int error -3*sensor1 - 1*sensor2 1*sensor3 3*sensor4;然后把error乘一个比例系数Kp直接作为左右轮的差速基准值int diff Kp * error; LeftMotorPWM BASE_SPEED diff; RightMotorPWM BASE_SPEED - diff;这个方法比查表灵活得多Kp调好以后小车过弯是平滑过渡而不是阶梯跳变。Kp太大会导致左右PWM频繁摆动Kp太小则转向不足冲出线。我在四路布局下误差范围大概是-6到6Kp取2到3之间比较合适具体可以根据车重和轮胎摩擦力微调。3.2 STM32端避障状态机STM32端的核心是避障决策逻辑。不能用简单的“探测到障碍就转弯转完就前进”这种无脑逻辑否则会出现震荡小车左转后如果障碍物还在侧前方马上又认为前方不安全再次转弯形成死循环。我用的方案是有限状态机把运行过程划分为几个明确状态typedef enum { STATE_FORWARD, STATE_TURN_LEFT, STATE_TURN_RIGHT, STATE_STOP } AvoidState; AvoidState state STATE_FORWARD; while (1) { distance MeasureDistance(); switch (state) { case STATE_FORWARD: if (distance 20) { state STATE_STOP; SendCommand(CMD_STOP); HAL_Delay(200); state STATE_TURN_LEFT; SendCommand(CMD_TURN_LEFT); } break; case STATE_TURN_LEFT: // 持续检测前方距离恢复后再前进 if (distance 35) { SendCommand(CMD_FORWARD); state STATE_FORWARD; } break; default: break; } HAL_Delay(60); }注意几个细节转弯前先发一个停车指令稍等片刻再发转向指令让车有足够时间响应不然高速状态下直接转向容易甩尾转弯状态下前方距离恢复的阈值要比触发阈值高我触发是20cm恢复是35cm这个差值相当于迟滞可以避免小车在障碍物边缘反复横跳。避障方向我是固定左转如果想让小车更智能可以在车头左右各装一个超声波模块哪个方向探测距离远就优先往哪个方向转。这个改进在工程里是预留了接口的拓展起来不需要改状态机主体。3.3 双MCU串口通信协议设计两块单片机之间通信最省事的方式就是UART波特率我用的9600——这个速率对51来说依然很稳定STM32也足够。真正需要设计的是帧协议不能直接裸发一个字节就完事否则干扰或者掉字节时系统状态容易错乱。我定义了一个最简协议帧帧头1帧头2指令字数据累加和结束符0xAA0x55CMDDATASUM0x0D 0x0A累加和SUM为指令字、数据、帧头所有字节之和的低8位。接收端收到0xAA 0x55后进入解析状态根据长度接收数据计算校验只有校验通过才更新控制标志位。如果校验失败整帧丢弃不会影响当前运行状态。指令字定义如下指令含义数据0x01前进无0x02停车无0x03左转转向时间或角度0x04右转转向时间或角度51端的串口接收用中断方式每收到一个字节进入中断按接收状态机填充缓冲区这里给一个51端收数据的核心思路void UART_ISR() interrupt 4 { u8 dat; if (RI) { RI 0; dat SBUF; // 按状态推进找帧头 - 收长度/指令/数据 - 校验 - 置标志 } }实际调试中我发现乱码有九成原因是两块板子没有共地。双机之间没有连GND的话电平参考点不一致串口电平判断就会错乱。所以接线时除了TX接RX、RX接TX一定要在STM32的GND和51的GND之间额外接一根线。3.4 工程文件组织与Keil环境配置注意整个工程的源码我分成了两套独立的Keil工程原因是STM32和51的编译器完全不同。Keil5如果想同时编译这两种芯片需要分别装MDK-ARM和C51两个编译器支持包每次打开工程时工程文件类型不同IDE会自动切换。如果出现打开工程后编译按钮灰色、提示选择编译器的现象多半就是对应支持包没装好。推荐的目录结构是这样Project/ ├── Docs/ │ ├── 接线表.md │ └── 原理图.pdf ├── STM32_F103/ │ ├── Core/Inc/ │ ├── Core/Src/ │ │ ├── main.c │ │ ├── hcsr04.c │ │ ├── avoid.c │ │ └── uart_protocol.c │ └── MDK-ARM/ └── STC_89C52/ ├── Source/ │ ├── main.c │ ├── motor.c │ ├── line_sensor.c │ └── uart_receive.c └── Output/这样组织的好处很明显51的代码和STM32的代码完全隔离各自调试互不影响。我在实际开发中就是先把51的循迹单独调通再把STM32的避障单独用串口助手模拟测距输出最后才联调通信问题定位会快很多。STM32端建议直接用STM32CubeMX生成初始化代码因为定时器输入捕获、串口DMA这些配置手写寄存器太费时间而且容易踩坑。生成之后再把业务逻辑加到生成的框架里。4. 调试实录常见问题与排查速查4.1 上电前的检查清单每次连上新硬件我不会急着上电下载代码而是先按固定顺序检查一遍。这些习惯能帮你省下大量排查时间。电压确认先用万用表测电源模块输出逻辑部分要稳定在5V电机供电按电池电压实测记录。不要直接接在降压模块输出端测要测板子电源引脚处的实际电压。BOOT0引脚STM32的BOOT0要接GND确保从Flash启动。否则明明下载成功一上电程序就是不跑。电机接线确认左右电机的A、B相顺序一致否则一个轮子前行一个轮子后退小车直接原地打转。传感器供电TCRT5000模块供电要接在干净的5V上不要从单片机IO直接供电。下载通路ST-LINK的SWDIO、SWCLK、GND三根线先接好再上电避免热插拔导致连接异常。4.2 高频问题与排查思路这个项目的每个环节我都踩过不少坑挑几个出现频率最高的问题列成速查表遇到类似现象可以直接对照。现象可能原因排查方法ST-LINK下载报错“No STM32 Target Found”BOOT0接错、SWD接线松动、供电不足检查BOOT0是否接GND重新插拔SWD线先用USB供电下载电机不转或只有一个转驱动板使能脚EN没接高、PWM引脚错位把EN接VCC对照原理图逐脚测量PWM输出小车走不直总是跑偏左右轮PWM基准不一致、减速比有差异调低速度逐轮测试软件里给慢轮加补偿循迹在小转弯处冲出线速度过快、传感器数量不足、Kp太小降速试验把PWM响应改为步进式增大Kp超声波测距偶尔返回0阻塞等待超时、测量周期太短改用定时器输入捕获加超时保护测量间隔大于50ms双机串口接收乱码波特率不匹配、没共地、电源噪声统一波特率两块板子接GND电机和逻辑分开供电电池电压一低就复位电机启动瞬间拉低系统电压电源输入端并接470uF以上电解电容电机和逻辑地分开布线4.3 调试顺序与参数微调心得我强烈建议按“先单机、再联机、先低速、再高速”的顺序来调不要一上来就全功能放开否则哪里出错都分不清。第一步是软硬件自检单独给51下载一个电机正反转测试程序确认左右电机方向正确给STM32下载一个超声波测距程序用串口助手观察距离读数是否和实际值吻合。这两个测试通过了才说明底层硬件是可信的。第二步是调循迹。让小车在直线黑线上低速走观察传感器输出的跳变是否稳定调整电位器让输出余量足够。然后调Kp值从较小的值开始逐步增加找到一个过弯不抖、直线不摆的平衡点。这个阶段我建议把速度控制在最大占空比的30%到40%超过这个范围过弯时惯性太大算法再好也救不回来。第三步才接入避障。STM32先单独工作通过串口发指令控制51动作观察避障行为是否正确。避障逻辑稳定后再把两个系统合起来进行全流程测试。最后分享一个经验电机电源和逻辑电源一定要分开供电。一开始图省事共用一个电源结果电机一转STM32的ADC波形就严重跳动超声波偶尔还会误触发。后来我在电机电源引脚并联大电容同时让逻辑电源走独立的降压模块问题立刻消失。嵌入式系统里电源的干净程度决定了系统稳定性的下限。这套系统跑通之后可玩性还很高。比如加上蓝牙模块就可以通过手机App手动遥控观察串口上报的状态数据换上OpenMV摄像头就能把寻迹从“反射式红外”升级成“视觉巡线”或者把无障碍路径规划做成固定轨迹记忆回放。这次整理的完整工程资料和源码已经在对应开源仓库中打包好关于代码实现方式、传感器选型、工程环境配置所有疑问都可以在评论区直接提我尽量及时回复。本文还有配套的精品资源点击获取