引言:智能小车要实现的功能很多——避障、跟随、贴边、灭火、直线行驶,每一个模块单独调试起来都有自己的难点。如果一开始就把所有模块塞进一个工程里联调,出了问题很难分清到底是传感器硬件的问题、电平极性的问题,还是上层控制算法的问题

        所以这一系列教程的思路是:先把每个功能模块单独拎出来,配一个不依赖其他硬件的最小测试程序,把模块本身调通调透,再回头看它在完整项目里是怎么被使用的

目录

一、系列教程前言

二、循迹模块硬件原理

 2.1 红外发射+接收

2.2 原理图讲解

三、硬件接线说明

3.1 循迹模块引脚定义

3.2 接线方案表

3.3 连接示意图

四、独立测试程序

五、调试流程演示

5.1 测试流程

5.2 演示视频

六、项目循迹功能实现

6.1 感知层加权质心算法

6.2 极性配置

6.3 丢线判断与防抽动消抖

6.4 从偏差值到电机控制

6.5 PID参数调试方法

七、常见问题解答(FAQ)

Q1:小车过弯时冲出赛道怎么办?

Q2:串口读到的电平与预期相反怎么办?

Q3:检测距离太近或太远怎么调?


一、系列教程前言

这是系列的第一篇,专门讲五路循迹模块——也是实际调试过程中踩坑最多、最容易出现“看起来能用但逻辑是错的”这种隐蔽问题的一个模块。整车项目里循迹功能遇到过三次崩溃:

次数 问题 根因 表现
第一次 精确匹配表遗漏传感器组合 五路传感器有 2⁵ = 32 种组合,匹配表只列了约 16 种 大量组合下小车卡死不动
第二次 极性参数传反 setColors(WHITE, BLACK) 把黑线当成了地面 小车在黑线边缘反复前后抽动
第三次 转向方向符号搞反 偏差值符号到电机转向的映射写反了 感知是对的,车却往错误方向转

这三个问题分别对应了 “算法设计”、“硬件极性适配”、“控制输出方向” 三个完全不同的层面,非常适合作为一个完整的调试案例


后续这个系列拆成以下几篇:

篇号 主题 涉及硬件
第一篇(本文) 五路循迹模块 TCRT5000 + LM393
第二篇 超声波测距与舵机云台 HC-SR04 + SG90,覆盖避障/贴边/魔术手三个模式
第三篇 红外双目跟随模块 红外避障对管
第四篇 霍尔编码器测速与直线行驶PID 霍尔传感器 + PID自整定
第五篇 环境感知模块 DHT11温湿度 + 火焰传感器 + 风扇
第六篇 蓝牙/串口双通道通信协议设计 HC-05/BT06
终篇 整车架构总览 串联前六篇

二、循迹模块硬件原理

        TCRT5000 接收管输出的是一个连续变化的模拟电压,电压高低取决于反射光的强弱

 2.1 红外发射+接收

①TCRT5000 反射式红外对管

        发射管持续发出红外光,如果前方是反光能力强的表面(比如白纸),大部分红外光会被反射回来被接收管捕获;如果前方是吸光能力强的表面(比如黑色胶带或黑漆),红外光被大量吸收,反射回来的光线很弱,接收管几乎收不到信号

不同颜色对红外光的反射率不同

表面颜色

红外反射

接收管收到的光

输出电压趋势

白色/浅色

强反射

收到大量红外

一种状态

黑色/深色

几乎吸收

收到极少红外

另一种状态

利用这个特性,五个 TCRT5000 在车头一字排开(最左-X5、左二-X4、正中-X3、右二-X2、最右-X1),同时扫过赛道,就能知道黑线相对车身的左右位置

②LM393 比较器

        LM393 的作用是比较两个输入电压的大小:当同相输入端(+) 电压高于反相输入端(-)电压时,比较器输出高电平。当同相输入端(+)电压低于反相输入端(-)电压时,比较器输出低电平

LM393 在循迹模块中的具体应用

        在循迹模块中,LM393 的一个输入端(通常是同相输入端)连接 TCRT5000 接收管的输出电压(随反射光强度变化),另一个输入端连接 电位器分压提供的参考电压(阈值)

比较器把这两个电压进行比较:

条件 输出状态
反射光强(白底),接收管导通,输入电压 低于 阈值 输出 低电平(DO=0)
反射光弱(黑线),接收管截止,输入电压 高于 阈值 输出 高电平(DO=1)

注意:不同厂家、不同批次的模块,输出电平与颜色的对应关系可能相反(有的模块黑线输出低电平,白底输出高电平)。本项目实测确认的是:黑线 = HIGH(1),白底 = LOW(0),这也是后面代码中使用的极性

③LM339 四路电压比较器

        本项目五路循迹模块实际上同时使用了 LM393 和 LM339 两种比较器——部分模块使用 LM393(双路),部分使用 LM339(四路),拼成五路检测通道

LM393 与 LM339 对比

对比项 LM393 LM339
比较器数量 2 路 4 路
引脚间距 2.54mm 2.54mm
单路功耗 ~0.6mA ~0.325mA(每路)
响应时间 ~1.3μs ~1.3μs
输入失调电压 ±1.0mV ±2.0mV

实际设计中采用 LM339(4 路)+ LM393(2 路)= 6 路比较器,刚好覆盖 5 路传感器,成本最优,PCB 布局也最紧凑

④电位器调节

        模块上那颗 蓝色方形可调3362电位器,本质上是 可调分压器——旋转旋钮改变分压比例,从而改变送入 LM393 比较器的参考电压(阈值)

调节效果

调节方向 阈值变化 效果
阈值 调低 参考电压降低 更容易判定为黑线(灵敏,但容易误判)
阈值 调高 参考电压升高 更难判定为黑线(不灵敏,抗干扰强)

实际调试方法

  • 模块正对白纸,悬空约 1~2cm
  • 慢慢旋转电位器,观察模块上那颗 状态指示灯(DO-LED)
  • 找到指示灯刚好从  变 (或从暗变亮)的临界点
  • 再往灵敏方向回调一点点(约 1/8 圈)
  • 用黑色遮挡物验证:压在黑线上时指示灯应熄灭(或点亮),移开应恢复

        每一路模块都要单独调节,因为五个模块的电位器是独立的,灵敏度不会自动一致

2.2 原理图讲解

①单路 TCRT5000 检测电路

典型的 TCRT5000 + LM393 循迹模块单路原理图

元件 作用
180Ω限流电阻 限制 TCRT5000 发射管电流,通常 100Ω ~ 200Ω
10KΩ分压电阻 与接收管内阻组成分压电路,决定接收管输出的电压变化范围
10KΩ上拉电阻 LM393 集电极开路输出需要上拉到 VCC,通常 10kΩ
VR1电位器 调节比较器参考阈值电压,通常 10kΩ 3296 型

LM393 和 LM339 都采用 集电极开路(Open-Collector)输出

  • 输出晶体管 导通 → 输出引脚被拉低到 GND(低电平)
  • 输出晶体管 截止 → 输出引脚 悬空,必须通过上拉电阻拉到 VCC(高电平)

三、硬件接线说明

3.1 循迹模块引脚定义

本项目五路循迹模块的引脚定义(与 pinsdefine.h 一致)

传感器位置 引脚号 说明
最左(TracePin1) 14 五路中最左侧
左二(TracePin2) 13 左侧第二路
中间(TracePin3) 12 正中间
右二(TracePin4) 17 右侧第二路
最右(TracePin5) 18 五路中最右侧

3.2 接线方案表

每个 TCRT5000 循迹模块通常有 4 个引脚:

信号线 模块端引脚 零知派迷你板引脚 代码常量 说明
VCC VCC 5V 五路模块共用一路5V供电
GND GND GND 五路模块共用地线
信号1 DO D14 TracePin1 最左路
信号2 DO D13 TracePin2 左二路
信号3 DO D12 TracePin3 正中路
信号4 DO D17 TracePin4 右二路
信号5 DO D18 TracePin5 最右路

3.3 连接示意图

四、独立测试程序

        下面这个程序只依赖五路循迹模块的信号线和串口,不需要接电机、舵机或其他任何传感器。引脚号与主项目 pinsdefine.h 完全一致

/**************************************************************************************
 * 文件: LineTracker_Standalone_Test.ino
 * 作者:零知实验室(深圳市在芯间科技有限公司)
 * 功能:5路TCRT5000循迹模块独立测试程序
 *       引脚定义与主项目 pinsdefine.h 完全一致,不修改任何引脚
 *       仅依赖串口,不需要电机、舵机等其他硬件
 *
 * 使用方法:
 *   1. 烧录本程序到零知派迷你板(STM32F103C8T6)
 *   2. 打开串口监视器,波特率 115200
 *   3. 把小车(或单独的循迹模块)放在白底黑线赛道上移动
 *   4. 观察串口输出的原始电平、加权质心偏差值
 *   5. 通过模块上的电位器旋钮调节灵敏度,同步观察数值变化
 **************************************************************************************/

// ── 引脚定义:与主项目 pinsdefine.h 完全一致 ──────────
#define TracePin1   14   // 最左
#define TracePin2   13
#define TracePin3   12   // 中间
#define TracePin4   17
#define TracePin5   18   // 最右

const uint8_t pins[5] = {TracePin1, TracePin2, TracePin3, TracePin4, TracePin5};
const int8_t  weight[5] = {-10, -5, 0, 5, 10};   // 与主项目算法保持一致的位置权重

void setup()
{
    Serial.begin(115200);
    delay(200);
    for (uint8_t i = 0; i < 5; i++) pinMode(pins[i], INPUT);

    Serial.println(F("===================================="));
    Serial.println(F(" TCRT5000 五路循迹模块 独立测试程序 "));
    Serial.println(F("===================================="));
    Serial.println(F("请将模块放在白底上,确认5路均为0"));
    Serial.println(F("再放在黑线上,确认对应路变为1"));
    Serial.println(F("如果方向相反请检查模块极性或接线顺序"));
    Serial.println(F(""));
}

void loop()
{
    byte b[5];
    for (uint8_t i = 0; i < 5; i++) b[i] = (byte)digitalRead(pins[i]);

    // ── 原始电平输出 ──────────────────────────────────────────
    Serial.print(F("RAW: "));
    for (uint8_t i = 0; i < 5; i++) {
        Serial.print(b[i]);
        if (i < 4) Serial.print('-');
    }

    // ── 加权质心计算(与主项目算法一致)──────────────────────
    int32_t weightSum = 0;
    uint8_t lineCount  = 0;
    for (uint8_t i = 0; i < 5; i++) {
        if (b[i] == 1) {   // 1 = 实测黑线电平(本项目极性)
            weightSum += weight[i];
            lineCount++;
        }
    }

    Serial.print(F("   压线数="));
    Serial.print(lineCount);

    if (lineCount == 0) {
        Serial.println(F("   状态=丢线(全白)"));
    } else {
        int dec = weightSum / (int)lineCount;
        Serial.print(F("   偏差值="));
        Serial.print(dec);

        if (dec < -5)       Serial.println(F("   状态=大幅左偏/直角左"));
        else if (dec < -2)  Serial.println(F("   状态=小幅左偏"));
        else if (dec > 5)   Serial.println(F("   状态=大幅右偏/直角右"));
        else if (dec > 2)   Serial.println(F("   状态=小幅右偏"));
        else                Serial.println(F("   状态=直行"));
    }

    delay(200);   // 测试阶段放慢刷新速度方便观察
}

五、调试流程演示

5.1 测试流程

①悬空测试

        模块抬高离开任何表面,五路应全部显示0

②单路压线测试

        用黑色胶带依次盖住每一路传感器,确认串口输出对应那一位从0变成1,其余保持0

  • 这一步验证接线顺序——如果压住最左边传感器,串口却显示最右边那一位变化,说明物理接线顺序和代码定义顺序搞反了

③电位器灵敏度调节

        固定模块在赛道黑线正上方,边压线边旋转电位器,观察对应位是否稳定为1,移到白色区域确认能稳定回到0

④整体过线测试

        让模块缓慢从赛道左侧移动到右侧,观察偏差值能否连续地从负值经过0变化到正值,过程应平滑无跳变

5.2 演示视频

零知派迷你板-5路循迹模块组装与测试

六、项目循迹功能实现

确认模块本身没问题之后,再看完整项目里循迹功能是怎么组织的。这部分代码分布在两个文件里:

文件 职责
sc_perception.h/.cpp 感知层:把硬件信号转换成偏差值
sc_behaviors.h/.cpp 行为层:把偏差值转换成电机控制指令

6.1 感知层加权质心算法

        sc_perception.hLineTracker 类的核心是五个固定权重

const int8_t _weight[5] = {-10, -5, 0, 5, 10};

getDecide() 的算法逻辑:只对压到黑线的传感器,把权重加起来再除以压线数量,得到加权平均位置作为偏差值

int32_t weightSum = 0;
uint8_t lineCount  = 0;
for (uint8_t i = 0; i < 5; i++) {
    if (b[i] == L) {
        weightSum += _weight[i];
        lineCount++;
    }
}
int dec = weightSum / (int)lineCount;

这个设计的优势:五路传感器无论压线的组合是哪几路、压了几路,公式永远能算出一个确定的结果,不存在某种组合没考虑到的遗漏问题。

场景 压线传感器 计算过程 偏差值
直行 仅中间 b[2] 0/1 0
轻微左偏 b[1]+b[2] (-5+0)/2 -2.5
轻微右偏 b[2]+b[3] (0+5)/2 2.5
中等右偏 仅 b[3] 5/1 5
最大右偏 仅 b[4] 10/1 10
十字路口 b[0..4]全压 (-10-5+0+5+10)/5 0

6.2 极性配置

setColors() 接口决定了代码怎么理解地面和线对应的实测电平值:

void setColors(byte groundColor, byte lineColor);

本项目实测黑线高电平、白底低电平,正确调用是:

g_tracker.setColors(BLACK, WHITE);

常见错误:如果把参数顺序写反成 setColors(WHITE, BLACK),黑线会被误判成地面,小车压在黑线上反而触发丢线逻辑,表现为小车在黑线和白底交界处来回乱动

6.3 丢线判断与防抽动消抖

        五路全部读到地面色才判定丢线,且要连续三次才确认,避免单次噪点误判触发不必要的后退动作

uint8_t _allOffCount = 0;
static const uint8_t ALL_OFF_CONFIRM = 3;

丢线状态机分三个阶段

enum SearchState : uint8_t { 
    NORMAL,        // 正常循迹
    SEARCH_BACK,   // 后退 300ms
    SEARCH_SPIN,   // 原地旋转找线(最多 10 秒)
    SEARCH_STOP    // 超时停车,等待人工介入
};

6.4 从偏差值到电机控制

        TrackBehavior::update() 拿到偏差值后分两种情况处理

①大角度(|decide|>=5)不走 PID,直接原地旋转

if (absD >= 5) {
    if (decide < 0) {
        g_motor.motorRun(-TURN_SPEED, TURN_SPEED);
    } else {
        g_motor.motorRun(TURN_SPEED, -TURN_SPEED);
    }
    return;
}

②小角度(|decide| < 5)用 PD 控制器

_input = (double)decide;
_pid.SetOutputLimits(-spd, spd);
_pid.Compute();
int lSpd = constrain(spd - (int)_output, -255, 255);
int rSpd = constrain(spd + (int)_output, -255, 255);
g_motor.motorRun(lSpd, rSpd);

        只用P和D两项,没用I,因为循迹偏差是瞬时测量值,不存在需要积分消除的固定偏置,积分项反而容易在连续弯道产生过冲震荡

自适应速度:偏差越大,基础速度越低

int spd = (absD <= 2) ? baseSpeed :
          (absD <= 3) ? baseSpeed * 85 / 100 :
                        baseSpeed * 70 / 100;

偏差越大基础速度越低,过弯更稳,直道全速

6.5 PID参数调试方法

        TrackBehavior 构造时传入的默认参数是 比例 Kp = 6.5,微分 Kd = 0.5

调试时可以通过蓝牙或串口发送命令实时热更新参数

命令 说明 示例
TKP=数值 设置比例项 TKP=8.0
TKD=数值 设置微分项 TKD=0.8
SPD=数值 设置基础速度 SPD=100

调试顺序

  • 微分项设为 0,只调比例项
  • 从较小值(如 3.0)开始逐步增大
  • 观察车身:太小则跟不住线,太大则来回摆动
  • 找到能跟住线但有轻微震荡的比例值
  • 逐步加大微分项(如 0.3 → 0.5 → 0.8),压制震荡
  • 微分项不能太大,否则转弯响应会变迟钝

七、常见问题解答(FAQ)

Q1:小车过弯时冲出赛道怎么办?

        A:请尝试,降低基础速度(SPD=60 或更低)、增大比例项 Kp(让转向更敏感)、适当增加微分项 Kd(抑制震荡)

Q2:串口读到的电平与预期相反怎么办?

        A:不同批次的 TCRT5000 模块输出极性可能相反。解决方法是修改 TrackBehavior::onEnter() 中的 setColors()

// 如果黑线=0,白底=1(与本文相反)
g_tracker.setColors(WHITE, BLACK);

Q3:检测距离太近或太远怎么调?

        A:调节电位器可以改变检测灵敏度。如果电位器调到极限仍不满足,可以调整 TCRT5000 的限流电阻(R1)来改变发射功率

资料整合

LM393 数据手册(TI):         lm393.pdf

LM339 数据手册(TI):         lm339.pdf

LM339/LM393 应用电路:        差分比较器应用电路

循迹这类依赖传感器输入的功能,调试时最容易出问题的往往不是算法本身的数学正确性,而是中间的“翻译层”——电平极性怎么对应到地面和线的语义、偏差值的符号怎么对应到电机的转向

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐