资讯动态

ESP32智能小车三合一:循迹、避障与跟随功能整合实战

发布时间:2026/8/16 12:58:28 来源:尧图企业网站定制
最近在做一个智能小车项目需要它既能沿着黑线跑又能避开路上的障碍物还能跟着一个移动的物体走。网上找了一圈资料发现要么只讲循迹要么只讲避障想找一个把循迹、避障、跟随三个功能整合到一辆基于ESP32的小车上的完整教程还真不容易。自己摸索着做了一遍踩了不少坑也总结了一套比较可靠的方案。本文将手把手带你从零搭建一辆“三合一”智能小车。我们会使用性价比极高的ESP32作为主控配合常见的红外传感器和电机驱动模块实现循迹、红外避障和超声波跟随功能。文章会涵盖完整的硬件选型、接线图、Arduino代码编写、逻辑调试以及常见问题的解决方案。无论你是学生正在准备电赛或课程设计还是嵌入式爱好者想做个有趣的玩具都能从这篇教程中找到可以直接复用的代码和清晰的思路。1. 项目核心功能与硬件选型在开始动手之前我们先明确这辆智能小车要实现的三个核心功能以及每个功能对应的硬件原理。1.1 功能定义与实现思路循迹让小车沿着地面上画好的黑色轨迹线自动行驶。这是通过车底朝下的红外循迹传感器实现的。传感器发射红外光黑色轨迹线会吸收大部分红外光而白色地面则会反射。通过检测反射光的强度传感器就能判断下方是黑线还是白地从而输出不同的电平信号给ESP32ESP32再控制左右轮差速实现转向。避障让小车在行驶过程中自动探测前方的障碍物并绕开或停止。我们采用红外避障模块。它同样利用红外反射原理当模块前方一定距离内有物体时反射的红外光被接收模块输出信号。ESP32检测到这个信号后会触发避障策略如后退、转向。跟随让小车跟随一个移动的物体比如人前进。这里我们使用超声波模块来测量小车与前方物体之间的距离。通过持续测量距离ESP32可以动态调整车速距离太近就减速或后退距离太远就加速追赶保持在一个设定的“安全跟随距离”区间内。逻辑优先级在实际运行时这三个功能需要有一个明确的优先级。通常避障的优先级最高因为安全第一。任何时候检测到障碍物都应立即中断当前行为循迹或跟随执行避障。其次是循迹当没有障碍物且处于循迹模式时小车专心沿黑线走。最后是跟随模式在没有黑线且没有紧急障碍物时小车尝试跟随前方的物体。1.2 硬件清单与作用以下是完成本项目所需的核心硬件清单所有模块都非常常见且价格低廉。组件型号/规格数量主要作用主控制器ESP32开发板 (如ESP32 DevKitC V4)1项目的大脑负责读取传感器数据、执行控制逻辑、驱动电机。电机驱动L298N或TB6612FNG电机驱动模块1接收ESP32的弱电控制信号驱动直流电机的大电流。TB6612FNG效率更高、发热更小更推荐。直流减速电机TT马达带减速箱2提供动力通常需要配合车轮使用。循迹传感器TCRT5000红外反射传感器3-5个检测地面黑线。五路传感器控制更精准三路足以应对简单路线。避障传感器E18-D80NK 或类似红外避障开关1-2个检测前方障碍物。通常安装在车头。测距传感器HC-SR04超声波模块1测量与前方物体的距离用于跟随功能。电源18650锂电池两节及电池盒1套为整个系统供电。电机驱动模块和ESP32最好分开供电。车体智能小车底盘套件含轮子、万向轮1套承载所有电子元件。连接线杜邦线公对公、公对母若干连接各模块。为什么选择ESP32ESP32集成了Wi-Fi和蓝牙虽然本项目暂未使用无线功能但它为后续扩展如手机APP遥控、OTA升级留下了巨大空间。其双核处理器也能轻松应对多传感器数据读取和电机控制任务性能远超传统的Arduino UNO。2. 硬件连接与电路搭建正确的硬件连接是项目成功的第一步。请务必在断电状态下进行接线。2.1 电机与驱动模块连接我们以TB6612FNG驱动模块为例因为它控制更精细且有待机功能。电机连接将左、右两个TT电机的线分别接到驱动模块的A A-和B B-输出端。电源连接电机电源 (VM)接锂电池正极如7.4V。GND接锂电池负极。逻辑电源 (VCC)接ESP32的3.3V或5V引脚根据模块说明TB6612FNG逻辑电压可接3.3V。GND接ESP32的GND。注意务必将锂电池的GND、驱动模块的GND和ESP32的GND连接在一起即共地这是电路正常工作的基础。控制信号连接将驱动模块的控制引脚连接到ESP32的GPIO口。AIN1,AIN2- 控制电机A左轮正反转。接ESP32的任意两个IO口如GPIO13,GPIO12。PWMA- 电机A的PWM调速引脚。接ESP32的任意支持PWM的IO口如GPIO14。BIN1,BIN2,PWMB- 同理控制电机B右轮。接GPIO27,GPIO26,GPIO25。STBY(待机引脚) - 接高电平如3.3V模块才工作也可以接一个IO口进行软件控制。2.2 传感器与ESP32连接五路循迹传感器模块模块的VCC和GND分别接ESP32的5V或3.3V看模块规格和GND。传感器的五个输出引脚通常标为OUT1-OUT5分别接ESP32的五个数字输入引脚例如GPIO15,GPIO2,GPIO4,GPIO5,GPIO18。当检测到黑线时对应引脚输出低电平0白线为高电平1。红外避障模块 (E18-D80NK)棕色线(VCC)- ESP325V。蓝色线(GND)- ESP32GND。黑色线(OUT)- ESP32 数字输入引脚如GPIO19。有障碍物时输出低电平。超声波模块 (HC-SR04)VCC- ESP325V。GND- ESP32GND。Trig(触发) - ESP32 数字输出引脚如GPIO23。Echo(回声) - ESP32 数字输入引脚如GPIO22。接线核对提示接完线后强烈建议画一张简单的接线图或用表格记录每个ESP32引脚的功能方便后续编程和排查故障。3. 软件开发环境搭建我们将使用Arduino IDE来为ESP32编写程序这是最快速上手的方式。3.1 安装Arduino IDE与ESP32开发板支持从Arduino官网下载并安装最新版Arduino IDE。打开IDE点击文件-首选项在“附加开发板管理器网址”中输入https://espressif.github.io/arduino-esp32/package_esp32_index.json可以点击输入框右侧的图标添加多个URL如果有其他URL用逗号隔开即可。点击工具-开发板-开发板管理器在搜索框中输入“esp32”找到“ESP32 by Espressif Systems”点击安装。安装完成后在工具-开发板中选择你的ESP32型号如“ESP32 Dev Module”。选择正确的端口连接ESP32后会出现。3.2 核心代码结构与函数封装为了让代码清晰易维护我们将功能模块化。首先定义所有用到的引脚。// 引脚定义 // 电机驱动引脚 (以TB6612FNG为例) #define MOTOR_AIN1 13 #define MOTOR_AIN2 12 #define MOTOR_PWMA 14 #define MOTOR_BIN1 27 #define MOTOR_BIN2 26 #define MOTOR_PWMB 25 #define MOTOR_STBY 33 // 如果使用待机控制 // 五路循迹传感器引脚 (从左到右) #define TRACK_SENSOR_1 15 #define TRACK_SENSOR_2 2 #define TRACK_SENSOR_3 4 #define TRACK_SENSOR_4 5 #define TRACK_SENSOR_5 18 // 红外避障传感器引脚 #define OBSTACLE_SENSOR 19 // 超声波传感器引脚 #define ULTRASONIC_TRIG 23 #define ULTRASONIC_ECHO 22 // 工作模式定义 #define MODE_TRACKING 1 #define MODE_FOLLOWING 2 int currentMode MODE_TRACKING; // 默认循迹模式接下来封装电机控制函数。这是小车运动的基础。// 电机控制函数 void motorInit() { pinMode(MOTOR_AIN1, OUTPUT); pinMode(MOTOR_AIN2, OUTPUT); pinMode(MOTOR_PWMA, OUTPUT); pinMode(MOTOR_BIN1, OUTPUT); pinMode(MOTOR_BIN2, OUTPUT); pinMode(MOTOR_PWMB, OUTPUT); pinMode(MOTOR_STBY, OUTPUT); digitalWrite(MOTOR_STBY, HIGH); // 解除待机 } // 控制单个电机speed范围-255~255负值为反转 void setMotor(int motor, int speed) { bool inPin1, inPin2; if (speed 0) { inPin1 HIGH; inPin2 LOW; } else if (speed 0) { inPin1 LOW; inPin2 HIGH; speed -speed; // 取绝对值 } else { inPin1 LOW; inPin2 LOW; } if (motor 0) { // 左轮电机A digitalWrite(MOTOR_AIN1, inPin1); digitalWrite(MOTOR_AIN2, inPin2); analogWrite(MOTOR_PWMA, speed); } else if (motor 1) { // 右轮电机B digitalWrite(MOTOR_BIN1, inPin1); digitalWrite(MOTOR_BIN2, inPin2); analogWrite(MOTOR_PWMB, speed); } } // 小车运动封装前进、后退、左转、右转、停止 void carForward(int speed) { setMotor(0, speed); setMotor(1, speed); } void carBackward(int speed) { setMotor(0, -speed); setMotor(1, -speed); } void carTurnLeft(int speed) { setMotor(0, -speed); setMotor(1, speed); } void carTurnRight(int speed) { setMotor(0, speed); setMotor(1, -speed); } void carStop() { setMotor(0, 0); setMotor(1, 0); }4. 核心功能实现与代码详解有了基础的电机控制我们现在来实现三个核心功能。4.1 循迹功能实现循迹算法的核心是根据五个传感器的状态决定小车的转向策略。我们采用经典的“PID思想”比例-微分-积分的简化版——状态机法。// 循迹传感器初始化 void trackSensorInit() { pinMode(TRACK_SENSOR_1, INPUT); pinMode(TRACK_SENSOR_2, INPUT); pinMode(TRACK_SENSOR_3, INPUT); pinMode(TRACK_SENSOR_4, INPUT); pinMode(TRACK_SENSOR_5, INPUT); } // 读取五路传感器状态返回一个5位的二进制数 // 例如0b00100 表示只有中间传感器检测到黑线 int readTrackSensors() { int sensorState 0; sensorState | (!digitalRead(TRACK_SENSOR_1)) 4; // 最左 sensorState | (!digitalRead(TRACK_SENSOR_2)) 3; sensorState | (!digitalRead(TRACK_SENSOR_3)) 2; // 中间 sensorState | (!digitalRead(TRACK_SENSOR_4)) 1; sensorState | (!digitalRead(TRACK_SENSOR_5)) 0; // 最右 // 注意很多传感器模块检测到黑线输出 LOW(0)所以用 ! 取反 // 这样 sensorState 中1 代表检测到黑线更直观。 return sensorState; } // 循迹控制主函数 void trackLine() { int state readTrackSensors(); int baseSpeed 150; // 基础速度 switch (state) { case 0b00100: // 000 中间传感器检测到线 case 0b01100: case 0b00110: carForward(baseSpeed); // 直行 break; case 0b00010: case 0b00011: case 0b00001: // 线偏右 carTurnLeft(baseSpeed); // 向左转以校正 break; case 0b01000: case 0b11000: case 0b10000: // 线偏左 carTurnRight(baseSpeed); // 向右转以校正 break; case 0b11111: // 所有传感器都检测到黑线可能是十字路口或终点 case 0b00000: // 所有传感器都看不到线可能脱线了 carStop(); // 这里可以添加更复杂的逻辑比如路口转向或原地旋转寻线 break; default: carForward(baseSpeed); // 其他状态默认前进 } }代码逻辑解释程序不断读取五个传感器的状态。如果只有中间传感器看到黑线小车直行。如果黑线偏右右侧传感器检测到说明小车向左偏了需要向左转来校正反之亦然。这是一种非常直观且有效的控制方式。4.2 避障功能实现避障功能的优先级最高。我们使用一个简单的状态机检测到障碍物就后退并转向然后继续前进。// 避障传感器初始化 void obstacleSensorInit() { pinMode(OBSTACLE_SENSOR, INPUT); } // 检测障碍物 bool checkObstacle() { // E18-D80NK有障碍物时输出 LOW return (digitalRead(OBSTACLE_SENSOR) LOW); } // 避障反应函数 void avoidObstacle() { if (checkObstacle()) { Serial.println(障碍物执行避障。); carStop(); delay(200); carBackward(180); // 后退 delay(500); carStop(); delay(200); carTurnRight(200); // 向右转你可以改为随机左转或右转 delay(300); carStop(); delay(200); // 避障动作完成后跳出当前循环让主循环重新判断模式 } }在主循环中checkObstacle()的检测应该放在最前面一旦为真立即调用avoidObstacle()并中断当前的循迹或跟随逻辑。4.3 跟随功能实现跟随功能依赖于超声波模块持续测距并通过PID控制器来平滑地调整小车速度使其保持设定距离。首先实现超声波测距函数。// 超声波传感器初始化 void ultrasonicInit() { pinMode(ULTRASONIC_TRIG, OUTPUT); pinMode(ULTRASONIC_ECHO, INPUT); } // 获取距离厘米 float getDistanceCM() { digitalWrite(ULTRASONIC_TRIG, LOW); delayMicroseconds(2); digitalWrite(ULTRASONIC_TRIG, HIGH); delayMicroseconds(10); digitalWrite(ULTRASONIC_TRIG, LOW); long duration pulseIn(ULTRASONIC_ECHO, HIGH, 30000); // 超时30ms约5米 // 声音速度 340 m/s 0.034 cm/μs 距离 (时间 * 0.034) / 2 float distance duration * 0.034 / 2.0; if (distance 0 || distance 500) { // 无效值处理 return -1.0; } return distance; }然后实现一个简单的跟随控制器。// 跟随控制参数 float targetDistance 20.0; // 目标跟随距离 20cm float currentDistance 0; float Kp 3.0; // 比例系数需要根据实际调试 // 跟随模式主函数 void followObject() { currentDistance getDistanceCM(); if (currentDistance 0) { // 测距无效可能是超出范围或没有物体 carStop(); return; } float error currentDistance - targetDistance; int speedAdjust (int)(Kp * error); // 简单的P控制 // 限制速度范围 int baseSpeed 150; int leftSpeed baseSpeed speedAdjust; int rightSpeed baseSpeed speedAdjust; // 速度限幅 leftSpeed constrain(leftSpeed, -255, 255); rightSpeed constrain(rightSpeed, -255, 255); setMotor(0, leftSpeed); setMotor(1, rightSpeed); // 如果距离太近可以设置一个急停阈值 if (currentDistance 10.0) { carStop(); delay(100); } }这个跟随逻辑非常基础。当测量距离大于目标距离时error为正speedAdjust为正小车加速前进反之则减速甚至后退。Kp系数需要在实际测试中调整太大容易震荡太小则反应迟钝。5. 系统整合与主程序逻辑现在我们将所有功能整合到一个完整的程序中并设计主循环的状态逻辑。// 模式切换引脚例如用一个按键或蓝牙指令这里用宏定义模拟 #define MODE_SWITCH_PIN 0 // 假设用GPIO0接按键低电平触发 void setup() { Serial.begin(115200); motorInit(); trackSensorInit(); obstacleSensorInit(); ultrasonicInit(); pinMode(MODE_SWITCH_PIN, INPUT_PULLUP); // 内部上拉 Serial.println(智能小车初始化完成); Serial.println(当前模式循迹模式); } void loop() { // 第一步最高优先级检查障碍物 if (checkObstacle()) { avoidObstacle(); // 避障后短暂延迟并重新开始循环避免模式混乱 delay(100); return; } // 第二步检查模式切换这里用按键模拟实际可用蓝牙指令 if (digitalRead(MODE_SWITCH_PIN) LOW) { delay(50); // 简单消抖 if (digitalRead(MODE_SWITCH_PIN) LOW) { currentMode (currentMode MODE_TRACKING) ? MODE_FOLLOWING : MODE_TRACKING; Serial.print(切换到模式); Serial.println(currentMode MODE_TRACKING ? 循迹 : 跟随); carStop(); delay(500); // 模式切换停顿 while (digitalRead(MODE_SWITCH_PIN) LOW); // 等待按键释放 } } // 第三步根据当前模式执行相应功能 switch (currentMode) { case MODE_TRACKING: trackLine(); break; case MODE_FOLLOWING: followObject(); break; default: carStop(); } // 添加一个小延迟稳定循环周期 delay(20); }主循环逻辑解读安全第一每次循环最先检查障碍物有则立即处理处理完后return跳出本次循环确保避障动作不被其他逻辑打断。模式管理检查是否有切换模式的指令示例中是按键。切换模式时让小车暂停一下避免状态突变导致失控。功能执行根据currentMode变量执行循迹或跟随函数。循环延迟一个固定的短延迟有助于稳定程序节奏防止ESP32全速运行导致传感器读取不稳定。6. 常见问题与调试技巧在制作过程中你几乎一定会遇到下面这些问题。这里提供排查思路。6.1 电机不转或转动异常问题现象可能原因排查步骤电机完全不转1. 电源未接通或电压不足。2. 电机驱动模块的STBY引脚未置高。3. 控制引脚接线错误或接触不良。4. 代码中电机控制引脚定义错误。1. 用万用表测量电机驱动模块的VM和GND之间电压是否正常7-12V。2. 检查STBY引脚是否接高电平或代码中是否设置为HIGH。3. 逐一检查AIN1/AIN2/PWMA到ESP32的连线。4. 在setup()中先让电机以一个方向简单转动测试。只有一个电机转1. 不转的电机接线松动或损坏。2. 对应的驱动芯片通道损坏。3. 代码中只给了一个电机信号。1. 交换两个电机的接线如果问题跟随电机走则是电机问题如果问题跟随驱动通道走则是驱动模块问题。2. 使用analogWrite()测试PWM引脚是否有输出。电机抖动或转速慢1. 电源功率不足电池电量低。2. PWM频率不合适。3. 机械结构卡顿。1. 更换新电池或使用外接电源测试。2. ESP32的analogWrite()频率默认是1kHz一般没问题。可以尝试更换引脚。3. 检查车轮是否安装过紧齿轮箱是否有异物。6.2 循迹传感器反应不灵敏或错误问题小车总是跑偏或者对黑线没反应。调试校准传感器在setup()中将小车放在白纸和黑线上用Serial.print()分别打印五个传感器的原始读数digitalRead的值确认检测到黑线和白纸时输出是否稳定且相反。很多模块需要调节上面的电位器来调整检测灵敏度。检查安装高度传感器离地面太远或太近都会影响检测效果一般距离地面1-2厘米为宜。环境光干扰强烈的环境光特别是日光会影响红外传感器。可以尝试在传感器上方加遮光罩或者使用数字传感器如灰度传感器替代。逻辑反了确认代码中的逻辑。!digitalRead()是因为我的模块检测到黑线输出LOW。如果你的模块相反需要调整逻辑。6.3 避障或跟随功能不稳定超声波测距不准原因HC-SR04对角度和物体表面材质敏感。绒布、斜面等会导致回波信号弱。解决确保被测物体表面平整在pulseIn函数中增加超时时间对多次测量的距离取中值或平均值滤波。// 示例中值滤波函数 float getFilteredDistanceCM(int samples) { float distances[samples]; for (int i 0; i samples; i) { distances[i] getDistanceCM(); delay(30); // 两次测量间稍作延迟 } // 简单排序取中值这里省略排序代码可用std::sort // ... 排序逻辑 ... return distances[samples / 2]; }避障模块误触发原因E18-D80NK探测距离可调可能被地面或车体自身部件触发。解决调节模块上的电位器缩短有效探测距离调整安装角度使其略微朝上避免检测到地面。6.4 程序运行混乱或ESP32重启电源问题电机启动瞬间电流很大可能导致ESP32电压被拉低而重启。务必为ESP32和电机驱动使用两套独立的电源或一个电池通过两个稳压模块并在电源输入端并联一个大电容如1000μF缓冲电流冲击。代码阻塞避免在loop()中使用长延时delay()这会导致传感器无法及时检测。对于需要延时的动作如避障时的后退可以考虑使用非阻塞的定时方式millis()。堆栈溢出如果添加了太多功能或复杂的算法注意检查串口监视器是否出现“Guru Meditation Error”等错误这可能需要优化代码或调整FreeRTOS任务设置。7. 优化与扩展方向当你的基础功能都能稳定运行后可以考虑以下优化和扩展让小车更智能、更强大。7.1 算法优化PID循迹将上面简单的状态机换成真正的PID控制。通过计算位置偏差例如将五个传感器的状态映射为一个连续的位置值用PID算法计算出更平滑的转向速度使小车运行更稳定过弯更流畅。避障策略优化当前的避障策略是固定的“后退-右转”。可以升级为随机转向每次随机选择左转或右转避免陷入死胡同。测距扫描配合舵机转动超声波模块扫描左右两侧距离选择更空旷的一侧转向。跟随模式优化引入完整的PID比例-积分-微分控制并加入死区和输出限幅让跟随过程更加平稳减少在目标距离附近的来回震荡。7.2 功能扩展无线控制与监控利用ESP32自带的Wi-Fi或蓝牙。手机APP遥控开发一个简单的APP或使用现成的蓝牙串口助手切换模式、手动控制小车。Web服务器让ESP32作为一个Web服务器通过浏览器可以实时查看传感器数据、摄像头画面需额外模块并控制小车。数据上传将小车的状态、传感器数据上传到物联网平台如阿里云、ThingsBoard实现远程监控。增加视觉功能使用ESP32-CAM模块实现颜色识别、人脸跟踪、二维码导航等更高级的AI功能。多传感器融合增加MPU6050陀螺仪和加速度计实现更精确的直线行走和角度控制。结合编码器电机可以实现精确的里程计计算。7.3 工程化建议电源管理这是项目稳定的基石。强烈建议使用带有稳压功能的电源模块如LM2596单独为ESP32提供稳定的5V或3.3V。电机电源则直接接电池。结构加固使用尼龙柱、螺丝将各模块牢固地固定在底盘上避免因震动导致线材松动。代码模块化将不同功能的代码进一步封装成独立的.h和.cpp文件例如MotorDriver.h、SensorHub.h、PIDController.h等使主程序非常简洁便于维护和移植。参数可配置将PID参数、速度、目标距离等变量放在程序开头或者通过串口命令动态调整方便调试。从零开始整合ESP32、多种传感器和电机驱动完成一个具备循迹、避障、跟随功能的智能小车是一个非常好的嵌入式系统入门项目。它不仅涵盖了GPIO控制、PWM调速、传感器数据采集、中断处理等基础知识点还涉及到多任务调度、控制算法等进阶概念。调试过程中耐心和系统性排查是关键。务必遵循“电源先行分步测试”的原则先确保电机能动再调循迹然后加入避障最后整合跟随。每增加一个功能就充分测试其独立性和与原有功能的兼容性。希望这份详细的教程和代码能为你提供一个坚实的起点。你可以在此基础上自由发挥添加更多传感器、更优的算法或酷炫的无线功能打造出属于你自己的独一无二的智能小车平台。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价