零知派——STM32智能小车系列教程(一):TCRT5000 五路循迹模块原理与调试
引言:智能小车要实现的功能很多——避障、跟随、贴边、灭火、直线行驶,每一个模块单独调试起来都有自己的难点。如果一开始就把所有模块塞进一个工程里联调,出了问题很难分清到底是传感器硬件的问题、电平极性的问题,还是上层控制算法的问题所以这一系列教程的思路是:先把每个功能模块单独拎出来,配一个不依赖其他硬件的最小测试程序,把模块本身调通调透,再回头看它在完整项目里是怎么被使用的目录
一、系列教程前言
二、循迹模块硬件原理
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 反射式红外对管
发射管持续发出红外光,如果前方是反光能力强的表面(比如白纸),大部分红外光会被反射回来被接收管捕获;如果前方是吸光能力强的表面(比如黑色胶带或黑漆),红外光被大量吸收,反射回来的光线很弱,接收管几乎收不到信号
不同颜色对红外光的反射率不同:
表面颜色
红外反射
接收管收到的光
输出电压趋势
白色/浅色
强反射
收到大量红外
一种状态
黑色/深色
几乎吸收
收到极少红外
另一种状态
https://file1.elecfans.com/web3/M00/67/4E/wKgZPGo8-iOAaH5wAAKp8IoRpxk672.png利用这个特性,五个 TCRT5000 在车头一字排开(最左-X5、左二-X4、正中-X3、右二-X2、最右-X1),同时扫过赛道,就能知道黑线相对车身的左右位置
②LM393 比较器
LM393 的作用是比较两个输入电压的大小:当同相输入端(+) 电压高于反相输入端(-)电压时,比较器输出高电平。当同相输入端(+)电压低于反相输入端(-)电压时,比较器输出低电平
https://file1.elecfans.com/web3/M00/67/4E/wKgZPGo8-jmAABhHAABDfmkowMA429.png
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 循迹模块单路原理图
https://file1.elecfans.com/web3/M00/67/4E/wKgZO2o8-kaAKZN1AAE_Gc3XmBw203.png
元件
作用
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 连接示意图https://file1.elecfans.com/web3/M00/67/4E/wKgZO2o8-k-AWjZcAAdS5-4_MxI209.png
四、独立测试程序下面这个程序只依赖五路循迹模块的信号线和串口,不需要接电机、舵机或其他任何传感器。引脚号与主项目 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 = {TracePin1, TracePin2, TracePin3, TracePin4, TracePin5};
const int8_tweight = {-10, -5, 0, 5, 10}; // 与主项目算法保持一致的位置权重
void setup()
{
Serial.begin(115200);
delay(200);
for (uint8_t i = 0; i < 5; i++) pinMode(pins, 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;
for (uint8_t i = 0; i < 5; i++) b = (byte)digitalRead(pins);
// ── 原始电平输出 ──────────────────────────────────────────
Serial.print(F("RAW: "));
for (uint8_t i = 0; i < 5; i++) {
Serial.print(b);
if (i < 4) Serial.print('-');
}
// ── 加权质心计算(与主项目算法一致)──────────────────────
int32_t weightSum = 0;
uint8_t lineCount= 0;
for (uint8_t i = 0; i < 5; i++) {
if (b == 1) { // 1 = 实测黑线电平(本项目极性)
weightSum += weight;
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
https://file1.elecfans.com/web3/M00/67/4E/wKgZPGo8-mCAbGJrABK_Svzh4hA743.png②单路压线测试
用黑色胶带依次盖住每一路传感器,确认串口输出对应那一位从0变成1,其余保持0
https://file1.elecfans.com/web3/M00/67/4E/wKgZPGo8-miAO8r3ACnx8RgGxEA647.png
[*]这一步验证接线顺序——如果压住最左边传感器,串口却显示最右边那一位变化,说明物理接线顺序和代码定义顺序搞反了
③电位器灵敏度调节
固定模块在赛道黑线正上方,边压线边旋转电位器,观察对应位是否稳定为1,移到白色区域确认能稳定回到0
https://file1.elecfans.com/web3/M00/67/4E/wKgZPGo8-n6AWvCfAAZPmhMGVSs073.png④整体过线测试
让模块缓慢从赛道左侧移动到右侧,观察偏差值能否连续地从负值经过0变化到正值,过程应平滑无跳变
5.2 演示视频零知派迷你板-5路循迹模块组装与测试:
https://live.csdn.net/v/531702?spm=1001.2014.3001.5501
六、项目循迹功能实现确认模块本身没问题之后,再看完整项目里循迹功能是怎么组织的。这部分代码分布在两个文件里:
文件
职责
sc_perception.h/.cpp
感知层:把硬件信号转换成偏差值
sc_behaviors.h/.cpp
行为层:把偏差值转换成电机控制指令
6.1 感知层加权质心算法sc_perception.h 里 LineTracker 类的核心是五个固定权重
const int8_t _weight = {-10, -5, 0, 5, 10};getDecide() 的算法逻辑:只对压到黑线的传感器,把权重加起来再除以压线数量,得到加权平均位置作为偏差值
int32_t weightSum = 0;
uint8_t lineCount= 0;
for (uint8_t i = 0; i < 5; i++) {
if (b == L) {
weightSum += _weight;
lineCount++;
}
}
int dec = weightSum / (int)lineCount;
这个设计的优势:五路传感器无论压线的组合是哪几路、压了几路,公式永远能算出一个确定的结果,不存在某种组合没考虑到的遗漏问题。
场景
压线传感器
计算过程
偏差值
直行
仅中间 b
0/1
0
轻微左偏
b+b
(-5+0)/2
-2.5
轻微右偏
b+b
(0+5)/2
2.5
中等右偏
仅 b
5/1
5
最大右偏
仅 b
10/1
10
十字路口
b全压
(-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()
<blockquote><p>// 如果黑线=0,白底=1(与本文相反)</p>Q3:检测距离太近或太远怎么调?A:调节电位器可以改变检测灵敏度。如果电位器调到极限仍不满足,可以调整 TCRT5000 的限流电阻(R1)来改变发射功率
资料整合
LM393 数据手册(TI): lm393.pdf
LM339 数据手册(TI): lm339.pdf
LM339/LM393 应用电路: 差分比较器应用电路
循迹这类依赖传感器输入的功能,调试时最容易出问题的往往不是算法本身的数学正确性,而是中间的**“翻译层”——电平极性**怎么对应到地面和线的语义、偏差值的符号怎么对应到电机的转向
页:
[1]