零知派——STM32智能小车系列教程(一):TCRT5000 五路循迹模块原理与调试
引言:智能小车要实现的功能很多——避障、跟随、贴边、灭火、直线行驶,每一个模块单独调试起来都有自己的难点。如果一开始就把所有模块塞进一个工程里联调,出了问题很难分清到底是传感器硬件的问题、电平极性的问题,还是上层控制算法的问题
所以这一系列教程的思路是:先把每个功能模块单独拎出来,配一个不依赖其他硬件的最小测试程序,把模块本身调通调透,再回头看它在完整项目里是怎么被使用的
目录
一、系列教程前言
这是系列的第一篇,专门讲五路循迹模块——也是实际调试过程中踩坑最多、最容易出现“看起来能用但逻辑是错的”这种隐蔽问题的一个模块。整车项目里循迹功能遇到过三次崩溃:
| 次数 | 问题 | 根因 | 表现 |
|---|---|---|---|
| 第一次 | 精确匹配表遗漏传感器组合 | 五路传感器有 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.h里LineTracker类的核心是五个固定权重
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 应用电路: 差分比较器应用电路
循迹这类依赖传感器输入的功能,调试时最容易出问题的往往不是算法本身的数学正确性,而是中间的“翻译层”——电平极性怎么对应到地面和线的语义、偏差值的符号怎么对应到电机的转向
更多推荐

所有评论(0)