零知派ESP32-S3-智能小车控制系统(4.2)-红外双目跟随模块使用
概述本文基于 ESP32-S3使用两个红外对射避障传感器GPIO20/GPIO4分别检测车头左右两侧目标经过INPUT_PULLUP输入处理得到稳定的双目触发信号再驱动跟随逻辑实现「单侧触发 → 转向对准、双侧触发 → 停车、目标丢失 → 停车等待」的跟随行为。整套功能封装为独立的FollowControl类通过CarDrive统一电机接口驱动底盘不依赖 Web、摄像头等其他模块可作为独立测试程序也可直接移植进整车工程。需要特别说明本次改动绕不开的引脚取舍ESP32-S3 版整车方案新增了摄像头模块OV2640并行接口光是XCLK、PCLK、VSYNC、HREF、SIOD、SIOC加 8 位数据线就占用了十几个 GPIO叠加电机驱动、舵机、超声波、循迹等原有模块的引脚需求后整板空闲 GPIO 已不够用。综合评估后项目做了取舍去掉霍尔编码器测速与 PID 直线行驶功能以及风扇灭火模块红外双目跟随模块复用这两个模块原本占用的引脚——本文里红外传感器接的GPIO20/GPIO4在原方案中分别是霍尔编码器信号脚和风扇驱动控制脚。如果你的项目仍保留测速和灭火功能需自行重新规划一组空闲 GPIO 给红外跟随模块不能照抄本文引脚号。这篇教程会把传感器原理、模块电路、类封装设计、调试过程和遇到的坑完整梳理一遍包括项目里这个模块走弯路的调试历程。一、系统接线部分1.1 硬件清单名称数量说明ESP32-S3 开发板1建议选带 USB CDC 的型号方便直接用 USB 口调试串口红外双目跟随模块2左、右各一个检测距离 2~30cm 可调电机驱动板 TT 直流电机1 套沿用原有底盘方案杜邦线若干、小车底盘、电池—常规耗材本方案中不再包含霍尔编码器测速模块和风扇火焰传感器灭火模块原因见「概述」中的引脚冲突说明。1.2 接线方案表每个红外跟随模块有 3 个引脚模块引脚ESP32-S3 开发板引脚说明左红外传感器VCC5V电源正极左红外传感器GNDGND电源地左红外传感器OUTGPIO20信号输出原霍尔编码器信号脚IR_LEFT_PIN右红外传感器VCC5V电源正极右红外传感器GNDGND电源地右红外传感器OUTGPIO4信号输出原风扇驱动控制脚IR_RIGHT_PIN1.3 连接示意图两个红外传感器在车头两侧呈八字形安装传感器正前方偏内约 15°与车身中轴线形成一定夹角左传感器检测到目标说明目标在左侧小车左转对准右传感器检测到目标说明目标在右侧小车右转对准两侧同时检测到说明目标已在正前方且距离很近停车。如果左右接反readLeft()和readRight()的含义会颠倒导致目标在右侧时车反而向左转这也是最常见的接线故障。1.4 具体接线图引脚定义与工程中config.h完全一致// config.h红外跟随相关节选ESP32-S3 版 // 注意GPIO20 / GPIO4 原本分配给霍尔编码器测速与风扇模块 // 现因摄像头占用大量引脚改为红外跟随模块复用 #define IR_LEFT_PIN 20 // 左侧红外传感器 OUT原编码器信号脚 #define IR_RIGHT_PIN 4 // 右侧红外传感器 OUT原风扇驱动脚 // 跟随参数 #define FOLLOW_SPEED_TURN 170 // 跟随转向速度关于引脚选择的说明。ESP32-S3 上GPIO0、GPIO3、GPIO45、GPIO46是芯片启动模式的 strapping 引脚不建议接常态外设——本方案中这几个脚已被摄像头并行数据线占用红外跟随模块已予以回避。GPIO19/GPIO20在很多板子上是 ESP32-S3 的原生 USB D-/D但若开发板通过独立 USB 转串口芯片下载、不依赖原生 USB CDC这两个脚即可当普通 GPIO 使用——本方案中GPIO20用作红外左输入、GPIO19用于电机驱动均实测可用。GPIO4是常规数字 IO从已下线的风扇模块腾出用作红外右输入没有问题。二、安装与使用教程2.1 开源平台-搜索红外双目-代码下载自动打开2.3 连接 - 验证 - 上传三、代码讲解部分3.1 独立测试程序ESP32-S3 版下面这个程序只依赖两个红外传感器不需要电机、超声波等其他模块在接入整车工程之前先用它验证传感器的方向和灵敏度。引脚号与config.h保持一致左GPIO20、右GPIO4烧录后建议打开串口监视器方便直接用 USB 口看输出。/************************************************************************************** * 文件: IR_Follow_Standalone_Test_ESP32S3.ino * 平台ESP32-S3 (Arduino-ESP32 框架) * 功能红外双目跟随模块 独立测试程序 * 引脚定义与主工程 config.h 完全一致 **************************************************************************************/ #define IR_LEFT_PIN 20 // 左侧红外传感器 OUT #define IR_RIGHT_PIN 4 // 右侧红外传感器 OUT void setup() { Serial.begin(115200); delay(200); pinMode(IR_LEFT_PIN, INPUT_PULLUP); pinMode(IR_RIGHT_PIN, INPUT_PULLUP); Serial.println(F()); Serial.println(F( 红外双目跟随模块 独立测试程序 (ESP32-S3))); Serial.println(F()); Serial.println(F(L1 表示左传感器检测到物体R1 表示右传感器检测到物体)); } void loop() { int leftVal digitalRead(IR_LEFT_PIN); int rightVal digitalRead(IR_RIGHT_PIN); // 传感器检测到物体输出 LOW取反后 1 表示检测到 bool leftDetected (leftVal LOW); bool rightDetected (rightVal LOW); Serial.print(F(L)); Serial.print(leftDetected ? 1 : 0); Serial.print(F( R)); Serial.print(rightDetected ? 1 : 0); if (leftDetected rightDetected) { Serial.println(F( → 停车目标居中且已近)); } else if (leftDetected !rightDetected) { Serial.println(F( → 左转目标偏左)); } else if (!leftDetected rightDetected) { Serial.println(F( → 右转目标偏右)); } else { Serial.println(F( → 停车目标丢失等待)); } delay(100); }3.2 FollowControl 类定义FollowControl.hFollowControl.h是整套跟随功能的接口层。它对外只暴露四个公有方法begin()负责引脚初始化与状态清零run()供主循环每帧调用、执行一次完整的感知与决策stop()退出跟随模式并停车isActive()供上层查询当前是否处于跟随态。感知部分由两个私有方法readLeft()/readRight()完成对调用方屏蔽了 GPIO 操作的细节。私有成员变量isLost与lostStartTime记录目标丢失的起始时刻为后续扩展例如丢失超时后触发旋转搜索预留接口。头文件本身不出现任何具体引脚号引脚全部集中在config.h里管理更换引脚时只需修改宏定义类代码无需改动。#ifndef FOLLOW_CONTROL_H #define FOLLOW_CONTROL_H #include Arduino.h #include config.h #include SpeedDrive.h class FollowControl { public: void begin(); // 初始化引脚与状态 void run(); // 主循环每帧调用读传感器 → 决策 → 驱动电机 void stop(); // 退出跟随模式停车 bool isActive(); private: bool active false; unsigned long lostStartTime 0; // 记录目标丢失起始时刻预留扩展用 bool isLost false; bool readLeft(); // 左传感器是否检测到目标 bool readRight(); // 右传感器是否检测到目标 }; extern FollowControl Follower; #endif3.3 FollowControl 跟随逻辑实现FollowControl.cpp实现文件把感知与决策放在同一个类里逻辑简洁清晰。begin()把两个 IR 引脚配置为INPUT_PULLUP无物体时保持 HIGH避免悬空导致误触发并清零所有状态标志。readLeft()/readRight()把「检测到物体输出 LOW」翻译成布尔true调用方无需关心原始电平逻辑。run()每帧读取双目信号按四种组合决策双侧触发停车、左侧触发左转、右侧触发右转、双侧丢失则停车并记录丢失起始时刻。stop()退出跟随模式时同步停车并重置标志确保退出状态干净。#include FollowControl.h FollowControl Follower; void FollowControl::begin() { // INPUT_PULLUP无物体时保持 HIGH避免悬空导致误触发 pinMode(IR_LEFT_PIN, INPUT_PULLUP); pinMode(IR_RIGHT_PIN, INPUT_PULLUP); active false; isLost false; lostStartTime 0; Serial.println([跟随] 红外双目模块初始化完成); } bool FollowControl::readLeft() { // 传感器检测到物体输出 LOW return digitalRead(IR_LEFT_PIN) LOW; } bool FollowControl::readRight() { return digitalRead(IR_RIGHT_PIN) LOW; } void FollowControl::run() { if (!active) { active true; isLost false; lostStartTime 0; Serial.println([跟随] 模式启动); } bool leftDetected readLeft(); bool rightDetected readRight(); if (leftDetected rightDetected) { // 目标居中且已近 - 停车 CarDrive.stop(); isLost false; Serial.println([跟随] 目标居中 - 停车); } else if (leftDetected !rightDetected) { // 目标偏左 - 左转 CarDrive.run(-FOLLOW_SPEED_TURN, FOLLOW_SPEED_TURN); isLost false; Serial.println([跟随] 目标偏左 - 左转); } else if (!leftDetected rightDetected) { // 目标偏右 - 右转 CarDrive.run(FOLLOW_SPEED_TURN, -FOLLOW_SPEED_TURN); isLost false; Serial.println([跟随] 目标偏右 - 右转); } else { // 两侧都未检测到 - 丢失目标停车等待 if (!isLost) { isLost true; lostStartTime millis(); } CarDrive.stop(); Serial.println([跟随] 目标丢失 - 停车等待); } } void FollowControl::stop() { if (active) { CarDrive.stop(); active false; isLost false; Serial.println([跟随] 模式停止); } } bool FollowControl::isActive() { return active; }丢失后的行为扩展原地旋转搜索当前实现的丢失策略是停车等待isLost与lostStartTime两个成员变量已经记录了丢失的起始时刻。如果需要在目标丢失超过一定时间后触发原地旋转搜索只需在run()的else分支里根据millis() - lostStartTime判断超时然后驱动电机旋转即可其余代码不受影响。下面是一段最简单的扩展示例// FollowControl.cpp — run() 里 else 分支的扩展写法 // 需要在 config.h 中额外定义 // #define FOLLOW_LOST_TIMEOUT_MS 2000 // 停车等待时长ms超过后开始搜索 // #define FOLLOW_SEARCH_SPEED 120 // 搜索旋转速度 else { if (!isLost) { isLost true; lostStartTime millis(); } unsigned long lostMs millis() - lostStartTime; if (lostMs FOLLOW_LOST_TIMEOUT_MS) { // 丢失时间较短先停车等待目标可能只是短暂晃出视野 CarDrive.stop(); Serial.println([跟随] 目标丢失 - 停车等待); } else { // 丢失时间超过阈值原地旋转搜索 CarDrive.run(FOLLOW_SEARCH_SPEED, -FOLLOW_SEARCH_SPEED); Serial.println([跟随] 目标丢失超时 - 旋转搜索); } }调试历程问题现象原因分析解决方法上电后串口读数持续乱跳 0/1传感器前无遮挡也频繁误触发引脚模式最初用的是普通INPUT没有上拉传感器未接时 GPIO 悬空引脚电平随环境噪声漂移改为INPUT_PULLUP无物体时引脚稳定保持 HIGH误触发消失跟随方向反了目标在左侧时车向右转GPIO20接的是右传感器、GPIO4接的是左传感器与config.h里IR_LEFT_PIN/IR_RIGHT_PIN的定义左右对调了对调两根杜邦线用独立测试程序确认「手挡左侧 →L1」后再接整车对准时来回抖动小车在目标正前方摆个不停FOLLOW_SPEED_TURN170转向力度偏大每次转向都过冲矫枉过正地触发另一侧适当降低转向速度同时检查两传感器安装间距是否过大双触发区间过窄会加剧抖动进入跟随模式后小车完全不响应串口无任何输出run()未被主循环周期性调用或上层发送G命令后没有持续触发Follower.run()确认主循环里跟随模式激活期间每帧都调用Follower.run()而不是只在收到命令时调用一次参数调优指南问题现象可能原因调整方案对准时过冲来回抖动FOLLOW_SPEED_TURN过大适当降低转向速度建议从 140 开始逐步上调转向太慢目标侧移后跟不上FOLLOW_SPEED_TURN过小适当提高转向速度双触发区间太窄小车总在目标正前方左右摆两传感器安装间距过大缩小两传感器之间的横向间距传感器始终触发无法进入停车状态灵敏度过高探测范围过大逆时针调节电位器降低灵敏度目标很近才能触发跟随距离过短灵敏度过低顺时针调节电位器提高灵敏度阳光直射下频繁误触发环境红外分量干扰接收管逆时针调节电位器降低灵敏度并给模块加装黑色遮光外壳与避障模式B 命令的功能共用说明同一对 IR 传感器在跟随与避障两种模式下复用底层读取接口相同上层决策逻辑完全相反对比项跟随模式G避障辅助B传感器安装推荐角度正前方偏内 ±15°斜前方 45°135°触发含义目标在那一侧应转向靠近那一侧有障碍应转向远离转向决策转向触发侧左触发 → 左转转离触发侧左触发 → 右转速度控制慢速跟随双触发停车紧急规避直接转向readLeft()/readRight()的读取逻辑在两种模式下完全相同上层行为由各自的控制类单独实现体现了感知与决策分离的设计价值——这也是本次去掉编码器测速和风扇灭火模块、把引脚腾给红外跟随时改动量能控制在「只改config.h宏定义」这一层的原因。四、项目结果演示零知派ESP32-S3 演示 双目跟随 功能零知派ESP32-S3 演示 双目跟随 功能演示内容建议包括以下几段独立测试程序串口输出。用手在左右传感器前方交替遮挡展示L/R状态实时变化以及对应的判断结果左转右转停车丢失等待。灵敏度调节演示。用螺丝刀旋转电位器观察触发距离随调节方向的变化。整车跟随效果。发送G命令进入跟随模式手持目标物在小车前方移动展示小车能够平滑跟随、目标靠近时停车、目标离开视野后停车等待的完整过程。引脚复用后的整车联调。由于本方案去掉了测速和灭火模块建议同时演示一次完整上电自检确认摄像头、循迹、超声波云台、红外跟随几个保留模块之间没有引脚冲突或相互干扰。五、模块技术原理讲解红外双目跟随模块使用的是主动式红外避障传感器每个传感器内部集成了一个红外发射管发射红外光和一个红外接收管接收反射回来的红外光。这部分硬件原理与主控芯片无关STM32 也好、ESP32-S3 也好模块本身的工作方式完全一样。5.1 红外发射光电接收红外发射管由红外发光二极管组成发光体用红外辐射效率高的材料常用砷化镓制成 PN 结正向偏压向 PN 结注入电流激发红外光。红外接收管将红外线光信号变成电信号其核心部件是一个特殊材料的 PN 结随着红外光强度的增加电流也随之增大。核心检测原理发射管持续发出 940nm 红外脉冲光目标物体反射红外光反射率与目标材质、颜色相关接收管检测反射光强度LM393 比较器将模拟信号转为数字电平输出。不同表面对红外光的反射特性表面特性红外反射接收管状态输出电平浅色光滑表面强反射导通LOW检测到物体深色粗糙表面弱反射吸收截止HIGH未检测到黑色物体几乎全部吸收截止HIGH未检测到当探测到障碍物在检测范围内时模块输出低电平LOW。该模块检测距离为 2~30cm检测角度约 35°距离可通过电位器调节。注意不同厂家、不同批次的模块输出电平与检测状态的对应关系可能相反。本项目实测确认检测到物体 → 输出 LOW0无物体 → 输出 HIGH1。代码中readLeft()/readRight()的 LOW判断即基于此结论如果你的模块行为相反将判断改为 HIGH即可。5.2 LM393 比较器原理LM393 是一款双差分比较器 IC内部包含两个独立的比较器。比较器对两路输入电压进行比较当同相输入端电压高于反相输入端−电压时比较器输出高电平当同相输入端电压低于反相输入端−电压时输出低电平。内部电路上Q1、Q2 构成同相输入端的输入级Q3、Q4 构成反相输入端的输入级Q5、Q6 用作电流镜Q7、Q8 为开关级。各级晶体管均由恒流源供电。一旦同相输入端的输入电压高于反相输入端的电压输出端晶体管 Q8 就会截止通过上拉电阻引出高电平反之同相输入端电压等于或低于反相输入端电压时输出端晶体管导通引出低电平。电压变化发生在反相输入端还是同相输入端对开关功能没有影响唯一关键的因素是两个输入端之间的电压差值。5.3 电位器调节原理模块上蓝色方形可调 3362 电位器本质上是可调分压器旋转旋钮改变分压比例从而改变送入 LM393 比较器的参考电压阈值调节方向参考电压变化检测距离变化顺时针旋转阈值降低检测距离增大更灵敏逆时针旋转阈值升高检测距离减小更不灵敏5.4 单路电路原理典型的 IR333 PT333 LM393 红外避障模块单路电路核心元件元件作用100Ω 限流电阻 R3限制 IR333 发射管电流通常 100Ω ~ 200Ω10kΩ 上拉电阻 R5LM393 集电极开路输出需要上拉到 VCC通常 10kΩ10kΩ 电位器 R2调节比较器参考阈值电压通常 10kΩ 3362 型5.5 双目探测逻辑两个红外传感器在车头两侧呈八字形安装左传感器检测到目标意味着目标在左侧小车应左转对准右传感器检测到目标意味着目标在右侧小车应右转对准。左传感器右传感器判断结果执行动作检测到LOW未检测HIGH目标在左侧左转未检测HIGH检测到LOW目标在右侧右转检测到LOW检测到LOW目标在正前方停车目标已近未检测HIGH未检测HIGH目标丢失停车等待如果方向反了小车会朝着远离目标的方向转永远跟不住。用独立测试程序确认「手挡左传感器 → 串口显示L1」是最快的方向验证方法。六、常见问题解答FAQQ1传感器接好了跟随时小车原地不动或方向反了是什么问题首先用独立测试程序检查把手放在左侧传感器前串口显示的是L1还是R1。如果显示R1说明GPIO20与GPIO4接反了对调两根杜邦线即可。如果双侧都是 0先排查供电是否正常再顺时针旋转电位器提高灵敏度。Q2红外传感器在阳光下失效怎么办该模块对环境光有一定适应性但在强阳光直射下阳光中的红外分量会干扰接收管导致误触发。解决方法逆时针调节电位器降低灵敏度同时给传感器加装遮光外壳可用黑色热缩管套住模块两侧。Q3进入跟随模式后小车完全没有反应串口也没有输出是什么问题FollowControl::run()需要在主循环里持续被调用而不是只在收到G命令时执行一次。检查主循环逻辑确认跟随模式激活期间每帧都执行了Follower.run()同时确认Follower.begin()已在setup()里调用过。Q4去掉测速和风扇灭火模块之后整车还能正常运行测速相关的调试命令吗不能。测速和 PID 直线行驶相关的命令、灭火相关的命令在本方案里已从代码中移除对应的GPIO20/GPIO4被红外跟随模块占用。如果之后想恢复这两个功能需要重新给摄像头之外的功能规划一套互不冲突的引脚表不能与红外跟随模块共用GPIO20/GPIO4。