零知派——STM32智能小车系列教程(三):红外双目跟随模块原理与调试
引言:智能小车要实现的功能很多——避障、跟随、贴边、灭火、直线行驶,每一个模块单独调试起来都有自己的难点。如果一开始就把所有模块塞进一个工程里联调,出了问题很难分清到底是传感器硬件的问题、电平极性的问题,还是上层控制算法的问题
所以这一系列教程的思路是:先把每个功能模块单独拎出来,配一个不依赖其他硬件的最小测试程序,把模块本身调通调透,再回头看它在完整项目里是怎么被使用的
目录
Q1:传感器接好了,跟随时小车原地转但不追目标,是什么问题?
一、引言
在前两篇教程里,我们把循迹传感器和超声波云台都调通了,这一篇讲红外双目跟随——用两个固定在车头左右两侧的红外对射传感器,让小车识别目标方向,像影子一样跟在目标后面走,相比超声波云台,双目红外跟随的优势在:
- 响应速度极快:毫秒级,无需舵机扫描等待
- 电路简单、成本极低:只需读两个GPIO电平
劣势是探测距离短(2~30cm可调)、无法测量具体距离、对高反射率目标(白色物体)更灵敏。正是因为这个特性,在本项目里这两个模块同时承担了两个角色:跟随模式(G命令)用于追踪目标,避障模式(B命令)的辅助IR用于检测45°斜前方近距离障碍
这篇教程会把传感器原理、模块电路、跟随算法设计、调试过程和遇到的坑都完整梳理一遍,包括项目里这个模块走弯路的调试历程
这个系列教程拆成以下几篇:
| 篇次 | 内容 |
|---|---|
| 第一篇(已完成) | 五路循迹模块(TCRT5000 + LM393/LM339) |
| 第二篇(已完成) | HC-SR04 超声波测距 + SG90 舵机云台三模式应用 |
| 第三篇(本文) | 红外双目跟随模块(红外避障对管) |
| 第四篇 | 霍尔编码器测速与直线行驶 PID(含 PID 自整定) |
| 第五篇 | DHT11 温湿度、火焰传感器、风扇灭火模块 |
| 第六篇 | 蓝牙/串口双通道通信协议设计 |
| 终篇 | 整车项目架构总览,把前面六篇串起来讲整体设计思路 |
二、红外对射传感器硬件原理
红外双目跟随模块使用的是主动式红外避障传感器,每个传感器内部集成了一个红外发射管(发射红外光)和一个红外接收管(接收反射回来的红外光)
2.1 红外发射+光电接收
红外跟随模块红外发射器与光电三极管集成在一起,适用于近接检测和反射传感

- 红外发射管是由红外发光二极管组成发光体,用红外辐射效率高的材料(常用砷化镓)制成 PN 结,正向偏压向 PN 结注入电流激发红外光
- 红外接收管则是将红外线光信号变成电信号的半导体器件,其核心部件是一个特殊材料的 PN 结,随着红外光强度的增加,电流也随之增大
①核心检测原理
① 发射管持续发出 940nm 红外脉冲光 → ② 目标物体反射红外光(反射率与目标材质、颜色相关) → ③ 接收管检测反射光强度 → LM393 比较器将模拟信号转为数字电平输出

不同表面对红外光的反射特性:
| 表面特性 | 红外反射 | 接收管状态 | 输出电平 |
|---|---|---|---|
| 浅色/光滑表面 | 强反射 | 导通 | LOW(检测到物体) |
| 深色/粗糙表面 | 弱反射/吸收 | 截止 | HIGH(未检测到) |
| 黑色物体 | 几乎吸收 | 截止 | HIGH(未检测到) |
②输出电平与检测状态
当探测到障碍物在检测范围内时,模块会输出低电平(LOW)。该模块检测距离为 2~30cm,检测角度 35°,距离可通过电位器调节

注意:不同厂家、不同批次的模块,输出电平与检测状态的对应关系可能相反。本项目实测确认:检测到物体 → 输出 LOW(0),无物体 → 输出 HIGH(1)
③LM393 比较器
LM393 是一款双差分比较器 IC,内部包含两个独立的运算放大器(比较器)。比较器对两路输入电压进行比较,判断哪路更大,然后给出输出

比较电压工作原理
- 当同相输入端(+) 电压高于反相输入端(-)电压时,比较器输出高电平。
- 当同相输入端(+)电压低于反相输入端(-)电压时,比较器输出低电平
比较器内部电路解析
晶体管Q1、Q2(绿色)构成同相输入端的输入级。反相输入端的输入级由晶体管Q3、Q4(黄色)组成。晶体管Q5、Q6(灰色)用作电流镜。晶体管Q7、Q8(红色)为开关级

- 各级晶体管均由恒流源供电、一旦同相输入端(+)的输入电压高于反相输入端(-)的电压,比较器输出端的晶体管(Q8)就会截止
- 若通过电阻(RL)为输出端提供5伏电压,即可引出高电平脉冲、同相输入端电压等于或低于反相输入端电压,输出端晶体管导通,比较器输出端向电源负电位侧切换,此时可引出低电平脉冲
电压变化发生在反相输入端还是同相输入端,对开关功能没有影响。唯一关键的因素是两个输入端之间的电压差值。各类开关示例中,将不同的电压变化情况(蓝色箭头)分开展示:
(1)同相输入端电压上升

同相输入端电压升高会使输出端电压随之升高
(2)同相输入端的电压降低

同相输入端产生电压降会使输出端出现电压降
(3)反相输入端电压上升

反相输入端电压升高会使输出端电压降低
(4)反相输入端的电压降低

同相输入端产生电压压降时,输出端也会出现电压压降
④电位器调节
模块上那颗 蓝色方形可调3362电位器,本质上是 可调分压器——旋转旋钮改变分压比例,从而改变送入 LM393 比较器的参考电压(阈值)
调节效果:
| 调节方向 | 参考电压变化 | 检测距离变化 |
|---|---|---|
| 顺时针旋转 | 阈值降低 | 检测距离增大(更灵敏) |
| 逆时针旋转 | 阈值升高 | 检测距离减小(更不灵敏) |
实际调试方法:
- 将模块正对目标物体(如手掌),距离约 10~15cm
- 慢慢旋转电位器,观察模块上的状态指示灯
- 找到指示灯刚好点亮(或熄灭)的临界点
- 再往灵敏方向回调一点点
两个模块需要分别调节,因为每个模块的电位器是独立的,灵敏度和检测距离不会自动一致
2.2 原理图讲解
①单路 红外跟随电路
典型的 IR333 + PT333 + LM393 红外避障模块单路原理图

| 元件 | 作用 |
|---|---|
| 100Ω限流电阻R3 | 限制 IR333发射管电流,通常 100Ω ~ 200Ω |
| 10KΩ上拉电阻R5 | LM393 集电极开路输出需要上拉到 VCC,通常 10kΩ |
| 10kΩ电位器R2 | 调节比较器参考阈值电压,通常 10kΩ 3362型 |
②探测逻辑
两个红外传感器在车头两侧呈八字形安装:
左传感器检测到目标意味着目标在左侧,小车应该左转对准;右传感器检测到目标意味着目标在右侧,小车应该右转对准
| 左传感器 | 右传感器 | 判断结果 | 执行动作 |
|---|---|---|---|
| 检测到(LOW) | 未检测(HIGH) | 目标在左侧 | 左转 |
| 未检测(HIGH) | 检测到(LOW) | 目标在右侧 | 右转 |
| 检测到(LOW) | 检测到(LOW) | 目标在正前方 | 停车(目标已近) |
| 未检测(HIGH) | 未检测(HIGH) | 目标丢失 | 原地旋转搜索 |
如果方向反了,小车会朝着远离目标的方向转,永远跟不住
三、硬件接线说明
3.1 红外跟随模块引脚定义
本项目跟随模块的引脚定义(与 pinsdefine.h 一致)
| 传感器位置 | 引脚号 | 代码定义 |
|---|---|---|
| 左侧IR模块 | 21 | ServoPWM3 |
| 右侧IR模块 | 24 | ServoPWM4 |
注意:这两个引脚虽然命名为
ServoPWM3/4,但在本项目中被复用为红外传感器的信号输入引脚
3.2 接线方案表
每个红外跟随模块有 3个引脚:
| 模块 | 引脚 | 零知派迷你板引脚 | 说明 |
|---|---|---|---|
| 左红外传感器 | VCC | 5V | 电源正极 |
| 左红外传感器 | GND | GND | 电源地 |
| 左红外传感器 | OUT | D21(ServoPWM3) | 信号输出 |
| 右红外传感器 | VCC | 5V | 电源正极 |
| 右红外传感器 | GND | GND | 电源地 |
| 右红外传感器 | OUT | D24(ServoPWM4) | 信号输出 |
3.3 连接示意图

左侧传感器(直插小车扩展板——舵机接口 PWM3)和右侧传感器(直插小车扩展板——舵机接口 PWM4)接反后,代码里 lIR 和 rIR 含义会颠倒,导致目标在右侧时车向左转。如果跟随方向错误,第一步先用独立测试程序确认左右传感器方向是否正确,再排查代码
四、独立测试程序
下面这个程序只依赖两个红外传感器,不需要电机、超声波等其他模块,在接入整车工程之前,先用这个程序独立验证传感器的方向和灵敏度
/**************************************************************************************
* 文件: IR_Follow_Standalone_Test.ino
* 作者:零知实验室(深圳市在芯间科技有限公司)
* 功能:红外双目跟随模块 独立测试程序
* 引脚定义与主项目 pinsdefine.h 完全一致
*
* 使用方法:
* 1. 烧录本程序到零知派迷你板
* 2. 打开串口监视器,波特率115200
* 3. 用手或物体在左右传感器前方移动
* 4. 观察串口输出的检测状态变化
* 5. 通过电位器调节每个传感器的检测距离
**************************************************************************************/
#define IR_LEFT_PIN 21 // ServoPWM3
#define IR_RIGHT_PIN 24 // ServoPWM4
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(" 红外双目跟随模块 独立测试程序"));
Serial.println(F("========================================"));
Serial.println(F("L=1 表示左传感器检测到物体"));
Serial.println(F("R=1 表示右传感器检测到物体"));
Serial.println(F(""));
Serial.println(F("状态说明:"));
Serial.println(F(" L=0 R=0 → 目标丢失"));
Serial.println(F(" L=1 R=0 → 目标在左侧 → 左转"));
Serial.println(F(" L=0 R=1 → 目标在右侧 → 右转"));
Serial.println(F(" L=1 R=1 → 目标在正前方 → 停车"));
Serial.println(F(""));
}
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);
}
五、调试流程演示
5.1 测试流程
①灵敏度测试

上电,传感器前无遮挡、串口显示 L=0 R=0 → 搜索(目标丢失)、如有误检测,调节电位器降低灵敏度
②遮挡测试

用手掌挡住左传感器,串口显示 L=1 R=0 → 左转(目标偏左)、用手掌挡住右传感器,串口显示 L=0 R=1 → 右转(目标偏右)、用手掌同时挡住两个传感器,串口显示 L=1 R=1 → 停车(目标居中且已近)
③跟随模式测试

发送命令"G",手掌从左侧移到右侧,状态从左转、停车到右转状态依次变化,确认状态切换平滑
5.2 演示视频
零知派迷你板智能小车-红外双目跟随模块调试
展示独立测试程序在串口监视器上的实时输出,用手在不同位置遮挡两个传感器,对应串口输出的状态变化;演示灵敏度调节——用螺丝刀旋转电位器,观察触发距离的变化;展示整车跟随效果
六、项目跟随功能实现
确认模块本身没问题之后,再看完整项目里跟随功能是怎么组织的。这部分代码分布在两个文件里:
| 文件 | 职责 |
|---|---|
sc_perception.h/.cpp |
IRSensor::read() ——底层读取两个 GPIO 电平,转换为布尔值 |
sc_behaviors.h/.cpp |
FollowBehavior::update() ——上层跟随状态机 |
6.1 IRSensor底层感知层
// sc_perception.cpp
IRSensor::IRSensor()
{
// INPUT_PULLUP:无物体时保持HIGH,避免悬空导致误触发
pinMode(ServoPWM3, INPUT_PULLUP);
pinMode(ServoPWM4, INPUT_PULLUP);
}
void IRSensor::read(bool *isLeft, bool *isRight)
{
// 检测到物体 → LOW → isLeft/isRight = true
// 无物体 → HIGH → false
*isLeft = (digitalRead(ServoPWM3) == LOW);
*isRight = (digitalRead(ServoPWM4) == LOW);
}
6.2 FollowBehavior跟随状态机
最终版本状态机
FOLLOW(跟随状态)
lIR && rIR → stop()(双触发=正对且近,停止)
lIR && !rIR → 左转对准(-FOLLOW_TURN, FOLLOW_TURN)
!lIR && rIR → 右转对准(FOLLOW_TURN, -FOLLOW_TURN)
连续 5 帧无触发(_lostFrames >= LOST_THRESH)
切换回 SEARCH_CW
void FollowBehavior::update()
{
bool lIR = false, rIR = false;
g_ir.read(&lIR, &rIR);
// 调试输出(正式发布可屏蔽)
Serial.print(F("[FOLLOW] L=")); Serial.print(lIR);
Serial.print(F(" R=")); Serial.println(rIR);
// ── 有目标:进入跟随 ─────────────────────────────────────────
if (lIR || rIR) {
_lostFrames = 0;
if (_st != FOLLOW) {
_st = FOLLOW;
Serial.println(F("[FOLLOW] 发现目标,进入跟随"));
BTSerial.println(F("[FOLLOW] Target found"));
}
if (lIR && rIR) {
// 双触发:目标正对且已非常近,停止
g_motor.stop();
} else if (lIR && !rIR) {
// 左侧触发:目标在左侧 → 左转对准
g_motor.motorRun(-FOLLOW_TURN, FOLLOW_TURN);
} else {
// 右侧触发:目标在右侧 → 右转对准
g_motor.motorRun(FOLLOW_TURN, -FOLLOW_TURN);
}
return;
}
// ── 无目标:消抖后进入搜索 ───────────────────────────────────
if (_st == FOLLOW) {
_lostFrames++;
if (_lostFrames < LOST_THRESH) {
g_motor.stop(); // 消抖期间保持停止
return;
}
_st = SEARCH_CW; _ts = millis(); _phase = 0; _lostFrames = 0;
g_motor.stop();
Serial.println(F("[FOLLOW] 目标丢失,开始搜索"));
BTSerial.println(F("[FOLLOW] Lost, searching"));
return;
}
// ── 搜索阶段 ─────────────────────────────────────────────────
unsigned long elapsed = millis() - _ts;
switch (_phase) {
case 0: g_motor.motorRun(SEARCH_SPEED, -SEARCH_SPEED);
if (elapsed >= SPIN_MS) { g_motor.stop(); _phase = 1; _ts = millis(); }
break;
case 1: if (elapsed >= PAUSE_MS) { _phase = 2; _ts = millis(); } break;
case 2: g_motor.motorRun(-SEARCH_SPEED, SEARCH_SPEED);
if (elapsed >= SPIN_MS * 2) { g_motor.stop(); _phase = 3; _ts = millis(); }
break;
case 3: if (elapsed >= PAUSE_MS) { _phase = 0; _ts = millis(); } break;
default: _phase = 0; _ts = millis(); break;
}
}
| 调试历程 | 功能迭代内容 |
|---|---|
| 卡死不动 | 最初 onEnter() 进入搜索状态旋转,IR引脚没有接线,读到的是拉高的 HIGH |
| 跟随逻辑方向反了 | motorRun(-FOLLOW_TURN, FOLLOW_TURN) 实际上是转向弄反 |
| 跟随效果不稳定,抖动明显 | 降低搜索和跟随转速,加入丢失目标消抖(连续5帧确认丢失才切换) |
6.3 系统流程图

①参数调优指南
| 问题现象 | 可能原因 | 调整方案 |
|---|---|---|
| 搜索时转速过快,找到目标有明显顿挫 | SEARCH_SPEED 太大 |
降低到 50~65 |
| 对准时过冲,来回抖动 | FOLLOW_TURN 太大 |
降低到 45~55 |
| 目标稍微晃动就进入搜索 | LOST_THRESH 太小 |
增加到 8~10 |
| 丢失目标后反应太慢才开始搜索 | LOST_THRESH 太大 |
减小到 3~5 |
| 双触发区间太窄,小车总在左右摆 | 两传感器间距太大 | 缩小传感器间距 |
| 传感器始终触发(误触发) | 灵敏度太高 | 逆时针调电位器降低灵敏度 |
②与避障模式(B命令)的功能共用说明
同一对 IR 传感器在两种模式下复用,逻辑完全不同:
| 对比项 | 跟随模式(G) | 避障辅助(B) |
|---|---|---|
| 传感器安装推荐角度 | 正前方,偏内 ±15° | 斜前方 45°/135° |
| 触发含义 | 目标在那一侧,应转向靠近 | 那一侧有障碍,应转向远离 |
| 转向决策 | 转向触发侧(左触发→左转) | 转离触发侧(左触发→右转) |
| 速度控制 | 慢速跟随,双触发停车 | 紧急规避,直接全速转向 |
这也是代码里两个模块功能共用同一套硬件的典型案例,上层算法完全不同,但底层
IRSensor::read()接口是相同的,体现了感知层与行为层分离的设计价值
七、常见问题解答(FAQ)
Q1:传感器接好了,跟随时小车原地转但不追目标,是什么问题?
A:首先用独立测试程序检查:把手放在左侧传感器前,串口显示的是
L=1还是R=1。如果显示R=1,说明左右信号线接反了,换线即可。如果双侧都是0,检查供电和电位器
Q2:红外传感器在阳光下失效怎么办?
A:该模块对环境光有一定的适应性,但在强阳光直射的情况下,阳光中的红外分量会干扰接收管,导致误触发。解决方法:逆时针调小电位器降低灵敏度,同时给传感器加装遮光外壳(可用黑色热缩管套住模块两侧)
资料整合
LM393 数据手册(TI): lm393.pdf
LM393 应用电路: 差分比较器应用电路
红外跟随和红外避障虽然用了同样的硬件,但控制逻辑完全不同。避障只需要二值判断(有/没有障碍),而跟随需要正确理解“目标在左还是在右”并执行对应的转向动作——方向一旦搞反,整个功能就完全失效了
更多推荐

所有评论(0)